sk-chart-duo 0.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.cjs ADDED
@@ -0,0 +1,1310 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ChartBase: () => ChartBase,
24
+ FoldBarChart: () => FoldBarChart,
25
+ VERSION: () => VERSION,
26
+ createBandScale: () => createBandScale,
27
+ createLinearScale: () => createLinearScale,
28
+ createPowerScale: () => createPowerScale,
29
+ defaultTooltipFormatter: () => defaultTooltipFormatter,
30
+ defaultValueFormat: () => defaultValueFormat,
31
+ getTheme: () => getTheme,
32
+ isThemePack: () => isThemePack,
33
+ niceScale: () => niceScale,
34
+ registerTheme: () => registerTheme
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+
38
+ // src/core/exporter.ts
39
+ function svgToMarkup(svg) {
40
+ const clone = svg.cloneNode(true);
41
+ const size = viewBoxSize(svg);
42
+ if (size) {
43
+ clone.setAttribute("width", String(size.width));
44
+ clone.setAttribute("height", String(size.height));
45
+ }
46
+ clone.setAttribute("xmlns", "http://www.w3.org/2000/svg");
47
+ return new XMLSerializer().serializeToString(clone);
48
+ }
49
+ function svgToDataUrl(svg) {
50
+ return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgToMarkup(svg))}`;
51
+ }
52
+ async function svgToPngDataUrl(svg, scale = 2, background) {
53
+ const size = viewBoxSize(svg) ?? { width: svg.clientWidth, height: svg.clientHeight };
54
+ if (!size.width || !size.height) throw new Error("sk-chart: cannot measure SVG for raster export");
55
+ const canvas = document.createElement("canvas");
56
+ canvas.width = Math.round(size.width * scale);
57
+ canvas.height = Math.round(size.height * scale);
58
+ const ctx = canvas.getContext("2d");
59
+ if (!ctx) throw new Error("sk-chart: canvas 2d context unavailable");
60
+ const url = URL.createObjectURL(new Blob([svgToMarkup(svg)], { type: "image/svg+xml;charset=utf-8" }));
61
+ try {
62
+ const image = await loadImage(url);
63
+ if (background) {
64
+ ctx.fillStyle = background;
65
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
66
+ }
67
+ ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
68
+ return canvas.toDataURL("image/png");
69
+ } finally {
70
+ URL.revokeObjectURL(url);
71
+ }
72
+ }
73
+ function viewBoxSize(svg) {
74
+ const [, , w, h] = (svg.getAttribute("viewBox") ?? "").split(/[\s,]+/).map(Number);
75
+ return Number.isFinite(w) && Number.isFinite(h) && w > 0 && h > 0 ? { width: w, height: h } : null;
76
+ }
77
+ function loadImage(url) {
78
+ return new Promise((resolve, reject) => {
79
+ const image = new Image();
80
+ image.onload = () => resolve(image);
81
+ image.onerror = () => reject(new Error("sk-chart: failed to rasterize SVG"));
82
+ image.src = url;
83
+ });
84
+ }
85
+ function triggerDownload(href, filename) {
86
+ const anchor = document.createElement("a");
87
+ anchor.href = href;
88
+ anchor.download = filename;
89
+ document.body.appendChild(anchor);
90
+ anchor.click();
91
+ anchor.remove();
92
+ }
93
+
94
+ // src/core/chart-base.ts
95
+ var ChartBase = class {
96
+ constructor(container, config) {
97
+ this.svg = null;
98
+ this.handlers = /* @__PURE__ */ new Map();
99
+ this.destroyed = false;
100
+ const element = typeof container === "string" ? document.querySelector(container) : container;
101
+ if (!element) {
102
+ throw new Error(`sk-chart: container "${String(container)}" not found`);
103
+ }
104
+ this.container = element;
105
+ this.config = config;
106
+ }
107
+ get isDestroyed() {
108
+ return this.destroyed;
109
+ }
110
+ on(event, handler) {
111
+ if (this.destroyed) return this;
112
+ const set = this.handlers.get(event) ?? /* @__PURE__ */ new Set();
113
+ this.handlers.set(event, set);
114
+ set.add(handler);
115
+ return this;
116
+ }
117
+ off(event, handler) {
118
+ this.handlers.get(event)?.delete(handler);
119
+ return this;
120
+ }
121
+ emit(event, payload) {
122
+ this.handlers.get(event)?.forEach((handler) => handler(payload));
123
+ }
124
+ /** Merges the partial config and rebuilds the chart. */
125
+ update(config) {
126
+ if (this.destroyed) return;
127
+ this.config = { ...this.config, ...config };
128
+ this.renderChart();
129
+ }
130
+ /** Changes the viewBox design space and rebuilds (userSpaceOnUse coordinates depend on it). */
131
+ resize(width, height) {
132
+ this.update({ width, height });
133
+ }
134
+ /** Standalone SVG markup (embedded style + defs included), renderable as-is. */
135
+ toSVGString() {
136
+ return svgToMarkup(this.requireSvg());
137
+ }
138
+ /** Data URL of the current chart; PNG is rasterized through canvas at `scale` (default 2x). */
139
+ async getDataURL(options = {}) {
140
+ const { type = "png", scale = 2, background } = options;
141
+ const svg = this.requireSvg();
142
+ return type === "svg" ? svgToDataUrl(svg) : svgToPngDataUrl(svg, scale, background);
143
+ }
144
+ /** Triggers a browser download of the chart, PNG at 2x by default. */
145
+ async download(options = {}) {
146
+ const { filename = "sk-chart", type = "png", scale, background } = options;
147
+ const url = await this.getDataURL({ type, scale, background });
148
+ triggerDownload(url, `${filename}.${type}`);
149
+ }
150
+ requireSvg() {
151
+ if (this.destroyed || !this.svg) throw new Error("sk-chart: chart is not rendered");
152
+ return this.svg;
153
+ }
154
+ destroy() {
155
+ if (this.destroyed) return;
156
+ this.destroyed = true;
157
+ this.onDestroy();
158
+ this.svg?.remove();
159
+ this.svg = null;
160
+ this.handlers.clear();
161
+ }
162
+ /** Subclass teardown hook, called before the SVG is removed. */
163
+ onDestroy() {
164
+ }
165
+ };
166
+
167
+ // src/core/svg-renderer.ts
168
+ var SVG_NS = "http://www.w3.org/2000/svg";
169
+ function createSvgElement(tag, attrs = {}) {
170
+ const node = document.createElementNS(SVG_NS, tag);
171
+ for (const [key, value] of Object.entries(attrs)) {
172
+ node.setAttribute(key, String(value));
173
+ }
174
+ return node;
175
+ }
176
+ function addStops(gradient, stops) {
177
+ for (const [offset, color] of stops) {
178
+ gradient.appendChild(createSvgElement("stop", { offset, "stop-color": color }));
179
+ }
180
+ return gradient;
181
+ }
182
+ var uidCounter = 0;
183
+ function createUid(prefix = "skc") {
184
+ uidCounter += 1;
185
+ return `${prefix}${uidCounter.toString(36)}${Math.random().toString(36).slice(2, 7)}`;
186
+ }
187
+ function scopedId(uid, name) {
188
+ return `${uid}-${name}`;
189
+ }
190
+ function urlRef(uid, name) {
191
+ return `url(#${scopedId(uid, name)})`;
192
+ }
193
+
194
+ // src/charts/fold-bar/geometry.ts
195
+ function computeLayout(input) {
196
+ const left = input.padding.left;
197
+ const right = input.width - input.padding.right;
198
+ const top = input.padding.top;
199
+ const bottom = input.height - input.padding.bottom;
200
+ const count = input.values.length;
201
+ const colWidth = count > 0 ? (right - left) / count : 0;
202
+ const barWidth = colWidth - input.foldRun - input.barGap;
203
+ const stairBase = bottom - input.stairBottomOffset;
204
+ const stairTop = top + input.stairTopOffset;
205
+ const maxValue = count > 0 ? Math.max(...input.values) : 0;
206
+ const domainMax = Math.max(input.domainMax, maxValue);
207
+ const span = stairBase - stairTop;
208
+ const barTopOf = (value) => {
209
+ if (domainMax <= 0) return stairBase;
210
+ const t = Math.pow(Math.max(0, value / domainMax), input.exponent);
211
+ return stairBase - t * span;
212
+ };
213
+ return {
214
+ plot: { left, right, top, bottom, width: right - left, height: bottom - top },
215
+ count,
216
+ colWidth,
217
+ barWidth,
218
+ stairBase,
219
+ stairTop,
220
+ maxValue,
221
+ domainMax,
222
+ barTopOf
223
+ };
224
+ }
225
+ function barGeometry(layout, index, value) {
226
+ const colStart = layout.plot.left + index * layout.colWidth;
227
+ const x = colStart + 1;
228
+ const y = layout.barTopOf(value);
229
+ return {
230
+ colStart,
231
+ x,
232
+ y,
233
+ width: layout.barWidth,
234
+ height: layout.plot.bottom - y,
235
+ centerX: x + layout.barWidth / 2
236
+ };
237
+ }
238
+ function flapGeometry(layout, index, values) {
239
+ if (index >= values.length - 1) return null;
240
+ const x0 = layout.plot.left + index * layout.colWidth + 1 + layout.barWidth;
241
+ const x1 = layout.plot.left + (index + 1) * layout.colWidth + 2;
242
+ const y0 = layout.barTopOf(values[index]);
243
+ const y1 = layout.barTopOf(values[index + 1]);
244
+ const bottom = layout.plot.bottom;
245
+ return {
246
+ points: `${x0},${y0} ${x1},${y1} ${x1},${bottom} ${x0},${bottom}`,
247
+ crease: { x1: x0, y1: y0, x2: x1, y2: y1 },
248
+ gradientY: [y0, bottom]
249
+ };
250
+ }
251
+ function pillGeometry(centerX, barTop, pill) {
252
+ return {
253
+ x: centerX - pill.width / 2,
254
+ y: barTop + pill.offsetY,
255
+ width: pill.width,
256
+ height: pill.height,
257
+ rx: pill.rx,
258
+ shadowX: centerX - pill.shadow.width / 2,
259
+ shadowY: barTop + pill.shadow.offsetY,
260
+ shadowWidth: pill.shadow.width,
261
+ shadowHeight: pill.shadow.height,
262
+ shadowRx: pill.shadow.rx
263
+ };
264
+ }
265
+ function gridLineXs(layout) {
266
+ return Array.from({ length: layout.count + 1 }, (_, i) => layout.plot.left + i * layout.colWidth);
267
+ }
268
+ function tooltipPlacement(layout, bar, tipWidth, options) {
269
+ const rawX = bar.x + bar.width * options.anchorRatio;
270
+ const y = Math.max(bar.y + options.offsetY, options.minY);
271
+ const { left, right } = layout.plot;
272
+ const x = Math.min(Math.max(rawX, left), Math.max(left, right - tipWidth));
273
+ return { x, y };
274
+ }
275
+ function hitRect(layout, index) {
276
+ return {
277
+ x: layout.plot.left + index * layout.colWidth,
278
+ y: layout.plot.top,
279
+ width: layout.colWidth,
280
+ height: layout.plot.height
281
+ };
282
+ }
283
+ function washRect(bar, washTop) {
284
+ return {
285
+ x: bar.x,
286
+ y: washTop,
287
+ width: bar.width,
288
+ height: Math.max(0, bar.y - washTop)
289
+ };
290
+ }
291
+
292
+ // src/theme/default-theme.ts
293
+ var BAR_GRADIENT_ACTIVE = [
294
+ [0, "#2A2FC8"],
295
+ [0.07, "#2A55E2"],
296
+ [0.22, "#2C62EC"],
297
+ [0.42, "#3184F7"],
298
+ [0.62, "#77ABF5"],
299
+ [0.82, "#C5DAF5"],
300
+ [1, "#EAF1FA"]
301
+ ];
302
+ var BAR_GRADIENT_NORMAL = [
303
+ [0, "#2B3AD6"],
304
+ [0.07, "#2C60EC"],
305
+ [0.22, "#2F76F2"],
306
+ [0.42, "#4C93F8"],
307
+ [0.62, "#87B5F6"],
308
+ [0.82, "#CDDDF5"],
309
+ [1, "#EBF2FA"]
310
+ ];
311
+ var DEFAULT_STYLE = {
312
+ stripePattern: {
313
+ enabled: true,
314
+ size: 7.1,
315
+ lineWidth: 2.1,
316
+ lineColor: "#fff",
317
+ lineOpacity: 0.93,
318
+ rotation: 45
319
+ },
320
+ barGradientActive: BAR_GRADIENT_ACTIVE,
321
+ barGradientNormal: BAR_GRADIENT_NORMAL,
322
+ foldGradient: [
323
+ [0, "#8FB2F8"],
324
+ [0.14, "#A6C2FA"],
325
+ [0.38, "#C6DAFA"],
326
+ [0.68, "#E6EDFB"],
327
+ [1, "#F4F7FC"]
328
+ ],
329
+ creaseGradient: [
330
+ [0, "rgba(255,255,255,0)"],
331
+ [0.18, "rgba(255,255,255,.62)"],
332
+ [0.55, "rgba(255,255,255,.18)"],
333
+ [1, "rgba(255,255,255,0)"]
334
+ ],
335
+ washTop: 115,
336
+ washGradient: [
337
+ [0, "rgba(222,240,254,0)"],
338
+ [0.55, "rgba(222,240,254,.55)"],
339
+ [1, "rgba(214,238,254,1)"]
340
+ ],
341
+ washEnabled: true,
342
+ pill: {
343
+ enabled: true,
344
+ width: 26,
345
+ height: 7,
346
+ rx: 3.5,
347
+ offsetY: -13,
348
+ gradient: [
349
+ [0, "#EFF7FF"],
350
+ [0.45, "#BBD9FF"],
351
+ [1, "#7FB2FF"]
352
+ ],
353
+ shadow: { width: 24, height: 1.4, rx: 0.7, offsetY: -7.5, color: "#4A78D8", opacity: 0.75 }
354
+ },
355
+ shadow: { dx: 0, dy: 3, blur: 5, color: "#1B3560", opacity: 0.14 },
356
+ fadeMask: { start: 344, end: 360 },
357
+ fadeEnabled: true,
358
+ labelY: 85,
359
+ numberY: 117,
360
+ labelXOffset: 0
361
+ };
362
+ var DEFAULT_THEME_TOKENS = {
363
+ fontFamily: '-apple-system,BlinkMacSystemFont,"Segoe UI","Inter","Helvetica Neue","PingFang SC","Microsoft YaHei",Arial,sans-serif',
364
+ title: { fill: "#14161F", fontSize: 21, fontWeight: 700, letterSpacing: "-0.2px" },
365
+ axis: { fill: "#9AA1AC", fontSize: 11, fontWeight: 500 },
366
+ label: { fill: "#C4CAD3", activeFill: "#242A36", fontSize: 11.5, fontWeight: 500 },
367
+ number: {
368
+ fill: "#C9CED6",
369
+ activeFill: "#15181F",
370
+ fontSize: 27,
371
+ fontWeight: 600,
372
+ letterSpacing: "-0.6px"
373
+ },
374
+ grid: { stroke: "#E9EBEF", strokeWidth: 1 },
375
+ tooltip: {
376
+ bg: "#fff",
377
+ stroke: "rgba(20,35,70,.07)",
378
+ fill: "#79839A",
379
+ strongFill: "#171B26",
380
+ softFill: "#C3C8D4",
381
+ fontSize: 10.5
382
+ },
383
+ transition: { state: ".32s ease", tip: "transform .34s cubic-bezier(.22,.61,.36,1)" }
384
+ };
385
+ function mergeStyleConfigs(base, top) {
386
+ if (!base) return top;
387
+ if (!top) return base;
388
+ return {
389
+ ...base,
390
+ ...top,
391
+ stripePattern: { ...base.stripePattern, ...top.stripePattern },
392
+ barGradient: { ...base.barGradient, ...top.barGradient },
393
+ pill: {
394
+ ...base.pill,
395
+ ...top.pill,
396
+ shadow: { ...base.pill?.shadow, ...top.pill?.shadow }
397
+ },
398
+ shadow: { ...base.shadow, ...top.shadow },
399
+ fadeMask: { ...base.fadeMask, ...top.fadeMask }
400
+ };
401
+ }
402
+ function resolveStyle(style, base) {
403
+ const merged = mergeStyleConfigs(base, style);
404
+ if (!merged) return DEFAULT_STYLE;
405
+ return {
406
+ stripePattern: { ...DEFAULT_STYLE.stripePattern, ...merged.stripePattern },
407
+ barGradientActive: merged.barGradient?.active ?? DEFAULT_STYLE.barGradientActive,
408
+ barGradientNormal: merged.barGradient?.normal ?? DEFAULT_STYLE.barGradientNormal,
409
+ foldGradient: merged.foldGradient ?? DEFAULT_STYLE.foldGradient,
410
+ creaseGradient: merged.creaseGradient ?? DEFAULT_STYLE.creaseGradient,
411
+ washTop: merged.washTop ?? DEFAULT_STYLE.washTop,
412
+ washGradient: merged.washGradient ?? DEFAULT_STYLE.washGradient,
413
+ washEnabled: merged.washEnabled ?? true,
414
+ pill: {
415
+ ...DEFAULT_STYLE.pill,
416
+ ...merged.pill,
417
+ gradient: merged.pill?.gradient ?? DEFAULT_STYLE.pill.gradient,
418
+ shadow: { ...DEFAULT_STYLE.pill.shadow, ...merged.pill?.shadow }
419
+ },
420
+ shadow: { ...DEFAULT_STYLE.shadow, ...merged.shadow },
421
+ fadeMask: { ...DEFAULT_STYLE.fadeMask, ...merged.fadeMask },
422
+ fadeEnabled: merged.fadeMask?.enabled ?? true,
423
+ labelY: merged.labelY ?? DEFAULT_STYLE.labelY,
424
+ numberY: merged.numberY ?? DEFAULT_STYLE.numberY,
425
+ labelXOffset: merged.labelXOffset ?? DEFAULT_STYLE.labelXOffset
426
+ };
427
+ }
428
+ function mergeTokens(base, top) {
429
+ if (!base) return top;
430
+ if (!top) return base;
431
+ return {
432
+ fontFamily: top.fontFamily ?? base.fontFamily,
433
+ title: { ...base.title, ...top.title },
434
+ axis: { ...base.axis, ...top.axis },
435
+ label: { ...base.label, ...top.label },
436
+ number: { ...base.number, ...top.number },
437
+ grid: { ...base.grid, ...top.grid },
438
+ tooltip: { ...base.tooltip, ...top.tooltip },
439
+ transition: { ...base.transition, ...top.transition }
440
+ };
441
+ }
442
+ function resolveTokens(theme) {
443
+ if (!theme) return DEFAULT_THEME_TOKENS;
444
+ return {
445
+ fontFamily: theme.fontFamily ?? DEFAULT_THEME_TOKENS.fontFamily,
446
+ title: { ...DEFAULT_THEME_TOKENS.title, ...theme.title },
447
+ axis: { ...DEFAULT_THEME_TOKENS.axis, ...theme.axis },
448
+ label: { ...DEFAULT_THEME_TOKENS.label, ...theme.label },
449
+ number: { ...DEFAULT_THEME_TOKENS.number, ...theme.number },
450
+ grid: { ...DEFAULT_THEME_TOKENS.grid, ...theme.grid },
451
+ tooltip: { ...DEFAULT_THEME_TOKENS.tooltip, ...theme.tooltip },
452
+ transition: { ...DEFAULT_THEME_TOKENS.transition, ...theme.transition }
453
+ };
454
+ }
455
+ function buildScopedCss(uid, tokens) {
456
+ const c = (name) => `.${uid}-${name}`;
457
+ return [
458
+ `${c("root")}{display:block;width:100%;height:auto;font-family:${tokens.fontFamily}}`,
459
+ `${c("title")}{font-size:${tokens.title.fontSize}px;font-weight:${tokens.title.fontWeight};fill:${tokens.title.fill};letter-spacing:${tokens.title.letterSpacing}}`,
460
+ `${c("axis")}{font-size:${tokens.axis.fontSize}px;font-weight:${tokens.axis.fontWeight};fill:${tokens.axis.fill};font-variant-numeric:tabular-nums}`,
461
+ `${c("lbl")}{font-size:${tokens.label.fontSize}px;font-weight:${tokens.label.fontWeight};fill:${tokens.label.fill};transition:fill .3s ease}`,
462
+ `${c("num")}{font-size:${tokens.number.fontSize}px;font-weight:${tokens.number.fontWeight};fill:${tokens.number.fill};letter-spacing:${tokens.number.letterSpacing};font-variant-numeric:tabular-nums;transition:fill .3s ease}`,
463
+ `${c("col")}.${uid}-active ${c("lbl")}{fill:${tokens.label.activeFill}}`,
464
+ `${c("col")}.${uid}-active ${c("num")}{fill:${tokens.number.activeFill}}`,
465
+ `${c("stripes")}{opacity:1;transition:opacity ${tokens.transition.state}}`,
466
+ `${c("col")}.${uid}-active ${c("stripes")}{opacity:0}`,
467
+ `${c("wash")}{opacity:0;transition:opacity ${tokens.transition.state}}`,
468
+ `${c("col")}.${uid}-active ${c("wash")}{opacity:1}`,
469
+ `${c("hit")}{fill:transparent;cursor:pointer}`,
470
+ `${c("tip")}{transition:${tokens.transition.tip};pointer-events:none}`,
471
+ `${c("tip")} text{font-size:${tokens.tooltip.fontSize}px;font-weight:400;fill:${tokens.tooltip.fill};font-variant-numeric:tabular-nums}`,
472
+ `${c("tip")} ${c("b")}{font-weight:700;fill:${tokens.tooltip.strongFill}}`,
473
+ `${c("tip")} ${c("s")}{fill:${tokens.tooltip.softFill}}`,
474
+ `@media (prefers-reduced-motion: reduce){${c("lbl")},${c("num")},${c("stripes")},${c("wash")},${c("tip")}{transition:none}}`
475
+ ].join("\n");
476
+ }
477
+
478
+ // src/theme/presets.ts
479
+ function isThemePack(value) {
480
+ return "style" in value || "tokens" in value || "formats" in value;
481
+ }
482
+ var registry = /* @__PURE__ */ new Map();
483
+ function registerTheme(name, pack) {
484
+ registry.set(name, pack);
485
+ }
486
+ function getTheme(name) {
487
+ return registry.get(name);
488
+ }
489
+ var DARK_STYLE = {
490
+ barGradient: {
491
+ normal: [
492
+ [0, "#5B8CFF"],
493
+ [0.07, "#4A7AF2"],
494
+ [0.22, "#3E68DE"],
495
+ [0.42, "#3255C2"],
496
+ [0.62, "#27408F"],
497
+ [0.82, "#1B2C5E"],
498
+ [1, "#111B3A"]
499
+ ],
500
+ active: [
501
+ [0, "#7BA2FF"],
502
+ [0.07, "#6A93F8"],
503
+ [0.22, "#5A80E8"],
504
+ [0.42, "#4A6ACC"],
505
+ [0.62, "#38529E"],
506
+ [0.82, "#263868"],
507
+ [1, "#172142"]
508
+ ]
509
+ },
510
+ foldGradient: [
511
+ [0, "#3A4E7E"],
512
+ [0.14, "#334572"],
513
+ [0.38, "#2A3A60"],
514
+ [0.68, "#1F2C4C"],
515
+ [1, "#18223C"]
516
+ ],
517
+ creaseGradient: [
518
+ [0, "rgba(255,255,255,0)"],
519
+ [0.18, "rgba(255,255,255,.28)"],
520
+ [0.55, "rgba(255,255,255,.10)"],
521
+ [1, "rgba(255,255,255,0)"]
522
+ ],
523
+ stripePattern: { lineColor: "#6E86B8", lineOpacity: 0.16 },
524
+ washGradient: [
525
+ [0, "rgba(96,146,255,0)"],
526
+ [0.55, "rgba(96,146,255,.24)"],
527
+ [1, "rgba(84,130,250,.48)"]
528
+ ],
529
+ pill: {
530
+ gradient: [
531
+ [0, "#4E669E"],
532
+ [0.45, "#3A507F"],
533
+ [1, "#2A3B61"]
534
+ ],
535
+ shadow: { color: "#000814", opacity: 0.8 }
536
+ },
537
+ shadow: { color: "#000000", opacity: 0.5 }
538
+ };
539
+ var DARK_TOKENS = {
540
+ title: { fill: "#F1F5FC" },
541
+ axis: { fill: "#5D6880" },
542
+ label: { fill: "#4E5871", activeFill: "#F1F5FC" },
543
+ number: { fill: "#566179", activeFill: "#FFFFFF" },
544
+ grid: { stroke: "#222C42" },
545
+ tooltip: {
546
+ bg: "#1A2236",
547
+ stroke: "rgba(255,255,255,.10)",
548
+ fill: "#93A0BC",
549
+ strongFill: "#FFFFFF",
550
+ softFill: "#46536F"
551
+ }
552
+ };
553
+ registerTheme("light", {});
554
+ registerTheme("dark", { style: DARK_STYLE, tokens: DARK_TOKENS });
555
+
556
+ // src/charts/fold-bar/defaults.ts
557
+ var defaultValueFormat = (value) => `${value.toFixed(1)}k`;
558
+ function defaultTooltipFormatter(datum) {
559
+ return [
560
+ { text: `${datum.label} `, tone: "n" },
561
+ { text: defaultValueFormat(datum.value), tone: "b" }
562
+ ];
563
+ }
564
+ function cleanNumber(value) {
565
+ return Number(value.toPrecision(12));
566
+ }
567
+ function niceScale(maxValue, targetCount = 5) {
568
+ if (!Number.isFinite(maxValue) || maxValue <= 0) return { axisMax: 0, step: 0, ticks: [] };
569
+ const rawStep = maxValue / targetCount;
570
+ const power = Math.floor(Math.log10(rawStep));
571
+ const base = Math.pow(10, power);
572
+ const error = rawStep / base;
573
+ const multiplier = error >= Math.sqrt(50) ? 10 : error >= Math.sqrt(10) ? 5 : error >= Math.SQRT2 ? 2 : 1;
574
+ const step = cleanNumber(multiplier * base);
575
+ const axisMax = cleanNumber(Math.ceil(maxValue / step - 1e-9) * step);
576
+ const intervalCount = Math.round(axisMax / step);
577
+ const ascending = Array.from({ length: intervalCount }, (_, i) => cleanNumber((i + 1) * step));
578
+ return { axisMax, step, ticks: ascending.slice(-targetCount).reverse() };
579
+ }
580
+ function resolveThemeRef(theme) {
581
+ if (!theme) return {};
582
+ if (typeof theme === "string") {
583
+ const pack = getTheme(theme);
584
+ if (!pack) console.warn(`sk-chart: unknown theme "${theme}"; falling back to defaults`);
585
+ return { pack: pack ?? {} };
586
+ }
587
+ if (isThemePack(theme)) return { pack: theme };
588
+ return { userTokens: theme };
589
+ }
590
+ function resolveOptions(config) {
591
+ const width = config.width ?? 860;
592
+ const height = config.height ?? 386;
593
+ const yField = config.yField ?? "value";
594
+ const maxValue = config.data.reduce((max, d) => Math.max(max, Number(d[yField]) || 0), 0);
595
+ const rawExponent = config.scale?.exponent ?? 1;
596
+ let exponent = rawExponent;
597
+ if (!Number.isFinite(exponent) || exponent <= 0) {
598
+ console.warn(`sk-chart: invalid scale.exponent ${rawExponent}; falling back to 1`);
599
+ exponent = 1;
600
+ }
601
+ const requestedActive = config.state?.defaultActive ?? config.data.length - 1;
602
+ const defaultActive = Math.min(
603
+ Math.max(requestedActive, -1),
604
+ Math.max(0, config.data.length - 1)
605
+ );
606
+ const { pack, userTokens } = resolveThemeRef(config.theme);
607
+ const tokens = resolveTokens(mergeTokens(pack?.tokens, userTokens));
608
+ const nice = niceScale(maxValue);
609
+ const ticks = config.axis?.ticks ?? nice.ticks;
610
+ const domainMax = Math.max(nice.axisMax, maxValue, ...ticks);
611
+ const bottomLabels = config.xAxis?.bottomLabels;
612
+ const bottomRows = bottomLabels ? config.data.reduce((max, d, i) => {
613
+ const out = bottomLabels(d, i, config.data);
614
+ return Math.max(max, Array.isArray(out) ? out.length : 1);
615
+ }, 0) : 0;
616
+ const titleText = config.xAxis?.title?.text ?? "";
617
+ const axisRows = bottomRows + (titleText ? 1 : 0);
618
+ const rowHeight = tokens.axis.fontSize + 4;
619
+ const defaultBottom = axisRows > 0 ? Math.max(26, 32 + axisRows * rowHeight) : 26;
620
+ const xField = config.xField ?? "label";
621
+ return {
622
+ width,
623
+ height,
624
+ xField,
625
+ yField,
626
+ valueFormat: config.valueFormat ?? pack?.formats?.valueFormat ?? defaultValueFormat,
627
+ ariaLabel: config.ariaLabel ?? "fold bar chart",
628
+ padding: { top: 64, right: 29, bottom: defaultBottom, left: 73, ...config.padding },
629
+ stair: { bottomOffset: 30, topOffset: 74, ...config.stair },
630
+ scale: { exponent },
631
+ fold: {
632
+ run: config.fold?.run ?? 20,
633
+ creaseColor: config.fold?.creaseColor ?? "rgba(255,255,255,.85)",
634
+ creaseWidth: config.fold?.creaseWidth ?? 1.2
635
+ },
636
+ axis: {
637
+ ticks,
638
+ tickFormat: config.axis?.tickFormat ?? pack?.formats?.tickFormat ?? ((v) => `${v}k`),
639
+ domainMax
640
+ },
641
+ xAxis: {
642
+ labelFormat: config.xAxis?.labelFormat ?? ((d) => String(d[xField] ?? "")),
643
+ bottomLabels,
644
+ title: {
645
+ text: titleText,
646
+ x: config.xAxis?.title?.x,
647
+ y: config.xAxis?.title?.y
648
+ },
649
+ showLine: config.xAxis?.showLine ?? false,
650
+ showTick: config.xAxis?.showTick ?? false,
651
+ showGrid: config.xAxis?.showGrid ?? true
652
+ },
653
+ tooltip: {
654
+ enabled: config.tooltip?.enabled ?? true,
655
+ formatter: config.tooltip?.formatter ?? defaultTooltipFormatter,
656
+ height: config.tooltip?.height ?? 28,
657
+ radius: config.tooltip?.radius ?? 9,
658
+ paddingX: config.tooltip?.paddingX ?? 12,
659
+ anchorRatio: config.tooltip?.anchorRatio ?? 0.42,
660
+ offsetY: config.tooltip?.offsetY ?? -30,
661
+ minY: config.tooltip?.minY ?? 140,
662
+ fixedWidth: config.tooltip?.fixedWidth
663
+ },
664
+ state: { defaultActive },
665
+ title: {
666
+ text: config.title?.text ?? "",
667
+ x: config.title?.x ?? 36,
668
+ y: config.title?.y ?? 52
669
+ },
670
+ style: resolveStyle(config.style, pack?.style),
671
+ tokens
672
+ };
673
+ }
674
+ function createModel(config, options) {
675
+ const data = config.data;
676
+ const labels = data.map((d, i) => options.xAxis.labelFormat(d, i, data));
677
+ const raw = data.map((d) => Number(d[options.yField]));
678
+ if (raw.some((v) => Number.isNaN(v) || v < 0)) {
679
+ console.warn("sk-chart: FoldBarChart data contains NaN or negative values; they render as zero.");
680
+ }
681
+ const values = raw.map((v) => v || 0);
682
+ const layout = computeLayout({
683
+ width: options.width,
684
+ height: options.height,
685
+ padding: options.padding,
686
+ stairBottomOffset: options.stair.bottomOffset,
687
+ stairTopOffset: options.stair.topOffset,
688
+ foldRun: options.fold.run,
689
+ barGap: 1,
690
+ exponent: options.scale.exponent,
691
+ domainMax: options.axis.domainMax,
692
+ values
693
+ });
694
+ const bars = values.map((v, i) => barGeometry(layout, i, v));
695
+ const flaps = values.map((_, i) => flapGeometry(layout, i, values));
696
+ const fade = config.style?.fadeMask ? options.style.fadeMask : { start: layout.plot.bottom - 16, end: layout.plot.bottom };
697
+ return { data, labels, values, layout, bars, flaps, fade, options };
698
+ }
699
+
700
+ // src/charts/fold-bar/interaction.ts
701
+ function measureTooltipText(text) {
702
+ text.setAttribute("x", "0");
703
+ text.setAttribute("y", "0");
704
+ let width = 0;
705
+ let centerY = 0;
706
+ if (typeof text.getComputedTextLength === "function") {
707
+ try {
708
+ width = text.getComputedTextLength();
709
+ } catch {
710
+ width = 0;
711
+ }
712
+ }
713
+ if (typeof text.getBBox === "function") {
714
+ try {
715
+ const box = text.getBBox();
716
+ centerY = box.y + box.height / 2;
717
+ } catch {
718
+ centerY = 0;
719
+ }
720
+ }
721
+ return { width, centerY };
722
+ }
723
+ function renderTooltipText(uid, text, parts) {
724
+ text.textContent = "";
725
+ for (const part of parts) {
726
+ const attrs = {};
727
+ if (part.tone && part.tone !== "n") {
728
+ attrs.class = scopedId(uid, part.tone);
729
+ }
730
+ const tspan = createSvgElement("tspan", attrs);
731
+ tspan.textContent = part.text;
732
+ text.appendChild(tspan);
733
+ }
734
+ }
735
+ function attachInteraction(ctx) {
736
+ const { uid, columns, columnsLayer, tooltip, model, host } = ctx;
737
+ const { options, layout, bars, data } = model;
738
+ let active = -1;
739
+ const cleanup = [];
740
+ function listen(target, type, fn, opts) {
741
+ target.addEventListener(type, fn, opts);
742
+ cleanup.push(() => target.removeEventListener(type, fn, opts));
743
+ }
744
+ function renderTip(index) {
745
+ if (!options.tooltip.enabled) return;
746
+ const parts = options.tooltip.formatter(data[index], index, data);
747
+ renderTooltipText(uid, tooltip.text, parts);
748
+ const measured = measureTooltipText(tooltip.text);
749
+ const width = options.tooltip.fixedWidth ?? measured.width + options.tooltip.paddingX * 2;
750
+ tooltip.rect.setAttribute("width", String(width));
751
+ tooltip.text.setAttribute("x", String(width / 2));
752
+ tooltip.text.setAttribute(
753
+ "y",
754
+ String(options.tooltip.height / 2 - measured.centerY)
755
+ );
756
+ const pos = tooltipPlacement(layout, bars[index], width, options.tooltip);
757
+ tooltip.group.setAttribute("transform", `translate(${pos.x},${pos.y})`);
758
+ }
759
+ function setActive(index) {
760
+ if (index === active || index < 0 || index >= columns.length) return;
761
+ active = index;
762
+ columns.forEach((col, i) => {
763
+ col.classList.toggle(`${uid}-active`, i === index);
764
+ col.setAttribute("aria-selected", String(i === index));
765
+ });
766
+ renderTip(index);
767
+ }
768
+ columns.forEach((col, i) => {
769
+ listen(col, "mouseenter", () => {
770
+ setActive(i);
771
+ host.emit("column:enter", { index: i, datum: data[i] });
772
+ });
773
+ listen(col, "click", () => {
774
+ host.emit("column:click", { index: i, datum: data[i] });
775
+ });
776
+ listen(col, "touchstart", () => setActive(i), { passive: true });
777
+ });
778
+ const releaseTouch = () => setActive(options.state.defaultActive);
779
+ listen(columnsLayer, "touchend", releaseTouch, { passive: true });
780
+ listen(columnsLayer, "touchcancel", releaseTouch, { passive: true });
781
+ listen(columnsLayer, "mouseleave", () => {
782
+ if (active >= 0) {
783
+ host.emit("column:leave", { index: active, datum: data[active] });
784
+ }
785
+ setActive(options.state.defaultActive);
786
+ });
787
+ const svg = columnsLayer.ownerSVGElement;
788
+ if (svg) {
789
+ svg.setAttribute("tabindex", "0");
790
+ listen(svg, "keydown", (event) => {
791
+ const key = event.key;
792
+ if (key === "ArrowRight") {
793
+ setActive(Math.min(active + 1, columns.length - 1));
794
+ event.preventDefault();
795
+ } else if (key === "ArrowLeft") {
796
+ setActive(Math.max(active - 1, 0));
797
+ event.preventDefault();
798
+ } else if (key === "Home") {
799
+ setActive(0);
800
+ event.preventDefault();
801
+ } else if (key === "End") {
802
+ setActive(columns.length - 1);
803
+ event.preventDefault();
804
+ } else if ((key === "Enter" || key === " ") && active >= 0) {
805
+ host.emit("column:click", { index: active, datum: data[active] });
806
+ event.preventDefault();
807
+ }
808
+ });
809
+ }
810
+ tooltip.group.style.transition = "none";
811
+ setActive(options.state.defaultActive);
812
+ requestAnimationFrame(() => {
813
+ tooltip.group.style.transition = "";
814
+ });
815
+ return {
816
+ setActive,
817
+ getActive: () => active,
818
+ destroy: () => cleanup.forEach((fn) => fn())
819
+ };
820
+ }
821
+
822
+ // src/charts/fold-bar/defs-builder.ts
823
+ function buildDefs(ctx) {
824
+ const { uid, style } = ctx;
825
+ const defs = createSvgElement("defs");
826
+ if (style.stripePattern.enabled) {
827
+ const pattern = createSvgElement("pattern", {
828
+ id: scopedId(uid, "stripes"),
829
+ width: style.stripePattern.size,
830
+ height: style.stripePattern.size,
831
+ patternUnits: "userSpaceOnUse",
832
+ patternTransform: `rotate(${style.stripePattern.rotation})`
833
+ });
834
+ pattern.appendChild(
835
+ createSvgElement("rect", {
836
+ width: style.stripePattern.size,
837
+ height: style.stripePattern.size,
838
+ fill: "none"
839
+ })
840
+ );
841
+ pattern.appendChild(
842
+ createSvgElement("rect", {
843
+ width: style.stripePattern.lineWidth,
844
+ height: style.stripePattern.size,
845
+ fill: style.stripePattern.lineColor,
846
+ "fill-opacity": style.stripePattern.lineOpacity
847
+ })
848
+ );
849
+ defs.appendChild(pattern);
850
+ }
851
+ for (let i = 0; i < ctx.barCount; i++) {
852
+ const stops = i === ctx.gradientIndex ? style.barGradientActive : style.barGradientNormal;
853
+ defs.appendChild(
854
+ addStops(
855
+ createSvgElement("linearGradient", { id: scopedId(uid, `bar${i}`), x1: 0, y1: 0, x2: 0, y2: 1 }),
856
+ stops
857
+ )
858
+ );
859
+ }
860
+ for (let i = 0; i < ctx.barCount; i++) {
861
+ const range = ctx.flapGradientYs[i];
862
+ if (!range) continue;
863
+ defs.appendChild(
864
+ addStops(
865
+ createSvgElement("linearGradient", {
866
+ id: scopedId(uid, `fold${i}`),
867
+ gradientUnits: "userSpaceOnUse",
868
+ x1: 0,
869
+ y1: range[0],
870
+ x2: 0,
871
+ y2: range[1]
872
+ }),
873
+ style.foldGradient
874
+ )
875
+ );
876
+ }
877
+ defs.appendChild(
878
+ addStops(
879
+ createSvgElement("linearGradient", { id: scopedId(uid, "crease"), x1: 0, y1: 0, x2: 1, y2: 0 }),
880
+ style.creaseGradient
881
+ )
882
+ );
883
+ if (style.washEnabled) {
884
+ defs.appendChild(
885
+ addStops(
886
+ createSvgElement("linearGradient", { id: scopedId(uid, "wash"), x1: 0, y1: 0, x2: 0, y2: 1 }),
887
+ style.washGradient
888
+ )
889
+ );
890
+ }
891
+ if (style.pill.enabled) {
892
+ defs.appendChild(
893
+ addStops(
894
+ createSvgElement("linearGradient", { id: scopedId(uid, "pill"), x1: 0, y1: 0, x2: 0, y2: 1 }),
895
+ style.pill.gradient
896
+ )
897
+ );
898
+ }
899
+ const filter = createSvgElement("filter", {
900
+ id: scopedId(uid, "soft"),
901
+ x: "-40%",
902
+ y: "-40%",
903
+ width: "180%",
904
+ height: "200%"
905
+ });
906
+ filter.appendChild(
907
+ createSvgElement("feDropShadow", {
908
+ dx: style.shadow.dx,
909
+ dy: style.shadow.dy,
910
+ stdDeviation: style.shadow.blur,
911
+ "flood-color": style.shadow.color,
912
+ "flood-opacity": style.shadow.opacity
913
+ })
914
+ );
915
+ defs.appendChild(filter);
916
+ if (style.fadeEnabled) {
917
+ defs.appendChild(
918
+ addStops(
919
+ createSvgElement("linearGradient", {
920
+ id: scopedId(uid, "fadeGrad"),
921
+ gradientUnits: "userSpaceOnUse",
922
+ x1: 0,
923
+ y1: ctx.fade.start,
924
+ x2: 0,
925
+ y2: ctx.fade.end
926
+ }),
927
+ [
928
+ [0, "#fff"],
929
+ [1, "#000"]
930
+ ]
931
+ )
932
+ );
933
+ const mask = createSvgElement("mask", {
934
+ id: scopedId(uid, "fade"),
935
+ maskUnits: "userSpaceOnUse",
936
+ x: 0,
937
+ y: 0,
938
+ width: ctx.width,
939
+ height: ctx.height
940
+ });
941
+ mask.appendChild(
942
+ createSvgElement("rect", {
943
+ x: 0,
944
+ y: 0,
945
+ width: ctx.width,
946
+ height: ctx.height,
947
+ fill: urlRef(uid, "fadeGrad")
948
+ })
949
+ );
950
+ defs.appendChild(mask);
951
+ }
952
+ return defs;
953
+ }
954
+
955
+ // src/charts/fold-bar/render.ts
956
+ function renderFoldBar(uid, model) {
957
+ const { options, layout, bars, flaps, labels, values, data } = model;
958
+ const { style, tokens } = options;
959
+ const svg = createSvgElement("svg", {
960
+ class: scopedId(uid, "root"),
961
+ viewBox: `0 0 ${options.width} ${options.height}`,
962
+ role: "img",
963
+ "aria-label": options.ariaLabel
964
+ });
965
+ const styleEl = createSvgElement("style");
966
+ styleEl.textContent = buildScopedCss(uid, tokens);
967
+ svg.appendChild(styleEl);
968
+ svg.appendChild(
969
+ buildDefs({
970
+ uid,
971
+ width: options.width,
972
+ height: options.height,
973
+ plotBottom: layout.plot.bottom,
974
+ barCount: values.length,
975
+ gradientIndex: options.state.defaultActive,
976
+ flapGradientYs: flaps.map((flap) => flap ? flap.gradientY : null),
977
+ fade: model.fade,
978
+ style
979
+ })
980
+ );
981
+ if (options.title.text) {
982
+ const title = createSvgElement("text", {
983
+ class: scopedId(uid, "title"),
984
+ x: options.title.x,
985
+ y: options.title.y
986
+ });
987
+ title.textContent = options.title.text;
988
+ svg.appendChild(title);
989
+ }
990
+ const grid = createSvgElement("g");
991
+ options.axis.ticks.forEach((tick) => {
992
+ const label = createSvgElement("text", {
993
+ class: scopedId(uid, "axis"),
994
+ x: layout.plot.left - 15,
995
+ y: layout.barTopOf(tick) + 4,
996
+ "text-anchor": "end"
997
+ });
998
+ label.textContent = options.axis.tickFormat(tick);
999
+ grid.appendChild(label);
1000
+ });
1001
+ if (options.xAxis.showGrid) {
1002
+ for (const x of gridLineXs(layout)) {
1003
+ grid.appendChild(
1004
+ createSvgElement("line", {
1005
+ x1: x,
1006
+ x2: x,
1007
+ y1: layout.plot.top,
1008
+ y2: layout.plot.bottom,
1009
+ stroke: tokens.grid.stroke,
1010
+ "stroke-width": tokens.grid.strokeWidth
1011
+ })
1012
+ );
1013
+ }
1014
+ }
1015
+ svg.appendChild(grid);
1016
+ const columnsLayer = createSvgElement(
1017
+ "g",
1018
+ style.fadeEnabled ? { mask: urlRef(uid, "fade"), role: "list" } : { role: "list" }
1019
+ );
1020
+ svg.appendChild(columnsLayer);
1021
+ const columns = [];
1022
+ values.forEach((value, i) => {
1023
+ const bar = bars[i];
1024
+ const col = createSvgElement("g", {
1025
+ class: scopedId(uid, "col"),
1026
+ "data-i": i,
1027
+ role: "listitem",
1028
+ "aria-selected": "false",
1029
+ "aria-label": `${labels[i]} ${options.valueFormat(value)}`
1030
+ });
1031
+ if (style.washEnabled) {
1032
+ col.appendChild(
1033
+ createSvgElement("rect", {
1034
+ class: scopedId(uid, "wash"),
1035
+ ...washRect(bar, style.washTop),
1036
+ fill: urlRef(uid, "wash")
1037
+ })
1038
+ );
1039
+ }
1040
+ col.appendChild(
1041
+ createSvgElement("rect", {
1042
+ x: bar.x,
1043
+ y: bar.y,
1044
+ width: bar.width,
1045
+ height: bar.height,
1046
+ fill: urlRef(uid, `bar${i}`)
1047
+ })
1048
+ );
1049
+ if (style.stripePattern.enabled) {
1050
+ col.appendChild(
1051
+ createSvgElement("rect", {
1052
+ class: scopedId(uid, "stripes"),
1053
+ x: bar.x,
1054
+ y: bar.y,
1055
+ width: bar.width,
1056
+ height: bar.height,
1057
+ fill: urlRef(uid, "stripes")
1058
+ })
1059
+ );
1060
+ }
1061
+ const flap = flaps[i];
1062
+ if (flap) {
1063
+ col.appendChild(
1064
+ createSvgElement("polygon", { points: flap.points, fill: urlRef(uid, `fold${i}`) })
1065
+ );
1066
+ col.appendChild(
1067
+ createSvgElement("polygon", { points: flap.points, fill: urlRef(uid, "crease") })
1068
+ );
1069
+ col.appendChild(
1070
+ createSvgElement("line", {
1071
+ x1: flap.crease.x1,
1072
+ y1: flap.crease.y1,
1073
+ x2: flap.crease.x2,
1074
+ y2: flap.crease.y2,
1075
+ stroke: options.fold.creaseColor,
1076
+ "stroke-width": options.fold.creaseWidth
1077
+ })
1078
+ );
1079
+ }
1080
+ if (style.pill.enabled) {
1081
+ const pill = pillGeometry(bar.centerX, bar.y, style.pill);
1082
+ col.appendChild(
1083
+ createSvgElement("rect", {
1084
+ x: pill.x,
1085
+ y: pill.y,
1086
+ width: pill.width,
1087
+ height: pill.height,
1088
+ rx: pill.rx,
1089
+ fill: urlRef(uid, "pill")
1090
+ })
1091
+ );
1092
+ col.appendChild(
1093
+ createSvgElement("rect", {
1094
+ x: pill.shadowX,
1095
+ y: pill.shadowY,
1096
+ width: pill.shadowWidth,
1097
+ height: pill.shadowHeight,
1098
+ rx: pill.shadowRx,
1099
+ fill: style.pill.shadow.color,
1100
+ "fill-opacity": style.pill.shadow.opacity
1101
+ })
1102
+ );
1103
+ }
1104
+ const labelX = bar.centerX + style.labelXOffset;
1105
+ const label = createSvgElement("text", {
1106
+ class: scopedId(uid, "lbl"),
1107
+ x: labelX,
1108
+ y: style.labelY,
1109
+ "text-anchor": "middle"
1110
+ });
1111
+ label.textContent = labels[i];
1112
+ col.appendChild(label);
1113
+ const num = createSvgElement("text", {
1114
+ class: scopedId(uid, "num"),
1115
+ x: labelX,
1116
+ y: style.numberY,
1117
+ "text-anchor": "middle"
1118
+ });
1119
+ num.textContent = options.valueFormat(value);
1120
+ col.appendChild(num);
1121
+ col.appendChild(
1122
+ createSvgElement("rect", { class: scopedId(uid, "hit"), ...hitRect(layout, i) })
1123
+ );
1124
+ columnsLayer.appendChild(col);
1125
+ columns.push(col);
1126
+ });
1127
+ const { xAxis } = options;
1128
+ if (xAxis.showLine || xAxis.showTick || xAxis.bottomLabels || xAxis.title.text) {
1129
+ const axisLayer = createSvgElement("g", {
1130
+ class: scopedId(uid, "xaxis"),
1131
+ "pointer-events": "none",
1132
+ "aria-hidden": "true"
1133
+ });
1134
+ const axisLineY = layout.plot.bottom + 4;
1135
+ if (xAxis.showLine) {
1136
+ axisLayer.appendChild(
1137
+ createSvgElement("line", {
1138
+ x1: layout.plot.left,
1139
+ x2: layout.plot.right,
1140
+ y1: axisLineY,
1141
+ y2: axisLineY,
1142
+ stroke: tokens.grid.stroke,
1143
+ "stroke-width": tokens.grid.strokeWidth
1144
+ })
1145
+ );
1146
+ }
1147
+ const rowHeight = tokens.axis.fontSize + 4;
1148
+ if (xAxis.showTick) {
1149
+ for (const x of gridLineXs(layout)) {
1150
+ axisLayer.appendChild(
1151
+ createSvgElement("line", {
1152
+ x1: x,
1153
+ x2: x,
1154
+ y1: axisLineY,
1155
+ y2: axisLineY + 4,
1156
+ stroke: tokens.grid.stroke,
1157
+ "stroke-width": tokens.grid.strokeWidth
1158
+ })
1159
+ );
1160
+ }
1161
+ }
1162
+ let bottomRows = 0;
1163
+ if (xAxis.bottomLabels) {
1164
+ const format = xAxis.bottomLabels;
1165
+ bars.forEach((bar, i) => {
1166
+ const out = format(data[i], i, data);
1167
+ const lines = Array.isArray(out) ? out : [out];
1168
+ bottomRows = Math.max(bottomRows, lines.length);
1169
+ lines.forEach((line, row) => {
1170
+ const text2 = createSvgElement("text", {
1171
+ class: scopedId(uid, "axis"),
1172
+ x: bar.centerX,
1173
+ y: layout.plot.bottom + 20 + row * rowHeight,
1174
+ "text-anchor": "middle"
1175
+ });
1176
+ text2.textContent = line;
1177
+ axisLayer.appendChild(text2);
1178
+ });
1179
+ });
1180
+ }
1181
+ if (xAxis.title.text) {
1182
+ const title = createSvgElement("text", {
1183
+ class: scopedId(uid, "axis"),
1184
+ x: xAxis.title.x ?? (layout.plot.left + layout.plot.right) / 2,
1185
+ y: xAxis.title.y ?? layout.plot.bottom + 26 + bottomRows * rowHeight,
1186
+ "text-anchor": "middle"
1187
+ });
1188
+ title.textContent = xAxis.title.text;
1189
+ axisLayer.appendChild(title);
1190
+ }
1191
+ svg.appendChild(axisLayer);
1192
+ }
1193
+ const group = createSvgElement("g", {
1194
+ class: scopedId(uid, "tip"),
1195
+ filter: urlRef(uid, "soft")
1196
+ });
1197
+ const rect = createSvgElement("rect", {
1198
+ x: 0,
1199
+ y: 0,
1200
+ height: options.tooltip.height,
1201
+ rx: options.tooltip.radius,
1202
+ fill: tokens.tooltip.bg,
1203
+ stroke: tokens.tooltip.stroke
1204
+ });
1205
+ const text = createSvgElement("text", { x: 0, y: 0, "text-anchor": "middle" });
1206
+ group.appendChild(rect);
1207
+ group.appendChild(text);
1208
+ svg.appendChild(group);
1209
+ return { svg, columns, columnsLayer, tooltip: { group, rect, text } };
1210
+ }
1211
+
1212
+ // src/charts/fold-bar/index.ts
1213
+ var FoldBarChart = class extends ChartBase {
1214
+ constructor(container, config) {
1215
+ super(container, config);
1216
+ this.uid = createUid();
1217
+ this.model = null;
1218
+ this.rendered = null;
1219
+ this.interaction = null;
1220
+ this.renderChart();
1221
+ }
1222
+ /** Currently active (highlighted) column index. */
1223
+ get activeIndex() {
1224
+ return this.interaction?.getActive() ?? -1;
1225
+ }
1226
+ /** Programmatically highlight a column. */
1227
+ setActive(index) {
1228
+ this.interaction?.setActive(index);
1229
+ }
1230
+ renderChart() {
1231
+ this.interaction?.destroy();
1232
+ this.interaction = null;
1233
+ this.rendered?.svg.remove();
1234
+ const options = resolveOptions(this.config);
1235
+ this.model = createModel(this.config, options);
1236
+ this.rendered = renderFoldBar(this.uid, this.model);
1237
+ this.container.appendChild(this.rendered.svg);
1238
+ this.svg = this.rendered.svg;
1239
+ this.interaction = this.model.data.length ? attachInteraction({
1240
+ uid: this.uid,
1241
+ columns: this.rendered.columns,
1242
+ columnsLayer: this.rendered.columnsLayer,
1243
+ tooltip: this.rendered.tooltip,
1244
+ model: this.model,
1245
+ host: { emit: (event, payload) => this.emit(event, payload) }
1246
+ }) : null;
1247
+ }
1248
+ onDestroy() {
1249
+ this.interaction?.destroy();
1250
+ this.interaction = null;
1251
+ }
1252
+ };
1253
+
1254
+ // src/scale/linear-scale.ts
1255
+ function createLinearScale({
1256
+ domain: [d0, d1],
1257
+ range: [r0, r1]
1258
+ }) {
1259
+ const span = d1 - d0;
1260
+ if (span === 0) return () => r0;
1261
+ const k = (r1 - r0) / span;
1262
+ return (value) => r0 + (value - d0) * k;
1263
+ }
1264
+
1265
+ // src/scale/power-scale.ts
1266
+ function createPowerScale({
1267
+ domain: [d0, d1],
1268
+ range: [r0, r1],
1269
+ exponent = 1
1270
+ }) {
1271
+ const span = d1 - d0;
1272
+ if (span === 0) return () => r0;
1273
+ return (value) => {
1274
+ const t = Math.max(0, (value - d0) / span);
1275
+ return r0 + Math.pow(t, exponent) * (r1 - r0);
1276
+ };
1277
+ }
1278
+
1279
+ // src/scale/band-scale.ts
1280
+ function createBandScale(count, range, paddingRatio = 0) {
1281
+ const [r0, r1] = range;
1282
+ const step = count > 0 ? (r1 - r0) / count : 0;
1283
+ const bandwidth = step * (1 - paddingRatio);
1284
+ const offset = (step - bandwidth) / 2;
1285
+ return {
1286
+ step,
1287
+ bandwidth,
1288
+ position: (index) => r0 + index * step + offset,
1289
+ center: (index) => r0 + index * step + step / 2
1290
+ };
1291
+ }
1292
+
1293
+ // src/index.ts
1294
+ var VERSION = "0.1.0";
1295
+ // Annotate the CommonJS export names for ESM import in node:
1296
+ 0 && (module.exports = {
1297
+ ChartBase,
1298
+ FoldBarChart,
1299
+ VERSION,
1300
+ createBandScale,
1301
+ createLinearScale,
1302
+ createPowerScale,
1303
+ defaultTooltipFormatter,
1304
+ defaultValueFormat,
1305
+ getTheme,
1306
+ isThemePack,
1307
+ niceScale,
1308
+ registerTheme
1309
+ });
1310
+ //# sourceMappingURL=index.cjs.map