Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
- Learn about [Advanced Usage](advanced-usage.md) for complex scenarios
10 changes: 10 additions & 0 deletions ooui/graph/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
7 changes: 6 additions & 1 deletion ooui/graph/chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -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':
Expand Down
11 changes: 11 additions & 0 deletions spec/graph/graph_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = """<?xml version="1.0"?>
<graph type="line" y_range="full" y_zoom="slider">
<field name="data_alta" axis="x"/>
<field name="data_alta" operator="+" axis="y"/>
</graph>
"""
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"):
Expand Down
46 changes: 46 additions & 0 deletions spec/graph/slider_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ('<graph type="{}" y_range="auto" y_zoom="slider">'
'<field name="name" axis="x"/>'
'<field name="amount" axis="y" operator="+"/>'
'</graph>').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 = ('<graph type="bar" y_range="{}" y_zoom="none">'
'<field name="name" axis="x"/>'
'<field name="amount" axis="y" operator="+"/>'
'</graph>').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}]
Expand All @@ -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 = ('<graph type="{}" y_range="slider" y_zoom="none">'
'<field name="name" axis="x"/>'
'<field name="amount" axis="y" operator="+"/>'
'</graph>').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}]
Expand Down
Loading