diff --git a/doc/PlotlyViewerExample.ipynb b/doc/PlotlyViewerExample.ipynb index ccbc943..c0fe49c 100644 --- a/doc/PlotlyViewerExample.ipynb +++ b/doc/PlotlyViewerExample.ipynb @@ -161,6 +161,25 @@ "# viewer = app.new_data_viewer(PlotlyHistogramView, data=data)" ] }, + { + "cell_type": "markdown", + "id": "26be6106-c61a-4d84-82e3-136ccfcee588", + "metadata": {}, + "source": [ + "You can adjust whether there are gaps between the bars and, if so, what fraction of the plot they occupy" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "821b6397-80e2-443c-b6c3-50bf954abbf2", + "metadata": {}, + "outputs": [], + "source": [ + "histogram_viewer.state.gaps = True\n", + "histogram_viewer.state.gap_fraction = 0.15" + ] + }, { "cell_type": "markdown", "id": "588347a7-8f4d-4f47-9838-a4a2e4106b02", diff --git a/glue_plotly/viewers/common/viewer.py b/glue_plotly/viewers/common/viewer.py index 5fdca6c..b428968 100644 --- a/glue_plotly/viewers/common/viewer.py +++ b/glue_plotly/viewers/common/viewer.py @@ -96,10 +96,10 @@ def axis_y(self): return self.figure.layout.yaxis def update_x_axislabel(self, label): - self.axis_x['title'] = label + self.axis_x['title'].update(text=label) def update_y_axislabel(self, label): - self.axis_y['title'] = label + self.axis_y['title'].update(text=label) def _update_selection_layer_bounds(self): x0 = 0.5 * (self.state.x_min + self.state.x_max) diff --git a/glue_plotly/viewers/histogram/state.py b/glue_plotly/viewers/histogram/state.py new file mode 100644 index 0000000..e308c68 --- /dev/null +++ b/glue_plotly/viewers/histogram/state.py @@ -0,0 +1,17 @@ +from glue.viewers.histogram.state import DDCProperty, HistogramViewerState + + +__all__ = ["PlotlyHistogramViewerState"] + + +class PlotlyHistogramViewerState(HistogramViewerState): + + gaps = DDCProperty(False, docstring='Whether to include gaps between histogram bars') + gap_fraction = DDCProperty(0.15, + docstring='The gap fraction if using gaps. For example, ' + '0 means that no gap between bars, 0.5 means ' + 'that the bars and gaps are evenly sized, and 1 ' + 'means that the entire bar mark is gap.') + + def __init__(self, **kwargs): + super().__init__(**kwargs) diff --git a/glue_plotly/viewers/histogram/tests/__init__.py b/glue_plotly/viewers/histogram/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/glue_plotly/viewers/histogram/tests/test_viewer.py b/glue_plotly/viewers/histogram/tests/test_viewer.py new file mode 100644 index 0000000..dc656e3 --- /dev/null +++ b/glue_plotly/viewers/histogram/tests/test_viewer.py @@ -0,0 +1,77 @@ +from glue.core import Data +from glue_jupyter import JupyterApplication +from plotly.graph_objects import Bar + +from glue_plotly.common import DEFAULT_FONT +from glue_plotly.viewers.histogram import PlotlyHistogramView + + +class TestHistogramViewer: + + def setup_method(self, method): + self.data = Data(label="histogram", x=[1, 1, 1, 2, 2, 3, 3, 3, 4, 6, 6]) + self.app = JupyterApplication() + self.app.session.data_collection.append(self.data) + self.viewer = self.app.new_data_viewer(PlotlyHistogramView) + self.viewer.add_data(self.data) + + viewer_state = self.viewer.state + viewer_state.x_min = 0.5 + viewer_state.hist_x_min = 0.5 + viewer_state.x_max = 6.5 + viewer_state.hist_x_max = 6.5 + viewer_state.hist_n_bin = 6 + viewer_state.y_min = 0 + viewer_state.y_max = 5 + viewer_state.x_axislabel = 'X Axis' + viewer_state.y_axislabel = 'Y Axis' + viewer_state.normalize = False + + self.layer = self.viewer.layers[0] + self.layer.state.color = "#abcdef" + self.layer.state.alpha = 0.75 + + def teardown_method(self, method): + self.viewer = None + self.app = None + + def test_basic(self): + assert len(self.viewer.layers) == 1 + traces = list(self.layer.traces()) + assert len(traces) == 1 + bars = traces[0] + assert isinstance(bars, Bar) + assert bars.marker.color == "#abcdef" + assert bars.marker.opacity == 0.75 + assert bars.x == tuple(range(1, 7)) + expected_y = [3, 2, 3, 1, 0, 2] + assert all(a == b for a, b in zip(bars.y, expected_y)) + + def test_axes(self): + x_axis = self.viewer.figure.layout.xaxis + y_axis = self.viewer.figure.layout.yaxis + + assert x_axis.title.text == 'X Axis' + assert y_axis.title.text == 'Y Axis' + assert x_axis.type == 'linear' + assert y_axis.type == 'linear' + + assert x_axis.range == (0.5, 6.5) + assert y_axis.range == (0, 5) + + assert all(f.family == DEFAULT_FONT for f in + (x_axis.title.font, x_axis.tickfont, y_axis.title.font, y_axis.tickfont)) + + common_items = dict(showgrid=False, showline=False, mirror=True, rangemode='normal', + zeroline=False, showspikes=False, showticklabels=True) + for axis in x_axis, y_axis: + assert all(axis[key] == value for key, value in common_items.items()) + + def test_gaps(self): + assert self.viewer.figure.layout.bargap == 0 + self.viewer.state.gaps = True + assert self.viewer.figure.layout.bargap == 0.15 + self.viewer.state.gap_fraction = 0.36 + assert self.viewer.figure.layout.bargap == 0.36 + self.viewer.state.gaps = False + assert self.viewer.figure.layout.bargap == 0 diff --git a/glue_plotly/viewers/histogram/viewer.py b/glue_plotly/viewers/histogram/viewer.py index 075be2d..c90bf4b 100644 --- a/glue_plotly/viewers/histogram/viewer.py +++ b/glue_plotly/viewers/histogram/viewer.py @@ -1,5 +1,4 @@ from glue.core.subset import XRangeROI, roi_to_subset_state -from glue.viewers.histogram.state import HistogramViewerState from glue_plotly.common import base_layout_config, base_rectilinear_axis from glue_plotly.viewers import PlotlyBaseView from glue_plotly.viewers.histogram.layer_artist import PlotlyHistogramLayerArtist @@ -7,6 +6,7 @@ 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 __all__ = ["PlotlyHistogramView"] @@ -22,7 +22,7 @@ class PlotlyHistogramView(PlotlyBaseView): allow_duplicate_data = False allow_duplicate_subset = False - _state_cls = HistogramViewerState + _state_cls = PlotlyHistogramViewerState _options_cls = HistogramViewerStateWidget _data_artist_cls = PlotlyHistogramLayerArtist _subset_artist_cls = PlotlyHistogramLayerArtist @@ -32,6 +32,8 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.state.add_callback('x_att', self._update_axes) self.state.add_callback('normalize', self._update_axes) + self.state.add_callback('gaps', self._gaps_changed) + self.state.add_callback('gap_fraction', self._gaps_changed) self._update_axes() def _create_layout_config(self): @@ -54,6 +56,10 @@ def _update_axes(self, *args): else: self.state.y_axislabel = 'Number' + def _gaps_changed(self, *args): + gap = self.state.gap_fraction if self.state.gaps else 0 + self.figure.layout.update(bargap=gap) + def _roi_to_subset_state(self, roi): return roi_to_subset_state(roi, x_att=self.state.x_att)