@rowsncolumns/charts 28.1.11 → 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 +462 -286
- 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 +198 -149
- package/dist/cjs/plotly/basic-chart/helpers.d.ts.map +1 -1
- package/dist/cjs/plotly/basic-chart/helpers.js +2 -0
- package/dist/cjs/utils/hierarchy.d.ts +6 -0
- package/dist/cjs/utils/hierarchy.d.ts.map +1 -1
- package/dist/cjs/utils/hierarchy.js +23 -1
- package/dist/cjs/utils/histogram.d.ts +42 -3
- package/dist/cjs/utils/histogram.d.ts.map +1 -1
- package/dist/cjs/utils/histogram.js +193 -5
- package/dist/cjs/utils/overrides.d.ts +1 -1
- package/dist/cjs/utils/overrides.d.ts.map +1 -1
- package/dist/cjs/utils/overrides.js +44 -39
- package/dist/esm/basic-chart/basic-chart.d.ts.map +1 -1
- package/dist/esm/basic-chart/basic-chart.js +297 -109
- 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 +132 -70
- package/dist/esm/plotly/basic-chart/helpers.d.ts.map +1 -1
- package/dist/esm/plotly/basic-chart/helpers.js +2 -0
- package/dist/esm/utils/hierarchy.d.ts +6 -0
- package/dist/esm/utils/hierarchy.d.ts.map +1 -1
- package/dist/esm/utils/hierarchy.js +21 -0
- package/dist/esm/utils/histogram.d.ts +42 -3
- package/dist/esm/utils/histogram.d.ts.map +1 -1
- package/dist/esm/utils/histogram.js +171 -4
- package/dist/esm/utils/overrides.d.ts +1 -1
- package/dist/esm/utils/overrides.d.ts.map +1 -1
- package/dist/esm/utils/overrides.js +4 -2
- package/package.json +2 -2
|
@@ -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";
|
|
@@ -25,7 +24,7 @@ import { resolvePlotlyClickSelection, wasDragged, } from "../../utils/plotly-cli
|
|
|
25
24
|
import { buildPlotlySurfaceTrace } from "../../utils/plotly-surface";
|
|
26
25
|
import { normalize3DSourceType, } from "../../utils/three-d";
|
|
27
26
|
import { extractHierarchyRows, getHierarchyHeaderTitle, hierarchyNodeColors, toPlotlyHierarchy, } from "../../utils/hierarchy";
|
|
28
|
-
import { binHistogram, computePareto, isParetoSpec, } from "../../utils/histogram";
|
|
27
|
+
import { aggregateByCategory, binHistogram, computePareto, hasExcelBinning, isParetoSpec, } from "../../utils/histogram";
|
|
29
28
|
import { clampSecondPieSize, isOfPieChartSpec, otherSliceHalfAngle, resolveOfPieType, splitOfPie, } from "../../utils/of-pie";
|
|
30
29
|
import { buildRadarIndicators } from "../../utils/radar";
|
|
31
30
|
import { buildSequentialColorRamp, isRegionMapSpec, resolveGeoScope, WORLD_GEO_SCOPE, } from "../../utils/region-map";
|
|
@@ -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);
|
|
@@ -851,9 +871,10 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
851
871
|
...(isSunburst ? { line: { width: 1 } } : {}),
|
|
852
872
|
},
|
|
853
873
|
name: seriesName,
|
|
854
|
-
|
|
874
|
+
// `dataLabels: { show: false }` turns the labels off (#1086).
|
|
875
|
+
textinfo: spec.dataLabels?.show === false ? "none" : "label+value",
|
|
855
876
|
branchvalues: "total",
|
|
856
|
-
...(isSunburst
|
|
877
|
+
...(isSunburst || spec.dataLabels?.show === false
|
|
857
878
|
? {}
|
|
858
879
|
: {
|
|
859
880
|
textposition: "bottom left",
|
|
@@ -1075,20 +1096,43 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1075
1096
|
});
|
|
1076
1097
|
}
|
|
1077
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
|
+
});
|
|
1078
1116
|
setPlotData(radarTraces);
|
|
1079
1117
|
setLayout({
|
|
1080
1118
|
title: { text: plotlyTitleText(title, spec.titleTextFormat) },
|
|
1081
1119
|
font: { color: textColor },
|
|
1082
1120
|
paper_bgcolor: bgColor,
|
|
1083
1121
|
plot_bgcolor: bgColor,
|
|
1084
|
-
showlegend: radarTraces.length > 1,
|
|
1122
|
+
showlegend: radarLegendOverrides.showlegend ?? radarTraces.length > 1,
|
|
1123
|
+
legend: { ...radarLegendOverrides.legend, ...(radarBand.legend ?? {}) },
|
|
1085
1124
|
polar: {
|
|
1086
1125
|
radialaxis: {
|
|
1087
1126
|
visible: radarAxisOptions?.visible !== false,
|
|
1088
1127
|
range: [radarScale.min, radarScale.max],
|
|
1089
1128
|
},
|
|
1090
1129
|
},
|
|
1091
|
-
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
|
+
},
|
|
1092
1136
|
autosize: true,
|
|
1093
1137
|
});
|
|
1094
1138
|
return;
|
|
@@ -1619,12 +1663,29 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1619
1663
|
if (effectiveChartType === "histogram") {
|
|
1620
1664
|
const rawValues = seriesValuesOf(seriesData);
|
|
1621
1665
|
const binCount = spec.binCount;
|
|
1666
|
+
const binningOptions = {
|
|
1667
|
+
binSize: spec.binSize,
|
|
1668
|
+
intervalClosed: spec.intervalClosed,
|
|
1669
|
+
overflow: spec.overflow,
|
|
1670
|
+
underflow: spec.underflow,
|
|
1671
|
+
};
|
|
1672
|
+
// A domain names a category column: aggregate per category (Excel's
|
|
1673
|
+
// "By Category" / Pareto over categories) instead of binning
|
|
1674
|
+
// (#1087). Excel binning options also pre-bin with the shared util,
|
|
1675
|
+
// since plotly's native histogram cannot close intervals on the
|
|
1676
|
+
// right or add overflow bins.
|
|
1677
|
+
const categories = domainData;
|
|
1678
|
+
const prebinned = categories.length
|
|
1679
|
+
? aggregateByCategory(categories, rawValues, series.aggregateType ?? "SUM")
|
|
1680
|
+
: hasExcelBinning(binningOptions)
|
|
1681
|
+
? binHistogram(rawValues, binCount, binningOptions)
|
|
1682
|
+
: null;
|
|
1622
1683
|
if (isPareto) {
|
|
1623
1684
|
// Plotly's native histogram trace bins in axis order and
|
|
1624
1685
|
// can't reorder bins, so Pareto pre-bins with the shared
|
|
1625
1686
|
// util, sorts descending, and renders a bar trace plus the
|
|
1626
1687
|
// cumulative-% line pointed at the y2 overlay axis.
|
|
1627
|
-
const { labels, counts, cumulativePercents } = computePareto(binHistogram(rawValues, binCount));
|
|
1688
|
+
const { labels, counts, cumulativePercents } = computePareto(prebinned ?? binHistogram(rawValues, binCount));
|
|
1628
1689
|
const barTrace = {
|
|
1629
1690
|
type: "bar",
|
|
1630
1691
|
x: labels,
|
|
@@ -1638,16 +1699,34 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1638
1699
|
effectiveChartType,
|
|
1639
1700
|
});
|
|
1640
1701
|
traces.push(barTrace);
|
|
1702
|
+
const paretoLineColor = spec.paretoLineColor;
|
|
1703
|
+
// Excel draws the Pareto line without markers.
|
|
1641
1704
|
traces.push({
|
|
1642
1705
|
type: "scatter",
|
|
1643
|
-
mode: "lines
|
|
1706
|
+
mode: "lines",
|
|
1644
1707
|
x: labels,
|
|
1645
1708
|
y: cumulativePercents,
|
|
1646
1709
|
name: "Cumulative %",
|
|
1647
1710
|
yaxis: "y2",
|
|
1711
|
+
...(paretoLineColor ? { line: { color: paretoLineColor } } : {}),
|
|
1648
1712
|
});
|
|
1649
1713
|
hasSecondaryAxis = true;
|
|
1650
1714
|
}
|
|
1715
|
+
else if (prebinned) {
|
|
1716
|
+
const barTrace = {
|
|
1717
|
+
type: "bar",
|
|
1718
|
+
x: prebinned.labels,
|
|
1719
|
+
y: prebinned.counts,
|
|
1720
|
+
name: dataLabel,
|
|
1721
|
+
};
|
|
1722
|
+
applyTraceColor(barTrace, series.color ?? undefined, {
|
|
1723
|
+
noFill: series.noFill,
|
|
1724
|
+
dataPointColors: series.dataPointColors,
|
|
1725
|
+
seriesIndex: currentSeriesIndex,
|
|
1726
|
+
effectiveChartType,
|
|
1727
|
+
});
|
|
1728
|
+
traces.push(barTrace);
|
|
1729
|
+
}
|
|
1651
1730
|
else {
|
|
1652
1731
|
const trace = {
|
|
1653
1732
|
type: "histogram",
|
|
@@ -1669,62 +1748,34 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1669
1748
|
else if (effectiveChartType === "boxplot") {
|
|
1670
1749
|
const rawValues = seriesValuesOf(seriesData);
|
|
1671
1750
|
const quartileMethod = spec.quartileMethod;
|
|
1672
|
-
const showOutliers = spec.showOutliers;
|
|
1751
|
+
const showOutliers = spec.showOutliers !== false;
|
|
1673
1752
|
const showMeanMarker = spec.showMeanMarker === true;
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
//
|
|
1677
|
-
//
|
|
1678
|
-
// (
|
|
1679
|
-
//
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
// the full min/max; with outliers shown they stop at the
|
|
1701
|
-
// most extreme points inside the 1.5×IQR fences.
|
|
1702
|
-
lowerfence: [hideOutliers ? summary.min : summary.lowerWhisker],
|
|
1703
|
-
upperfence: [hideOutliers ? summary.max : summary.upperWhisker],
|
|
1704
|
-
...(hideOutliers
|
|
1705
|
-
? { boxpoints: false }
|
|
1706
|
-
: {
|
|
1707
|
-
// Sample points ride along so plotly can still draw
|
|
1708
|
-
// the outlier dots against the precomputed fences.
|
|
1709
|
-
y: [rawValues.filter((v) => v != null)],
|
|
1710
|
-
boxpoints: "outliers",
|
|
1711
|
-
}),
|
|
1712
|
-
...(showMeanMarker
|
|
1713
|
-
? { mean: [summary.mean], boxmean: true }
|
|
1714
|
-
: {}),
|
|
1715
|
-
};
|
|
1716
|
-
}
|
|
1717
|
-
else {
|
|
1718
|
-
trace = {
|
|
1719
|
-
type: "box",
|
|
1720
|
-
y: rawValues.map((v) => (v ?? null)),
|
|
1721
|
-
name: dataLabel,
|
|
1722
|
-
// Raw mode is used when outliers are visible (today's
|
|
1723
|
-
// default and `showOutliers: true` alike).
|
|
1724
|
-
boxpoints: "outliers",
|
|
1725
|
-
...(showMeanMarker ? { boxmean: true } : {}),
|
|
1726
|
-
};
|
|
1727
|
-
}
|
|
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
|
+
};
|
|
1728
1779
|
applyTraceColor(trace, series.color ?? undefined, {
|
|
1729
1780
|
noFill: series.noFill,
|
|
1730
1781
|
dataPointColors: series.dataPointColors,
|
|
@@ -2131,6 +2182,17 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
2131
2182
|
// gapWidth=150 (default) → ~0.4 bargap.
|
|
2132
2183
|
...(typeof spec.gapWidth === "number"
|
|
2133
2184
|
? { bargap: Math.max(0, Math.min(0.95, spec.gapWidth / 400)) }
|
|
2185
|
+
: spec.chartType === "histogram"
|
|
2186
|
+
? // Excel's histogram columns touch by default (#1087).
|
|
2187
|
+
{ bargap: 0 }
|
|
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
|
+
}
|
|
2134
2196
|
: {}),
|
|
2135
2197
|
font: {
|
|
2136
2198
|
family: "Untitled Sans, apple-system, sans-serif",
|
|
@@ -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") {
|
|
@@ -46,5 +46,11 @@ export declare const toPlotlyHierarchy: (rows: HierarchyRow[]) => PlotlyHierarch
|
|
|
46
46
|
* Returns the colour per node of `toPlotlyHierarchy`'s flat arrays.
|
|
47
47
|
*/
|
|
48
48
|
export declare const hierarchyNodeColors: (hierarchy: Pick<PlotlyHierarchy, "ids" | "parents">, palette: string[]) => string[];
|
|
49
|
+
/**
|
|
50
|
+
* Black or white, whichever reads on `background` (a `#rgb` / `#rrggbb`
|
|
51
|
+
* colour; anything else gets white) — for a treemap banner filled with
|
|
52
|
+
* its category's colour (rowsncolumns/spreadsheet#1086).
|
|
53
|
+
*/
|
|
54
|
+
export declare const contrastTextColor: (background: string | undefined) => string;
|
|
49
55
|
export {};
|
|
50
56
|
//# sourceMappingURL=hierarchy.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hierarchy.d.ts","sourceRoot":"","sources":["../../../utils/hierarchy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,SAAS,EACT,UAAU,EACV,YAAY,EACb,MAAM,2BAA2B,CAAC;AAEnC,KAAK,SAAS,GAAG,CACf,KAAK,EAAE,UAAU,EACjB,cAAc,CAAC,EAAE,SAAS,CAAC,yBAAyB,CAAC,KAClD,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,CAAC;AAEtD,MAAM,MAAM,YAAY,GAAG;IACzB,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,+DAA+D;IAC/D,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;CAC5B,CAAC;AAEF,KAAK,cAAc,GAAG;IACpB,wBAAwB,EAAE,SAAS,CAAC;IACpC,wBAAwB,EAAE,SAAS,CAAC;IACpC,uBAAuB,CAAC,EAAE,SAAS,CAAC,yBAAyB,CAAC,CAAC;CAChE,CAAC;AA0CF,eAAO,MAAM,uBAAuB,GAClC,MAAM,YAAY,EAClB,SAAS,cAAc,KACtB,MAAM,GAAG,SAgBX,CAAC;AAyCF,eAAO,MAAM,oBAAoB,GAC/B,MAAM,YAAY,EAClB,SAAS,cAAc,KACtB,YAAY,EAiFd,CAAC;AAiDF,eAAO,MAAM,kBAAkB,GAAI,MAAM,YAAY,EAAE,KAAG,aAAa,EAmDtE,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,4BAA4B;IAC5B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,6EAA6E;IAC7E,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,yCAAyC;IACzC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,oCAAoC;IACpC,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,MAAM,YAAY,EAAE,KAAG,eAwBxD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAC9B,WAAW,IAAI,CAAC,eAAe,EAAE,KAAK,GAAG,SAAS,CAAC,EACnD,SAAS,MAAM,EAAE,KAChB,MAAM,EAYR,CAAC"}
|
|
1
|
+
{"version":3,"file":"hierarchy.d.ts","sourceRoot":"","sources":["../../../utils/hierarchy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,SAAS,EACT,UAAU,EACV,YAAY,EACb,MAAM,2BAA2B,CAAC;AAEnC,KAAK,SAAS,GAAG,CACf,KAAK,EAAE,UAAU,EACjB,cAAc,CAAC,EAAE,SAAS,CAAC,yBAAyB,CAAC,KAClD,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,CAAC;AAEtD,MAAM,MAAM,YAAY,GAAG;IACzB,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,+DAA+D;IAC/D,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;CAC5B,CAAC;AAEF,KAAK,cAAc,GAAG;IACpB,wBAAwB,EAAE,SAAS,CAAC;IACpC,wBAAwB,EAAE,SAAS,CAAC;IACpC,uBAAuB,CAAC,EAAE,SAAS,CAAC,yBAAyB,CAAC,CAAC;CAChE,CAAC;AA0CF,eAAO,MAAM,uBAAuB,GAClC,MAAM,YAAY,EAClB,SAAS,cAAc,KACtB,MAAM,GAAG,SAgBX,CAAC;AAyCF,eAAO,MAAM,oBAAoB,GAC/B,MAAM,YAAY,EAClB,SAAS,cAAc,KACtB,YAAY,EAiFd,CAAC;AAiDF,eAAO,MAAM,kBAAkB,GAAI,MAAM,YAAY,EAAE,KAAG,aAAa,EAmDtE,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,4BAA4B;IAC5B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,6EAA6E;IAC7E,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,yCAAyC;IACzC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,oCAAoC;IACpC,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,MAAM,YAAY,EAAE,KAAG,eAwBxD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAC9B,WAAW,IAAI,CAAC,eAAe,EAAE,KAAK,GAAG,SAAS,CAAC,EACnD,SAAS,MAAM,EAAE,KAChB,MAAM,EAYR,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAAI,YAAY,MAAM,GAAG,SAAS,KAAG,MAelE,CAAC"}
|
|
@@ -266,3 +266,24 @@ export const hierarchyNodeColors = (hierarchy, palette) => {
|
|
|
266
266
|
return color;
|
|
267
267
|
});
|
|
268
268
|
};
|
|
269
|
+
/**
|
|
270
|
+
* Black or white, whichever reads on `background` (a `#rgb` / `#rrggbb`
|
|
271
|
+
* colour; anything else gets white) — for a treemap banner filled with
|
|
272
|
+
* its category's colour (rowsncolumns/spreadsheet#1086).
|
|
273
|
+
*/
|
|
274
|
+
export const contrastTextColor = (background) => {
|
|
275
|
+
const hex = (background ?? "").trim().replace(/^#/, "");
|
|
276
|
+
const full = hex.length === 3
|
|
277
|
+
? hex
|
|
278
|
+
.split("")
|
|
279
|
+
.map((c) => c + c)
|
|
280
|
+
.join("")
|
|
281
|
+
: hex;
|
|
282
|
+
if (!/^[0-9a-f]{6}$/i.test(full))
|
|
283
|
+
return "#ffffff";
|
|
284
|
+
const r = parseInt(full.slice(0, 2), 16);
|
|
285
|
+
const g = parseInt(full.slice(2, 4), 16);
|
|
286
|
+
const b = parseInt(full.slice(4, 6), 16);
|
|
287
|
+
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
288
|
+
return luminance > 0.6 ? "#000000" : "#ffffff";
|
|
289
|
+
};
|
|
@@ -39,8 +39,47 @@ export declare const isParetoSpec: (spec: {
|
|
|
39
39
|
*/
|
|
40
40
|
export declare const computePareto: (bins: HistogramBins) => ParetoData;
|
|
41
41
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
42
|
+
* Excel's `<cx:binning>` options (rowsncolumns/spreadsheet#1087). Any of
|
|
43
|
+
* them present switches the histogram to Excel's conventions: intervals
|
|
44
|
+
* closed on the right by default and automatic bins by Excel's rule.
|
|
44
45
|
*/
|
|
45
|
-
export
|
|
46
|
+
export type HistogramBinningOptions = {
|
|
47
|
+
/** Fixed bin width (`<cx:binSize>`); wins over `binCount`. */
|
|
48
|
+
binSize?: number | null;
|
|
49
|
+
/** Which side of an interval is closed (`intervalClosed`); Excel's default is `r`. */
|
|
50
|
+
intervalClosed?: "l" | "r" | null;
|
|
51
|
+
/** Values above this collect in a final `> overflow` bin. */
|
|
52
|
+
overflow?: number | null;
|
|
53
|
+
/** Values below this collect in a first `< underflow` bin. */
|
|
54
|
+
underflow?: number | null;
|
|
55
|
+
};
|
|
56
|
+
/** True when the spec authored any Excel binning option. */
|
|
57
|
+
export declare const hasExcelBinning: (options: HistogramBinningOptions | null | undefined) => boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Excel's automatic bin width: Scott's normal reference rule,
|
|
60
|
+
* 3.5 · σ / ∛n with the sample standard deviation, rounded up to two
|
|
61
|
+
* significant digits. For the 20 values 12 … 95 of #1087 (σ ≈ 23.86) that
|
|
62
|
+
* is 30.77 → 31, i.e. Excel's three bins [12, 43], (43, 74], (74, 105].
|
|
63
|
+
*/
|
|
64
|
+
export declare const excelAutoBinWidth: (numeric: number[]) => number;
|
|
65
|
+
/**
|
|
66
|
+
* Bin a set of numeric values into equal-width bins.
|
|
67
|
+
*
|
|
68
|
+
* Default (no Excel options): `binCount` bins over [min, max], closed on
|
|
69
|
+
* the left — or Sturges' rule, ceil(log2(n) + 1), when `binCount` is
|
|
70
|
+
* omitted. With Excel binning options (#1087): bins start at the minimum
|
|
71
|
+
* (or the underflow bound), are `binSize` wide — else (max − min) /
|
|
72
|
+
* `binCount`, else Excel's automatic width — closed on the right unless
|
|
73
|
+
* `intervalClosed` is `l`, and values beyond `overflow` / `underflow`
|
|
74
|
+
* collect in their own end bins.
|
|
75
|
+
*/
|
|
76
|
+
export declare const binHistogram: (values: (number | null | undefined)[], binCount?: number | null, options?: HistogramBinningOptions | null) => HistogramBins;
|
|
77
|
+
export type HistogramAggregateType = "SUM" | "AVERAGE" | "COUNT" | "MAX" | "MIN" | "MEDIAN";
|
|
78
|
+
/**
|
|
79
|
+
* Excel's histogram "By Category" / Pareto over categories
|
|
80
|
+
* (`<cx:aggregation/>`, #1087): one column per distinct category in order
|
|
81
|
+
* of first appearance, its value the aggregate (sum by default) of the
|
|
82
|
+
* series values of that category — instead of binning the values.
|
|
83
|
+
*/
|
|
84
|
+
export declare const aggregateByCategory: (categories: (string | number | null | undefined)[], values: (number | null | undefined)[], aggregateType?: HistogramAggregateType | null | undefined) => HistogramBins;
|
|
46
85
|
//# sourceMappingURL=histogram.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"histogram.d.ts","sourceRoot":"","sources":["../../../utils/histogram.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,gDAAgD;IAChD,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,mEAAmE;IACnE,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB;;;;OAIG;IACH,kBAAkB,EAAE,MAAM,EAAE,CAAC;CAC9B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC,KAAG,OAGgD,CAAC;AAErD;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,aAAa,KAAG,UAmBnD,CAAC;
|
|
1
|
+
{"version":3,"file":"histogram.d.ts","sourceRoot":"","sources":["../../../utils/histogram.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,gDAAgD;IAChD,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,mEAAmE;IACnE,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB;;;;OAIG;IACH,kBAAkB,EAAE,MAAM,EAAE,CAAC;CAC9B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC,KAAG,OAGgD,CAAC;AAErD;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,aAAa,KAAG,UAmBnD,CAAC;AAUF;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,sFAAsF;IACtF,cAAc,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,IAAI,CAAC;IAClC,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,CAAC;AAKF,4DAA4D;AAC5D,eAAO,MAAM,eAAe,GAC1B,SAAS,uBAAuB,GAAG,IAAI,GAAG,SAAS,KAClD,OAQA,CAAC;AASJ;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAAI,SAAS,MAAM,EAAE,KAAG,MASrD,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY,GACvB,QAAQ,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,EACrC,WAAW,MAAM,GAAG,IAAI,EACxB,UAAU,uBAAuB,GAAG,IAAI,KACvC,aA4CF,CAAC;AAiFF,MAAM,MAAM,sBAAsB,GAC9B,KAAK,GACL,SAAS,GACT,OAAO,GACP,KAAK,GACL,KAAK,GACL,QAAQ,CAAC;AAEb;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAC9B,YAAY,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,EAClD,QAAQ,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,EACrC,gBAAe,sBAAsB,GAAG,IAAI,GAAG,SAAiB,KAC/D,aA0CF,CAAC"}
|
|
@@ -45,17 +45,58 @@ export const computePareto = (bins) => {
|
|
|
45
45
|
const formatBinBound = (value) => {
|
|
46
46
|
if (!Number.isFinite(value))
|
|
47
47
|
return String(value);
|
|
48
|
+
if (value === 0)
|
|
49
|
+
return "0";
|
|
48
50
|
const abs = Math.abs(value);
|
|
49
51
|
if (abs >= 1000 || abs < 0.01)
|
|
50
52
|
return value.toExponential(2);
|
|
51
53
|
return Number.parseFloat(value.toFixed(2)).toString();
|
|
52
54
|
};
|
|
55
|
+
const isFinite_ = (v) => typeof v === "number" && Number.isFinite(v);
|
|
56
|
+
/** True when the spec authored any Excel binning option. */
|
|
57
|
+
export const hasExcelBinning = (options) => Boolean(options &&
|
|
58
|
+
(isFinite_(options.binSize) ||
|
|
59
|
+
options.intervalClosed === "l" ||
|
|
60
|
+
options.intervalClosed === "r" ||
|
|
61
|
+
isFinite_(options.overflow) ||
|
|
62
|
+
isFinite_(options.underflow)));
|
|
63
|
+
/** Round up to two significant digits (31 for 30.77, 0.038 for 0.0377). */
|
|
64
|
+
const ceilToTwoSignificant = (value) => {
|
|
65
|
+
if (!(value > 0))
|
|
66
|
+
return value;
|
|
67
|
+
const magnitude = 10 ** (Math.floor(Math.log10(value)) - 1);
|
|
68
|
+
return Math.ceil(value / magnitude - 1e-9) * magnitude;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Excel's automatic bin width: Scott's normal reference rule,
|
|
72
|
+
* 3.5 · σ / ∛n with the sample standard deviation, rounded up to two
|
|
73
|
+
* significant digits. For the 20 values 12 … 95 of #1087 (σ ≈ 23.86) that
|
|
74
|
+
* is 30.77 → 31, i.e. Excel's three bins [12, 43], (43, 74], (74, 105].
|
|
75
|
+
*/
|
|
76
|
+
export const excelAutoBinWidth = (numeric) => {
|
|
77
|
+
const n = numeric.length;
|
|
78
|
+
if (n < 2)
|
|
79
|
+
return 1;
|
|
80
|
+
const mean = numeric.reduce((sum, v) => sum + v, 0) / n;
|
|
81
|
+
const variance = numeric.reduce((sum, v) => sum + (v - mean) ** 2, 0) / (n - 1);
|
|
82
|
+
const sigma = Math.sqrt(variance);
|
|
83
|
+
if (!(sigma > 0))
|
|
84
|
+
return 1;
|
|
85
|
+
return ceilToTwoSignificant((3.5 * sigma) / Math.cbrt(n));
|
|
86
|
+
};
|
|
53
87
|
/**
|
|
54
|
-
* Bin a set of numeric values into
|
|
55
|
-
*
|
|
88
|
+
* Bin a set of numeric values into equal-width bins.
|
|
89
|
+
*
|
|
90
|
+
* Default (no Excel options): `binCount` bins over [min, max], closed on
|
|
91
|
+
* the left — or Sturges' rule, ceil(log2(n) + 1), when `binCount` is
|
|
92
|
+
* omitted. With Excel binning options (#1087): bins start at the minimum
|
|
93
|
+
* (or the underflow bound), are `binSize` wide — else (max − min) /
|
|
94
|
+
* `binCount`, else Excel's automatic width — closed on the right unless
|
|
95
|
+
* `intervalClosed` is `l`, and values beyond `overflow` / `underflow`
|
|
96
|
+
* collect in their own end bins.
|
|
56
97
|
*/
|
|
57
|
-
export const binHistogram = (values, binCount) => {
|
|
58
|
-
const numeric = values.filter(
|
|
98
|
+
export const binHistogram = (values, binCount, options) => {
|
|
99
|
+
const numeric = values.filter(isFinite_);
|
|
59
100
|
if (numeric.length === 0) {
|
|
60
101
|
return { labels: [], counts: [] };
|
|
61
102
|
}
|
|
@@ -67,6 +108,9 @@ export const binHistogram = (values, binCount) => {
|
|
|
67
108
|
if (v > max)
|
|
68
109
|
max = v;
|
|
69
110
|
}
|
|
111
|
+
if (hasExcelBinning(options)) {
|
|
112
|
+
return binExcel(numeric, min, max, binCount, options);
|
|
113
|
+
}
|
|
70
114
|
const sturges = Math.max(1, Math.ceil(Math.log2(numeric.length) + 1));
|
|
71
115
|
const bins = typeof binCount === "number" && binCount > 0
|
|
72
116
|
? Math.floor(binCount)
|
|
@@ -93,3 +137,126 @@ export const binHistogram = (values, binCount) => {
|
|
|
93
137
|
}
|
|
94
138
|
return { labels, counts };
|
|
95
139
|
};
|
|
140
|
+
const binExcel = (numeric, min, max, binCount, options) => {
|
|
141
|
+
const rightClosed = options.intervalClosed !== "l";
|
|
142
|
+
const underflow = isFinite_(options.underflow) ? options.underflow : null;
|
|
143
|
+
const overflow = isFinite_(options.overflow) ? options.overflow : null;
|
|
144
|
+
// The regular bins span the values between the end bins.
|
|
145
|
+
const inRange = numeric.filter((v) => (underflow === null || v >= underflow) &&
|
|
146
|
+
(overflow === null || v <= overflow));
|
|
147
|
+
const start = underflow ?? (inRange.length ? Math.min(...inRange) : min);
|
|
148
|
+
const end = overflow ?? (inRange.length ? Math.max(...inRange) : max);
|
|
149
|
+
const span = end - start;
|
|
150
|
+
let width;
|
|
151
|
+
if (isFinite_(options.binSize) && options.binSize > 0) {
|
|
152
|
+
width = options.binSize;
|
|
153
|
+
}
|
|
154
|
+
else if (typeof binCount === "number" && binCount > 0) {
|
|
155
|
+
width = span > 0 ? span / Math.floor(binCount) : 1;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
width = excelAutoBinWidth(inRange.length ? inRange : numeric);
|
|
159
|
+
}
|
|
160
|
+
// Enough bins to reach the last value; a right-closed layout needs one
|
|
161
|
+
// more when the span is an exact multiple (the boundary value belongs to
|
|
162
|
+
// the bin below, so the top bin stays).
|
|
163
|
+
let bins = span > 0 ? Math.ceil(span / width - 1e-9) : 1;
|
|
164
|
+
bins = Math.max(1, bins);
|
|
165
|
+
const counts = new Array(bins).fill(0);
|
|
166
|
+
const edges = Array.from({ length: bins + 1 }, (_, i) => start + width * i);
|
|
167
|
+
let under = 0;
|
|
168
|
+
let over = 0;
|
|
169
|
+
for (const v of numeric) {
|
|
170
|
+
if (underflow !== null && v < underflow) {
|
|
171
|
+
under++;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (overflow !== null && v > overflow) {
|
|
175
|
+
over++;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
let idx;
|
|
179
|
+
if (rightClosed) {
|
|
180
|
+
// (lo, hi]: a value on a boundary belongs to the bin below it; the
|
|
181
|
+
// first bin also takes its own lower bound.
|
|
182
|
+
idx = Math.ceil((v - start) / width - 1e-9) - 1;
|
|
183
|
+
if (idx < 0)
|
|
184
|
+
idx = 0;
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
idx = Math.floor((v - start) / width + 1e-9);
|
|
188
|
+
}
|
|
189
|
+
if (idx >= bins)
|
|
190
|
+
idx = bins - 1;
|
|
191
|
+
counts[idx]++;
|
|
192
|
+
}
|
|
193
|
+
const labels = edges.slice(0, -1).map((lo, i) => {
|
|
194
|
+
const hi = edges[i + 1];
|
|
195
|
+
if (rightClosed) {
|
|
196
|
+
const opening = i === 0 ? "[" : "(";
|
|
197
|
+
return `${opening}${formatBinBound(lo)}, ${formatBinBound(hi)}]`;
|
|
198
|
+
}
|
|
199
|
+
const closing = i === bins - 1 ? "]" : ")";
|
|
200
|
+
return `[${formatBinBound(lo)}, ${formatBinBound(hi)}${closing}`;
|
|
201
|
+
});
|
|
202
|
+
if (underflow !== null) {
|
|
203
|
+
labels.unshift(`< ${formatBinBound(underflow)}`);
|
|
204
|
+
counts.unshift(under);
|
|
205
|
+
}
|
|
206
|
+
if (overflow !== null) {
|
|
207
|
+
labels.push(`> ${formatBinBound(overflow)}`);
|
|
208
|
+
counts.push(over);
|
|
209
|
+
}
|
|
210
|
+
return { labels, counts };
|
|
211
|
+
};
|
|
212
|
+
/**
|
|
213
|
+
* Excel's histogram "By Category" / Pareto over categories
|
|
214
|
+
* (`<cx:aggregation/>`, #1087): one column per distinct category in order
|
|
215
|
+
* of first appearance, its value the aggregate (sum by default) of the
|
|
216
|
+
* series values of that category — instead of binning the values.
|
|
217
|
+
*/
|
|
218
|
+
export const aggregateByCategory = (categories, values, aggregateType = "SUM") => {
|
|
219
|
+
const groups = new Map();
|
|
220
|
+
const order = [];
|
|
221
|
+
const count = Math.max(categories.length, values.length);
|
|
222
|
+
for (let i = 0; i < count; i++) {
|
|
223
|
+
const raw = categories[i];
|
|
224
|
+
const label = raw === null || raw === undefined ? "" : `${raw}`.trim();
|
|
225
|
+
if (label === "")
|
|
226
|
+
continue;
|
|
227
|
+
if (!groups.has(label)) {
|
|
228
|
+
groups.set(label, []);
|
|
229
|
+
order.push(label);
|
|
230
|
+
}
|
|
231
|
+
const v = values[i];
|
|
232
|
+
if (isFinite_(v))
|
|
233
|
+
groups.get(label).push(v);
|
|
234
|
+
}
|
|
235
|
+
const reduce = (nums) => {
|
|
236
|
+
if (nums.length === 0)
|
|
237
|
+
return 0;
|
|
238
|
+
switch (aggregateType ?? "SUM") {
|
|
239
|
+
case "COUNT":
|
|
240
|
+
return nums.length;
|
|
241
|
+
case "AVERAGE":
|
|
242
|
+
return nums.reduce((a, b) => a + b, 0) / nums.length;
|
|
243
|
+
case "MAX":
|
|
244
|
+
return Math.max(...nums);
|
|
245
|
+
case "MIN":
|
|
246
|
+
return Math.min(...nums);
|
|
247
|
+
case "MEDIAN": {
|
|
248
|
+
const sorted = [...nums].sort((a, b) => a - b);
|
|
249
|
+
const mid = Math.floor(sorted.length / 2);
|
|
250
|
+
return sorted.length % 2
|
|
251
|
+
? sorted[mid]
|
|
252
|
+
: (sorted[mid - 1] + sorted[mid]) / 2;
|
|
253
|
+
}
|
|
254
|
+
default:
|
|
255
|
+
return nums.reduce((a, b) => a + b, 0);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
return {
|
|
259
|
+
labels: order,
|
|
260
|
+
counts: order.map((label) => reduce(groups.get(label) ?? [])),
|
|
261
|
+
};
|
|
262
|
+
};
|
|
@@ -8,7 +8,7 @@ export declare const applyLegendOverrides: (legend: LegendComponentOption, opts:
|
|
|
8
8
|
* becomes the tick-label formatter.
|
|
9
9
|
*/
|
|
10
10
|
export declare const applyAxisOverrides: (axis: any, opts: any, numberFormatPattern?: string, locale?: string) => any;
|
|
11
|
-
export declare const dataLabelFormatter: (opts: any, locale?: string) => (params: any) => string;
|
|
11
|
+
export declare const dataLabelFormatter: (opts: any, locale?: string, joiner?: string) => (params: any) => string;
|
|
12
12
|
/**
|
|
13
13
|
* The data-label options one series renders with: its own
|
|
14
14
|
* `ChartData.dataLabels` when it has them (OOXML's `<c:ser><c:dLbls>`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overrides.d.ts","sourceRoot":"","sources":["../../../utils/overrides.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAWrD,oEAAoE;AACpE,eAAO,MAAM,4BAA4B,GAAI,KAAK,GAAG,oCASpD,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,qBAAqB,EAC7B,MAAM,GAAG,KACR,qBA6CF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAC7B,MAAM,GAAG,EACT,MAAM,GAAG,EACT,sBAAsB,MAAM,EAC5B,SAAS,MAAM,QA4BhB,CAAC;AAgBF,eAAO,MAAM,kBAAkB,
|
|
1
|
+
{"version":3,"file":"overrides.d.ts","sourceRoot":"","sources":["../../../utils/overrides.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAWrD,oEAAoE;AACpE,eAAO,MAAM,4BAA4B,GAAI,KAAK,GAAG,oCASpD,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,qBAAqB,EAC7B,MAAM,GAAG,KACR,qBA6CF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAC7B,MAAM,GAAG,EACT,MAAM,GAAG,EACT,sBAAsB,MAAM,EAC5B,SAAS,MAAM,QA4BhB,CAAC;AAgBF,eAAO,MAAM,kBAAkB,GAC7B,MAAM,GAAG,EACT,SAAS,MAAM,EAEf,eAAa,MACT,QAAQ,GAAG,WAoDhB,CAAC;AAgCF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GAAI,CAAC,EACvC,YAAY,CAAC,GAAG,IAAI,GAAG,SAAS,EAChC,QAAQ;IAAE,UAAU,CAAC,EAAE,CAAC,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,KACnD,CAAC,GAAG,IAAI,GAAG,SAA6C,CAAC;AAE5D,eAAO,MAAM,sBAAsB,GACjC,MAAM,GAAG,EACT,aAAa,MAAM,EACnB,SAAS,MAAM;;;iCAlGH,GAAG;aA+GhB,CAAC"}
|