@fundar/data-chart-telling 0.0.39 → 0.0.41
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/dist/plots/bar/Plot.svelte +275 -249
- package/dist/plots/line/Plot.svelte +227 -182
- package/dist/plots/line/Plot.svelte.d.ts +2 -2
- package/dist/plots/pyramid/Plot.svelte +288 -260
- package/dist/plots/scatter/Plot.svelte +266 -242
- package/dist/plots/utils/labelOverflow.svelte.d.ts +17 -1
- package/dist/plots/utils/labelOverflow.svelte.js +44 -8
- package/package.json +1 -1
|
@@ -1,271 +1,297 @@
|
|
|
1
|
-
<script
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
1
|
+
<script lang="ts" generics="TData extends Record<string, unknown>">
|
|
2
|
+
import { paletteColor } from '../../utils/color';
|
|
3
|
+
import { buildSeries } from '../../utils/grouping';
|
|
4
|
+
import { getLegendInteraction } from '../../layout/legend/interaction.svelte';
|
|
5
|
+
import { disabledFillOverride } from '../utils/legendDisabled';
|
|
6
|
+
import { resolveAccessor } from '../utils/accessors';
|
|
7
|
+
import { getConfiguration } from '../../configuration/config.svelte';
|
|
8
|
+
import {
|
|
9
|
+
buildGroupedSeries,
|
|
10
|
+
validateSegments,
|
|
11
|
+
buildScopedMarkerGroups
|
|
12
|
+
} from '../utils/segments';
|
|
13
|
+
import { createBarLayout } from './layout.svelte';
|
|
14
|
+
import { createLabelMarginTracker } from '../utils/labelOverflow.svelte';
|
|
15
|
+
import { buildHoverPoints } from './hoverPoints';
|
|
16
|
+
import { buildBarMarkers } from './buildBarMarkers';
|
|
17
|
+
import { resolveGapsForSeries, resolveDiscreteGapFills } from '../utils/gaps';
|
|
18
|
+
import ValueLabels from './ValueLabels.svelte';
|
|
19
|
+
import BasePlotLayout from '../../layout/plot/BasePlotLayout.svelte';
|
|
20
|
+
import {
|
|
21
|
+
hasHoverMarker,
|
|
22
|
+
resolveHoverStrategy,
|
|
23
|
+
resolveHoverSync
|
|
24
|
+
} from '../../layout/tooltip/utils';
|
|
25
|
+
import type { AxisBasedMarkersConfig } from '../../types/markers/common';
|
|
26
|
+
import type { BasePlotProps } from '../../types/plots/props';
|
|
27
|
+
import type { AxisScale, AxisBasedScalesConfig } from '../../types/layout/scales';
|
|
28
|
+
import type { Series, SeriesProps, DataProps } from '../../types/plots/data/common';
|
|
29
|
+
import type { GapsAwarePlotStylesConfig } from '../../types/layout/styles';
|
|
30
|
+
import type { ValueAnchor } from '../../types/plots/constants';
|
|
31
|
+
import type { BarSegmentStyle } from '../../types/plots/segments/common';
|
|
32
|
+
import type { LegendDisabledStyle } from '../../types/layout/legend';
|
|
28
33
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
/**
|
|
35
|
+
* A bar chart, vertical by default. Accepts either a pre-built `series`
|
|
36
|
+
* array or flat `data/x/y/z` props — mirrors the line/heatmap plot contract
|
|
37
|
+
* so the chart-level container/timeline/facet/legend wrap it unchanged.
|
|
38
|
+
*/
|
|
39
|
+
type Props = BasePlotProps<
|
|
40
|
+
SeriesProps<TData> | DataProps<TData>,
|
|
41
|
+
TData,
|
|
42
|
+
GapsAwarePlotStylesConfig<BarSegmentStyle>,
|
|
43
|
+
BarSegmentStyle,
|
|
44
|
+
AxisBasedMarkersConfig,
|
|
45
|
+
AxisBasedScalesConfig<TData>
|
|
46
|
+
>;
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
48
|
+
let {
|
|
49
|
+
width,
|
|
50
|
+
height,
|
|
51
|
+
series,
|
|
52
|
+
data,
|
|
53
|
+
x,
|
|
54
|
+
y,
|
|
55
|
+
z,
|
|
56
|
+
styles = {},
|
|
57
|
+
segments = {},
|
|
58
|
+
markers = [],
|
|
59
|
+
scales = {},
|
|
60
|
+
margins,
|
|
61
|
+
tooltip = undefined
|
|
62
|
+
}: Props = $props();
|
|
58
63
|
|
|
59
|
-
|
|
64
|
+
$effect(() => {
|
|
65
|
+
validateSegments(segments);
|
|
66
|
+
});
|
|
60
67
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
const cfg = $derived(getConfiguration());
|
|
69
|
+
const resolvedSeries = $derived(series ?? buildSeries(data!, x!, y!, z));
|
|
70
|
+
const groupedSeries = $derived(
|
|
71
|
+
// connect: false — bars are discrete; there's no visual gap between
|
|
72
|
+
// adjacent categories to bridge (see buildGroupedSeries's own doc).
|
|
73
|
+
buildGroupedSeries<Series<TData>, BarSegmentStyle>(resolvedSeries, segments, false)
|
|
74
|
+
);
|
|
75
|
+
const showVals = $derived(styles.values?.show ?? false);
|
|
76
|
+
const formatValue = $derived(styles.values?.format ?? ((v: number) => `${v}`));
|
|
77
|
+
const valAnchor = $derived<ValueAnchor>(styles.values?.anchor ?? 'outside');
|
|
71
78
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
// Fills leading/internal/trailing missing-data holes with an
|
|
80
|
+
// interpolated, distinctly-styled bar, resolved per series like
|
|
81
|
+
// `segments` — see `GapsAwarePlotStylesConfig` (`types/layout/styles.ts`).
|
|
82
|
+
const gaps = $derived(styles.gaps ?? {});
|
|
83
|
+
const gapFillMaps = $derived(
|
|
84
|
+
new Map(
|
|
85
|
+
resolvedSeries.map((s) => [
|
|
86
|
+
s.name,
|
|
87
|
+
resolveDiscreteGapFills(s, resolveGapsForSeries(gaps, s.name))
|
|
88
|
+
])
|
|
89
|
+
)
|
|
90
|
+
);
|
|
79
91
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
92
|
+
// `xAcc`/`yAcc` are always data-semantic (category/value) — independent of
|
|
93
|
+
// which *visual* axis ends up drawing them, decided by `categoricalAxis`.
|
|
94
|
+
const xAcc = $derived(resolveAccessor(resolvedSeries[0]?.x ?? ((d: TData) => d)));
|
|
95
|
+
const yAcc = $derived(resolveAccessor(resolvedSeries[0]?.y ?? ((d: TData) => d)));
|
|
96
|
+
const flat = $derived(resolvedSeries.flatMap((s) => s.data));
|
|
85
97
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
98
|
+
const scopedGroups = $derived(
|
|
99
|
+
buildScopedMarkerGroups<TData, Series<TData>, BarSegmentStyle>({
|
|
100
|
+
groupedSeries,
|
|
101
|
+
segments,
|
|
102
|
+
colorFor: (_series, seriesIndex) => paletteColor(seriesIndex, cfg.palette),
|
|
103
|
+
segmentColorFor: (style, defaultColor) => style?.fill ?? defaultColor
|
|
104
|
+
})
|
|
105
|
+
);
|
|
94
106
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
107
|
+
/**
|
|
108
|
+
* Which visual axis draws the category, and therefore whether this renders
|
|
109
|
+
* as vertical bars (`BarY`, category on X) or horizontal bars (`BarX`,
|
|
110
|
+
* category on Y). Mirrors how pyramid/heatmap default an `AxisScale.type`
|
|
111
|
+
* themselves: `scales.x`/`scales.y` always describe *that visual axis*
|
|
112
|
+
* regardless of which data field lands there, so marking `scales.y.type:
|
|
113
|
+
* 'categorical'` simply says "the category is drawn on Y this time" — x
|
|
114
|
+
* defaults to categorical when neither is marked.
|
|
115
|
+
*/
|
|
116
|
+
const categoricalAxis = $derived.by((): 'x' | 'y' => {
|
|
117
|
+
const xCategorical = scales.x?.type === 'categorical';
|
|
118
|
+
const yCategorical = scales.y?.type === 'categorical';
|
|
119
|
+
if (xCategorical && yCategorical) {
|
|
120
|
+
console.warn(
|
|
121
|
+
"BarPlot: scales.x.type and scales.y.type can't both be 'categorical' — " +
|
|
122
|
+
'mark only one axis as the category axis. Defaulting to x.'
|
|
123
|
+
);
|
|
124
|
+
return 'x';
|
|
125
|
+
}
|
|
126
|
+
return yCategorical ? 'y' : 'x';
|
|
127
|
+
});
|
|
128
|
+
const isHorizontal = $derived(categoricalAxis === 'y');
|
|
117
129
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
130
|
+
/**
|
|
131
|
+
* `'categorical'` is our own sentinel (BarPlot-only), not a real svelteplot
|
|
132
|
+
* scale type — svelteplot's x/y scales don't recognise it (it's valid only
|
|
133
|
+
* for `color`/`symbol`). Strip it before the scale reaches svelteplot so it
|
|
134
|
+
* auto-infers the real type (typically `point`/`band`) the same way it
|
|
135
|
+
* already does when `type` is left unset entirely.
|
|
136
|
+
*/
|
|
137
|
+
function stripCategoricalType(scale?: AxisScale<TData>): AxisScale<TData> | undefined {
|
|
138
|
+
if (scale?.type !== 'categorical') return scale;
|
|
139
|
+
const rest = { ...scale };
|
|
140
|
+
delete rest.type;
|
|
141
|
+
return rest;
|
|
142
|
+
}
|
|
143
|
+
// The single, fully-resolved scales object BasePlotLayout reads for
|
|
144
|
+
// everything (ticks, grid, sort, and the real svelteplot scale) — avoids
|
|
145
|
+
// keeping a second, separately-patched copy of `scales.x`/`scales.y` in
|
|
146
|
+
// sync with the one actually forwarded.
|
|
147
|
+
const resolvedScales = $derived({
|
|
148
|
+
...scales,
|
|
149
|
+
x: stripCategoricalType(scales.x),
|
|
150
|
+
y: stripCategoricalType(scales.y)
|
|
151
|
+
});
|
|
140
152
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
153
|
+
// The accessor feeding each *visual* channel — swapped from the
|
|
154
|
+
// data-semantic xAcc/yAcc when the category is drawn on Y.
|
|
155
|
+
const plotGetX = $derived(isHorizontal ? (d: TData) => Number(yAcc(d)) : xAcc);
|
|
156
|
+
const plotGetY = $derived(isHorizontal ? xAcc : (d: TData) => Number(yAcc(d)));
|
|
145
157
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
158
|
+
/**
|
|
159
|
+
* Series-layout geometry (grouped/stacked offsets, band-width estimate,
|
|
160
|
+
* value baseline) — the single source of truth shared by bar-segment
|
|
161
|
+
* rendering, value labels, and hover markers below. See `layout.svelte.ts`.
|
|
162
|
+
*/
|
|
163
|
+
const barLayout = createBarLayout<TData>({
|
|
164
|
+
scales: () => scales,
|
|
165
|
+
isHorizontal: () => isHorizontal,
|
|
166
|
+
width: () => width,
|
|
167
|
+
height: () => height,
|
|
168
|
+
margins: () => margins,
|
|
169
|
+
flat: () => flat,
|
|
170
|
+
xAcc: () => xAcc,
|
|
171
|
+
groupedSeries: () => groupedSeries,
|
|
172
|
+
gapFillMaps: () => gapFillMaps
|
|
173
|
+
});
|
|
162
174
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
175
|
+
// `x`/`y` here are visual (matched against BasePlotLayout's own getX/getY,
|
|
176
|
+
// which drive tick/domain computation and the Pointer's hit-testing) —
|
|
177
|
+
// `row` (see BasePlotLayout/HoverPoint) is what lets the template recover the
|
|
178
|
+
// semantic category regardless of which axis it landed on.
|
|
179
|
+
const legendInteraction = $derived(getLegendInteraction());
|
|
180
|
+
const hoverIsolate = $derived(styles.values?.hoverIsolate ?? false);
|
|
169
181
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
182
|
+
// A value label hovered while `styles.values.hoverIsolate` is on dims
|
|
183
|
+
// every other series exactly like a legend toggle would — local to this
|
|
184
|
+
// one `<Plot>` instance (not the shared HoverStore) so the gesture can't
|
|
185
|
+
// leak into unrelated tooltip/crosshair behavior.
|
|
186
|
+
let hoverIsolatedSeries = $state<string | null>(null);
|
|
175
187
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
188
|
+
function effectiveDisabledFor(name: string): LegendDisabledStyle | undefined {
|
|
189
|
+
return (
|
|
190
|
+
legendInteraction?.disabledSeries.get(name) ??
|
|
191
|
+
(hoverIsolate && hoverIsolatedSeries && hoverIsolatedSeries !== name
|
|
192
|
+
? { opacity: cfg.legend.disabledOpacity }
|
|
193
|
+
: undefined)
|
|
194
|
+
);
|
|
195
|
+
}
|
|
184
196
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
197
|
+
// A legend-disabled series is dimmed on the bar itself, but never surfaces
|
|
198
|
+
// in hover/tooltip — its points are simply absent from the candidate list.
|
|
199
|
+
const hoverPoints = $derived(
|
|
200
|
+
buildHoverPoints(resolvedSeries, isHorizontal, barLayout, cfg.palette).filter(
|
|
201
|
+
(p) => !legendInteraction?.disabledSeries.has(p.series!)
|
|
202
|
+
)
|
|
203
|
+
);
|
|
192
204
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
205
|
+
const hover = $derived({
|
|
206
|
+
active: tooltip != null || hasHoverMarker(markers),
|
|
207
|
+
points: hoverPoints,
|
|
208
|
+
label: (r: TData) => String(yAcc(r)),
|
|
209
|
+
strategy: resolveHoverStrategy(tooltip?.strategy, markers, categoricalAxis),
|
|
210
|
+
sync: resolveHoverSync(tooltip?.sync, markers),
|
|
211
|
+
tooltip,
|
|
212
|
+
// The category axis is a band scale — `<Pointer>`'s nearest-point-by-
|
|
213
|
+
// distance match (svelteplot's own projection of each candidate's band
|
|
214
|
+
// centre) can disagree by a couple of pixels with where `<BarX>`/`<BarY>`
|
|
215
|
+
// (via svelteplot's `RectPath`) actually render that band, which is
|
|
216
|
+
// enough to mismatch the neighbouring category once bands get thin
|
|
217
|
+
// (many categories). The `role: 'hitArea'` bar markers `buildBarMarkers`
|
|
218
|
+
// emits hit-test the category bands directly instead — see its own doc
|
|
219
|
+
// comment.
|
|
220
|
+
pointMatching: 'contains' as const
|
|
221
|
+
});
|
|
210
222
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
223
|
+
// A bar's value label can grow past the plot's own content box (e.g. a
|
|
224
|
+
// tall bar's `anchor: 'outside'` label pushing past the top margin) —
|
|
225
|
+
// reserving real space there is what each label's own overflow
|
|
226
|
+
// measurement feeds into here, via a tracker local to this one `<Plot>`
|
|
227
|
+
// instance. See `labelOverflow.svelte.ts`. Resets on `resolvedSeries` so
|
|
228
|
+
// overflow from an earlier, differently-shaped frame (e.g. a timeline
|
|
229
|
+
// scrubber revealing a different slice of rows) doesn't linger once
|
|
230
|
+
// that frame is gone.
|
|
231
|
+
const labelMargin = createLabelMarginTracker(
|
|
232
|
+
() => margins,
|
|
233
|
+
() => resolvedSeries
|
|
234
|
+
);
|
|
217
235
|
</script>
|
|
218
236
|
|
|
219
237
|
<BasePlotLayout
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
238
|
+
{width}
|
|
239
|
+
{height}
|
|
240
|
+
{styles}
|
|
241
|
+
data={flat}
|
|
242
|
+
getX={plotGetX}
|
|
243
|
+
getY={plotGetY}
|
|
244
|
+
{hover}
|
|
245
|
+
scales={resolvedScales}
|
|
246
|
+
{...labelMargin.plotProps}
|
|
247
|
+
xTickRotate={resolvedScales?.x?.tickRotate}
|
|
248
|
+
markers={({ setHoverMatch, clearHoverMatch }) => [
|
|
249
|
+
...buildBarMarkers({
|
|
250
|
+
groupedSeries,
|
|
251
|
+
barLayout,
|
|
252
|
+
isHorizontal,
|
|
253
|
+
hoverPoints,
|
|
254
|
+
palette: cfg.palette,
|
|
255
|
+
disabledFillFor: (name) => disabledFillOverride(effectiveDisabledFor(name)),
|
|
256
|
+
active: hover.active,
|
|
257
|
+
setHoverMatch,
|
|
258
|
+
clearHoverMatch,
|
|
259
|
+
gapFillMaps
|
|
260
|
+
}),
|
|
261
|
+
...markers
|
|
262
|
+
]}
|
|
263
|
+
seriesData={resolvedSeries}
|
|
264
|
+
{scopedGroups}
|
|
247
265
|
>
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
266
|
+
{#snippet children({ numericMargins })}
|
|
267
|
+
{#if showVals}
|
|
268
|
+
{#each groupedSeries as group, seriesIndex (group.series.name)}
|
|
269
|
+
<ValueLabels
|
|
270
|
+
{group}
|
|
271
|
+
{seriesIndex}
|
|
272
|
+
{barLayout}
|
|
273
|
+
{isHorizontal}
|
|
274
|
+
{valAnchor}
|
|
275
|
+
{formatValue}
|
|
276
|
+
values={styles.values}
|
|
277
|
+
axisColor={cfg.axis.color}
|
|
278
|
+
disabled={effectiveDisabledFor(group.series.name)}
|
|
279
|
+
{width}
|
|
280
|
+
{height}
|
|
281
|
+
{numericMargins}
|
|
282
|
+
onHoverSeries={hoverIsolate
|
|
283
|
+
? () => {
|
|
284
|
+
hoverIsolatedSeries = group.series.name;
|
|
285
|
+
}
|
|
286
|
+
: undefined}
|
|
287
|
+
onHoverEnd={hoverIsolate
|
|
288
|
+
? () => {
|
|
289
|
+
hoverIsolatedSeries = null;
|
|
290
|
+
}
|
|
291
|
+
: undefined}
|
|
292
|
+
onOverflow={labelMargin.report}
|
|
293
|
+
/>
|
|
294
|
+
{/each}
|
|
295
|
+
{/if}
|
|
296
|
+
{/snippet}
|
|
271
297
|
</BasePlotLayout>
|