@react-magma/charts 13.1.1-next.0 → 13.2.0-next.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/dist/charts.js +224 -115
- package/dist/charts.js.map +1 -1
- package/dist/charts.modern.module.js +224 -115
- package/dist/charts.modern.module.js.map +1 -1
- package/dist/charts.umd.js +574 -293
- package/dist/charts.umd.js.map +1 -1
- package/dist/components/CarbonChart/CarbonChart.d.ts +5 -0
- package/dist/components/CarbonChart/CarbonChartBar.stories.d.ts +27 -9
- package/package.json +3 -3
- package/src/components/CarbonChart/CarbonChart.test.js +185 -0
- package/src/components/CarbonChart/CarbonChart.tsx +262 -120
- package/src/components/CarbonChart/CarbonChartArea.stories.tsx +1 -1
- package/src/components/CarbonChart/CarbonChartAreaStacked.stories.tsx +1 -1
- package/src/components/CarbonChart/CarbonChartBar.stories.tsx +9 -2
- package/src/components/CarbonChart/CarbonChartBarFloating.stories.tsx +1 -1
- package/src/components/CarbonChart/CarbonChartBarGrouped.stories.tsx +1 -1
- package/src/components/CarbonChart/CarbonChartBarStacked.stories.tsx +1 -1
- package/src/components/CarbonChart/CarbonChartBoxplot.stories.tsx +1 -1
- package/src/components/CarbonChart/CarbonChartBubble.stories.tsx +1 -1
- package/src/components/CarbonChart/CarbonChartBullet.stories.tsx +1 -1
- package/src/components/CarbonChart/CarbonChartCombo.stories.tsx +1 -1
- package/src/components/CarbonChart/CarbonChartDonut.stories.tsx +3 -1
- package/src/components/CarbonChart/CarbonChartGauge.stories.tsx +1 -1
- package/src/components/CarbonChart/CarbonChartHistogram.stories.tsx +1 -1
- package/src/components/CarbonChart/CarbonChartLine.stories.tsx +1 -1
- package/src/components/CarbonChart/CarbonChartLollipop.stories.tsx +1 -1
- package/src/components/CarbonChart/CarbonChartMeter.stories.tsx +1 -1
- package/src/components/CarbonChart/CarbonChartPie.stories.tsx +1 -1
- package/src/components/CarbonChart/CarbonChartRadar.stories.tsx +1 -1
- package/src/components/CarbonChart/CarbonChartScatter.stories.tsx +1 -1
- package/src/components/CarbonChart/CarbonChartSparkline.stories.tsx +1 -1
- package/src/components/CarbonChart/CarbonChartStep.stories.tsx +1 -1
- package/src/components/ChartTable/ChartMoreOptionsButton.tsx +1 -0
- package/dist/components/ChartTable/ChartTable.test.d.ts +0 -1
- /package/src/components/ChartTable/{ChartTable.test.tsx → ChartTable.test.js} +0 -0
|
@@ -103,6 +103,11 @@ export interface ChartToolbarConfig {
|
|
|
103
103
|
* @default 2
|
|
104
104
|
*/
|
|
105
105
|
tableHeaderLevel?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
106
|
+
/**
|
|
107
|
+
* Heading level for the chart title.
|
|
108
|
+
* @default 2
|
|
109
|
+
*/
|
|
110
|
+
titleLevel?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
106
111
|
}
|
|
107
112
|
|
|
108
113
|
export interface CarbonChartProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
@@ -490,6 +495,9 @@ const CarbonChartWrapper = styled.div<{
|
|
|
490
495
|
: props.theme.colors.focus} !important;
|
|
491
496
|
outline-offset: 0;
|
|
492
497
|
}
|
|
498
|
+
circle.dot:focus {
|
|
499
|
+
outline: none !important;
|
|
500
|
+
}
|
|
493
501
|
.cds--overflow-menu-options__btn:focus,
|
|
494
502
|
.cds--overflow-menu:focus,
|
|
495
503
|
.cds--overflow-menu__trigger:focus,
|
|
@@ -594,6 +602,17 @@ const CarbonChartWrapper = styled.div<{
|
|
|
594
602
|
overflow: visible;
|
|
595
603
|
}
|
|
596
604
|
|
|
605
|
+
circle.dot:focus {
|
|
606
|
+
outline: none;
|
|
607
|
+
stroke: ${props =>
|
|
608
|
+
props.isInverse
|
|
609
|
+
? props.theme.colors.focusInverse
|
|
610
|
+
: props.theme.colors.focus} !important;
|
|
611
|
+
stroke-width: 6px !important;
|
|
612
|
+
stroke-opacity: 1 !important;
|
|
613
|
+
paint-order: stroke fill;
|
|
614
|
+
}
|
|
615
|
+
|
|
597
616
|
.cds--cc--chart-wrapper text {
|
|
598
617
|
font-size: ${props => props.theme.typeScale.size02.fontSize};
|
|
599
618
|
}
|
|
@@ -637,6 +656,11 @@ interface ColorsObject {
|
|
|
637
656
|
[key: string]: string;
|
|
638
657
|
}
|
|
639
658
|
|
|
659
|
+
interface ExtendedChartOptions extends ChartOptions {
|
|
660
|
+
title?: string;
|
|
661
|
+
colors?: string[];
|
|
662
|
+
}
|
|
663
|
+
|
|
640
664
|
const ToolbarWrapper = styled.div<{
|
|
641
665
|
isFullscreen?: boolean;
|
|
642
666
|
isInverse?: boolean;
|
|
@@ -838,86 +862,124 @@ function downloadImage(
|
|
|
838
862
|
|
|
839
863
|
const svgRect = svg.getBoundingClientRect();
|
|
840
864
|
const legendItems = readLegendItems(wrapper);
|
|
841
|
-
const scale = 2;
|
|
842
865
|
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
let
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
866
|
+
const doWork = () => {
|
|
867
|
+
const scale = 2;
|
|
868
|
+
|
|
869
|
+
// Measure legend height
|
|
870
|
+
const tempCanvas = document.createElement('canvas');
|
|
871
|
+
const tempCtx = tempCanvas.getContext('2d');
|
|
872
|
+
const fontSize = 13 * scale;
|
|
873
|
+
const swatchSize = 12 * scale;
|
|
874
|
+
const gap = 8 * scale;
|
|
875
|
+
const itemGap = 16 * scale;
|
|
876
|
+
const paddingX = 16 * scale;
|
|
877
|
+
const canvasWidth = svgRect.width * scale;
|
|
878
|
+
|
|
879
|
+
let legendHeight = 0;
|
|
880
|
+
if (legendItems.length > 0 && tempCtx) {
|
|
881
|
+
tempCtx.font = `${fontSize}px sans-serif`;
|
|
882
|
+
let x = paddingX;
|
|
883
|
+
let rows = 1;
|
|
884
|
+
for (const item of legendItems) {
|
|
885
|
+
const textWidth = tempCtx.measureText(item.label).width;
|
|
886
|
+
const itemWidth = swatchSize + gap + textWidth + itemGap;
|
|
887
|
+
if (x + itemWidth > canvasWidth - paddingX && x > paddingX) {
|
|
888
|
+
x = paddingX;
|
|
889
|
+
rows++;
|
|
890
|
+
}
|
|
891
|
+
x += itemWidth;
|
|
864
892
|
}
|
|
865
|
-
|
|
893
|
+
legendHeight = rows * (fontSize + gap) + gap * 2;
|
|
866
894
|
}
|
|
867
|
-
legendHeight = rows * (fontSize + gap) + gap * 2;
|
|
868
|
-
}
|
|
869
895
|
|
|
870
|
-
|
|
871
|
-
|
|
896
|
+
const width = svgRect.width * scale;
|
|
897
|
+
const height = svgRect.height * scale + legendHeight;
|
|
872
898
|
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
899
|
+
const clone = svg.cloneNode(true) as SVGSVGElement;
|
|
900
|
+
clone.setAttribute('width', String(svgRect.width));
|
|
901
|
+
clone.setAttribute('height', String(svgRect.height));
|
|
902
|
+
clone.setAttribute('viewBox', `0 0 ${svgRect.width} ${svgRect.height}`);
|
|
903
|
+
clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
878
904
|
|
|
879
|
-
|
|
905
|
+
inlineStyles(svg, clone);
|
|
880
906
|
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
+
const serializer = new XMLSerializer();
|
|
908
|
+
const svgString = serializer.serializeToString(clone);
|
|
909
|
+
const svgBlob = new Blob([svgString], {
|
|
910
|
+
type: 'image/svg+xml;charset=utf-8',
|
|
911
|
+
});
|
|
912
|
+
const url = URL.createObjectURL(svgBlob);
|
|
913
|
+
|
|
914
|
+
const mimeType = format === 'jpg' ? 'image/jpeg' : 'image/png';
|
|
915
|
+
const ext = format === 'jpg' ? 'jpg' : 'png';
|
|
916
|
+
|
|
917
|
+
const img = new Image();
|
|
918
|
+
img.onload = () => {
|
|
919
|
+
const canvas = document.createElement('canvas');
|
|
920
|
+
canvas.width = width;
|
|
921
|
+
canvas.height = height;
|
|
922
|
+
const ctx = canvas.getContext('2d');
|
|
923
|
+
if (!ctx) return;
|
|
924
|
+
|
|
925
|
+
ctx.fillStyle = '#ffffff';
|
|
926
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
927
|
+
ctx.drawImage(img, 0, 0, svgRect.width * scale, svgRect.height * scale);
|
|
928
|
+
URL.revokeObjectURL(url);
|
|
929
|
+
|
|
930
|
+
if (legendItems.length > 0) {
|
|
931
|
+
drawLegend(
|
|
932
|
+
ctx,
|
|
933
|
+
legendItems,
|
|
934
|
+
svgRect.height * scale + gap,
|
|
935
|
+
width,
|
|
936
|
+
scale
|
|
937
|
+
);
|
|
938
|
+
}
|
|
907
939
|
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
940
|
+
canvas.toBlob(blob => {
|
|
941
|
+
if (!blob) return;
|
|
942
|
+
const imgUrl = URL.createObjectURL(blob);
|
|
943
|
+
const a = document.createElement('a');
|
|
944
|
+
a.href = imgUrl;
|
|
945
|
+
a.download = `${title || 'chart'}.${ext}`;
|
|
946
|
+
a.click();
|
|
947
|
+
URL.revokeObjectURL(imgUrl);
|
|
948
|
+
}, mimeType);
|
|
949
|
+
};
|
|
950
|
+
img.onerror = () => URL.revokeObjectURL(url);
|
|
951
|
+
img.src = url;
|
|
917
952
|
};
|
|
918
|
-
|
|
953
|
+
|
|
954
|
+
// Defer to idle time with a 2-second deadline so it always runs.
|
|
955
|
+
if (typeof requestIdleCallback === 'function') {
|
|
956
|
+
requestIdleCallback(doWork, { timeout: 2000 });
|
|
957
|
+
} else {
|
|
958
|
+
setTimeout(doWork, 0);
|
|
959
|
+
}
|
|
919
960
|
}
|
|
920
961
|
|
|
962
|
+
const ALL_CHARTS: Record<CarbonChartType, React.ComponentType<any>> = {
|
|
963
|
+
area: AreaChart,
|
|
964
|
+
areaStacked: StackedAreaChart,
|
|
965
|
+
bar: SimpleBarChart,
|
|
966
|
+
barGrouped: GroupedBarChart,
|
|
967
|
+
barStacked: StackedBarChart,
|
|
968
|
+
donut: DonutChart,
|
|
969
|
+
line: LineChart,
|
|
970
|
+
lollipop: LollipopChart,
|
|
971
|
+
pie: PieChart,
|
|
972
|
+
radar: RadarChart,
|
|
973
|
+
boxplot: BoxplotChart,
|
|
974
|
+
bubble: BubbleChart,
|
|
975
|
+
bullet: BulletChart,
|
|
976
|
+
gauge: GaugeChart,
|
|
977
|
+
histogram: HistogramChart,
|
|
978
|
+
meter: MeterChart,
|
|
979
|
+
scatter: ScatterChart,
|
|
980
|
+
combo: ComboChart,
|
|
981
|
+
};
|
|
982
|
+
|
|
921
983
|
interface InternalToolbarProps {
|
|
922
984
|
config: ChartToolbarConfig;
|
|
923
985
|
dataSet: Array<Record<string, unknown>>;
|
|
@@ -986,7 +1048,11 @@ function CarbonChartToolbar({
|
|
|
986
1048
|
isInverse={isInverse}
|
|
987
1049
|
theme={theme}
|
|
988
1050
|
>
|
|
989
|
-
<ChartTitle
|
|
1051
|
+
<ChartTitle
|
|
1052
|
+
as={`h${config.titleLevel ?? 2}` as keyof JSX.IntrinsicElements}
|
|
1053
|
+
isInverse={isInverse}
|
|
1054
|
+
theme={theme}
|
|
1055
|
+
>
|
|
990
1056
|
{resolvedTitle}
|
|
991
1057
|
</ChartTitle>
|
|
992
1058
|
<ToolbarActions theme={theme}>
|
|
@@ -1078,8 +1144,15 @@ export const CarbonChart = React.forwardRef<HTMLDivElement, CarbonChartProps>(
|
|
|
1078
1144
|
}
|
|
1079
1145
|
};
|
|
1080
1146
|
document.addEventListener('fullscreenchange', onFullscreenChange);
|
|
1081
|
-
return () =>
|
|
1147
|
+
return () => {
|
|
1082
1148
|
document.removeEventListener('fullscreenchange', onFullscreenChange);
|
|
1149
|
+
// Restore height if the component unmounts while in fullscreen.
|
|
1150
|
+
const chartHolder =
|
|
1151
|
+
internalRef.current?.querySelector<HTMLElement>('.cds--chart-holder');
|
|
1152
|
+
if (chartHolder && document.fullscreenElement) {
|
|
1153
|
+
chartHolder.style.height = savedHeightRef.current;
|
|
1154
|
+
}
|
|
1155
|
+
};
|
|
1083
1156
|
}, [fullscreenEnabled]);
|
|
1084
1157
|
|
|
1085
1158
|
const openTableModal = React.useCallback(
|
|
@@ -1092,9 +1165,9 @@ export const CarbonChart = React.forwardRef<HTMLDivElement, CarbonChartProps>(
|
|
|
1092
1165
|
|
|
1093
1166
|
const closeTableModal = React.useCallback(() => {
|
|
1094
1167
|
setIsTableOpen(false);
|
|
1095
|
-
|
|
1168
|
+
requestAnimationFrame(() => {
|
|
1096
1169
|
lastTableTriggerRef.current?.focus();
|
|
1097
|
-
}
|
|
1170
|
+
});
|
|
1098
1171
|
}, []);
|
|
1099
1172
|
|
|
1100
1173
|
const toggleFullscreen = React.useCallback(() => {
|
|
@@ -1110,64 +1183,47 @@ export const CarbonChart = React.forwardRef<HTMLDivElement, CarbonChartProps>(
|
|
|
1110
1183
|
}, []);
|
|
1111
1184
|
|
|
1112
1185
|
const chartTitle: string =
|
|
1113
|
-
(options as
|
|
1186
|
+
(options as ExtendedChartOptions).title || toolbarI18n.defaultTitle;
|
|
1114
1187
|
|
|
1115
1188
|
const handleModalDownloadCsv = React.useCallback(() => {
|
|
1116
1189
|
downloadCsv(dataSet as Array<Record<string, unknown>>, chartTitle);
|
|
1117
1190
|
}, [dataSet, chartTitle]);
|
|
1118
1191
|
|
|
1119
1192
|
useCarbonModalFocusManagement(internalRef);
|
|
1120
|
-
const allCharts = {
|
|
1121
|
-
area: AreaChart,
|
|
1122
|
-
areaStacked: StackedAreaChart,
|
|
1123
|
-
bar: SimpleBarChart,
|
|
1124
|
-
barGrouped: GroupedBarChart,
|
|
1125
|
-
barStacked: StackedBarChart,
|
|
1126
|
-
donut: DonutChart,
|
|
1127
|
-
line: LineChart,
|
|
1128
|
-
lollipop: LollipopChart,
|
|
1129
|
-
pie: PieChart,
|
|
1130
|
-
radar: RadarChart,
|
|
1131
|
-
boxplot: BoxplotChart,
|
|
1132
|
-
bubble: BubbleChart,
|
|
1133
|
-
bullet: BulletChart,
|
|
1134
|
-
gauge: GaugeChart,
|
|
1135
|
-
histogram: HistogramChart,
|
|
1136
|
-
meter: MeterChart,
|
|
1137
|
-
scatter: ScatterChart,
|
|
1138
|
-
combo: ComboChart,
|
|
1139
|
-
};
|
|
1140
1193
|
|
|
1141
|
-
|
|
1194
|
+
const colorScale = React.useMemo(() => {
|
|
1142
1195
|
const scaleColorsObj: ColorsObject = {};
|
|
1143
|
-
|
|
1144
|
-
const allGroups = dataSet.map(item => {
|
|
1145
|
-
return 'group' in item ? item['group'] : null;
|
|
1146
|
-
});
|
|
1147
|
-
const uniqueGroups = allGroups.filter(
|
|
1148
|
-
(g, index) => allGroups.indexOf(g) === index
|
|
1149
|
-
);
|
|
1150
|
-
const customColors = ((options as any).colors as string[]) || [];
|
|
1196
|
+
const customColors = (options as ExtendedChartOptions).colors || [];
|
|
1151
1197
|
const allColors = [...customColors, ...theme.chartColors];
|
|
1152
1198
|
const allInverseColors = [...customColors, ...theme.chartColorsInverse];
|
|
1199
|
+
const allGroups = dataSet.map(item =>
|
|
1200
|
+
'group' in item ? (item as Record<string, unknown>)['group'] : null
|
|
1201
|
+
);
|
|
1202
|
+
const uniqueGroups = Array.from(new Set(allGroups));
|
|
1153
1203
|
|
|
1154
|
-
uniqueGroups.
|
|
1155
|
-
|
|
1156
|
-
|
|
1204
|
+
if (uniqueGroups.length <= allColors.length) {
|
|
1205
|
+
for (let i = 0; i < uniqueGroups.length; i++) {
|
|
1206
|
+
const group = uniqueGroups[i];
|
|
1207
|
+
scaleColorsObj[String(group ?? 'null')] = isInverse
|
|
1157
1208
|
? allInverseColors[i]
|
|
1158
|
-
: allColors[i]
|
|
1209
|
+
: allColors[i];
|
|
1159
1210
|
}
|
|
1160
|
-
|
|
1161
|
-
});
|
|
1211
|
+
}
|
|
1162
1212
|
|
|
1163
1213
|
return scaleColorsObj;
|
|
1164
|
-
}
|
|
1214
|
+
}, [
|
|
1215
|
+
dataSet,
|
|
1216
|
+
options,
|
|
1217
|
+
theme.chartColors,
|
|
1218
|
+
theme.chartColorsInverse,
|
|
1219
|
+
isInverse,
|
|
1220
|
+
]);
|
|
1165
1221
|
|
|
1166
1222
|
const newOptions = {
|
|
1167
1223
|
...options,
|
|
1168
1224
|
theme: isInverse ? ChartTheme.G100 : ChartTheme.WHITE,
|
|
1169
1225
|
color: {
|
|
1170
|
-
scale:
|
|
1226
|
+
scale: colorScale,
|
|
1171
1227
|
},
|
|
1172
1228
|
tooltip: {
|
|
1173
1229
|
...(options?.tooltip || {}),
|
|
@@ -1178,21 +1234,107 @@ export const CarbonChart = React.forwardRef<HTMLDivElement, CarbonChartProps>(
|
|
|
1178
1234
|
...(chartToolbar ? { toolbar: { enabled: false } } : {}),
|
|
1179
1235
|
};
|
|
1180
1236
|
|
|
1181
|
-
const ChartType =
|
|
1237
|
+
const ChartType = ALL_CHARTS[type];
|
|
1182
1238
|
|
|
1183
|
-
// Adding aria-label to main SVG container
|
|
1184
1239
|
React.useEffect(() => {
|
|
1185
|
-
if (ariaLabel)
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1240
|
+
if (!ariaLabel) return;
|
|
1241
|
+
const rafId = requestAnimationFrame(() => {
|
|
1242
|
+
const svgEl = internalRef.current?.querySelector('.graph-frame');
|
|
1243
|
+
if (!svgEl) return;
|
|
1244
|
+
svgEl.setAttribute('aria-label', ariaLabel);
|
|
1245
|
+
});
|
|
1246
|
+
return () => cancelAnimationFrame(rafId);
|
|
1247
|
+
}, [ariaLabel]);
|
|
1248
|
+
|
|
1249
|
+
// Make Carbon Charts data points keyboard-focusable.
|
|
1250
|
+
React.useEffect(() => {
|
|
1251
|
+
const wrapper = internalRef.current;
|
|
1252
|
+
if (!wrapper) return;
|
|
1253
|
+
|
|
1254
|
+
const isDot = (el: EventTarget | null): el is SVGCircleElement =>
|
|
1255
|
+
el instanceof Element &&
|
|
1256
|
+
el.nodeName.toLowerCase() === 'circle' &&
|
|
1257
|
+
el.classList.contains('dot');
|
|
1258
|
+
|
|
1259
|
+
const onFocusIn = (e: FocusEvent) => {
|
|
1260
|
+
if (!isDot(e.target)) return;
|
|
1261
|
+
const dot = e.target as SVGCircleElement;
|
|
1262
|
+
dot.style.opacity = '1';
|
|
1263
|
+
const { left, top, width, height } = dot.getBoundingClientRect();
|
|
1264
|
+
const cx = left + width / 2;
|
|
1265
|
+
const cy = top + height / 2;
|
|
1266
|
+
dot.dispatchEvent(
|
|
1267
|
+
new MouseEvent('mouseover', {
|
|
1268
|
+
bubbles: true,
|
|
1269
|
+
clientX: cx,
|
|
1270
|
+
clientY: cy,
|
|
1271
|
+
screenX: cx,
|
|
1272
|
+
screenY: cy,
|
|
1273
|
+
})
|
|
1274
|
+
);
|
|
1275
|
+
dot.dispatchEvent(
|
|
1276
|
+
new MouseEvent('mousemove', {
|
|
1277
|
+
bubbles: true,
|
|
1278
|
+
clientX: cx,
|
|
1279
|
+
clientY: cy,
|
|
1280
|
+
screenX: cx,
|
|
1281
|
+
screenY: cy,
|
|
1282
|
+
})
|
|
1283
|
+
);
|
|
1284
|
+
};
|
|
1191
1285
|
|
|
1192
|
-
|
|
1286
|
+
const onFocusOut = (e: FocusEvent) => {
|
|
1287
|
+
if (!isDot(e.target)) return;
|
|
1288
|
+
const dot = e.target;
|
|
1289
|
+
dot.style.opacity = '';
|
|
1290
|
+
dot.dispatchEvent(new MouseEvent('mouseout', { bubbles: true }));
|
|
1291
|
+
};
|
|
1292
|
+
|
|
1293
|
+
const onKeyDown = (e: KeyboardEvent) => {
|
|
1294
|
+
if (!isDot(e.target)) return;
|
|
1295
|
+
if (e.key !== 'Enter' && e.key !== ' ') return;
|
|
1296
|
+
e.preventDefault();
|
|
1297
|
+
const dot = e.target;
|
|
1298
|
+
const { left, top, width, height } = dot.getBoundingClientRect();
|
|
1299
|
+
const cx = left + width / 2;
|
|
1300
|
+
const cy = top + height / 2;
|
|
1301
|
+
dot.dispatchEvent(
|
|
1302
|
+
new MouseEvent('click', {
|
|
1303
|
+
bubbles: true,
|
|
1304
|
+
clientX: cx,
|
|
1305
|
+
clientY: cy,
|
|
1306
|
+
})
|
|
1307
|
+
);
|
|
1308
|
+
};
|
|
1309
|
+
|
|
1310
|
+
const rafId = requestAnimationFrame(() => {
|
|
1311
|
+
wrapper
|
|
1312
|
+
.querySelectorAll<SVGCircleElement>('circle.dot')
|
|
1313
|
+
.forEach(dot => {
|
|
1314
|
+
if (!dot.hasAttribute('tabindex')) {
|
|
1315
|
+
dot.setAttribute('tabindex', '0');
|
|
1316
|
+
}
|
|
1317
|
+
});
|
|
1318
|
+
});
|
|
1319
|
+
|
|
1320
|
+
wrapper.addEventListener('focusin', onFocusIn);
|
|
1321
|
+
wrapper.addEventListener('focusout', onFocusOut);
|
|
1322
|
+
wrapper.addEventListener('keydown', onKeyDown);
|
|
1323
|
+
|
|
1324
|
+
return () => {
|
|
1325
|
+
cancelAnimationFrame(rafId);
|
|
1326
|
+
wrapper.removeEventListener('focusin', onFocusIn);
|
|
1327
|
+
wrapper.removeEventListener('focusout', onFocusOut);
|
|
1328
|
+
wrapper.removeEventListener('keydown', onKeyDown);
|
|
1329
|
+
};
|
|
1330
|
+
}, [type]);
|
|
1331
|
+
|
|
1332
|
+
const groupsLength = Object.keys(colorScale).length;
|
|
1193
1333
|
|
|
1194
1334
|
const showTable = chartToolbar?.showAsTable !== false;
|
|
1195
1335
|
|
|
1336
|
+
if (!ChartType) return null;
|
|
1337
|
+
|
|
1196
1338
|
return (
|
|
1197
1339
|
<FullscreenRoot ref={mergedRef} isInverse={isInverse} theme={theme}>
|
|
1198
1340
|
<CarbonChartWrapper
|
|
@@ -20,12 +20,19 @@ export default {
|
|
|
20
20
|
options: CarbonChartType,
|
|
21
21
|
},
|
|
22
22
|
},
|
|
23
|
+
titleLevel: {
|
|
24
|
+
control: { type: 'select' },
|
|
25
|
+
options: [1, 2, 3, 4, 5, 6],
|
|
26
|
+
description: 'Heading level for the chart title (h1–h6).',
|
|
27
|
+
},
|
|
23
28
|
},
|
|
24
29
|
} as Meta;
|
|
25
30
|
|
|
26
|
-
const Template: StoryFn<
|
|
31
|
+
const Template: StoryFn<
|
|
32
|
+
CarbonChartProps & { titleLevel?: 1 | 2 | 3 | 4 | 5 | 6 }
|
|
33
|
+
> = ({ titleLevel, ...args }) => (
|
|
27
34
|
<Card isInverse={args.isInverse} style={{ padding: '12px' }}>
|
|
28
|
-
<CarbonChart {...args} />
|
|
35
|
+
<CarbonChart {...args} chartToolbar={{ titleLevel }} />
|
|
29
36
|
</Card>
|
|
30
37
|
);
|
|
31
38
|
|
|
@@ -25,7 +25,9 @@ export default {
|
|
|
25
25
|
|
|
26
26
|
const Template: StoryFn<CarbonChartProps> = args => (
|
|
27
27
|
<Card isInverse={args.isInverse} style={{ padding: '12px' }}>
|
|
28
|
-
<CarbonChart {...args}
|
|
28
|
+
<CarbonChart {...args} chartToolbar={{}}>
|
|
29
|
+
Sample text
|
|
30
|
+
</CarbonChart>
|
|
29
31
|
</Card>
|
|
30
32
|
);
|
|
31
33
|
|