Skip to content

Commit 21786f4

Browse files
teunbrandclaude
andauthored
Lines and Segments (#159)
* Implement segments * docs for segment * enable hline/vline * Add RenderContext to provide renderers with read-access to scales Introduce RenderContext struct to pass scale information to renderers during layer preparation. This allows renderers to access resolved scale properties (such as input ranges/extents) when preparing data or building encoding/spec modifications. Changes: - Add RenderContext struct with scales field and helper methods: - new() - constructor - find_scale() - lookup scale by aesthetic name - get_extent() - extract numeric min/max from continuous scales - Update GeomRenderer trait signatures to accept RenderContext: - prepare_data() now takes context parameter - modify_encoding() now takes context parameter - modify_spec() now takes context parameter - Update all existing renderer implementations for new signatures (BarRenderer, PathRenderer, SegmentRenderer, RibbonRenderer, AreaRenderer, PolygonRenderer, ViolinRenderer, BoxplotRenderer) - Update call sites in mod.rs to create and pass context - Add unit test for RenderContext::get_extent() with 4 test cases Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Implement ABLineRenderer for slope-intercept lines Add ABLineRenderer that draws lines based on slope and intercept parameters using a clipping-based approach: - Uses global x-axis extent from scales (not per-panel) - Creates calculate transforms for x_min, x_max, y_min, y_max - Relies on Vega-Lite clipping for faceted plots - Uses rule mark for independent line segments - Removes slope/intercept from encoding (used in transforms only) Key implementation details: - modify_spec(): Creates transforms for constant x extent and computed y values based on slope * x + intercept - modify_encoding(): Removes slope/intercept, adds x/x2/y/y2 encodings using x_min/x_max/y_min/y_max fields - geom_to_mark(): Uses "rule" mark for AbLine - get_renderer(): Dispatches AbLine to ABLineRenderer Naming convention: x_min, x_max, y_min, y_max (consistent underscores) Grammar update: Add 'slope' and 'intercept' as valid aesthetic names for ABLine geom support. Includes integration test with 3 lines of different slopes, verifying transforms, encodings, and stroke encoding for color. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * update test expectations * add docs * fix clippy warning * implement errorbar * add docs for errorbar * unite hline/vline into rule * rename abline/slope to linear/coef * Apply suggestion from @teunbrand --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent dc3848f commit 21786f4

20 files changed

Lines changed: 1247 additions & 125 deletions

File tree

CLAUDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ pub enum Geom {
334334
// Statistical geoms
335335
Histogram, Density, Smooth, Boxplot, Violin,
336336
// Annotation geoms
337-
Text, Label, Segment, Arrow, HLine, VLine, AbLine, ErrorBar,
337+
Text, Label, Segment, Arrow, Rule, Linear, ErrorBar,
338338
}
339339

340340
pub enum AestheticValue {
@@ -1202,7 +1202,7 @@ All clauses (MAPPING, SETTING, PARTITION BY, FILTER) are optional.
12021202

12031203
- **Basic**: `point`, `line`, `path`, `bar`, `col`, `area`, `tile`, `polygon`, `ribbon`
12041204
- **Statistical**: `histogram`, `density`, `smooth`, `boxplot`, `violin`
1205-
- **Annotation**: `text`, `label`, `segment`, `arrow`, `hline`, `vline`, `abline`, `errorbar`
1205+
- **Annotation**: `text`, `label`, `segment`, `arrow`, `rule`, `linear`, `errorbar`
12061206

12071207
**MAPPING Clause** (Aesthetic Mappings):
12081208

doc/ggsql.xml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,8 @@
141141
<item>label</item>
142142
<item>segment</item>
143143
<item>arrow</item>
144-
<item>hline</item>
145-
<item>vline</item>
146-
<item>abline</item>
144+
<item>rule</item>
145+
<item>linear</item>
147146
<item>errorbar</item>
148147
</list>
149148

doc/syntax/index.qmd

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,21 @@ ggsql augments the standard SQL syntax with a number of new clauses to describe
1515
## Layers
1616
There are many different layers to choose from when visualising your data. Some are straightforward translations of your data into visual marks such as a point layer, while others perform more or less complicated calculations like e.g. the histogram layer. A layer is selected by providing the layer name after the `DRAW` clause
1717

18-
- [`point`](layer/point.qmd) is used to create a scatterplot layer
19-
- [`line`](layer/line.qmd) is used to produce lineplots with the data sorted along the x axis
20-
- [`path`](layer/path.qmd) is like `line` above but does not sort the data but plot it according to its own order
18+
- [`point`](layer/point.qmd) is used to create a scatterplot layer.
19+
- [`line`](layer/line.qmd) is used to produce lineplots with the data sorted along the x axis.
20+
- [`path`](layer/path.qmd) is like `line` above but does not sort the data but plot it according to its own order.
21+
- [`segment`](layer/segment.qmd) connects two points with a line segment.
22+
- [`linear`](layer/linear.qmd) draws a long line parameterised by a coefficient and intercept.
23+
- [`rule`](layer/rule.qmd) draws horizontal and vertical reference lines.
2124
- [`area`](layer/area.qmd) is used to display series as an area chart.
2225
- [`ribbon`](layer/ribbon.qmd) is used to display series extrema.
2326
- [`polygon`](layer/polygon.qmd) is used to display arbitrary shapes as polygons.
24-
- [`bar`](layer/bar.qmd) creates a bar chart, optionally calculating y from the number of records in each bar
25-
- [`density`](layer/density.qmd) creates univariate kernel density estimates, showing the distribution of a variable
26-
- [`violin`](layer/violin.qmd) displays a rotated kernel density estimate
27-
- [`histogram`](layer/histogram.qmd) bins the data along the x axis and produces a bar for each bin showing the number of records in it
28-
- [`boxplot`](layer/boxplot.qmd) displays continuous variables as 5-number summaries
27+
- [`bar`](layer/bar.qmd) creates a bar chart, optionally calculating y from the number of records in each bar.
28+
- [`density`](layer/density.qmd) creates univariate kernel density estimates, showing the distribution of a variable.
29+
- [`violin`](layer/violin.qmd) displays a rotated kernel density estimate.
30+
- [`histogram`](layer/histogram.qmd) bins the data along the x axis and produces a bar for each bin showing the number of records in it.
31+
- [`boxplot`](layer/boxplot.qmd) displays continuous variables as 5-number summaries.
32+
- [`errorbar`](layer/errorbar.qmd) a line segment with hinges at the endpoints.
2933

3034
## Scales
3135
A scale is responsible for translating a data value to an aesthetic literal, e.g. a specific color for the fill aesthetic, or a radius in points for the size aesthetic. A scale is a combination of a specific aesthetic and a scale type

doc/syntax/layer/errorbar.qmd

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
---
2+
title: "Errorbar"
3+
---
4+
5+
> Layers are declared with the [`DRAW` clause](../clause/draw.qmd). Read the documentation for this clause for a thorough description of how to use it.
6+
7+
Errorbars are used to display paired metrics, typically some interval, for a variable. It is displayed as a line between the two values, often with hinges at the ends.
8+
9+
## Aesthetics
10+
The following aesthetics are recognised by the errorbar layer.
11+
12+
### Required
13+
* `x` or `y`: Position on the x- or y-axis. These are mutually exclusive.
14+
* `xmin` or `ymin`: Position of one of the interval ends orthogonal to the main position. These are also mutually exclusive.
15+
* `xmax` or `ymax`: Position of the other interval end orthogonal to the main position. These are also mutually exclusive.
16+
17+
Note that the required aesthetics is either a set of {`x`, `ymin`, `ymax`} or {`y`, `xmin`, `xmax`} and *not* a combination of the two.
18+
19+
### Optional
20+
* `stroke`/`colour`: The colour of the lines in the errorbar.
21+
* `opacity`: The opacity of the colour.
22+
* `linewidth`: The width of the lines in the errorbar.
23+
* `linetype`: The dash pattern of the lines in the errorbar.
24+
25+
## Settings
26+
* `width`: The width of the hinges in points. Can be set to `null` to not display hinges.
27+
28+
## Data transformation
29+
The errorbar layer does not transform its data but passes it through unchanged.
30+
31+
## Examples
32+
33+
```{ggsql}
34+
#| code-fold: true
35+
#| code-summary: "Create example data"
36+
CREATE TABLE penguin_summary AS
37+
SELECT
38+
species,
39+
MEAN(bill_dep) - STDDEV(bill_dep) AS low,
40+
MEAN(bill_dep) AS mean,
41+
MEAN(bill_dep) + STDDEV(bill_dep) AS high
42+
FROM ggsql:penguins
43+
GROUP BY species
44+
```
45+
46+
Classic errorbar with point at centre.
47+
48+
```{ggsql}
49+
VISUALISE species AS x FROM penguin_summary
50+
DRAW errorbar MAPPING low AS ymax, high AS ymin
51+
DRAW point MAPPING mean AS y
52+
```
53+
54+
Dynamite plot using bars instead of points, using extra wide hinges.
55+
56+
```{ggsql}
57+
VISUALISE species AS x FROM penguin_summary
58+
DRAW errorbar
59+
MAPPING low AS ymax, high AS ymin
60+
SETTING width => 40
61+
DRAW bar MAPPING mean AS y
62+
```
63+
64+
The hinges can be omitted by setting `null` as width.
65+
66+
```{ggsql}
67+
VISUALISE species AS x FROM penguin_summary
68+
DRAW errorbar
69+
MAPPING low AS ymax, high AS ymin
70+
SETTING width => null
71+
```

doc/syntax/layer/linear.qmd

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
---
2+
title: "Linear line"
3+
---
4+
5+
> Layers are declared with the [`DRAW` clause](../clause/draw.qmd). Read the documentation for this clause for a thorough description of how to use it.
6+
7+
The linear layer is used to draw diagonal reference lines based on a coefficient and intercept. This is useful for adding regression lines, diagonal guides, or mathematical functions to plots. The lines extend across the full extent of the x-axis, regardless of the data range.
8+
The layer is named for the following formula:
9+
10+
$$
11+
y = a + \beta x
12+
$$
13+
14+
Where $a$ is the `intercept` and $\beta$ is the `coef`.
15+
16+
## Aesthetics
17+
The following aesthetics are recognised by the abline layer.
18+
19+
### Required
20+
* `coef`: The coefficient/slope of the line i.e. the amount $y$ increases for every unit of $x$.
21+
* `intercept`: The intercept where the line crosses the y-axis at $x = 0$.
22+
23+
### Optional
24+
* `colour`/`stroke`: The colour of the line
25+
* `opacity`: The opacity of the line
26+
* `linewidth`: The width of the line
27+
* `linetype`: The type of the line, i.e. the dashing pattern
28+
29+
## Settings
30+
The linear layer has no additional settings.
31+
32+
## Data transformation
33+
The linear layer does not transform its data but passes it through unchanged.
34+
35+
## Examples
36+
37+
Add a simple reference line to a scatterplot:
38+
39+
```{ggsql}
40+
VISUALISE FROM ggsql:penguins
41+
DRAW point MAPPING bill_len AS x, bill_dep AS y
42+
DRAW linear MAPPING 0.4 AS coef, -1 AS intercept
43+
```
44+
45+
Add multiple reference lines with different colors from a separate dataset:
46+
47+
```{ggsql}
48+
WITH lines AS (
49+
SELECT * FROM (VALUES
50+
(0.4, -1, 'Line A'),
51+
(0.2, 8, 'Line B'),
52+
(0.8, -19, 'Line C')
53+
) AS t(coef, intercept, label)
54+
)
55+
VISUALISE FROM ggsql:penguins
56+
DRAW point MAPPING bill_len AS x, bill_dep AS y
57+
DRAW linear
58+
MAPPING
59+
coef AS coef,
60+
intercept AS intercept,
61+
label AS colour
62+
FROM lines
63+
```

doc/syntax/layer/rule.qmd

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
---
2+
title: "Rule"
3+
---
4+
5+
> Layers are declared with the [`DRAW` clause](../clause/draw.qmd). Read the documentation for this clause for a thorough description of how to use it.
6+
7+
The rule layer is used to draw horizontal or vertical reference lines at specified values. This is useful for adding thresholds, means, medians, avent markers, cutoff dates or other guides to the plot. The lines span the full width or height of the panels.
8+
9+
## Aesthetics
10+
The following aesthetics are recognised by the hline layer.
11+
12+
### Required
13+
* `x`\*: The x-coordinate for the vertical line.
14+
* `y`\*: The y-coordinate for the horizontal line
15+
16+
\* Exactly one of `x` or `y` is required, not both.
17+
18+
### Optional
19+
* `colour`/`stroke`: The colour of the line
20+
* `opacity`: The opacity of the line
21+
* `linewidth`: The width of the line
22+
* `linetype`: The type of the line, i.e. the dashing pattern
23+
24+
## Settings
25+
The rule layer has no additional settings.
26+
27+
## Data transformation
28+
The rule layer does not transform its data but passes it through unchanged.
29+
30+
## Examples
31+
32+
Add a horizontal threshold line to a time series plot:
33+
34+
```{ggsql}
35+
SELECT Date AS date, temp AS temperature
36+
FROM ggsql:airquality
37+
WHERE Month = 5
38+
39+
VISUALISE
40+
DRAW line MAPPING date AS x, temperature AS y
41+
DRAW rule MAPPING 70 AS y
42+
```
43+
44+
Add a vertical line to mark a specific value:
45+
46+
```{ggsql}
47+
VISUALISE FROM ggsql:penguins
48+
DRAW point MAPPING bill_len AS x, bill_dep AS y
49+
DRAW rule MAPPING 45 AS x
50+
```
51+
52+
Add multiple threshold lines with different colors:
53+
54+
```{ggsql}
55+
WITH thresholds AS (
56+
SELECT * FROM (VALUES
57+
(70, 'Target'),
58+
(80, 'Warning'),
59+
(90, 'Critical')
60+
) AS t(value, label)
61+
)
62+
SELECT Date AS date, temp AS temperature
63+
FROM ggsql:airquality
64+
WHERE Month = 5
65+
66+
VISUALISE
67+
DRAW line MAPPING date AS x, temperature AS y
68+
DRAW rule MAPPING value AS y, label AS colour FROM thresholds
69+
```

doc/syntax/layer/segment.qmd

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
---
2+
title: "Segment"
3+
---
4+
5+
> Layers are declared with the [`DRAW` clause](../clause/draw.qmd). Read the documentation for this clause for a thorough description of how to use it.
6+
7+
The segment layer is used to create line segments between two endpoints. If differs from [lines](line.qmd) and [paths](path.qmd) in that it connects just two points rather than many. Data is expected to be in a different shape, with 4 coordinates for the start (x, y) and end (xend, yend) points on a single row.
8+
9+
## Aesthetics
10+
The following aesthetics are recognised by the segment layer.
11+
12+
### Required
13+
* `x`: Position along the x-axis of the start point.
14+
* `y`: Position along the y-axis of the end point.
15+
* `xend`\*: Position along the x-axis of the end point.
16+
* `yend`\*: Position along the y-axis of the end point.
17+
18+
\* Only one of `xend` and `yend` is required.
19+
If one is missing, it takes on the value of the start point.
20+
21+
### Optional
22+
* `colour`/`stroke`: The colour of the line.
23+
* `opacity`: The opacity of the line.
24+
* `linewidth`: The width of the line.
25+
* `linetype`: The type of the line, i.e. the dashing pattern.
26+
27+
## Settings
28+
The segment layer has no additional settings.
29+
30+
## Data transformation
31+
The segment layer does not transform its data but passes it through unchanged.
32+
33+
## Data transformation
34+
35+
## Examples
36+
37+
Segments are useful when you have known start and end points of the data. For example in a graph
38+
39+
```{ggsql}
40+
WITH edges AS (
41+
SELECT * FROM (VALUES
42+
(0, 0, 1, 1, 'A'),
43+
(1, 1, 2, 1, 'A'),
44+
(2, 1, 3, 0, 'A'),
45+
(0, 3, 1, 2, 'B'),
46+
(1, 2, 2, 2, 'B'),
47+
(2, 2, 3, 3, 'B'),
48+
(1, 1, 1, 2, 'C'),
49+
(2, 2, 2, 1, 'C')
50+
) AS t(x, y, xend, yend, type)
51+
)
52+
VISUALISE x, y, xend, yend FROM edges
53+
DRAW segment MAPPING type AS stroke
54+
```
55+
56+
You can use segments as part of a lollipop chart by anchoring one of the ends to 0.
57+
Note that `xend` is missing and has taken up the value of `x`.
58+
59+
```{ggsql}
60+
SELECT ROUND(bill_dep) AS bill_dep, COUNT(*) AS n
61+
FROM ggsql:penguins
62+
GROUP BY ROUND(bill_dep)
63+
64+
VISUALISE bill_dep AS x, n AS y
65+
DRAW segment MAPPING 0 AS yend
66+
DRAW point
67+
```
68+
69+
By overlaying a thick line on a thin line, you can create a candlestick chart.
70+
71+
```{ggsql}
72+
SELECT
73+
FIRST(Date) AS date,
74+
FIRST(temp) AS open,
75+
LAST(temp) AS close,
76+
MAX(temp) AS high,
77+
MIN(temp) AS low,
78+
CASE
79+
WHEN FIRST(temp) > LAST(temp) THEN 'colder'
80+
ELSE 'warmer'
81+
END AS trend
82+
FROM ggsql:airquality
83+
GROUP BY WEEKOFYEAR(Date)
84+
85+
VISUALISE date AS x, trend AS colour
86+
DRAW segment
87+
MAPPING open AS y, close AS yend
88+
SETTING linewidth => 5
89+
DRAW segment
90+
MAPPING low AS y, high AS yend
91+
```

ggsql-vscode/syntaxes/ggsql.tmLanguage.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@
294294
"patterns": [
295295
{
296296
"name": "support.type.geom.ggsql",
297-
"match": "\\b(point|line|path|bar|col|area|tile|polygon|ribbon|histogram|density|smooth|boxplot|violin|text|label|segment|arrow|hline|vline|abline|errorbar)\\b"
297+
"match": "\\b(point|line|path|bar|col|area|tile|polygon|ribbon|histogram|density|smooth|boxplot|violin|text|label|segment|arrow|rule|linear|errorbar)\\b"
298298
},
299299
{ "include": "#common-clause-patterns" }
300300
]

src/parser/builder.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -595,9 +595,8 @@ fn parse_geom_type(text: &str) -> Result<Geom> {
595595
"label" => Ok(Geom::label()),
596596
"segment" => Ok(Geom::segment()),
597597
"arrow" => Ok(Geom::arrow()),
598-
"hline" => Ok(Geom::hline()),
599-
"vline" => Ok(Geom::vline()),
600-
"abline" => Ok(Geom::abline()),
598+
"rule" => Ok(Geom::rule()),
599+
"linear" => Ok(Geom::linear()),
601600
"errorbar" => Ok(Geom::errorbar()),
602601
_ => Err(GgsqlError::ParseError(format!(
603602
"Unknown geom type: {}",

0 commit comments

Comments
 (0)