@rowsncolumns/charts 28.1.6 → 28.1.8

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.
Files changed (43) hide show
  1. package/dist/cjs/basic-chart/basic-chart.d.ts +1 -1
  2. package/dist/cjs/basic-chart/basic-chart.d.ts.map +1 -1
  3. package/dist/cjs/basic-chart/basic-chart.js +207 -152
  4. package/dist/cjs/components/editors/chart-editor.d.ts.map +1 -1
  5. package/dist/cjs/components/editors/chart-editor.js +5 -1
  6. package/dist/cjs/plotly/basic-chart/basic-chart.d.ts +1 -1
  7. package/dist/cjs/plotly/basic-chart/basic-chart.d.ts.map +1 -1
  8. package/dist/cjs/plotly/basic-chart/basic-chart.js +169 -96
  9. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  10. package/dist/cjs/utils/axis-number-format.d.ts +26 -7
  11. package/dist/cjs/utils/axis-number-format.d.ts.map +1 -1
  12. package/dist/cjs/utils/axis-number-format.js +94 -16
  13. package/dist/cjs/utils/doughnut.d.ts +19 -0
  14. package/dist/cjs/utils/doughnut.d.ts.map +1 -0
  15. package/dist/cjs/utils/doughnut.js +28 -0
  16. package/dist/cjs/utils/overrides.d.ts +12 -3
  17. package/dist/cjs/utils/overrides.d.ts.map +1 -1
  18. package/dist/cjs/utils/overrides.js +41 -16
  19. package/dist/cjs/utils/radar.d.ts +31 -5
  20. package/dist/cjs/utils/radar.d.ts.map +1 -1
  21. package/dist/cjs/utils/radar.js +67 -27
  22. package/dist/esm/basic-chart/basic-chart.d.ts +1 -1
  23. package/dist/esm/basic-chart/basic-chart.d.ts.map +1 -1
  24. package/dist/esm/basic-chart/basic-chart.js +129 -63
  25. package/dist/esm/components/editors/chart-editor.d.ts.map +1 -1
  26. package/dist/esm/components/editors/chart-editor.js +5 -1
  27. package/dist/esm/plotly/basic-chart/basic-chart.d.ts +1 -1
  28. package/dist/esm/plotly/basic-chart/basic-chart.d.ts.map +1 -1
  29. package/dist/esm/plotly/basic-chart/basic-chart.js +99 -23
  30. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  31. package/dist/esm/utils/axis-number-format.d.ts +26 -7
  32. package/dist/esm/utils/axis-number-format.d.ts.map +1 -1
  33. package/dist/esm/utils/axis-number-format.js +83 -13
  34. package/dist/esm/utils/doughnut.d.ts +19 -0
  35. package/dist/esm/utils/doughnut.d.ts.map +1 -0
  36. package/dist/esm/utils/doughnut.js +21 -0
  37. package/dist/esm/utils/overrides.d.ts +12 -3
  38. package/dist/esm/utils/overrides.d.ts.map +1 -1
  39. package/dist/esm/utils/overrides.js +40 -16
  40. package/dist/esm/utils/radar.d.ts +31 -5
  41. package/dist/esm/utils/radar.d.ts.map +1 -1
  42. package/dist/esm/utils/radar.js +44 -16
  43. package/package.json +4 -4
@@ -1,5 +1,4 @@
1
- import { ssfFormat } from "@rowsncolumns/utils";
2
- import { valueAxisLabelFormatter } from "./axis-number-format";
1
+ import { formatAxisValue, valueAxisLabelFormatter } from "./axis-number-format";
3
2
  // Map ChartSpec.legend / dataLabels / axisOptions to ECharts option
4
3
  // fragments. Each helper is a no-op when the spec field is missing, so
5
4
  // callers can spread the result without checks. Shared by the main
@@ -75,8 +74,8 @@ export const applyLegendOverrides = (legend, opts) => {
75
74
  * the linked source-cell format — see `resolveValueAxisNumberFormat`); it
76
75
  * becomes the tick-label formatter.
77
76
  */
78
- export const applyAxisOverrides = (axis, opts, numberFormatPattern) => {
79
- const formatter = valueAxisLabelFormatter(numberFormatPattern);
77
+ export const applyAxisOverrides = (axis, opts, numberFormatPattern, locale) => {
78
+ const formatter = valueAxisLabelFormatter(numberFormatPattern, locale);
80
79
  if (!opts && !formatter)
81
80
  return axis;
82
81
  const next = { ...axis };
@@ -117,14 +116,8 @@ export const applyAxisOverrides = (axis, opts, numberFormatPattern) => {
117
116
  * over the normalized percentage so labels read "$1,831" not "84.6"
118
117
  * — Excel's behaviour.
119
118
  */
120
- const formatLabelValue = (rawValue, originalValue, pattern) => {
121
- const target = originalValue ?? rawValue;
122
- if (pattern) {
123
- return ssfFormat(pattern, target);
124
- }
125
- return target === undefined || target === null ? "" : String(target);
126
- };
127
- export const dataLabelFormatter = (opts) => (params) => {
119
+ const formatLabelValue = (rawValue, originalValue, pattern, locale) => formatAxisValue(pattern, originalValue ?? rawValue, locale);
120
+ export const dataLabelFormatter = (opts, locale) => (params) => {
128
121
  const parts = [];
129
122
  if (opts.showSeriesName && params.seriesName)
130
123
  parts.push(params.seriesName);
@@ -147,7 +140,7 @@ export const dataLabelFormatter = (opts) => (params) => {
147
140
  const originalValue = typeof params.data === "object" && params.data
148
141
  ? params.data.originalValue
149
142
  : undefined;
150
- parts.push(formatLabelValue(params.value, originalValue, opts.numberFormatPattern));
143
+ parts.push(formatLabelValue(params.value, originalValue, opts.numberFormatPattern, locale));
151
144
  }
152
145
  return parts.join(": ");
153
146
  };
@@ -158,14 +151,45 @@ const ECHARTS_DLBL_POS = {
158
151
  insideEnd: "insideTop",
159
152
  outsideEnd: "top",
160
153
  bestFit: "top",
154
+ above: "top",
155
+ below: "bottom",
156
+ left: "left",
157
+ right: "right",
161
158
  };
162
- export const buildSeriesLabelOption = (opts) => {
159
+ // A line / scatter label sits on a point, not in a bar: echarts has no
160
+ // `outside` / `insideTop` for it (an unknown position is drawn at the
161
+ // default) and Excel offers centre / left / right / above / below. The
162
+ // bar-only placements land on Excel's default for a line, above the point.
163
+ const ECHARTS_POINT_DLBL_POS = {
164
+ center: "inside",
165
+ inside: "inside",
166
+ outside: "top",
167
+ insideEnd: "top",
168
+ outsideEnd: "top",
169
+ bestFit: "top",
170
+ above: "top",
171
+ below: "bottom",
172
+ left: "left",
173
+ right: "right",
174
+ };
175
+ const POINT_SERIES_TYPES = new Set(["line", "scatter", "effectScatter"]);
176
+ /**
177
+ * The data-label options one series renders with: its own
178
+ * `ChartData.dataLabels` when it has them (OOXML's `<c:ser><c:dLbls>`
179
+ * override — `{ show: false }` turns a labelled chart's labels off for
180
+ * that series alone), else the chart-level `ChartSpec.dataLabels`.
181
+ */
182
+ export const resolveSeriesDataLabels = (chartLevel, series) => series?.dataLabels ?? chartLevel;
183
+ export const buildSeriesLabelOption = (opts, seriesType, locale) => {
163
184
  if (!opts?.show)
164
185
  return undefined;
165
- const position = (opts.position && ECHARTS_DLBL_POS[opts.position]) ?? "top";
186
+ const positions = seriesType && POINT_SERIES_TYPES.has(seriesType)
187
+ ? ECHARTS_POINT_DLBL_POS
188
+ : ECHARTS_DLBL_POS;
189
+ const position = (opts.position && positions[opts.position]) ?? "top";
166
190
  return {
167
191
  show: true,
168
192
  position,
169
- formatter: dataLabelFormatter(opts),
193
+ formatter: dataLabelFormatter(opts, locale),
170
194
  };
171
195
  };
@@ -16,15 +16,41 @@ export type RadarSeriesInput = {
16
16
  };
17
17
  export type RadarIndicator = {
18
18
  name: string;
19
+ min: number;
19
20
  max: number;
20
21
  };
22
+ export type RadarAxisScale = {
23
+ min: number;
24
+ max: number;
25
+ };
26
+ /** The value-axis options a radar's common scale reads (`ChartAxisOptions`). */
27
+ export type RadarAxisOptions = {
28
+ min?: number | null;
29
+ max?: number | null;
30
+ visible?: boolean | null;
31
+ } | null | undefined;
32
+ /**
33
+ * The next "nice" axis bound at or above `value` — the top tick a value
34
+ * axis would auto-scale to: the step is 1 / 2 / 5 × a power of ten sized
35
+ * for about five divisions, and the bound is the first multiple of that
36
+ * step at or above the value (9 → 10, 33 → 40, 0.7 → 0.8).
37
+ */
38
+ export declare const niceAxisCeiling: (value: number) => number;
39
+ /**
40
+ * One value scale shared by every radar axis, as Excel draws it: `max` is
41
+ * the spec's `valueAxisOptions.max` when set, else the largest value across
42
+ * all series and categories rounded up to a nice bound (never below 1);
43
+ * `min` is `valueAxisOptions.min` when set (and below `max`), else 0.
44
+ */
45
+ export declare const radarValueScale: (series: RadarSeriesInput[], axisOptions?: RadarAxisOptions) => RadarAxisScale;
21
46
  /**
22
- * Build echarts radar indicators from category labels and series.
23
- * The `max` per indicator is the largest value seen across all series at
24
- * that category (rounded up a touch so the polygon doesn't touch the
25
- * rim). When every series is empty at an index, `max` defaults to 1.
47
+ * Build echarts radar indicators from category labels and series. Every
48
+ * indicator carries the SAME `min` / `max` — the common scale of
49
+ * {@link radarValueScale} — so values on different axes compare, and a
50
+ * single series draws the shape of its data rather than a regular polygon
51
+ * (rowsncolumns/spreadsheet#1066).
26
52
  */
27
- export declare const buildRadarIndicators: (categories: (string | number | boolean | null | undefined)[], series: RadarSeriesInput[]) => RadarIndicator[];
53
+ export declare const buildRadarIndicators: (categories: (string | number | boolean | null | undefined)[], series: RadarSeriesInput[], axisOptions?: RadarAxisOptions) => RadarIndicator[];
28
54
  /**
29
55
  * Shape series into echarts radar data: one entry per series with its
30
56
  * value vector aligned to the indicator order. Missing/non-numeric
@@ -1 +1 @@
1
- {"version":3,"file":"radar.d.ts","sourceRoot":"","sources":["../../../utils/radar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAQ3D;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,GAC/B,YAAY,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,EAC5D,QAAQ,gBAAgB,EAAE,KACzB,cAAc,EAahB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,gBAAgB,EAAE,EAC1B,gBAAgB,MAAM,KACrB,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAQzC,CAAC"}
1
+ {"version":3,"file":"radar.d.ts","sourceRoot":"","sources":["../../../utils/radar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAQxE,MAAM,MAAM,cAAc,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1D,gFAAgF;AAChF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC1B,GAAG,IAAI,GAAG,SAAS,CAAC;AAErB;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,MAAM,KAAG,MAW/C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAC1B,QAAQ,gBAAgB,EAAE,EAC1B,cAAc,gBAAgB,KAC7B,cAaF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAC/B,YAAY,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,EAC5D,QAAQ,gBAAgB,EAAE,EAC1B,cAAc,gBAAgB,KAC7B,cAAc,EAGhB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,gBAAgB,EAAE,EAC1B,gBAAgB,MAAM,KACrB,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAQzC,CAAC"}
@@ -13,25 +13,53 @@
13
13
  const isFiniteNum = (v) => typeof v === "number" && Number.isFinite(v);
14
14
  const toNumber = (v) => isFiniteNum(v) ? v : 0;
15
15
  /**
16
- * Build echarts radar indicators from category labels and series.
17
- * The `max` per indicator is the largest value seen across all series at
18
- * that category (rounded up a touch so the polygon doesn't touch the
19
- * rim). When every series is empty at an index, `max` defaults to 1.
16
+ * The next "nice" axis bound at or above `value` — the top tick a value
17
+ * axis would auto-scale to: the step is 1 / 2 / 5 × a power of ten sized
18
+ * for about five divisions, and the bound is the first multiple of that
19
+ * step at or above the value (9 → 10, 33 → 40, 0.7 → 0.8).
20
20
  */
21
- export const buildRadarIndicators = (categories, series) => {
22
- return categories.map((label, i) => {
23
- let max = 0;
24
- for (const s of series) {
25
- const v = s.values[i];
21
+ export const niceAxisCeiling = (value) => {
22
+ if (!isFiniteNum(value) || value <= 0)
23
+ return 1;
24
+ const rough = value / 5;
25
+ const magnitude = 10 ** Math.floor(Math.log10(rough));
26
+ const residual = rough / magnitude;
27
+ const step = (residual <= 1 ? 1 : residual <= 2 ? 2 : residual <= 5 ? 5 : 10) *
28
+ magnitude;
29
+ const ceiling = Math.ceil(value / step - 1e-9) * step;
30
+ // Avoid 0.30000000000000004-style bounds from the float arithmetic.
31
+ return Number(ceiling.toPrecision(12));
32
+ };
33
+ /**
34
+ * One value scale shared by every radar axis, as Excel draws it: `max` is
35
+ * the spec's `valueAxisOptions.max` when set, else the largest value across
36
+ * all series and categories rounded up to a nice bound (never below 1);
37
+ * `min` is `valueAxisOptions.min` when set (and below `max`), else 0.
38
+ */
39
+ export const radarValueScale = (series, axisOptions) => {
40
+ let largest = 0;
41
+ for (const s of series) {
42
+ for (const v of s.values) {
26
43
  if (isFiniteNum(v))
27
- max = Math.max(max, v);
44
+ largest = Math.max(largest, v);
28
45
  }
29
- return {
30
- name: String(label ?? ""),
31
- // Pad by 10% so the outermost ring has breathing room; never 0.
32
- max: max > 0 ? max * 1.1 : 1,
33
- };
34
- });
46
+ }
47
+ const max = isFiniteNum(axisOptions?.max)
48
+ ? axisOptions.max
49
+ : niceAxisCeiling(largest);
50
+ const min = isFiniteNum(axisOptions?.min) && axisOptions.min < max ? axisOptions.min : 0;
51
+ return { min, max };
52
+ };
53
+ /**
54
+ * Build echarts radar indicators from category labels and series. Every
55
+ * indicator carries the SAME `min` / `max` — the common scale of
56
+ * {@link radarValueScale} — so values on different axes compare, and a
57
+ * single series draws the shape of its data rather than a regular polygon
58
+ * (rowsncolumns/spreadsheet#1066).
59
+ */
60
+ export const buildRadarIndicators = (categories, series, axisOptions) => {
61
+ const { min, max } = radarValueScale(series, axisOptions);
62
+ return categories.map((label) => ({ name: String(label ?? ""), min, max }));
35
63
  };
36
64
  /**
37
65
  * Shape series into echarts radar data: one entry per series with its
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowsncolumns/charts",
3
- "version": "28.1.6",
3
+ "version": "28.1.8",
4
4
  "author": "Vinay M",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "main": "./dist/cjs/index.js",
@@ -70,9 +70,9 @@
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.5",
74
- "@rowsncolumns/spreadsheet-state": ">=29.1.7",
75
- "@rowsncolumns/toolkit": ">=24.5.7",
73
+ "@rowsncolumns/spreadsheet": ">=10.4.7",
74
+ "@rowsncolumns/spreadsheet-state": ">=29.1.8",
75
+ "@rowsncolumns/toolkit": ">=24.5.9",
76
76
  "@rowsncolumns/ui": ">=16.0.4",
77
77
  "@rowsncolumns/utils": ">=9.5.5",
78
78
  "@types/react-plotly.js": "^2.6.3",