diff --git a/docs/usage.md b/docs/usage.md
index cf8789b..81bcd72 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -66,12 +66,16 @@ result = graph.process(data, fields)
print(result) # Processed graph data ready for visualization
```
-For an X-axis or Y-axis range slider on a line or bar graph, set
-`x_range="slider"` or `y_range="slider"`. Both may be used together. The
-processed result includes `xAxisOpts` and/or `yAxisOpts` with `mode: 'slider'`.
-Line graphs also use `yAxisOpts` for the default, `auto`, and `full` Y-axis
-modes. For `auto`, `valueOpts` contains the calculated minimum and maximum
-values.
+For an X-axis range slider on a line or bar graph, set `x_range="slider"`.
+The Y-axis initial range and zoom control can be configured independently with
+`y_range="auto|full"` and `y_zoom="none|slider"`. For example,
+`y_range="auto" y_zoom="slider"` starts at the data range and lets the user
+adjust it. The processed `yAxisOpts` keeps the range in `mode` and exposes the
+control as `zoom: 'slider'`. For `auto`, `valueOpts` contains the calculated
+minimum and maximum values. The legacy `y_range="slider"` syntax remains
+supported and returns `mode: 'slider'`. An explicit `y_zoom="none"` is returned
+as `zoom: 'none'`; when `y_zoom` is omitted, `zoom` is omitted too for backward
+compatibility.
Gantt graphs use one X field for the task name and two Y fields with `start`
and `end` roles. An optional `label` on either Y field groups intervals:
@@ -340,4 +344,4 @@ except Exception as e:
- Check out [Examples](examples.md) for more practical use cases
- Explore the [API Reference](api-reference.md) for detailed method documentation
-- Learn about [Advanced Usage](advanced-usage.md) for complex scenarios
\ No newline at end of file
+- Learn about [Advanced Usage](advanced-usage.md) for complex scenarios
diff --git a/ooui/graph/base.py b/ooui/graph/base.py
index d461eaf..3da0224 100644
--- a/ooui/graph/base.py
+++ b/ooui/graph/base.py
@@ -10,6 +10,8 @@ def __init__(self, element):
self._timerange = element.get('timerange', None)
self._x_range = element.get('x_range', 'default')
self._y_range = element.get('y_range', "default")
+ self._y_zoom = element.get('y_zoom', 'none')
+ self._y_zoom_explicit = element.get('y_zoom') is not None
interval = element.get('interval', None)
self._interval = int(interval) if interval is not None else 1
@@ -32,6 +34,14 @@ def interval(self):
def y_range(self):
return self._y_range
+ @property
+ def y_zoom(self):
+ return self._y_zoom
+
+ @property
+ def y_zoom_explicit(self):
+ return self._y_zoom_explicit
+
@property
def x_range(self):
return self._x_range
diff --git a/ooui/graph/chart.py b/ooui/graph/chart.py
index 8c85172..7babf3a 100644
--- a/ooui/graph/chart.py
+++ b/ooui/graph/chart.py
@@ -183,10 +183,15 @@ def process(self, values, fields, options=None):
}
if ((self.type == 'line' and self.y_range)
- or (self.type == 'bar' and self.y_range == 'slider')):
+ or (self.type == 'bar' and (
+ self.y_range in ('auto', 'full', 'slider')
+ or self.y_zoom == 'slider'
+ ))):
y_axis_opts = {'mode': self.y_range}
if self.y_range == 'auto' and final_data:
y_axis_opts['valueOpts'] = get_min_max(final_data)
+ if self.y_zoom_explicit:
+ y_axis_opts['zoom'] = self.y_zoom
result['yAxisOpts'] = y_axis_opts
if self.type in ('line', 'bar') and self.x_range == 'slider':
diff --git a/spec/graph/graph_spec.py b/spec/graph/graph_spec.py
index c3c5dcf..ff993d4 100644
--- a/spec/graph/graph_spec.py
+++ b/spec/graph/graph_spec.py
@@ -162,6 +162,17 @@
expect(graph.y[0].operator).to(equal('+'))
expect(graph.y_range).to(equal('auto'))
+ with it("should parse an independent y-axis zoom mode"):
+ xml = """
+
+
+
+
+ """
+ graph = parse_graph(xml)
+ expect(graph.y_range).to(equal('full'))
+ expect(graph.y_zoom).to(equal('slider'))
+
with description("Processing a Graph"):
with description("A line graph with y_range auto"):
with it("should return yAxisOpts to the result with min and max values"):
diff --git a/spec/graph/slider_spec.py b/spec/graph/slider_spec.py
index 763cd98..868b653 100644
--- a/spec/graph/slider_spec.py
+++ b/spec/graph/slider_spec.py
@@ -22,6 +22,38 @@
expect(result).not_to(have_key('xAxisOpts'))
expect(result).not_to(have_key('yAxisProps'))
+ with it('returns independent Y range and zoom metadata'):
+ fields = {'name': {'type': 'char'}, 'amount': {'type': 'integer'}}
+ values = [{'name': 'January', 'amount': 10}]
+ for graph_type in ('line', 'bar'):
+ xml = (''
+ ''
+ ''
+ '').format(graph_type)
+ result = parse_graph(xml).process(values, fields)
+ expect(result['yAxisOpts']).to(equal({
+ 'mode': 'auto',
+ 'zoom': 'slider',
+ 'valueOpts': {'min': 10, 'max': 10},
+ }))
+
+ with it('returns an explicit Y range for bars without enabling zoom'):
+ fields = {'name': {'type': 'char'}, 'amount': {'type': 'integer'}}
+ values = [
+ {'name': 'January', 'amount': 10},
+ {'name': 'February', 'amount': 20},
+ ]
+ for y_range in ('auto', 'full'):
+ xml = (''
+ ''
+ ''
+ '').format(y_range)
+ result = parse_graph(xml).process(values, fields)
+ expected = {'mode': y_range, 'zoom': 'none'}
+ if y_range == 'auto':
+ expected['valueOpts'] = {'min': 9, 'max': 21}
+ expect(result['yAxisOpts']).to(equal(expected))
+
with it('returns slider metadata for line and bar under one key'):
fields = {'name': {'type': 'char'}, 'amount': {'type': 'integer', 'string': 'Amount'}}
values = [{'name': 'January', 'amount': 10}]
@@ -35,6 +67,20 @@
expect(result).not_to(have_key('xAxisOpts'))
expect(result).not_to(have_key('yAxisProps'))
+ with it('preserves an explicitly disabled Y zoom with a legacy range'):
+ fields = {'name': {'type': 'char'}, 'amount': {'type': 'integer'}}
+ values = [{'name': 'January', 'amount': 10}]
+ for graph_type in ('line', 'bar'):
+ xml = (''
+ ''
+ ''
+ '').format(graph_type)
+ result = parse_graph(xml).process(values, fields)
+ expect(result['yAxisOpts']).to(equal({
+ 'mode': 'slider',
+ 'zoom': 'none',
+ }))
+
with it('returns an X-axis slider for line and bar independently of Y'):
fields = {'name': {'type': 'char'}, 'amount': {'type': 'integer'}}
values = [{'name': 'January', 'amount': 10}]