@x12i/charts 1.0.0 → 1.1.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/index.js CHANGED
@@ -1,3 +1,106 @@
1
+ import {
2
+ AXIS_FONT_SIZE,
3
+ DEFAULT_LOCALE,
4
+ ExpressionError,
5
+ MAX_HUES,
6
+ SVG_POINT_LIMIT,
7
+ TICK_PADDING,
8
+ TICK_SIZE,
9
+ annotationTone,
10
+ applyTransforms,
11
+ buildChartModel,
12
+ capitalize,
13
+ categoricalTones,
14
+ chartToCsv,
15
+ chartToTable,
16
+ checkWhere,
17
+ compileWhere,
18
+ computeAxisMargins,
19
+ correlation,
20
+ countPoints,
21
+ createBandScale,
22
+ createContinuousScale,
23
+ createDateFormatter,
24
+ createFormatters,
25
+ createNumberFormatter,
26
+ createPointScale,
27
+ createTimeTickFormatter,
28
+ cumulativeSum,
29
+ dashArrays,
30
+ dashStyles,
31
+ defaultMessages,
32
+ describeChart,
33
+ describeModel,
34
+ describeTarget,
35
+ divergingColor,
36
+ dodgePositions,
37
+ effectiveStack,
38
+ escapeCsvField,
39
+ estimateTextWidth,
40
+ evaluateWhere,
41
+ extent,
42
+ fitTickLabels,
43
+ forecastLinear,
44
+ formatList,
45
+ formatMessage,
46
+ formatShare,
47
+ fractionDigitsForStep,
48
+ histogramBins,
49
+ hitTest,
50
+ isNavKey,
51
+ isOpenMarker,
52
+ isRtlLocale,
53
+ isSupportedMark,
54
+ layoutChart,
55
+ linearRegression,
56
+ lttb,
57
+ lttbIndices,
58
+ markerPath,
59
+ markerShapes,
60
+ mean,
61
+ median,
62
+ minMax,
63
+ minMaxIndices,
64
+ modelToTable,
65
+ movingAverage,
66
+ navKeys,
67
+ navigate,
68
+ niceTicks,
69
+ patternIndex,
70
+ percentChange,
71
+ periodChange,
72
+ pointInfo,
73
+ quantile,
74
+ rampGradient,
75
+ readField,
76
+ regressionBand,
77
+ resolveMessages,
78
+ resolveRenderer,
79
+ resolveRows,
80
+ sequentialColor,
81
+ seriesStyle,
82
+ stackValues,
83
+ standardDeviation,
84
+ sum,
85
+ supportedMarks,
86
+ tableToCsv,
87
+ targetById,
88
+ themeColorsFromTokens,
89
+ toCategory,
90
+ toEChartsOption,
91
+ toNumber,
92
+ toTime,
93
+ toneCssVar,
94
+ toneTokenPath,
95
+ toneVarName,
96
+ tooltipFor,
97
+ trendDirection,
98
+ truncateLabel,
99
+ usesBandX,
100
+ valueDomain,
101
+ variance
102
+ } from "./chunk-U3U6ZV7A.js";
103
+
1
104
  // src/spec.ts
2
105
  var chartTypes = [
3
106
  // Comparison
@@ -25,6 +128,7 @@ var chartTypes = [
25
128
  "ecdf",
26
129
  // Composition
27
130
  "donut",
131
+ "pie",
28
132
  "treemap",
29
133
  "waterfall",
30
134
  "funnel",
@@ -56,37 +160,443 @@ var chartTypes = [
56
160
  "timeline",
57
161
  "burndown",
58
162
  "cumulative-flow",
59
- "calendar-heatmap"
163
+ "calendar-heatmap",
164
+ // Mixed marks on one plot (bars with lines, optionally on two axes)
165
+ "combo"
60
166
  ];
61
167
  function defineChartSpec(spec) {
62
168
  return spec;
63
169
  }
64
170
 
65
- // src/renderer.ts
66
- var SVG_POINT_LIMIT = 5e3;
67
- var CANVAS_ONLY = /* @__PURE__ */ new Set([
68
- "candlestick",
69
- "ohlc",
70
- "sunburst",
71
- "marimekko",
72
- "hexbin",
73
- "contour",
74
- "sankey",
75
- "chord",
76
- "alluvial"
77
- ]);
78
- function resolveRenderer({
79
- renderer = "auto",
80
- mark,
81
- pointCount,
82
- target = "screen"
83
- }) {
84
- if (target !== "screen") return "svg";
85
- if (renderer !== "auto") return renderer;
86
- if (CANVAS_ONLY.has(mark)) return "canvas";
87
- return pointCount > SVG_POINT_LIMIT ? "canvas" : "svg";
171
+ // src/validate.ts
172
+ var FIELD_TYPES = ["category", "number", "time", "boolean"];
173
+ var SCALE_TYPES = ["linear", "log", "sqrt", "time", "band", "ordinal"];
174
+ var STACK_MODES = ["none", "stacked", "normalized", "diverging"];
175
+ var CARTESIAN = /* @__PURE__ */ new Set(["bar", "lollipop", "line", "area", "step", "sparkline", "combo", "scatter", "bubble", "waterfall"]);
176
+ var MAX_SERIES = 8;
177
+ var PROBE_ROWS = 200;
178
+ function isRecord(value) {
179
+ return typeof value === "object" && value !== null && !Array.isArray(value);
88
180
  }
89
-
90
- export { SVG_POINT_LIMIT, chartTypes, defineChartSpec, resolveRenderer };
91
- //# sourceMappingURL=index.js.map
181
+ function validateChartSpec(input, options = {}) {
182
+ const issues = [];
183
+ const add = (severity, code, path, message) => {
184
+ issues.push({ severity, code, path, message });
185
+ };
186
+ if (!isRecord(input)) {
187
+ add("error", "spec-not-object", "", "A chart spec must be an object.");
188
+ return issues;
189
+ }
190
+ const spec = input;
191
+ if (typeof spec.title !== "string" || spec.title.trim() === "") {
192
+ add("error", "title-missing", "title", "Every chart needs a title that states the finding.");
193
+ }
194
+ if (!isRecord(spec.a11y) || typeof spec.a11y.description !== "string" || spec.a11y.description.trim() === "") {
195
+ add("error", "a11y-description-missing", "a11y.description", "Every chart needs an accessibility description.");
196
+ }
197
+ if (spec.version !== void 0 && spec.version !== 1) {
198
+ add("warning", "version-unknown", "version", `Unknown schema version ${String(spec.version)}.`);
199
+ }
200
+ const mark = spec.mark;
201
+ if (typeof mark !== "string" || !chartTypes.includes(mark)) {
202
+ add("error", "mark-invalid", "mark", `Unknown chart type "${String(mark)}".`);
203
+ } else if (!isSupportedMark(mark)) {
204
+ add("warning", "mark-unsupported", "mark", `The ${mark} chart type is not drawn by the SVG renderer; it needs a different renderer.`);
205
+ }
206
+ let rows;
207
+ const data = spec.data;
208
+ if (!isRecord(data)) {
209
+ add("error", "data-missing", "data", "A spec needs `data`: `{ values }` or `{ ref }`.");
210
+ } else if ("values" in data) {
211
+ if (!Array.isArray(data.values)) add("error", "data-invalid", "data.values", "`data.values` must be an array of rows.");
212
+ else rows = data.values;
213
+ } else if (typeof data.ref === "string") {
214
+ if (options.datasets) {
215
+ rows = options.datasets[data.ref];
216
+ if (!rows) add("error", "dataset-missing", "data.ref", `Dataset "${data.ref}" was not provided.`);
217
+ }
218
+ } else {
219
+ add("error", "data-invalid", "data", "`data` needs `values` or `ref`.");
220
+ }
221
+ const hasOwnSeriesData = Array.isArray(spec.series) && spec.series.length > 0 && spec.series.every((series2) => isRecord(series2) && Array.isArray(series2.points));
222
+ const literalGauge = (spec.mark === "gauge" || spec.mark === "progress-ring") && isRecord(spec.gauge) && typeof spec.gauge.value === "number";
223
+ if (rows && rows.length === 0 && !hasOwnSeriesData && !literalGauge) {
224
+ add("warning", "data-empty", "data", "The dataset has no rows; the chart will show its empty state.");
225
+ }
226
+ const fieldExists = (field) => {
227
+ if (!rows || rows.length === 0) return true;
228
+ return rows.slice(0, PROBE_ROWS).some((row) => isRecord(row) && readField(row, field) !== void 0);
229
+ };
230
+ const checkEncoding = (name) => {
231
+ const encoding = spec[name];
232
+ if (encoding === void 0) return;
233
+ if (!isRecord(encoding) || typeof encoding.field !== "string" || encoding.field === "") {
234
+ add("error", "encoding-invalid", name, `\`${name}\` needs a \`field\`.`);
235
+ return;
236
+ }
237
+ if (typeof encoding.type !== "string" || !FIELD_TYPES.includes(encoding.type)) {
238
+ add("error", "encoding-type-invalid", `${name}.type`, `\`${name}.type\` must be one of ${FIELD_TYPES.join(", ")}.`);
239
+ }
240
+ if (!fieldExists(encoding.field)) {
241
+ add("warning", "field-unknown", `${name}.field`, `No row has the field "${encoding.field}".`);
242
+ }
243
+ if ((name === "y" || name === "size") && encoding.type !== void 0 && encoding.type !== "number" && mark && CARTESIAN.has(mark)) {
244
+ add("warning", "encoding-type-unexpected", `${name}.type`, `\`${name}.type\` should be "number" for ${mark} charts.`);
245
+ }
246
+ };
247
+ ["x", "y", "color", "size", "value"].forEach(checkEncoding);
248
+ const series = Array.isArray(spec.series) ? spec.series : void 0;
249
+ const seriesList = series?.filter(isRecord) ?? [];
250
+ const hasSeries = seriesList.length > 0;
251
+ const seriesNeedY = hasSeries && seriesList.every((entry) => typeof entry.field === "string" || Array.isArray(entry.points));
252
+ if (mark) {
253
+ const needX = CARTESIAN.has(mark) || mark === "histogram" || mark === "heatmap" || mark === "pie" || mark === "donut";
254
+ if (needX && !spec.x && !hasOwnSeriesData) add("error", "encoding-x-missing", "x", `A ${mark} chart needs an \`x\` encoding.`);
255
+ const needY = CARTESIAN.has(mark) || mark === "pie" || mark === "donut" || mark === "heatmap";
256
+ if (needY && !spec.y && !seriesNeedY) add("error", "encoding-y-missing", "y", `A ${mark} chart needs a \`y\` encoding or series with a \`field\` or \`points\`.`);
257
+ if (mark === "heatmap" && !spec.value && !spec.color) add("error", "encoding-value-missing", "value", "A heatmap needs a `value` encoding for the cell color.");
258
+ if (mark === "bubble" && !spec.size && !seriesList.some((entry) => Array.isArray(entry.points) && entry.points.some((p) => isRecord(p) && p.size !== void 0))) {
259
+ add("warning", "bubble-size-missing", "size", "A bubble chart should have a `size` encoding; without it every bubble is the same size.");
260
+ }
261
+ if ((mark === "gauge" || mark === "progress-ring") && !(isRecord(spec.gauge) && typeof spec.gauge.value === "number") && !spec.value && !spec.y) {
262
+ add("error", "gauge-value-missing", "gauge.value", "A gauge needs `gauge.value` or a `value` encoding.");
263
+ }
264
+ }
265
+ const seenIds = /* @__PURE__ */ new Set();
266
+ seriesList.forEach((entry, index) => {
267
+ const path = `series[${index}]`;
268
+ if (typeof entry.id !== "string" || entry.id === "") {
269
+ add("error", "series-id-missing", `${path}.id`, "Every series needs an `id`.");
270
+ } else if (seenIds.has(entry.id)) {
271
+ add("error", "series-id-duplicate", `${path}.id`, `Series id "${entry.id}" is used twice.`);
272
+ } else {
273
+ seenIds.add(entry.id);
274
+ }
275
+ if (typeof entry.field !== "string" && !Array.isArray(entry.points) && !spec.y) {
276
+ add("error", "series-data-missing", path, "A series needs a `field` or its own `points`.");
277
+ }
278
+ if (typeof entry.field === "string" && !fieldExists(entry.field)) {
279
+ add("warning", "field-unknown", `${path}.field`, `No row has the field "${entry.field}".`);
280
+ }
281
+ if (entry.axis === "y2" && spec.dualAxis !== true) {
282
+ add("error", "series-axis-needs-dual-axis", `${path}.axis`, "A second value axis needs `dualAxis: true`, which adds a visible warning to the chart.");
283
+ }
284
+ if (entry.mark !== void 0 && mark !== "combo") {
285
+ add("warning", "series-mark-ignored", `${path}.mark`, "`series.mark` only applies when the chart type is `combo`.");
286
+ }
287
+ });
288
+ if (spec.dualAxis === true && !seriesList.some((entry) => entry.axis === "y2")) {
289
+ add("warning", "dual-axis-unused", "dualAxis", '`dualAxis` is set but no series uses `axis: "y2"`.');
290
+ }
291
+ if (mark === "combo" && hasSeries) {
292
+ const marks = new Set(seriesList.map((entry) => typeof entry.mark === "string" ? entry.mark : "bar"));
293
+ if (marks.size < 2) add("warning", "combo-single-mark", "series", "A combo chart mixes marks, for example `bar` and `line`; all series use the same one.");
294
+ }
295
+ let colorGroups = 0;
296
+ if (rows && isRecord(spec.color) && typeof spec.color.field === "string") {
297
+ const field = spec.color.field;
298
+ colorGroups = new Set(rows.slice(0, 5e3).map((row) => isRecord(row) ? String(readField(row, field)) : "")).size;
299
+ }
300
+ const seriesCount = hasSeries ? seriesList.length : colorGroups;
301
+ if (seriesCount > MAX_SERIES && mark !== "pie" && mark !== "donut" && mark !== "heatmap") {
302
+ add("warning", "too-many-series", hasSeries ? "series" : "color", `${seriesCount} series; charts use at most ${MAX_SERIES} hues. Group the rest as "Other" (a top-n transform does this).`);
303
+ }
304
+ if (spec.scales !== void 0 && !isRecord(spec.scales)) add("error", "scales-invalid", "scales", "`scales` must be an object.");
305
+ for (const axis of ["x", "y", "y2"]) {
306
+ const scale = isRecord(spec.scales) ? spec.scales[axis] : void 0;
307
+ if (scale === void 0) continue;
308
+ const path = `scales.${axis}`;
309
+ if (!isRecord(scale)) {
310
+ add("error", "scale-invalid", path, "A scale must be an object.");
311
+ continue;
312
+ }
313
+ if (scale.type !== void 0 && (typeof scale.type !== "string" || !SCALE_TYPES.includes(scale.type))) {
314
+ add("error", "scale-type-invalid", `${path}.type`, `Scale type must be one of ${SCALE_TYPES.join(", ")}.`);
315
+ }
316
+ if ((axis === "y" || axis === "y2") && (scale.type === "band" || scale.type === "ordinal" || scale.type === "time")) {
317
+ add("error", "scale-type-mismatch", `${path}.type`, `The value axis needs a quantitative scale (linear, log or sqrt), not ${String(scale.type)}.`);
318
+ }
319
+ if (scale.domain !== void 0) {
320
+ const quantitative = scale.type === void 0 || ["linear", "log", "sqrt", "time"].includes(String(scale.type));
321
+ if (!Array.isArray(scale.domain) || quantitative && scale.domain.length !== 2) {
322
+ add("error", "scale-domain-invalid", `${path}.domain`, "A quantitative scale domain is `[min, max]`.");
323
+ }
324
+ }
325
+ if (scale.padding !== void 0 && (typeof scale.padding !== "number" || scale.padding < 0 || scale.padding > 0.9)) {
326
+ add("error", "scale-padding-range", `${path}.padding`, "Band padding must be between 0 and 0.9.");
327
+ }
328
+ if (scale.type === "log" && rows) {
329
+ const field = axis === "y" ? spec.y?.field : void 0;
330
+ if (field && rows.slice(0, 5e3).some((row) => {
331
+ const value = toNumber(isRecord(row) ? readField(row, field) : void 0);
332
+ return value !== null && value <= 0;
333
+ })) {
334
+ add("error", "scale-log-nonpositive", path, "A log scale cannot show zero or negative values.");
335
+ }
336
+ }
337
+ if (axis === "y" && scale.type === void 0 && Array.isArray(scale.domain) && typeof scale.domain[0] === "number" && scale.domain[0] > 0 && (mark === "bar" || mark === "lollipop" || mark === "histogram" || mark === "area")) {
338
+ add("warning", "bar-baseline", `${path}.domain`, "Bars and areas should start at zero; a truncated baseline exaggerates differences.");
339
+ }
340
+ }
341
+ if (isRecord(spec.scales) && isRecord(spec.scales.y) && spec.scales.y.type === "log" && (mark === "bar" || mark === "area" || mark === "histogram")) {
342
+ add("warning", "bar-baseline", "scales.y.type", "Bars and areas on a log scale have no zero baseline.");
343
+ }
344
+ if (spec.stack !== void 0) {
345
+ if (typeof spec.stack !== "string" || !STACK_MODES.includes(spec.stack)) {
346
+ add("error", "stack-invalid", "stack", `\`stack\` must be one of ${STACK_MODES.join(", ")}.`);
347
+ } else if (spec.stack !== "none" && mark && !["bar", "area", "combo"].includes(mark)) {
348
+ add("warning", "stack-unsupported", "stack", `Stacking applies to bar, area and combo charts; ${mark} ignores it.`);
349
+ }
350
+ }
351
+ if (spec.orientation !== void 0 && spec.orientation !== "vertical" && spec.orientation !== "horizontal") {
352
+ add("error", "orientation-invalid", "orientation", "`orientation` must be vertical or horizontal.");
353
+ }
354
+ if (spec.orientation === "horizontal" && mark && !["bar", "lollipop", "waterfall"].includes(mark)) {
355
+ add("warning", "orientation-unsupported", "orientation", `Horizontal orientation applies to bar, lollipop and waterfall charts; ${mark} ignores it.`);
356
+ }
357
+ if (isRecord(spec.axes)) {
358
+ for (const axis of ["x", "y", "y2"]) {
359
+ const config = spec.axes[axis];
360
+ if (!isRecord(config)) continue;
361
+ if (config.ticks !== void 0 && (typeof config.ticks !== "number" || config.ticks < 1)) {
362
+ add("error", "axis-ticks-invalid", `axes.${axis}.ticks`, "Tick count must be at least 1.");
363
+ }
364
+ if (config.labelAngle !== void 0 && config.labelAngle !== "auto" && (typeof config.labelAngle !== "number" || config.labelAngle < -90 || config.labelAngle > 90)) {
365
+ add("error", "axis-label-angle", `axes.${axis}.labelAngle`, "Label angle is `auto` or a number from -90 to 90.");
366
+ }
367
+ }
368
+ }
369
+ const annotations = Array.isArray(spec.annotations) ? spec.annotations : [];
370
+ annotations.forEach((annotation, index) => {
371
+ const path = `annotations[${index}]`;
372
+ if (!isRecord(annotation) || typeof annotation.type !== "string") {
373
+ add("error", "annotation-invalid", path, "An annotation needs a `type`.");
374
+ return;
375
+ }
376
+ const label = annotation.type === "text" ? annotation.text : annotation.label;
377
+ if (typeof label !== "string" || label.trim() === "") {
378
+ add("error", "annotation-label-missing", path, "Targets, limits and forecasts are always labeled marks: add a label.");
379
+ }
380
+ if (annotation.type === "reference-line" && annotation.x === void 0 && annotation.y === void 0) {
381
+ add("error", "annotation-position-missing", path, "A reference line needs `x` or `y`.");
382
+ }
383
+ if (annotation.type === "reference-band" && (annotation.axis !== "x" && annotation.axis !== "y")) {
384
+ add("error", "annotation-axis-invalid", `${path}.axis`, 'A reference band needs `axis: "x"` or `"y"`.');
385
+ }
386
+ if (!["reference-line", "reference-band", "text", "forecast", "point"].includes(annotation.type)) {
387
+ add("error", "annotation-type-invalid", `${path}.type`, `Unknown annotation type "${annotation.type}".`);
388
+ }
389
+ });
390
+ const thresholds = Array.isArray(spec.thresholds) ? spec.thresholds : [];
391
+ thresholds.forEach((threshold, index) => {
392
+ const path = `thresholds[${index}]`;
393
+ if (!isRecord(threshold)) {
394
+ add("error", "threshold-invalid", path, "A threshold must be an object.");
395
+ return;
396
+ }
397
+ if (typeof threshold.label !== "string" || threshold.label.trim() === "") {
398
+ add("error", "threshold-label-missing", `${path}.label`, "Thresholds are always labeled.");
399
+ }
400
+ if (threshold.value === void 0 || typeof threshold.value !== "number" && typeof threshold.value !== "string") {
401
+ add("error", "threshold-value-invalid", `${path}.value`, "A threshold needs a numeric `value`.");
402
+ }
403
+ if (threshold.breach !== void 0 && threshold.breach !== "above" && threshold.breach !== "below") {
404
+ add("error", "threshold-breach-invalid", `${path}.breach`, "`breach` is `above` or `below`.");
405
+ }
406
+ if (threshold.axis === "y2" && spec.dualAxis !== true) {
407
+ add("error", "series-axis-needs-dual-axis", `${path}.axis`, "A threshold on the second axis needs `dualAxis: true`.");
408
+ }
409
+ });
410
+ const transforms = Array.isArray(spec.transforms) ? spec.transforms : [];
411
+ transforms.forEach((transform, index) => {
412
+ const path = `transforms[${index}]`;
413
+ if (!isRecord(transform) || typeof transform.type !== "string") {
414
+ add("error", "transform-invalid", path, "A transform needs a `type`.");
415
+ return;
416
+ }
417
+ const known = ["aggregate", "bin", "stack", "percent-of-total", "sort", "top-n", "rolling-average", "cumulative-sum", "period-change", "downsample"];
418
+ if (!known.includes(transform.type)) {
419
+ add("error", "transform-invalid", `${path}.type`, `Unknown transform "${transform.type}".`);
420
+ return;
421
+ }
422
+ const positiveInt = (key, min) => {
423
+ const value = transform[key];
424
+ if (typeof value !== "number" || !Number.isFinite(value) || value < min) {
425
+ add("error", "transform-parameter-invalid", `${path}.${key}`, `\`${key}\` must be a number of at least ${min}.`);
426
+ }
427
+ };
428
+ if (transform.type === "aggregate") {
429
+ if (!Array.isArray(transform.groupBy) || transform.groupBy.length === 0) add("error", "transform-parameter-invalid", `${path}.groupBy`, "`groupBy` needs at least one field.");
430
+ if (!["sum", "mean", "min", "max", "count"].includes(String(transform.op))) add("error", "transform-parameter-invalid", `${path}.op`, "`op` must be sum, mean, min, max or count.");
431
+ if (transform.op !== "count" && typeof transform.field !== "string") add("error", "transform-parameter-invalid", `${path}.field`, "Only `count` works without a `field`.");
432
+ }
433
+ if (transform.type === "rolling-average") positiveInt("window", 1);
434
+ if (transform.type === "top-n") positiveInt("n", 1);
435
+ if (transform.type === "downsample") positiveInt("threshold", 3);
436
+ if (transform.type === "period-change" && transform.periods !== void 0) positiveInt("periods", 1);
437
+ if (transform.type === "bin" && transform.maxBins !== void 0) positiveInt("maxBins", 1);
438
+ if (typeof transform.field === "string" && transform.type !== "aggregate" && !fieldExists(transform.field)) {
439
+ add("warning", "field-unknown", `${path}.field`, `No row has the field "${transform.field}".`);
440
+ }
441
+ });
442
+ if (isRecord(spec.highlight)) {
443
+ if (typeof spec.highlight.where !== "string") {
444
+ add("error", "highlight-invalid", "highlight.where", "`highlight.where` must be an expression string.");
445
+ } else {
446
+ const problem = checkWhere(spec.highlight.where);
447
+ if (problem) add("error", "highlight-expression-invalid", "highlight.where", `Highlight expression does not parse: ${problem}.`);
448
+ }
449
+ }
450
+ if (isRecord(spec.gauge)) {
451
+ const { min, max, value } = spec.gauge;
452
+ if (min !== void 0 && max !== void 0 && !(max > min)) add("error", "gauge-range-invalid", "gauge", "`gauge.max` must be greater than `gauge.min`.");
453
+ if (typeof value === "number" && min !== void 0 && max !== void 0 && (value < min || value > max)) {
454
+ add("warning", "gauge-value-out-of-range", "gauge.value", "The gauge value is outside its range; the arc is clamped.");
455
+ }
456
+ }
457
+ if (isRecord(spec.donut)) {
458
+ const radius = spec.donut.innerRadius;
459
+ if (radius !== void 0 && (typeof radius !== "number" || radius < 0 || radius > 0.9)) {
460
+ add("error", "donut-inner-radius-range", "donut.innerRadius", "`innerRadius` is a fraction from 0 to 0.9.");
461
+ }
462
+ if (typeof spec.donut.maxSlices === "number" && spec.donut.maxSlices > 5) {
463
+ add("warning", "too-many-slices", "donut.maxSlices", "Donuts read best with at most five slices.");
464
+ }
465
+ }
466
+ if ((mark === "pie" || mark === "donut") && rows && isRecord(spec.y) && typeof spec.y.field === "string") {
467
+ const field = spec.y.field;
468
+ if (rows.slice(0, 5e3).some((row) => (toNumber(isRecord(row) ? readField(row, field) : void 0) ?? 0) < 0)) {
469
+ add("error", "pie-negative", "y", "A part-to-whole chart cannot show negative values.");
470
+ }
471
+ }
472
+ if (spec.renderer !== void 0 && spec.renderer !== "svg" && spec.renderer !== "canvas" && spec.renderer !== "auto") {
473
+ add("error", "renderer-invalid", "renderer", "`renderer` must be svg, canvas or auto.");
474
+ }
475
+ if (spec.direction !== void 0 && !["ltr", "rtl", "auto"].includes(String(spec.direction))) {
476
+ add("error", "direction-invalid", "direction", "`direction` must be ltr, rtl or auto.");
477
+ }
478
+ if (isRecord(spec.interactions)) {
479
+ for (const name of ["brush", "zoom", "drilldown"]) {
480
+ if (spec.interactions[name] === true) {
481
+ add("warning", "interaction-unsupported", `interactions.${name}`, `\`interactions.${name}\` is part of the spec but this renderer does not implement it; it is ignored.`);
482
+ }
483
+ }
484
+ }
485
+ if (spec.renderer === "svg" && isRecord(data)) {
486
+ const count = rows ? resolveRows(spec, options.datasets)?.length ?? 0 : 0;
487
+ if (count > SVG_POINT_LIMIT) {
488
+ add("warning", "svg-point-limit", "renderer", `${count} rows is above the SVG comfort limit of ${SVG_POINT_LIMIT}; consider \`renderer: "auto"\` or a downsample transform.`);
489
+ }
490
+ }
491
+ return issues;
492
+ }
493
+ function isValidChartSpec(input, options = {}) {
494
+ return !validateChartSpec(input, options).some((issue) => issue.severity === "error");
495
+ }
496
+ export {
497
+ AXIS_FONT_SIZE,
498
+ DEFAULT_LOCALE,
499
+ ExpressionError,
500
+ MAX_HUES,
501
+ SVG_POINT_LIMIT,
502
+ TICK_PADDING,
503
+ TICK_SIZE,
504
+ annotationTone,
505
+ applyTransforms,
506
+ buildChartModel,
507
+ capitalize,
508
+ categoricalTones,
509
+ chartToCsv,
510
+ chartToTable,
511
+ chartTypes,
512
+ checkWhere,
513
+ compileWhere,
514
+ computeAxisMargins,
515
+ correlation,
516
+ countPoints,
517
+ createBandScale,
518
+ createContinuousScale,
519
+ createDateFormatter,
520
+ createFormatters,
521
+ createNumberFormatter,
522
+ createPointScale,
523
+ createTimeTickFormatter,
524
+ cumulativeSum,
525
+ dashArrays,
526
+ dashStyles,
527
+ defaultMessages,
528
+ defineChartSpec,
529
+ describeChart,
530
+ describeModel,
531
+ describeTarget,
532
+ divergingColor,
533
+ dodgePositions,
534
+ effectiveStack,
535
+ escapeCsvField,
536
+ estimateTextWidth,
537
+ evaluateWhere,
538
+ extent,
539
+ fitTickLabels,
540
+ forecastLinear,
541
+ formatList,
542
+ formatMessage,
543
+ formatShare,
544
+ fractionDigitsForStep,
545
+ histogramBins,
546
+ hitTest,
547
+ isNavKey,
548
+ isOpenMarker,
549
+ isRtlLocale,
550
+ isSupportedMark,
551
+ isValidChartSpec,
552
+ layoutChart,
553
+ linearRegression,
554
+ lttb,
555
+ lttbIndices,
556
+ markerPath,
557
+ markerShapes,
558
+ mean,
559
+ median,
560
+ minMax,
561
+ minMaxIndices,
562
+ modelToTable,
563
+ movingAverage,
564
+ navKeys,
565
+ navigate,
566
+ niceTicks,
567
+ patternIndex,
568
+ percentChange,
569
+ periodChange,
570
+ pointInfo,
571
+ quantile,
572
+ rampGradient,
573
+ readField,
574
+ regressionBand,
575
+ resolveMessages,
576
+ resolveRenderer,
577
+ resolveRows,
578
+ sequentialColor,
579
+ seriesStyle,
580
+ stackValues,
581
+ standardDeviation,
582
+ sum,
583
+ supportedMarks,
584
+ tableToCsv,
585
+ targetById,
586
+ themeColorsFromTokens,
587
+ toCategory,
588
+ toEChartsOption,
589
+ toNumber,
590
+ toTime,
591
+ toneCssVar,
592
+ toneTokenPath,
593
+ toneVarName,
594
+ tooltipFor,
595
+ trendDirection,
596
+ truncateLabel,
597
+ usesBandX,
598
+ validateChartSpec,
599
+ valueDomain,
600
+ variance
601
+ };
92
602
  //# sourceMappingURL=index.js.map