Skip to content

Commit 1f6ed15

Browse files
authored
Merge pull request #3182 from perspective-dev/fix-update-corruption
`<perspective-viewer>` bug fixes and UI polish
2 parents 62e5023 + 97d1fa5 commit 1f6ed15

61 files changed

Lines changed: 2558 additions & 791 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

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

Lines changed: 74 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ export type BarCategoryAxis =
142142
| { mode: "category"; domain: CategoricalDomain }
143143
| { mode: "numeric"; domain: AxisDomain; ticks: number[] };
144144

145+
export type BarValueAxis =
146+
| { mode: "category"; domain: CategoricalDomain }
147+
| { mode: "numeric"; domain: AxisDomain; ticks: number[] };
148+
145149
/**
146150
* Render a numeric date-aware axis along the bottom of the plot. Aliases
147151
* the bar-axis bottom variant so heatmap can share the implementation.
@@ -190,13 +194,11 @@ export interface BarAxesFormatters {
190194
export function renderBarAxesChrome(
191195
canvas: Canvas2D,
192196
catAxis: BarCategoryAxis,
193-
valueDomain: AxisDomain,
194-
valueTicks: number[],
197+
valueAxis: BarValueAxis,
195198
layout: PlotLayout,
196199
theme: Theme,
197200
dpr: number,
198-
altDomain?: AxisDomain,
199-
altTicks?: number[],
201+
altAxis: BarValueAxis | undefined,
200202
isHorizontal = false,
201203
formatters: BarAxesFormatters = {},
202204
): void {
@@ -212,7 +214,7 @@ export function renderBarAxesChrome(
212214
ctx.moveTo(plot.x, plot.y);
213215
ctx.lineTo(plot.x, plot.y + plot.height);
214216
ctx.lineTo(plot.x + plot.width, plot.y + plot.height);
215-
if (altDomain) {
217+
if (altAxis) {
216218
if (isHorizontal) {
217219
ctx.moveTo(plot.x, plot.y);
218220
ctx.lineTo(plot.x + plot.width, plot.y);
@@ -238,31 +240,49 @@ export function renderBarAxesChrome(
238240
);
239241
}
240242

241-
drawNumericXAxis(
242-
ctx,
243-
layout,
244-
valueDomain,
245-
valueTicks,
246-
"bottom",
247-
theme,
248-
formatters.value,
249-
);
250-
if (altDomain && altTicks) {
251-
const origMin = layout.paddedXMin;
252-
const origMax = layout.paddedXMax;
253-
layout.paddedXMin = altDomain.min;
254-
layout.paddedXMax = altDomain.max;
243+
if (valueAxis.mode === "category") {
244+
// Categorical value axis on the bottom: reuse the X
245+
// categorical painter. Slot indices on the layout's X
246+
// domain already place each category at its slot pixel.
247+
renderCategoricalXTicks(ctx, layout, valueAxis.domain, theme);
248+
} else {
255249
drawNumericXAxis(
256250
ctx,
257251
layout,
258-
altDomain,
259-
altTicks,
260-
"top",
252+
valueAxis.domain,
253+
valueAxis.ticks,
254+
"bottom",
261255
theme,
262-
formatters.alt,
256+
formatters.value,
263257
);
264-
layout.paddedXMin = origMin;
265-
layout.paddedXMax = origMax;
258+
}
259+
260+
if (altAxis) {
261+
// Alt-axis painter expects the layout's X domain to match
262+
// the alt domain — temporarily swap in `altDomain.min/max`
263+
// for the duration of the call. Categorical alt has no
264+
// top-side painter; render with the bottom-side painter
265+
// (visual overlap with the primary side — documented
266+
// limitation; user-pinned categorical alt is rare).
267+
if (altAxis.mode === "category") {
268+
renderCategoricalXTicks(ctx, layout, altAxis.domain, theme);
269+
} else {
270+
const origMin = layout.paddedXMin;
271+
const origMax = layout.paddedXMax;
272+
layout.paddedXMin = altAxis.domain.min;
273+
layout.paddedXMax = altAxis.domain.max;
274+
drawNumericXAxis(
275+
ctx,
276+
layout,
277+
altAxis.domain,
278+
altAxis.ticks,
279+
"top",
280+
theme,
281+
formatters.alt,
282+
);
283+
layout.paddedXMin = origMin;
284+
layout.paddedXMax = origMax;
285+
}
266286
}
267287
} else {
268288
if (catAxis.mode === "category") {
@@ -278,31 +298,40 @@ export function renderBarAxesChrome(
278298
);
279299
}
280300

281-
drawYAxis(
282-
ctx,
283-
layout,
284-
valueDomain,
285-
valueTicks,
286-
"left",
287-
theme,
288-
formatters.value,
289-
);
290-
if (altDomain && altTicks) {
291-
const origMin = layout.paddedYMin;
292-
const origMax = layout.paddedYMax;
293-
layout.paddedYMin = altDomain.min;
294-
layout.paddedYMax = altDomain.max;
301+
if (valueAxis.mode === "category") {
302+
renderCategoricalYTicks(ctx, layout, valueAxis.domain, theme);
303+
} else {
295304
drawYAxis(
296305
ctx,
297306
layout,
298-
altDomain,
299-
altTicks,
300-
"right",
307+
valueAxis.domain,
308+
valueAxis.ticks,
309+
"left",
301310
theme,
302-
formatters.alt,
311+
formatters.value,
303312
);
304-
layout.paddedYMin = origMin;
305-
layout.paddedYMax = origMax;
313+
}
314+
315+
if (altAxis) {
316+
if (altAxis.mode === "category") {
317+
renderCategoricalYTicks(ctx, layout, altAxis.domain, theme);
318+
} else {
319+
const origMin = layout.paddedYMin;
320+
const origMax = layout.paddedYMax;
321+
layout.paddedYMin = altAxis.domain.min;
322+
layout.paddedYMax = altAxis.domain.max;
323+
drawYAxis(
324+
ctx,
325+
layout,
326+
altAxis.domain,
327+
altAxis.ticks,
328+
"right",
329+
theme,
330+
formatters.alt,
331+
);
332+
layout.paddedYMin = origMin;
333+
layout.paddedYMax = origMax;
334+
}
306335
}
307336
}
308337
}

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -272,13 +272,15 @@ export function renderCandlestickChromeOverlay(chart: CandlestickChart): void {
272272
renderBarAxesChrome(
273273
chart._chromeCanvas,
274274
catAxis,
275-
chart._lastYDomain,
276-
chart._lastYTicks,
275+
{
276+
mode: "numeric",
277+
domain: chart._lastYDomain,
278+
ticks: chart._lastYTicks,
279+
},
277280
chart._lastLayout,
278281
theme,
279282
chart._glManager?.dpr ?? 1,
280283
undefined,
281-
undefined,
282284
false,
283285
{
284286
value: chart.getColumnFormatter(valueColumn, "tick"),

packages/viewer-charts/src/ts/charts/candlestick/glyphs/draw-candlesticks.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,23 @@ interface WickCache {
4646
u_color: WebGLUniformLocation | null;
4747
u_resolution: WebGLUniformLocation | null;
4848
u_line_width: WebGLUniformLocation | null;
49+
50+
/**
51+
* `line-uniform` was extended to support the Y-Line `interpolate`
52+
* feature with a per-segment alpha multiplier driven by
53+
* `a_real_start * a_real_end` and `u_interp_alpha`. Wicks are
54+
* always "real" data — every segment renders fully — so we hold
55+
* these locations to neutralize them at draw time (uniform = 1.0,
56+
* constant attribute values = 1.0). Without that, an unset uniform
57+
* defaults to 0 and the fragment alpha collapses to 0, rendering
58+
* the wicks invisible.
59+
*/
60+
u_interp_alpha: WebGLUniformLocation | null;
4961
a_corner: number;
5062
a_start: number;
5163
a_end: number;
64+
a_real_start: number;
65+
a_real_end: number;
5266
}
5367

5468
interface ProgramCache {
@@ -124,8 +138,14 @@ export class BodyWickGlyph {
124138
"line-uniform",
125139
lineVert,
126140
lineFrag,
127-
["u_projection", "u_color", "u_resolution", "u_line_width"],
128-
["a_corner", "a_start", "a_end"],
141+
[
142+
"u_projection",
143+
"u_color",
144+
"u_resolution",
145+
"u_line_width",
146+
"u_interp_alpha",
147+
],
148+
["a_corner", "a_start", "a_end", "a_real_start", "a_real_end"],
129149
);
130150
const wick: WickCache = {
131151
...wickPartial,
@@ -375,6 +395,20 @@ function drawWicks(
375395
gl.uniform2f(cache.u_resolution, gl.canvas.width, gl.canvas.height);
376396
gl.uniform1f(cache.u_line_width, chart._pluginConfig.wick_width_px * dpr);
377397

398+
// `line-uniform` was extended for the Y-Line interpolate feature
399+
// with a per-segment alpha multiplier; neutralize it here.
400+
// Constant attribute values (used when the array is disabled) and
401+
// uniform are stable for every draw, so set once after
402+
// `useProgram`. Disabling the arrays first guards against a prior
403+
// Y-Line draw that left them enabled at the same attribute index
404+
// (locations are shared because both programs link from the same
405+
// source).
406+
gl.disableVertexAttribArray(cache.a_real_start);
407+
gl.disableVertexAttribArray(cache.a_real_end);
408+
gl.vertexAttrib1f(cache.a_real_start, 1.0);
409+
gl.vertexAttrib1f(cache.a_real_end, 1.0);
410+
gl.uniform1f(cache.u_interp_alpha, 1.0);
411+
378412
const instancing = getInstancing(glManager);
379413
const { setDivisor } = instancing;
380414

packages/viewer-charts/src/ts/charts/candlestick/glyphs/draw-ohlc.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,23 @@ interface OHLCCache {
3030
u_color: WebGLUniformLocation | null;
3131
u_resolution: WebGLUniformLocation | null;
3232
u_line_width: WebGLUniformLocation | null;
33+
34+
/**
35+
* `line-uniform` was extended to support the Y-Line `interpolate`
36+
* feature with a per-segment alpha multiplier driven by
37+
* `a_real_start * a_real_end` and `u_interp_alpha`. The OHLC glyph
38+
* doesn't need that — every segment is "real" — so we hold these
39+
* locations to neutralize them at draw time (uniform = 1.0,
40+
* constant attribute values = 1.0). Without that, an unset uniform
41+
* defaults to 0 and the fragment alpha collapses to 0, rendering
42+
* the entire OHLC glyph invisible.
43+
*/
44+
u_interp_alpha: WebGLUniformLocation | null;
3345
a_corner: number;
3446
a_start: number;
3547
a_end: number;
48+
a_real_start: number;
49+
a_real_end: number;
3650
}
3751

3852
/**
@@ -76,8 +90,14 @@ export class OHLCGlyph {
7690
"line-uniform",
7791
lineVert,
7892
lineFrag,
79-
["u_projection", "u_color", "u_resolution", "u_line_width"],
80-
["a_corner", "a_start", "a_end"],
93+
[
94+
"u_projection",
95+
"u_color",
96+
"u_resolution",
97+
"u_line_width",
98+
"u_interp_alpha",
99+
],
100+
["a_corner", "a_start", "a_end", "a_real_start", "a_real_end"],
81101
);
82102
this._program = {
83103
...partial,
@@ -229,6 +249,20 @@ export class OHLCGlyph {
229249
chart._pluginConfig.ohlc_line_width_px * dpr,
230250
);
231251

252+
// `line-uniform` was extended for the Y-Line interpolate
253+
// feature with a per-segment alpha multiplier; neutralize it
254+
// here. Constant attribute values (used when the array is
255+
// disabled) and uniform are stable for every draw, so set
256+
// once after `useProgram`. Disabling the arrays first guards
257+
// against a prior Y-Line draw that left them enabled at the
258+
// same attribute index (locations are shared because both
259+
// programs link from the same source).
260+
gl.disableVertexAttribArray(cache.a_real_start);
261+
gl.disableVertexAttribArray(cache.a_real_end);
262+
gl.vertexAttrib1f(cache.a_real_start, 1.0);
263+
gl.vertexAttrib1f(cache.a_real_end, 1.0);
264+
gl.uniform1f(cache.u_interp_alpha, 1.0);
265+
232266
const instancing = getInstancing(glManager);
233267
const { setDivisor } = instancing;
234268

0 commit comments

Comments
 (0)