@internetstiftelsen/charts 0.8.0 → 0.9.1

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.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +97 -8
  3. package/{area.d.ts → dist/area.d.ts} +1 -2
  4. package/{area.js → dist/area.js} +2 -19
  5. package/{bar.d.ts → dist/bar.d.ts} +3 -5
  6. package/{bar.js → dist/bar.js} +8 -33
  7. package/{base-chart.d.ts → dist/base-chart.d.ts} +75 -14
  8. package/{base-chart.js → dist/base-chart.js} +429 -122
  9. package/dist/chart-interface.d.ts +19 -0
  10. package/{donut-center-content.d.ts → dist/donut-center-content.d.ts} +1 -1
  11. package/dist/donut-chart.d.ts +51 -0
  12. package/dist/donut-chart.js +374 -0
  13. package/{gauge-chart.d.ts → dist/gauge-chart.d.ts} +19 -8
  14. package/{gauge-chart.js → dist/gauge-chart.js} +317 -106
  15. package/{grid.d.ts → dist/grid.d.ts} +1 -1
  16. package/{layout-manager.d.ts → dist/layout-manager.d.ts} +5 -5
  17. package/{legend.d.ts → dist/legend.d.ts} +3 -1
  18. package/{legend.js → dist/legend.js} +32 -0
  19. package/{line.d.ts → dist/line.d.ts} +1 -1
  20. package/{line.js → dist/line.js} +3 -25
  21. package/{pie-chart.d.ts → dist/pie-chart.d.ts} +10 -21
  22. package/{pie-chart.js → dist/pie-chart.js} +51 -172
  23. package/dist/radial-chart-base.d.ts +25 -0
  24. package/dist/radial-chart-base.js +79 -0
  25. package/dist/scale-utils.d.ts +3 -0
  26. package/dist/scale-utils.js +14 -0
  27. package/{theme.d.ts → dist/theme.d.ts} +2 -0
  28. package/{theme.js → dist/theme.js} +24 -29
  29. package/{title.d.ts → dist/title.d.ts} +1 -1
  30. package/{tooltip.d.ts → dist/tooltip.d.ts} +1 -1
  31. package/{tooltip.js → dist/tooltip.js} +239 -74
  32. package/{types.d.ts → dist/types.d.ts} +27 -10
  33. package/{utils.d.ts → dist/utils.d.ts} +7 -2
  34. package/{utils.js → dist/utils.js} +24 -5
  35. package/dist/word-cloud-chart.d.ts +32 -0
  36. package/dist/word-cloud-chart.js +201 -0
  37. package/{x-axis.d.ts → dist/x-axis.d.ts} +2 -1
  38. package/{x-axis.js → dist/x-axis.js} +18 -14
  39. package/{xy-chart.d.ts → dist/xy-chart.d.ts} +14 -9
  40. package/{xy-chart.js → dist/xy-chart.js} +107 -130
  41. package/{y-axis.d.ts → dist/y-axis.d.ts} +1 -1
  42. package/{y-axis.js → dist/y-axis.js} +4 -4
  43. package/package.json +39 -35
  44. package/chart-interface.d.ts +0 -13
  45. package/donut-chart.d.ts +0 -38
  46. package/donut-chart.js +0 -316
  47. /package/{chart-interface.js → dist/chart-interface.js} +0 -0
  48. /package/{donut-center-content.js → dist/donut-center-content.js} +0 -0
  49. /package/{export-image.d.ts → dist/export-image.d.ts} +0 -0
  50. /package/{export-image.js → dist/export-image.js} +0 -0
  51. /package/{export-pdf.d.ts → dist/export-pdf.d.ts} +0 -0
  52. /package/{export-pdf.js → dist/export-pdf.js} +0 -0
  53. /package/{export-tabular.d.ts → dist/export-tabular.d.ts} +0 -0
  54. /package/{export-tabular.js → dist/export-tabular.js} +0 -0
  55. /package/{export-xlsx.d.ts → dist/export-xlsx.d.ts} +0 -0
  56. /package/{export-xlsx.js → dist/export-xlsx.js} +0 -0
  57. /package/{grid.js → dist/grid.js} +0 -0
  58. /package/{grouped-data.d.ts → dist/grouped-data.d.ts} +0 -0
  59. /package/{grouped-data.js → dist/grouped-data.js} +0 -0
  60. /package/{grouped-tabular.d.ts → dist/grouped-tabular.d.ts} +0 -0
  61. /package/{grouped-tabular.js → dist/grouped-tabular.js} +0 -0
  62. /package/{layout-manager.js → dist/layout-manager.js} +0 -0
  63. /package/{title.js → dist/title.js} +0 -0
  64. /package/{types.js → dist/types.js} +0 -0
  65. /package/{validation.d.ts → dist/validation.d.ts} +0 -0
  66. /package/{validation.js → dist/validation.js} +0 -0
@@ -0,0 +1,32 @@
1
+ import { BaseChart, type BaseChartConfig, type BaseRenderContext } from './base-chart.js';
2
+ import type { ChartData } from './types.js';
3
+ export type WordCloudRotationMode = 'none' | 'right-angle';
4
+ export type WordCloudSpiral = 'archimedean' | 'rectangular';
5
+ export type WordCloudConfig = {
6
+ maxWords?: number;
7
+ minWordLength?: number;
8
+ minValue?: number;
9
+ minFontSize?: number;
10
+ maxFontSize?: number;
11
+ padding?: number;
12
+ rotation?: WordCloudRotationMode;
13
+ spiral?: WordCloudSpiral;
14
+ };
15
+ export type WordCloudChartConfig = Pick<BaseChartConfig, 'data' | 'width' | 'height' | 'theme' | 'responsive'> & {
16
+ wordCloud?: WordCloudConfig;
17
+ };
18
+ export declare class WordCloudChart extends BaseChart {
19
+ private readonly options;
20
+ private layout;
21
+ private layoutRunId;
22
+ private resolvePendingReady;
23
+ constructor(config: WordCloudChartConfig);
24
+ destroy(): void;
25
+ protected validateSourceData(data: ChartData): void;
26
+ protected renderChart({ svg, plotArea }: BaseRenderContext): void;
27
+ protected createExportChart(): BaseChart;
28
+ private startLayout;
29
+ private renderWords;
30
+ private stopLayout;
31
+ private finishReady;
32
+ }
@@ -0,0 +1,201 @@
1
+ import cloud from 'd3-cloud';
2
+ import { scaleSqrt } from 'd3';
3
+ import { BaseChart, } from './base-chart.js';
4
+ import { isGroupedData } from './grouped-data.js';
5
+ const DEFAULT_OPTIONS = {
6
+ maxWords: 75,
7
+ minWordLength: 1,
8
+ minValue: 1,
9
+ minFontSize: 3,
10
+ maxFontSize: 20,
11
+ padding: 1,
12
+ rotation: undefined,
13
+ spiral: 'archimedean',
14
+ };
15
+ const GROUPED_DATA_ERROR = 'WordCloudChart: grouped datasets are not supported; provide a flat array of rows instead';
16
+ function createPreparedWords(data, plotArea, options, colors) {
17
+ const counts = new Map();
18
+ data.forEach((row) => {
19
+ const word = row.word.trim();
20
+ const count = row.count;
21
+ if (!word ||
22
+ word.length < options.minWordLength ||
23
+ !Number.isFinite(count) ||
24
+ count < options.minValue) {
25
+ return;
26
+ }
27
+ counts.set(word, (counts.get(word) ?? 0) + count);
28
+ });
29
+ if (counts.size === 0) {
30
+ throw new Error('WordCloudChart: no valid words remain after filtering; adjust minWordLength or minValue, or provide valid rows');
31
+ }
32
+ const words = Array.from(counts.entries())
33
+ .map(([text, value]) => ({ text, value }))
34
+ .sort((left, right) => right.value - left.value)
35
+ .slice(0, options.maxWords);
36
+ const baseDimension = Math.max(1, Math.min(plotArea.width, plotArea.height));
37
+ const minFontSize = (baseDimension * options.minFontSize) / 100;
38
+ const maxFontSize = (baseDimension * options.maxFontSize) / 100;
39
+ const maxValue = words[0].value;
40
+ const minValue = words[words.length - 1].value;
41
+ const fontScale = maxValue === minValue
42
+ ? null
43
+ : scaleSqrt()
44
+ .domain([minValue, maxValue])
45
+ .range([minFontSize, maxFontSize]);
46
+ return words.map((word, index) => {
47
+ return {
48
+ text: word.text,
49
+ value: word.value,
50
+ size: fontScale ? fontScale(word.value) : maxFontSize,
51
+ color: colors[index % colors.length] ?? '#000000',
52
+ };
53
+ });
54
+ }
55
+ export class WordCloudChart extends BaseChart {
56
+ constructor(config) {
57
+ super(config);
58
+ Object.defineProperty(this, "options", {
59
+ enumerable: true,
60
+ configurable: true,
61
+ writable: true,
62
+ value: void 0
63
+ });
64
+ Object.defineProperty(this, "layout", {
65
+ enumerable: true,
66
+ configurable: true,
67
+ writable: true,
68
+ value: null
69
+ });
70
+ Object.defineProperty(this, "layoutRunId", {
71
+ enumerable: true,
72
+ configurable: true,
73
+ writable: true,
74
+ value: 0
75
+ });
76
+ Object.defineProperty(this, "resolvePendingReady", {
77
+ enumerable: true,
78
+ configurable: true,
79
+ writable: true,
80
+ value: null
81
+ });
82
+ const wordCloud = config.wordCloud ?? {};
83
+ this.options = {
84
+ maxWords: wordCloud.maxWords ?? DEFAULT_OPTIONS.maxWords,
85
+ minWordLength: wordCloud.minWordLength ?? DEFAULT_OPTIONS.minWordLength,
86
+ minValue: wordCloud.minValue ?? DEFAULT_OPTIONS.minValue,
87
+ minFontSize: wordCloud.minFontSize ?? DEFAULT_OPTIONS.minFontSize,
88
+ maxFontSize: wordCloud.maxFontSize ?? DEFAULT_OPTIONS.maxFontSize,
89
+ padding: wordCloud.padding ?? DEFAULT_OPTIONS.padding,
90
+ rotation: wordCloud.rotation,
91
+ spiral: wordCloud.spiral ?? DEFAULT_OPTIONS.spiral,
92
+ };
93
+ this.initializeDataState();
94
+ }
95
+ destroy() {
96
+ this.layoutRunId += 1;
97
+ this.stopLayout();
98
+ this.setReadyPromise(Promise.resolve());
99
+ super.destroy();
100
+ }
101
+ validateSourceData(data) {
102
+ if (isGroupedData(data)) {
103
+ throw new Error(GROUPED_DATA_ERROR);
104
+ }
105
+ }
106
+ renderChart({ svg, plotArea }) {
107
+ this.stopLayout();
108
+ this.renderTitle(svg);
109
+ const words = createPreparedWords(this.data, plotArea, this.options, this.renderTheme.colorPalette);
110
+ this.setReadyPromise(new Promise((resolve) => {
111
+ this.resolvePendingReady = resolve;
112
+ this.startLayout(words, plotArea, ++this.layoutRunId, resolve);
113
+ }));
114
+ }
115
+ createExportChart() {
116
+ return new WordCloudChart({
117
+ data: this.sourceData,
118
+ width: this.configuredWidth,
119
+ height: this.configuredHeight,
120
+ theme: this.theme,
121
+ responsive: this.responsiveConfig,
122
+ wordCloud: this.options,
123
+ });
124
+ }
125
+ startLayout(words, plotArea, runId, resolve) {
126
+ const layout = cloud()
127
+ .words(words.map((word) => ({ ...word })))
128
+ .size([
129
+ Math.max(1, Math.floor(plotArea.width)),
130
+ Math.max(1, Math.floor(plotArea.height)),
131
+ ])
132
+ .padding(this.options.padding)
133
+ .spiral(this.options.spiral)
134
+ .font(this.renderTheme.fontFamily)
135
+ .fontWeight(this.renderTheme.valueLabel.fontWeight)
136
+ .fontSize((word) => word.size)
137
+ .text((word) => word.text)
138
+ .on('end', (placedWords) => {
139
+ this.layout = null;
140
+ if (runId !== this.layoutRunId ||
141
+ !this.plotGroup ||
142
+ !this.plotArea) {
143
+ this.finishReady(resolve);
144
+ return;
145
+ }
146
+ if (placedWords.length < words.length) {
147
+ console.warn(`[Chart Warning] WordCloudChart: rendered ${placedWords.length} of ${words.length} words within the available area; reduce maxWords or font sizes to fit more words`);
148
+ }
149
+ this.renderWords(this.plotGroup, this.plotArea, placedWords);
150
+ this.finishReady(resolve);
151
+ });
152
+ if (this.options.rotation === 'none') {
153
+ layout.rotate(0);
154
+ }
155
+ else if (this.options.rotation === 'right-angle') {
156
+ layout.rotate((_word, index) => (index % 2 === 0 ? 0 : 90));
157
+ }
158
+ this.layout = layout;
159
+ layout.start();
160
+ }
161
+ renderWords(plotGroup, plotArea, words) {
162
+ plotGroup.attr('transform', `translate(${plotArea.left}, ${plotArea.top})`);
163
+ plotGroup
164
+ .append('rect')
165
+ .attr('class', 'word-cloud-viewport')
166
+ .attr('x', 0)
167
+ .attr('y', 0)
168
+ .attr('width', plotArea.width)
169
+ .attr('height', plotArea.height)
170
+ .attr('fill', 'transparent')
171
+ .attr('stroke', 'none')
172
+ .attr('pointer-events', 'none');
173
+ plotGroup
174
+ .append('g')
175
+ .attr('class', 'word-cloud')
176
+ .attr('transform', `translate(${plotArea.width / 2}, ${plotArea.height / 2})`)
177
+ .selectAll('text')
178
+ .data(words)
179
+ .join('text')
180
+ .attr('class', 'word-cloud-word')
181
+ .attr('text-anchor', 'middle')
182
+ .style('font-family', this.renderTheme.fontFamily)
183
+ .style('font-weight', String(this.renderTheme.valueLabel.fontWeight))
184
+ .style('font-size', (word) => `${word.size}px`)
185
+ .style('fill', (word) => word.color)
186
+ .attr('transform', (word) => `translate(${word.x ?? 0}, ${word.y ?? 0}) rotate(${word.rotate ?? 0})`)
187
+ .text((word) => word.text);
188
+ }
189
+ stopLayout() {
190
+ if (this.layout) {
191
+ this.layout.stop();
192
+ this.layout = null;
193
+ }
194
+ this.resolvePendingReady?.();
195
+ this.resolvePendingReady = null;
196
+ }
197
+ finishReady(resolve) {
198
+ this.resolvePendingReady = null;
199
+ resolve();
200
+ }
201
+ }
@@ -22,10 +22,11 @@ export declare class XAxis implements LayoutAwareComponent<XAxisConfigBase> {
22
22
  private readonly minLabelGap;
23
23
  private readonly preserveEndLabels;
24
24
  readonly exportHooks?: ExportHooks<XAxisConfigBase>;
25
+ private resolveFontSizeValue;
25
26
  private resolveGroupLabelStyle;
26
27
  constructor(config?: XAxisConfig);
27
28
  getExportConfig(): XAxisConfigBase;
28
- createExportComponent(override?: Partial<XAxisConfigBase>): LayoutAwareComponent;
29
+ createExportComponent(override?: Partial<XAxisConfigBase>): LayoutAwareComponent<XAxisConfigBase>;
29
30
  /**
30
31
  * Returns the space required by the x-axis
31
32
  */
@@ -2,14 +2,23 @@ import { axisBottom } from 'd3';
2
2
  import { measureTextWidth, truncateText, wrapText, mergeDeep } from './utils.js';
3
3
  import { GROUPED_CATEGORY_ID_KEY, GROUPED_CATEGORY_LABEL_KEY, GROUPED_GAP_TICK_PREFIX, GROUPED_GROUP_LABEL_KEY, } from './grouped-data.js';
4
4
  export class XAxis {
5
+ resolveFontSizeValue(fontSize, fallback) {
6
+ if (typeof fontSize === 'number' && Number.isFinite(fontSize)) {
7
+ return fontSize;
8
+ }
9
+ if (typeof fontSize === 'string') {
10
+ const parsedFontSize = parseFloat(fontSize);
11
+ if (Number.isFinite(parsedFontSize)) {
12
+ return parsedFontSize;
13
+ }
14
+ }
15
+ return fallback;
16
+ }
5
17
  resolveGroupLabelStyle(theme) {
6
18
  const axisGroupLabel = theme.axis.groupLabel;
7
19
  const fontFamily = axisGroupLabel?.fontFamily ?? theme.axis.fontFamily;
8
20
  const fontWeight = axisGroupLabel?.fontWeight ?? '700';
9
- const parsedFontSize = parseFloat(axisGroupLabel?.fontSize ?? theme.axis.fontSize);
10
- const fontSize = Number.isFinite(parsedFontSize)
11
- ? parsedFontSize
12
- : this.fontSize;
21
+ const fontSize = this.resolveFontSizeValue(axisGroupLabel?.fontSize ?? theme.axis.fontSize, this.fontSize);
13
22
  const color = axisGroupLabel?.color ?? '#111827';
14
23
  return {
15
24
  fontFamily,
@@ -91,7 +100,6 @@ export class XAxis {
91
100
  writable: true,
92
101
  value: void 0
93
102
  });
94
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
95
103
  Object.defineProperty(this, "tickFormat", {
96
104
  enumerable: true,
97
105
  configurable: true,
@@ -223,12 +231,7 @@ export class XAxis {
223
231
  this.wrapLineCount = 1;
224
232
  return;
225
233
  }
226
- const parsedFontSize = typeof theme.axis.fontSize === 'string'
227
- ? parseFloat(theme.axis.fontSize)
228
- : theme.axis.fontSize;
229
- this.fontSize = Number.isFinite(parsedFontSize)
230
- ? parsedFontSize
231
- : this.fontSize;
234
+ this.fontSize = this.resolveFontSizeValue(theme.axis.fontSize, this.fontSize);
232
235
  const fontSize = this.fontSize;
233
236
  const fontFamily = theme.axis.fontFamily;
234
237
  const fontWeight = theme.axis.fontWeight || 'normal';
@@ -308,11 +311,12 @@ export class XAxis {
308
311
  }
309
312
  else if (this.tickFormat) {
310
313
  // Apply tick formatting if specified
311
- if (typeof this.tickFormat === 'function') {
312
- axisGenerator.tickFormat(this.tickFormat);
314
+ const tickFormat = this.tickFormat;
315
+ if (typeof tickFormat === 'function') {
316
+ axisGenerator.tickFormat((value) => tickFormat(value));
313
317
  }
314
318
  else {
315
- axisGenerator.ticks(5, this.tickFormat);
319
+ axisGenerator.ticks(5, tickFormat);
316
320
  }
317
321
  }
318
322
  const axis = svg
@@ -1,7 +1,8 @@
1
- import { BaseChart, type BaseChartConfig } from './base-chart.js';
2
- import type { ChartComponent } from './chart-interface.js';
3
- import { type AreaStackConfig, type BarStackConfig, type LegendSeries } from './types.js';
1
+ import { BaseChart, type BaseChartConfig, type BaseLayoutContext, type BaseRenderContext } from './base-chart.js';
2
+ import type { ChartComponentBase } from './chart-interface.js';
3
+ import { type AreaStackConfig, type BarStackConfig, type LegendSeries, type Orientation } from './types.js';
4
4
  export type XYChartConfig = BaseChartConfig & {
5
+ orientation?: Orientation;
5
6
  barStack?: BarStackConfig;
6
7
  areaStack?: AreaStackConfig;
7
8
  };
@@ -11,21 +12,25 @@ export declare class XYChart extends BaseChart {
11
12
  private barStackGap;
12
13
  private barStackReverseSeries;
13
14
  private areaStackMode;
15
+ private readonly orientation;
14
16
  constructor(config: XYChartConfig);
15
- addChild(component: ChartComponent): this;
16
- protected getExportComponents(): ChartComponent[];
17
+ addChild(component: ChartComponentBase): this;
18
+ protected getExportComponents(): ChartComponentBase[];
17
19
  protected createExportChart(): BaseChart;
18
- protected applyComponentOverrides(overrides: Map<ChartComponent, ChartComponent>): () => void;
19
- private rerender;
20
- protected prepareLayout(): void;
21
- protected renderChart(): void;
20
+ protected applyComponentOverrides(overrides: Map<ChartComponentBase, ChartComponentBase>): () => void;
21
+ protected prepareLayout(context: BaseLayoutContext): void;
22
+ protected renderChart({ svg, plotGroup, plotArea, }: BaseRenderContext): void;
22
23
  private getXKey;
23
24
  protected getLegendSeries(): LegendSeries[];
24
25
  private getCategoryScaleType;
25
26
  private getVisibleSeries;
26
27
  private getDisplaySeries;
28
+ private resolveSeriesDefaults;
29
+ private shouldReplaceSeriesColor;
30
+ private cloneSeriesWithOverride;
27
31
  private setupScales;
28
32
  private isHorizontalOrientation;
33
+ private validateSeriesOrientation;
29
34
  private collectSeriesValues;
30
35
  private getStackedAreaGroups;
31
36
  private buildBandDomainWithGroupGaps;