juxscript 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/lib/components/areachart.ts +1246 -0
- package/lib/components/areachartsmooth.ts +1380 -0
- package/lib/components/barchart.ts +16 -14
- package/lib/components/docs-data.json +796 -106
- package/lib/components/doughnutchart.ts +1191 -0
- package/lib/components/kpicard.ts +605 -0
- package/lib/jux.ts +25 -3
- package/package.json +1 -1
|
@@ -0,0 +1,1380 @@
|
|
|
1
|
+
import { getOrCreateContainer } from './helpers.js';
|
|
2
|
+
import { State } from '../reactivity/state.js';
|
|
3
|
+
import {
|
|
4
|
+
googleTheme,
|
|
5
|
+
seriesaTheme,
|
|
6
|
+
hrTheme,
|
|
7
|
+
figmaTheme,
|
|
8
|
+
notionTheme,
|
|
9
|
+
chalkTheme,
|
|
10
|
+
mintTheme
|
|
11
|
+
} from '../themes/charts.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Bar chart data point
|
|
15
|
+
*/
|
|
16
|
+
export interface AreaChartSmoothDataPoint {
|
|
17
|
+
label: string;
|
|
18
|
+
value: number;
|
|
19
|
+
color?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Bar chart options
|
|
24
|
+
*/
|
|
25
|
+
export interface AreaChartSmoothOptions {
|
|
26
|
+
data?: AreaChartSmoothDataPoint[];
|
|
27
|
+
title?: string;
|
|
28
|
+
subtitle?: string;
|
|
29
|
+
xAxisLabel?: string;
|
|
30
|
+
yAxisLabel?: string;
|
|
31
|
+
showTicksX?: boolean;
|
|
32
|
+
showTicksY?: boolean;
|
|
33
|
+
showScaleX?: boolean;
|
|
34
|
+
showScaleY?: boolean;
|
|
35
|
+
scaleXUnit?: string;
|
|
36
|
+
scaleYUnit?: string;
|
|
37
|
+
showLegend?: boolean;
|
|
38
|
+
legendOrientation?: 'horizontal' | 'vertical';
|
|
39
|
+
showDataTable?: boolean;
|
|
40
|
+
showDataLabels?: boolean;
|
|
41
|
+
animate?: boolean;
|
|
42
|
+
animationDuration?: number;
|
|
43
|
+
chartOrientation?: 'vertical' | 'horizontal'; // NEW
|
|
44
|
+
chartDirection?: 'normal' | 'reverse'; // NEW: normal = bottom-to-top or left-to-right, reverse = opposite
|
|
45
|
+
width?: number;
|
|
46
|
+
height?: number;
|
|
47
|
+
colors?: string[];
|
|
48
|
+
class?: string;
|
|
49
|
+
style?: string;
|
|
50
|
+
theme?: 'google' | 'seriesa' | 'hr' | 'figma' | 'notion' | 'chalk' | 'mint';
|
|
51
|
+
styleMode?: 'default' | 'gradient' | 'glow' | 'glass';
|
|
52
|
+
borderRadius?: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Bar chart state
|
|
57
|
+
*/
|
|
58
|
+
type AreaChartSmoothState = {
|
|
59
|
+
data: AreaChartSmoothDataPoint[];
|
|
60
|
+
title: string;
|
|
61
|
+
subtitle: string;
|
|
62
|
+
xAxisLabel: string;
|
|
63
|
+
yAxisLabel: string;
|
|
64
|
+
showTicksX: boolean;
|
|
65
|
+
showTicksY: boolean;
|
|
66
|
+
showScaleX: boolean;
|
|
67
|
+
showScaleY: boolean;
|
|
68
|
+
scaleXUnit: string;
|
|
69
|
+
scaleYUnit: string;
|
|
70
|
+
showLegend: boolean;
|
|
71
|
+
legendOrientation: 'horizontal' | 'vertical';
|
|
72
|
+
showDataTable: boolean;
|
|
73
|
+
showDataLabels: boolean;
|
|
74
|
+
animate: boolean;
|
|
75
|
+
animationDuration: number;
|
|
76
|
+
chartOrientation: 'vertical' | 'horizontal'; // NEW
|
|
77
|
+
chartDirection: 'normal' | 'reverse'; // NEW
|
|
78
|
+
width: number;
|
|
79
|
+
height: number;
|
|
80
|
+
colors: string[];
|
|
81
|
+
class: string;
|
|
82
|
+
style: string;
|
|
83
|
+
theme?: 'google' | 'seriesa' | 'hr' | 'figma' | 'notion' | 'chalk' | 'mint';
|
|
84
|
+
styleMode: 'default' | 'gradient' | 'glow' | 'glass';
|
|
85
|
+
borderRadius: number;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Bar chart component - Simple SVG-based bar chart
|
|
90
|
+
*
|
|
91
|
+
* Usage:
|
|
92
|
+
* jux.areachartsmooth('sales-chart')
|
|
93
|
+
* .data([
|
|
94
|
+
* { label: 'Jan', value: 100 },
|
|
95
|
+
* { label: 'Feb', value: 150 },
|
|
96
|
+
* { label: 'Mar', value: 200 }
|
|
97
|
+
* ])
|
|
98
|
+
* .title('Monthly Sales')
|
|
99
|
+
* .showLegend(true)
|
|
100
|
+
* .render('#app');
|
|
101
|
+
*/
|
|
102
|
+
export class AreaChartSmooth {
|
|
103
|
+
state: AreaChartSmoothState;
|
|
104
|
+
container: HTMLElement | null = null;
|
|
105
|
+
_id: string;
|
|
106
|
+
id: string;
|
|
107
|
+
|
|
108
|
+
// State bindings
|
|
109
|
+
private _boundTheme?: State<string>;
|
|
110
|
+
private _boundStyleMode?: State<string>;
|
|
111
|
+
private _boundBorderRadius?: State<number>;
|
|
112
|
+
|
|
113
|
+
constructor(id: string, options: AreaChartSmoothOptions = {}) {
|
|
114
|
+
this._id = id;
|
|
115
|
+
this.id = id;
|
|
116
|
+
|
|
117
|
+
let defaultColors = [
|
|
118
|
+
'#3b82f6', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6',
|
|
119
|
+
'#ec4899', '#06b6d4', '#f97316', '#84cc16', '#6366f1'
|
|
120
|
+
];
|
|
121
|
+
// using defaultColors, select a random color from the list.
|
|
122
|
+
const randomColor = defaultColors[Math.floor(Math.random() * defaultColors.length)];
|
|
123
|
+
defaultColors = [randomColor];
|
|
124
|
+
|
|
125
|
+
this.state = {
|
|
126
|
+
data: options.data ?? [],
|
|
127
|
+
title: options.title ?? '',
|
|
128
|
+
subtitle: options.subtitle ?? '',
|
|
129
|
+
xAxisLabel: options.xAxisLabel ?? '',
|
|
130
|
+
yAxisLabel: options.yAxisLabel ?? '',
|
|
131
|
+
showTicksX: options.showTicksX ?? true,
|
|
132
|
+
showTicksY: options.showTicksY ?? true,
|
|
133
|
+
showScaleX: options.showScaleX ?? true,
|
|
134
|
+
showScaleY: options.showScaleY ?? true,
|
|
135
|
+
scaleXUnit: options.scaleXUnit ?? '',
|
|
136
|
+
scaleYUnit: options.scaleYUnit ?? '',
|
|
137
|
+
showLegend: options.showLegend ?? false,
|
|
138
|
+
legendOrientation: options.legendOrientation ?? 'horizontal',
|
|
139
|
+
showDataTable: options.showDataTable ?? false,
|
|
140
|
+
showDataLabels: options.showDataLabels ?? true,
|
|
141
|
+
animate: options.animate ?? true,
|
|
142
|
+
animationDuration: options.animationDuration ?? 800,
|
|
143
|
+
chartOrientation: options.chartOrientation ?? 'vertical', // NEW
|
|
144
|
+
chartDirection: options.chartDirection ?? 'normal', // NEW
|
|
145
|
+
width: options.width ?? 600,
|
|
146
|
+
height: options.height ?? 400,
|
|
147
|
+
colors: options.colors ?? defaultColors,
|
|
148
|
+
class: options.class ?? '',
|
|
149
|
+
style: options.style ?? '',
|
|
150
|
+
theme: options.theme,
|
|
151
|
+
styleMode: options.styleMode ?? 'default',
|
|
152
|
+
borderRadius: options.borderRadius ?? 4
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* -------------------------
|
|
157
|
+
* State Binding Methods
|
|
158
|
+
* ------------------------- */
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Bind theme to reactive state
|
|
162
|
+
*/
|
|
163
|
+
bindTheme(stateObj: State<string>): this {
|
|
164
|
+
this._boundTheme = stateObj;
|
|
165
|
+
|
|
166
|
+
stateObj.subscribe((val) => {
|
|
167
|
+
this.theme(val as any);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
return this;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Bind styleMode to reactive state
|
|
175
|
+
*/
|
|
176
|
+
bindStyleMode(stateObj: State<string>): this {
|
|
177
|
+
this._boundStyleMode = stateObj;
|
|
178
|
+
|
|
179
|
+
stateObj.subscribe((val) => {
|
|
180
|
+
this.styleMode(val as any);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
return this;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Bind borderRadius to reactive state
|
|
188
|
+
*/
|
|
189
|
+
bindBorderRadius(stateObj: State<number>): this {
|
|
190
|
+
this._boundBorderRadius = stateObj;
|
|
191
|
+
|
|
192
|
+
stateObj.subscribe((val) => {
|
|
193
|
+
this.borderRadius(val);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
return this;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/* -------------------------
|
|
200
|
+
* Fluent API
|
|
201
|
+
* ------------------------- */
|
|
202
|
+
|
|
203
|
+
data(value: AreaChartSmoothDataPoint[]): this {
|
|
204
|
+
this.state.data = value;
|
|
205
|
+
this._updateChart();
|
|
206
|
+
return this;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
title(value: string): this {
|
|
210
|
+
this.state.title = value;
|
|
211
|
+
this._updateChart();
|
|
212
|
+
return this;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
subtitle(value: string): this {
|
|
216
|
+
this.state.subtitle = value;
|
|
217
|
+
this._updateChart();
|
|
218
|
+
return this;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
xAxisLabel(value: string): this {
|
|
222
|
+
this.state.xAxisLabel = value;
|
|
223
|
+
this._updateChart();
|
|
224
|
+
return this;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
yAxisLabel(value: string): this {
|
|
228
|
+
this.state.yAxisLabel = value;
|
|
229
|
+
this._updateChart();
|
|
230
|
+
return this;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
showTicksX(value: boolean): this {
|
|
234
|
+
this.state.showTicksX = value;
|
|
235
|
+
this._updateChart();
|
|
236
|
+
return this;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
showTicksY(value: boolean): this {
|
|
240
|
+
this.state.showTicksY = value;
|
|
241
|
+
this._updateChart();
|
|
242
|
+
return this;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
showScaleX(value: boolean): this {
|
|
246
|
+
this.state.showScaleX = value;
|
|
247
|
+
this._updateChart();
|
|
248
|
+
return this;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
showScaleY(value: boolean): this {
|
|
252
|
+
this.state.showScaleY = value;
|
|
253
|
+
this._updateChart();
|
|
254
|
+
return this;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
scaleXUnit(value: string): this {
|
|
258
|
+
this.state.scaleXUnit = value;
|
|
259
|
+
this._updateChart();
|
|
260
|
+
return this;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
scaleYUnit(value: string): this {
|
|
264
|
+
this.state.scaleYUnit = value;
|
|
265
|
+
this._updateChart();
|
|
266
|
+
return this;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
showLegend(value: boolean): this {
|
|
270
|
+
this.state.showLegend = value;
|
|
271
|
+
this._updateChart();
|
|
272
|
+
return this;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
legendOrientation(value: 'horizontal' | 'vertical'): this {
|
|
276
|
+
this.state.legendOrientation = value;
|
|
277
|
+
this._updateChart();
|
|
278
|
+
return this;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
showDataTable(value: boolean): this {
|
|
282
|
+
this.state.showDataTable = value;
|
|
283
|
+
this._updateChart();
|
|
284
|
+
return this;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Show/hide value labels on bars
|
|
289
|
+
*/
|
|
290
|
+
showDataLabels(value: boolean): this {
|
|
291
|
+
this.state.showDataLabels = value;
|
|
292
|
+
this._updateChart();
|
|
293
|
+
return this;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Enable/disable bar grow animation
|
|
298
|
+
*/
|
|
299
|
+
animate(value: boolean): this {
|
|
300
|
+
this.state.animate = value;
|
|
301
|
+
this._updateChart();
|
|
302
|
+
return this;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Set animation duration in milliseconds
|
|
307
|
+
*/
|
|
308
|
+
animationDuration(value: number): this {
|
|
309
|
+
this.state.animationDuration = value;
|
|
310
|
+
this._updateChart();
|
|
311
|
+
return this;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Set chart orientation (vertical bars or horizontal bars)
|
|
316
|
+
*/
|
|
317
|
+
chartOrientation(value: 'vertical' | 'horizontal'): this {
|
|
318
|
+
this.state.chartOrientation = value;
|
|
319
|
+
this._updateChart();
|
|
320
|
+
return this;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Set chart direction (normal or reverse)
|
|
325
|
+
* For vertical: normal = bottom-to-top, reverse = top-to-bottom
|
|
326
|
+
* For horizontal: normal = left-to-right, reverse = right-to-left
|
|
327
|
+
*/
|
|
328
|
+
chartDirection(value: 'normal' | 'reverse'): this {
|
|
329
|
+
this.state.chartDirection = value;
|
|
330
|
+
this._updateChart();
|
|
331
|
+
return this;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
width(value: number): this {
|
|
335
|
+
this.state.width = value;
|
|
336
|
+
this._updateChart();
|
|
337
|
+
return this;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
height(value: number): this {
|
|
341
|
+
this.state.height = value;
|
|
342
|
+
this._updateChart();
|
|
343
|
+
return this;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
colors(value: string[]): this {
|
|
347
|
+
this.state.colors = value;
|
|
348
|
+
this._updateChart();
|
|
349
|
+
return this;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
class(value: string): this {
|
|
353
|
+
this.state.class = value;
|
|
354
|
+
return this;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
style(value: string): this {
|
|
358
|
+
this.state.style = value;
|
|
359
|
+
return this;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Set chart theme
|
|
364
|
+
*/
|
|
365
|
+
theme(value: 'google' | 'seriesa' | 'hr' | 'figma' | 'notion' | 'chalk' | 'mint'): this {
|
|
366
|
+
this.state.theme = value;
|
|
367
|
+
this._applyTheme(value);
|
|
368
|
+
this._updateChart();
|
|
369
|
+
return this;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Set bar style mode
|
|
374
|
+
*/
|
|
375
|
+
styleMode(value: 'default' | 'gradient' | 'glow' | 'glass'): this {
|
|
376
|
+
this.state.styleMode = value;
|
|
377
|
+
this._updateChart();
|
|
378
|
+
return this;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Set border radius for bars (0 = sharp corners, higher = rounder)
|
|
383
|
+
*/
|
|
384
|
+
borderRadius(value: number): this {
|
|
385
|
+
this.state.borderRadius = value;
|
|
386
|
+
this._updateChart();
|
|
387
|
+
return this;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/* -------------------------
|
|
391
|
+
* Update chart
|
|
392
|
+
* ------------------------- */
|
|
393
|
+
|
|
394
|
+
private _updateChart(): void {
|
|
395
|
+
if (!this.container) return;
|
|
396
|
+
|
|
397
|
+
// Find the wrapper div
|
|
398
|
+
const wrapper = this.container.querySelector(`#${this._id}`) as HTMLElement;
|
|
399
|
+
if (!wrapper) return;
|
|
400
|
+
|
|
401
|
+
// Clear and rebuild
|
|
402
|
+
wrapper.innerHTML = '';
|
|
403
|
+
this._buildChart(wrapper);
|
|
404
|
+
|
|
405
|
+
// Reapply theme after rebuild
|
|
406
|
+
if (this.state.theme) {
|
|
407
|
+
this._applyThemeToWrapper(wrapper);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
private _buildChart(wrapper: HTMLElement): void {
|
|
412
|
+
const { data, title, subtitle, width, height, showLegend, showDataTable } = this.state;
|
|
413
|
+
|
|
414
|
+
// Title
|
|
415
|
+
if (title) {
|
|
416
|
+
const titleEl = document.createElement('h3');
|
|
417
|
+
titleEl.className = 'jux-areachartsmooth-title';
|
|
418
|
+
titleEl.textContent = title;
|
|
419
|
+
wrapper.appendChild(titleEl);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Subtitle
|
|
423
|
+
if (subtitle) {
|
|
424
|
+
const subtitleEl = document.createElement('p');
|
|
425
|
+
subtitleEl.className = 'jux-areachartsmooth-subtitle';
|
|
426
|
+
subtitleEl.textContent = subtitle;
|
|
427
|
+
wrapper.appendChild(subtitleEl);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// SVG Chart
|
|
431
|
+
const svg = this._createSVG();
|
|
432
|
+
wrapper.appendChild(svg);
|
|
433
|
+
|
|
434
|
+
// Legend
|
|
435
|
+
if (showLegend) {
|
|
436
|
+
const legend = this._createLegend();
|
|
437
|
+
wrapper.appendChild(legend);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Data Table
|
|
441
|
+
if (showDataTable) {
|
|
442
|
+
const table = this._createDataTable();
|
|
443
|
+
wrapper.appendChild(table);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
private _createSVG(): SVGSVGElement {
|
|
448
|
+
const {
|
|
449
|
+
data, width, height, colors, xAxisLabel, yAxisLabel,
|
|
450
|
+
showTicksX, showTicksY, showScaleX, showScaleY, scaleYUnit,
|
|
451
|
+
styleMode, borderRadius, showDataLabels, animate, animationDuration,
|
|
452
|
+
chartOrientation, chartDirection
|
|
453
|
+
} = this.state;
|
|
454
|
+
|
|
455
|
+
if (!data.length) {
|
|
456
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
457
|
+
svg.setAttribute('width', width.toString());
|
|
458
|
+
svg.setAttribute('height', height.toString());
|
|
459
|
+
svg.setAttribute('class', 'jux-areachartsmooth-svg');
|
|
460
|
+
return svg;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
464
|
+
svg.setAttribute('width', width.toString());
|
|
465
|
+
svg.setAttribute('height', height.toString());
|
|
466
|
+
svg.setAttribute('class', 'jux-areachartsmooth-svg');
|
|
467
|
+
|
|
468
|
+
// Add animation styles to SVG
|
|
469
|
+
if (animate) {
|
|
470
|
+
const animationId = `bar-grow-${this._id}`;
|
|
471
|
+
const style = document.createElementNS('http://www.w3.org/2000/svg', 'style');
|
|
472
|
+
|
|
473
|
+
// Different animation based on orientation and direction
|
|
474
|
+
let transformOrigin = 'bottom';
|
|
475
|
+
let scaleAxis = 'scaleY';
|
|
476
|
+
|
|
477
|
+
if (chartOrientation === 'horizontal') {
|
|
478
|
+
if (chartDirection === 'normal') {
|
|
479
|
+
transformOrigin = 'left';
|
|
480
|
+
scaleAxis = 'scaleX';
|
|
481
|
+
} else {
|
|
482
|
+
transformOrigin = 'right';
|
|
483
|
+
scaleAxis = 'scaleX';
|
|
484
|
+
}
|
|
485
|
+
} else {
|
|
486
|
+
if (chartDirection === 'reverse') {
|
|
487
|
+
transformOrigin = 'top';
|
|
488
|
+
scaleAxis = 'scaleY';
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
style.textContent = `
|
|
493
|
+
@keyframes ${animationId} {
|
|
494
|
+
from {
|
|
495
|
+
transform: ${scaleAxis}(0);
|
|
496
|
+
opacity: 0;
|
|
497
|
+
}
|
|
498
|
+
to {
|
|
499
|
+
transform: ${scaleAxis}(1);
|
|
500
|
+
opacity: 1;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
.jux-bar-animated {
|
|
504
|
+
transform-origin: ${transformOrigin};
|
|
505
|
+
animation: ${animationId} ${animationDuration}ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
|
506
|
+
}
|
|
507
|
+
.jux-label-animated {
|
|
508
|
+
opacity: 0;
|
|
509
|
+
animation: fadeIn ${animationDuration}ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
|
510
|
+
}
|
|
511
|
+
@keyframes fadeIn {
|
|
512
|
+
from { opacity: 0; }
|
|
513
|
+
to { opacity: 1; }
|
|
514
|
+
}
|
|
515
|
+
`;
|
|
516
|
+
svg.appendChild(style);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Calculate dimensions
|
|
520
|
+
const padding = { top: 40, right: 40, bottom: 60, left: 60 };
|
|
521
|
+
const chartWidth = width - padding.left - padding.right;
|
|
522
|
+
const chartHeight = height - padding.top - padding.bottom;
|
|
523
|
+
|
|
524
|
+
const maxValue = Math.max(...data.map(d => d.value));
|
|
525
|
+
|
|
526
|
+
// Determine if we're working with vertical or horizontal layout
|
|
527
|
+
const isVertical = chartOrientation === 'vertical';
|
|
528
|
+
const isReverse = chartDirection === 'reverse';
|
|
529
|
+
|
|
530
|
+
if (isVertical) {
|
|
531
|
+
// VERTICAL BARS (original logic with direction support)
|
|
532
|
+
this._renderVerticalBars(svg, data, colors, padding, chartWidth, chartHeight, maxValue, isReverse);
|
|
533
|
+
} else {
|
|
534
|
+
// HORIZONTAL BARS (new logic)
|
|
535
|
+
this._renderHorizontalBars(svg, data, colors, padding, chartWidth, chartHeight, maxValue, isReverse);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return svg;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
private _renderVerticalBars(
|
|
542
|
+
svg: SVGSVGElement,
|
|
543
|
+
data: AreaChartSmoothDataPoint[],
|
|
544
|
+
colors: string[],
|
|
545
|
+
padding: { top: number, right: number, bottom: number, left: number },
|
|
546
|
+
chartWidth: number,
|
|
547
|
+
chartHeight: number,
|
|
548
|
+
maxValue: number,
|
|
549
|
+
isReverse: boolean
|
|
550
|
+
): void {
|
|
551
|
+
const { width, height, xAxisLabel, yAxisLabel, showTicksX, showTicksY, showScaleX, showScaleY, scaleYUnit, styleMode, showDataLabels, animate, animationDuration } = this.state;
|
|
552
|
+
|
|
553
|
+
const yScale = chartHeight / maxValue;
|
|
554
|
+
// Change spacing calculation to use data.length - 1 for even distribution
|
|
555
|
+
const spacing = chartWidth / (data.length - 1);
|
|
556
|
+
|
|
557
|
+
// Y-axis
|
|
558
|
+
if (showScaleY) {
|
|
559
|
+
const yAxis = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
560
|
+
yAxis.setAttribute('x1', padding.left.toString());
|
|
561
|
+
yAxis.setAttribute('y1', padding.top.toString());
|
|
562
|
+
yAxis.setAttribute('x2', padding.left.toString());
|
|
563
|
+
yAxis.setAttribute('y2', (height - padding.bottom).toString());
|
|
564
|
+
yAxis.setAttribute('stroke', '#9ca3af');
|
|
565
|
+
yAxis.setAttribute('stroke-width', '2');
|
|
566
|
+
svg.appendChild(yAxis);
|
|
567
|
+
|
|
568
|
+
if (yAxisLabel && showTicksY) {
|
|
569
|
+
const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
570
|
+
label.setAttribute('x', '20');
|
|
571
|
+
label.setAttribute('y', (padding.top + chartHeight / 2).toString());
|
|
572
|
+
label.setAttribute('text-anchor', 'middle');
|
|
573
|
+
label.setAttribute('transform', `rotate(-90, 20, ${padding.top + chartHeight / 2})`);
|
|
574
|
+
label.setAttribute('fill', '#6b7280');
|
|
575
|
+
label.setAttribute('font-size', '12');
|
|
576
|
+
label.setAttribute('font-weight', '500');
|
|
577
|
+
label.setAttribute('font-family', 'inherit');
|
|
578
|
+
label.textContent = yAxisLabel;
|
|
579
|
+
svg.appendChild(label);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (showTicksY) {
|
|
583
|
+
const numTicks = 5;
|
|
584
|
+
for (let i = 0; i <= numTicks; i++) {
|
|
585
|
+
const value = (maxValue / numTicks) * i;
|
|
586
|
+
const y = isReverse
|
|
587
|
+
? padding.top + (value * yScale)
|
|
588
|
+
: height - padding.bottom - (value * yScale);
|
|
589
|
+
|
|
590
|
+
const gridLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
591
|
+
gridLine.setAttribute('x1', padding.left.toString());
|
|
592
|
+
gridLine.setAttribute('y1', y.toString());
|
|
593
|
+
gridLine.setAttribute('x2', (width - padding.right).toString());
|
|
594
|
+
gridLine.setAttribute('y2', y.toString());
|
|
595
|
+
gridLine.setAttribute('stroke', '#e5e7eb');
|
|
596
|
+
gridLine.setAttribute('stroke-width', '1');
|
|
597
|
+
gridLine.setAttribute('stroke-dasharray', '4,4');
|
|
598
|
+
svg.appendChild(gridLine);
|
|
599
|
+
|
|
600
|
+
const tickLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
601
|
+
tickLabel.setAttribute('x', (padding.left - 10).toString());
|
|
602
|
+
tickLabel.setAttribute('y', (y + 4).toString());
|
|
603
|
+
tickLabel.setAttribute('text-anchor', 'end');
|
|
604
|
+
tickLabel.setAttribute('fill', '#6b7280');
|
|
605
|
+
tickLabel.setAttribute('font-size', '11');
|
|
606
|
+
tickLabel.setAttribute('font-family', 'inherit');
|
|
607
|
+
tickLabel.textContent = Math.round(value).toString() + (scaleYUnit || '');
|
|
608
|
+
svg.appendChild(tickLabel);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// X-axis
|
|
614
|
+
if (showScaleX) {
|
|
615
|
+
const xAxis = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
616
|
+
const axisY = isReverse ? padding.top : height - padding.bottom;
|
|
617
|
+
xAxis.setAttribute('x1', padding.left.toString());
|
|
618
|
+
xAxis.setAttribute('y1', axisY.toString());
|
|
619
|
+
xAxis.setAttribute('x2', (width - padding.right).toString());
|
|
620
|
+
xAxis.setAttribute('y2', axisY.toString());
|
|
621
|
+
xAxis.setAttribute('stroke', '#9ca3af');
|
|
622
|
+
xAxis.setAttribute('stroke-width', '2');
|
|
623
|
+
svg.appendChild(xAxis);
|
|
624
|
+
|
|
625
|
+
if (xAxisLabel && showTicksX) {
|
|
626
|
+
const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
627
|
+
label.setAttribute('x', (padding.left + chartWidth / 2).toString());
|
|
628
|
+
label.setAttribute('y', (height - 15).toString());
|
|
629
|
+
label.setAttribute('text-anchor', 'middle');
|
|
630
|
+
label.setAttribute('fill', '#6b7280');
|
|
631
|
+
label.setAttribute('font-size', '12');
|
|
632
|
+
label.setAttribute('font-weight', '500');
|
|
633
|
+
label.setAttribute('font-family', 'inherit');
|
|
634
|
+
label.textContent = xAxisLabel;
|
|
635
|
+
svg.appendChild(label);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Generate smooth path
|
|
640
|
+
const pathData = this._generateSmoothPath(data, padding, chartWidth, chartHeight, maxValue, yScale, spacing, isReverse, false);
|
|
641
|
+
const color = colors[0];
|
|
642
|
+
|
|
643
|
+
// Create area path with gradient fill
|
|
644
|
+
this._renderAreaPath(svg, pathData, color, isReverse ? padding.top : height - padding.bottom, animate);
|
|
645
|
+
|
|
646
|
+
// Add data point circles and labels
|
|
647
|
+
data.forEach((point, index) => {
|
|
648
|
+
// Changed calculation to place points at edges
|
|
649
|
+
const x = padding.left + (index * spacing);
|
|
650
|
+
const pointY = point.value * yScale;
|
|
651
|
+
const y = isReverse ? padding.top + pointY : height - padding.bottom - pointY;
|
|
652
|
+
|
|
653
|
+
// Data point circle
|
|
654
|
+
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
655
|
+
circle.setAttribute('cx', x.toString());
|
|
656
|
+
circle.setAttribute('cy', y.toString());
|
|
657
|
+
circle.setAttribute('r', '4');
|
|
658
|
+
circle.setAttribute('fill', 'white');
|
|
659
|
+
circle.setAttribute('stroke', color);
|
|
660
|
+
circle.setAttribute('stroke-width', '2');
|
|
661
|
+
|
|
662
|
+
if (animate) {
|
|
663
|
+
circle.classList.add('jux-label-animated');
|
|
664
|
+
circle.style.animationDelay = `${animationDuration}ms`;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
svg.appendChild(circle);
|
|
668
|
+
|
|
669
|
+
// X-axis label
|
|
670
|
+
if (showTicksX && showScaleX) {
|
|
671
|
+
const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
672
|
+
label.setAttribute('x', x.toString());
|
|
673
|
+
label.setAttribute('y', (height - padding.bottom + 20).toString());
|
|
674
|
+
label.setAttribute('text-anchor', 'middle');
|
|
675
|
+
label.setAttribute('fill', '#6b7280');
|
|
676
|
+
label.setAttribute('font-size', '11');
|
|
677
|
+
label.setAttribute('font-family', 'inherit');
|
|
678
|
+
label.textContent = point.label;
|
|
679
|
+
|
|
680
|
+
if (animate) {
|
|
681
|
+
label.classList.add('jux-label-animated');
|
|
682
|
+
label.style.animationDelay = `${index * 100 + 200}ms`;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
svg.appendChild(label);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// Value labels
|
|
689
|
+
if (showDataLabels) {
|
|
690
|
+
const valueLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
691
|
+
valueLabel.setAttribute('x', x.toString());
|
|
692
|
+
valueLabel.setAttribute('y', (y - 10).toString());
|
|
693
|
+
valueLabel.setAttribute('text-anchor', 'middle');
|
|
694
|
+
valueLabel.setAttribute('fill', '#374151');
|
|
695
|
+
valueLabel.setAttribute('font-size', '11');
|
|
696
|
+
valueLabel.setAttribute('font-weight', '600');
|
|
697
|
+
valueLabel.setAttribute('font-family', 'inherit');
|
|
698
|
+
valueLabel.textContent = point.value.toString();
|
|
699
|
+
|
|
700
|
+
if (animate) {
|
|
701
|
+
valueLabel.classList.add('jux-label-animated');
|
|
702
|
+
valueLabel.style.animationDelay = `${animationDuration}ms`;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
svg.appendChild(valueLabel);
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
private _renderHorizontalBars(
|
|
711
|
+
svg: SVGSVGElement,
|
|
712
|
+
data: AreaChartSmoothDataPoint[],
|
|
713
|
+
colors: string[],
|
|
714
|
+
padding: { top: number, right: number, bottom: number, left: number },
|
|
715
|
+
chartWidth: number,
|
|
716
|
+
chartHeight: number,
|
|
717
|
+
maxValue: number,
|
|
718
|
+
isReverse: boolean
|
|
719
|
+
): void {
|
|
720
|
+
const { width, height, xAxisLabel, yAxisLabel, showTicksX, showTicksY, showScaleX, showScaleY, scaleXUnit, showDataLabels, animate, animationDuration } = this.state;
|
|
721
|
+
|
|
722
|
+
const xScale = chartWidth / maxValue;
|
|
723
|
+
// Change spacing calculation to use data.length - 1 for even distribution
|
|
724
|
+
const spacing = chartHeight / (data.length - 1);
|
|
725
|
+
|
|
726
|
+
// X-axis (value axis)
|
|
727
|
+
if (showScaleX) {
|
|
728
|
+
const xAxis = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
729
|
+
const axisX = isReverse ? width - padding.right : padding.left;
|
|
730
|
+
xAxis.setAttribute('x1', axisX.toString());
|
|
731
|
+
xAxis.setAttribute('y1', padding.top.toString());
|
|
732
|
+
xAxis.setAttribute('x2', axisX.toString());
|
|
733
|
+
xAxis.setAttribute('y2', (height - padding.bottom).toString());
|
|
734
|
+
xAxis.setAttribute('stroke', '#9ca3af');
|
|
735
|
+
xAxis.setAttribute('stroke-width', '2');
|
|
736
|
+
svg.appendChild(xAxis);
|
|
737
|
+
|
|
738
|
+
if (xAxisLabel && showTicksX) {
|
|
739
|
+
const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
740
|
+
label.setAttribute('x', (padding.left + chartWidth / 2).toString());
|
|
741
|
+
label.setAttribute('y', (height - 15).toString());
|
|
742
|
+
label.setAttribute('text-anchor', 'middle');
|
|
743
|
+
label.setAttribute('fill', '#6b7280');
|
|
744
|
+
label.setAttribute('font-size', '12');
|
|
745
|
+
label.setAttribute('font-weight', '500');
|
|
746
|
+
label.setAttribute('font-family', 'inherit');
|
|
747
|
+
label.textContent = xAxisLabel;
|
|
748
|
+
svg.appendChild(label);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
if (showTicksX) {
|
|
752
|
+
const numTicks = 5;
|
|
753
|
+
for (let i = 0; i <= numTicks; i++) {
|
|
754
|
+
const value = (maxValue / numTicks) * i;
|
|
755
|
+
const x = isReverse
|
|
756
|
+
? width - padding.right - (value * xScale)
|
|
757
|
+
: padding.left + (value * xScale);
|
|
758
|
+
|
|
759
|
+
const gridLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
760
|
+
gridLine.setAttribute('x1', x.toString());
|
|
761
|
+
gridLine.setAttribute('y1', padding.top.toString());
|
|
762
|
+
gridLine.setAttribute('x2', x.toString());
|
|
763
|
+
gridLine.setAttribute('y2', (height - padding.bottom).toString());
|
|
764
|
+
gridLine.setAttribute('stroke', '#e5e7eb');
|
|
765
|
+
gridLine.setAttribute('stroke-width', '1');
|
|
766
|
+
gridLine.setAttribute('stroke-dasharray', '4,4');
|
|
767
|
+
svg.appendChild(gridLine);
|
|
768
|
+
|
|
769
|
+
const tickLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
770
|
+
tickLabel.setAttribute('x', x.toString());
|
|
771
|
+
tickLabel.setAttribute('y', (height - padding.bottom + 20).toString());
|
|
772
|
+
tickLabel.setAttribute('text-anchor', 'middle');
|
|
773
|
+
tickLabel.setAttribute('fill', '#6b7280');
|
|
774
|
+
tickLabel.setAttribute('font-size', '11');
|
|
775
|
+
tickLabel.setAttribute('font-family', 'inherit');
|
|
776
|
+
tickLabel.textContent = Math.round(value).toString() + (scaleXUnit || '');
|
|
777
|
+
svg.appendChild(tickLabel);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// Y-axis (category axis)
|
|
783
|
+
if (showScaleY) {
|
|
784
|
+
const yAxis = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
785
|
+
yAxis.setAttribute('x1', padding.left.toString());
|
|
786
|
+
yAxis.setAttribute('y1', padding.top.toString());
|
|
787
|
+
yAxis.setAttribute('x2', padding.left.toString());
|
|
788
|
+
yAxis.setAttribute('y2', (height - padding.bottom).toString());
|
|
789
|
+
yAxis.setAttribute('stroke', '#9ca3af');
|
|
790
|
+
yAxis.setAttribute('stroke-width', '2');
|
|
791
|
+
svg.appendChild(yAxis);
|
|
792
|
+
|
|
793
|
+
if (yAxisLabel && showTicksY) {
|
|
794
|
+
const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
795
|
+
label.setAttribute('x', '20');
|
|
796
|
+
label.setAttribute('y', (padding.top + chartHeight / 2).toString());
|
|
797
|
+
label.setAttribute('text-anchor', 'middle');
|
|
798
|
+
label.setAttribute('transform', `rotate(-90, 20, ${padding.top + chartHeight / 2})`);
|
|
799
|
+
label.setAttribute('fill', '#6b7280');
|
|
800
|
+
label.setAttribute('font-size', '12');
|
|
801
|
+
label.setAttribute('font-weight', '500');
|
|
802
|
+
label.setAttribute('font-family', 'inherit');
|
|
803
|
+
label.textContent = yAxisLabel;
|
|
804
|
+
svg.appendChild(label);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// Generate smooth path for horizontal orientation
|
|
809
|
+
const pathData = this._generateSmoothPath(data, padding, chartWidth, chartHeight, maxValue, xScale, spacing, isReverse, true);
|
|
810
|
+
const color = colors[0];
|
|
811
|
+
|
|
812
|
+
// Create area path
|
|
813
|
+
this._renderAreaPath(svg, pathData, color, isReverse ? width - padding.right : padding.left, animate, true);
|
|
814
|
+
|
|
815
|
+
// Add data point circles and labels
|
|
816
|
+
data.forEach((point, index) => {
|
|
817
|
+
// Changed calculation to place points at edges
|
|
818
|
+
const y = padding.top + (index * spacing);
|
|
819
|
+
const pointX = point.value * xScale;
|
|
820
|
+
const x = isReverse ? width - padding.right - pointX : padding.left + pointX;
|
|
821
|
+
|
|
822
|
+
// Data point circle
|
|
823
|
+
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
824
|
+
circle.setAttribute('cx', x.toString());
|
|
825
|
+
circle.setAttribute('cy', y.toString());
|
|
826
|
+
circle.setAttribute('r', '4');
|
|
827
|
+
circle.setAttribute('fill', 'white');
|
|
828
|
+
circle.setAttribute('stroke', color);
|
|
829
|
+
circle.setAttribute('stroke-width', '2');
|
|
830
|
+
|
|
831
|
+
if (animate) {
|
|
832
|
+
circle.classList.add('jux-label-animated');
|
|
833
|
+
circle.style.animationDelay = `${animationDuration}ms`;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
svg.appendChild(circle);
|
|
837
|
+
|
|
838
|
+
// Category label
|
|
839
|
+
if (showTicksY && showScaleY) {
|
|
840
|
+
const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
841
|
+
label.setAttribute('x', (padding.left - 10).toString());
|
|
842
|
+
label.setAttribute('y', (y + 4).toString());
|
|
843
|
+
label.setAttribute('text-anchor', 'end');
|
|
844
|
+
label.setAttribute('fill', '#6b7280');
|
|
845
|
+
label.setAttribute('font-size', '11');
|
|
846
|
+
label.setAttribute('font-family', 'inherit');
|
|
847
|
+
label.textContent = point.label;
|
|
848
|
+
|
|
849
|
+
if (animate) {
|
|
850
|
+
label.classList.add('jux-label-animated');
|
|
851
|
+
label.style.animationDelay = `${index * 100 + 200}ms`;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
svg.appendChild(label);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// Value labels
|
|
858
|
+
if (showDataLabels) {
|
|
859
|
+
const valueLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
860
|
+
valueLabel.setAttribute('x', (x + 10).toString());
|
|
861
|
+
valueLabel.setAttribute('y', (y + 4).toString());
|
|
862
|
+
valueLabel.setAttribute('text-anchor', 'start');
|
|
863
|
+
valueLabel.setAttribute('fill', '#374151');
|
|
864
|
+
valueLabel.setAttribute('font-size', '11');
|
|
865
|
+
valueLabel.setAttribute('font-weight', '600');
|
|
866
|
+
valueLabel.setAttribute('font-family', 'inherit');
|
|
867
|
+
valueLabel.textContent = point.value.toString();
|
|
868
|
+
|
|
869
|
+
if (animate) {
|
|
870
|
+
valueLabel.classList.add('jux-label-animated');
|
|
871
|
+
valueLabel.style.animationDelay = `${animationDuration}ms`;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
svg.appendChild(valueLabel);
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
private _generateSmoothPath(
|
|
880
|
+
data: AreaChartSmoothDataPoint[],
|
|
881
|
+
padding: { top: number, right: number, bottom: number, left: number },
|
|
882
|
+
chartWidth: number,
|
|
883
|
+
chartHeight: number,
|
|
884
|
+
maxValue: number,
|
|
885
|
+
scale: number,
|
|
886
|
+
spacing: number,
|
|
887
|
+
isReverse: boolean,
|
|
888
|
+
isHorizontal: boolean
|
|
889
|
+
): string {
|
|
890
|
+
if (data.length === 0) return '';
|
|
891
|
+
|
|
892
|
+
const points: { x: number, y: number }[] = data.map((point, index) => {
|
|
893
|
+
if (isHorizontal) {
|
|
894
|
+
// Changed to place points at edges
|
|
895
|
+
const y = padding.top + (index * spacing);
|
|
896
|
+
const pointX = point.value * scale;
|
|
897
|
+
const x = isReverse
|
|
898
|
+
? padding.right + chartWidth - pointX
|
|
899
|
+
: padding.left + pointX;
|
|
900
|
+
return { x, y };
|
|
901
|
+
} else {
|
|
902
|
+
// Changed to place points at edges
|
|
903
|
+
const x = padding.left + (index * spacing);
|
|
904
|
+
const pointY = point.value * scale;
|
|
905
|
+
const y = isReverse
|
|
906
|
+
? padding.top + pointY
|
|
907
|
+
: padding.top + chartHeight - pointY;
|
|
908
|
+
return { x, y };
|
|
909
|
+
}
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
// Start path at first data point
|
|
913
|
+
let path = `M ${points[0].x},${points[0].y}`;
|
|
914
|
+
|
|
915
|
+
// Generate smooth curve using cubic bezier
|
|
916
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
917
|
+
const current = points[i];
|
|
918
|
+
const next = points[i + 1];
|
|
919
|
+
const prev = i > 0 ? points[i - 1] : current;
|
|
920
|
+
const afterNext = i < points.length - 2 ? points[i + 2] : next;
|
|
921
|
+
|
|
922
|
+
// Calculate control points for smooth curve
|
|
923
|
+
const tension = 0.3;
|
|
924
|
+
|
|
925
|
+
if (isHorizontal) {
|
|
926
|
+
const cp1x = current.x + (next.x - prev.x) * tension;
|
|
927
|
+
const cp1y = current.y;
|
|
928
|
+
const cp2x = next.x - (afterNext.x - current.x) * tension;
|
|
929
|
+
const cp2y = next.y;
|
|
930
|
+
path += ` C ${cp1x},${cp1y} ${cp2x},${cp2y} ${next.x},${next.y}`;
|
|
931
|
+
} else {
|
|
932
|
+
const cp1x = current.x;
|
|
933
|
+
const cp1y = current.y + (next.y - prev.y) * tension;
|
|
934
|
+
const cp2x = next.x;
|
|
935
|
+
const cp2y = next.y - (afterNext.y - current.y) * tension;
|
|
936
|
+
path += ` C ${cp1x},${cp1y} ${cp2x},${cp2y} ${next.x},${next.y}`;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
return path;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
private _renderAreaPath(
|
|
944
|
+
svg: SVGSVGElement,
|
|
945
|
+
pathData: string,
|
|
946
|
+
color: string,
|
|
947
|
+
baseline: number,
|
|
948
|
+
animate: boolean,
|
|
949
|
+
isHorizontal: boolean = false
|
|
950
|
+
): void {
|
|
951
|
+
const { styleMode, animationDuration, width, height } = this.state;
|
|
952
|
+
|
|
953
|
+
// Extract first and last points from the path
|
|
954
|
+
const pathParts = pathData.split(/[MC]/);
|
|
955
|
+
const firstPoint = pathParts[1].trim().split(',');
|
|
956
|
+
const lastMatch = pathData.match(/(\d+\.?\d*),(\d+\.?\d*)(?!.*\d)/);
|
|
957
|
+
const lastPoint = lastMatch ? [lastMatch[1], lastMatch[2]] : firstPoint;
|
|
958
|
+
|
|
959
|
+
// Create closed area path - connect last point to baseline, then back to first point at baseline
|
|
960
|
+
let areaPath = pathData;
|
|
961
|
+
if (isHorizontal) {
|
|
962
|
+
// For horizontal: go down to baseline, then back to first point at baseline
|
|
963
|
+
areaPath += ` L ${baseline},${lastPoint[1]} L ${baseline},${firstPoint[1]} Z`;
|
|
964
|
+
} else {
|
|
965
|
+
// For vertical: go down to baseline, then back to first point at baseline
|
|
966
|
+
areaPath += ` L ${lastPoint[0]},${baseline} L ${firstPoint[0]},${baseline} Z`;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// Create area element
|
|
970
|
+
const area = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
971
|
+
area.setAttribute('d', areaPath);
|
|
972
|
+
|
|
973
|
+
// Apply style modes
|
|
974
|
+
if (styleMode === 'gradient') {
|
|
975
|
+
const gradientId = `area-gradient-${this._id}`;
|
|
976
|
+
const gradient = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient');
|
|
977
|
+
gradient.setAttribute('id', gradientId);
|
|
978
|
+
|
|
979
|
+
if (isHorizontal) {
|
|
980
|
+
gradient.setAttribute('x1', '0%');
|
|
981
|
+
gradient.setAttribute('y1', '0%');
|
|
982
|
+
gradient.setAttribute('x2', '100%');
|
|
983
|
+
gradient.setAttribute('y2', '0%');
|
|
984
|
+
} else {
|
|
985
|
+
gradient.setAttribute('x1', '0%');
|
|
986
|
+
gradient.setAttribute('y1', '0%');
|
|
987
|
+
gradient.setAttribute('x2', '0%');
|
|
988
|
+
gradient.setAttribute('y2', '100%');
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
const stop1 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
|
|
992
|
+
stop1.setAttribute('offset', '0%');
|
|
993
|
+
stop1.setAttribute('stop-color', color);
|
|
994
|
+
stop1.setAttribute('stop-opacity', '0.4');
|
|
995
|
+
|
|
996
|
+
const stop2 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
|
|
997
|
+
stop2.setAttribute('offset', '100%');
|
|
998
|
+
stop2.setAttribute('stop-color', color);
|
|
999
|
+
stop2.setAttribute('stop-opacity', '0.05');
|
|
1000
|
+
|
|
1001
|
+
gradient.appendChild(stop1);
|
|
1002
|
+
gradient.appendChild(stop2);
|
|
1003
|
+
svg.appendChild(gradient);
|
|
1004
|
+
|
|
1005
|
+
area.setAttribute('fill', `url(#${gradientId})`);
|
|
1006
|
+
} else if (styleMode === 'glow') {
|
|
1007
|
+
area.setAttribute('fill', color);
|
|
1008
|
+
area.setAttribute('fill-opacity', '0.3');
|
|
1009
|
+
|
|
1010
|
+
const filterId = `glow-area-${this._id}`;
|
|
1011
|
+
const defs = svg.querySelector('defs') || document.createElementNS('http://www.w3.org/2000/svg', 'defs');
|
|
1012
|
+
if (!svg.querySelector('defs')) {
|
|
1013
|
+
svg.insertBefore(defs, svg.firstChild);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
const filter = document.createElementNS('http://www.w3.org/2000/svg', 'filter');
|
|
1017
|
+
filter.setAttribute('id', filterId);
|
|
1018
|
+
filter.setAttribute('x', '-50%');
|
|
1019
|
+
filter.setAttribute('y', '-50%');
|
|
1020
|
+
filter.setAttribute('width', '200%');
|
|
1021
|
+
filter.setAttribute('height', '200%');
|
|
1022
|
+
|
|
1023
|
+
const feGaussianBlur = document.createElementNS('http://www.w3.org/2000/svg', 'feGaussianBlur');
|
|
1024
|
+
feGaussianBlur.setAttribute('in', 'SourceGraphic');
|
|
1025
|
+
feGaussianBlur.setAttribute('stdDeviation', '6');
|
|
1026
|
+
filter.appendChild(feGaussianBlur);
|
|
1027
|
+
defs.appendChild(filter);
|
|
1028
|
+
|
|
1029
|
+
area.setAttribute('filter', `url(#${filterId})`);
|
|
1030
|
+
} else if (styleMode === 'glass') {
|
|
1031
|
+
area.setAttribute('fill', color);
|
|
1032
|
+
area.setAttribute('fill-opacity', '0.15');
|
|
1033
|
+
} else {
|
|
1034
|
+
area.setAttribute('fill', color);
|
|
1035
|
+
area.setAttribute('fill-opacity', '0.2');
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
area.setAttribute('stroke', 'none');
|
|
1039
|
+
|
|
1040
|
+
if (animate) {
|
|
1041
|
+
area.style.opacity = '0';
|
|
1042
|
+
area.style.animation = `fadeIn ${animationDuration}ms cubic-bezier(0.4, 0, 0.2, 1) forwards`;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
svg.appendChild(area);
|
|
1046
|
+
|
|
1047
|
+
// Create stroke line on top (just the curve)
|
|
1048
|
+
const line = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
1049
|
+
line.setAttribute('d', pathData);
|
|
1050
|
+
line.setAttribute('fill', 'none');
|
|
1051
|
+
line.setAttribute('stroke', color);
|
|
1052
|
+
line.setAttribute('stroke-width', '3');
|
|
1053
|
+
line.setAttribute('stroke-linecap', 'round');
|
|
1054
|
+
line.setAttribute('stroke-linejoin', 'round');
|
|
1055
|
+
|
|
1056
|
+
if (animate) {
|
|
1057
|
+
const length = (line as any).getTotalLength?.() || 1000;
|
|
1058
|
+
line.style.strokeDasharray = length.toString();
|
|
1059
|
+
line.style.strokeDashoffset = length.toString();
|
|
1060
|
+
line.style.animation = `drawLine ${animationDuration}ms cubic-bezier(0.4, 0, 0.2, 1) forwards`;
|
|
1061
|
+
|
|
1062
|
+
// Add draw animation
|
|
1063
|
+
const styleId = `draw-line-animation-${this._id}`;
|
|
1064
|
+
if (!document.getElementById(styleId)) {
|
|
1065
|
+
const style = document.createElement('style');
|
|
1066
|
+
style.id = styleId;
|
|
1067
|
+
style.textContent = `
|
|
1068
|
+
@keyframes drawLine {
|
|
1069
|
+
to {
|
|
1070
|
+
stroke-dashoffset: 0;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
`;
|
|
1074
|
+
document.head.appendChild(style);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
svg.appendChild(line);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
private _renderBar(
|
|
1082
|
+
svg: SVGSVGElement,
|
|
1083
|
+
x: number,
|
|
1084
|
+
y: number,
|
|
1085
|
+
width: number,
|
|
1086
|
+
height: number,
|
|
1087
|
+
color: string,
|
|
1088
|
+
index: number,
|
|
1089
|
+
point: AreaChartSmoothDataPoint,
|
|
1090
|
+
labelPosition: 'top' | 'bottom' | 'left' | 'right',
|
|
1091
|
+
isHorizontal: boolean = false
|
|
1092
|
+
): void {
|
|
1093
|
+
// This method is no longer used for smooth area charts
|
|
1094
|
+
// Keeping it for backward compatibility
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/* -------------------------
|
|
1098
|
+
* Legend and Data Table
|
|
1099
|
+
* ------------------------- */
|
|
1100
|
+
|
|
1101
|
+
private _createLegend(): HTMLElement {
|
|
1102
|
+
const { data, colors, legendOrientation } = this.state;
|
|
1103
|
+
|
|
1104
|
+
const legend = document.createElement('div');
|
|
1105
|
+
legend.className = 'jux-areachartsmooth-legend';
|
|
1106
|
+
|
|
1107
|
+
data.forEach((point, index) => {
|
|
1108
|
+
const color = point.color || colors[index % colors.length];
|
|
1109
|
+
|
|
1110
|
+
const item = document.createElement('div');
|
|
1111
|
+
item.className = 'jux-areachartsmooth-legend-item';
|
|
1112
|
+
|
|
1113
|
+
const swatch = document.createElement('div');
|
|
1114
|
+
swatch.className = 'jux-areachartsmooth-legend-swatch';
|
|
1115
|
+
swatch.style.background = color;
|
|
1116
|
+
|
|
1117
|
+
const label = document.createElement('span');
|
|
1118
|
+
label.className = 'jux-areachartsmooth-legend-label';
|
|
1119
|
+
label.textContent = point.label;
|
|
1120
|
+
|
|
1121
|
+
item.appendChild(swatch);
|
|
1122
|
+
item.appendChild(label);
|
|
1123
|
+
legend.appendChild(item);
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
return legend;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
private _createDataTable(): HTMLElement {
|
|
1130
|
+
const { data, xAxisLabel, yAxisLabel } = this.state;
|
|
1131
|
+
|
|
1132
|
+
const table = document.createElement('table');
|
|
1133
|
+
table.className = 'jux-areachartsmooth-table';
|
|
1134
|
+
|
|
1135
|
+
const thead = document.createElement('thead');
|
|
1136
|
+
const headerRow = document.createElement('tr');
|
|
1137
|
+
|
|
1138
|
+
const columnHeaders = [
|
|
1139
|
+
xAxisLabel || 'Label',
|
|
1140
|
+
yAxisLabel || 'Value'
|
|
1141
|
+
];
|
|
1142
|
+
|
|
1143
|
+
columnHeaders.forEach(text => {
|
|
1144
|
+
const th = document.createElement('th');
|
|
1145
|
+
th.textContent = text;
|
|
1146
|
+
headerRow.appendChild(th);
|
|
1147
|
+
});
|
|
1148
|
+
thead.appendChild(headerRow);
|
|
1149
|
+
table.appendChild(thead);
|
|
1150
|
+
|
|
1151
|
+
const tbody = document.createElement('tbody');
|
|
1152
|
+
data.forEach(point => {
|
|
1153
|
+
const row = document.createElement('tr');
|
|
1154
|
+
|
|
1155
|
+
const labelCell = document.createElement('td');
|
|
1156
|
+
labelCell.textContent = point.label;
|
|
1157
|
+
|
|
1158
|
+
const valueCell = document.createElement('td');
|
|
1159
|
+
valueCell.textContent = point.value.toString();
|
|
1160
|
+
|
|
1161
|
+
row.appendChild(labelCell);
|
|
1162
|
+
row.appendChild(valueCell);
|
|
1163
|
+
tbody.appendChild(row);
|
|
1164
|
+
});
|
|
1165
|
+
table.appendChild(tbody);
|
|
1166
|
+
|
|
1167
|
+
return table;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
private _lightenColor(color: string, percent: number): string {
|
|
1171
|
+
const num = parseInt(color.replace('#', ''), 16);
|
|
1172
|
+
const r = Math.min(255, Math.floor((num >> 16) + ((255 - (num >> 16)) * percent / 100)));
|
|
1173
|
+
const g = Math.min(255, Math.floor(((num >> 8) & 0x00FF) + ((255 - ((num >> 8) & 0x00FF)) * percent / 100)));
|
|
1174
|
+
const b = Math.min(255, Math.floor((num & 0x0000FF) + ((255 - (num & 0x0000FF)) * percent / 100)));
|
|
1175
|
+
return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
private _applyTheme(themeName: string): void {
|
|
1179
|
+
const themes: Record<string, any> = {
|
|
1180
|
+
google: googleTheme,
|
|
1181
|
+
seriesa: seriesaTheme,
|
|
1182
|
+
hr: hrTheme,
|
|
1183
|
+
figma: figmaTheme,
|
|
1184
|
+
notion: notionTheme,
|
|
1185
|
+
chalk: chalkTheme,
|
|
1186
|
+
mint: mintTheme
|
|
1187
|
+
};
|
|
1188
|
+
|
|
1189
|
+
const theme = themes[themeName];
|
|
1190
|
+
if (!theme) return;
|
|
1191
|
+
|
|
1192
|
+
// Apply colors
|
|
1193
|
+
// get one color;
|
|
1194
|
+
const randomColor = theme.colors[Math.floor(Math.random() * theme.colors.length)];
|
|
1195
|
+
this.state.colors = [randomColor];
|
|
1196
|
+
|
|
1197
|
+
// Inject base styles (once)
|
|
1198
|
+
const baseStyleId = 'jux-areachartsmooth-base-styles';
|
|
1199
|
+
if (!document.getElementById(baseStyleId)) {
|
|
1200
|
+
const style = document.createElement('style');
|
|
1201
|
+
style.id = baseStyleId;
|
|
1202
|
+
style.textContent = this._getBaseStyles();
|
|
1203
|
+
document.head.appendChild(style);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// Inject font (once per theme)
|
|
1207
|
+
if (theme.font && !document.querySelector(`link[href="${theme.font}"]`)) {
|
|
1208
|
+
const link = document.createElement('link');
|
|
1209
|
+
link.rel = 'stylesheet';
|
|
1210
|
+
link.href = theme.font;
|
|
1211
|
+
document.head.appendChild(link);
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
// Apply theme-specific styles
|
|
1215
|
+
const styleId = `jux-areachartsmooth-theme-${themeName}`;
|
|
1216
|
+
let styleElement = document.getElementById(styleId) as HTMLStyleElement;
|
|
1217
|
+
|
|
1218
|
+
if (!styleElement) {
|
|
1219
|
+
styleElement = document.createElement('style');
|
|
1220
|
+
styleElement.id = styleId;
|
|
1221
|
+
document.head.appendChild(styleElement);
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
// Generate CSS with theme variables
|
|
1225
|
+
const variablesCSS = Object.entries(theme.variables)
|
|
1226
|
+
.map(([key, value]) => ` ${key}: ${value};`)
|
|
1227
|
+
.join('\n');
|
|
1228
|
+
|
|
1229
|
+
styleElement.textContent = `
|
|
1230
|
+
.jux-areachartsmooth.theme-${themeName} {
|
|
1231
|
+
${variablesCSS}
|
|
1232
|
+
}
|
|
1233
|
+
`;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
private _applyThemeToWrapper(wrapper: HTMLElement): void {
|
|
1237
|
+
if (!this.state.theme) return;
|
|
1238
|
+
|
|
1239
|
+
// Remove old theme classes
|
|
1240
|
+
wrapper.classList.remove('theme-google', 'theme-seriesa', 'theme-hr', 'theme-figma', 'theme-notion', 'theme-chalk', 'theme-mint');
|
|
1241
|
+
|
|
1242
|
+
// Add new theme class
|
|
1243
|
+
wrapper.classList.add(`theme-${this.state.theme}`);
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
private _getBaseStyles(): string {
|
|
1247
|
+
return `
|
|
1248
|
+
.jux-areachartsmooth {
|
|
1249
|
+
font-family: var(--chart-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif);
|
|
1250
|
+
display: inline-block;
|
|
1251
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
1252
|
+
border-radius: 12px;
|
|
1253
|
+
background: white;
|
|
1254
|
+
padding: 24px;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
.jux-areachartsmooth-title {
|
|
1258
|
+
margin: 0 0 0.5rem 0;
|
|
1259
|
+
font-size: 1.25rem;
|
|
1260
|
+
font-weight: 600;
|
|
1261
|
+
font-family: inherit;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
.jux-areachartsmooth-subtitle {
|
|
1265
|
+
margin: 0 0 1rem 0;
|
|
1266
|
+
font-size: 0.875rem;
|
|
1267
|
+
color: #6b7280;
|
|
1268
|
+
font-family: inherit;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
.jux-areachartsmooth-legend {
|
|
1272
|
+
display: flex;
|
|
1273
|
+
flex-wrap: wrap;
|
|
1274
|
+
gap: 1rem;
|
|
1275
|
+
margin-top: 1rem;
|
|
1276
|
+
justify-content: center;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
.jux-areachartsmooth-legend-item {
|
|
1280
|
+
display: flex;
|
|
1281
|
+
align-items: center;
|
|
1282
|
+
gap: 0.5rem;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
.jux-areachartsmooth-legend-swatch {
|
|
1286
|
+
width: 12px;
|
|
1287
|
+
height: 12px;
|
|
1288
|
+
border-radius: 2px;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
.jux-areachartsmooth-legend-label {
|
|
1292
|
+
font-size: 0.875rem;
|
|
1293
|
+
color: #374151;
|
|
1294
|
+
font-family: inherit;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
.jux-areachartsmooth-table {
|
|
1298
|
+
width: 100%;
|
|
1299
|
+
margin-top: 1rem;
|
|
1300
|
+
border-collapse: collapse;
|
|
1301
|
+
font-size: 0.875rem;
|
|
1302
|
+
font-family: inherit;
|
|
1303
|
+
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
.jux-areachartsmooth-table thead th {
|
|
1307
|
+
text-align: left;
|
|
1308
|
+
padding: 0.5rem;
|
|
1309
|
+
border-bottom: 2px solid #e5e7eb;
|
|
1310
|
+
font-weight: 600;
|
|
1311
|
+
text-align: center;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
.jux-areachartsmooth-table tbody td {
|
|
1315
|
+
padding: 0.5rem;
|
|
1316
|
+
border-bottom: 1px solid #f3f4f6;
|
|
1317
|
+
text-align: center;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
.jux-areachartsmooth-svg {
|
|
1321
|
+
font-family: inherit;
|
|
1322
|
+
}
|
|
1323
|
+
`;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
render(targetId?: string | HTMLElement): this {
|
|
1327
|
+
// Apply theme first if set
|
|
1328
|
+
if (this.state.theme) {
|
|
1329
|
+
this._applyTheme(this.state.theme);
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
let container: HTMLElement;
|
|
1333
|
+
|
|
1334
|
+
if (targetId) {
|
|
1335
|
+
if (targetId instanceof HTMLElement) {
|
|
1336
|
+
container = targetId;
|
|
1337
|
+
} else {
|
|
1338
|
+
const target = document.querySelector(targetId);
|
|
1339
|
+
if (!target || !(target instanceof HTMLElement)) {
|
|
1340
|
+
throw new Error(`AreaChartSmooth: Target element "${targetId}" not found`);
|
|
1341
|
+
}
|
|
1342
|
+
container = target;
|
|
1343
|
+
}
|
|
1344
|
+
} else {
|
|
1345
|
+
container = getOrCreateContainer(this._id);
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
this.container = container;
|
|
1349
|
+
const { class: className, style } = this.state;
|
|
1350
|
+
|
|
1351
|
+
const wrapper = document.createElement('div');
|
|
1352
|
+
wrapper.id = this._id;
|
|
1353
|
+
wrapper.className = 'jux-areachartsmooth';
|
|
1354
|
+
|
|
1355
|
+
// Add theme class
|
|
1356
|
+
if (this.state.theme) {
|
|
1357
|
+
wrapper.classList.add(`theme-${this.state.theme}`);
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
// Add custom class
|
|
1361
|
+
if (className) {
|
|
1362
|
+
wrapper.classList.add(...className.split(' '));
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
if (style) {
|
|
1366
|
+
wrapper.setAttribute('style', style);
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
container.appendChild(wrapper);
|
|
1370
|
+
|
|
1371
|
+
// Build chart content
|
|
1372
|
+
this._buildChart(wrapper);
|
|
1373
|
+
|
|
1374
|
+
return this;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
export function areachartsmooth(id: string, options: AreaChartSmoothOptions = {}): AreaChartSmooth {
|
|
1379
|
+
return new AreaChartSmooth(id, options);
|
|
1380
|
+
}
|