@internetstiftelsen/charts 0.8.0 → 0.9.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 +53 -8
- package/area.d.ts +0 -1
- package/area.js +2 -19
- package/bar.d.ts +0 -1
- package/bar.js +5 -22
- package/base-chart.d.ts +54 -5
- package/base-chart.js +260 -73
- package/donut-chart.d.ts +7 -9
- package/donut-chart.js +51 -131
- package/gauge-chart.d.ts +18 -7
- package/gauge-chart.js +315 -106
- package/line.js +3 -25
- package/package.json +3 -1
- package/pie-chart.d.ts +7 -11
- package/pie-chart.js +30 -153
- package/radial-chart-base.d.ts +25 -0
- package/radial-chart-base.js +77 -0
- package/scale-utils.d.ts +3 -0
- package/scale-utils.js +14 -0
- package/utils.d.ts +7 -0
- package/utils.js +24 -0
- package/word-cloud-chart.d.ts +32 -0
- package/word-cloud-chart.js +199 -0
- package/xy-chart.d.ts +6 -4
- package/xy-chart.js +77 -126
package/README.md
CHANGED
|
@@ -6,7 +6,8 @@ A framework-agnostic, composable charting library built on D3.js with TypeScript
|
|
|
6
6
|
|
|
7
7
|
- **Framework Agnostic** - Works with vanilla JS, React, Vue, Svelte, or any framework
|
|
8
8
|
- **Composable Architecture** - Build charts by composing components
|
|
9
|
-
- **Multiple Chart Types** - XYChart (lines, areas, bars), DonutChart, PieChart, and GaugeChart
|
|
9
|
+
- **Multiple Chart Types** - XYChart (lines, areas, bars), WordCloudChart, DonutChart, PieChart, and GaugeChart
|
|
10
|
+
- **Optional Gauge Animation** - Animate gauge value transitions with `gauge.animate`
|
|
10
11
|
- **Stacking Control** - Bar stacking modes with optional reversed visual series order
|
|
11
12
|
- **Flexible Scales** - Band, linear, time, and logarithmic scales
|
|
12
13
|
- **Auto Resize** - Built-in ResizeObserver handles responsive behavior
|
|
@@ -81,6 +82,37 @@ chart
|
|
|
81
82
|
chart.render('#chart-container');
|
|
82
83
|
```
|
|
83
84
|
|
|
85
|
+
## Word Cloud
|
|
86
|
+
|
|
87
|
+
```javascript
|
|
88
|
+
import { WordCloudChart } from '@internetstiftelsen/charts/word-cloud-chart';
|
|
89
|
+
|
|
90
|
+
const data = [
|
|
91
|
+
{ word: 'internet', count: 96 },
|
|
92
|
+
{ word: 'social', count: 82 },
|
|
93
|
+
{ word: 'news', count: 75 },
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
const chart = new WordCloudChart({
|
|
97
|
+
data,
|
|
98
|
+
wordCloud: {
|
|
99
|
+
minValue: 5,
|
|
100
|
+
minWordLength: 3,
|
|
101
|
+
minFontSize: 3,
|
|
102
|
+
maxFontSize: 20,
|
|
103
|
+
padding: 1,
|
|
104
|
+
spiral: 'archimedean',
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
chart.render('#word-cloud');
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`minFontSize` and `maxFontSize` are percentages of the smaller plot-area
|
|
112
|
+
dimension and define the relative size range passed into `d3-cloud`. The chart
|
|
113
|
+
expects flat `{ word, count }` rows, aggregates duplicate words after trimming,
|
|
114
|
+
and maps theme typography and colors directly into the layout and rendered SVG.
|
|
115
|
+
|
|
84
116
|
## Export
|
|
85
117
|
|
|
86
118
|
`chart.export()` supports `svg`, `json`, `csv`, `xlsx`, `png`, `jpg`, and `pdf`.
|
|
@@ -104,12 +136,9 @@ It auto-detects grouped and normal (flat) table layouts.
|
|
|
104
136
|
import { toChartData } from '@internetstiftelsen/charts/utils';
|
|
105
137
|
import { XYChart } from '@internetstiftelsen/charts/xy-chart';
|
|
106
138
|
|
|
107
|
-
const data = toChartData(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
categoryKey: 'Category',
|
|
111
|
-
},
|
|
112
|
-
);
|
|
139
|
+
const data = toChartData('\t\tDaily\tWeekly\nAll users\tSegment A\t85%\t92%\n\tSegment B\t84%\t91%', {
|
|
140
|
+
categoryKey: 'Category',
|
|
141
|
+
});
|
|
113
142
|
|
|
114
143
|
const chart = new XYChart({ data });
|
|
115
144
|
chart.render('#chart-container');
|
|
@@ -118,16 +147,32 @@ chart.render('#chart-container');
|
|
|
118
147
|
The parser supports JSON-escaped string payloads and grouped carry-forward row
|
|
119
148
|
structure (blank first column on continuation rows).
|
|
120
149
|
|
|
150
|
+
Supported input shapes:
|
|
151
|
+
|
|
152
|
+
- Plain tab-delimited strings
|
|
153
|
+
- JSON-escaped string payloads
|
|
154
|
+
|
|
155
|
+
Auto-detection behavior:
|
|
156
|
+
|
|
157
|
+
- Grouped rows when a carry-forward group structure is present
|
|
158
|
+
- Flat rows when no grouped continuation rows are detected
|
|
159
|
+
|
|
160
|
+
Grouped parsing rules:
|
|
161
|
+
|
|
162
|
+
- Header row starts with two structural columns (`group`, `category`) before metrics
|
|
163
|
+
- Continuation rows leave the first column blank to inherit the previous group
|
|
164
|
+
- Blank separator rows are ignored
|
|
165
|
+
|
|
121
166
|
## Documentation
|
|
122
167
|
|
|
123
168
|
- [Getting Started](./docs/getting-started.md) - Installation, Vanilla JS, React integration
|
|
124
169
|
- [XYChart](./docs/xy-chart.md) - Line, area, and bar charts API
|
|
170
|
+
- [WordCloudChart](./docs/word-cloud-chart.md) - Word frequency visualization API
|
|
125
171
|
- [DonutChart](./docs/donut-chart.md) - Donut/pie charts API
|
|
126
172
|
- [PieChart](./docs/pie-chart.md) - Pie chart API
|
|
127
173
|
- [GaugeChart](./docs/gauge-chart.md) - Gauge chart API
|
|
128
174
|
- [Components](./docs/components.md) - Axes, Grid, Tooltip, Legend, Title
|
|
129
175
|
- [Theming](./docs/theming.md) - Colors, fonts, and styling
|
|
130
|
-
- [Advanced](./docs/advanced.md) - Scales, TypeScript, architecture, performance
|
|
131
176
|
|
|
132
177
|
## Browser Support
|
|
133
178
|
|
package/area.d.ts
CHANGED
|
@@ -19,7 +19,6 @@ export declare class Area implements ChartComponent<AreaConfigBase> {
|
|
|
19
19
|
constructor(config: AreaConfig);
|
|
20
20
|
getExportConfig(): AreaConfigBase;
|
|
21
21
|
createExportComponent(override?: Partial<AreaConfigBase>): ChartComponent;
|
|
22
|
-
private getScaledPosition;
|
|
23
22
|
private getStackValues;
|
|
24
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>): void;
|
|
25
24
|
private renderValueLabels;
|
package/area.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { area, curveBasis, curveCardinal, curveLinear, curveMonotoneX, curveNatural, curveStep, line, } from 'd3';
|
|
2
2
|
import { mergeDeep, sanitizeForCSS } from './utils.js';
|
|
3
|
+
import { getScalePosition } from './scale-utils.js';
|
|
3
4
|
const AREA_CURVE_FACTORIES = {
|
|
4
5
|
linear: curveLinear,
|
|
5
6
|
monotone: curveMonotoneX,
|
|
@@ -132,24 +133,6 @@ export class Area {
|
|
|
132
133
|
exportHooks: this.exportHooks,
|
|
133
134
|
});
|
|
134
135
|
}
|
|
135
|
-
getScaledPosition(data, key, scale, scaleType) {
|
|
136
|
-
const value = data[key];
|
|
137
|
-
let scaledValue;
|
|
138
|
-
switch (scaleType) {
|
|
139
|
-
case 'band':
|
|
140
|
-
scaledValue = String(value);
|
|
141
|
-
break;
|
|
142
|
-
case 'time':
|
|
143
|
-
scaledValue =
|
|
144
|
-
value instanceof Date ? value : new Date(String(value));
|
|
145
|
-
break;
|
|
146
|
-
case 'linear':
|
|
147
|
-
case 'log':
|
|
148
|
-
scaledValue = typeof value === 'number' ? value : Number(value);
|
|
149
|
-
break;
|
|
150
|
-
}
|
|
151
|
-
return scale(scaledValue) || 0;
|
|
152
|
-
}
|
|
153
136
|
getStackValues(dataPoint, xKey, parseValue, stackingContext) {
|
|
154
137
|
const value = parseValue(dataPoint[this.dataKey]);
|
|
155
138
|
if (!stackingContext || stackingContext.mode === 'none') {
|
|
@@ -176,7 +159,7 @@ export class Area {
|
|
|
176
159
|
}
|
|
177
160
|
render(plotGroup, data, xKey, x, y, parseValue, xScaleType = 'band', theme, stackingContext, valueLabelLayer) {
|
|
178
161
|
const getXPosition = (d) => {
|
|
179
|
-
const scaled =
|
|
162
|
+
const scaled = getScalePosition(x, d.data[xKey], xScaleType);
|
|
180
163
|
return scaled + (x.bandwidth ? x.bandwidth() / 2 : 0);
|
|
181
164
|
};
|
|
182
165
|
const hasValidValue = (d) => {
|
package/bar.d.ts
CHANGED
|
@@ -13,7 +13,6 @@ export declare class Bar implements ChartComponent<BarConfigBase> {
|
|
|
13
13
|
constructor(config: BarConfig);
|
|
14
14
|
getExportConfig(): BarConfigBase;
|
|
15
15
|
createExportComponent(override?: Partial<BarConfigBase>): ChartComponent;
|
|
16
|
-
private getScaledPosition;
|
|
17
16
|
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): void;
|
|
18
17
|
private renderVertical;
|
|
19
18
|
private renderHorizontal;
|
package/bar.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getContrastTextColor, sanitizeForCSS, mergeDeep } from './utils.js';
|
|
2
|
+
import { getScalePosition } from './scale-utils.js';
|
|
2
3
|
const LABEL_INSET_DEFAULT = 4;
|
|
3
4
|
const LABEL_INSET_STACKED = 6;
|
|
4
5
|
const LABEL_MIN_PADDING_DEFAULT = 8;
|
|
@@ -88,24 +89,6 @@ export class Bar {
|
|
|
88
89
|
exportHooks: this.exportHooks,
|
|
89
90
|
});
|
|
90
91
|
}
|
|
91
|
-
getScaledPosition(data, key, scale, scaleType) {
|
|
92
|
-
const value = data[key];
|
|
93
|
-
let scaledValue;
|
|
94
|
-
switch (scaleType) {
|
|
95
|
-
case 'band':
|
|
96
|
-
scaledValue = String(value);
|
|
97
|
-
break;
|
|
98
|
-
case 'time':
|
|
99
|
-
scaledValue =
|
|
100
|
-
value instanceof Date ? value : new Date(String(value));
|
|
101
|
-
break;
|
|
102
|
-
case 'linear':
|
|
103
|
-
case 'log':
|
|
104
|
-
scaledValue = typeof value === 'number' ? value : Number(value);
|
|
105
|
-
break;
|
|
106
|
-
}
|
|
107
|
-
return scale(scaledValue) || 0;
|
|
108
|
-
}
|
|
109
92
|
render(plotGroup, data, xKey, x, y, parseValue, xScaleType = 'band', theme, stackingContext) {
|
|
110
93
|
if (this.orientation === 'vertical') {
|
|
111
94
|
this.renderVertical(plotGroup, data, xKey, x, y, parseValue, xScaleType, stackingContext);
|
|
@@ -178,7 +161,7 @@ export class Bar {
|
|
|
178
161
|
.attr('class', `bar-${sanitizedKey}`)
|
|
179
162
|
.attr('data-index', (_, i) => i)
|
|
180
163
|
.attr('x', (d) => {
|
|
181
|
-
const xPos =
|
|
164
|
+
const xPos = getScalePosition(x, d[xKey], xScaleType);
|
|
182
165
|
return xScaleType === 'band'
|
|
183
166
|
? xPos + barOffset
|
|
184
167
|
: xPos - barWidth / 2;
|
|
@@ -301,7 +284,7 @@ export class Bar {
|
|
|
301
284
|
}
|
|
302
285
|
})
|
|
303
286
|
.attr('y', (d) => {
|
|
304
|
-
const yPos =
|
|
287
|
+
const yPos = getScalePosition(y, d[xKey], yScaleType);
|
|
305
288
|
return yScaleType === 'band'
|
|
306
289
|
? yPos + barOffset
|
|
307
290
|
: yPos - barHeight / 2;
|
|
@@ -388,7 +371,7 @@ export class Bar {
|
|
|
388
371
|
const categoryKey = String(d[xKey]);
|
|
389
372
|
const value = parseValue(d[this.dataKey]);
|
|
390
373
|
const valueText = String(value);
|
|
391
|
-
const xPos =
|
|
374
|
+
const xPos = getScalePosition(x, d[xKey], xScaleType);
|
|
392
375
|
const barColor = this.colorAdapter
|
|
393
376
|
? this.colorAdapter(d, i)
|
|
394
377
|
: this.fill;
|
|
@@ -572,7 +555,7 @@ export class Bar {
|
|
|
572
555
|
const categoryKey = String(d[xKey]);
|
|
573
556
|
const value = parseValue(d[this.dataKey]);
|
|
574
557
|
const valueText = String(value);
|
|
575
|
-
const yPos =
|
|
558
|
+
const yPos = getScalePosition(y, d[xKey], yScaleType);
|
|
576
559
|
const barColor = this.colorAdapter
|
|
577
560
|
? this.colorAdapter(d, i)
|
|
578
561
|
: this.fill;
|
package/base-chart.d.ts
CHANGED
|
@@ -18,6 +18,34 @@ type ResponsiveOverrides = {
|
|
|
18
18
|
theme?: DeepPartial<ChartTheme>;
|
|
19
19
|
components: Map<ChartComponent, Record<string, unknown>>;
|
|
20
20
|
};
|
|
21
|
+
type BaseLayoutComponentsOptions = {
|
|
22
|
+
title?: boolean;
|
|
23
|
+
xAxis?: boolean;
|
|
24
|
+
yAxis?: boolean;
|
|
25
|
+
inlineLegend?: boolean;
|
|
26
|
+
};
|
|
27
|
+
type BaseExportComponentsOptions = {
|
|
28
|
+
title?: boolean;
|
|
29
|
+
grid?: boolean;
|
|
30
|
+
xAxis?: boolean;
|
|
31
|
+
yAxis?: boolean;
|
|
32
|
+
tooltip?: boolean;
|
|
33
|
+
legend?: boolean;
|
|
34
|
+
};
|
|
35
|
+
export type BaseLayoutContext = {
|
|
36
|
+
svg: Selection<SVGSVGElement, undefined, null, undefined>;
|
|
37
|
+
svgNode: SVGSVGElement;
|
|
38
|
+
};
|
|
39
|
+
export type BaseRenderContext = BaseLayoutContext & {
|
|
40
|
+
plotGroup: Selection<SVGGElement, undefined, null, undefined>;
|
|
41
|
+
plotArea: PlotAreaBounds;
|
|
42
|
+
};
|
|
43
|
+
type ComponentSlot<TComponent extends ChartComponent = ChartComponent> = {
|
|
44
|
+
type: TComponent['type'];
|
|
45
|
+
get: () => TComponent | null;
|
|
46
|
+
set: (component: TComponent | null) => void;
|
|
47
|
+
onRegister?: (component: TComponent) => void;
|
|
48
|
+
};
|
|
21
49
|
export type BaseChartConfig = {
|
|
22
50
|
data: ChartData;
|
|
23
51
|
theme?: Partial<ChartTheme>;
|
|
@@ -49,12 +77,14 @@ export declare abstract class BaseChart {
|
|
|
49
77
|
protected resizeObserver: ResizeObserver | null;
|
|
50
78
|
protected layoutManager: LayoutManager;
|
|
51
79
|
protected plotArea: PlotAreaBounds | null;
|
|
80
|
+
private readyPromise;
|
|
52
81
|
private disconnectedLegendContainer;
|
|
82
|
+
private renderThemeOverride;
|
|
53
83
|
protected constructor(config: BaseChartConfig);
|
|
54
84
|
/**
|
|
55
85
|
* Adds a component (axis, grid, tooltip, etc.) to the chart
|
|
56
86
|
*/
|
|
57
|
-
|
|
87
|
+
addChild(component: ChartComponent): this;
|
|
58
88
|
/**
|
|
59
89
|
* Renders the chart to the specified target element
|
|
60
90
|
*/
|
|
@@ -71,13 +101,18 @@ export declare abstract class BaseChart {
|
|
|
71
101
|
}): ResponsiveRenderContext;
|
|
72
102
|
private resolveBreakpointName;
|
|
73
103
|
private resolveRenderTheme;
|
|
74
|
-
private
|
|
104
|
+
private applyRenderTheme;
|
|
105
|
+
protected get renderTheme(): ChartTheme;
|
|
75
106
|
/**
|
|
76
107
|
* Get layout-aware components in order
|
|
77
108
|
* Override in subclasses to provide chart-specific components
|
|
78
109
|
*/
|
|
79
110
|
protected getLayoutComponents(): LayoutAwareComponent[];
|
|
111
|
+
protected getBaseLayoutComponents(options: BaseLayoutComponentsOptions): LayoutAwareComponent[];
|
|
80
112
|
protected getExportComponents(): ChartComponent[];
|
|
113
|
+
protected getOverrideableComponents(): ChartComponent[];
|
|
114
|
+
protected getBaseExportComponents(options: BaseExportComponentsOptions): ChartComponent[];
|
|
115
|
+
protected registerBaseComponent(component: ChartComponent): boolean;
|
|
81
116
|
protected collectExportOverrides(context: ExportRenderContext): Map<ChartComponent, Record<string, unknown>>;
|
|
82
117
|
protected collectResponsiveOverrides(context: ResponsiveRenderContext): ResponsiveOverrides;
|
|
83
118
|
protected runExportHooks(context: ExportHookContext): void;
|
|
@@ -85,7 +120,14 @@ export declare abstract class BaseChart {
|
|
|
85
120
|
private createOverrideComponents;
|
|
86
121
|
protected applyComponentOverrides(overrides: Map<ChartComponent, ChartComponent>): () => void;
|
|
87
122
|
private renderExportChart;
|
|
88
|
-
protected
|
|
123
|
+
protected renderTitle(svg: Selection<SVGSVGElement, undefined, null, undefined>): void;
|
|
124
|
+
protected renderInlineLegend(svg: Selection<SVGSVGElement, undefined, null, undefined>): void;
|
|
125
|
+
protected measureInlineLegend(svgNode: SVGSVGElement): void;
|
|
126
|
+
protected filterVisibleItems<T>(items: T[], getDataKey: (item: T) => string): T[];
|
|
127
|
+
protected validateSourceData(_data: ChartData): void;
|
|
128
|
+
protected syncDerivedState(_previousData?: DataItem[]): void;
|
|
129
|
+
protected initializeDataState(): void;
|
|
130
|
+
protected prepareLayout(context: BaseLayoutContext): void;
|
|
89
131
|
/**
|
|
90
132
|
* Setup ResizeObserver for automatic resize handling
|
|
91
133
|
*/
|
|
@@ -93,8 +135,10 @@ export declare abstract class BaseChart {
|
|
|
93
135
|
/**
|
|
94
136
|
* Subclasses must implement this method to define their rendering logic
|
|
95
137
|
*/
|
|
96
|
-
protected abstract renderChart(): void;
|
|
138
|
+
protected abstract renderChart(context: BaseRenderContext): void;
|
|
97
139
|
protected abstract createExportChart(): BaseChart;
|
|
140
|
+
protected setReadyPromise(promise: Promise<void>): void;
|
|
141
|
+
whenReady(): Promise<void>;
|
|
98
142
|
protected getLegendSeries(): LegendSeries[];
|
|
99
143
|
getLegendItems(): LegendItem[];
|
|
100
144
|
isLegendSeriesVisible(dataKey: string): boolean;
|
|
@@ -114,6 +158,11 @@ export declare abstract class BaseChart {
|
|
|
114
158
|
private resolveDisconnectedLegendHost;
|
|
115
159
|
private cleanupDisconnectedLegendContainer;
|
|
116
160
|
protected parseValue(value: unknown): number;
|
|
161
|
+
protected rerender(): void;
|
|
162
|
+
protected tryRegisterComponent(component: ChartComponent, slots: readonly ComponentSlot[]): boolean;
|
|
163
|
+
protected applySlotOverrides(overrides: Map<ChartComponent, ChartComponent>, slots: readonly ComponentSlot[]): () => void;
|
|
164
|
+
protected applyArrayComponentOverrides<TComponent extends ChartComponent>(components: TComponent[], overrides: Map<ChartComponent, ChartComponent>, isComponent: (component: ChartComponent) => component is TComponent): () => void;
|
|
165
|
+
private getBaseComponentSlots;
|
|
117
166
|
/**
|
|
118
167
|
* Exports the chart in the specified format
|
|
119
168
|
* @param format - The export format
|
|
@@ -131,7 +180,7 @@ export declare abstract class BaseChart {
|
|
|
131
180
|
private exportXLSX;
|
|
132
181
|
private exportImage;
|
|
133
182
|
private exportPDF;
|
|
134
|
-
protected exportSVG(options?: ExportOptions, formatForHooks?: VisualExportFormat): string
|
|
183
|
+
protected exportSVG(options?: ExportOptions, formatForHooks?: VisualExportFormat): Promise<string>;
|
|
135
184
|
protected exportJSON(): string;
|
|
136
185
|
}
|
|
137
186
|
export {};
|