Skip to content

Commit 5d65b0f

Browse files
committed
Fix columns_config and plugin_config bugs
Signed-off-by: Andrew Stein <steinlink@gmail.com>
1 parent 595cdbc commit 5d65b0f

34 files changed

Lines changed: 1032 additions & 778 deletions

.github/workflows/build.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ on:
2828
- docs/
2929
- examples/
3030
- rust/perspective-python/README.md
31-
pull_request_target:
31+
pull_request:
3232
branches:
3333
- master
3434
workflow_dispatch:

packages/viewer-charts/src/ts/axis/categorical-axis.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,6 @@ function categoryIndexToPixelY(layout: PlotLayout, index: number): number {
5656
return layout.dataToPixel(0, index).py;
5757
}
5858

59-
export const categoryIndexToPixel = categoryIndexToPixelX;
60-
6159
function leafLevelLayout(
6260
numRows: number,
6361
longestCharCount: number,

packages/viewer-charts/src/ts/charts/candlestick/candlestick-render.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,13 +125,14 @@ export function renderCandlestickFrame(
125125
yMax: chart._yDomain.max,
126126
};
127127

128-
// Auto-fit the price axis to the visible X window. Skipped at
129-
// default zoom (the refit equals `_yDomain` there and would only
130-
// churn baselines).
128+
// Auto-fit the price axis to the visible X window. Skipped when X
129+
// is at default zoom (the refit equals `_yDomain` there and would
130+
// only churn baselines) — Y-axis pan/zoom alone shouldn't trigger
131+
// an X-window refit.
131132
if (
132133
chart._autoFitValue &&
133134
chart._zoomController &&
134-
!chart._zoomController.isDefault()
135+
!chart._zoomController.isXDefault()
135136
) {
136137
const fit = computeVisibleCandleExtent(chart, vis.xMin, vis.xMax);
137138
if (fit.hasFit) {

packages/viewer-charts/src/ts/charts/candlestick/candlestick.ts

Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
} from "./candlestick-interact";
3434
import { BodyWickGlyph } from "./glyphs/draw-candlesticks";
3535
import { OHLCGlyph } from "./glyphs/draw-ohlc";
36+
import { expandDomainInPlace } from "../common/expand-domain";
3637

3738
/**
3839
* Per-frame memo of the auto-fit Y extent for a {@link CandlestickChart},
@@ -266,38 +267,18 @@ export class CandlestickChart extends CategoricalYChart {
266267
scratchCandles: this._candles,
267268
});
268269
// `domain_mode: "expand"` post-build union — mirrors the series
269-
// pipeline. Mutate the pipeline result in place so the
270+
// pipeline. `expandDomainInPlace` mutates `result.*` so the
270271
// assignments below pick up the grown extent automatically.
271272
if (this._pluginConfig.domain_mode === "expand") {
272-
if (this._expandedYDomain) {
273-
result.yDomain.min = Math.min(
274-
this._expandedYDomain.min,
275-
result.yDomain.min,
276-
);
277-
result.yDomain.max = Math.max(
278-
this._expandedYDomain.max,
279-
result.yDomain.max,
280-
);
281-
}
282-
283-
this._expandedYDomain = { ...result.yDomain };
284-
273+
this._expandedYDomain = expandDomainInPlace(
274+
this._expandedYDomain,
275+
result.yDomain,
276+
);
285277
if (result.numericCategoryDomain) {
286-
if (this._expandedCategoryDomain) {
287-
result.numericCategoryDomain.min = Math.min(
288-
this._expandedCategoryDomain.min,
289-
result.numericCategoryDomain.min,
290-
);
291-
result.numericCategoryDomain.max = Math.max(
292-
this._expandedCategoryDomain.max,
293-
result.numericCategoryDomain.max,
294-
);
295-
}
296-
297-
this._expandedCategoryDomain = {
298-
min: result.numericCategoryDomain.min,
299-
max: result.numericCategoryDomain.max,
300-
};
278+
this._expandedCategoryDomain = expandDomainInPlace(
279+
this._expandedCategoryDomain,
280+
result.numericCategoryDomain,
281+
);
301282
}
302283
} else {
303284
this._expandedYDomain = null;

packages/viewer-charts/src/ts/charts/chart-base.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ import type { ViewConfig } from "@perspective-dev/client";
4646
import { resolveThemeFromVars, type Theme } from "../theme/theme";
4747
import { requestRender as scheduleRender } from "../render/scheduler";
4848

49+
// TODO I don't know if this is the behavior we want. On the plus side, this
50+
// ad-hoc formatter scales well to small and large data ranges, making a good
51+
// guess at the right format without user input. On the minus side, this
52+
// behavior is inconsistent with datagrid and the rest of the app, and the ad-hoc
53+
// surprising behavior when overriding one field in `number_format` and suddenly
54+
// the entire formatter is replaced.
55+
const REGRESSION_BEHAVIOR = true;
56+
4957
/**
5058
* Locale-aware fallback formatter applied to numeric tooltip / legend
5159
* values when the column has no `number_format` configured. Two
@@ -55,9 +63,12 @@ import { requestRender as scheduleRender } from "../render/scheduler";
5563
const DEFAULT_VALUE_FORMATTER: (v: number) => string = ((): ((
5664
v: number,
5765
) => string) => {
58-
return formatTickValue;
59-
// const intl = createNumberFormatter("float");
60-
// return (v) => intl.format(v);
66+
if (REGRESSION_BEHAVIOR) {
67+
return formatTickValue;
68+
} else {
69+
const intl = createNumberFormatter("float");
70+
return (v) => intl.format(v);
71+
}
6172
})();
6273

6374
/**
@@ -69,9 +80,12 @@ const DEFAULT_VALUE_FORMATTER: (v: number) => string = ((): ((
6980
const DEFAULT_DATETIME_FORMATTER: (v: number) => string = ((): ((
7081
v: number,
7182
) => string) => {
72-
return formatDateTickValue;
73-
// const intl = createDatetimeFormatter();
74-
// return (v) => intl.format(v);
83+
if (REGRESSION_BEHAVIOR) {
84+
return formatDateTickValue;
85+
} else {
86+
const intl = createDatetimeFormatter();
87+
return (v) => intl.format(v);
88+
}
7589
})();
7690

7791
/**
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
8+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
10+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12+
13+
/**
14+
* Numeric extent — used by series + candlestick build pipelines for
15+
* value / category axis domains.
16+
*/
17+
export interface Domain {
18+
min: number;
19+
max: number;
20+
}
21+
22+
/**
23+
* Union `next` (a freshly-computed extent) with `prev` (the prior
24+
* accumulator) IN PLACE on `next`, then return a fresh copy to store
25+
* back as the new accumulator. Idempotent when `prev` is null — `next`
26+
* is left untouched.
27+
*
28+
* Used by the `domain_mode: "expand"` mirror-back step in the series /
29+
* candlestick / cartesian build pipelines: mutating `next` in place
30+
* means every downstream assignment that reads from the pipeline
31+
* result struct automatically picks up the grown extent.
32+
*/
33+
export function expandDomainInPlace(prev: Domain | null, next: Domain): Domain {
34+
if (prev) {
35+
next.min = Math.min(prev.min, next.min);
36+
next.max = Math.max(prev.max, next.max);
37+
}
38+
39+
return { min: next.min, max: next.max };
40+
}

packages/viewer-charts/src/ts/charts/common/tree-chart.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,25 @@
1111
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
1212

1313
import { AbstractChart } from "../chart-base";
14+
import type { ColumnDataMap } from "../../data/view-reader";
1415
import { NodeStore, NULL_NODE } from "./node-store";
1516
import { LazyTooltip } from "../../interaction/lazy-tooltip";
1617

18+
/**
19+
* Sentinel fallback for the Size slot when the user hasn't picked one:
20+
* use the first non-metadata column in the incoming view. Tree charts
21+
* still need *some* numeric-ish column to size geometry.
22+
*/
23+
export function firstNonMetadataColumn(columns: ColumnDataMap): string {
24+
for (const k of columns.keys()) {
25+
if (!k.startsWith("__")) {
26+
return k;
27+
}
28+
}
29+
30+
return "";
31+
}
32+
1733
/**
1834
* Shared state for hierarchical charts (treemap, sunburst). Holds the
1935
* tree store + streaming-insert scaffolding + per-row tooltip data

packages/viewer-charts/src/ts/charts/common/tree-chrome.ts

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,17 @@
1010
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
1111
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
1212

13-
import type { Context2D } from "../canvas-types";
13+
import type { Canvas2D, Context2D } from "../canvas-types";
14+
import type { PlotRect } from "../../layout/plot-layout";
15+
import { PlotLayout } from "../../layout/plot-layout";
16+
import type { GradientStop } from "../../theme/gradient";
17+
import type { Vec3 } from "../../theme/palette";
18+
import type { Theme } from "../../theme/theme";
19+
import {
20+
renderCategoricalLegend,
21+
renderCategoricalLegendAt,
22+
renderLegend,
23+
} from "../../axis/legend";
1424
import type { TreeChartBase } from "./tree-chart";
1525
import { drawTooltipBox } from "./draw-tooltip-box";
1626

@@ -121,3 +131,78 @@ export function renderTreeTooltip(
121131
fontFamily,
122132
);
123133
}
134+
135+
/**
136+
* Paint a color legend (categorical swatches or numeric gradient bar)
137+
* for a tree chart. Shared by sunburst + treemap; both consult
138+
* `_colorMode` / `_uniqueColorLabels.size` / `_colorMin..max` the same
139+
* way.
140+
*
141+
* `categoricalRect`, when non-null, is used as the explicit rect for
142+
* the categorical-swatch variant (sunburst's faceted mode passes
143+
* `FacetGrid.legendRect` here). Numeric mode always derives from a
144+
* synthetic single-plot `PlotLayout` to match the legacy per-chart
145+
* branch — its gradient bar's vertical span doesn't fit the
146+
* categorical legend's compact rect.
147+
*
148+
* Returns silently when the color slot is empty, when categorical mode
149+
* has only one label, or when numeric mode has a degenerate
150+
* (`min >= max`) extent.
151+
*/
152+
export function renderTreeColorLegend(
153+
chart: TreeChartBase,
154+
canvas: Canvas2D,
155+
palette: Vec3[],
156+
stops: GradientStop[],
157+
theme: Theme,
158+
cssWidth: number,
159+
cssHeight: number,
160+
categoricalRect: PlotRect | null = null,
161+
): void {
162+
if (chart._colorMode === "series" && chart._uniqueColorLabels.size > 1) {
163+
if (categoricalRect) {
164+
renderCategoricalLegendAt(
165+
canvas,
166+
categoricalRect,
167+
chart._uniqueColorLabels,
168+
palette,
169+
theme,
170+
);
171+
} else {
172+
renderCategoricalLegend(
173+
canvas,
174+
syntheticLegendLayout(cssWidth, cssHeight),
175+
chart._uniqueColorLabels,
176+
palette,
177+
theme,
178+
);
179+
}
180+
} else if (
181+
chart._colorMode === "numeric" &&
182+
chart._colorMin < chart._colorMax
183+
) {
184+
renderLegend(
185+
canvas,
186+
syntheticLegendLayout(cssWidth, cssHeight),
187+
{
188+
min: chart._colorMin,
189+
max: chart._colorMax,
190+
label: chart._colorName,
191+
},
192+
stops,
193+
theme,
194+
chart.getColumnFormatter(chart._colorName, "value"),
195+
);
196+
}
197+
}
198+
199+
function syntheticLegendLayout(
200+
cssWidth: number,
201+
cssHeight: number,
202+
): PlotLayout {
203+
return new PlotLayout(cssWidth, cssHeight, {
204+
hasXLabel: false,
205+
hasYLabel: false,
206+
hasLegend: true,
207+
});
208+
}

0 commit comments

Comments
 (0)