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
1 change: 1 addition & 0 deletions docs/md/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
- [Arranging panels: layouts and dashboards](./how_to/javascript/layout.md)
- [Listening for events](./how_to/javascript/events.md)
- [Plugin render limits](./how_to/javascript/plugin_settings.md)
- [Configuration defaults](./how_to/javascript/config_defaults.md)
- [Map tile sources](./how_to/javascript/map_tile_sources.md)
- [Configuring the LLM agent](./how_to/javascript/agent.md)
- [Virtual Servers](./how_to/javascript/virtual_server.md)
Expand Down
112 changes: 112 additions & 0 deletions docs/md/how_to/javascript/config_defaults.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Configuration defaults

Every plugin declares a default for each `plugin_config` and `columns_config`
key it understands. Two static methods on the `<perspective-viewer>` element
class replace those defaults for every viewer on the page:

```javascript
const Viewer = customElements.get("perspective-viewer");

// Every plugin that declares `group_pad` now defaults it to 10.
Viewer.setPluginConfigDefaults({ group_pad: 10 });

// Datetime columns render in UTC in every plugin.
Viewer.setColumnConfigDefaults(
{ date_format: { timeZone: "UTC" } },
{ type: "datetime" },
);
```

A replaced default behaves exactly like the plugin's own:

- The settings and column style editors show it as the unedited value.
- A value equal to it is left out of `save()`, `saveWorkspace()` and the
`perspective-config-update` event, so a column nobody edited saves nothing.
- The plugin renders it wherever the stored config leaves the key out.
- `restore()` drops a value equal to it, like any other default.

Defaults are part of the host application, like registered plugins and themes.
They are never saved, and `restore()` and `reset()` never change them.

## Narrowing

Both methods take an `options` object:

| Option | Method | Effect |
| -------- | ------------------------- | -------------------------------------------------------------------------------------------------------- |
| `plugin` | both | Applies only to the named plugin, e.g. `"Datagrid"` or `"X/Y Line"` |
| `type` | `setColumnConfigDefaults` | Applies only to columns of this view type: `integer`, `float`, `string`, `boolean`, `date` or `datetime` |

Without `plugin`, a default applies to every plugin that declares the key and is
ignored by the rest. When several defaults set the same key, the most specific
wins:

| Precedence (low to high) |
| ----------------------------------------------------- |
| The plugin's declared default |
| `setColumnConfigDefaults(defaults)` |
| `setColumnConfigDefaults(defaults, { type })` |
| `setColumnConfigDefaults(defaults, { plugin })` |
| `setColumnConfigDefaults(defaults, { plugin, type })` |
| The column's own stored config |

`plugin_config` defaults follow the same order without the `type` levels.

## Merging and removing

Each call merges into the defaults set by earlier calls with the same options.
`null` removes a default at any depth:

| Call | Effect |
| ------------------------------------- | -------------------------------------------- |
| `{ group_pad: 10 }` | Sets `group_pad`, keeping everything else |
| `{ group_pad: null }` | Removes `group_pad` |
| `{ date_format: { timeZone: null } }` | Removes only `timeZone` from `date_format` |
| `null` | Removes every default set with these options |

`getPluginConfigDefaults()` and `getColumnConfigDefaults()` return the current
defaults, one entry per combination of options.

## Formats

`date_format` and `number_format` defaults combine with the plugin's declared
format and with a column's stored format by group rather than as a whole:

| Format | Applies as one group | Applies individually |
| --------------- | ----------------------------------------------------------------------------------------------------------------- | -------------------- |
| `date_format` | `dateStyle`, `timeStyle`, `weekday`, `year`, `month`, `day`, `hour`, `minute`, `second`, `fractionalSecondDigits` | `timeZone`, `hour12` |
| `number_format` | `style` and its `currency*`/`unit*` options; `notation` and `compactDisplay` | everything else |

So a page-wide `timeZone` reaches a column whose user picked a long date, while
a default `dateStyle` never mixes with a column's own per-part fields. A
`number_format` default that sets one digit bound inherits the other from the
plugin, so `{ maximumFractionDigits: 4 }` on a datagrid float column means 2 to
4 digits.

In the charts, a `date_format` default that sets only `timeZone` or `hour12`
keeps the adaptive axis labels, which choose their precision from the zoom
level. A default that sets a display field, or any `number_format` default,
fixes that type's axis labels to the format, as a format chosen in the column
style editor does.

## Setting defaults before restoring

Call these methods before `load()` and `restore()`. `restore()` drops values
equal to the defaults in force at that moment, so a config restored first loses
any value equal to the plugin's own default, and a default set afterwards then
replaces it.

Saved configs leave out values equal to the defaults. Restoring one in an
application without the same defaults renders the plugins' own defaults instead.

A default for a key whose plugin default comes from the column's data, such as a
gradient's maximum, replaces that computed value with the fixed one.

## Errors

A call throws when its arguments are malformed: `defaults` is not an object or
`null`, `plugin` names no registered plugin, `type` is not a column type, or a
`date_format` mixes `dateStyle`/`timeStyle` with per-part fields. A value that a
particular plugin cannot use, such as an `Enum` value outside its choices or a
number outside its range, is logged as an error naming the plugin, column and
key, and that plugin keeps its own default.
25 changes: 25 additions & 0 deletions docs/md/how_to/javascript/save_restore.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,31 @@ name. Only the single-panel `restore()` accepts the inline form — in

See [Derived tables](../../explanation/derived_table.md).

## Datetime formats

A `date` or `datetime` column's `date_format` is a subset of
`Intl.DateTimeFormatOptions`: either the `dateStyle`/`timeStyle` presets or
per-part fields (`weekday`, `year`, `month`, `day`, `hour`, `minute`, `second`,
`fractionalSecondDigits`), never both, plus optional `timeZone` and `hour12`. As
in `Intl`, a display field that is left out is not shown:

```javascript
await viewer.restore({
columns_config: {
"Order Date": { date_format: { dateStyle: "long", timeZone: "UTC" } },
},
});
```

A column inherits the plugin's display fields until it sets any of them, and
inherits `timeZone` and `hour12` individually.

Configs saved before 5.7.0 used a `"disabled"` value and a `format: "custom"`
marker, and a missing preset field meant `short` date or `medium` time.
`restore()` rewrites these into the current form with the same rendering. A
hand-written config with no `version` and a missing preset field takes the
current meaning: `{ dateStyle: "long" }` now shows the date alone.

## Colors, palettes and gradients

Per-column color styling lives in a panel's `columns_config`, keyed by column
Expand Down
13 changes: 11 additions & 2 deletions docs/md/how_to/javascript/viewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,15 @@ for charts and text for the datagrid.
| `restyleElement()` | Re-read CSS and repaint |
| `getPlugin(name?)` / `getAllPlugins()` | Look up registered plugins |

The element class also has page-wide static methods:

| Method | Description |
|---|---|
| `registerPlugin(name)` | Register a plugin custom element |
| `setPluginConfigDefaults(defaults, options?)` / `setColumnConfigDefaults(defaults, options?)` | Replace plugins' `plugin_config` / `columns_config` defaults |
| `getPluginConfigDefaults()` / `getColumnConfigDefaults()` | Read the replaced defaults |

See [Saving and restoring UI state](./save_restore.md) for the `save`/`restore`
formats and the panel selector, and
[Plugin render limits](./plugin_settings.md) for `getPlugin`.
formats and the panel selector,
[Plugin render limits](./plugin_settings.md) for `getPlugin`, and
[Configuration defaults](./config_defaults.md) for the defaults methods.
2 changes: 1 addition & 1 deletion packages/viewer-charts/src/ts/axis/bar-axis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function tickFormatter(
return override;
}

return stepTickFormatter(domain.isDate, ticks);
return stepTickFormatter(domain.isDate, ticks, domain.dateModifiers);
}

/**
Expand Down
6 changes: 5 additions & 1 deletion packages/viewer-charts/src/ts/axis/numeric-axis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,16 @@ import {
drawYTickColumn,
} from "./axis-primitives";
import type { Theme } from "../theme/theme";
import type { DateFormatModifiers } from "@perspective-dev/viewer/column-format";

export interface AxisDomain {
min: number;
max: number;
label: string;
isDate?: boolean;

/** The `timeZone`/`hour12` adaptive date labels on this axis apply. */
dateModifiers?: DateFormatModifiers;
}

export interface TickResult {
Expand Down Expand Up @@ -77,7 +81,7 @@ function tickFmt(
return override;
}

return stepTickFormatter(domain.isDate, ticks);
return stepTickFormatter(domain.isDate, ticks, domain.dateModifiers);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

import type { DateFormatModifiers } from "@perspective-dev/viewer/column-format";
import type { ColumnDataMap } from "../../data/view-reader";
import { buildSplitGroups } from "../../data/split-groups";
import type { CategoricalLevel } from "../../axis/categorical-axis";
Expand Down Expand Up @@ -156,6 +157,9 @@ export interface CandlestickPipelineInput {
*/
groupByTypes: Record<string, string>;

/** Per-column `timeZone`/`hour12` adaptive date labels apply. */
dateModifiers?: ReadonlyMap<string, DateFormatModifiers>;

/**
* Band-slot geometry knobs sourced from
* {@link PluginConfig.band_inner_frac} / `bar_inner_pad`. Forwarded
Expand Down Expand Up @@ -230,6 +234,7 @@ export function buildCandlestickPipeline(
groupBy,
splitBy,
groupByTypes,
dateModifiers,
bandInnerFrac,
barInnerPad,
scratchCandles,
Expand Down Expand Up @@ -282,6 +287,7 @@ export function buildCandlestickPipeline(
groupBy.length,
levelTypes,
axisMode,
groupBy.map((name) => dateModifiers?.get(name)),
);
if (numCategories === 0) {
return {
Expand Down Expand Up @@ -321,6 +327,9 @@ export function buildCandlestickPipeline(
groupBy[0] ?? "",
axisMode.numericType === "date" ||
axisMode.numericType === "datetime",
axisMode.numericType === "datetime"
? dateModifiers?.get(groupBy[0])
: undefined,
);
if (resolved) {
categoryPositions = resolved.categoryPositions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ function paintCandlestickChromeOverlay(chart: CandlestickChart): void {
max: chart._numericCategoryDomain.max,
isDate: chart._numericCategoryDomain.isDate,
label: chart._numericCategoryDomain.label,
dateModifiers: chart._numericCategoryDomain.dateModifiers,
},
ticks: chart._lastCatTicks,
};
Expand Down Expand Up @@ -355,6 +356,7 @@ function renderCandlestickTooltip(chart: CandlestickChart): void {
stepTickFormatter(
chart._numericCategoryDomain.isDate,
chart._lastCatTicks,
chart._numericCategoryDomain.dateModifiers,
);
categoryText = catFmt(chart._categoryPositions[candles.catIdx[i]]);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ export class CandlestickChart extends CategoricalYChart {
groupBy: this._groupBy,
splitBy: this._splitBy,
groupByTypes: this._groupByTypes,
dateModifiers: this._columnDateModifiers,
bandInnerFrac: this._pluginConfig.band_inner_frac,
barInnerPad: this._pluginConfig.bar_inner_pad,
scratchCandles: this._candles,
Expand Down
18 changes: 16 additions & 2 deletions packages/viewer-charts/src/ts/charts/cartesian/cartesian-render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,9 @@ function buildXDomain(
label:
chart._xLabel || (chart._xIsRowIndex ? "Row" : chart._xName || ""),
isDate,
dateModifiers: isDate
? chart.getDateModifiers(chart._xName)
: undefined,
};
}

Expand All @@ -302,6 +305,9 @@ function buildYDomain(
max,
label: chart._yLabel || chart._yName,
isDate,
dateModifiers: isDate
? chart.getDateModifiers(chart._yName)
: undefined,
};
}

Expand Down Expand Up @@ -1424,10 +1430,18 @@ function cartesianAxisIndicators(
} else {
const xFmt =
xOverride ??
stepTickFormatter(chart._lastXDomain?.isDate, chart._lastXTicks);
stepTickFormatter(
chart._lastXDomain?.isDate,
chart._lastXTicks,
chart._lastXDomain?.dateModifiers,
);
const yFmt =
yOverride ??
stepTickFormatter(chart._lastYDomain?.isDate, chart._lastYTicks);
stepTickFormatter(
chart._lastYDomain?.isDate,
chart._lastYTicks,
chart._lastYDomain?.dateModifiers,
);
xText = xFmt(dataX);
yText = yFmt(dataY);
}
Expand Down
Loading
Loading