@fundar/data-chart-telling 1.17.0 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/dist/configuration/config.svelte.js +3 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +2 -0
- package/dist/layout/geometry/GeometryLayout.svelte +188 -54
- package/dist/layout/hover/HoverDefaults.d.ts +52 -0
- package/dist/layout/hover/HoverDefaults.js +96 -0
- package/dist/layout/hover/HoverLayers.d.ts +35 -0
- package/dist/layout/hover/HoverLayers.js +122 -0
- package/dist/layout/hover/HoverSettings.d.ts +1 -3
- package/dist/layout/hover/HoverSettings.js +3 -2
- package/dist/layout/hover/HoverShapes.d.ts +44 -0
- package/dist/layout/hover/HoverShapes.js +103 -0
- package/dist/layout/hover/PlotHover.svelte.d.ts +18 -0
- package/dist/layout/hover/PlotHover.svelte.js +54 -1
- package/dist/layout/legend/LegendState.svelte.d.ts +2 -0
- package/dist/layout/legend/LegendState.svelte.js +5 -0
- package/dist/layout/plot/Core.svelte +2 -2
- package/dist/layout/plot/PublicPlotContext.d.ts +6 -0
- package/dist/layout/plot/PublicPlotContext.js +8 -0
- package/dist/layout/styles/ValueLabels.d.ts +12 -4
- package/dist/layout/styles/ValueLabels.js +22 -13
- package/dist/layout/tooltip/TooltipState.svelte.d.ts +8 -9
- package/dist/layout/tooltip/TooltipState.svelte.js +28 -29
- package/dist/markers/MarkerKinds.d.ts +5 -0
- package/dist/markers/MarkerKinds.js +1 -0
- package/dist/markers/band/BandMarker.svelte +74 -0
- package/dist/markers/band/BandMarker.svelte.d.ts +7 -0
- package/dist/markers/band/Bands.d.ts +16 -0
- package/dist/markers/band/Bands.js +48 -0
- package/dist/markers/bar/BarMarker.svelte +23 -66
- package/dist/markers/hover/HoverMarker.svelte +7 -265
- package/dist/markers/hover/HoverMarker.svelte.d.ts +4 -27
- package/dist/markers/utils/PointPixel.d.ts +37 -0
- package/dist/markers/utils/PointPixel.js +44 -0
- package/dist/phases/Pipeline.d.ts +7 -7
- package/dist/phases/Pipeline.js +12 -9
- package/dist/phases/context/ContextPhase.d.ts +22 -0
- package/dist/phases/context/ContextPhase.js +23 -0
- package/dist/phases/context/RowContext.d.ts +35 -0
- package/dist/phases/context/RowContext.js +95 -0
- package/dist/phases/data/DataPhase.d.ts +4 -4
- package/dist/phases/data/DataPhase.js +7 -7
- package/dist/phases/hover/HoverPhase.d.ts +6 -7
- package/dist/phases/hover/HoverPhase.js +28 -23
- package/dist/phases/mapping/MappingPhase.d.ts +4 -4
- package/dist/phases/mapping/MappingPhase.js +2 -2
- package/dist/phases/markers/MarkerStyles.d.ts +8 -0
- package/dist/phases/markers/MarkerStyles.js +15 -0
- package/dist/phases/markers/MarkersPhase.d.ts +4 -4
- package/dist/phases/markers/MarkersPhase.js +4 -4
- package/dist/phases/scales/ScalesPhase.d.ts +4 -4
- package/dist/phases/scales/ScalesPhase.js +4 -4
- package/dist/phases/sharedScales/SharedScalesPhase.d.ts +2 -2
- package/dist/phases/sharedScales/SharedScalesPhase.js +2 -2
- package/dist/phases/statistics/MarkerGrouping.d.ts +0 -9
- package/dist/phases/statistics/MarkerGrouping.js +0 -16
- package/dist/phases/styles/StylesPhase.d.ts +3 -3
- package/dist/phases/styles/StylesPhase.js +2 -2
- package/dist/phases/tooltip/TooltipPhase.d.ts +4 -4
- package/dist/phases/tooltip/TooltipPhase.js +10 -7
- package/dist/phases/transforms/TransformsPhase.d.ts +3 -3
- package/dist/phases/transforms/TransformsPhase.js +2 -2
- package/dist/plots/correlation/heatmap/defaults.d.ts +2 -2
- package/dist/plots/correlation/heatmap/defaults.js +21 -6
- package/dist/plots/correlation/heatmap/markers.d.ts +2 -2
- package/dist/plots/correlation/heatmap/markers.js +4 -3
- package/dist/plots/correlation/scatter/defaults.d.ts +2 -2
- package/dist/plots/correlation/scatter/defaults.js +9 -3
- package/dist/plots/correlation/scatter/markers.d.ts +2 -2
- package/dist/plots/correlation/scatter/markers.js +4 -3
- package/dist/plots/distribution/box/BoxSummaries.d.ts +17 -8
- package/dist/plots/distribution/box/BoxSummaries.js +90 -40
- package/dist/plots/distribution/box/defaults.d.ts +2 -2
- package/dist/plots/distribution/box/defaults.js +9 -6
- package/dist/plots/distribution/box/markers.d.ts +2 -2
- package/dist/plots/distribution/box/markers.js +3 -3
- package/dist/plots/distribution/density/defaults.d.ts +2 -2
- package/dist/plots/distribution/density/defaults.js +17 -1
- package/dist/plots/distribution/density/markers.d.ts +2 -2
- package/dist/plots/distribution/density/markers.js +4 -4
- package/dist/plots/distribution/histogram/defaults.d.ts +2 -2
- package/dist/plots/distribution/histogram/defaults.js +20 -25
- package/dist/plots/distribution/histogram/markers.d.ts +2 -2
- package/dist/plots/distribution/histogram/markers.js +5 -4
- package/dist/plots/distribution/pyramid/defaults.d.ts +2 -2
- package/dist/plots/distribution/pyramid/defaults.js +4 -1
- package/dist/plots/distribution/pyramid/markers.d.ts +2 -2
- package/dist/plots/distribution/pyramid/markers.js +4 -3
- package/dist/plots/distribution/violin/defaults.d.ts +2 -2
- package/dist/plots/distribution/violin/defaults.js +9 -7
- package/dist/plots/distribution/violin/markers.d.ts +2 -2
- package/dist/plots/distribution/violin/markers.js +3 -3
- package/dist/plots/evolution/area/defaults.d.ts +2 -2
- package/dist/plots/evolution/area/defaults.js +1 -1
- package/dist/plots/evolution/area/markers.d.ts +2 -2
- package/dist/plots/evolution/area/markers.js +4 -4
- package/dist/plots/evolution/line/defaults.d.ts +2 -2
- package/dist/plots/evolution/line/defaults.js +1 -1
- package/dist/plots/evolution/line/markers.d.ts +2 -2
- package/dist/plots/evolution/line/markers.js +3 -3
- package/dist/plots/evolution/slope/defaults.d.ts +2 -2
- package/dist/plots/evolution/slope/defaults.js +8 -4
- package/dist/plots/evolution/slope/markers.d.ts +2 -2
- package/dist/plots/evolution/slope/markers.js +3 -3
- package/dist/plots/evolution/sparkline/defaults.d.ts +2 -2
- package/dist/plots/evolution/sparkline/defaults.js +5 -2
- package/dist/plots/evolution/sparkline/markers.d.ts +2 -2
- package/dist/plots/evolution/sparkline/markers.js +3 -3
- package/dist/plots/map/geo/Core.svelte +3 -3
- package/dist/plots/map/geo/defaults.d.ts +2 -2
- package/dist/plots/map/geo/phases/data.d.ts +1 -1
- package/dist/plots/map/geo/phases/data.js +1 -1
- package/dist/plots/map/geo/phases/hover.d.ts +2 -2
- package/dist/plots/map/geo/phases/hover.js +3 -3
- package/dist/plots/map/geo/phases/markers.d.ts +2 -2
- package/dist/plots/map/geo/phases/markers.js +3 -3
- package/dist/plots/map/geo/phases/scales.d.ts +2 -2
- package/dist/plots/map/geo/phases/scales.js +2 -2
- package/dist/plots/part-of-a-whole/treemap/defaults.d.ts +2 -2
- package/dist/plots/part-of-a-whole/treemap/defaults.js +37 -21
- package/dist/plots/ranking/bar/defaults.d.ts +2 -2
- package/dist/plots/ranking/bar/defaults.js +13 -7
- package/dist/plots/ranking/bar/markers.d.ts +3 -3
- package/dist/plots/ranking/bar/markers.js +8 -7
- package/dist/plots/ranking/bullet/defaults.d.ts +2 -2
- package/dist/plots/ranking/bullet/defaults.js +4 -4
- package/dist/plots/ranking/bullet/markers.d.ts +3 -3
- package/dist/plots/ranking/bullet/markers.js +9 -9
- package/dist/plots/ranking/cleveland/ClevelandMarkers.d.ts +3 -3
- package/dist/plots/ranking/cleveland/ClevelandMarkers.js +11 -10
- package/dist/plots/ranking/cleveland/defaults.d.ts +2 -2
- package/dist/plots/ranking/cleveland/defaults.js +7 -4
- package/dist/plots/ranking/dumbbell/defaults.d.ts +2 -2
- package/dist/plots/ranking/forest/defaults.d.ts +2 -2
- package/dist/plots/ranking/lollipop/defaults.d.ts +2 -2
- package/dist/types/configuration/styling.d.ts +4 -0
- package/dist/types/layout/context.d.ts +21 -0
- package/dist/types/layout/context.js +1 -0
- package/dist/types/layout/hover.d.ts +3 -0
- package/dist/types/layout/styles/values.d.ts +8 -2
- package/dist/types/layout/tooltip.d.ts +5 -10
- package/dist/types/markers/axis/axisBasedMarkersConfig.d.ts +2 -1
- package/dist/types/markers/axis/band.d.ts +24 -0
- package/dist/types/markers/axis/band.js +1 -0
- package/dist/types/markers/axis/bar.d.ts +2 -2
- package/dist/types/markers/axis/hover.d.ts +60 -15
- package/dist/types/markers/channels/commonMarkerConfig.d.ts +6 -2
- package/dist/types/markers/common.d.ts +1 -0
- package/dist/types/markers/common.js +1 -0
- package/dist/types/phases/context/phase.d.ts +7 -0
- package/dist/types/phases/context/phase.js +1 -0
- package/dist/types/phases/data/phase.d.ts +2 -2
- package/dist/types/phases/hover/phase.d.ts +10 -7
- package/dist/types/phases/index.d.ts +2 -1
- package/dist/types/phases/kind.d.ts +11 -8
- package/dist/types/phases/mapping/phase.d.ts +2 -2
- package/dist/types/phases/markers/phase.d.ts +3 -3
- package/dist/types/phases/scales/phase.d.ts +4 -4
- package/dist/types/phases/stage.d.ts +3 -3
- package/dist/types/phases/state.d.ts +1 -1
- package/dist/types/phases/styles/phase.d.ts +3 -2
- package/dist/types/phases/transforms/phase.d.ts +2 -2
- package/dist/types/plots/constants.d.ts +3 -5
- package/dist/types/plots/correlation/heatmap/state.d.ts +2 -2
- package/dist/types/plots/correlation/scatter/state.d.ts +2 -2
- package/dist/types/plots/distribution/box/props.d.ts +23 -1
- package/dist/types/plots/distribution/box/state.d.ts +6 -17
- package/dist/types/plots/distribution/density/state.d.ts +2 -2
- package/dist/types/plots/distribution/histogram/props.d.ts +14 -1
- package/dist/types/plots/distribution/histogram/state.d.ts +2 -2
- package/dist/types/plots/distribution/pyramid/state.d.ts +2 -2
- package/dist/types/plots/distribution/violin/props.d.ts +2 -1
- package/dist/types/plots/distribution/violin/state.d.ts +2 -2
- package/dist/types/plots/evolution/area/state.d.ts +2 -2
- package/dist/types/plots/evolution/line/state.d.ts +2 -2
- package/dist/types/plots/evolution/slope/state.d.ts +2 -2
- package/dist/types/plots/evolution/sparkline/state.d.ts +2 -2
- package/dist/types/plots/map/geo/state.d.ts +1 -1
- package/dist/types/plots/part-of-a-whole/treemap/props.d.ts +14 -4
- package/dist/types/plots/part-of-a-whole/treemap/state.d.ts +2 -2
- package/dist/types/plots/props.d.ts +3 -0
- package/dist/types/plots/ranking/bar/state.d.ts +2 -2
- package/dist/types/plots/ranking/bullet/state.d.ts +2 -2
- package/dist/types/plots/ranking/cleveland/state.d.ts +2 -2
- package/dist/types/plots/ranking/dumbbell/state.d.ts +2 -2
- package/dist/types/plots/ranking/forest/state.d.ts +2 -2
- package/dist/types/plots/ranking/lollipop/state.d.ts +2 -2
- package/docs/markers.md +34 -2
- package/docs/plots/evolution/line.md +1 -1
- package/docs/plots/evolution/slope.md +1 -1
- package/docs/plots/part-of-a-whole/treemap.md +36 -2
- package/docs/plots/ranking/bar.md +1 -0
- package/docs/plots/ranking/cleveland.md +1 -1
- package/docs/state.md +33 -0
- package/docs/styles-and-segments.md +2 -2
- package/docs/theming.md +2 -1
- package/docs/tooltip-and-hover.md +81 -3
- package/examples/LineDates.svelte +1 -1
- package/examples/ResponsiveLine.svelte +3 -1
- package/examples/TreemapHover.svelte +25 -0
- package/examples/ValueLabels.svelte +1 -1
- package/llms-full.txt +8 -6
- package/llms.txt +2 -1
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@ import { seriesIdOf } from '../../utils/grouping';
|
|
|
6
6
|
import { isGapValue } from '../../plots/utils/gaps';
|
|
7
7
|
import { PlotAxes } from '../../phases/data/PlotAxes';
|
|
8
8
|
import { NumberFormat } from '../../utils/NumberFormat';
|
|
9
|
+
import { RowContext } from '../../phases/context/RowContext';
|
|
9
10
|
/**
|
|
10
11
|
* A plot's value labels: the author's `styles.values` over the plot kind's
|
|
11
12
|
* own defaults, how their text measures, where they are expected to sit
|
|
@@ -15,6 +16,11 @@ import { NumberFormat } from '../../utils/NumberFormat';
|
|
|
15
16
|
export class ValueLabels {
|
|
16
17
|
show;
|
|
17
18
|
anchor;
|
|
19
|
+
/**
|
|
20
|
+
* A label's text for `value`: the author's `format`, else the kind's, else the value as the display fields ask.
|
|
21
|
+
* `row` is the row it labels, whose context the format reads; `series` names its series where there is no row
|
|
22
|
+
* (a label measured ahead of the rows).
|
|
23
|
+
*/
|
|
18
24
|
format;
|
|
19
25
|
hoverIsolate;
|
|
20
26
|
font;
|
|
@@ -33,12 +39,10 @@ export class ValueLabels {
|
|
|
33
39
|
this.anchor = values?.anchor ?? options.anchor ?? 'outside';
|
|
34
40
|
// The value as the display fields ask (else as it is), which a plot kind's own text builds on.
|
|
35
41
|
const number = NumberFormat.maybe(NumberFormat.displayOf(values), cfg) ?? ((v) => `${v}`);
|
|
36
|
-
const
|
|
37
|
-
this.format =
|
|
38
|
-
? (v,
|
|
39
|
-
:
|
|
40
|
-
? (v, name) => kindFormat(v, name, number)
|
|
41
|
-
: (v) => number(v);
|
|
42
|
+
const own = values?.format ?? options.format;
|
|
43
|
+
this.format = own
|
|
44
|
+
? (v, row, series) => own(v, number, ValueLabels.contextOf(row, series))
|
|
45
|
+
: (v) => number(v);
|
|
42
46
|
this.hoverIsolate = values?.hoverIsolate ?? false;
|
|
43
47
|
this.font = values?.fontStyle;
|
|
44
48
|
this.fontSize = typeof this.font?.fontSize === 'number' ? this.font.fontSize : 12;
|
|
@@ -48,6 +52,11 @@ export class ValueLabels {
|
|
|
48
52
|
this.#cfg = cfg;
|
|
49
53
|
this.reach = (this.show && reach?.(this)) || [];
|
|
50
54
|
}
|
|
55
|
+
/** The context a label's `format` reads: its row's, with `series` when the row gives none. */
|
|
56
|
+
static contextOf(row, series) {
|
|
57
|
+
const context = RowContext.of(row);
|
|
58
|
+
return context.series === undefined && series !== undefined ? { ...context, series } : context;
|
|
59
|
+
}
|
|
51
60
|
/** A plot's value labels: `values` over `options`, the kind's own defaults, their reach read off `state`. */
|
|
52
61
|
static build(values, cfg, options, state) {
|
|
53
62
|
const { reach } = options;
|
|
@@ -134,8 +143,8 @@ export class ValueLabels {
|
|
|
134
143
|
* dims with its row's series, and with `hoverIsolate`, hovering it
|
|
135
144
|
* isolates that series. A row without a series gets the margins only.
|
|
136
145
|
*/
|
|
137
|
-
hooks(
|
|
138
|
-
const interaction =
|
|
146
|
+
hooks(env) {
|
|
147
|
+
const interaction = env.interaction;
|
|
139
148
|
const seriesOf = (d) => d?.[SERIES_KEY] === undefined ? undefined : String(d[SERIES_KEY]);
|
|
140
149
|
const isolate = this.hoverIsolate && interaction;
|
|
141
150
|
return {
|
|
@@ -190,8 +199,8 @@ export class ValueLabels {
|
|
|
190
199
|
* never reaches out of the plot, so only one `outside` has the margins grow to hold it; and it takes the default
|
|
191
200
|
* `fill` only `outside`, for inside it is written in the color that contrasts with its shape.
|
|
192
201
|
*/
|
|
193
|
-
shapeLabel(
|
|
194
|
-
const { fitMargins, styles: shared, ...events } = this.hooks(
|
|
202
|
+
shapeLabel(env, text, defaults = {}) {
|
|
203
|
+
const { fitMargins, styles: shared, ...events } = this.hooks(env);
|
|
195
204
|
const { font } = this;
|
|
196
205
|
return {
|
|
197
206
|
...events,
|
|
@@ -289,7 +298,7 @@ export class ValueLabels {
|
|
|
289
298
|
x: (processed.channels.x ?? position.x),
|
|
290
299
|
y: (processed.channels.y2 ?? processed.channels.y ?? topOf),
|
|
291
300
|
grouping: SERIES_ID_KEY,
|
|
292
|
-
text: (d) => format(Number(valueOf(d)),
|
|
301
|
+
text: (d) => format(Number(valueOf(d)), d, nameOf(d)),
|
|
293
302
|
styles: {
|
|
294
303
|
...font,
|
|
295
304
|
fill: this.fill ?? ((d) => colorOf(d) ?? 'currentColor'),
|
|
@@ -356,7 +365,7 @@ export class ValueLabels {
|
|
|
356
365
|
const lo = Number(domain[0]);
|
|
357
366
|
const hi = Number(domain[domain.length - 1]);
|
|
358
367
|
for (const s of series)
|
|
359
|
-
texts.push(format(lo, s.name), format(hi, s.name));
|
|
368
|
+
texts.push(format(lo, undefined, s.name), format(hi, undefined, s.name));
|
|
360
369
|
}
|
|
361
370
|
else {
|
|
362
371
|
for (const s of series) {
|
|
@@ -366,7 +375,7 @@ export class ValueLabels {
|
|
|
366
375
|
for (const d of s.data) {
|
|
367
376
|
const v = Number(y(d));
|
|
368
377
|
if (!Number.isNaN(v))
|
|
369
|
-
texts.push(format(v, s.name));
|
|
378
|
+
texts.push(format(v, undefined, s.name));
|
|
370
379
|
}
|
|
371
380
|
}
|
|
372
381
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Box } from '../margins/Box';
|
|
2
|
-
import
|
|
2
|
+
import { PlotAxes } from '../../phases/data/PlotAxes';
|
|
3
3
|
import type { ProcessedData, ProcessedRow } from '../../types/phases';
|
|
4
|
-
import type { TooltipAnchorX, TooltipAnchorY } from '../../types/layout/tooltip';
|
|
5
4
|
import type { Series } from '../../types/plots/data/common';
|
|
5
|
+
import type { TooltipAnchorX, TooltipAnchorY } from '../../types/layout/tooltip';
|
|
6
6
|
/** What a tooltip is shown from, each read reactively. */
|
|
7
7
|
export type TooltipInputs = {
|
|
8
8
|
/** The point it sits beside. */
|
|
@@ -46,16 +46,15 @@ export declare class TooltipState {
|
|
|
46
46
|
/** A value as plain text: a date as a locale date, a number as `number` writes it (when given), anything else as itself. */
|
|
47
47
|
static value(value: unknown, number?: (value: number) => string): string;
|
|
48
48
|
/**
|
|
49
|
-
* How a hovered row reads by default, on every plot: its series,
|
|
50
|
-
* plot groups its rows; then where it sits - `category: value` when one
|
|
51
|
-
* axis
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* (the plot's `y` when neither axis holds categories) and the color's - read as it writes them.
|
|
49
|
+
* How a hovered row reads by default, on every plot, from its context (see {@link RowContext}): its series,
|
|
50
|
+
* when the plot groups its rows; then where it sits - `category: value` when one axis is a band of categories,
|
|
51
|
+
* else `x: y` (an axis the transforms spread into a span, a bin's, as `from-to`); then the value its color
|
|
52
|
+
* shows, when that is a number (a heatmap's cells). With `number`, the values - the one on the value axis and
|
|
53
|
+
* the color's - read as it writes them.
|
|
55
54
|
*/
|
|
56
55
|
static default(state: {
|
|
57
56
|
processedData?: ProcessedData;
|
|
58
|
-
resolvedSeries?: Series<
|
|
57
|
+
resolvedSeries?: Series<any>[];
|
|
59
58
|
channels?: {
|
|
60
59
|
grouping?: unknown;
|
|
61
60
|
color?: {
|
|
@@ -2,7 +2,8 @@ import { Box } from '../margins/Box';
|
|
|
2
2
|
import { PlotMargins } from '../margins/PlotMargins.svelte';
|
|
3
3
|
import { resolveAccessor } from '../../utils/accessors';
|
|
4
4
|
import { SERIES_KEY } from '../../phases/transforms/TransformsPhase';
|
|
5
|
-
import {
|
|
5
|
+
import { RowContext } from '../../phases/context/RowContext';
|
|
6
|
+
import { PlotAxes } from '../../phases/data/PlotAxes';
|
|
6
7
|
/** A tooltip anchor as the side of the point the tooltip sits on. */
|
|
7
8
|
const SIDES = {
|
|
8
9
|
left: 'before',
|
|
@@ -69,29 +70,33 @@ export class TooltipState {
|
|
|
69
70
|
return number && typeof value === 'number' ? number(value) : String(value);
|
|
70
71
|
}
|
|
71
72
|
/**
|
|
72
|
-
* How a hovered row reads by default, on every plot: its series,
|
|
73
|
-
* plot groups its rows; then where it sits - `category: value` when one
|
|
74
|
-
* axis
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* (the plot's `y` when neither axis holds categories) and the color's - read as it writes them.
|
|
73
|
+
* How a hovered row reads by default, on every plot, from its context (see {@link RowContext}): its series,
|
|
74
|
+
* when the plot groups its rows; then where it sits - `category: value` when one axis is a band of categories,
|
|
75
|
+
* else `x: y` (an axis the transforms spread into a span, a bin's, as `from-to`); then the value its color
|
|
76
|
+
* shows, when that is a number (a heatmap's cells). With `number`, the values - the one on the value axis and
|
|
77
|
+
* the color's - read as it writes them.
|
|
78
78
|
*/
|
|
79
79
|
static default(state, number) {
|
|
80
80
|
const processed = state.processedData;
|
|
81
81
|
if (!processed)
|
|
82
82
|
return () => '';
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
// With no band axis, the value is the y.
|
|
86
|
-
const valueY = TooltipState.#axisText(processed, 'y', number);
|
|
83
|
+
const valueAxis = PlotAxes.valueOf(state);
|
|
84
|
+
const contextOf = RowContext.reader(state);
|
|
87
85
|
const bandX = state.axes?.bands.includes('x') ?? false;
|
|
88
86
|
const bandY = state.axes?.bands.includes('y') ?? false;
|
|
89
|
-
const
|
|
87
|
+
const along = (axis) => {
|
|
88
|
+
const span = TooltipState.#spanText(processed, axis, number);
|
|
89
|
+
const measured = axis === valueAxis && !state.axes?.bands.includes(axis);
|
|
90
|
+
return (row) => span ? span(row) : TooltipState.value(contextOf(row)[axis], measured ? number : undefined);
|
|
91
|
+
};
|
|
92
|
+
const x = along('x');
|
|
93
|
+
const y = along('y');
|
|
94
|
+
const valueOf = (row) => TooltipState.value(contextOf(row).value, number);
|
|
90
95
|
const position = bandX && !bandY
|
|
91
|
-
? (row) => `${x(row)}: ${
|
|
96
|
+
? (row) => `${x(row)}: ${valueOf(row)}`
|
|
92
97
|
: bandY && !bandX
|
|
93
|
-
? (row) => `${y(row)}: ${
|
|
94
|
-
: (row) => `${x(row)}: ${
|
|
98
|
+
? (row) => `${y(row)}: ${valueOf(row)}`
|
|
99
|
+
: (row) => `${x(row)}: ${y(row)}`;
|
|
95
100
|
const grouped = state.channels?.grouping !== undefined;
|
|
96
101
|
const color = state.channels?.color;
|
|
97
102
|
const colorName = typeof color?.source === 'string' ? color.source : 'color';
|
|
@@ -106,26 +111,20 @@ export class TooltipState {
|
|
|
106
111
|
};
|
|
107
112
|
}
|
|
108
113
|
/**
|
|
109
|
-
* How
|
|
110
|
-
* the
|
|
114
|
+
* How an axis the transforms spread into a span reads on a processed row - a bin's edges as `from-to` - or
|
|
115
|
+
* `undefined` when the axis has a value of its own or is a stack's (whose own value the context gives).
|
|
111
116
|
*/
|
|
112
|
-
static #
|
|
117
|
+
static #spanText(processed, axis, number) {
|
|
113
118
|
const { channels, transforms } = processed;
|
|
114
|
-
const read = (channel) => resolveAccessor(channel);
|
|
115
|
-
const point = channels[axis];
|
|
116
|
-
if (point !== undefined) {
|
|
117
|
-
const value = read(point);
|
|
118
|
-
return (row) => TooltipState.value(value(row), number);
|
|
119
|
-
}
|
|
120
119
|
const low = channels[`${axis}1`];
|
|
121
120
|
const high = channels[`${axis}2`];
|
|
122
|
-
if (low === undefined || high === undefined)
|
|
123
|
-
return
|
|
121
|
+
if (channels[axis] !== undefined || low === undefined || high === undefined)
|
|
122
|
+
return undefined;
|
|
123
|
+
if (transforms.some((t) => t.type === STACKS[axis]))
|
|
124
|
+
return undefined;
|
|
125
|
+
const read = (channel) => resolveAccessor(channel);
|
|
124
126
|
const from = read(low);
|
|
125
127
|
const to = read(high);
|
|
126
|
-
if (transforms.some((t) => t.type === STACKS[axis])) {
|
|
127
|
-
return (row) => TooltipState.value(Number(to(row)) - Number(from(row)), number);
|
|
128
|
-
}
|
|
129
128
|
return (row) => `${TooltipState.value(from(row), number)}-${TooltipState.value(to(row), number)}`;
|
|
130
129
|
}
|
|
131
130
|
}
|
|
@@ -6,6 +6,7 @@ export class MarkerKinds {
|
|
|
6
6
|
static traits = {
|
|
7
7
|
area: { clip: true, drawsRows: true, legendSymbol: 'area' },
|
|
8
8
|
arrow: { clip: false, drawsRows: true },
|
|
9
|
+
band: { clip: true, drawsRows: false, legendSymbol: 'rect' },
|
|
9
10
|
bar: { clip: true, drawsRows: true, legendSymbol: 'bar' },
|
|
10
11
|
bollinger: { clip: true, drawsRows: true, legendSymbol: 'line' },
|
|
11
12
|
brush: { clip: false, drawsRows: false },
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { usePlot } from '../../layout/plot/usePlot';
|
|
3
|
+
import { getConfiguration } from '../../configuration/config.svelte';
|
|
4
|
+
import { MarkerEvents } from '../utils/MarkerEvents';
|
|
5
|
+
import { RowStyle } from '../utils/RowStyle';
|
|
6
|
+
import { Bands, type Band } from './Bands';
|
|
7
|
+
import type { BandMarkerConfig } from '../../types/markers/common';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Renders one `'band'` marker: a rectangle over the band of each category it names, across the whole of the
|
|
11
|
+
* other axis. Nothing on an axis that is not a band scale. It takes the pointer only with a handler of its own,
|
|
12
|
+
* so a band behind the plot's shapes does not take their hover.
|
|
13
|
+
*/
|
|
14
|
+
const { marker }: { marker: BandMarkerConfig } = $props();
|
|
15
|
+
const cfg = $derived(getConfiguration());
|
|
16
|
+
const events = MarkerEvents.use(() => marker);
|
|
17
|
+
const plot = usePlot().svelteplot;
|
|
18
|
+
|
|
19
|
+
const bands = $derived(Bands.of(marker));
|
|
20
|
+
const rows = $derived(marker.data ?? []);
|
|
21
|
+
const styles = $derived({ ...cfg.band, ...marker.styles });
|
|
22
|
+
const scale = $derived(plot.scales[marker.axis]);
|
|
23
|
+
const across = $derived(plot.scales[marker.axis === 'x' ? 'y' : 'x']);
|
|
24
|
+
|
|
25
|
+
/** The pixels the other axis spans, lowest first. */
|
|
26
|
+
const extent = $derived.by((): [number, number] => {
|
|
27
|
+
const range = across?.fn?.range?.() as number[] | undefined;
|
|
28
|
+
if (!range || range.length < 2) return [0, 0];
|
|
29
|
+
const [a, b] = [Number(range[0]), Number(range[range.length - 1])];
|
|
30
|
+
return [Math.min(a, b), Math.max(a, b)];
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
/** Where a band is drawn: its slice of the band along the axis, the other axis' whole span across it. */
|
|
34
|
+
function boxOf(band: Band) {
|
|
35
|
+
const bandwidth = scale.fn.bandwidth?.() ?? 0;
|
|
36
|
+
const start = Number(scale.fn(band.category as never));
|
|
37
|
+
const from = start + band.slice.start * bandwidth;
|
|
38
|
+
const size = Math.max(0, bandwidth * (1 - band.slice.start - band.slice.end));
|
|
39
|
+
const [low, high] = extent;
|
|
40
|
+
return marker.axis === 'x'
|
|
41
|
+
? { x: from, y: low, width: size, height: high - low }
|
|
42
|
+
: { x: low, y: from, width: high - low, height: size };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** A color of `styles` for the row: a value, the field of the rows it names, or a function of the row. */
|
|
46
|
+
const colorOf = (style: unknown, row: unknown) => RowStyle.colorOf(style, rows)?.(row);
|
|
47
|
+
/** A number of `styles` for the row: a value, the field of the rows it names, or a function of the row. */
|
|
48
|
+
const numberOf = (style: unknown, row: unknown): number | undefined => {
|
|
49
|
+
const read = RowStyle.field(style as never) as unknown;
|
|
50
|
+
const value = typeof read === 'function' ? (read as (row: unknown) => unknown)(row) : read;
|
|
51
|
+
return value == null ? undefined : Number(value);
|
|
52
|
+
};
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
{#if scale?.type === 'band'}
|
|
56
|
+
<g class="band" pointer-events={events.any ? undefined : 'none'}>
|
|
57
|
+
{#each bands as band (band.category instanceof Date ? +band.category : band.category)}
|
|
58
|
+
{@const box = boxOf(band)}
|
|
59
|
+
{@const radius = numberOf(styles.borderRadius, band.row)}
|
|
60
|
+
<rect
|
|
61
|
+
{...box}
|
|
62
|
+
rx={radius}
|
|
63
|
+
ry={radius}
|
|
64
|
+
fill={colorOf(styles.fill, band.row)}
|
|
65
|
+
fill-opacity={numberOf(styles.fillOpacity, band.row)}
|
|
66
|
+
stroke={colorOf(styles.stroke, band.row)}
|
|
67
|
+
stroke-width={numberOf(styles.strokeWidth, band.row)}
|
|
68
|
+
stroke-opacity={numberOf(styles.strokeOpacity, band.row)}
|
|
69
|
+
stroke-dasharray={colorOf(styles.strokeDasharray, band.row)}
|
|
70
|
+
{...events.forRow(band.row, band.index)}
|
|
71
|
+
/>
|
|
72
|
+
{/each}
|
|
73
|
+
</g>
|
|
74
|
+
{/if}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { BandMarkerConfig } from '../../types/markers/common';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
marker: BandMarkerConfig;
|
|
4
|
+
};
|
|
5
|
+
declare const BandMarker: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
6
|
+
type BandMarker = ReturnType<typeof BandMarker>;
|
|
7
|
+
export default BandMarker;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AxisValue } from '../../types/layout/mapping';
|
|
2
|
+
import type { BandSlice } from '../../types/layout/hover';
|
|
3
|
+
import type { BandMarkerConfig } from '../../types/markers/common';
|
|
4
|
+
/** One band to draw: its category, the part of it filled, and the first row that names it. */
|
|
5
|
+
export type Band = {
|
|
6
|
+
category: AxisValue;
|
|
7
|
+
slice: BandSlice;
|
|
8
|
+
row: unknown;
|
|
9
|
+
index: number;
|
|
10
|
+
};
|
|
11
|
+
/** The bands a `band` marker draws, one per category its rows (or its `value`) name, in the order they first come. */
|
|
12
|
+
export declare class Bands {
|
|
13
|
+
#private;
|
|
14
|
+
/** The marker's bands. Rows that share a category share one band, which fills what all of their slices do. */
|
|
15
|
+
static of(marker: Pick<BandMarkerConfig, 'value' | 'slice' | 'data'>): Band[];
|
|
16
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { resolveAccessor } from '../../utils/accessors';
|
|
2
|
+
/** The bands a `band` marker draws, one per category its rows (or its `value`) name, in the order they first come. */
|
|
3
|
+
export class Bands {
|
|
4
|
+
static #WHOLE = { start: 0, end: 0 };
|
|
5
|
+
/** The marker's bands. Rows that share a category share one band, which fills what all of their slices do. */
|
|
6
|
+
static of(marker) {
|
|
7
|
+
const { rows, categoryOf } = Bands.#rowsOf(marker);
|
|
8
|
+
const sliceOf = Bands.#sliceOf(marker.slice);
|
|
9
|
+
const bands = new Map();
|
|
10
|
+
rows.forEach((row, index) => {
|
|
11
|
+
const category = categoryOf(row);
|
|
12
|
+
if (category == null)
|
|
13
|
+
return;
|
|
14
|
+
const key = category instanceof Date ? +category : category;
|
|
15
|
+
const slice = sliceOf(row);
|
|
16
|
+
const band = bands.get(key);
|
|
17
|
+
if (!band) {
|
|
18
|
+
bands.set(key, { category, slice, row, index });
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
// What either one fills: the least left out at each end.
|
|
22
|
+
band.slice = {
|
|
23
|
+
start: Math.min(band.slice.start, slice.start),
|
|
24
|
+
end: Math.min(band.slice.end, slice.end)
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
return [...bands.values()];
|
|
28
|
+
}
|
|
29
|
+
/** The rows a marker draws from, and the category of each: its `data` and `value`, or its `value`'s own. */
|
|
30
|
+
static #rowsOf(marker) {
|
|
31
|
+
const { value, data } = marker;
|
|
32
|
+
if (data) {
|
|
33
|
+
const read = resolveAccessor(value);
|
|
34
|
+
return { rows: data, categoryOf: read };
|
|
35
|
+
}
|
|
36
|
+
const values = (Array.isArray(value) ? value : [value]);
|
|
37
|
+
return { rows: values, categoryOf: (row) => row };
|
|
38
|
+
}
|
|
39
|
+
/** Each row's slice: the marker's own, or a function of the row's; the whole band when it gives none. */
|
|
40
|
+
static #sliceOf(slice) {
|
|
41
|
+
const whole = Bands.#WHOLE;
|
|
42
|
+
const read = typeof slice === 'function' ? slice : () => slice;
|
|
43
|
+
return (row) => {
|
|
44
|
+
const own = read(row);
|
|
45
|
+
return own ? { start: own.start ?? 0, end: own.end ?? 0 } : whole;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -9,13 +9,11 @@
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Renders one `'bar'` marker: `role: 'segment'` (visible bar via
|
|
12
|
-
* `<BarX>`/`<BarY>`)
|
|
13
|
-
* one segment)
|
|
12
|
+
* `<BarX>`/`<BarY>`) or `role: 'hitArea'` (invisible hover hit region for
|
|
13
|
+
* one segment).
|
|
14
14
|
*/
|
|
15
15
|
let { marker }: { marker: BarMarkerConfig } = $props();
|
|
16
16
|
const events = MarkerEvents.use(() => marker);
|
|
17
|
-
/** The hover band stands for no row, so a style that is a function of one does not apply to it. */
|
|
18
|
-
const flatStyles = $derived(RowStyle.flat(marker.styles));
|
|
19
17
|
|
|
20
18
|
const cfg = $derived(getConfiguration());
|
|
21
19
|
const data = $derived(marker.data ?? []);
|
|
@@ -90,69 +88,28 @@
|
|
|
90
88
|
{@const insetRight = (marker.styles?.insetRight ?? 0) * bandwidth}
|
|
91
89
|
{@const insetTop = (marker.styles?.insetTop ?? 0) * bandwidth}
|
|
92
90
|
{@const insetBottom = (marker.styles?.insetBottom ?? 0) * bandwidth}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
/>
|
|
106
|
-
{:else}
|
|
107
|
-
<rect
|
|
108
|
-
class="dct-hit-area"
|
|
109
|
-
x={bandStart + insetLeft}
|
|
110
|
-
y={rangeMin + insetBottom}
|
|
111
|
-
width={Math.max(0, bandwidth - insetLeft - insetRight)}
|
|
112
|
-
height={Math.max(0, rangeMax - rangeMin - insetTop - insetBottom)}
|
|
113
|
-
fill="transparent"
|
|
114
|
-
{...events.forRow(data[0], 0)}
|
|
115
|
-
/>
|
|
116
|
-
{/if}
|
|
91
|
+
<!-- The band runs the plot's whole value axis, so the pointer needn't be over the bar. -->
|
|
92
|
+
{@const [rangeMin, rangeMax] = valueExtentFor(marker.isHorizontal)}
|
|
93
|
+
{#if marker.isHorizontal}
|
|
94
|
+
<rect
|
|
95
|
+
class="dct-hit-area"
|
|
96
|
+
x={rangeMin + insetLeft}
|
|
97
|
+
y={bandStart + insetBottom}
|
|
98
|
+
width={Math.max(0, rangeMax - rangeMin - insetLeft - insetRight)}
|
|
99
|
+
height={Math.max(0, bandwidth - insetTop - insetBottom)}
|
|
100
|
+
fill="transparent"
|
|
101
|
+
{...events.forRow(data[0], 0)}
|
|
102
|
+
/>
|
|
117
103
|
{:else}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
{
|
|
121
|
-
{
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
height={thickness}
|
|
128
|
-
fill={flatStyles.fill}
|
|
129
|
-
fill-opacity={flatStyles.fillOpacity}
|
|
130
|
-
stroke={flatStyles.stroke}
|
|
131
|
-
stroke-width={flatStyles.strokeWidth}
|
|
132
|
-
stroke-opacity={flatStyles.strokeOpacity}
|
|
133
|
-
stroke-dasharray={flatStyles.strokeDasharray}
|
|
134
|
-
rx={flatStyles.borderRadius}
|
|
135
|
-
pointer-events="none"
|
|
136
|
-
/>
|
|
137
|
-
{:else}
|
|
138
|
-
{@const thickness = Math.max(0, bandwidth - insetLeft - insetRight) + 2}
|
|
139
|
-
{@const center = bandStart + insetLeft + (bandwidth - insetLeft - insetRight) / 2}
|
|
140
|
-
<rect
|
|
141
|
-
class="hover-area"
|
|
142
|
-
x={center - thickness / 2}
|
|
143
|
-
y={valueMin}
|
|
144
|
-
width={thickness}
|
|
145
|
-
height={Math.max(0, valueMax - valueMin)}
|
|
146
|
-
fill={flatStyles.fill}
|
|
147
|
-
fill-opacity={flatStyles.fillOpacity}
|
|
148
|
-
stroke={flatStyles.stroke}
|
|
149
|
-
stroke-width={flatStyles.strokeWidth}
|
|
150
|
-
stroke-opacity={flatStyles.strokeOpacity}
|
|
151
|
-
stroke-dasharray={flatStyles.strokeDasharray}
|
|
152
|
-
ry={flatStyles.borderRadius}
|
|
153
|
-
pointer-events="none"
|
|
154
|
-
/>
|
|
155
|
-
{/if}
|
|
104
|
+
<rect
|
|
105
|
+
class="dct-hit-area"
|
|
106
|
+
x={bandStart + insetLeft}
|
|
107
|
+
y={rangeMin + insetBottom}
|
|
108
|
+
width={Math.max(0, bandwidth - insetLeft - insetRight)}
|
|
109
|
+
height={Math.max(0, rangeMax - rangeMin - insetTop - insetBottom)}
|
|
110
|
+
fill="transparent"
|
|
111
|
+
{...events.forRow(data[0], 0)}
|
|
112
|
+
/>
|
|
156
113
|
{/if}
|
|
157
114
|
{/if}
|
|
158
115
|
{/if}
|