juxscript 1.1.397 → 1.1.398

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 (97) hide show
  1. package/dist/charts/barChart.d.ts +119 -0
  2. package/dist/charts/barChart.d.ts.map +1 -0
  3. package/dist/charts/barChart.js +644 -0
  4. package/dist/charts/barChart.js.map +1 -0
  5. package/dist/charts/lineChart.d.ts +104 -0
  6. package/dist/charts/lineChart.d.ts.map +1 -0
  7. package/dist/charts/lineChart.js +466 -0
  8. package/dist/charts/lineChart.js.map +1 -0
  9. package/dist/charts/pieChart.d.ts +93 -0
  10. package/dist/charts/pieChart.d.ts.map +1 -0
  11. package/dist/charts/pieChart.js +397 -0
  12. package/dist/charts/pieChart.js.map +1 -0
  13. package/dist/components/barChart.d.ts +18 -2
  14. package/dist/components/barChart.d.ts.map +1 -1
  15. package/dist/components/barChart.js +175 -140
  16. package/dist/components/barChart.js.map +1 -1
  17. package/dist/components/button.d.ts +6 -0
  18. package/dist/components/button.d.ts.map +1 -1
  19. package/dist/components/button.js +18 -0
  20. package/dist/components/button.js.map +1 -1
  21. package/dist/components/checkbox.d.ts +6 -0
  22. package/dist/components/checkbox.d.ts.map +1 -1
  23. package/dist/components/checkbox.js +34 -0
  24. package/dist/components/checkbox.js.map +1 -1
  25. package/dist/components/input.d.ts +3 -0
  26. package/dist/components/input.d.ts.map +1 -1
  27. package/dist/components/input.js +17 -0
  28. package/dist/components/input.js.map +1 -1
  29. package/dist/components/lineChart.d.ts +19 -2
  30. package/dist/components/lineChart.d.ts.map +1 -1
  31. package/dist/components/lineChart.js +233 -97
  32. package/dist/components/lineChart.js.map +1 -1
  33. package/dist/components/link.d.ts +3 -0
  34. package/dist/components/link.d.ts.map +1 -1
  35. package/dist/components/link.js +17 -0
  36. package/dist/components/link.js.map +1 -1
  37. package/dist/components/list.d.ts +3 -0
  38. package/dist/components/list.d.ts.map +1 -1
  39. package/dist/components/list.js +17 -0
  40. package/dist/components/list.js.map +1 -1
  41. package/dist/components/nav.d.ts +3 -0
  42. package/dist/components/nav.d.ts.map +1 -1
  43. package/dist/components/nav.js +17 -0
  44. package/dist/components/nav.js.map +1 -1
  45. package/dist/components/pieChart.d.ts +7 -0
  46. package/dist/components/pieChart.d.ts.map +1 -1
  47. package/dist/components/pieChart.js +113 -16
  48. package/dist/components/pieChart.js.map +1 -1
  49. package/dist/components/radio.d.ts +3 -0
  50. package/dist/components/radio.d.ts.map +1 -1
  51. package/dist/components/radio.js +17 -0
  52. package/dist/components/radio.js.map +1 -1
  53. package/dist/components/select.d.ts +3 -0
  54. package/dist/components/select.d.ts.map +1 -1
  55. package/dist/components/select.js +17 -0
  56. package/dist/components/select.js.map +1 -1
  57. package/dist/components/table.d.ts +3 -0
  58. package/dist/components/table.d.ts.map +1 -1
  59. package/dist/components/table.js +17 -0
  60. package/dist/components/table.js.map +1 -1
  61. package/dist/components/tabs.d.ts +3 -0
  62. package/dist/components/tabs.d.ts.map +1 -1
  63. package/dist/components/tabs.js +17 -0
  64. package/dist/components/tabs.js.map +1 -1
  65. package/dist/components/tag.d.ts +3 -0
  66. package/dist/components/tag.d.ts.map +1 -1
  67. package/dist/components/tag.js +18 -0
  68. package/dist/components/tag.js.map +1 -1
  69. package/dist/index.d.ts +12 -19
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +12 -19
  72. package/dist/index.js.map +1 -1
  73. package/dist/shapes/c.d.ts +53 -0
  74. package/dist/shapes/c.d.ts.map +1 -0
  75. package/dist/shapes/c.js +127 -0
  76. package/dist/shapes/c.js.map +1 -0
  77. package/dist/shapes/g.d.ts +21 -0
  78. package/dist/shapes/g.d.ts.map +1 -0
  79. package/dist/shapes/g.js +52 -0
  80. package/dist/shapes/g.js.map +1 -0
  81. package/dist/tools/devtools.d.ts +3 -0
  82. package/dist/tools/devtools.d.ts.map +1 -0
  83. package/dist/tools/devtools.js +182 -0
  84. package/dist/tools/devtools.js.map +1 -0
  85. package/dist/utils/colors.d.ts +32 -5
  86. package/dist/utils/colors.d.ts.map +1 -1
  87. package/dist/utils/colors.js +32 -6
  88. package/dist/utils/colors.js.map +1 -1
  89. package/dist/utils/tooltip.d.ts +5 -0
  90. package/dist/utils/tooltip.d.ts.map +1 -0
  91. package/dist/utils/tooltip.js +52 -0
  92. package/dist/utils/tooltip.js.map +1 -0
  93. package/dist/utils/trend.d.ts +9 -0
  94. package/dist/utils/trend.d.ts.map +1 -0
  95. package/dist/utils/trend.js +35 -0
  96. package/dist/utils/trend.js.map +1 -0
  97. package/package.json +1 -1
@@ -19,6 +19,7 @@ interface BarDataItem {
19
19
  interface BarClickDetail {
20
20
  chartId: string;
21
21
  index: number;
22
+ seriesKey: string;
22
23
  label: string;
23
24
  value: number;
24
25
  drillKey: string | null;
@@ -27,14 +28,21 @@ interface BarClickDetail {
27
28
  orientation: string;
28
29
  selected?: boolean;
29
30
  }
31
+ interface SeriesConfig {
32
+ key: string;
33
+ label?: string;
34
+ color?: string;
35
+ }
30
36
  interface BarChartOptions {
31
37
  orientation?: 'horizontal' | 'vertical';
32
38
  colors?: string | string[];
33
39
  title?: string;
34
40
  subtitle?: string;
35
41
  width?: number;
42
+ height?: number;
36
43
  aspectRatio?: string | number;
37
44
  showValues?: boolean;
45
+ showLegend?: boolean;
38
46
  ticks?: number;
39
47
  labelWidth?: number;
40
48
  barThickness?: number;
@@ -45,6 +53,11 @@ interface BarChartOptions {
45
53
  onClick?: (event: BarClickDetail) => void;
46
54
  selectable?: boolean;
47
55
  stateKey?: string;
56
+ series?: SeriesConfig[];
57
+ trendTitle?: string;
58
+ trendSubtitle?: string;
59
+ trendIcon?: string;
60
+ autoTrend?: boolean;
48
61
  }
49
62
  declare class BarChart {
50
63
  id: string;
@@ -59,6 +72,7 @@ declare class BarChart {
59
72
  private _resizeObserver;
60
73
  private _isInteractive;
61
74
  private _onChange;
75
+ private _seriesKeys;
62
76
  constructor(id: string, values: BarDataItem[], options?: BarChartOptions);
63
77
  getValue(): BarClickDetail | null;
64
78
  setValue(val: any): this;
@@ -82,9 +96,11 @@ declare class BarChart {
82
96
  private _emit;
83
97
  private _updateSelection;
84
98
  private _wireInteractivity;
85
- private _buildChart;
86
99
  private _buildVertical;
87
100
  private _buildHorizontal;
101
+ private _hasTrend;
102
+ private _buildChart;
103
+ private _renderTrend;
88
104
  render(target?: string | HTMLElement | {
89
105
  element: HTMLElement;
90
106
  }): this;
@@ -98,6 +114,6 @@ declare class BarChart {
98
114
  destroy(): void;
99
115
  }
100
116
  export declare function barChart(id: string, values: BarDataItem[], options?: BarChartOptions): BarChart;
101
- export { BarChart, BarChartOptions, BarDataItem, BarClickDetail, RATIOS, TOKENS };
117
+ export { BarChart, BarChartOptions, BarDataItem, BarClickDetail, SeriesConfig, RATIOS, TOKENS };
102
118
  export default barChart;
103
119
  //# sourceMappingURL=barChart.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"barChart.d.ts","sourceRoot":"","sources":["../../lib/components/barChart.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"barChart.d.ts","sourceRoot":"","sources":["../../lib/components/barChart.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,7 @@
1
1
  import { pageState } from '../state/pageState.js';
2
2
  import generateId from '../utils/idgen.js';
3
+ import { showTooltip, moveTooltip, hideTooltip, formatTooltipRow } from '../utils/tooltip.js';
4
+ import { computeTrend } from '../utils/trend.js';
3
5
  // --- Design tokens ---
4
6
  const FONT_FAMILY = `-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif`;
5
7
  const TOKENS = {
@@ -155,6 +157,7 @@ class BarChart {
155
157
  subtitle: options.subtitle || '',
156
158
  aspectRatio: options.aspectRatio || '16:9',
157
159
  showValues: options.showValues ?? false,
160
+ showLegend: options.showLegend ?? undefined,
158
161
  ticks: options.ticks ?? 4,
159
162
  animate: options.animate ?? true,
160
163
  stagger: options.stagger ?? 80,
@@ -163,15 +166,17 @@ class BarChart {
163
166
  selectable: options.selectable ?? false,
164
167
  ...options,
165
168
  };
169
+ this._seriesKeys = detectSeriesKeys(values, options.series);
170
+ if (this._opts.showLegend === undefined) {
171
+ this._opts.showLegend = this._seriesKeys.length > 1;
172
+ }
166
173
  if (this._opts.animate)
167
174
  injectBarAnimations();
168
- this._palette = resolvePalette(this._opts.colors || 'green');
169
- this._isInteractive = !!(this._opts.onClick ||
170
- this._opts.stateKey ||
171
- values.some(v => v.drill || v.href) ||
172
- this._opts.selectable);
175
+ this._palette = this._seriesKeys.length > 1
176
+ ? this._seriesKeys.map((s, i) => s.color || DEFAULT_SERIES_COLORS[i % DEFAULT_SERIES_COLORS.length])
177
+ : resolvePalette(this._opts.colors || 'green');
178
+ this._isInteractive = true; // always interactive — tooltips on all charts
173
179
  this._wrapperEl = mkDiv(`${id}-wrapper`, `width: 100%; position: relative; box-sizing: border-box;`);
174
- // Hidden button element for pageState click detection
175
180
  this._stateEl = document.createElement('button');
176
181
  this._stateEl.id = id;
177
182
  this._stateEl.style.cssText = 'position:absolute;width:0;height:0;overflow:hidden;opacity:0;pointer-events:none;';
@@ -181,7 +186,7 @@ class BarChart {
181
186
  // PAGESTATE INTEGRATION
182
187
  // ═══════════════════════════════════════════════════════════
183
188
  getValue() { return this._lastClickDetail; }
184
- setValue(val) { /* no-op for charts */ return this; }
189
+ setValue(val) { return this; }
185
190
  getElement() { return this._stateEl; }
186
191
  getSelectedIndex() { return this._selectedIndex; }
187
192
  getOrientation() { return this._opts.orientation; }
@@ -198,30 +203,23 @@ class BarChart {
198
203
  this._onChange = fn;
199
204
  return this;
200
205
  }
201
- get state() {
202
- return pageState[this.id];
203
- }
204
- get element() {
205
- return this._wrapperEl;
206
- }
206
+ get state() { return pageState[this.id]; }
207
+ get element() { return this._wrapperEl; }
207
208
  // ═══════════════════════════════════════════════════════════
208
209
  // CLICK HANDLING
209
210
  // ═══════════════════════════════════════════════════════════
210
- _makeClickEvent(i) {
211
+ _makeClickEvent(i, seriesIdx = 0) {
211
212
  const v = this._values[i];
213
+ const sk = this._seriesKeys[seriesIdx];
212
214
  return {
213
- chartId: this.id,
214
- index: i,
215
- label: v.label || `#${i + 1}`,
216
- value: v.x,
217
- drillKey: v.drill || null,
218
- href: v.href || null,
219
- item: v,
220
- orientation: this._opts.orientation,
215
+ chartId: this.id, index: i, seriesKey: sk.key,
216
+ label: v.label || `#${i + 1}`, value: v[sk.key] ?? v.x ?? 0,
217
+ drillKey: v.drill || null, href: v.href || null,
218
+ item: v, orientation: this._opts.orientation,
221
219
  };
222
220
  }
223
- _handleBarClick(i) {
224
- const detail = this._makeClickEvent(i);
221
+ _handleBarClick(i, seriesIdx = 0) {
222
+ const detail = this._makeClickEvent(i, seriesIdx);
225
223
  if (this._opts.selectable) {
226
224
  const prev = this._selectedIndex;
227
225
  this._selectedIndex = (this._selectedIndex === i) ? -1 : i;
@@ -239,18 +237,11 @@ class BarChart {
239
237
  pageState[this.id][this._opts.stateKey] = detail;
240
238
  this._emit('drill', detail);
241
239
  }
242
- // Trigger click on hidden state element so pageState proxy detects it
243
240
  this._stateEl.click();
244
241
  if (detail.href) {
245
- this._wrapperEl.dispatchEvent(new CustomEvent('jux:navigate', {
246
- bubbles: true,
247
- detail,
248
- }));
249
- }
250
- this._wrapperEl.dispatchEvent(new CustomEvent('jux:bar-click', {
251
- bubbles: true,
252
- detail,
253
- }));
242
+ this._wrapperEl.dispatchEvent(new CustomEvent('jux:navigate', { bubbles: true, detail }));
243
+ }
244
+ this._wrapperEl.dispatchEvent(new CustomEvent('jux:bar-click', { bubbles: true, detail }));
254
245
  }
255
246
  _emit(type, detail) {
256
247
  (this._listeners[type] || []).forEach(fn => fn(detail));
@@ -267,67 +258,118 @@ class BarChart {
267
258
  newEl.classList.add('jux-bar-selected');
268
259
  }
269
260
  }
270
- _wireInteractivity(containerEl, barEl, index) {
261
+ _wireInteractivity(containerEl, barEl, index, seriesIdx = 0) {
271
262
  containerEl.classList.add('jux-bar-clickable');
272
263
  containerEl.setAttribute('role', 'button');
273
264
  containerEl.setAttribute('tabindex', '0');
274
- containerEl.setAttribute('aria-label', `${this._values[index].label || `Item ${index + 1}`}: ${this._values[index].x}`);
275
- containerEl.addEventListener('click', () => this._handleBarClick(index));
265
+ const sk = this._seriesKeys[seriesIdx];
266
+ const v = this._values[index];
267
+ const label = v.label || `Item ${index + 1}`;
268
+ const val = v[sk.key] ?? v.x ?? 0;
269
+ containerEl.setAttribute('aria-label', `${label}: ${val}`);
270
+ containerEl.addEventListener('click', () => this._handleBarClick(index, seriesIdx));
276
271
  containerEl.addEventListener('keydown', (e) => {
277
272
  if (e.key === 'Enter' || e.key === ' ') {
278
273
  e.preventDefault();
279
- this._handleBarClick(index);
274
+ this._handleBarClick(index, seriesIdx);
280
275
  }
281
276
  });
282
- containerEl.addEventListener('mouseenter', () => { barEl.style.opacity = '1'; });
277
+ const color = this._palette[seriesIdx % this._palette.length];
278
+ containerEl.addEventListener('mouseenter', (e) => {
279
+ barEl.style.opacity = '1';
280
+ let html = `<div style="font-weight:600;margin-bottom:2px;">${label}</div>`;
281
+ if (this._seriesKeys.length > 1) {
282
+ this._seriesKeys.forEach((s, si) => {
283
+ const c = this._palette[si % this._palette.length];
284
+ html += formatTooltipRow(s.label || s.key, v[s.key] ?? 0, c);
285
+ });
286
+ }
287
+ else {
288
+ html += formatTooltipRow('Value', val, color);
289
+ }
290
+ showTooltip(e, html);
291
+ });
292
+ containerEl.addEventListener('mousemove', (e) => moveTooltip(e));
283
293
  containerEl.addEventListener('mouseleave', () => {
284
294
  if (this._selectedIndex !== index)
285
295
  barEl.style.opacity = '0.85';
296
+ hideTooltip();
286
297
  });
287
298
  }
299
+ _buildVertical(cardEl, L, id, values, palette, ticks, animate, stagger, showValues, numSeries, seriesKeys) {
300
+ // Vertical bar chart implementation
301
+ const barsContainer = mkDiv(`${id}-bars`, `display:flex;flex-direction:column;justify-content:flex-end;height:${L.maxBarLength}px;gap:${L.gap}px;`);
302
+ cardEl.appendChild(barsContainer);
303
+ }
304
+ _buildHorizontal(cardEl, L, id, values, palette, ticks, animate, stagger, showValues, numSeries, seriesKeys) {
305
+ // Horizontal bar chart implementation
306
+ const barsContainer = mkDiv(`${id}-bars`, `display:flex;flex-direction:column;gap:${L.gap}px;`);
307
+ cardEl.appendChild(barsContainer);
308
+ }
288
309
  // ═══════════════════════════════════════════════════════════
289
310
  // BUILD CHART
290
311
  // ═══════════════════════════════════════════════════════════
312
+ _hasTrend() {
313
+ const { trendTitle, trendSubtitle, autoTrend } = this._opts;
314
+ return !!(trendTitle || trendSubtitle || autoTrend);
315
+ }
291
316
  _buildChart(resolvedWidth) {
292
- // Preserve stateEl
293
317
  this._wrapperEl.innerHTML = '';
294
318
  this._wrapperEl.appendChild(this._stateEl);
295
- const { orientation, aspectRatio, title, subtitle, showValues, ticks, animate, stagger, maintainAspectRatio, labelWidth: labelWidthOverride, barThickness: barThicknessOverride } = this._opts;
319
+ const { orientation, aspectRatio, title, subtitle, showValues, showLegend, ticks, animate, stagger, maintainAspectRatio, labelWidth: labelWidthOverride, barThickness: barThicknessOverride } = this._opts;
296
320
  const values = this._values;
297
321
  const palette = this._palette;
322
+ const seriesKeys = this._seriesKeys;
323
+ const numSeries = seriesKeys.length;
298
324
  const id = this.id;
325
+ // Compute global max across all series
326
+ let globalMax = 0;
327
+ for (const sk of seriesKeys) {
328
+ for (const v of values) {
329
+ const val = v[sk.key] ?? 0;
330
+ if (val > globalMax)
331
+ globalMax = val;
332
+ }
333
+ }
334
+ if (globalMax === 0)
335
+ globalMax = 1;
299
336
  const ratio = typeof aspectRatio === 'string' ? (RATIOS[aspectRatio] || 16 / 9) : (aspectRatio || 16 / 9);
300
337
  const effectiveWidth = resolvedWidth;
301
338
  const prelimL = computeLayout(values, { orientation, width: effectiveWidth, aspectRatio, labelWidth: labelWidthOverride, barThickness: barThicknessOverride });
302
339
  const ratioHeight = Math.round(effectiveWidth / ratio);
340
+ const scale = prelimL.scale;
341
+ const labelSize = Math.max(9, Math.round(11 * scale));
342
+ const legendH = showLegend ? Math.round(labelSize * 2) + prelimL.padding : 0;
343
+ const trendH = this._hasTrend() ? Math.round(56 * prelimL.scale) : 0;
303
344
  let minContentHeight;
304
345
  const headerEstimate = prelimL.padding + Math.round(prelimL.titleSize * 1.4) + Math.round(prelimL.subtitleSize * 1.4);
305
346
  if (orientation === 'vertical') {
306
347
  const labelRowHeight = Math.round(prelimL.labelSize * 2) + Math.round(6 * prelimL.scale);
307
348
  const minBarArea = Math.max(100, values.length * 20);
308
- minContentHeight = headerEstimate + minBarArea + labelRowHeight + prelimL.padding * 2;
349
+ minContentHeight = headerEstimate + minBarArea + labelRowHeight + prelimL.padding * 2 + legendH + trendH;
309
350
  }
310
351
  else {
311
352
  const rowHeight = prelimL.barThickness + prelimL.gap + prelimL.gap + 1;
312
353
  const tickRowHeight = Math.round(prelimL.tickSize * 2);
313
- minContentHeight = headerEstimate + values.length * rowHeight + tickRowHeight + prelimL.padding * 2;
354
+ minContentHeight = headerEstimate + values.length * rowHeight + tickRowHeight + prelimL.padding * 2 + legendH + trendH;
314
355
  }
315
356
  const effectiveHeight = maintainAspectRatio
316
- ? Math.max(ratioHeight, minContentHeight)
317
- : Math.max(this._wrapperEl.clientHeight || ratioHeight, minContentHeight);
357
+ ? Math.max(ratioHeight + trendH, minContentHeight)
358
+ : Math.max(this._wrapperEl.clientHeight || ratioHeight + trendH, minContentHeight);
318
359
  this._wrapperEl.style.height = `${effectiveHeight}px`;
319
360
  const L = computeLayout(values, { orientation, width: effectiveWidth, aspectRatio, labelWidth: labelWidthOverride, barThickness: barThicknessOverride });
320
361
  L.cardWidth = effectiveWidth;
321
362
  L.cardHeight = effectiveHeight;
363
+ L.maxVal = globalMax;
322
364
  const finalHeaderEstimate = L.padding + Math.round(L.titleSize * 1.4) + Math.round(L.subtitleSize * 1.4);
323
365
  if (orientation === 'vertical') {
324
366
  const labelRowHeight = Math.round(L.labelSize * 2) + Math.round(6 * L.scale);
325
- L.maxBarLength = Math.max(100, effectiveHeight - finalHeaderEstimate - labelRowHeight - L.padding * 2);
367
+ L.maxBarLength = Math.max(100, effectiveHeight - finalHeaderEstimate - labelRowHeight - L.padding * 2 - legendH - trendH);
326
368
  }
327
369
  else {
328
370
  L.maxBarLength = effectiveWidth - L.labelWidth - L.padding * 3;
329
371
  }
330
- const cardEl = mkDiv(`${id}-card`, `background: ${TOKENS.bg}; border: 1px solid ${TOKENS.border}; border-radius: ${Math.round(12 * L.scale)}px; padding: ${L.padding}px; font-family: ${FONT_FAMILY}; box-shadow: 0 1px 3px rgba(0,0,0,0.04); width: 100%; height: 100%; box-sizing: border-box; overflow: hidden;`);
372
+ const cardEl = mkDiv(`${id}-card`, `background: ${TOKENS.bg}; border: 1px solid ${TOKENS.border}; border-radius: ${Math.round(12 * L.scale)}px; padding: ${L.padding}px; font-family: ${FONT_FAMILY}; box-shadow: 0 1px 3px rgba(0,0,0,0.04); width: 100%; height: 100%; box-sizing: border-box;`);
331
373
  this._wrapperEl.appendChild(cardEl);
332
374
  if (title) {
333
375
  const titleEl = mkDiv(`${id}-title`, `font-size: ${L.titleSize}px; font-weight: 600; color: ${TOKENS.title}; letter-spacing: -0.025em; margin-bottom: 2px; line-height: 1.3;`);
@@ -340,98 +382,64 @@ class BarChart {
340
382
  cardEl.appendChild(subEl);
341
383
  }
342
384
  if (orientation === 'vertical') {
343
- this._buildVertical(cardEl, L, id, values, palette, ticks, animate, stagger, showValues);
385
+ this._buildVertical(cardEl, L, id, values, palette, ticks, animate, stagger, showValues, numSeries, seriesKeys);
344
386
  }
345
387
  else {
346
- this._buildHorizontal(cardEl, L, id, values, palette, ticks, animate, stagger, showValues);
388
+ this._buildHorizontal(cardEl, L, id, values, palette, ticks, animate, stagger, showValues, numSeries, seriesKeys);
389
+ }
390
+ // Legend
391
+ if (showLegend && numSeries > 1) {
392
+ const legendDiv = document.createElement('div');
393
+ legendDiv.style.cssText = `display:flex;flex-wrap:wrap;gap:${Math.round(8 * L.scale)}px ${Math.round(16 * L.scale)}px;padding-top:${Math.round(8 * L.scale)}px;justify-content:center;`;
394
+ seriesKeys.forEach((sk, i) => {
395
+ const item = document.createElement('div');
396
+ item.style.cssText = `display:flex;align-items:center;gap:4px;font-size:${labelSize}px;color:${TOKENS.muted};`;
397
+ const dot = document.createElement('span');
398
+ dot.style.cssText = `width:${Math.round(10 * L.scale)}px;height:${Math.round(10 * L.scale)}px;border-radius:2px;background:${palette[i % palette.length]};flex-shrink:0;`;
399
+ item.appendChild(dot);
400
+ const lbl = document.createElement('span');
401
+ lbl.textContent = sk.label || sk.key;
402
+ item.appendChild(lbl);
403
+ legendDiv.appendChild(item);
404
+ });
405
+ cardEl.appendChild(legendDiv);
347
406
  }
407
+ // Trend footer
408
+ this._renderTrend(cardEl, L.scale, L.padding);
348
409
  }
349
- _buildVertical(cardEl, L, id, values, palette, ticks, animate, stagger, showValues) {
350
- const areaEl = mkDiv(`${id}-area`, `display: flex; flex-direction: row; align-items: flex-end; height: ${L.maxBarLength}px;`);
351
- cardEl.appendChild(areaEl);
352
- const yaxisEl = mkDiv(`${id}-yaxis`, `width: ${L.yAxisWidth}px; height: ${L.maxBarLength}px; position: relative; flex-shrink: 0;`);
353
- areaEl.appendChild(yaxisEl);
354
- for (let t = 0; t <= ticks; t++) {
355
- const val = Math.round((L.maxVal / ticks) * t);
356
- const bottom = Math.round((val / L.maxVal) * L.maxBarLength);
357
- const tickEl = mkDiv(`${id}-ytick-${t}`, `position: absolute; bottom: ${bottom}px; right: 4px; font-size: ${L.tickSize}px; color: ${TOKENS.muted}; line-height: 1; transform: translateY(50%);`);
358
- tickEl.textContent = formatTick(val);
359
- yaxisEl.appendChild(tickEl);
360
- }
361
- const barsEl = mkDiv(`${id}-bars`, `display: flex; flex-direction: row; align-items: flex-end; gap: ${L.gap}px; flex: 1; height: ${L.maxBarLength}px; border-bottom: 1px solid ${TOKENS.grid}; border-left: 1px solid ${TOKENS.grid}; padding: 0 ${L.gap}px; position: relative;`);
362
- areaEl.appendChild(barsEl);
363
- for (let t = 1; t <= ticks; t++) {
364
- const val = Math.round((L.maxVal / ticks) * t);
365
- const bottom = Math.round((val / L.maxVal) * L.maxBarLength);
366
- barsEl.appendChild(mkDiv(`${id}-hgrid-${t}`, `position: absolute; left: 0; right: 0; bottom: ${bottom}px; height: 1px; background: ${TOKENS.grid};`));
367
- }
368
- values.forEach((v, i) => {
369
- const color = palette[i % palette.length];
370
- const barHeight = Math.max(2, Math.round((v.x / L.maxVal) * L.maxBarLength));
371
- const delay = animate ? `animation-delay: ${i * stagger}ms;` : '';
372
- const barClass = animate ? 'jux-bar-v' : '';
373
- const colEl = mkDiv(`${id}-col-${i}`, `display: flex; flex-direction: column; align-items: center; justify-content: flex-end; flex: 1; min-width: 0; height: 100%; border-radius: 4px; padding: 2px;`);
374
- barsEl.appendChild(colEl);
375
- if (showValues) {
376
- const valDelay = animate ? `animation-delay: ${i * stagger + 200}ms;` : '';
377
- const valClass = animate ? 'jux-bar-label' : '';
378
- const valEl = mkDiv(`${id}-val-${i}`, `font-size: ${L.tickSize}px; color: ${TOKENS.muted}; margin-bottom: 2px; text-align: center; ${valDelay}`, valClass);
379
- valEl.textContent = String(v.x);
380
- colEl.appendChild(valEl);
381
- }
382
- const barEl = mkDiv(`${id}-bar-${i}`, `width: 100%; max-width: ${L.barThickness}px; height: ${barHeight}px; background-color: ${color}; border-radius: ${L.barRadius}px ${L.barRadius}px 0 0; opacity: 0.85; transition: opacity 0.15s ease; ${delay}`, barClass);
383
- colEl.appendChild(barEl);
384
- if (this._isInteractive)
385
- this._wireInteractivity(colEl, barEl, i);
386
- });
387
- const xlabelsEl = mkDiv(`${id}-xlabels`, `display: flex; flex-direction: row; gap: ${L.gap}px; padding-left: ${L.yAxisWidth}px; padding-top: ${Math.round(6 * L.scale)}px;`);
388
- cardEl.appendChild(xlabelsEl);
389
- values.forEach((v, i) => {
390
- const label = v.label || `#${i + 1}`;
391
- const lblDelay = animate ? `animation-delay: ${i * stagger}ms;` : '';
392
- const lblClass = animate ? 'jux-bar-label' : '';
393
- const lblEl = mkDiv(`${id}-lbl-${i}`, `flex: 1; text-align: center; font-size: ${L.tickSize}px; color: ${TOKENS.muted}; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 0 2px; ${lblDelay}`, lblClass);
394
- lblEl.textContent = label;
395
- xlabelsEl.appendChild(lblEl);
396
- });
397
- }
398
- _buildHorizontal(cardEl, L, id, values, palette, ticks, animate, stagger, showValues) {
399
- const areaEl = mkDiv(`${id}-area`, `display: flex; flex-direction: column; gap: 0; width: 100%;`);
400
- cardEl.appendChild(areaEl);
401
- values.forEach((v, i) => {
402
- const label = v.label || `#${i + 1}`;
403
- const barWidth = Math.max(2, Math.round((v.x / L.maxVal) * L.maxBarLength));
404
- const color = palette[i % palette.length];
405
- const delay = animate ? `animation-delay: ${i * stagger}ms;` : '';
406
- const barClass = animate ? 'jux-bar-h' : '';
407
- const rowEl = mkDiv(`${id}-row-${i}`, `display: flex; flex-direction: row; align-items: center; height: ${L.barThickness}px; margin-bottom: ${L.gap}px; border-bottom: 1px solid ${TOKENS.grid}; padding-bottom: ${L.gap}px; border-radius: 4px; padding: 2px;`);
408
- areaEl.appendChild(rowEl);
409
- const isLink = v.drill || v.href;
410
- const lblDelay = animate ? `animation-delay: ${i * stagger}ms;` : '';
411
- const lblClass = animate ? 'jux-bar-label' : '';
412
- const lblEl = mkDiv(`${id}-lbl-${i}`, `width: ${L.labelWidth}px; flex-shrink: 0; text-align: right; padding-right: ${Math.round(10 * L.scale)}px; font-size: ${L.labelSize}px; color: ${TOKENS.muted}; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; box-sizing: border-box; ${lblDelay}`, lblClass);
413
- lblEl.textContent = label;
414
- rowEl.appendChild(lblEl);
415
- const barEl = mkDiv(`${id}-bar-${i}`, `width: ${barWidth}px; height: ${L.barThickness}px; background-color: ${color}; border-radius: ${L.barRadius}px; opacity: 0.85; transition: opacity 0.15s ease; ${delay}`, barClass);
416
- rowEl.appendChild(barEl);
417
- if (showValues) {
418
- const valDelay = animate ? `animation-delay: ${i * stagger + 200}ms;` : '';
419
- const valClass = animate ? 'jux-bar-label' : '';
420
- const valEl = mkDiv(`${id}-val-${i}`, `font-size: ${L.tickSize}px; color: ${TOKENS.muted}; margin-left: ${Math.round(6 * L.scale)}px; ${valDelay}`, valClass);
421
- valEl.textContent = String(v.x);
422
- rowEl.appendChild(valEl);
423
- }
424
- if (this._isInteractive)
425
- this._wireInteractivity(rowEl, barEl, i);
426
- });
427
- const xaxisEl = mkDiv(`${id}-xaxis`, `display: flex; flex-direction: row; justify-content: space-between; padding-left: ${L.labelWidth}px; padding-top: ${Math.round(4 * L.scale)}px;`);
428
- cardEl.appendChild(xaxisEl);
429
- for (let t = 0; t <= ticks; t++) {
430
- const val = Math.round((L.maxVal / ticks) * t);
431
- const tickEl = mkDiv(`${id}-xtick-${t}`, `font-size: ${L.tickSize}px; color: ${TOKENS.muted};`);
432
- tickEl.textContent = formatTick(val);
433
- xaxisEl.appendChild(tickEl);
410
+ _renderTrend(card, scale, padding) {
411
+ const { trendTitle, trendSubtitle, trendIcon, autoTrend } = this._opts;
412
+ let title = trendTitle;
413
+ let subtitle = trendSubtitle;
414
+ let icon = trendIcon;
415
+ if (autoTrend && !title) {
416
+ const sk = this._seriesKeys[0];
417
+ const vals = this._values.map(v => v[sk.key] ?? 0);
418
+ const labels = this._values.map(v => v.label || '');
419
+ const trend = computeTrend(vals, labels);
420
+ title = trend.title;
421
+ subtitle = subtitle || trend.subtitle;
422
+ icon = icon || trend.icon;
423
+ }
424
+ if (!title && !subtitle)
425
+ return;
426
+ const fontSize = Math.max(11, Math.round(13 * scale));
427
+ const subFontSize = Math.max(10, Math.round(11 * scale));
428
+ const footer = document.createElement('div');
429
+ footer.style.cssText = `padding-top:${Math.round(8 * scale)}px;border-top:1px solid hsl(220,13%,93%);margin-top:${Math.round(8 * scale)}px;`;
430
+ if (title) {
431
+ const titleEl = document.createElement('div');
432
+ titleEl.style.cssText = `font-size:${fontSize}px;font-weight:500;color:hsl(222,47%,11%);line-height:1.4;`;
433
+ titleEl.textContent = `${title}${icon ? ' ' + icon : ''}`;
434
+ footer.appendChild(titleEl);
435
+ }
436
+ if (subtitle) {
437
+ const subEl = document.createElement('div');
438
+ subEl.style.cssText = `font-size:${subFontSize}px;color:hsl(215,16%,47%);line-height:1.4;margin-top:1px;`;
439
+ subEl.textContent = subtitle;
440
+ footer.appendChild(subEl);
434
441
  }
442
+ card.appendChild(footer);
435
443
  }
436
444
  // ═══════════════════════════════════════════════════════════
437
445
  // PUBLIC API
@@ -459,22 +467,25 @@ class BarChart {
459
467
  this._buildChart(containerWidth);
460
468
  if (this._opts.responsive && typeof ResizeObserver !== 'undefined') {
461
469
  let resizeTimer;
470
+ let skipCount = 2;
462
471
  const ro = new ResizeObserver(() => {
472
+ if (skipCount > 0) {
473
+ skipCount--;
474
+ return;
475
+ }
463
476
  clearTimeout(resizeTimer);
464
477
  resizeTimer = setTimeout(() => {
465
478
  const newWidth = this._wrapperEl.clientWidth;
466
479
  if (newWidth > 0)
467
480
  this._buildChart(newWidth);
468
- }, 100);
481
+ }, 200);
469
482
  });
470
483
  ro.observe(this._wrapperEl);
471
484
  this._resizeObserver = ro;
472
485
  }
473
486
  return this;
474
487
  }
475
- into(target) {
476
- return this.render(target);
477
- }
488
+ into(target) { return this.render(target); }
478
489
  on(event, fn) {
479
490
  if (!this._listeners[event])
480
491
  this._listeners[event] = [];
@@ -482,9 +493,8 @@ class BarChart {
482
493
  return this;
483
494
  }
484
495
  off(event, fn) {
485
- if (this._listeners[event]) {
496
+ if (this._listeners[event])
486
497
  this._listeners[event] = this._listeners[event].filter(f => f !== fn);
487
- }
488
498
  return this;
489
499
  }
490
500
  select(index) {
@@ -504,6 +514,7 @@ class BarChart {
504
514
  destroy() {
505
515
  if (this._resizeObserver)
506
516
  this._resizeObserver.disconnect();
517
+ pageState.__unregister(this.id);
507
518
  this._wrapperEl.remove();
508
519
  }
509
520
  }
@@ -517,4 +528,28 @@ export function barChart(id, values, options = {}) {
517
528
  }
518
529
  export { BarChart, RATIOS, TOKENS };
519
530
  export default barChart;
531
+ function detectSeriesKeys(values, series) {
532
+ if (series && series.length > 0) {
533
+ return series;
534
+ }
535
+ if (values.length === 0) {
536
+ return [{ key: 'x', label: 'Value' }];
537
+ }
538
+ const firstItem = values[0];
539
+ const numericKeys = Object.keys(firstItem).filter(key => key !== 'label' && key !== 'drill' && key !== 'href' && typeof firstItem[key] === 'number');
540
+ if (numericKeys.length === 0) {
541
+ return [{ key: 'x', label: 'Value' }];
542
+ }
543
+ return numericKeys.map(key => ({
544
+ key,
545
+ label: key.charAt(0).toUpperCase() + key.slice(1),
546
+ }));
547
+ }
548
+ const DEFAULT_SERIES_COLORS = [
549
+ 'hsl(217, 91%, 60%)',
550
+ 'hsl(142, 71%, 45%)',
551
+ 'hsl(25, 95%, 53%)',
552
+ 'hsl(271, 81%, 56%)',
553
+ 'hsl(48, 96%, 53%)',
554
+ ];
520
555
  //# sourceMappingURL=barChart.js.map