@internetstiftelsen/charts 0.11.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 +23 -1
- package/dist/base-chart.d.ts +50 -3
- package/dist/base-chart.js +188 -40
- 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 +59 -3
- package/dist/title.d.ts +6 -12
- package/dist/title.js +29 -82
- package/dist/tooltip.d.ts +4 -2
- package/dist/tooltip.js +101 -77
- package/dist/types.d.ts +34 -1
- package/dist/xy-chart.js +1 -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 +25 -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/tooltip.js
CHANGED
|
@@ -6,9 +6,15 @@ const TOOLTIP_VIEWPORT_PADDING_PX = 10;
|
|
|
6
6
|
const TOOLTIP_CONNECTOR_INSET_PX = 14;
|
|
7
7
|
const TOOLTIP_CONNECTOR_PADDING_PX = 4;
|
|
8
8
|
const TOOLTIP_CONNECTOR_ELBOW_RATIO = 0.45;
|
|
9
|
+
const TOOLTIP_BORDER_WIDTH_PX = 1;
|
|
9
10
|
const TOOLTIP_BOX_ARROW_LENGTH_PX = 10;
|
|
10
11
|
const TOOLTIP_BOX_ARROW_HALF_HEIGHT_PX = 6;
|
|
11
12
|
const TOOLTIP_CONNECTOR_ALIGNMENT_TOLERANCE_PX = 1;
|
|
13
|
+
const TOOLTIP_CONNECTOR_Z_INDEX = 3;
|
|
14
|
+
const TOOLTIP_ARROW_BORDER_Z_INDEX = 4;
|
|
15
|
+
const TOOLTIP_ARROW_FILL_Z_INDEX = 5;
|
|
16
|
+
const TOOLTIP_BODY_Z_INDEX = 6;
|
|
17
|
+
const TOOLTIP_TOTAL_BORDER_WIDTH_PX = TOOLTIP_BORDER_WIDTH_PX * 2;
|
|
12
18
|
const SPLIT_TOOLTIP_GAP_PX = 8;
|
|
13
19
|
const DEFAULT_TOOLTIP_TRANSITION = {
|
|
14
20
|
show: false,
|
|
@@ -706,7 +712,7 @@ export class Tooltip {
|
|
|
706
712
|
tooltip
|
|
707
713
|
.style('position', 'absolute')
|
|
708
714
|
.style('background-color', theme.tooltip.background)
|
|
709
|
-
.style('border',
|
|
715
|
+
.style('border', `${TOOLTIP_BORDER_WIDTH_PX}px solid ${theme.tooltip.border}`)
|
|
710
716
|
.style('border-radius', '4px')
|
|
711
717
|
.style('padding', '8px')
|
|
712
718
|
.style('box-shadow', '0 2px 4px rgba(0,0,0,0.1)')
|
|
@@ -762,6 +768,7 @@ export class Tooltip {
|
|
|
762
768
|
return;
|
|
763
769
|
}
|
|
764
770
|
this.appendTooltipConnector(tooltip, connectorLayout);
|
|
771
|
+
this.appendTooltipArrow(tooltip, connectorLayout);
|
|
765
772
|
this.showTooltipAt(tooltip, left, top);
|
|
766
773
|
}
|
|
767
774
|
renderTooltipWithoutConnector(tooltip, left, top) {
|
|
@@ -806,10 +813,9 @@ export class Tooltip {
|
|
|
806
813
|
if (body.empty()) {
|
|
807
814
|
return;
|
|
808
815
|
}
|
|
809
|
-
body.style('position', 'relative').style('z-index',
|
|
816
|
+
body.style('position', 'relative').style('z-index', String(TOOLTIP_BODY_Z_INDEX));
|
|
810
817
|
}
|
|
811
818
|
appendTooltipConnector(tooltip, connectorLayout) {
|
|
812
|
-
const tooltipBackground = this.tooltipTheme?.background ?? '#ffffff';
|
|
813
819
|
const tooltipBorder = this.tooltipTheme?.border ?? '#dddddd';
|
|
814
820
|
const connector = tooltip
|
|
815
821
|
.append('svg')
|
|
@@ -824,7 +830,7 @@ export class Tooltip {
|
|
|
824
830
|
.style('top', `${connectorLayout.top}px`)
|
|
825
831
|
.style('pointer-events', 'none')
|
|
826
832
|
.style('overflow', 'visible')
|
|
827
|
-
.style('z-index',
|
|
833
|
+
.style('z-index', String(TOOLTIP_CONNECTOR_Z_INDEX));
|
|
828
834
|
connector
|
|
829
835
|
.append('path')
|
|
830
836
|
.attr('data-chart-tooltip-connector-path', 'true')
|
|
@@ -834,29 +840,76 @@ export class Tooltip {
|
|
|
834
840
|
.attr('stroke-width', 1.25)
|
|
835
841
|
.attr('stroke-linecap', 'round')
|
|
836
842
|
.attr('stroke-linejoin', 'round');
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
.
|
|
848
|
-
.attr('
|
|
849
|
-
.attr('
|
|
850
|
-
.attr('
|
|
851
|
-
|
|
852
|
-
.
|
|
853
|
-
.
|
|
854
|
-
.
|
|
855
|
-
.
|
|
856
|
-
.
|
|
857
|
-
.
|
|
858
|
-
|
|
859
|
-
|
|
843
|
+
}
|
|
844
|
+
appendTooltipArrow(tooltip, connectorLayout) {
|
|
845
|
+
const tooltipBackground = this.tooltipTheme?.background ?? '#ffffff';
|
|
846
|
+
const tooltipBorder = this.tooltipTheme?.border ?? '#dddddd';
|
|
847
|
+
this.appendTooltipArrowTriangle(tooltip, connectorLayout, 'data-chart-tooltip-arrow', tooltipBorder, TOOLTIP_BOX_ARROW_LENGTH_PX, TOOLTIP_BOX_ARROW_HALF_HEIGHT_PX, TOOLTIP_ARROW_BORDER_Z_INDEX);
|
|
848
|
+
this.appendTooltipArrowTriangle(tooltip, connectorLayout, 'data-chart-tooltip-arrow-fill', tooltipBackground, TOOLTIP_BOX_ARROW_LENGTH_PX - 1, TOOLTIP_BOX_ARROW_HALF_HEIGHT_PX - 1, TOOLTIP_ARROW_FILL_Z_INDEX);
|
|
849
|
+
}
|
|
850
|
+
appendTooltipArrowTriangle(tooltip, connectorLayout, dataAttribute, color, length, halfHeight, zIndex) {
|
|
851
|
+
const position = this.resolveTooltipArrowPosition(connectorLayout.arrowEdge, connectorLayout.arrowX, connectorLayout.arrowY, length, halfHeight);
|
|
852
|
+
const arrow = tooltip
|
|
853
|
+
.append('div')
|
|
854
|
+
.attr(dataAttribute, 'true')
|
|
855
|
+
.attr('data-chart-tooltip-arrow-edge', connectorLayout.arrowEdge)
|
|
856
|
+
.attr('aria-hidden', 'true')
|
|
857
|
+
.style('position', 'absolute')
|
|
858
|
+
.style('left', `${position.left}px`)
|
|
859
|
+
.style('top', `${position.top}px`)
|
|
860
|
+
.style('width', '0')
|
|
861
|
+
.style('height', '0')
|
|
862
|
+
.style('pointer-events', 'none')
|
|
863
|
+
.style('z-index', String(zIndex));
|
|
864
|
+
if (connectorLayout.arrowEdge === 'left') {
|
|
865
|
+
arrow
|
|
866
|
+
.style('border-top', `${halfHeight}px solid transparent`)
|
|
867
|
+
.style('border-bottom', `${halfHeight}px solid transparent`)
|
|
868
|
+
.style('border-right', `${length}px solid ${color}`);
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
if (connectorLayout.arrowEdge === 'right') {
|
|
872
|
+
arrow
|
|
873
|
+
.style('border-top', `${halfHeight}px solid transparent`)
|
|
874
|
+
.style('border-bottom', `${halfHeight}px solid transparent`)
|
|
875
|
+
.style('border-left', `${length}px solid ${color}`);
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
if (connectorLayout.arrowEdge === 'top') {
|
|
879
|
+
arrow
|
|
880
|
+
.style('border-left', `${halfHeight}px solid transparent`)
|
|
881
|
+
.style('border-right', `${halfHeight}px solid transparent`)
|
|
882
|
+
.style('border-bottom', `${length}px solid ${color}`);
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
arrow
|
|
886
|
+
.style('border-left', `${halfHeight}px solid transparent`)
|
|
887
|
+
.style('border-right', `${halfHeight}px solid transparent`)
|
|
888
|
+
.style('border-top', `${length}px solid ${color}`);
|
|
889
|
+
}
|
|
890
|
+
resolveTooltipArrowPosition(arrowEdge, boxX, boxY, length, halfHeight) {
|
|
891
|
+
switch (arrowEdge) {
|
|
892
|
+
case 'left':
|
|
893
|
+
return {
|
|
894
|
+
left: boxX - length,
|
|
895
|
+
top: boxY - halfHeight,
|
|
896
|
+
};
|
|
897
|
+
case 'right':
|
|
898
|
+
return {
|
|
899
|
+
left: boxX,
|
|
900
|
+
top: boxY - halfHeight,
|
|
901
|
+
};
|
|
902
|
+
case 'top':
|
|
903
|
+
return {
|
|
904
|
+
left: boxX - halfHeight,
|
|
905
|
+
top: boxY - length,
|
|
906
|
+
};
|
|
907
|
+
case 'bottom':
|
|
908
|
+
return {
|
|
909
|
+
left: boxX - halfHeight,
|
|
910
|
+
top: boxY,
|
|
911
|
+
};
|
|
912
|
+
}
|
|
860
913
|
}
|
|
861
914
|
resolveBarTooltipAnchor(svgNode, dataKey, index) {
|
|
862
915
|
const barNode = svgNode.querySelector(`.bar-${sanitizeForCSS(dataKey)}[data-index="${index}"]`);
|
|
@@ -1050,25 +1103,21 @@ export class Tooltip {
|
|
|
1050
1103
|
return null;
|
|
1051
1104
|
}
|
|
1052
1105
|
const boxArrowPosition = this.resolveTooltipBoxArrowPosition(arrowEdge, tooltipLeft, tooltipTop, tooltipWidth, tooltipHeight, targetX, targetY);
|
|
1053
|
-
const
|
|
1054
|
-
const minX = Math.min(
|
|
1055
|
-
const maxX = Math.max(
|
|
1056
|
-
const minY = Math.min(
|
|
1057
|
-
const maxY = Math.max(
|
|
1106
|
+
const arrowTip = this.resolveTooltipArrowTip(arrowEdge, boxArrowPosition.x, boxArrowPosition.y, TOOLTIP_BOX_ARROW_LENGTH_PX);
|
|
1107
|
+
const minX = Math.min(arrowTip.x, localTargetX) - TOOLTIP_CONNECTOR_PADDING_PX;
|
|
1108
|
+
const maxX = Math.max(arrowTip.x, localTargetX) + TOOLTIP_CONNECTOR_PADDING_PX;
|
|
1109
|
+
const minY = Math.min(arrowTip.y, localTargetY) - TOOLTIP_CONNECTOR_PADDING_PX;
|
|
1110
|
+
const maxY = Math.max(arrowTip.y, localTargetY) + TOOLTIP_CONNECTOR_PADDING_PX;
|
|
1058
1111
|
const width = Math.max(1, maxX - minX);
|
|
1059
1112
|
const height = Math.max(1, maxY - minY);
|
|
1060
1113
|
const boxX = boxArrowPosition.x - minX;
|
|
1061
1114
|
const boxY = boxArrowPosition.y - minY;
|
|
1062
|
-
const startX =
|
|
1063
|
-
const startY =
|
|
1064
|
-
const arrowBaseStartX = arrow.baseStartX - minX;
|
|
1065
|
-
const arrowBaseStartY = arrow.baseStartY - minY;
|
|
1066
|
-
const arrowBaseEndX = arrow.baseEndX - minX;
|
|
1067
|
-
const arrowBaseEndY = arrow.baseEndY - minY;
|
|
1115
|
+
const startX = arrowTip.x - minX;
|
|
1116
|
+
const startY = arrowTip.y - minY;
|
|
1068
1117
|
const endX = localTargetX - minX;
|
|
1069
1118
|
const endY = localTargetY - minY;
|
|
1070
1119
|
const connectorPath = this.resolveTooltipConnectorPath(arrowEdge, startX, startY, endX, endY);
|
|
1071
|
-
if (!this.hasFiniteNumbers(width, height, boxX, boxY, startX, startY,
|
|
1120
|
+
if (!this.hasFiniteNumbers(width, height, boxX, boxY, startX, startY, endX, endY)) {
|
|
1072
1121
|
return null;
|
|
1073
1122
|
}
|
|
1074
1123
|
return {
|
|
@@ -1078,12 +1127,15 @@ export class Tooltip {
|
|
|
1078
1127
|
width,
|
|
1079
1128
|
height,
|
|
1080
1129
|
path: connectorPath,
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
arrowBorderPath: `M ${arrowBaseStartX},${arrowBaseStartY} L ${startX},${startY} L ${arrowBaseEndX},${arrowBaseEndY}`,
|
|
1130
|
+
arrowX: boxArrowPosition.x,
|
|
1131
|
+
arrowY: boxArrowPosition.y,
|
|
1084
1132
|
};
|
|
1085
1133
|
}
|
|
1086
1134
|
resolveTooltipBoxArrowPosition(arrowEdge, tooltipLeft, tooltipTop, tooltipWidth, tooltipHeight, targetX, targetY) {
|
|
1135
|
+
// Arrow offsets are relative to the padding box, while measured
|
|
1136
|
+
// tooltip dimensions include both borders.
|
|
1137
|
+
const rightInnerBorderX = tooltipWidth - TOOLTIP_TOTAL_BORDER_WIDTH_PX;
|
|
1138
|
+
const bottomInnerBorderY = tooltipHeight - TOOLTIP_TOTAL_BORDER_WIDTH_PX;
|
|
1087
1139
|
switch (arrowEdge) {
|
|
1088
1140
|
case 'left':
|
|
1089
1141
|
return {
|
|
@@ -1092,7 +1144,7 @@ export class Tooltip {
|
|
|
1092
1144
|
};
|
|
1093
1145
|
case 'right':
|
|
1094
1146
|
return {
|
|
1095
|
-
x:
|
|
1147
|
+
x: rightInnerBorderX,
|
|
1096
1148
|
y: this.getTooltipConnectorOffset(tooltipTop, tooltipHeight, targetY),
|
|
1097
1149
|
};
|
|
1098
1150
|
case 'top':
|
|
@@ -1103,22 +1155,10 @@ export class Tooltip {
|
|
|
1103
1155
|
case 'bottom':
|
|
1104
1156
|
return {
|
|
1105
1157
|
x: this.getTooltipConnectorOffset(tooltipLeft, tooltipWidth, targetX),
|
|
1106
|
-
y:
|
|
1158
|
+
y: bottomInnerBorderY,
|
|
1107
1159
|
};
|
|
1108
1160
|
}
|
|
1109
1161
|
}
|
|
1110
|
-
resolveTooltipArrowBaseMaskPath(arrowEdge, boxX, boxY, startX, startY, endX, endY) {
|
|
1111
|
-
if (arrowEdge === 'top') {
|
|
1112
|
-
return `M ${startX - 1},${boxY + 1} L ${endX + 1},${boxY + 1}`;
|
|
1113
|
-
}
|
|
1114
|
-
if (arrowEdge === 'bottom') {
|
|
1115
|
-
return `M ${startX - 1},${boxY - 1} L ${endX + 1},${boxY - 1}`;
|
|
1116
|
-
}
|
|
1117
|
-
if (arrowEdge === 'left') {
|
|
1118
|
-
return `M ${boxX + 1},${startY - 1} L ${boxX + 1},${endY + 1}`;
|
|
1119
|
-
}
|
|
1120
|
-
return `M ${boxX - 1},${startY - 1} L ${boxX - 1},${endY + 1}`;
|
|
1121
|
-
}
|
|
1122
1162
|
resolveTooltipConnectorPath(arrowEdge, startX, startY, endX, endY) {
|
|
1123
1163
|
if (arrowEdge === 'left' || arrowEdge === 'right') {
|
|
1124
1164
|
if (Math.abs(endY - startY) <=
|
|
@@ -1134,32 +1174,16 @@ export class Tooltip {
|
|
|
1134
1174
|
const elbowY = startY + (endY - startY) * TOOLTIP_CONNECTOR_ELBOW_RATIO;
|
|
1135
1175
|
return `M ${startX},${startY} L ${startX},${elbowY} L ${endX},${endY}`;
|
|
1136
1176
|
}
|
|
1137
|
-
|
|
1177
|
+
resolveTooltipArrowTip(arrowEdge, boxX, boxY, length) {
|
|
1138
1178
|
if (arrowEdge === 'left' || arrowEdge === 'right') {
|
|
1139
|
-
const baseX = boxX;
|
|
1140
|
-
const tipX = arrowEdge === 'left'
|
|
1141
|
-
? baseX - TOOLTIP_BOX_ARROW_LENGTH_PX
|
|
1142
|
-
: baseX + TOOLTIP_BOX_ARROW_LENGTH_PX;
|
|
1143
1179
|
return {
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
baseStartX: baseX,
|
|
1147
|
-
baseStartY: boxY - TOOLTIP_BOX_ARROW_HALF_HEIGHT_PX,
|
|
1148
|
-
baseEndX: baseX,
|
|
1149
|
-
baseEndY: boxY + TOOLTIP_BOX_ARROW_HALF_HEIGHT_PX,
|
|
1180
|
+
x: arrowEdge === 'left' ? boxX - length : boxX + length,
|
|
1181
|
+
y: boxY,
|
|
1150
1182
|
};
|
|
1151
1183
|
}
|
|
1152
|
-
const baseY = boxY;
|
|
1153
|
-
const tipY = arrowEdge === 'top'
|
|
1154
|
-
? baseY - TOOLTIP_BOX_ARROW_LENGTH_PX
|
|
1155
|
-
: baseY + TOOLTIP_BOX_ARROW_LENGTH_PX;
|
|
1156
1184
|
return {
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
baseStartX: boxX - TOOLTIP_BOX_ARROW_HALF_HEIGHT_PX,
|
|
1160
|
-
baseStartY: baseY,
|
|
1161
|
-
baseEndX: boxX + TOOLTIP_BOX_ARROW_HALF_HEIGHT_PX,
|
|
1162
|
-
baseEndY: baseY,
|
|
1185
|
+
x: boxX,
|
|
1186
|
+
y: arrowEdge === 'top' ? boxY - length : boxY + length,
|
|
1163
1187
|
};
|
|
1164
1188
|
}
|
|
1165
1189
|
hasFiniteNumbers(...values) {
|
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/dist/xy-chart.js
CHANGED
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
|
package/docs/components.md
CHANGED
|
@@ -224,34 +224,116 @@ charts from one shared legend.
|
|
|
224
224
|
|
|
225
225
|
---
|
|
226
226
|
|
|
227
|
-
##
|
|
227
|
+
## Text
|
|
228
228
|
|
|
229
|
-
Renders
|
|
229
|
+
Renders layout-aware text above or below the chart. Use it for titles,
|
|
230
|
+
subtitles, captions, source notes, and other short chart copy.
|
|
230
231
|
|
|
231
232
|
```typescript
|
|
232
|
-
new
|
|
233
|
-
display?: boolean,
|
|
234
|
-
text: string,
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
align?: 'left' | 'center' | 'right',
|
|
238
|
-
|
|
239
|
-
|
|
233
|
+
new Text({
|
|
234
|
+
display?: boolean, // Render text and reserve layout space (default: true)
|
|
235
|
+
text: string, // Text content (required)
|
|
236
|
+
position?: 'top' | 'bottom', // Layout position (default: 'top')
|
|
237
|
+
variant?: string, // Theme variant (default: 'caption')
|
|
238
|
+
align?: 'left' | 'center' | 'right', // Alignment (default: 'center')
|
|
239
|
+
fontSize?: number, // Override variant font size
|
|
240
|
+
fontWeight?: string, // Override variant font weight
|
|
241
|
+
fontFamily?: string, // Override variant font family
|
|
242
|
+
color?: string, // Override variant text color
|
|
243
|
+
lineHeight?: number, // Override variant line height
|
|
244
|
+
marginTop?: number, // Override variant top spacing
|
|
245
|
+
marginBottom?: number, // Override variant bottom spacing
|
|
240
246
|
})
|
|
241
247
|
```
|
|
242
248
|
|
|
243
249
|
### Example
|
|
244
250
|
|
|
245
251
|
```javascript
|
|
252
|
+
import { Text } from '@internetstiftelsen/charts/text';
|
|
253
|
+
|
|
254
|
+
chart
|
|
255
|
+
.addChild(
|
|
256
|
+
new Text({
|
|
257
|
+
text: 'Monthly Revenue',
|
|
258
|
+
variant: 'title',
|
|
259
|
+
align: 'left',
|
|
260
|
+
}),
|
|
261
|
+
)
|
|
262
|
+
.addChild(
|
|
263
|
+
new Text({
|
|
264
|
+
text: 'Revenue by month, SEK',
|
|
265
|
+
variant: 'subtitle',
|
|
266
|
+
align: 'left',
|
|
267
|
+
}),
|
|
268
|
+
)
|
|
269
|
+
.addChild(
|
|
270
|
+
new Text({
|
|
271
|
+
text: 'Source: Internal reporting',
|
|
272
|
+
position: 'bottom',
|
|
273
|
+
variant: 'caption',
|
|
274
|
+
align: 'left',
|
|
275
|
+
}),
|
|
276
|
+
);
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Text variants come from `theme.text.variants`. Built-in variants are `title`,
|
|
280
|
+
`subtitle`, and `caption`. Component config overrides the selected variant.
|
|
281
|
+
|
|
282
|
+
```javascript
|
|
283
|
+
const chart = new XYChart({
|
|
284
|
+
data,
|
|
285
|
+
theme: {
|
|
286
|
+
text: {
|
|
287
|
+
variants: {
|
|
288
|
+
source: {
|
|
289
|
+
fontSize: 10,
|
|
290
|
+
color: '#64748b',
|
|
291
|
+
marginTop: 6,
|
|
292
|
+
marginBottom: 0,
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
|
|
246
299
|
chart.addChild(
|
|
247
|
-
new
|
|
248
|
-
text: '
|
|
249
|
-
|
|
250
|
-
|
|
300
|
+
new Text({
|
|
301
|
+
text: 'Source: Internetstiftelsen',
|
|
302
|
+
position: 'bottom',
|
|
303
|
+
variant: 'source',
|
|
251
304
|
}),
|
|
252
305
|
);
|
|
253
306
|
```
|
|
254
307
|
|
|
308
|
+
## Title
|
|
309
|
+
|
|
310
|
+
`Title` remains available as a compatibility shortcut for top title text:
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
new Title({
|
|
314
|
+
display?: boolean,
|
|
315
|
+
text: string,
|
|
316
|
+
fontSize?: number,
|
|
317
|
+
fontWeight?: string,
|
|
318
|
+
fontFamily?: string,
|
|
319
|
+
align?: 'left' | 'center' | 'right',
|
|
320
|
+
color?: string,
|
|
321
|
+
lineHeight?: number,
|
|
322
|
+
marginTop?: number,
|
|
323
|
+
marginBottom?: number,
|
|
324
|
+
})
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
It maps to the same rendering and layout behavior as:
|
|
328
|
+
|
|
329
|
+
```javascript
|
|
330
|
+
new Text({
|
|
331
|
+
text: 'Monthly Revenue',
|
|
332
|
+
position: 'top',
|
|
333
|
+
variant: 'title',
|
|
334
|
+
});
|
|
335
|
+
```
|
|
336
|
+
|
|
255
337
|
---
|
|
256
338
|
|
|
257
339
|
## Export Hooks
|
|
@@ -346,12 +428,14 @@ Components are rendered in the order they're added. A typical order:
|
|
|
346
428
|
|
|
347
429
|
```javascript
|
|
348
430
|
chart
|
|
349
|
-
.addChild(new
|
|
431
|
+
.addChild(new Text({ text: 'Chart Title', variant: 'title' }))
|
|
432
|
+
.addChild(new Text({ text: 'Subtitle', variant: 'subtitle' }))
|
|
350
433
|
.addChild(new Grid({ value: true }))
|
|
351
434
|
.addChild(new XAxis({ dataKey: 'date' }))
|
|
352
435
|
.addChild(new YAxis())
|
|
353
436
|
.addChild(new Tooltip())
|
|
354
437
|
.addChild(new Legend({ position: 'bottom' }))
|
|
438
|
+
.addChild(new Text({ text: 'Source note', position: 'bottom' }))
|
|
355
439
|
.addChild(new Line({ dataKey: 'value1' }))
|
|
356
440
|
.addChild(new Line({ dataKey: 'value2' }));
|
|
357
441
|
```
|
package/docs/donut-chart.md
CHANGED
|
@@ -176,7 +176,8 @@ new DonutCenterContent({
|
|
|
176
176
|
DonutChart supports the following components via `addChild()`:
|
|
177
177
|
|
|
178
178
|
- `DonutCenterContent` - Center text content
|
|
179
|
-
- `
|
|
179
|
+
- `Text` - Chart text, captions, and source notes
|
|
180
|
+
- `Title` - Shortcut for top title text
|
|
180
181
|
- `Legend` - Interactive legend (click to toggle segments)
|
|
181
182
|
- `Tooltip` - Hover tooltips with segment info
|
|
182
183
|
|
package/docs/gauge-chart.md
CHANGED
|
@@ -170,6 +170,7 @@ const chart = new GaugeChart({
|
|
|
170
170
|
|
|
171
171
|
GaugeChart supports the following components via `addChild()`:
|
|
172
172
|
|
|
173
|
-
- `
|
|
173
|
+
- `Text` - Chart text, captions, and source notes
|
|
174
|
+
- `Title` - Shortcut for top title text
|
|
174
175
|
- `Tooltip` - Hover tooltip (value, target)
|
|
175
176
|
- `Legend` - Segment legend with visibility toggles
|
package/docs/getting-started.md
CHANGED
|
@@ -57,6 +57,30 @@ chart.update(newData);
|
|
|
57
57
|
chart.destroy();
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
## Lifecycle Events
|
|
61
|
+
|
|
62
|
+
Charts support lifecycle listeners with `on()` and `off()`.
|
|
63
|
+
|
|
64
|
+
```javascript
|
|
65
|
+
const handleReady = (event) => {
|
|
66
|
+
console.log(event.reason);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
chart.on('ready', handleReady);
|
|
70
|
+
chart.off('ready', handleReady);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
All event payloads include `type` and `chart`.
|
|
74
|
+
|
|
75
|
+
| Name | Description | Params |
|
|
76
|
+
| --------------- | ------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
|
77
|
+
| `render:start` | A render pass is starting, before the SVG is recreated. | `reason` |
|
|
78
|
+
| `render` | Synchronous rendering has completed. | `reason` |
|
|
79
|
+
| `ready` | The current render has fully settled, matching `chart.whenReady()`. | `reason` |
|
|
80
|
+
| `data` | `chart.update(data)` has accepted new data, before the update render. | `data`, `previousData` |
|
|
81
|
+
| `legend:change` | Legend visibility changed. | - |
|
|
82
|
+
| `destroy` | The chart is being destroyed, before DOM and internal state are cleaned up. | - |
|
|
83
|
+
|
|
60
84
|
## Sizing
|
|
61
85
|
|
|
62
86
|
By default, charts size themselves from the render container.
|
|
@@ -342,5 +366,5 @@ directly when laying out and rendering the cloud.
|
|
|
342
366
|
- [DonutChart API](./donut-chart.md) - Donut/pie charts
|
|
343
367
|
- [PieChart API](./pie-chart.md) - Pie charts
|
|
344
368
|
- [GaugeChart API](./gauge-chart.md) - Gauge charts
|
|
345
|
-
- [Components](./components.md) - Axes, Grid, Tooltip, Legend, Title
|
|
369
|
+
- [Components](./components.md) - Axes, Grid, Tooltip, Legend, Text, Title
|
|
346
370
|
- [Theming](./theming.md) - Customize colors and styles
|
package/docs/pie-chart.md
CHANGED
|
@@ -108,7 +108,8 @@ fit inside its slice is hidden. In `auto` mode, labels that are too tight (based
|
|
|
108
108
|
|
|
109
109
|
PieChart supports the following components via `addChild()`:
|
|
110
110
|
|
|
111
|
-
- `
|
|
111
|
+
- `Text` - Chart text, captions, and source notes
|
|
112
|
+
- `Title` - Shortcut for top title text
|
|
112
113
|
- `Legend` - Interactive legend (click to toggle slices)
|
|
113
114
|
- `Tooltip` - Hover/focus tooltips with slice info
|
|
114
115
|
|
package/docs/theming.md
CHANGED
|
@@ -111,6 +111,41 @@ const data = [
|
|
|
111
111
|
|
|
112
112
|
---
|
|
113
113
|
|
|
114
|
+
## Text Variants
|
|
115
|
+
|
|
116
|
+
`Text` uses `theme.text.variants` for reusable typography and spacing. Built-in
|
|
117
|
+
variants are `title`, `subtitle`, and `caption`; add custom keys when a chart
|
|
118
|
+
needs another text style.
|
|
119
|
+
|
|
120
|
+
```javascript
|
|
121
|
+
theme: {
|
|
122
|
+
text: {
|
|
123
|
+
variants: {
|
|
124
|
+
title: {
|
|
125
|
+
fontSize: 18,
|
|
126
|
+
fontWeight: 'bold',
|
|
127
|
+
color: '#1f2a36',
|
|
128
|
+
lineHeight: 1.2,
|
|
129
|
+
marginTop: 10,
|
|
130
|
+
marginBottom: 15,
|
|
131
|
+
},
|
|
132
|
+
source: {
|
|
133
|
+
fontSize: 10,
|
|
134
|
+
color: '#64748b',
|
|
135
|
+
lineHeight: 1.3,
|
|
136
|
+
marginTop: 6,
|
|
137
|
+
marginBottom: 0,
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Component config overrides the chosen variant, so a one-off chart can adjust
|
|
145
|
+
`fontSize`, `color`, or spacing without changing the theme.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
114
149
|
## Donut Theme Defaults
|
|
115
150
|
|
|
116
151
|
DonutChart has additional theme defaults:
|