Skip to content

Commit f84b5a9

Browse files
committed
first stab at a rebase
1 parent 7f23445 commit f84b5a9

14 files changed

Lines changed: 1320 additions & 77 deletions

File tree

doc/syntax/layer/type/bar.qmd

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ The following aesthetics are recognised by the bar layer.
1313
The bar layer has no required aesthetics
1414

1515
### Optional
16-
* `x`: Position on the x-axis. If missing all records will be shown in the same bar
17-
* `y`: The height of the plot. If missing, it will be calculated by the layer
16+
* Primary axis (e.g. `x`): The categories to create bars for. If missing all records will be shown in the same bar
17+
* Secondary axis (e.g. `y`): The height of the bars. If missing, it will be calculated by the layer
1818
* `colour`: The default colour of each bar
1919
* `stroke`: The colour of the stroke around each bar. Overrides `colour`
2020
* `fill`: The fill colour of each bar. Overrides `colour`
@@ -27,7 +27,7 @@ The bar layer has no required aesthetics
2727
* `width`: The width of the bars as a proportion of the available width
2828

2929
## Data transformation
30-
If `y` has not been mapped the layer will calculate it for you.
30+
If the secondary axis has not been mapped the layer will calculate it for you.
3131

3232
### Properties
3333

@@ -40,7 +40,10 @@ If `y` has not been mapped the layer will calculate it for you.
4040

4141
### Default remappings
4242

43-
* `count AS y`: By default the barplot will show count as the height of the bars
43+
* `count AS <secondary axis>`: By default the barplot will show count as the height of the bars
44+
45+
## Orientation
46+
Bar plots have categories along their primary axis. The orientation can be deduced purely from the mapping. To create a horizontal bar plot you map the categories to `y` instead of `x` (assuming a default Cartesian coordinate system).
4447

4548
## Examples
4649

@@ -100,3 +103,11 @@ SCALE BINNED x
100103
And use with a polar coordinate system to create a pie chart
101104

102105
**TBD**
106+
107+
Create a horizontal bar plot by changing the mapping
108+
109+
```{ggsql}
110+
VISUALISE FROM ggsql:penguins
111+
DRAW bar
112+
MAPPING species AS y
113+
```

doc/syntax/layer/type/boxplot.qmd

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ Boxplots display a summary of a continuous distribution. In the style of Tukey,
99
The following aesthetics are recognised by the boxplot layer.
1010

1111
### Required
12-
* `x`: Position on the x-axis
13-
* `y`: Position on the y-axis
12+
* Primary axis (e.g. `x`): The categorical variable to group by
13+
* Secondary axis (e.g. `y`): The continuous variable to summarize
1414

1515
### Optional
1616
* `stroke`: The colour of the box contours, whiskers, median line and outliers.
@@ -43,7 +43,10 @@ Observations are considered outliers when they are more extreme than the whisker
4343

4444
### Default remapping
4545

46-
* `value AS y`: By default the values are displayed along the y-axis.
46+
* `value AS <secondary axis>`: By default the values are displayed along the secondary axis.
47+
48+
## Orientation
49+
The boxplot has its categorical groups along the primary axis and the continuous values along the secondary axis. The orientation can be deduced from the scale types or from the mapping. To create a horizontal boxplot, map the categorical variable to `y` and the continuous variable to `x` (assuming a default Cartesian coordinate system).
4750

4851
### Examples
4952

@@ -83,3 +86,11 @@ DRAW boxplot
8386
MAPPING species AS x, bill_len AS y
8487
SETTING coef => 0.1
8588
```
89+
90+
Create a horizontal boxplot by swapping x and y:
91+
92+
```{ggsql}
93+
VISUALISE FROM ggsql:penguins
94+
DRAW boxplot
95+
MAPPING species AS y, bill_len AS x
96+
```

doc/syntax/layer/type/density.qmd

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Visualise the distribution of a single continuous variable by computing a kernel
1010
The following aesthetics are recognised by the density layer.
1111

1212
### Required
13-
* `x`: Position on the x-axis.
13+
* Primary axis (e.g. `x`): The continuous variable to estimate density for.
1414

1515
### Optional
1616
* `stroke`: The colour of the contour lines.
@@ -62,7 +62,10 @@ $$
6262

6363
### Default remappings
6464

65-
* `density AS y`: By default the density layer will display the computed density along the y-axis.
65+
* `density AS <secondary axis>`: By default the density layer will display the computed density along the secondary axis.
66+
67+
## Orientation
68+
The density has its primary axis along the variable for which density is computed. The orientation can be deduced from the mapping. To create a horizontal density plot, map the variable to `y` instead of `x` (assuming a default Cartesian coordinate system).
6669

6770
## Examples
6871

doc/syntax/layer/type/histogram.qmd

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ title: "Histogram"
44

55
> 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.
66
7-
Visualise the distribution of a single continuous variable by dividing the x axis into bins and counting the number of observations in each bin. If providing a weight then a weighted histogram is calculated instead.
7+
Visualise the distribution of a single continuous variable by dividing the primary axis into bins and counting the number of observations in each bin. If providing a weight then a weighted histogram is calculated instead.
88

99
## Aesthetics
1010
The following aesthetics are recognised by the bar layer.
1111

1212
### Required
13-
* `x`: Position on the x-axis
13+
* Primary axis (e.g. `x`): The continuous variable to bin
1414

1515
### Optional
1616
* `colour`: The default colour of each bar
@@ -40,7 +40,10 @@ The histogram layer will bin the records in each group and count them. By defaul
4040

4141
### Default remappings
4242

43-
* `count AS y`: By default the histogram will show count as the height of the bars
43+
* `count AS <secondary axis>`: By default the histogram will show count as the height of the bars
44+
45+
## Orientation
46+
The histogram has its primary axis along the binned variable. The orientation can be deduced from the mapping. To create a horizontal histogram you map the variable to `y` instead of `x` (assuming a default Cartesian coordinate system).
4447

4548
## Examples
4649

@@ -86,3 +89,11 @@ DRAW histogram
8689
MAPPING body_mass AS x
8790
SETTING binwidth => 100
8891
```
92+
93+
Create a horizontal histogram by changing the mapping
94+
95+
```{ggsql}
96+
VISUALISE FROM ggsql:penguins
97+
DRAW histogram
98+
MAPPING body_mass AS y
99+
```

doc/syntax/layer/type/ribbon.qmd

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ The ribbon layer is used to display extrema over a sorted x-axis. It can be seen
1010
The following aesthetics are recognised by the ribbon layer.
1111

1212
### Required
13-
* `x`: Position along the x-axis
14-
* `ymin`: Lower position along the y-axis.
15-
* `ymax`: Upper position along the y-axis.
13+
* Primary axis (e.g. `x`): Position along the primary (domain) axis
14+
* Secondary axis min (e.g. `ymin`): Lower position along the secondary (value) axis.
15+
* Secondary axis max (e.g. `ymax`): Upper position along the secondary (value) axis.
1616

1717
### Optional
1818
* `stroke`: The colour of the contour lines.
@@ -27,6 +27,9 @@ The following aesthetics are recognised by the ribbon layer.
2727
## Data transformation
2828
The ribbon layer does not transform its data but passes it through unchanged.
2929

30+
## Orientation
31+
The ribbon has its domain axis as the primary axis and the value range on the secondary axis. The orientation can be deduced from the mapping. To create a horizontal ribbon, map the domain to `y` and use `xmin`/`xmax` for the value range (assuming a default Cartesian coordinate system).
32+
3033
## Examples
3134

3235
A ribbon plot with arbitrary values as minima/maxima

doc/syntax/layer/type/violin.qmd

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ The violins are mirrored kernel density estimates, similar to the [density](dens
1111
The following aesthetics are recognised by the violin layer.
1212

1313
### Required
14-
* `x`: Position on the x-axis (categorical).
15-
* `y`: Value on the y-axis for which to compute density.
14+
* Primary axis (e.g. `x`): The categorical variable for grouping.
15+
* Secondary axis (e.g. `y`): The continuous variable to compute density for.
1616

1717
### Optional
1818
* `stroke`: The colour of the contour lines.
@@ -52,6 +52,9 @@ The major difference between a violin layer and a density layer is just the matt
5252

5353
* `density AS offset`: By default the offsets around a centerline reflect the computed density.
5454

55+
## Orientation
56+
The violin plot has its categorical groups along the primary axis and the continuous values along the secondary axis. The orientation can be deduced from the scale types or from the mapping. To create horizontal violins, swap `x` and `y` in the mapping (assuming a default Cartesian coordinate system).
57+
5558
## Examples
5659

5760
A typical violin plot.
@@ -83,3 +86,10 @@ VISUALISE species AS x, bill_dep AS y, island AS fill FROM ggsql:penguins
8386
DRAW violin
8487
```
8588

89+
Create horizontal violins by swapping x and y:
90+
91+
```{ggsql}
92+
VISUALISE species AS y, bill_dep AS x FROM ggsql:penguins
93+
DRAW violin
94+
```
95+

src/execute/layer.rs

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ pub fn build_layer_base_query(
337337
/// * `schema` - The layer's schema (with min/max from base_query)
338338
/// * `scales` - All resolved scales
339339
/// * `type_names` - SQL type names for the database backend
340+
/// * `aesthetic_ctx` - Aesthetic context for name transformations
340341
/// * `execute_query` - Function to execute queries (needed for some stat transforms)
341342
///
342343
/// # Returns
@@ -348,15 +349,23 @@ pub fn apply_layer_transforms<F>(
348349
schema: &Schema,
349350
scales: &[Scale],
350351
type_names: &SqlTypeNames,
352+
aesthetic_ctx: &crate::plot::aesthetic::AestheticContext,
351353
execute_query: &F,
352354
) -> Result<String>
353355
where
354356
F: Fn(&str) -> Result<DataFrame>,
355357
{
358+
use crate::plot::layer::orientation::{flip_mappings, flip_remappings, Orientation};
359+
356360
// Clone order_by early to avoid borrow conflicts
357361
let order_by = layer.order_by.clone();
358362

363+
// Orientation detection and initial flip was already done in mod.rs before
364+
// build_layer_base_query. We just check if we need to flip back after stat.
365+
let needs_flip = layer.orientation == Some(Orientation::Transposed);
366+
359367
// Build the aesthetic-named schema for stat transforms
368+
// Note: Mappings were already flipped in mod.rs if needed, so schema reflects normalized orientation
360369
let aesthetic_schema: Schema = build_aesthetic_schema(layer, schema);
361370

362371
// Collect literal aesthetic column names BEFORE conversion to Column values.
@@ -418,8 +427,17 @@ where
418427
execute_query,
419428
)?;
420429

430+
// Flip user remappings BEFORE merging defaults for Transposed orientation.
431+
// User remappings are in user orientation (e.g., `count AS x` for horizontal histogram).
432+
// We flip them to aligned orientation so they're uniform with defaults.
433+
// At the end, we flip everything back together.
434+
if needs_flip {
435+
flip_remappings(layer);
436+
}
437+
421438
// Apply literal default remappings from geom defaults (e.g., y2 => 0.0 for bar baseline).
422439
// These apply regardless of stat transform, but only if user hasn't overridden them.
440+
// Defaults are always in aligned orientation.
423441
for (aesthetic, default_value) in layer.geom.default_remappings() {
424442
// Only process literal values here (Column values are handled in Transformed branch)
425443
if !matches!(default_value, DefaultAestheticValue::Column(_)) {
@@ -555,7 +573,22 @@ where
555573
StatResult::Identity => query,
556574
};
557575

558-
// Apply ORDER BY
576+
// Flip mappings back after stat transforms if we flipped them earlier
577+
// Now pos1/pos2 map to the user's intended x/y positions
578+
// Note: We only flip mappings here, not remappings. Remappings are flipped
579+
// later in mod.rs after apply_remappings_post_query creates the columns,
580+
// so that Phase 4.5 can flip those columns along with everything else.
581+
if needs_flip {
582+
flip_mappings(layer);
583+
584+
// Normalize mapping column names to match their aesthetic keys.
585+
// After flip_mappings, pos1 might point to __ggsql_aes_pos2__ (and vice versa).
586+
// We update the column names so pos1 → __ggsql_aes_pos1__, etc.
587+
// The DataFrame columns will be renamed correspondingly in mod.rs.
588+
normalize_mapping_column_names(layer, aesthetic_ctx);
589+
}
590+
591+
// Apply explicit ORDER BY if provided
559592
let final_query = if let Some(ref o) = order_by {
560593
format!("{} ORDER BY {}", final_query, o.as_str())
561594
} else {
@@ -564,3 +597,57 @@ where
564597

565598
Ok(final_query)
566599
}
600+
601+
/// Normalize mapping column names to match their aesthetic keys after flip-back.
602+
///
603+
/// After `flip_mappings`, the mapping values (column names) may not match the keys.
604+
/// For example, pos1 might point to `__ggsql_aes_pos2__`.
605+
/// This function updates the column names so pos1 → `__ggsql_aes_pos1__`, etc.
606+
///
607+
/// This should be called after flip_mappings during flip-back.
608+
/// The DataFrame columns should be renamed correspondingly using `flip_dataframe_columns`.
609+
fn normalize_mapping_column_names(
610+
layer: &mut Layer,
611+
aesthetic_ctx: &crate::plot::aesthetic::AestheticContext,
612+
) {
613+
// Collect the aesthetics to update (to avoid borrowing issues)
614+
let aesthetics_to_update: Vec<String> = layer
615+
.mappings
616+
.aesthetics
617+
.keys()
618+
.filter(|aes| crate::plot::aesthetic::is_positional_aesthetic(aes))
619+
.cloned()
620+
.collect();
621+
622+
for aesthetic in aesthetics_to_update {
623+
if let Some(value) = layer.mappings.aesthetics.get_mut(&aesthetic) {
624+
let expected_col = naming::aesthetic_column(&aesthetic);
625+
match value {
626+
AestheticValue::Column {
627+
name,
628+
original_name,
629+
..
630+
} => {
631+
// The current column name might be wrong (e.g., __ggsql_aes_pos2__ for pos1)
632+
// We need to flip it to match the aesthetic key
633+
let current_col_aesthetic =
634+
naming::extract_aesthetic_name(name).unwrap_or_default();
635+
let flipped_aesthetic = aesthetic_ctx.flip_positional(current_col_aesthetic);
636+
637+
// If flipping results in the correct aesthetic, update the column name
638+
if flipped_aesthetic == aesthetic {
639+
*name = expected_col;
640+
}
641+
// Preserve original_name for axis labels
642+
if original_name.is_none() {
643+
*original_name = Some(aesthetic.clone());
644+
}
645+
}
646+
AestheticValue::Literal(_) => {
647+
// Literals become columns with the expected name
648+
*value = AestheticValue::standard_column(expected_col);
649+
}
650+
}
651+
}
652+
}
653+
}

0 commit comments

Comments
 (0)