@internetstiftelsen/charts 0.12.0 → 0.13.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 +10 -1
- package/dist/base-chart.d.ts +12 -1
- package/dist/base-chart.js +58 -18
- package/dist/chart-group.d.ts +15 -2
- package/dist/chart-group.js +181 -45
- package/dist/chart-interface.d.ts +3 -3
- package/dist/grouped-data.d.ts +1 -0
- package/dist/grouped-data.js +80 -40
- package/dist/grouped-tabular.js +12 -3
- package/dist/layout-manager.js +4 -4
- package/dist/text.d.ts +40 -0
- package/dist/text.js +217 -0
- package/dist/theme.js +56 -0
- package/dist/title.d.ts +6 -12
- package/dist/title.js +29 -82
- package/dist/types.d.ts +34 -1
- package/docs/chart-group.md +24 -5
- package/docs/components.md +99 -15
- package/docs/donut-chart.md +2 -1
- package/docs/gauge-chart.md +2 -1
- package/docs/getting-started.md +1 -1
- package/docs/pie-chart.md +2 -1
- package/docs/theming.md +35 -0
- package/docs/word-cloud-chart.md +1 -0
- package/package.json +1 -1
package/dist/text.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type Selection } from 'd3';
|
|
2
|
+
import type { ChartTheme, ExportHooks, TextAlign, TextConfig, TextConfigBase, TextPosition } from './types.js';
|
|
3
|
+
import type { ComponentSpace, LayoutAwareComponent } from './chart-interface.js';
|
|
4
|
+
type TextComponentType = 'text' | 'title';
|
|
5
|
+
type ResolvedTextStyle = {
|
|
6
|
+
fontSize: number;
|
|
7
|
+
fontWeight: string;
|
|
8
|
+
fontFamily: string;
|
|
9
|
+
color: string;
|
|
10
|
+
lineHeight: number;
|
|
11
|
+
marginTop: number;
|
|
12
|
+
marginBottom: number;
|
|
13
|
+
};
|
|
14
|
+
export declare class Text implements LayoutAwareComponent<TextConfigBase> {
|
|
15
|
+
readonly type: TextComponentType;
|
|
16
|
+
readonly display: boolean;
|
|
17
|
+
readonly text: string;
|
|
18
|
+
readonly position: TextPosition;
|
|
19
|
+
readonly variant: string;
|
|
20
|
+
readonly align: TextAlign;
|
|
21
|
+
readonly exportHooks?: ExportHooks<TextConfigBase>;
|
|
22
|
+
protected readonly fontSize?: number;
|
|
23
|
+
protected readonly fontWeight?: string;
|
|
24
|
+
protected readonly fontFamily?: string;
|
|
25
|
+
protected readonly color?: string;
|
|
26
|
+
protected readonly lineHeight?: number;
|
|
27
|
+
protected readonly marginTop?: number;
|
|
28
|
+
protected readonly marginBottom?: number;
|
|
29
|
+
constructor(config: TextConfig, componentType?: TextComponentType);
|
|
30
|
+
getExportConfig(): TextConfigBase;
|
|
31
|
+
createExportComponent(override?: Partial<TextConfigBase>): LayoutAwareComponent<TextConfigBase>;
|
|
32
|
+
getRequiredSpace(theme?: ChartTheme): ComponentSpace;
|
|
33
|
+
render(svg: Selection<SVGSVGElement, undefined, null, undefined>, theme: ChartTheme, width: number, x?: number, y?: number): void;
|
|
34
|
+
protected resolveStyle(theme?: ChartTheme): ResolvedTextStyle;
|
|
35
|
+
private resolveStyleValue;
|
|
36
|
+
private getLines;
|
|
37
|
+
private resolveTextPosition;
|
|
38
|
+
private resolveClassName;
|
|
39
|
+
}
|
|
40
|
+
export {};
|
package/dist/text.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { mergeDeep, sanitizeForCSS } from './utils.js';
|
|
2
|
+
export class Text {
|
|
3
|
+
constructor(config, componentType = 'text') {
|
|
4
|
+
Object.defineProperty(this, "type", {
|
|
5
|
+
enumerable: true,
|
|
6
|
+
configurable: true,
|
|
7
|
+
writable: true,
|
|
8
|
+
value: void 0
|
|
9
|
+
});
|
|
10
|
+
Object.defineProperty(this, "display", {
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
writable: true,
|
|
14
|
+
value: void 0
|
|
15
|
+
});
|
|
16
|
+
Object.defineProperty(this, "text", {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
configurable: true,
|
|
19
|
+
writable: true,
|
|
20
|
+
value: void 0
|
|
21
|
+
});
|
|
22
|
+
Object.defineProperty(this, "position", {
|
|
23
|
+
enumerable: true,
|
|
24
|
+
configurable: true,
|
|
25
|
+
writable: true,
|
|
26
|
+
value: void 0
|
|
27
|
+
});
|
|
28
|
+
Object.defineProperty(this, "variant", {
|
|
29
|
+
enumerable: true,
|
|
30
|
+
configurable: true,
|
|
31
|
+
writable: true,
|
|
32
|
+
value: void 0
|
|
33
|
+
});
|
|
34
|
+
Object.defineProperty(this, "align", {
|
|
35
|
+
enumerable: true,
|
|
36
|
+
configurable: true,
|
|
37
|
+
writable: true,
|
|
38
|
+
value: void 0
|
|
39
|
+
});
|
|
40
|
+
Object.defineProperty(this, "exportHooks", {
|
|
41
|
+
enumerable: true,
|
|
42
|
+
configurable: true,
|
|
43
|
+
writable: true,
|
|
44
|
+
value: void 0
|
|
45
|
+
});
|
|
46
|
+
Object.defineProperty(this, "fontSize", {
|
|
47
|
+
enumerable: true,
|
|
48
|
+
configurable: true,
|
|
49
|
+
writable: true,
|
|
50
|
+
value: void 0
|
|
51
|
+
});
|
|
52
|
+
Object.defineProperty(this, "fontWeight", {
|
|
53
|
+
enumerable: true,
|
|
54
|
+
configurable: true,
|
|
55
|
+
writable: true,
|
|
56
|
+
value: void 0
|
|
57
|
+
});
|
|
58
|
+
Object.defineProperty(this, "fontFamily", {
|
|
59
|
+
enumerable: true,
|
|
60
|
+
configurable: true,
|
|
61
|
+
writable: true,
|
|
62
|
+
value: void 0
|
|
63
|
+
});
|
|
64
|
+
Object.defineProperty(this, "color", {
|
|
65
|
+
enumerable: true,
|
|
66
|
+
configurable: true,
|
|
67
|
+
writable: true,
|
|
68
|
+
value: void 0
|
|
69
|
+
});
|
|
70
|
+
Object.defineProperty(this, "lineHeight", {
|
|
71
|
+
enumerable: true,
|
|
72
|
+
configurable: true,
|
|
73
|
+
writable: true,
|
|
74
|
+
value: void 0
|
|
75
|
+
});
|
|
76
|
+
Object.defineProperty(this, "marginTop", {
|
|
77
|
+
enumerable: true,
|
|
78
|
+
configurable: true,
|
|
79
|
+
writable: true,
|
|
80
|
+
value: void 0
|
|
81
|
+
});
|
|
82
|
+
Object.defineProperty(this, "marginBottom", {
|
|
83
|
+
enumerable: true,
|
|
84
|
+
configurable: true,
|
|
85
|
+
writable: true,
|
|
86
|
+
value: void 0
|
|
87
|
+
});
|
|
88
|
+
this.type = componentType;
|
|
89
|
+
this.display = config.display ?? true;
|
|
90
|
+
this.text = config.text;
|
|
91
|
+
this.position = config.position ?? 'top';
|
|
92
|
+
this.variant = config.variant ?? 'caption';
|
|
93
|
+
this.align = config.align ?? 'center';
|
|
94
|
+
this.fontSize = config.fontSize;
|
|
95
|
+
this.fontWeight = config.fontWeight;
|
|
96
|
+
this.fontFamily = config.fontFamily;
|
|
97
|
+
this.color = config.color;
|
|
98
|
+
this.lineHeight = config.lineHeight;
|
|
99
|
+
this.marginTop = config.marginTop;
|
|
100
|
+
this.marginBottom = config.marginBottom;
|
|
101
|
+
this.exportHooks = config.exportHooks;
|
|
102
|
+
}
|
|
103
|
+
getExportConfig() {
|
|
104
|
+
return {
|
|
105
|
+
display: this.display,
|
|
106
|
+
text: this.text,
|
|
107
|
+
position: this.position,
|
|
108
|
+
variant: this.variant,
|
|
109
|
+
align: this.align,
|
|
110
|
+
fontSize: this.fontSize,
|
|
111
|
+
fontWeight: this.fontWeight,
|
|
112
|
+
fontFamily: this.fontFamily,
|
|
113
|
+
color: this.color,
|
|
114
|
+
lineHeight: this.lineHeight,
|
|
115
|
+
marginTop: this.marginTop,
|
|
116
|
+
marginBottom: this.marginBottom,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
createExportComponent(override) {
|
|
120
|
+
const merged = mergeDeep(this.getExportConfig(), override);
|
|
121
|
+
return new Text({
|
|
122
|
+
...merged,
|
|
123
|
+
exportHooks: this.exportHooks,
|
|
124
|
+
}, this.type);
|
|
125
|
+
}
|
|
126
|
+
getRequiredSpace(theme) {
|
|
127
|
+
if (!this.display) {
|
|
128
|
+
return {
|
|
129
|
+
width: 0,
|
|
130
|
+
height: 0,
|
|
131
|
+
position: this.position,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const style = this.resolveStyle(theme);
|
|
135
|
+
const lineCount = this.getLines().length;
|
|
136
|
+
const textHeight = style.fontSize +
|
|
137
|
+
Math.max(0, lineCount - 1) * style.fontSize * style.lineHeight;
|
|
138
|
+
return {
|
|
139
|
+
width: 0,
|
|
140
|
+
height: style.marginTop + textHeight + style.marginBottom,
|
|
141
|
+
position: this.position,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
render(svg, theme, width, x = 0, y = 0) {
|
|
145
|
+
if (!this.display) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const style = this.resolveStyle(theme);
|
|
149
|
+
const { textX, textAnchor } = this.resolveTextPosition(theme, width);
|
|
150
|
+
const textGroup = svg
|
|
151
|
+
.append('g')
|
|
152
|
+
.attr('class', this.resolveClassName())
|
|
153
|
+
.attr('transform', `translate(${x}, ${y})`);
|
|
154
|
+
const text = textGroup
|
|
155
|
+
.append('text')
|
|
156
|
+
.attr('x', textX)
|
|
157
|
+
.attr('text-anchor', textAnchor)
|
|
158
|
+
.attr('font-size', `${style.fontSize}px`)
|
|
159
|
+
.attr('font-weight', style.fontWeight)
|
|
160
|
+
.attr('font-family', style.fontFamily)
|
|
161
|
+
.attr('fill', style.color);
|
|
162
|
+
this.getLines().forEach((line, index) => {
|
|
163
|
+
const tspan = text.append('tspan').attr('x', textX).text(line);
|
|
164
|
+
if (index === 0) {
|
|
165
|
+
tspan.attr('y', style.marginTop + style.fontSize);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
tspan.attr('dy', `${style.lineHeight}em`);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
resolveStyle(theme) {
|
|
172
|
+
const variant = theme?.text.variants[this.variant] ?? {};
|
|
173
|
+
return {
|
|
174
|
+
fontSize: this.resolveStyleValue(this.fontSize, variant.fontSize, 12),
|
|
175
|
+
fontWeight: this.resolveStyleValue(this.fontWeight, variant.fontWeight, 'normal'),
|
|
176
|
+
fontFamily: this.fontFamily ??
|
|
177
|
+
variant.fontFamily ??
|
|
178
|
+
theme?.fontFamily ??
|
|
179
|
+
'',
|
|
180
|
+
color: this.resolveStyleValue(this.color, variant.color, '#1f2a36'),
|
|
181
|
+
lineHeight: this.resolveStyleValue(this.lineHeight, variant.lineHeight, 1.2),
|
|
182
|
+
marginTop: this.resolveStyleValue(this.marginTop, variant.marginTop, 0),
|
|
183
|
+
marginBottom: this.resolveStyleValue(this.marginBottom, variant.marginBottom, 0),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
resolveStyleValue(configured, variant, fallback) {
|
|
187
|
+
return configured ?? variant ?? fallback;
|
|
188
|
+
}
|
|
189
|
+
getLines() {
|
|
190
|
+
return this.text.split('\n');
|
|
191
|
+
}
|
|
192
|
+
resolveTextPosition(theme, width) {
|
|
193
|
+
if (this.align === 'left') {
|
|
194
|
+
return {
|
|
195
|
+
textX: theme.margins.left,
|
|
196
|
+
textAnchor: 'start',
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
if (this.align === 'right') {
|
|
200
|
+
return {
|
|
201
|
+
textX: width - theme.margins.right,
|
|
202
|
+
textAnchor: 'end',
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
textX: width / 2,
|
|
207
|
+
textAnchor: 'middle',
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
resolveClassName() {
|
|
211
|
+
const classes = ['text', `text--${sanitizeForCSS(this.variant)}`];
|
|
212
|
+
if (this.type === 'title') {
|
|
213
|
+
classes.unshift('title');
|
|
214
|
+
}
|
|
215
|
+
return classes.join(' ');
|
|
216
|
+
}
|
|
217
|
+
}
|
package/dist/theme.js
CHANGED
|
@@ -40,6 +40,34 @@ export const defaultTheme = {
|
|
|
40
40
|
itemSpacingX: 20,
|
|
41
41
|
itemSpacingY: 8,
|
|
42
42
|
},
|
|
43
|
+
text: {
|
|
44
|
+
variants: {
|
|
45
|
+
title: {
|
|
46
|
+
fontSize: 18,
|
|
47
|
+
fontWeight: 'bold',
|
|
48
|
+
color: '#1f2a36',
|
|
49
|
+
lineHeight: 1.2,
|
|
50
|
+
marginTop: 10,
|
|
51
|
+
marginBottom: 15,
|
|
52
|
+
},
|
|
53
|
+
subtitle: {
|
|
54
|
+
fontSize: 14,
|
|
55
|
+
fontWeight: 'normal',
|
|
56
|
+
color: '#4b5563',
|
|
57
|
+
lineHeight: 1.35,
|
|
58
|
+
marginTop: 0,
|
|
59
|
+
marginBottom: 12,
|
|
60
|
+
},
|
|
61
|
+
caption: {
|
|
62
|
+
fontSize: 12,
|
|
63
|
+
fontWeight: 'normal',
|
|
64
|
+
color: '#6b7280',
|
|
65
|
+
lineHeight: 1.3,
|
|
66
|
+
marginTop: 8,
|
|
67
|
+
marginBottom: 0,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
43
71
|
tooltip: {
|
|
44
72
|
background: '#ffffff',
|
|
45
73
|
border: '#dddddd',
|
|
@@ -128,6 +156,34 @@ export const newspaperTheme = {
|
|
|
128
156
|
itemSpacingX: 20,
|
|
129
157
|
itemSpacingY: 8,
|
|
130
158
|
},
|
|
159
|
+
text: {
|
|
160
|
+
variants: {
|
|
161
|
+
title: {
|
|
162
|
+
fontSize: 18,
|
|
163
|
+
fontWeight: 'bold',
|
|
164
|
+
color: '#1a1a1a',
|
|
165
|
+
lineHeight: 1.2,
|
|
166
|
+
marginTop: 10,
|
|
167
|
+
marginBottom: 15,
|
|
168
|
+
},
|
|
169
|
+
subtitle: {
|
|
170
|
+
fontSize: 14,
|
|
171
|
+
fontWeight: 'normal',
|
|
172
|
+
color: '#4a4a4a',
|
|
173
|
+
lineHeight: 1.35,
|
|
174
|
+
marginTop: 0,
|
|
175
|
+
marginBottom: 12,
|
|
176
|
+
},
|
|
177
|
+
caption: {
|
|
178
|
+
fontSize: 12,
|
|
179
|
+
fontWeight: 'normal',
|
|
180
|
+
color: '#6b6b6b',
|
|
181
|
+
lineHeight: 1.3,
|
|
182
|
+
marginTop: 8,
|
|
183
|
+
marginBottom: 0,
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
},
|
|
131
187
|
tooltip: {
|
|
132
188
|
background: '#111111',
|
|
133
189
|
border: '#111111',
|
package/dist/title.d.ts
CHANGED
|
@@ -1,23 +1,17 @@
|
|
|
1
1
|
import { type Selection } from 'd3';
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
2
|
+
import type { ChartTheme, ExportHooks, TitleConfig, TitleConfigBase } from './types.js';
|
|
3
|
+
import type { ComponentSpace, LayoutAwareComponent } from './chart-interface.js';
|
|
4
4
|
export declare class Title implements LayoutAwareComponent<TitleConfigBase> {
|
|
5
5
|
readonly type: "title";
|
|
6
6
|
readonly display: boolean;
|
|
7
7
|
readonly text: string;
|
|
8
|
+
readonly position: "top";
|
|
9
|
+
readonly variant = "title";
|
|
8
10
|
readonly exportHooks?: ExportHooks<TitleConfigBase>;
|
|
9
|
-
private readonly
|
|
10
|
-
private readonly fontWeight;
|
|
11
|
-
private readonly fontFamily?;
|
|
12
|
-
private readonly align;
|
|
13
|
-
private readonly marginTop;
|
|
14
|
-
private readonly marginBottom;
|
|
11
|
+
private readonly textComponent;
|
|
15
12
|
constructor(config: TitleConfig);
|
|
16
13
|
getExportConfig(): TitleConfigBase;
|
|
17
14
|
createExportComponent(override?: Partial<TitleConfigBase>): LayoutAwareComponent<TitleConfigBase>;
|
|
18
|
-
|
|
19
|
-
* Returns the space required by the title
|
|
20
|
-
*/
|
|
21
|
-
getRequiredSpace(): ComponentSpace;
|
|
15
|
+
getRequiredSpace(theme?: ChartTheme): ComponentSpace;
|
|
22
16
|
render(svg: Selection<SVGSVGElement, undefined, null, undefined>, theme: ChartTheme, width: number, x?: number, y?: number): void;
|
|
23
17
|
}
|
package/dist/title.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Text } from './text.js';
|
|
1
2
|
import { mergeDeep } from './utils.js';
|
|
2
3
|
export class Title {
|
|
3
4
|
constructor(config) {
|
|
@@ -19,68 +20,53 @@ export class Title {
|
|
|
19
20
|
writable: true,
|
|
20
21
|
value: void 0
|
|
21
22
|
});
|
|
22
|
-
Object.defineProperty(this, "
|
|
23
|
-
enumerable: true,
|
|
24
|
-
configurable: true,
|
|
25
|
-
writable: true,
|
|
26
|
-
value: void 0
|
|
27
|
-
});
|
|
28
|
-
Object.defineProperty(this, "fontSize", {
|
|
29
|
-
enumerable: true,
|
|
30
|
-
configurable: true,
|
|
31
|
-
writable: true,
|
|
32
|
-
value: void 0
|
|
33
|
-
});
|
|
34
|
-
Object.defineProperty(this, "fontWeight", {
|
|
35
|
-
enumerable: true,
|
|
36
|
-
configurable: true,
|
|
37
|
-
writable: true,
|
|
38
|
-
value: void 0
|
|
39
|
-
});
|
|
40
|
-
Object.defineProperty(this, "fontFamily", {
|
|
23
|
+
Object.defineProperty(this, "position", {
|
|
41
24
|
enumerable: true,
|
|
42
25
|
configurable: true,
|
|
43
26
|
writable: true,
|
|
44
|
-
value:
|
|
27
|
+
value: 'top'
|
|
45
28
|
});
|
|
46
|
-
Object.defineProperty(this, "
|
|
29
|
+
Object.defineProperty(this, "variant", {
|
|
47
30
|
enumerable: true,
|
|
48
31
|
configurable: true,
|
|
49
32
|
writable: true,
|
|
50
|
-
value:
|
|
33
|
+
value: 'title'
|
|
51
34
|
});
|
|
52
|
-
Object.defineProperty(this, "
|
|
35
|
+
Object.defineProperty(this, "exportHooks", {
|
|
53
36
|
enumerable: true,
|
|
54
37
|
configurable: true,
|
|
55
38
|
writable: true,
|
|
56
39
|
value: void 0
|
|
57
40
|
});
|
|
58
|
-
Object.defineProperty(this, "
|
|
41
|
+
Object.defineProperty(this, "textComponent", {
|
|
59
42
|
enumerable: true,
|
|
60
43
|
configurable: true,
|
|
61
44
|
writable: true,
|
|
62
45
|
value: void 0
|
|
63
46
|
});
|
|
64
|
-
this.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
this.
|
|
71
|
-
this.
|
|
47
|
+
this.textComponent = new Text({
|
|
48
|
+
...config,
|
|
49
|
+
position: 'top',
|
|
50
|
+
variant: 'title',
|
|
51
|
+
exportHooks: config.exportHooks,
|
|
52
|
+
}, 'title');
|
|
53
|
+
this.display = this.textComponent.display;
|
|
54
|
+
this.text = this.textComponent.text;
|
|
72
55
|
this.exportHooks = config.exportHooks;
|
|
73
56
|
}
|
|
74
57
|
getExportConfig() {
|
|
58
|
+
const config = this.textComponent.getExportConfig();
|
|
75
59
|
return {
|
|
76
|
-
display:
|
|
77
|
-
text:
|
|
78
|
-
fontSize:
|
|
79
|
-
fontWeight:
|
|
80
|
-
fontFamily:
|
|
81
|
-
align:
|
|
82
|
-
|
|
83
|
-
|
|
60
|
+
display: config.display,
|
|
61
|
+
text: config.text,
|
|
62
|
+
fontSize: config.fontSize,
|
|
63
|
+
fontWeight: config.fontWeight,
|
|
64
|
+
fontFamily: config.fontFamily,
|
|
65
|
+
align: config.align,
|
|
66
|
+
color: config.color,
|
|
67
|
+
lineHeight: config.lineHeight,
|
|
68
|
+
marginTop: config.marginTop,
|
|
69
|
+
marginBottom: config.marginBottom,
|
|
84
70
|
};
|
|
85
71
|
}
|
|
86
72
|
createExportComponent(override) {
|
|
@@ -90,49 +76,10 @@ export class Title {
|
|
|
90
76
|
exportHooks: this.exportHooks,
|
|
91
77
|
});
|
|
92
78
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
*/
|
|
96
|
-
getRequiredSpace() {
|
|
97
|
-
if (!this.display) {
|
|
98
|
-
return {
|
|
99
|
-
width: 0,
|
|
100
|
-
height: 0,
|
|
101
|
-
position: 'top',
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
return {
|
|
105
|
-
width: 0, // Title spans full width
|
|
106
|
-
height: this.marginTop + this.fontSize + this.marginBottom,
|
|
107
|
-
position: 'top',
|
|
108
|
-
};
|
|
79
|
+
getRequiredSpace(theme) {
|
|
80
|
+
return this.textComponent.getRequiredSpace(theme);
|
|
109
81
|
}
|
|
110
82
|
render(svg, theme, width, x = 0, y = 0) {
|
|
111
|
-
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
const titleGroup = svg
|
|
115
|
-
.append('g')
|
|
116
|
-
.attr('class', 'title')
|
|
117
|
-
.attr('transform', `translate(${x}, ${y})`);
|
|
118
|
-
let textX = width / 2;
|
|
119
|
-
let textAnchor = 'middle';
|
|
120
|
-
if (this.align === 'left') {
|
|
121
|
-
textX = theme.margins.left;
|
|
122
|
-
textAnchor = 'start';
|
|
123
|
-
}
|
|
124
|
-
else if (this.align === 'right') {
|
|
125
|
-
textX = width - theme.margins.right;
|
|
126
|
-
textAnchor = 'end';
|
|
127
|
-
}
|
|
128
|
-
titleGroup
|
|
129
|
-
.append('text')
|
|
130
|
-
.attr('x', textX)
|
|
131
|
-
.attr('y', this.marginTop + this.fontSize)
|
|
132
|
-
.attr('text-anchor', textAnchor)
|
|
133
|
-
.attr('font-size', `${this.fontSize}px`)
|
|
134
|
-
.attr('font-weight', this.fontWeight)
|
|
135
|
-
.attr('font-family', this.fontFamily || theme.fontFamily)
|
|
136
|
-
.text(this.text);
|
|
83
|
+
this.textComponent.render(svg, theme, width, x, y);
|
|
137
84
|
}
|
|
138
85
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -69,6 +69,9 @@ export type ChartTheme = {
|
|
|
69
69
|
itemSpacingX: number;
|
|
70
70
|
itemSpacingY: number;
|
|
71
71
|
};
|
|
72
|
+
text: {
|
|
73
|
+
variants: Record<string, TextVariantStyle>;
|
|
74
|
+
};
|
|
72
75
|
tooltip: {
|
|
73
76
|
background: string;
|
|
74
77
|
border: string;
|
|
@@ -333,13 +336,43 @@ export type LegendItem = {
|
|
|
333
336
|
color: string;
|
|
334
337
|
visible: boolean;
|
|
335
338
|
};
|
|
339
|
+
export type TextPosition = 'top' | 'bottom';
|
|
340
|
+
export type TextAlign = 'left' | 'center' | 'right';
|
|
341
|
+
export type TextVariantStyle = {
|
|
342
|
+
fontSize?: number;
|
|
343
|
+
fontWeight?: string;
|
|
344
|
+
fontFamily?: string;
|
|
345
|
+
color?: string;
|
|
346
|
+
lineHeight?: number;
|
|
347
|
+
marginTop?: number;
|
|
348
|
+
marginBottom?: number;
|
|
349
|
+
};
|
|
350
|
+
export type TextConfigBase = {
|
|
351
|
+
display?: boolean;
|
|
352
|
+
text: string;
|
|
353
|
+
position?: TextPosition;
|
|
354
|
+
variant?: string;
|
|
355
|
+
align?: TextAlign;
|
|
356
|
+
fontSize?: number;
|
|
357
|
+
fontWeight?: string;
|
|
358
|
+
fontFamily?: string;
|
|
359
|
+
color?: string;
|
|
360
|
+
lineHeight?: number;
|
|
361
|
+
marginTop?: number;
|
|
362
|
+
marginBottom?: number;
|
|
363
|
+
};
|
|
364
|
+
export type TextConfig = TextConfigBase & {
|
|
365
|
+
exportHooks?: ExportHooks<TextConfigBase>;
|
|
366
|
+
};
|
|
336
367
|
export type TitleConfigBase = {
|
|
337
368
|
display?: boolean;
|
|
338
369
|
text: string;
|
|
339
370
|
fontSize?: number;
|
|
340
371
|
fontWeight?: string;
|
|
341
372
|
fontFamily?: string;
|
|
342
|
-
align?:
|
|
373
|
+
align?: TextAlign;
|
|
374
|
+
color?: string;
|
|
375
|
+
lineHeight?: number;
|
|
343
376
|
marginTop?: number;
|
|
344
377
|
marginBottom?: number;
|
|
345
378
|
};
|
package/docs/chart-group.md
CHANGED
|
@@ -18,7 +18,7 @@ new ChartGroup(config: ChartGroupConfig)
|
|
|
18
18
|
| `syncY` | `boolean` | `false` | Sync the Y domain across visible vertical `XYChart` children |
|
|
19
19
|
| `height` | `number` | container height | Fixed total group height. Omit it to size from the container |
|
|
20
20
|
| `chartHeight` | `number` | `DEFAULT_CHART_HEIGHT` | Fallback child height when neither the child nor container provides one |
|
|
21
|
-
| `theme` | `DeepPartial<ChartTheme>` | - | Theme override for the shared group legend and
|
|
21
|
+
| `theme` | `DeepPartial<ChartTheme>` | - | Theme override for the shared group legend and text |
|
|
22
22
|
| `responsive` | `ChartGroupResponsiveConfig` | - | Declarative responsive overrides for group-level `cols` and `gap` |
|
|
23
23
|
|
|
24
24
|
`ChartGroup` manages child widths. If a child chart has an explicit `width`,
|
|
@@ -32,6 +32,7 @@ import { XYChart } from '@internetstiftelsen/charts/xy-chart';
|
|
|
32
32
|
import { Line } from '@internetstiftelsen/charts/line';
|
|
33
33
|
import { Bar } from '@internetstiftelsen/charts/bar';
|
|
34
34
|
import { Legend } from '@internetstiftelsen/charts/legend';
|
|
35
|
+
import { Text } from '@internetstiftelsen/charts/text';
|
|
35
36
|
import { Title } from '@internetstiftelsen/charts/title';
|
|
36
37
|
|
|
37
38
|
const lineChart = new XYChart({ data: lineData });
|
|
@@ -53,6 +54,13 @@ const group = new ChartGroup({
|
|
|
53
54
|
|
|
54
55
|
group
|
|
55
56
|
.addChild(new Title({ text: 'Revenue vs Expenses' }))
|
|
57
|
+
.addChild(
|
|
58
|
+
new Text({
|
|
59
|
+
text: 'Source: finance team',
|
|
60
|
+
position: 'bottom',
|
|
61
|
+
variant: 'caption',
|
|
62
|
+
}),
|
|
63
|
+
)
|
|
56
64
|
.addChart(barChart, { span: 1 })
|
|
57
65
|
.addChart(lineChart, { span: 2 })
|
|
58
66
|
.addChild(new Legend());
|
|
@@ -165,13 +173,21 @@ After a breakpoint's `maxWidth`, that breakpoint stops matching. Use the base
|
|
|
165
173
|
group config plus `minWidth` breakpoints for mobile-first layouts, or the base
|
|
166
174
|
group config plus `maxWidth` breakpoints for desktop-down layouts.
|
|
167
175
|
|
|
168
|
-
## Title
|
|
176
|
+
## Text and Title
|
|
169
177
|
|
|
170
|
-
`ChartGroup` accepts `Title` via `addChild()
|
|
171
|
-
chart layout
|
|
178
|
+
`ChartGroup` accepts `Text` and `Title` via `addChild()`. Top text renders above
|
|
179
|
+
the grouped chart layout. Bottom text renders below the grouped chart layout and
|
|
180
|
+
shared legend:
|
|
172
181
|
|
|
173
182
|
```typescript
|
|
174
183
|
group.addChild(new Title({ text: 'Revenue vs Expenses' }));
|
|
184
|
+
group.addChild(
|
|
185
|
+
new Text({
|
|
186
|
+
text: 'Source: finance team',
|
|
187
|
+
position: 'bottom',
|
|
188
|
+
variant: 'caption',
|
|
189
|
+
}),
|
|
190
|
+
);
|
|
175
191
|
```
|
|
176
192
|
|
|
177
193
|
## Legend
|
|
@@ -208,6 +224,9 @@ await group.export('pdf');
|
|
|
208
224
|
|
|
209
225
|
- Export width can be overridden with `options.width`
|
|
210
226
|
- Export height is layout-derived in v1 and cannot be overridden
|
|
211
|
-
- Group
|
|
227
|
+
- Group text is included in the combined export
|
|
228
|
+
- Group-level `Text` and `Title` export hooks run during visual export, so text
|
|
229
|
+
can be hidden live with `display: false` and included only in export by
|
|
230
|
+
returning `{ display: true }` from `beforeRender`
|
|
212
231
|
- Child chart legends are suppressed in the combined export
|
|
213
232
|
- Non-visual exports (`json`, `csv`, `xlsx`) are not supported in v1
|