Skip to content

Commit

Permalink
Merge pull request #88 from Carifio24/log-axes
Browse files Browse the repository at this point in the history
Log axes for viewers
  • Loading branch information
Carifio24 authored Aug 26, 2024
2 parents eea3f12 + 70d567c commit 6eaaf06
Show file tree
Hide file tree
Showing 10 changed files with 170 additions and 7 deletions.
32 changes: 32 additions & 0 deletions glue_plotly/viewers/common/tests/base_viewer_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,35 @@ def test_unique_class(self):
def test_config(self):
config = self.viewer.figure._config
assert config["displayModeBar"] is False

def test_log_x_axis(self):
assert self.viewer.axis_x.type == "linear"
self.viewer.state.x_log = True
assert self.viewer.axis_x.type == "log"
self.viewer.state.x_log = False
assert self.viewer.axis_x.type == "linear"

def test_log_y_axis(self):
assert self.viewer.axis_y.type == "linear"
self.viewer.state.y_log = True
assert self.viewer.axis_y.type == "log"
self.viewer.state.y_log = False
assert self.viewer.axis_y.type == "linear"

def test_set_x_axis_bounds(self):
self.viewer.state.x_min = 1
self.viewer.state.x_max = 25
assert self.viewer.axis_x['range'] == (1, 25)
self.viewer.state.x_log = True
self.viewer.state.x_min = 10
self.viewer.state.x_max = 1000
assert self.viewer.axis_x['range'] == (1.0, 3.0)

def test_set_y_axis_bounds(self):
self.viewer.state.y_min = 1
self.viewer.state.y_max = 25
assert self.viewer.axis_y['range'] == (1, 25)
self.viewer.state.y_log = True
self.viewer.state.y_min = 10
self.viewer.state.y_max = 1000
assert self.viewer.axis_y['range'] == (1.0, 3.0)
2 changes: 2 additions & 0 deletions glue_plotly/viewers/common/tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ class ExampleState(ViewerState):
x_max = CallbackProperty(1)
y_min = CallbackProperty(0)
y_max = CallbackProperty(1)
x_log = CallbackProperty(False)
y_log = CallbackProperty(False)
show_axes = CallbackProperty(True)

def reset_limits(self):
Expand Down
38 changes: 36 additions & 2 deletions glue_plotly/viewers/common/viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

from echo import delay_callback
from glue.core.command import ApplySubsetState
from glue.utils import avoid_circular
from numpy import log10

import plotly.graph_objects as go

Expand Down Expand Up @@ -48,12 +50,16 @@ def __init__(self, session, state=None):
visible=False)
self.figure.add_trace(selection_layer)

# Note that we need the log updates to be high priority for the histogram viewer
# so that the axes are updated before the limits are reset
self.state.add_callback('x_axislabel', self.update_x_axislabel)
self.state.add_callback('y_axislabel', self.update_y_axislabel)
self.state.add_callback('x_min', self._update_plotly_x_limits)
self.state.add_callback('x_max', self._update_plotly_x_limits)
self.state.add_callback('x_log', self._update_x_log, priority=10000)
self.state.add_callback('y_min', self._update_plotly_y_limits)
self.state.add_callback('y_max', self._update_plotly_y_limits)
self.state.add_callback('y_log', self._update_y_log, priority=10000)
self.state.add_callback('show_axes', self._update_axes_visible)

self.axis_x.on_change(lambda _obj, x_range: self._set_x_state_bounds(x_range), 'range')
Expand Down Expand Up @@ -100,6 +106,14 @@ def update_x_axislabel(self, label):
def update_y_axislabel(self, label):
self.axis_y['title'].update(text=label)

def _update_x_log(self, log):
axis_type = 'log' if log else 'linear'
self.figure.update_xaxes(type=axis_type, range=self._x_axis_range_from_state())

def _update_y_log(self, log):
axis_type = 'log' if log else 'linear'
self.figure.update_yaxes(type=axis_type, range=self._y_axis_range_from_state())

def _update_selection_layer_bounds(self):
x0 = 0.5 * (self.state.x_min + self.state.x_max)
dx = self.state.x_max - self.state.x_min
Expand All @@ -115,28 +129,48 @@ def set_selection_active(self, visible):
def set_selection_callback(self, on_selection):
self.selection_layer.on_selection(on_selection)

def _x_axis_range_from_state(self):
x_range = [self.state.x_min, self.state.x_max]
if self.state.x_log:
x_range = tuple(log10([float(x) for x in x_range]))
return x_range

def _y_axis_range_from_state(self):
y_range = [self.state.y_min, self.state.y_max]
if self.state.y_log:
y_range = tuple(log10([float(y) for y in y_range]))
return y_range

@avoid_circular
def _update_plotly_x_limits(self, *args):
with self.figure.batch_update():
if self.state.x_min is not None and self.state.x_max is not None:
self.axis_x['range'] = [self.state.x_min, self.state.x_max]
self.axis_x['range'] = self._x_axis_range_from_state()

@avoid_circular
def _update_plotly_y_limits(self, *args):
with self.figure.batch_update():
if self.state.y_min is not None and self.state.y_max is not None:
self.axis_y['range'] = [self.state.y_min, self.state.y_max]
self.axis_y['range'] = self._y_axis_range_from_state()

def _update_axes_visible(self, *args):
with self.figure.batch_update():
self.axis_x.visible = self.state.show_axes
self.axis_y.visible = self.state.show_axes

@avoid_circular
def _set_x_state_bounds(self, x_range):
with delay_callback(self.state, 'x_min', 'x_max'):
if self.state.x_log:
x_range = tuple(pow(10, x) for x in x_range)
self.state.x_min = x_range[0]
self.state.x_max = x_range[1]

@avoid_circular
def _set_y_state_bounds(self, y_range):
with delay_callback(self.state, 'y_min', 'y_max'):
if self.state.y_log:
y_range = tuple(pow(10, y) for y in y_range)
self.state.y_min = y_range[0]
self.state.y_max = y_range[1]

Expand Down
4 changes: 2 additions & 2 deletions glue_plotly/viewers/histogram/viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

from glue_jupyter.registries import viewer_registry
from glue_jupyter.common.state_widgets.layer_histogram import HistogramLayerStateWidget
from glue_jupyter.common.state_widgets.viewer_histogram import HistogramViewerStateWidget
from glue_plotly.viewers.histogram.state import PlotlyHistogramViewerState
from glue_plotly.viewers.histogram.viewer_state_widget import PlotlyHistogramViewerStateWidget


__all__ = ["PlotlyHistogramView"]
Expand All @@ -23,7 +23,7 @@ class PlotlyHistogramView(PlotlyBaseView):
allow_duplicate_subset = False

_state_cls = PlotlyHistogramViewerState
_options_cls = HistogramViewerStateWidget
_options_cls = PlotlyHistogramViewerStateWidget
_data_artist_cls = PlotlyHistogramLayerArtist
_subset_artist_cls = PlotlyHistogramLayerArtist
_layer_style_widget_cls = HistogramLayerStateWidget
Expand Down
6 changes: 6 additions & 0 deletions glue_plotly/viewers/histogram/viewer_state_widget.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from glue_jupyter.common.state_widgets.viewer_histogram import HistogramViewerStateWidget


class PlotlyHistogramViewerStateWidget(HistogramViewerStateWidget):

template_file = (__file__, "viewer_state_widget.vue")
53 changes: 53 additions & 0 deletions glue_plotly/viewers/histogram/viewer_state_widget.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<template>
<div>
<div>
<v-select :items="x_att_items" label="x axis" v-model="x_att_selected"/>
</div>
<div>
<v-btn-toggle dense multiple :value="modeSet" @change="modeSetChange">

<v-tooltip bottom>
<template v-slot:activator="{ on }">
<v-btn v-on="on" small value="normalize">
<v-icon>unfold_more</v-icon>
</v-btn>
</template>
<span>normalize</span>
</v-tooltip>

<v-tooltip bottom>
<template v-slot:activator="{ on }">
<v-btn v-on="on" small value="cumulative">
<v-icon>trending_up</v-icon>
</v-btn>
</template>
<span>cumulative</span>
</v-tooltip>
</v-btn-toggle>
</div>
<div>
<v-subheader class="pl-0 slider-label">x log</v-subheader>
<v-switch v-model="glue_state.x_log" hide-details style="margin-top: 0"/>
</div>
<div>
<v-subheader class="pl-0 slider-label">y log</v-subheader>
<v-switch v-model="glue_state.y_log" hide-details style="margin-top: 0"/>
</div>
<v-switch v-model="glue_state.show_axes" label="Show axes" hide-details/>
</div>
</template>
<script>
module.exports = {
computed: {
modeSet() {
return [this.glue_state.normalize && 'normalize', this.glue_state.cumulative && 'cumulative']
}
},
methods: {
modeSetChange(v) {
this.glue_state.normalize = v.includes('normalize');
this.glue_state.cumulative = v.includes('cumulative');
}
}
}
</script>
4 changes: 2 additions & 2 deletions glue_plotly/viewers/scatter/viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

from glue_plotly.common.scatter2d import polar_layout_config, radial_axis, rectilinear_layout_config

from glue_jupyter.common.state_widgets.viewer_scatter import ScatterViewerStateWidget
from glue_jupyter.common.state_widgets.layer_scatter import ScatterLayerStateWidget
from glue_jupyter.registries import viewer_registry

from .layer_artist import PlotlyScatterLayerArtist
from .viewer_state_widget import PlotlyScatterViewerStateWidget
from glue_plotly.viewers import PlotlyBaseView


Expand All @@ -30,7 +30,7 @@ class PlotlyScatterView(PlotlyBaseView):
large_data_size = 1e7

_state_cls = ScatterViewerState
_options_cls = ScatterViewerStateWidget
_options_cls = PlotlyScatterViewerStateWidget
_data_artist_cls = PlotlyScatterLayerArtist
_subset_artist_cls = PlotlyScatterLayerArtist
_layer_style_widget_cls = ScatterLayerStateWidget
Expand Down
6 changes: 6 additions & 0 deletions glue_plotly/viewers/scatter/viewer_state_widget.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from glue_jupyter.common.state_widgets.viewer_scatter import ScatterViewerStateWidget


class PlotlyScatterViewerStateWidget(ScatterViewerStateWidget):

template_file = (__file__, "viewer_state_widget.vue")
30 changes: 30 additions & 0 deletions glue_plotly/viewers/scatter/viewer_state_widget.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<template>
<div class="glue-viewer-scatter-plotly">
<div>
<v-select label="x axis" :items="x_att_items" v-model="x_att_selected" hide-details />
</div>
<div>
<v-select label="y axis" :items="y_att_items" v-model="y_att_selected" hide-details />
</div>
<div>
<v-subheader class="pl-0 slider-label">x log</v-subheader>
<v-switch v-model="glue_state.x_log" hide-details style="margin-top: 0"/>
</div>
<div>
<v-subheader class="pl-0 slider-label">y log</v-subheader>
<v-switch v-model="glue_state.y_log" hide-details style="margin-top: 0"/>
</div>
<div>
<v-subheader class="pl-0 slider-label">show axes</v-subheader>
<v-switch v-model="glue_state.show_axes" hide-details style="margin-top: 0"/>
</div>
</div>
</template>

<style id="viewer_image">
.glue-viewer-scatter-plotly .v-subheader.slider-label {
font-size: 12px;
height: 16px;
margin-top: 6px;
}
</style>
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jupyter =
ipyfilechooser

[options.package_data]
* = *.png, *.svg, *.ui
* = *.png, *.svg, *.ui, *.vue

[options.entry_points]
glue.plugins =
Expand Down

0 comments on commit 6eaaf06

Please sign in to comment.