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
44 changes: 22 additions & 22 deletions examples/blocks/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,23 @@ import { execSync } from "child_process";
const version = JSON.parse(fs.readFileSync("./package.json")).version;
const __dirname = url.fileURLToPath(new URL(".", import.meta.url)).slice(0, -1);

// TODO jsdelivr has slightly different logic for trailing '/' that causes
// the wasm assets to not load correctly when using aliases, hence we must link
// directly to the assets.
const replacements = {
"/node_modules/": `https://cdn.jsdelivr.net/npm/`,
"@perspective-dev/client/dist/cdn/perspective.js": `@perspective-dev/client@${version}/dist/cdn/perspective.js`,
"@perspective-dev/viewer/dist/cdn/perspective-viewer.js": `@perspective-dev/viewer@${version}/dist/cdn/perspective-viewer.js`,
"@perspective-dev/viewer-datagrid/dist/cdn/perspective-viewer-datagrid.js": `@perspective-dev/viewer-datagrid@${version}/dist/cdn/perspective-viewer-datagrid.js`,
"@perspective-dev/viewer-charts/dist/cdn/perspective-viewer-charts.js": `@perspective-dev/viewer-charts@${version}/dist/cdn/perspective-viewer-charts.js`,
"@perspective-dev/server/dist/cdn/perspective.cpp.wasm": `@perspective-dev/client@${version}/dist/cdn/perspective.cpp.wasm`,
"@perspective-dev/viewer/dist/cdn/perspective.rx.wasm": `@perspective-dev/viewer@${version}/dist/cdn/perspective.rx.wasm`,
"@perspective-dev/client/dist/cdn/perspective.worker.js": `@perspective-dev/client@${version}/dist/cdn/perspective.worker.js`,
};
const superstore_version = JSON.parse(
fs.readFileSync(`${__dirname}/node_modules/superstore-arrow/package.json`),
).version;

export function rewrite_cdn_urls(filecontents) {
return filecontents
.replace(
/\/node_modules\/@perspective-dev\/([A-Za-z0-9_-]+)\//g,
(_, pkg) =>
`https://cdn.jsdelivr.net/npm/@perspective-dev/${pkg}@${version}/`,
)
.replace(
/\/node_modules\/superstore-arrow\//g,
`https://cdn.jsdelivr.net/npm/superstore-arrow@${superstore_version}/`,
)
.replace(/\/node_modules\//g, `https://cdn.jsdelivr.net/npm/`);
}

export async function dist_examples(
outpath = `${__dirname}/../../docs/static/blocks`,
Expand All @@ -56,15 +60,11 @@ export async function dist_examples(
filename.endsWith(".js") ||
filename.endsWith(".html")
) {
let filecontents = fs
.readFileSync(`${__dirname}/src/${name}/${filename}`)
.toString();
for (const pattern of Object.keys(replacements)) {
filecontents = filecontents.replace(
new RegExp(pattern, "g"),
replacements[pattern],
);
}
const filecontents = rewrite_cdn_urls(
fs
.readFileSync(`${__dirname}/src/${name}/${filename}`)
.toString(),
);
fs.writeFileSync(
`${outpath}/${name}/${filename}`,
filecontents,
Expand Down
4 changes: 3 additions & 1 deletion packages/react/test/js/react.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ test.describe("Perspective React", () => {
const name = "abcdef";
const comp = await mount(<SingleView name={name} />);
const addViewer = comp.locator("button.add-viewer");
const settingsBtn = comp.locator(`perspective-viewer #settings_button`);
const settingsBtn = comp.locator(
`perspective-viewer perspective-viewer-tab .psp-tab-settings`,
);

await settingsBtn.waitFor();
await addViewer.waitFor();
Expand Down
29 changes: 12 additions & 17 deletions packages/viewer-charts/src/ts/charts/candlestick/candlestick.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,16 @@ export class CandlestickChart extends CategoricalYChart {
_yDomain: { min: number; max: number } = { min: 0, max: 1 };

/**
* `domain_mode: "expand"` accumulators. Hold the running union of
* the value-axis (and, in numeric-category mode, category-axis)
* extent across data loads. Cleared in `resetExpandedDomain` —
* wired from the worker's `resetAllZooms` and from view-config
* mutations on `AbstractChart`. `null` whenever the option is
* `"fit"` or the accumulator has just been cleared.
* `domain_mode: "expand"` accumulator — the VALUE (Y) axis only.
* The category axis has NO accumulator on band charts (see
* `SeriesChart._expandedLeftDomain`): a numeric/datetime `group_by`
* axis always fits the current data. Cleared in
* `resetExpandedDomain` — wired from the worker's `resetAllZooms`
* and from view-config mutations on `AbstractChart`. `null`
* whenever the option is `"fit"` or the accumulator has just been
* cleared.
*/
_expandedYDomain: { min: number; max: number } | null = null;
_expandedCategoryDomain: { min: number; max: number } | null = null;

/**
* Numeric category-axis state (single non-string group_by).
Expand Down Expand Up @@ -267,22 +268,17 @@ export class CandlestickChart extends CategoricalYChart {
scratchCandles: this._candles,
});
// `domain_mode: "expand"` post-build union — mirrors the series
// pipeline. `expandDomainInPlace` mutates `result.*` so the
// assignments below pick up the grown extent automatically.
// pipeline: VALUE (Y) axis only, never the category axis (see
// `SeriesChart.uploadAndRender`). `expandDomainInPlace` mutates
// `result.yDomain` so the assignment below picks up the grown
// extent automatically.
if (this._pluginConfig.domain_mode === "expand") {
this._expandedYDomain = expandDomainInPlace(
this._expandedYDomain,
result.yDomain,
);
if (result.numericCategoryDomain) {
this._expandedCategoryDomain = expandDomainInPlace(
this._expandedCategoryDomain,
result.numericCategoryDomain,
);
}
} else {
this._expandedYDomain = null;
this._expandedCategoryDomain = null;
}

this._rowPaths = result.rowPaths;
Expand Down Expand Up @@ -320,7 +316,6 @@ export class CandlestickChart extends CategoricalYChart {

override resetExpandedDomain(): void {
this._expandedYDomain = null;
this._expandedCategoryDomain = null;
}

protected destroyInternal(): void {
Expand Down
29 changes: 24 additions & 5 deletions packages/viewer-charts/src/ts/charts/chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,19 @@ export interface PluginConfig {
* Faceting strategy when `split_by` is non-empty.
*
* - `"grid"` — one small-multiple sub-plot per split group.
* - `"overlay"` — single plot with split groups differentiated by
* color. Synced into `_facetConfig.facet_mode`.
* - `"overlay"` — a single plot: cartesian charts differentiate
* split groups by color; the categorical band pipeline (series
* charts) stacks splits within each aggregate's band slot.
* Synced into `_facetConfig.facet_mode`.
*
* The default differs by family via
* `ChartTypeConfig.plugin_field_defaults`: cartesian / density /
* map chart types default to `"grid"`, the series / financial
* band-pipeline types to `"overlay"` (their historical split
* rendering). Series charts REBUILD on a mode change — grid mode
* keys the stack ladder per split (`facetSplits` in
* `buildSeriesPipeline`) so each facet grows from its own
* baseline.
*/
facet_mode: "grid" | "overlay";

Expand Down Expand Up @@ -302,15 +313,23 @@ export interface PluginConfig {
/**
* Domain accumulation policy across successive `View` updates.
*
* - `"fit"` — every update recomputes the rendered domain (and on
* cartesian charts, the X/Y range and color/size scales) from
* - `"fit"` — every update recomputes the affected domains from
* the current data extent. Can grow or shrink frame-to-frame.
* - `"expand"` — the rendered domain monotonically *grows*: each
* - `"expand"` — the affected domains monotonically *grow*: each
* update unions the new data extent with the previously rendered
* extent, so once a value is in scope it stays in scope. Reset
* by the "Reset Zoom" button, view-config changes (group_by /
* split_by / column-slot / column-type), or toggling back to
* `"fit"`.
*
* AXIS SCOPE differs by family: cartesian charts (X/Y Scatter,
* X/Y Line, Density, Maps) apply it to BOTH axes plus the
* color/size scales (categorical string axes opt out — slot
* indices are frame-local); the categorical band pipeline (series
* / financial) applies it to the VALUE axis only — Y for the
* Y-family, X for X Bar — while the category axis always fits, so
* a streaming numeric/datetime `group_by` axis releases departed
* categories instead of pinning to its history.
*/
domain_mode: "fit" | "expand";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,11 @@ function formatLevelValue(
valid: boolean,
levelType: string,
): string {
if (!valid) {
// `value` can be `undefined` even when `valid` claims otherwise — an
// out-of-bounds read when a row window outran the delivered Arrow
// (belt to `loadAndRender`'s delivered-rows clamp) — treat it as
// null rather than crash the label formatters.
if (!valid || value == null || Number.isNaN(value)) {
return "";
}

Expand Down
11 changes: 10 additions & 1 deletion packages/viewer-charts/src/ts/charts/series/glyphs/draw-areas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,8 @@ export class AreaGlyph {

/**
* Bind persistent strip buffers and dispatch one TRIANGLE_STRIP per
* series-run. Skips hidden series.
* series-run. Skips hidden series. `splitFilter` (faceted frames)
* draws only the series whose `splitIdx` matches.
*/
draw(
chart: SeriesChart,
Expand All @@ -196,6 +197,7 @@ export class AreaGlyph {
projLeft: Float32Array,
projRight: Float32Array,
opacity: number,
splitFilter?: number,
): void {
const buf = this._buffers;
const cache = this._program;
Expand All @@ -212,6 +214,13 @@ export class AreaGlyph {
continue;
}

if (
splitFilter !== undefined &&
chart._series[s.seriesId].splitIdx !== splitFilter
) {
continue;
}

gl.uniformMatrix4fv(
cache.u_projection,
false,
Expand Down
39 changes: 30 additions & 9 deletions packages/viewer-charts/src/ts/charts/series/glyphs/draw-bars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,20 @@ type GL = WebGL2RenderingContext | WebGLRenderingContext;
/**
* Draw the bar-typed subset of `chart._bars` as instanced quads. Assumes
* the caller has already `useProgram`'d the bar shader and set uniforms.
*
* `range` draws a contiguous instance sub-range instead of the full
* uploaded set — the faceted renderer uploads instances split-major
* (see `uploadBarInstances`) and dispatches one range per facet.
*/
export function drawBars(
chart: SeriesChart,
gl: GL,
glManager: WebGLContextManager,
range?: { start: number; count: number },
): void {
if (chart._uploadedBars === 0) {
const first = range?.start ?? 0;
const count = range?.count ?? chart._uploadedBars;
if (chart._uploadedBars === 0 || count === 0) {
return;
}

Expand All @@ -62,45 +69,59 @@ export function drawBars(
loc.a_x_center,
"bar_x",
1,
first,
) &&
bindInstancedFloatAttr(
glManager,
instancing,
loc.a_half_width,
"bar_hw",
1,
first,
) &&
bindInstancedFloatAttr(
glManager,
instancing,
loc.a_y0,
"bar_y0",
1,
first,
) &&
bindInstancedFloatAttr(
glManager,
instancing,
loc.a_y1,
"bar_y1",
1,
first,
) &&
bindInstancedFloatAttr(glManager, instancing, loc.a_y0, "bar_y0", 1) &&
bindInstancedFloatAttr(glManager, instancing, loc.a_y1, "bar_y1", 1) &&
bindInstancedFloatAttr(
glManager,
instancing,
loc.a_color,
"bar_color",
3,
first,
) &&
bindInstancedFloatAttr(
glManager,
instancing,
loc.a_series_id,
"bar_sid",
1,
first,
) &&
bindInstancedFloatAttr(
glManager,
instancing,
loc.a_axis,
"bar_axis",
1,
first,
);

if (ok) {
instancing.drawArraysInstanced(
gl.TRIANGLE_STRIP,
0,
4,
chart._uploadedBars,
);
instancing.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, count);
}

setDivisor(loc.a_x_center, 0);
Expand Down
11 changes: 10 additions & 1 deletion packages/viewer-charts/src/ts/charts/series/glyphs/draw-lines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,14 +271,16 @@ export class LineGlyph {
* Bind the persistent vertex buffers and dispatch one instanced draw
* per series. Skips hidden series via `_hiddenSeries`. Gap /
* transparency rendering is governed by `u_interp_alpha`, set per
* series.
* series. `splitFilter` (faceted frames) draws only the series
* whose `splitIdx` matches — one call per facet.
*/
draw(
chart: SeriesChart,
gl: GL,
glManager: WebGLContextManager,
projLeft: Float32Array,
projRight: Float32Array,
splitFilter?: number,
): void {
const buf = this._buffers;
const cache = this._program;
Expand Down Expand Up @@ -310,6 +312,13 @@ export class LineGlyph {
continue;
}

if (
splitFilter !== undefined &&
chart._series[s.seriesId].splitIdx !== splitFilter
) {
continue;
}

gl.uniformMatrix4fv(
cache.u_projection,
false,
Expand Down
Loading
Loading