@internetstiftelsen/charts 0.10.0 → 0.11.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/README.md +65 -1
- package/dist/area.d.ts +11 -1
- package/dist/area.js +199 -55
- package/dist/bar.d.ts +26 -1
- package/dist/bar.js +425 -306
- package/dist/base-chart.d.ts +5 -0
- package/dist/base-chart.js +91 -67
- package/dist/chart-group.d.ts +16 -0
- package/dist/chart-group.js +201 -143
- package/dist/donut-center-content.d.ts +1 -0
- package/dist/donut-center-content.js +21 -38
- package/dist/donut-chart.js +32 -32
- package/dist/gauge-chart.d.ts +23 -4
- package/dist/gauge-chart.js +235 -185
- package/dist/lazy-mount.d.ts +13 -0
- package/dist/lazy-mount.js +90 -0
- package/dist/legend.js +10 -9
- package/dist/line.d.ts +9 -1
- package/dist/line.js +144 -24
- package/dist/pie-chart.d.ts +3 -0
- package/dist/pie-chart.js +49 -47
- package/dist/radial-chart-base.d.ts +4 -3
- package/dist/radial-chart-base.js +27 -12
- package/dist/scatter.d.ts +5 -1
- package/dist/scatter.js +92 -9
- package/dist/theme.js +17 -0
- package/dist/tooltip.d.ts +55 -3
- package/dist/tooltip.js +968 -159
- package/dist/types.d.ts +23 -1
- package/dist/utils.js +11 -19
- package/dist/x-axis.d.ts +10 -0
- package/dist/x-axis.js +190 -149
- package/dist/xy-animation.d.ts +3 -0
- package/dist/xy-animation.js +2 -0
- package/dist/xy-chart.d.ts +35 -1
- package/dist/xy-chart.js +358 -153
- package/dist/xy-motion/config.d.ts +2 -0
- package/dist/xy-motion/config.js +177 -0
- package/dist/xy-motion/driver.d.ts +9 -0
- package/dist/xy-motion/driver.js +10 -0
- package/dist/xy-motion/helpers.d.ts +17 -0
- package/dist/xy-motion/helpers.js +105 -0
- package/dist/xy-motion/live-state.d.ts +8 -0
- package/dist/xy-motion/live-state.js +240 -0
- package/dist/xy-motion/noop-xy-motion-driver.d.ts +9 -0
- package/dist/xy-motion/noop-xy-motion-driver.js +15 -0
- package/dist/xy-motion/types.d.ts +85 -0
- package/dist/xy-motion/types.js +1 -0
- package/dist/xy-motion/xy-motion-driver.d.ts +19 -0
- package/dist/xy-motion/xy-motion-driver.js +130 -0
- package/dist/y-axis.d.ts +7 -2
- package/dist/y-axis.js +99 -10
- package/docs/components.md +50 -1
- package/docs/getting-started.md +35 -0
- package/docs/theming.md +14 -0
- package/docs/xy-chart.md +88 -7
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -10,7 +10,8 @@ A framework-agnostic, composable charting library built on D3.js with TypeScript
|
|
|
10
10
|
- **Combined Chart Layouts** - `ChartGroup` composes existing charts into shared dashboards with one coordinated legend
|
|
11
11
|
- **Divergent Bar Support** - Bar charts automatically render from zero and diverge around `0` for mixed positive/negative values
|
|
12
12
|
- **Mirrored Bar Sides** - Horizontal bars can mirror a series to the left for population-pyramid style charts without changing source data
|
|
13
|
-
- **
|
|
13
|
+
- **Custom Value Labels** - XY, pie, and donut charts support optional on-chart labels with custom formatters
|
|
14
|
+
- **Optional XY Animation** - Animate XY series on first render and `chart.update(...)` with `animate`
|
|
14
15
|
- **Optional Gauge Animation** - Animate gauge value transitions with `gauge.animate`
|
|
15
16
|
- **Stacking Control** - Bar stacking modes with optional reversed visual series order
|
|
16
17
|
- **Axis Direction Control** - Use `scales.x.reverse` / `scales.y.reverse` to flip an axis when needed
|
|
@@ -18,6 +19,7 @@ A framework-agnostic, composable charting library built on D3.js with TypeScript
|
|
|
18
19
|
- **Explicit or Responsive Sizing** - Set top-level `width`/`height` or let the container drive size
|
|
19
20
|
- **Auto Resize** - Built-in ResizeObserver handles responsive behavior
|
|
20
21
|
- **Responsive Policy** - Chart-level container-query overrides for theme and components
|
|
22
|
+
- **Lazy Mount Utility** - Observe a container and defer chart imports until it approaches the viewport
|
|
21
23
|
- **Type Safe** - Written in TypeScript with full type definitions
|
|
22
24
|
- **Data Validation** - Built-in validation with helpful error messages
|
|
23
25
|
- **Auto Colors** - Smart color palette with sensible defaults
|
|
@@ -110,6 +112,68 @@ from the render container.
|
|
|
110
112
|
Theme overrides are deep-partial, so nested overrides like
|
|
111
113
|
`theme.axis.fontSize` preserve the rest of the theme defaults.
|
|
112
114
|
|
|
115
|
+
## XY Animation
|
|
116
|
+
|
|
117
|
+
Enable `animate` on `XYChart` when you want series marks to animate on the
|
|
118
|
+
first render and on later `chart.update(...)` calls.
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
const chart = new XYChart({
|
|
122
|
+
data,
|
|
123
|
+
animate: {
|
|
124
|
+
duration: 700,
|
|
125
|
+
easing: 'ease-in-out',
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
chart
|
|
130
|
+
.addChild(new XAxis({ dataKey: 'month' }))
|
|
131
|
+
.addChild(new YAxis())
|
|
132
|
+
.addChild(new Line({ dataKey: 'value' }));
|
|
133
|
+
|
|
134
|
+
chart.render('#chart-container');
|
|
135
|
+
|
|
136
|
+
await chart.whenReady();
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Animation is off by default, applies to XY series marks only, and visual
|
|
140
|
+
exports always render the final static state.
|
|
141
|
+
|
|
142
|
+
## Lazy Loading
|
|
143
|
+
|
|
144
|
+
Use `mountChartWhenVisible` when you want the page to wait until a chart is
|
|
145
|
+
near the viewport before importing chart code and rendering it.
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { mountChartWhenVisible } from '@internetstiftelsen/charts/lazy-mount';
|
|
149
|
+
|
|
150
|
+
const lazyChart = mountChartWhenVisible(
|
|
151
|
+
'#chart-container',
|
|
152
|
+
async (container) => {
|
|
153
|
+
const [{ XYChart }, { Line }, { XAxis }, { YAxis }] = await Promise.all([import('@internetstiftelsen/charts/xy-chart'), import('@internetstiftelsen/charts/line'), import('@internetstiftelsen/charts/x-axis'), import('@internetstiftelsen/charts/y-axis')]);
|
|
154
|
+
|
|
155
|
+
const chart = new XYChart({ data });
|
|
156
|
+
chart
|
|
157
|
+
.addChild(new XAxis({ dataKey: 'month' }))
|
|
158
|
+
.addChild(new YAxis())
|
|
159
|
+
.addChild(new Line({ dataKey: 'value' }));
|
|
160
|
+
|
|
161
|
+
chart.render(container);
|
|
162
|
+
return chart;
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
rootMargin: '240px 0px',
|
|
166
|
+
},
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
// Optional: preload manually before it scrolls into view
|
|
170
|
+
await lazyChart.load();
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The utility is intentionally small: it observes the container, calls your async
|
|
174
|
+
factory once, and gives you `load()` plus `destroy()` so higher-level DOM
|
|
175
|
+
conventions such as `data-chart` scanners can build on top of it.
|
|
176
|
+
|
|
113
177
|
## Chart Groups
|
|
114
178
|
|
|
115
179
|
Use `ChartGroup` when you want to combine existing charts into one layout while
|
package/dist/area.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type Selection } from 'd3';
|
|
2
2
|
import type { AreaConfig, AreaCurveType, AreaConfigBase, AreaStackingContext, ChartTheme, D3Scale, DataItem, ExportHooks, LineValueLabelConfig, ScaleType } from './types.js';
|
|
3
3
|
import type { ChartComponent } from './chart-interface.js';
|
|
4
|
+
import type { XYAreaAnimationContext, XYAreaPointSnapshot, XYSeriesRenderResult } from './xy-motion/types.js';
|
|
4
5
|
export declare class Area implements ChartComponent<AreaConfigBase> {
|
|
5
6
|
readonly type: "area";
|
|
6
7
|
readonly dataKey: string;
|
|
@@ -20,6 +21,15 @@ export declare class Area implements ChartComponent<AreaConfigBase> {
|
|
|
20
21
|
getExportConfig(): AreaConfigBase;
|
|
21
22
|
createExportComponent(override?: Partial<AreaConfigBase>): ChartComponent<AreaConfigBase>;
|
|
22
23
|
private getStackValues;
|
|
23
|
-
render(plotGroup: Selection<SVGGElement, undefined, null, undefined>, data: DataItem[], xKey: string, x: D3Scale, y: D3Scale, parseValue: (value: unknown) => number, xScaleType: ScaleType | undefined, theme: ChartTheme, stackingContext?: AreaStackingContext, valueLabelLayer?: Selection<SVGGElement, undefined, null, undefined
|
|
24
|
+
render(plotGroup: Selection<SVGGElement, undefined, null, undefined>, data: DataItem[], xKey: string, x: D3Scale, y: D3Scale, parseValue: (value: unknown) => number, xScaleType: ScaleType | undefined, theme: ChartTheme, stackingContext?: AreaStackingContext, valueLabelLayer?: Selection<SVGGElement, undefined, null, undefined>, animation?: XYAreaAnimationContext): XYSeriesRenderResult<XYAreaPointSnapshot>;
|
|
25
|
+
private buildAreaData;
|
|
26
|
+
private buildAnimatedAreaData;
|
|
27
|
+
private createSnapshot;
|
|
28
|
+
private renderAreaFill;
|
|
29
|
+
private renderLinePath;
|
|
30
|
+
private getInitialAreaPathValue;
|
|
31
|
+
private getInitialLinePathValue;
|
|
32
|
+
private createRevealTransitions;
|
|
33
|
+
private renderPoints;
|
|
24
34
|
private renderValueLabels;
|
|
25
35
|
}
|
package/dist/area.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { area, curveBasis, curveCardinal, curveLinear, curveMonotoneX, curveNatural, curveStep, line, } from 'd3';
|
|
2
2
|
import { mergeDeep, sanitizeForCSS } from './utils.js';
|
|
3
3
|
import { getScalePosition } from './scale-utils.js';
|
|
4
|
+
import { buildXYDatumSnapshotKeys, createTransitionCompletionPromise, createLeftToRightRevealTransition, getEnterStaggerTiming, } from './xy-motion/helpers.js';
|
|
4
5
|
const AREA_CURVE_FACTORIES = {
|
|
5
6
|
linear: curveLinear,
|
|
6
7
|
monotone: curveMonotoneX,
|
|
@@ -157,9 +158,9 @@ export class Area {
|
|
|
157
158
|
y1: cumulative + value,
|
|
158
159
|
};
|
|
159
160
|
}
|
|
160
|
-
render(plotGroup, data, xKey, x, y, parseValue, xScaleType = 'band', theme, stackingContext, valueLabelLayer) {
|
|
161
|
-
const getXPosition = (
|
|
162
|
-
const scaled = getScalePosition(x,
|
|
161
|
+
render(plotGroup, data, xKey, x, y, parseValue, xScaleType = 'band', theme, stackingContext, valueLabelLayer, animation) {
|
|
162
|
+
const getXPosition = (dataPoint) => {
|
|
163
|
+
const scaled = getScalePosition(x, dataPoint[xKey], xScaleType);
|
|
163
164
|
return scaled + (x.bandwidth ? x.bandwidth() / 2 : 0);
|
|
164
165
|
};
|
|
165
166
|
const hasValidValue = (d) => {
|
|
@@ -169,74 +170,215 @@ export class Area {
|
|
|
169
170
|
}
|
|
170
171
|
return Number.isFinite(parseValue(value));
|
|
171
172
|
};
|
|
172
|
-
const
|
|
173
|
-
|
|
173
|
+
const snapshotKeys = buildXYDatumSnapshotKeys(data, xKey);
|
|
174
|
+
const areaData = this.buildAreaData(data, xKey, snapshotKeys, y, parseValue, stackingContext, getXPosition, hasValidValue);
|
|
175
|
+
const animatedAreaData = this.buildAnimatedAreaData(areaData, animation);
|
|
176
|
+
const curveFactory = AREA_CURVE_FACTORIES[this.curve] || curveLinear;
|
|
177
|
+
const sanitizedKey = sanitizeForCSS(this.dataKey);
|
|
178
|
+
const transitions = this.renderAreaFill(plotGroup, areaData, animatedAreaData, curveFactory, sanitizedKey, animation);
|
|
179
|
+
const snapshot = this.createSnapshot(areaData);
|
|
180
|
+
if (this.showLine) {
|
|
181
|
+
const lineTransitions = this.renderLinePath(plotGroup, areaData, animatedAreaData, curveFactory, theme, sanitizedKey, animation);
|
|
182
|
+
transitions.push(...lineTransitions);
|
|
183
|
+
}
|
|
184
|
+
if (this.showPoints) {
|
|
185
|
+
const pointTransitions = this.renderPoints(plotGroup, areaData, animatedAreaData, theme, sanitizedKey, animation);
|
|
186
|
+
transitions.push(...pointTransitions);
|
|
187
|
+
}
|
|
188
|
+
if (this.valueLabel?.show) {
|
|
189
|
+
this.renderValueLabels(valueLabelLayer ?? plotGroup, areaData.filter((d) => d.valid), y, parseValue, theme);
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
snapshot,
|
|
193
|
+
transitions,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
buildAreaData(data, xKey, snapshotKeys, y, parseValue, stackingContext, getXPosition, hasValidValue) {
|
|
197
|
+
return data.map((entry, index) => {
|
|
198
|
+
const valid = hasValidValue(entry);
|
|
174
199
|
const stackValues = valid
|
|
175
|
-
? this.getStackValues(
|
|
200
|
+
? this.getStackValues(entry, xKey, parseValue, stackingContext)
|
|
176
201
|
: { y0: this.baseline, y1: this.baseline };
|
|
177
202
|
return {
|
|
178
|
-
data:
|
|
203
|
+
data: entry,
|
|
179
204
|
valid,
|
|
180
|
-
|
|
181
|
-
|
|
205
|
+
snapshotKey: snapshotKeys[index] ?? String(index),
|
|
206
|
+
x: getXPosition(entry),
|
|
207
|
+
y0: y(stackValues.y0) ?? 0,
|
|
208
|
+
y1: y(stackValues.y1) ?? 0,
|
|
182
209
|
};
|
|
183
210
|
});
|
|
184
|
-
|
|
211
|
+
}
|
|
212
|
+
buildAnimatedAreaData(areaData, animation) {
|
|
213
|
+
return areaData.map((entry) => {
|
|
214
|
+
if (!animation || !entry.valid) {
|
|
215
|
+
return entry;
|
|
216
|
+
}
|
|
217
|
+
const previousSnapshot = animation.previousSnapshot?.get(entry.snapshotKey);
|
|
218
|
+
return {
|
|
219
|
+
...entry,
|
|
220
|
+
x: previousSnapshot?.x ?? entry.x,
|
|
221
|
+
y0: previousSnapshot?.y0 ?? entry.y0,
|
|
222
|
+
y1: previousSnapshot?.y1 ?? entry.y0,
|
|
223
|
+
};
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
createSnapshot(areaData) {
|
|
227
|
+
const snapshot = new Map();
|
|
228
|
+
areaData.forEach((entry) => {
|
|
229
|
+
if (!entry.valid) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
snapshot.set(entry.snapshotKey, {
|
|
233
|
+
x: entry.x,
|
|
234
|
+
y0: entry.y0,
|
|
235
|
+
y1: entry.y1,
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
return snapshot;
|
|
239
|
+
}
|
|
240
|
+
renderAreaFill(plotGroup, areaData, animatedAreaData, curveFactory, sanitizedKey, animation) {
|
|
241
|
+
const areaOpacity = Math.max(0, Math.min(1, this.opacity));
|
|
185
242
|
const areaGenerator = area()
|
|
186
243
|
.defined((d) => d.valid)
|
|
187
244
|
.curve(curveFactory)
|
|
188
|
-
.x(
|
|
189
|
-
.y0((
|
|
190
|
-
.y1((
|
|
191
|
-
const
|
|
192
|
-
const
|
|
193
|
-
plotGroup
|
|
245
|
+
.x((entry) => entry.x)
|
|
246
|
+
.y0((entry) => entry.y0)
|
|
247
|
+
.y1((entry) => entry.y1);
|
|
248
|
+
const finalAreaPath = areaGenerator(areaData);
|
|
249
|
+
const areaPath = plotGroup
|
|
194
250
|
.append('path')
|
|
195
251
|
.datum(areaData)
|
|
196
252
|
.attr('class', `area-${sanitizedKey}`)
|
|
197
253
|
.attr('fill', this.fill)
|
|
198
254
|
.attr('fill-opacity', areaOpacity)
|
|
199
255
|
.attr('stroke', 'none')
|
|
200
|
-
.attr('d', areaGenerator);
|
|
201
|
-
if (
|
|
202
|
-
|
|
203
|
-
.defined((d) => d.valid)
|
|
204
|
-
.curve(curveFactory)
|
|
205
|
-
.x(getXPosition)
|
|
206
|
-
.y((d) => y(d.y1) || 0);
|
|
207
|
-
const lineStrokeWidth = this.strokeWidth ?? theme.line.strokeWidth;
|
|
208
|
-
plotGroup
|
|
209
|
-
.append('path')
|
|
210
|
-
.datum(areaData)
|
|
211
|
-
.attr('class', `area-line-${sanitizedKey}`)
|
|
212
|
-
.attr('fill', 'none')
|
|
213
|
-
.attr('stroke', this.stroke)
|
|
214
|
-
.attr('stroke-width', lineStrokeWidth)
|
|
215
|
-
.attr('d', lineGenerator);
|
|
256
|
+
.attr('d', this.getInitialAreaPathValue(finalAreaPath, areaData, animatedAreaData, areaGenerator, animation));
|
|
257
|
+
if (!animation || !finalAreaPath) {
|
|
258
|
+
return [];
|
|
216
259
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
260
|
+
const revealTransitions = this.createRevealTransitions(areaPath.node(), `area-${sanitizedKey}-reveal`, animation, animation.previousAreaRevealProgress);
|
|
261
|
+
const transition = areaPath
|
|
262
|
+
.transition()
|
|
263
|
+
.duration(animation.duration)
|
|
264
|
+
.ease(animation.easing)
|
|
265
|
+
.attr('d', finalAreaPath);
|
|
266
|
+
return [
|
|
267
|
+
...revealTransitions,
|
|
268
|
+
createTransitionCompletionPromise(transition),
|
|
269
|
+
];
|
|
270
|
+
}
|
|
271
|
+
renderLinePath(plotGroup, areaData, animatedAreaData, curveFactory, theme, sanitizedKey, animation) {
|
|
272
|
+
const lineGenerator = line()
|
|
273
|
+
.defined((d) => d.valid)
|
|
274
|
+
.curve(curveFactory)
|
|
275
|
+
.x((entry) => entry.x)
|
|
276
|
+
.y((entry) => entry.y1);
|
|
277
|
+
const lineStrokeWidth = this.strokeWidth ?? theme.line.strokeWidth;
|
|
278
|
+
const finalPath = lineGenerator(areaData);
|
|
279
|
+
const linePath = plotGroup
|
|
280
|
+
.append('path')
|
|
281
|
+
.datum(areaData)
|
|
282
|
+
.attr('class', `area-line-${sanitizedKey}`)
|
|
283
|
+
.attr('fill', 'none')
|
|
284
|
+
.attr('stroke', this.stroke)
|
|
285
|
+
.attr('stroke-width', lineStrokeWidth)
|
|
286
|
+
.attr('d', this.getInitialLinePathValue(finalPath, areaData, animatedAreaData, lineGenerator, animation));
|
|
287
|
+
if (!animation || !finalPath) {
|
|
288
|
+
return [];
|
|
234
289
|
}
|
|
235
|
-
|
|
236
|
-
|
|
290
|
+
const revealTransitions = this.createRevealTransitions(linePath.node(), `area-line-${sanitizedKey}-reveal`, animation, animation.previousLineRevealProgress, lineStrokeWidth);
|
|
291
|
+
const transition = linePath
|
|
292
|
+
.transition()
|
|
293
|
+
.duration(animation.duration)
|
|
294
|
+
.ease(animation.easing)
|
|
295
|
+
.attr('d', finalPath);
|
|
296
|
+
return [
|
|
297
|
+
...revealTransitions,
|
|
298
|
+
createTransitionCompletionPromise(transition),
|
|
299
|
+
];
|
|
300
|
+
}
|
|
301
|
+
getInitialAreaPathValue(finalAreaPath, areaData, animatedAreaData, areaGenerator, animation) {
|
|
302
|
+
if (animation?.mode === 'initial') {
|
|
303
|
+
return finalAreaPath;
|
|
304
|
+
}
|
|
305
|
+
if (!animation) {
|
|
306
|
+
return finalAreaPath;
|
|
307
|
+
}
|
|
308
|
+
return (animation.previousAreaPath ??
|
|
309
|
+
areaGenerator(animation ? animatedAreaData : areaData));
|
|
310
|
+
}
|
|
311
|
+
getInitialLinePathValue(finalPath, areaData, animatedAreaData, lineGenerator, animation) {
|
|
312
|
+
if (animation?.mode === 'initial') {
|
|
313
|
+
return finalPath;
|
|
314
|
+
}
|
|
315
|
+
if (!animation) {
|
|
316
|
+
return finalPath;
|
|
317
|
+
}
|
|
318
|
+
return (animation.previousLinePath ??
|
|
319
|
+
lineGenerator(animation ? animatedAreaData : areaData));
|
|
320
|
+
}
|
|
321
|
+
createRevealTransitions(path, revealId, animation, previousRevealProgress, padding = 0) {
|
|
322
|
+
if (animation.mode === 'initial') {
|
|
323
|
+
return createLeftToRightRevealTransition(path, animation.duration, animation.easing, revealId, padding);
|
|
324
|
+
}
|
|
325
|
+
if (previousRevealProgress == null) {
|
|
326
|
+
return [];
|
|
327
|
+
}
|
|
328
|
+
return createLeftToRightRevealTransition(path, animation.duration, animation.easing, revealId, padding, previousRevealProgress);
|
|
329
|
+
}
|
|
330
|
+
renderPoints(plotGroup, areaData, animatedAreaData, theme, sanitizedKey, animation) {
|
|
331
|
+
const validData = areaData.filter((d) => d.valid);
|
|
332
|
+
const validAnimatedData = animatedAreaData.filter((d) => d.valid);
|
|
333
|
+
const pointSize = this.pointSize ?? theme.line.point.size;
|
|
334
|
+
const pointStrokeWidth = theme.line.point.strokeWidth;
|
|
335
|
+
const pointStrokeColor = theme.line.point.strokeColor || this.stroke;
|
|
336
|
+
const pointColor = theme.line.point.color || this.stroke;
|
|
337
|
+
const isInitialAnimation = animation?.mode === 'initial';
|
|
338
|
+
const circles = plotGroup
|
|
339
|
+
.selectAll(`.area-point-${sanitizedKey}`)
|
|
340
|
+
.data(validData)
|
|
341
|
+
.join('circle')
|
|
342
|
+
.attr('class', `area-point-${sanitizedKey}`)
|
|
343
|
+
.attr('cx', (_, index) => (isInitialAnimation
|
|
344
|
+
? validData[index]?.x
|
|
345
|
+
: animation
|
|
346
|
+
? validAnimatedData[index]?.x
|
|
347
|
+
: validData[index]?.x) ?? 0)
|
|
348
|
+
.attr('cy', (_, index) => (isInitialAnimation
|
|
349
|
+
? validData[index]?.y1
|
|
350
|
+
: animation
|
|
351
|
+
? validAnimatedData[index]?.y1
|
|
352
|
+
: validData[index]?.y1) ?? 0)
|
|
353
|
+
.attr('r', isInitialAnimation ? 0 : pointSize)
|
|
354
|
+
.attr('fill', pointColor)
|
|
355
|
+
.attr('stroke', pointStrokeColor)
|
|
356
|
+
.attr('stroke-width', pointStrokeWidth);
|
|
357
|
+
if (!animation) {
|
|
358
|
+
return [];
|
|
359
|
+
}
|
|
360
|
+
if (isInitialAnimation) {
|
|
361
|
+
const transition = circles
|
|
362
|
+
.transition()
|
|
363
|
+
.delay((_, index) => {
|
|
364
|
+
return getEnterStaggerTiming(index, validData.length, animation.duration).delay;
|
|
365
|
+
})
|
|
366
|
+
.duration((_, index) => {
|
|
367
|
+
return getEnterStaggerTiming(index, validData.length, animation.duration).duration;
|
|
368
|
+
})
|
|
369
|
+
.ease(animation.easing)
|
|
370
|
+
.attr('r', pointSize);
|
|
371
|
+
return [createTransitionCompletionPromise(transition)];
|
|
237
372
|
}
|
|
373
|
+
const transition = circles
|
|
374
|
+
.transition()
|
|
375
|
+
.duration(animation.duration)
|
|
376
|
+
.ease(animation.easing)
|
|
377
|
+
.attr('cx', (_, index) => validData[index]?.x ?? 0)
|
|
378
|
+
.attr('cy', (_, index) => validData[index]?.y1 ?? 0);
|
|
379
|
+
return [createTransitionCompletionPromise(transition)];
|
|
238
380
|
}
|
|
239
|
-
renderValueLabels(plotGroup, data, y, parseValue, theme
|
|
381
|
+
renderValueLabels(plotGroup, data, y, parseValue, theme) {
|
|
240
382
|
const config = this.valueLabel;
|
|
241
383
|
const fontSize = config.fontSize ?? theme.valueLabel.fontSize;
|
|
242
384
|
const fontFamily = config.fontFamily ?? theme.valueLabel.fontFamily;
|
|
@@ -260,9 +402,11 @@ export class Area {
|
|
|
260
402
|
if (!Number.isFinite(parsedValue)) {
|
|
261
403
|
return;
|
|
262
404
|
}
|
|
263
|
-
const valueText =
|
|
264
|
-
|
|
265
|
-
|
|
405
|
+
const valueText = config.formatter
|
|
406
|
+
? config.formatter(this.dataKey, parsedValue, d.data)
|
|
407
|
+
: String(parsedValue);
|
|
408
|
+
const xPos = d.x;
|
|
409
|
+
const yPos = d.y1;
|
|
266
410
|
const tempText = labelGroup
|
|
267
411
|
.append('text')
|
|
268
412
|
.style('font-size', `${fontSize}px`)
|
package/dist/bar.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Selection } from 'd3';
|
|
2
2
|
import type { BarConfig, BarStackingContext, BarSide, BarValueLabelConfig, ChartTheme, D3Scale, DataItem, Orientation, ScaleType, ExportHooks, BarConfigBase } from './types.js';
|
|
3
3
|
import type { ChartComponent } from './chart-interface.js';
|
|
4
|
+
import type { XYBarAnimationContext, XYBarSnapshot, XYSeriesRenderResult } from './xy-motion/types.js';
|
|
4
5
|
export declare class Bar implements ChartComponent<BarConfigBase> {
|
|
5
6
|
readonly type: "bar";
|
|
6
7
|
readonly dataKey: string;
|
|
@@ -14,9 +15,33 @@ export declare class Bar implements ChartComponent<BarConfigBase> {
|
|
|
14
15
|
getExportConfig(): BarConfigBase;
|
|
15
16
|
createExportComponent(override?: Partial<BarConfigBase>): ChartComponent<BarConfigBase>;
|
|
16
17
|
getRenderedValue(value: number, orientation?: Orientation): number;
|
|
17
|
-
render(plotGroup: Selection<SVGGElement, undefined, null, undefined>, data: DataItem[], xKey: string, x: D3Scale, y: D3Scale, parseValue: (value: unknown) => number, xScaleType?: ScaleType, theme?: ChartTheme, stackingContext?: BarStackingContext, orientation?: 'vertical' | 'horizontal'):
|
|
18
|
+
render(plotGroup: Selection<SVGGElement, undefined, null, undefined>, data: DataItem[], xKey: string, x: D3Scale, y: D3Scale, parseValue: (value: unknown) => number, xScaleType?: ScaleType, theme?: ChartTheme, stackingContext?: BarStackingContext, orientation?: 'vertical' | 'horizontal', animation?: XYBarAnimationContext): XYSeriesRenderResult<XYBarSnapshot>;
|
|
18
19
|
private renderVertical;
|
|
19
20
|
private renderHorizontal;
|
|
21
|
+
private createVerticalLayoutData;
|
|
22
|
+
private createHorizontalLayoutData;
|
|
23
|
+
private createAnimatedVerticalLayoutData;
|
|
24
|
+
private createAnimatedHorizontalLayoutData;
|
|
25
|
+
private resolveAnimatedLayoutDatum;
|
|
26
|
+
private createSnapshot;
|
|
27
|
+
private renderBars;
|
|
28
|
+
private resolveValueLabelConfig;
|
|
29
|
+
private resolveValueLabelPlacement;
|
|
30
|
+
private resolveValueLabelStyle;
|
|
31
|
+
private getValueLabelText;
|
|
32
|
+
private getBarColor;
|
|
33
|
+
private measureLabelBox;
|
|
34
|
+
private getLabelColor;
|
|
35
|
+
private appendValueLabel;
|
|
36
|
+
private getVerticalLabelPlacement;
|
|
37
|
+
private getVerticalOutsideLabelPlacement;
|
|
38
|
+
private getVerticalInsideLabelY;
|
|
39
|
+
private getHorizontalLabelPlacement;
|
|
40
|
+
private getHorizontalOutsideLabelPlacement;
|
|
41
|
+
private getHorizontalInsideLabelX;
|
|
42
|
+
private isHorizontalLabelWithinBounds;
|
|
43
|
+
private renderVerticalValueLabel;
|
|
44
|
+
private renderHorizontalValueLabel;
|
|
20
45
|
private renderVerticalValueLabels;
|
|
21
46
|
private renderHorizontalValueLabels;
|
|
22
47
|
}
|