@rowsncolumns/charts 28.1.12 → 28.1.13
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/cjs/basic-chart/basic-chart.d.ts.map +1 -1
- package/dist/cjs/basic-chart/basic-chart.js +392 -269
- package/dist/cjs/basic-chart/helpers.d.ts.map +1 -1
- package/dist/cjs/basic-chart/helpers.js +2 -0
- package/dist/cjs/plotly/basic-chart/basic-chart.d.ts.map +1 -1
- package/dist/cjs/plotly/basic-chart/basic-chart.js +166 -144
- package/dist/cjs/plotly/basic-chart/helpers.d.ts.map +1 -1
- package/dist/cjs/plotly/basic-chart/helpers.js +2 -0
- package/dist/esm/basic-chart/basic-chart.d.ts.map +1 -1
- package/dist/esm/basic-chart/basic-chart.js +191 -82
- package/dist/esm/basic-chart/helpers.d.ts.map +1 -1
- package/dist/esm/basic-chart/helpers.js +2 -0
- package/dist/esm/plotly/basic-chart/basic-chart.d.ts.map +1 -1
- package/dist/esm/plotly/basic-chart/basic-chart.js +88 -65
- package/dist/esm/plotly/basic-chart/helpers.d.ts.map +1 -1
- package/dist/esm/plotly/basic-chart/helpers.js +2 -0
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../../plotly/basic-chart/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,eAAO,MAAM,qBAAqB,GAChC,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,KACvC,MAmEF,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAC9B,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC;;;;;;;;;CAgBzC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,GAClC,QAAQ,MAAM,GAAG,SAAS,GAAG,IAAI,KAChC,MAAM,GAAG,SA0BX,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../../plotly/basic-chart/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,eAAO,MAAM,qBAAqB,GAChC,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,KACvC,MAmEF,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAC9B,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC;;;;;;;;;CAgBzC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,GAClC,QAAQ,MAAM,GAAG,SAAS,GAAG,IAAI,KAChC,MAAM,GAAG,SA0BX,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,YAmBjE,CAAC"}
|
|
@@ -109,6 +109,8 @@ var showXAxis = function (type) {
|
|
|
109
109
|
type === "histogram" ||
|
|
110
110
|
// A waterfall's steps are categories (Opening, Sales, …) (#1070).
|
|
111
111
|
type === "waterfall" ||
|
|
112
|
+
// A box & whisker's slots are categories (or the series names) (#1092).
|
|
113
|
+
type === "boxplot" ||
|
|
112
114
|
// Stock is line-derived on a category x-axis. Radar is polar and
|
|
113
115
|
// does not use the cartesian x-axis.
|
|
114
116
|
type === "stock") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"basic-chart.d.ts","sourceRoot":"","sources":["../../../basic-chart/basic-chart.tsx"],"names":[],"mappings":"AAkEA,OAAO,KAAiD,MAAM,OAAO,CAAC;AAEtE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAsTrD,MAAM,MAAM,eAAe,GAAG,oBAAoB,CAAC;AAiGnD,eAAO,MAAM,UAAU,GAAI,gPAkBxB,eAAe,
|
|
1
|
+
{"version":3,"file":"basic-chart.d.ts","sourceRoot":"","sources":["../../../basic-chart/basic-chart.tsx"],"names":[],"mappings":"AAkEA,OAAO,KAAiD,MAAM,OAAO,CAAC;AAEtE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAsTrD,MAAM,MAAM,eAAe,GAAG,oBAAoB,CAAC;AAiGnD,eAAO,MAAM,UAAU,GAAI,gPAkBxB,eAAe,sBAgtHjB,CAAC"}
|
|
@@ -774,6 +774,27 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
774
774
|
resolveRange: (range) => getSeriesValuesFromRange(range, spec.hiddenDimensionStrategy),
|
|
775
775
|
});
|
|
776
776
|
});
|
|
777
|
+
const bubbleLegend = applyLegendOverrides({
|
|
778
|
+
...legend,
|
|
779
|
+
show: preparedSeries.length > 1,
|
|
780
|
+
data: preparedSeries.map((entry) => entry.name),
|
|
781
|
+
}, spec.legend);
|
|
782
|
+
const bubbleBand = reserveEchartsLegendBand({
|
|
783
|
+
position: spec.legend?.position,
|
|
784
|
+
visible: bubbleLegend.show !== false,
|
|
785
|
+
labels: preparedSeries.map((entry) => entry.name),
|
|
786
|
+
legendFontSize: bubbleLegend.textStyle?.fontSize ??
|
|
787
|
+
spec.legend?.textFormat?.fontSize ??
|
|
788
|
+
12,
|
|
789
|
+
legendItemWidth: defaultLegendConfig.itemWidth,
|
|
790
|
+
width: width ?? 0,
|
|
791
|
+
height: height ?? 0,
|
|
792
|
+
hasTitle: Boolean(explicitChartTitle),
|
|
793
|
+
titleFontSize: spec.titleTextFormat?.fontSize ?? 16,
|
|
794
|
+
hasBottomAxisTitle: Boolean(spec.horizontalAxisTitle),
|
|
795
|
+
bottomAxisNameGap: 30,
|
|
796
|
+
hasLeftAxisTitle: Boolean(spec.verticalAxisTitle),
|
|
797
|
+
});
|
|
777
798
|
chartRef.current?.setOption({
|
|
778
799
|
xAxis: [
|
|
779
800
|
applyAxisOverrides({ ...xAxisConfig }, spec.categoryAxisOptions),
|
|
@@ -799,17 +820,10 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
799
820
|
]
|
|
800
821
|
: []),
|
|
801
822
|
],
|
|
802
|
-
legend:
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
}, spec.legend),
|
|
807
|
-
grid: {
|
|
808
|
-
left: "3%",
|
|
809
|
-
right: "4%",
|
|
810
|
-
bottom: "3%",
|
|
811
|
-
containLabel: true,
|
|
812
|
-
},
|
|
823
|
+
legend: { ...bubbleLegend, ...bubbleBand.legend },
|
|
824
|
+
// The plot shrinks to leave the legend its own band, as the
|
|
825
|
+
// category charts do (#1089).
|
|
826
|
+
grid: bubbleBand.grid,
|
|
813
827
|
tooltip: {
|
|
814
828
|
trigger: "item",
|
|
815
829
|
formatter: tooltipFormatter,
|
|
@@ -1385,8 +1399,44 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1385
1399
|
const lines = indicators.map((indicator, i) => `${indicator.name}: ${formatAxisValue(pattern, values[i], locale)}`);
|
|
1386
1400
|
return [params?.name ?? "", ...lines].filter(Boolean).join("<br/>");
|
|
1387
1401
|
};
|
|
1402
|
+
const radarLegend = applyLegendOverrides({
|
|
1403
|
+
...legend,
|
|
1404
|
+
show: radarData.length > 1,
|
|
1405
|
+
data: radarData.map((d) => d.name),
|
|
1406
|
+
}, spec.legend);
|
|
1407
|
+
// The circle moves and shrinks to leave the legend (and the title)
|
|
1408
|
+
// their own band instead of being drawn over (#1089).
|
|
1409
|
+
const radarBand = reserveEchartsLegendBand({
|
|
1410
|
+
position: spec.legend?.position,
|
|
1411
|
+
visible: radarLegend.show !== false,
|
|
1412
|
+
labels: radarData.map((d) => d.name),
|
|
1413
|
+
legendFontSize: radarLegend.textStyle?.fontSize ??
|
|
1414
|
+
spec.legend?.textFormat?.fontSize ??
|
|
1415
|
+
12,
|
|
1416
|
+
legendItemWidth: defaultLegendConfig.itemWidth,
|
|
1417
|
+
width: width ?? 0,
|
|
1418
|
+
height: height ?? 0,
|
|
1419
|
+
hasTitle: Boolean(explicitChartTitle),
|
|
1420
|
+
titleFontSize: spec.titleTextFormat?.fontSize ?? 16,
|
|
1421
|
+
hasBottomAxisTitle: false,
|
|
1422
|
+
bottomAxisNameGap: 0,
|
|
1423
|
+
hasLeftAxisTitle: false,
|
|
1424
|
+
});
|
|
1425
|
+
const radarPlotWidth = Math.max(0, (width ?? 0) - radarBand.grid.left - radarBand.grid.right);
|
|
1426
|
+
const radarPlotHeight = Math.max(0, (height ?? 0) - radarBand.grid.top - radarBand.grid.bottom);
|
|
1427
|
+
// Leave ~30px around the circle for the axis names.
|
|
1428
|
+
const radarGeometry = radarPlotWidth > 0 && radarPlotHeight > 0
|
|
1429
|
+
? {
|
|
1430
|
+
center: [
|
|
1431
|
+
radarBand.grid.left + radarPlotWidth / 2,
|
|
1432
|
+
radarBand.grid.top + radarPlotHeight / 2,
|
|
1433
|
+
],
|
|
1434
|
+
radius: Math.max(10, Math.min(radarPlotWidth, radarPlotHeight) / 2 - 30),
|
|
1435
|
+
}
|
|
1436
|
+
: {};
|
|
1388
1437
|
chartRef.current?.setOption({
|
|
1389
1438
|
radar: {
|
|
1439
|
+
...radarGeometry,
|
|
1390
1440
|
// The common scale's tick labels along ONE axis, as Excel draws
|
|
1391
1441
|
// them: echarts labels every indicator axis, so each indicator
|
|
1392
1442
|
// carries its own `axisLabel.show` (an indicator's own options win
|
|
@@ -1405,11 +1455,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1405
1455
|
formatter: (value) => formatAxisValue(radarFormats[0], value, locale),
|
|
1406
1456
|
},
|
|
1407
1457
|
},
|
|
1408
|
-
legend:
|
|
1409
|
-
...legend,
|
|
1410
|
-
show: radarData.length > 1,
|
|
1411
|
-
data: radarData.map((d) => d.name),
|
|
1412
|
-
}, spec.legend),
|
|
1458
|
+
legend: { ...radarLegend, ...radarBand.legend },
|
|
1413
1459
|
tooltip: { trigger: "item", formatter: radarTooltipFormatter },
|
|
1414
1460
|
series: [
|
|
1415
1461
|
{
|
|
@@ -2356,6 +2402,10 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
2356
2402
|
top: 60,
|
|
2357
2403
|
bottom: 20,
|
|
2358
2404
|
};
|
|
2405
|
+
// `dataLabels: { show: false }` hides the values inside the bars; the
|
|
2406
|
+
// stage names on the left stay (#1091).
|
|
2407
|
+
const funnelLabelsOff = resolveSeriesDataLabels(spec.dataLabels, seriesEntry)?.show ===
|
|
2408
|
+
false;
|
|
2359
2409
|
chartSeries.push({
|
|
2360
2410
|
type: "funnel",
|
|
2361
2411
|
id: chartSeriesId(0, 0),
|
|
@@ -2365,11 +2415,13 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
2365
2415
|
itemStyle: {
|
|
2366
2416
|
color: (params) => stageColors.get(params.dataIndex) ?? funnelColor,
|
|
2367
2417
|
},
|
|
2368
|
-
label:
|
|
2369
|
-
show:
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2418
|
+
label: funnelLabelsOff
|
|
2419
|
+
? { show: false }
|
|
2420
|
+
: {
|
|
2421
|
+
show: true,
|
|
2422
|
+
position: "inside",
|
|
2423
|
+
formatter: (params) => formatAxisValue(undefined, params.value, locale),
|
|
2424
|
+
},
|
|
2373
2425
|
});
|
|
2374
2426
|
// The stage names sit to the left of the bars, where Excel's
|
|
2375
2427
|
// category axis puts them: an invisible twin funnel carries them.
|
|
@@ -2386,49 +2438,49 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
2386
2438
|
labelLine: { show: false },
|
|
2387
2439
|
z: 1,
|
|
2388
2440
|
});
|
|
2389
|
-
legend.
|
|
2441
|
+
// The legend lists the series (shown when `legend.position` says so).
|
|
2442
|
+
legend.data = seriesEntry?.dataLabel ? [seriesEntry.dataLabel] : [];
|
|
2390
2443
|
}
|
|
2391
2444
|
else if (spec.chartType === "boxplot") {
|
|
2392
2445
|
const quartileMethod = spec.quartileMethod;
|
|
2393
|
-
const showOutliers = spec.showOutliers;
|
|
2446
|
+
const showOutliers = spec.showOutliers !== false;
|
|
2394
2447
|
const showMeanMarker = spec.showMeanMarker === true;
|
|
2395
|
-
const
|
|
2396
|
-
const
|
|
2397
|
-
//
|
|
2398
|
-
//
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
// the points beyond them (Excel's "show outlier points").
|
|
2412
|
-
// Otherwise the whiskers span the full min/max — today's
|
|
2413
|
-
// rendering.
|
|
2414
|
-
const lo = showOutliers === true ? summary.lowerWhisker : summary.min;
|
|
2415
|
-
const hi = showOutliers === true ? summary.upperWhisker : summary.max;
|
|
2416
|
-
boxStats.push([lo, summary.q1, summary.median, summary.q3, hi]);
|
|
2417
|
-
boxLabels.push(label);
|
|
2418
|
-
if (showOutliers === true) {
|
|
2419
|
-
for (const outlier of summary.outliers) {
|
|
2420
|
-
outlierPoints.push([boxIndex, outlier]);
|
|
2421
|
-
}
|
|
2422
|
-
}
|
|
2423
|
-
if (showMeanMarker) {
|
|
2424
|
-
meanPoints.push([boxIndex, summary.mean]);
|
|
2425
|
-
}
|
|
2448
|
+
const showInnerPoints = spec.showInnerPoints === true;
|
|
2449
|
+
const showMeanLine = spec.showMeanLine === true;
|
|
2450
|
+
// A category domain groups every series by its distinct values (in
|
|
2451
|
+
// order of first appearance): one box per category per series, as
|
|
2452
|
+
// Excel draws it. Without a domain each series is one box on its own
|
|
2453
|
+
// slot (#1092).
|
|
2454
|
+
const boxDomainSource = spec.domains?.[0]?.sources?.[0];
|
|
2455
|
+
const boxCategories = boxDomainSource
|
|
2456
|
+
? (getDomainValuesFromRange(boxDomainSource, spec.hiddenDimensionStrategy) ?? []).map((v) => (v === null || v === undefined ? "" : `${v}`.trim()))
|
|
2457
|
+
: [];
|
|
2458
|
+
const grouped = boxCategories.some((c) => c !== "");
|
|
2459
|
+
const groups = [];
|
|
2460
|
+
if (grouped) {
|
|
2461
|
+
for (const c of boxCategories) {
|
|
2462
|
+
if (c !== "" && !groups.includes(c))
|
|
2463
|
+
groups.push(c);
|
|
2426
2464
|
}
|
|
2427
2465
|
}
|
|
2466
|
+
const boxSeriesSpecs = spec.series ?? [];
|
|
2467
|
+
const slots = grouped ? groups : boxSeriesSpecs.map((s, i) => s.dataLabel && !isAFormula(s.dataLabel) ? s.dataLabel : `Series ${i + 1}`);
|
|
2468
|
+
// Excel sizes the boxes like columns: gapWidth is a percentage of the
|
|
2469
|
+
// box width (default 150). echarts' boxplot only clamps its width
|
|
2470
|
+
// (`boxWidth`), so the clamp is the box width that gap allows in the
|
|
2471
|
+
// plot's category band.
|
|
2472
|
+
const boxGap = typeof spec.gapWidth === "number" ? Math.max(0, spec.gapWidth) : 150;
|
|
2473
|
+
const boxPlotWidth = Math.max(0, (width ?? 0) * 0.85 - 40);
|
|
2474
|
+
const boxBand = slots.length ? boxPlotWidth / slots.length : boxPlotWidth;
|
|
2475
|
+
const boxWidthMax = Math.max(2, Math.round(boxBand / (Math.max(1, boxSeriesSpecs.length) + boxGap / 100)));
|
|
2476
|
+
const boxCount = Math.max(1, boxSeriesSpecs.length);
|
|
2477
|
+
// Points of series i sit at that series' sub-slot inside the band
|
|
2478
|
+
// (echarts spreads a category's boxes over ~80% of it).
|
|
2479
|
+
const slotX = (slotIndex, seriesIndex) => slotIndex + ((seriesIndex + 0.5) / boxCount - 0.5) * 0.8;
|
|
2428
2480
|
chartDomains.length = 0;
|
|
2429
2481
|
chartDomains.push({
|
|
2430
2482
|
type: "category",
|
|
2431
|
-
data:
|
|
2483
|
+
data: slots,
|
|
2432
2484
|
show: showAxis,
|
|
2433
2485
|
axisLine: { show: true },
|
|
2434
2486
|
axisTick: { show: true },
|
|
@@ -2438,38 +2490,95 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
2438
2490
|
nameGap: 30,
|
|
2439
2491
|
});
|
|
2440
2492
|
chartSeries.length = 0;
|
|
2441
|
-
const
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2493
|
+
const boxLegend = [];
|
|
2494
|
+
boxSeriesSpecs.forEach((seriesEntry, seriesIndex) => {
|
|
2495
|
+
const label = seriesEntry.dataLabel && !isAFormula(seriesEntry.dataLabel)
|
|
2496
|
+
? seriesEntry.dataLabel
|
|
2497
|
+
: "";
|
|
2498
|
+
const rawValues = flattenChartDataValues([seriesEntry], getSeriesValuesFromRange, spec.hiddenDimensionStrategy).map((v) => (typeof v === "number" ? v : Number(v)));
|
|
2499
|
+
const color = seriesEntry.color ?? paletteColorAt(seriesIndex);
|
|
2500
|
+
const boxData = [];
|
|
2501
|
+
const outlierPoints = [];
|
|
2502
|
+
const innerPoints = [];
|
|
2503
|
+
const meanPoints = [];
|
|
2504
|
+
const meanLine = [];
|
|
2505
|
+
const slotValues = grouped
|
|
2506
|
+
? groups.map((g) => rawValues.filter((_, i) => boxCategories[i] === g))
|
|
2507
|
+
: slots.map((_, i) => (i === seriesIndex ? rawValues : []));
|
|
2508
|
+
slotValues.forEach((values, slotIndex) => {
|
|
2509
|
+
const summary = computeBoxplotSummary(values, { quartileMethod });
|
|
2510
|
+
if (!summary) {
|
|
2511
|
+
boxData.push([]);
|
|
2512
|
+
meanLine.push(null);
|
|
2513
|
+
return;
|
|
2514
|
+
}
|
|
2515
|
+
// Whiskers stop at the most extreme points inside the 1.5×IQR
|
|
2516
|
+
// fences; hiding the outliers hides the points only (Excel).
|
|
2517
|
+
boxData.push([
|
|
2518
|
+
summary.lowerWhisker,
|
|
2519
|
+
summary.q1,
|
|
2520
|
+
summary.median,
|
|
2521
|
+
summary.q3,
|
|
2522
|
+
summary.upperWhisker,
|
|
2523
|
+
]);
|
|
2524
|
+
meanLine.push(summary.mean);
|
|
2525
|
+
const x = slotX(slotIndex, seriesIndex);
|
|
2526
|
+
if (showOutliers) {
|
|
2527
|
+
for (const outlier of summary.outliers)
|
|
2528
|
+
outlierPoints.push([x, outlier]);
|
|
2529
|
+
}
|
|
2530
|
+
if (showInnerPoints) {
|
|
2531
|
+
for (const v of values) {
|
|
2532
|
+
if (v >= summary.lowerWhisker && v <= summary.upperWhisker) {
|
|
2533
|
+
innerPoints.push([x, v]);
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
if (showMeanMarker)
|
|
2538
|
+
meanPoints.push([x, summary.mean]);
|
|
2459
2539
|
});
|
|
2460
|
-
|
|
2461
|
-
|
|
2540
|
+
if (label)
|
|
2541
|
+
boxLegend.push(label);
|
|
2462
2542
|
chartSeries.push({
|
|
2543
|
+
type: "boxplot",
|
|
2544
|
+
id: chartSeriesId(seriesIndex, 0),
|
|
2545
|
+
name: label,
|
|
2546
|
+
data: boxData,
|
|
2547
|
+
boxWidth: [2, boxWidthMax],
|
|
2548
|
+
itemStyle: { color, borderColor: color },
|
|
2549
|
+
});
|
|
2550
|
+
const pointSeries = (name, points, extra) => ({
|
|
2463
2551
|
type: "scatter",
|
|
2464
|
-
name
|
|
2465
|
-
data:
|
|
2466
|
-
|
|
2467
|
-
symbolSize: 9,
|
|
2468
|
-
...(boxColor ? { itemStyle: { color: boxColor } } : {}),
|
|
2552
|
+
name,
|
|
2553
|
+
data: points,
|
|
2554
|
+
itemStyle: { color },
|
|
2469
2555
|
tooltip: { show: false },
|
|
2556
|
+
...extra,
|
|
2470
2557
|
});
|
|
2471
|
-
|
|
2472
|
-
|
|
2558
|
+
if (outlierPoints.length > 0) {
|
|
2559
|
+
chartSeries.push(pointSeries("", outlierPoints, { symbolSize: 6 }));
|
|
2560
|
+
}
|
|
2561
|
+
if (innerPoints.length > 0) {
|
|
2562
|
+
chartSeries.push(pointSeries("", innerPoints, { symbolSize: 4, itemStyle: { color, opacity: 0.6 } }));
|
|
2563
|
+
}
|
|
2564
|
+
if (meanPoints.length > 0) {
|
|
2565
|
+
chartSeries.push(pointSeries("", meanPoints, { symbol: "diamond", symbolSize: 9 }));
|
|
2566
|
+
}
|
|
2567
|
+
if (showMeanLine && meanLine.some((m) => m !== null)) {
|
|
2568
|
+
chartSeries.push({
|
|
2569
|
+
type: "line",
|
|
2570
|
+
name: "",
|
|
2571
|
+
data: meanLine.map((m, slotIndex) => m === null ? null : [slotX(slotIndex, seriesIndex), m]),
|
|
2572
|
+
showSymbol: false,
|
|
2573
|
+
lineStyle: { color, type: "dashed", width: 1 },
|
|
2574
|
+
itemStyle: { color },
|
|
2575
|
+
tooltip: { show: false },
|
|
2576
|
+
z: 3,
|
|
2577
|
+
});
|
|
2578
|
+
}
|
|
2579
|
+
});
|
|
2580
|
+
// The legend lists the series (shown when `legend.position` says so).
|
|
2581
|
+
legend.data = boxLegend;
|
|
2473
2582
|
}
|
|
2474
2583
|
else if (spec.chartType === "histogram") {
|
|
2475
2584
|
const rawValues = flattenChartDataValues(spec.series, getSeriesValuesFromRange, spec.hiddenDimensionStrategy).map((v) => (typeof v === "number" ? v : Number(v)));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../basic-chart/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,eAAO,MAAM,sBAAsB,GACjC,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,yGA6DzC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,GACnC,QAAQ,MAAM,GAAG,SAAS,GAAG,IAAI,KAChC,MAAM,GAAG,SAuBX,CAAC;AAEF,uEAAuE;AACvE,eAAO,MAAM,mBAAmB,GAC9B,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,GAAG,SAAS,KACnD,OAAgD,CAAC;AAEpD;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAChC,QAAQ,aAAa,CAAC,OAAO,CAAC,KAC7B,MAAM,EAmBR,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC;;aASzC,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../basic-chart/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,eAAO,MAAM,sBAAsB,GACjC,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,yGA6DzC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,GACnC,QAAQ,MAAM,GAAG,SAAS,GAAG,IAAI,KAChC,MAAM,GAAG,SAuBX,CAAC;AAEF,uEAAuE;AACvE,eAAO,MAAM,mBAAmB,GAC9B,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,GAAG,SAAS,KACnD,OAAgD,CAAC;AAEpD;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAChC,QAAQ,aAAa,CAAC,OAAO,CAAC,KAC7B,MAAM,EAmBR,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC;;aASzC,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,YAmBjE,CAAC"}
|
|
@@ -121,6 +121,8 @@ export const showXAxis = (type) => {
|
|
|
121
121
|
type === "histogram" ||
|
|
122
122
|
// A waterfall's steps are categories (Opening, Sales, …) (#1070).
|
|
123
123
|
type === "waterfall" ||
|
|
124
|
+
// A box & whisker's slots are categories (or the series names) (#1092).
|
|
125
|
+
type === "boxplot" ||
|
|
124
126
|
// Stock is line-derived and lives on a category x-axis. Radar is
|
|
125
127
|
// axis-less (it uses a polar `radar` indicator instead).
|
|
126
128
|
type === "stock") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"basic-chart.d.ts","sourceRoot":"","sources":["../../../../plotly/basic-chart/basic-chart.tsx"],"names":[],"mappings":"AAoCA,OAAO,KAAiD,MAAM,OAAO,CAAC;AAEtE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"basic-chart.d.ts","sourceRoot":"","sources":["../../../../plotly/basic-chart/basic-chart.tsx"],"names":[],"mappings":"AAoCA,OAAO,KAAiD,MAAM,OAAO,CAAC;AAEtE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AA8ZxD,MAAM,MAAM,eAAe,GAAG,oBAAoB,CAAC;AAmGnD,eAAO,MAAM,UAAU,GAAI,gPAkBxB,eAAe,sBAkqFjB,CAAC"}
|
|
@@ -6,7 +6,6 @@ import { chartPaletteColorAt, debug, expandChartPalette, isAFormula, isNil, ssfF
|
|
|
6
6
|
import React, { useCallback, useMemo, useRef, useState } from "react";
|
|
7
7
|
import { aggregateSeriesByDomain, getDomainValuesForAxis, sanitizeDomainValues, shouldAggregateSeries, } from "../../utils/aggregation";
|
|
8
8
|
import { formatAxisValue, numberFormatToPlotlyTicks, plotlySeparators, resolveSeriesNumberFormats, resolveValueAxisNumberFormat, valueAxisNameGap, } from "../../utils/axis-number-format";
|
|
9
|
-
import { computeBoxplotSummary } from "../../utils/boxplot";
|
|
10
9
|
import { createBubbleSizeScale, prepareBubbleSeriesData, toLabelString, } from "../../utils/bubble";
|
|
11
10
|
import { canSwitchRowColumn, cellLabelGetterFromRangeReader, switchRowColumn, } from "../../utils/chart-orientation";
|
|
12
11
|
import { dataUrlToBlob } from "../../utils/chart-image";
|
|
@@ -736,6 +735,29 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
736
735
|
const xAxisFmt = spec.categoryAxisOptions?.textFormat;
|
|
737
736
|
const yAxisFmt = spec.valueAxisOptions?.textFormat;
|
|
738
737
|
const legendFmt = spec.legend?.textFormat;
|
|
738
|
+
const bubbleLegendOverrides = applyLegendOverridesPlotly({
|
|
739
|
+
font: {
|
|
740
|
+
color: legendFmt?.color ?? textColor,
|
|
741
|
+
...(legendFmt?.fontFamily ? { family: legendFmt.fontFamily } : {}),
|
|
742
|
+
...(legendFmt?.fontSize ? { size: legendFmt.fontSize } : {}),
|
|
743
|
+
},
|
|
744
|
+
}, spec.legend);
|
|
745
|
+
const bubbleBand = reservePlotlyLegendBand({
|
|
746
|
+
position: spec.legend?.position,
|
|
747
|
+
visible: (bubbleLegendOverrides.showlegend ?? preparedSeries.length > 1) !==
|
|
748
|
+
false,
|
|
749
|
+
labels: preparedSeries.map((entry) => entry.name),
|
|
750
|
+
legendFontSize: legendFmt?.fontSize ?? 12,
|
|
751
|
+
width: Math.max(0, (width ?? 0) - 2),
|
|
752
|
+
height: Math.max(0, (height ?? 0) - 2),
|
|
753
|
+
hasTitle: Boolean(title),
|
|
754
|
+
titleFontSize: titleFmt?.fontSize ?? 16,
|
|
755
|
+
hasBottomAxisTitle: Boolean(spec.horizontalAxisTitle),
|
|
756
|
+
hasLeftAxisTitle: Boolean(spec.verticalAxisTitle),
|
|
757
|
+
leftAxisDepth: 60,
|
|
758
|
+
rightAxisDepth: hasBubbleSecondaryAxis ? 60 : 0,
|
|
759
|
+
axisFontSize: 12,
|
|
760
|
+
});
|
|
739
761
|
const bubbleLayout = {
|
|
740
762
|
title: {
|
|
741
763
|
text: plotlyTitleText(title, spec.titleTextFormat),
|
|
@@ -789,16 +811,14 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
789
811
|
}
|
|
790
812
|
: {}),
|
|
791
813
|
legend: {
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
...(legendFmt?.fontFamily ? { family: legendFmt.fontFamily } : {}),
|
|
795
|
-
...(legendFmt?.fontSize ? { size: legendFmt.fontSize } : {}),
|
|
796
|
-
},
|
|
814
|
+
...bubbleLegendOverrides.legend,
|
|
815
|
+
...(bubbleBand.legend ?? {}),
|
|
797
816
|
},
|
|
798
817
|
paper_bgcolor: bgColor,
|
|
799
818
|
plot_bgcolor: bgColor,
|
|
800
|
-
showlegend: preparedSeries.length > 1,
|
|
801
|
-
|
|
819
|
+
showlegend: bubbleLegendOverrides.showlegend ?? preparedSeries.length > 1,
|
|
820
|
+
// The plot shrinks to leave the legend its own band (#1089).
|
|
821
|
+
margin: bubbleBand.margin,
|
|
802
822
|
};
|
|
803
823
|
setPlotData(traces);
|
|
804
824
|
setLayout(bubbleLayout);
|
|
@@ -1076,20 +1096,43 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1076
1096
|
});
|
|
1077
1097
|
}
|
|
1078
1098
|
const radarScale = indicators[0] ?? { min: 0, max: 1 };
|
|
1099
|
+
// The legend gets its own band so it never covers the circle (#1089).
|
|
1100
|
+
const radarLegendOverrides = applyLegendOverridesPlotly({ font: { color: textColor } }, spec.legend);
|
|
1101
|
+
const radarBand = reservePlotlyLegendBand({
|
|
1102
|
+
position: spec.legend?.position,
|
|
1103
|
+
visible: (radarLegendOverrides.showlegend ?? radarTraces.length > 1) !== false,
|
|
1104
|
+
labels: radarInputs.map((input) => input.name),
|
|
1105
|
+
legendFontSize: spec.legend?.textFormat?.fontSize ?? 12,
|
|
1106
|
+
width: Math.max(0, (width ?? 0) - 2),
|
|
1107
|
+
height: Math.max(0, (height ?? 0) - 2),
|
|
1108
|
+
hasTitle: Boolean(title),
|
|
1109
|
+
titleFontSize: spec.titleTextFormat?.fontSize ?? 16,
|
|
1110
|
+
hasBottomAxisTitle: false,
|
|
1111
|
+
hasLeftAxisTitle: false,
|
|
1112
|
+
leftAxisDepth: 50,
|
|
1113
|
+
rightAxisDepth: 50,
|
|
1114
|
+
axisFontSize: 12,
|
|
1115
|
+
});
|
|
1079
1116
|
setPlotData(radarTraces);
|
|
1080
1117
|
setLayout({
|
|
1081
1118
|
title: { text: plotlyTitleText(title, spec.titleTextFormat) },
|
|
1082
1119
|
font: { color: textColor },
|
|
1083
1120
|
paper_bgcolor: bgColor,
|
|
1084
1121
|
plot_bgcolor: bgColor,
|
|
1085
|
-
showlegend: radarTraces.length > 1,
|
|
1122
|
+
showlegend: radarLegendOverrides.showlegend ?? radarTraces.length > 1,
|
|
1123
|
+
legend: { ...radarLegendOverrides.legend, ...(radarBand.legend ?? {}) },
|
|
1086
1124
|
polar: {
|
|
1087
1125
|
radialaxis: {
|
|
1088
1126
|
visible: radarAxisOptions?.visible !== false,
|
|
1089
1127
|
range: [radarScale.min, radarScale.max],
|
|
1090
1128
|
},
|
|
1091
1129
|
},
|
|
1092
|
-
margin: {
|
|
1130
|
+
margin: {
|
|
1131
|
+
l: Math.max(50, radarBand.margin.l),
|
|
1132
|
+
r: Math.max(50, radarBand.margin.r),
|
|
1133
|
+
t: Math.max(50, radarBand.margin.t),
|
|
1134
|
+
b: Math.max(50, radarBand.margin.b),
|
|
1135
|
+
},
|
|
1093
1136
|
autosize: true,
|
|
1094
1137
|
});
|
|
1095
1138
|
return;
|
|
@@ -1705,62 +1748,34 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1705
1748
|
else if (effectiveChartType === "boxplot") {
|
|
1706
1749
|
const rawValues = seriesValuesOf(seriesData);
|
|
1707
1750
|
const quartileMethod = spec.quartileMethod;
|
|
1708
|
-
const showOutliers = spec.showOutliers;
|
|
1751
|
+
const showOutliers = spec.showOutliers !== false;
|
|
1709
1752
|
const showMeanMarker = spec.showMeanMarker === true;
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
//
|
|
1713
|
-
//
|
|
1714
|
-
// (
|
|
1715
|
-
//
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
// the full min/max; with outliers shown they stop at the
|
|
1737
|
-
// most extreme points inside the 1.5×IQR fences.
|
|
1738
|
-
lowerfence: [hideOutliers ? summary.min : summary.lowerWhisker],
|
|
1739
|
-
upperfence: [hideOutliers ? summary.max : summary.upperWhisker],
|
|
1740
|
-
...(hideOutliers
|
|
1741
|
-
? { boxpoints: false }
|
|
1742
|
-
: {
|
|
1743
|
-
// Sample points ride along so plotly can still draw
|
|
1744
|
-
// the outlier dots against the precomputed fences.
|
|
1745
|
-
y: [rawValues.filter((v) => v != null)],
|
|
1746
|
-
boxpoints: "outliers",
|
|
1747
|
-
}),
|
|
1748
|
-
...(showMeanMarker
|
|
1749
|
-
? { mean: [summary.mean], boxmean: true }
|
|
1750
|
-
: {}),
|
|
1751
|
-
};
|
|
1752
|
-
}
|
|
1753
|
-
else {
|
|
1754
|
-
trace = {
|
|
1755
|
-
type: "box",
|
|
1756
|
-
y: rawValues.map((v) => (v ?? null)),
|
|
1757
|
-
name: dataLabel,
|
|
1758
|
-
// Raw mode is used when outliers are visible (today's
|
|
1759
|
-
// default and `showOutliers: true` alike).
|
|
1760
|
-
boxpoints: "outliers",
|
|
1761
|
-
...(showMeanMarker ? { boxmean: true } : {}),
|
|
1762
|
-
};
|
|
1763
|
-
}
|
|
1753
|
+
const showInnerPoints = spec.showInnerPoints === true;
|
|
1754
|
+
const showMeanLine = spec.showMeanLine === true;
|
|
1755
|
+
// A category domain groups the series by its values (one box per
|
|
1756
|
+
// category per series, `boxmode: "group"` in the layout); without
|
|
1757
|
+
// one each series is a box on its own slot (#1092). Plotly's box
|
|
1758
|
+
// trace computes the quartiles (Excel's inclusive / exclusive
|
|
1759
|
+
// methods are native), keeps the whiskers at the 1.5×IQR fences
|
|
1760
|
+
// and only hides the points when outliers are off.
|
|
1761
|
+
const boxDomainSource = spec.domains?.[0]?.sources?.[0];
|
|
1762
|
+
const boxCategories = boxDomainSource
|
|
1763
|
+
? (getDomainValuesFromRange(boxDomainSource, spec.hiddenDimensionStrategy) ?? []).map((v) => (v === null || v === undefined ? "" : `${v}`.trim()))
|
|
1764
|
+
: [];
|
|
1765
|
+
const grouped = boxCategories.some((c) => c !== "");
|
|
1766
|
+
const values = rawValues.map((v) => (v ?? null));
|
|
1767
|
+
const trace = {
|
|
1768
|
+
type: "box",
|
|
1769
|
+
name: dataLabel,
|
|
1770
|
+
y: values,
|
|
1771
|
+
...(grouped
|
|
1772
|
+
? { x: values.map((_, i) => boxCategories[i] ?? "") }
|
|
1773
|
+
: {}),
|
|
1774
|
+
...(quartileMethod ? { quartilemethod: quartileMethod } : {}),
|
|
1775
|
+
boxpoints: showInnerPoints ? "all" : showOutliers ? "outliers" : false,
|
|
1776
|
+
...(showInnerPoints ? { jitter: 0, pointpos: 0 } : {}),
|
|
1777
|
+
...(showMeanMarker || showMeanLine ? { boxmean: true } : {}),
|
|
1778
|
+
};
|
|
1764
1779
|
applyTraceColor(trace, series.color ?? undefined, {
|
|
1765
1780
|
noFill: series.noFill,
|
|
1766
1781
|
dataPointColors: series.dataPointColors,
|
|
@@ -2171,6 +2186,14 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
2171
2186
|
? // Excel's histogram columns touch by default (#1087).
|
|
2172
2187
|
{ bargap: 0 }
|
|
2173
2188
|
: {}),
|
|
2189
|
+
// Box & whisker: boxes grouped by category, `gapWidth` as for columns
|
|
2190
|
+
// (#1092).
|
|
2191
|
+
...(spec.chartType === "boxplot"
|
|
2192
|
+
? {
|
|
2193
|
+
boxmode: "group",
|
|
2194
|
+
boxgap: Math.max(0, Math.min(0.95, (typeof spec.gapWidth === "number" ? spec.gapWidth : 150) / 400)),
|
|
2195
|
+
}
|
|
2196
|
+
: {}),
|
|
2174
2197
|
font: {
|
|
2175
2198
|
family: "Untitled Sans, apple-system, sans-serif",
|
|
2176
2199
|
size: 12,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../../plotly/basic-chart/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,eAAO,MAAM,qBAAqB,GAChC,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,KACvC,MAmEF,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAC9B,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC;;;;;;;;;CAgBzC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,GAClC,QAAQ,MAAM,GAAG,SAAS,GAAG,IAAI,KAChC,MAAM,GAAG,SA0BX,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../../plotly/basic-chart/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,eAAO,MAAM,qBAAqB,GAChC,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,KACvC,MAmEF,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAC9B,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC;;;;;;;;;CAgBzC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,GAClC,QAAQ,MAAM,GAAG,SAAS,GAAG,IAAI,KAChC,MAAM,GAAG,SA0BX,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,YAmBjE,CAAC"}
|
|
@@ -103,6 +103,8 @@ export const showXAxis = (type) => {
|
|
|
103
103
|
type === "histogram" ||
|
|
104
104
|
// A waterfall's steps are categories (Opening, Sales, …) (#1070).
|
|
105
105
|
type === "waterfall" ||
|
|
106
|
+
// A box & whisker's slots are categories (or the series names) (#1092).
|
|
107
|
+
type === "boxplot" ||
|
|
106
108
|
// Stock is line-derived on a category x-axis. Radar is polar and
|
|
107
109
|
// does not use the cartesian x-axis.
|
|
108
110
|
type === "stock") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rowsncolumns/charts",
|
|
3
|
-
"version": "28.1.
|
|
3
|
+
"version": "28.1.13",
|
|
4
4
|
"author": "Vinay M",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"@rowsncolumns/common-types": ">=5.3.2",
|
|
71
71
|
"@rowsncolumns/grid": ">=13.2.35",
|
|
72
72
|
"@rowsncolumns/icons": ">=7.2.33",
|
|
73
|
-
"@rowsncolumns/spreadsheet": ">=10.4.
|
|
73
|
+
"@rowsncolumns/spreadsheet": ">=10.4.11",
|
|
74
74
|
"@rowsncolumns/spreadsheet-state": ">=29.1.8",
|
|
75
75
|
"@rowsncolumns/toolkit": ">=24.5.10",
|
|
76
76
|
"@rowsncolumns/ui": ">=16.0.4",
|