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,605 @@
|
|
|
1
|
+
import { State } from '../reactivity/state.js';
|
|
2
|
+
import {
|
|
3
|
+
googleTheme,
|
|
4
|
+
seriesaTheme,
|
|
5
|
+
hrTheme,
|
|
6
|
+
figmaTheme,
|
|
7
|
+
notionTheme,
|
|
8
|
+
chalkTheme,
|
|
9
|
+
mintTheme
|
|
10
|
+
} from '../themes/charts.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* KPI card options
|
|
14
|
+
*/
|
|
15
|
+
export interface KPICardOptions {
|
|
16
|
+
title?: string;
|
|
17
|
+
value?: string | number;
|
|
18
|
+
delta?: number; // Percentage change (e.g., 10 for +10%, -5 for -5%)
|
|
19
|
+
prefix?: string; // e.g., "$", "€"
|
|
20
|
+
suffix?: string; // e.g., "k", "M", "%"
|
|
21
|
+
width?: number;
|
|
22
|
+
height?: number;
|
|
23
|
+
theme?: 'google' | 'seriesa' | 'hr' | 'figma' | 'notion' | 'chalk' | 'mint';
|
|
24
|
+
styleMode?: 'default' | 'gradient' | 'glass' | 'outline' | 'glow';
|
|
25
|
+
animate?: boolean;
|
|
26
|
+
animationDuration?: number;
|
|
27
|
+
borderRadius?: number;
|
|
28
|
+
showAccentBar?: boolean;
|
|
29
|
+
class?: string;
|
|
30
|
+
style?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* KPI card state
|
|
35
|
+
*/
|
|
36
|
+
type KPICardState = {
|
|
37
|
+
title: string;
|
|
38
|
+
value: string | number;
|
|
39
|
+
delta: number;
|
|
40
|
+
prefix: string;
|
|
41
|
+
suffix: string;
|
|
42
|
+
width: number;
|
|
43
|
+
height: number;
|
|
44
|
+
theme: 'google' | 'seriesa' | 'hr' | 'figma' | 'notion' | 'chalk' | 'mint';
|
|
45
|
+
styleMode: 'default' | 'gradient' | 'glass' | 'outline' | 'glow';
|
|
46
|
+
animate: boolean;
|
|
47
|
+
animationDuration: number;
|
|
48
|
+
borderRadius: number;
|
|
49
|
+
showAccentBar: boolean;
|
|
50
|
+
class: string;
|
|
51
|
+
style: string;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* KPI card component - Displays key performance indicators
|
|
56
|
+
*
|
|
57
|
+
* Usage:
|
|
58
|
+
* jux.kpicard('users-kpi')
|
|
59
|
+
* .title('New users')
|
|
60
|
+
* .value('78k')
|
|
61
|
+
* .delta(10)
|
|
62
|
+
* .theme('mint')
|
|
63
|
+
* .styleMode('gradient')
|
|
64
|
+
* .render('#app');
|
|
65
|
+
*/
|
|
66
|
+
export class KPICard {
|
|
67
|
+
private _id: string;
|
|
68
|
+
private _element?: HTMLElement;
|
|
69
|
+
private _container?: string;
|
|
70
|
+
|
|
71
|
+
public state: KPICardState = {
|
|
72
|
+
title: 'KPI',
|
|
73
|
+
value: 0,
|
|
74
|
+
delta: 0,
|
|
75
|
+
prefix: '',
|
|
76
|
+
suffix: '',
|
|
77
|
+
width: 280,
|
|
78
|
+
height: 200,
|
|
79
|
+
theme: 'google',
|
|
80
|
+
styleMode: 'default',
|
|
81
|
+
animate: true,
|
|
82
|
+
animationDuration: 600,
|
|
83
|
+
borderRadius: 16,
|
|
84
|
+
showAccentBar: true,
|
|
85
|
+
class: '',
|
|
86
|
+
style: ''
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
private _boundTheme?: State<string>;
|
|
90
|
+
private _boundStyleMode?: State<string>;
|
|
91
|
+
private _boundTitle?: State<string>;
|
|
92
|
+
private _boundValue?: State<string | number>;
|
|
93
|
+
private _boundDelta?: State<number>;
|
|
94
|
+
|
|
95
|
+
constructor(id: string, options: KPICardOptions = {}) {
|
|
96
|
+
this._id = id;
|
|
97
|
+
|
|
98
|
+
// Apply options
|
|
99
|
+
if (options.title !== undefined) this.state.title = options.title;
|
|
100
|
+
if (options.value !== undefined) this.state.value = options.value;
|
|
101
|
+
if (options.delta !== undefined) this.state.delta = options.delta;
|
|
102
|
+
if (options.prefix !== undefined) this.state.prefix = options.prefix;
|
|
103
|
+
if (options.suffix !== undefined) this.state.suffix = options.suffix;
|
|
104
|
+
if (options.width !== undefined) this.state.width = options.width;
|
|
105
|
+
if (options.height !== undefined) this.state.height = options.height;
|
|
106
|
+
if (options.theme !== undefined) this.state.theme = options.theme;
|
|
107
|
+
if (options.styleMode !== undefined) this.state.styleMode = options.styleMode;
|
|
108
|
+
if (options.animate !== undefined) this.state.animate = options.animate;
|
|
109
|
+
if (options.animationDuration !== undefined) this.state.animationDuration = options.animationDuration;
|
|
110
|
+
if (options.borderRadius !== undefined) this.state.borderRadius = options.borderRadius;
|
|
111
|
+
if (options.showAccentBar !== undefined) this.state.showAccentBar = options.showAccentBar;
|
|
112
|
+
if (options.class !== undefined) this.state.class = options.class;
|
|
113
|
+
if (options.style !== undefined) this.state.style = options.style;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Chainable setters
|
|
117
|
+
title(value: string | State<string>): this {
|
|
118
|
+
if (value instanceof State) {
|
|
119
|
+
this._boundTitle = value;
|
|
120
|
+
this.state.title = value.value;
|
|
121
|
+
value.subscribe((newValue) => {
|
|
122
|
+
this.state.title = newValue;
|
|
123
|
+
this._updateCard();
|
|
124
|
+
});
|
|
125
|
+
} else {
|
|
126
|
+
this.state.title = value;
|
|
127
|
+
}
|
|
128
|
+
return this;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
value(value: string | number | State<string | number>): this {
|
|
132
|
+
if (value instanceof State) {
|
|
133
|
+
this._boundValue = value;
|
|
134
|
+
this.state.value = value.value;
|
|
135
|
+
value.subscribe((newValue) => {
|
|
136
|
+
this.state.value = newValue;
|
|
137
|
+
this._updateCard();
|
|
138
|
+
});
|
|
139
|
+
} else {
|
|
140
|
+
this.state.value = value;
|
|
141
|
+
}
|
|
142
|
+
return this;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
delta(value: number | State<number>): this {
|
|
146
|
+
if (value instanceof State) {
|
|
147
|
+
this._boundDelta = value;
|
|
148
|
+
this.state.delta = value.value;
|
|
149
|
+
value.subscribe((newValue) => {
|
|
150
|
+
this.state.delta = newValue;
|
|
151
|
+
this._updateCard();
|
|
152
|
+
});
|
|
153
|
+
} else {
|
|
154
|
+
this.state.delta = value;
|
|
155
|
+
}
|
|
156
|
+
return this;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
prefix(value: string): this {
|
|
160
|
+
this.state.prefix = value;
|
|
161
|
+
return this;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
suffix(value: string): this {
|
|
165
|
+
this.state.suffix = value;
|
|
166
|
+
return this;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
width(value: number): this {
|
|
170
|
+
this.state.width = value;
|
|
171
|
+
return this;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
height(value: number): this {
|
|
175
|
+
this.state.height = value;
|
|
176
|
+
return this;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
theme(value: string | State<string>): this {
|
|
180
|
+
if (value instanceof State) {
|
|
181
|
+
this._boundTheme = value;
|
|
182
|
+
this.state.theme = value.value as any;
|
|
183
|
+
value.subscribe((newValue) => {
|
|
184
|
+
this.state.theme = newValue as any;
|
|
185
|
+
this._updateCard();
|
|
186
|
+
});
|
|
187
|
+
} else {
|
|
188
|
+
this.state.theme = value as any;
|
|
189
|
+
}
|
|
190
|
+
return this;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
styleMode(value: string | State<string>): this {
|
|
194
|
+
if (value instanceof State) {
|
|
195
|
+
this._boundStyleMode = value;
|
|
196
|
+
this.state.styleMode = value.value as any;
|
|
197
|
+
value.subscribe((newValue) => {
|
|
198
|
+
this.state.styleMode = newValue as any;
|
|
199
|
+
this._updateCard();
|
|
200
|
+
});
|
|
201
|
+
} else {
|
|
202
|
+
this.state.styleMode = value as any;
|
|
203
|
+
}
|
|
204
|
+
return this;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
animate(value: boolean): this {
|
|
208
|
+
this.state.animate = value;
|
|
209
|
+
return this;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
animationDuration(value: number): this {
|
|
213
|
+
this.state.animationDuration = value;
|
|
214
|
+
return this;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
borderRadius(value: number): this {
|
|
218
|
+
this.state.borderRadius = value;
|
|
219
|
+
return this;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
showAccentBar(value: boolean): this {
|
|
223
|
+
this.state.showAccentBar = value;
|
|
224
|
+
return this;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
class(value: string): this {
|
|
228
|
+
this.state.class = value;
|
|
229
|
+
return this;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
style(value: string): this {
|
|
233
|
+
this.state.style = value;
|
|
234
|
+
return this;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
render(container: string): this {
|
|
238
|
+
this._container = container;
|
|
239
|
+
const element = document.querySelector(container);
|
|
240
|
+
if (!element) {
|
|
241
|
+
console.warn(`Container ${container} not found`);
|
|
242
|
+
return this;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
this._loadThemeFont();
|
|
246
|
+
this._buildCard(element as HTMLElement);
|
|
247
|
+
return this;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private _updateCard(): void {
|
|
251
|
+
if (!this._container) return;
|
|
252
|
+
|
|
253
|
+
const element = document.querySelector(this._container);
|
|
254
|
+
if (!element) return;
|
|
255
|
+
|
|
256
|
+
// Clear and rebuild
|
|
257
|
+
element.innerHTML = '';
|
|
258
|
+
this._loadThemeFont();
|
|
259
|
+
this._buildCard(element as HTMLElement);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private _loadThemeFont(): void {
|
|
263
|
+
const themeConfig = this._getThemeConfig();
|
|
264
|
+
|
|
265
|
+
if (themeConfig.font && !document.querySelector(`link[href="${themeConfig.font}"]`)) {
|
|
266
|
+
const link = document.createElement('link');
|
|
267
|
+
link.rel = 'stylesheet';
|
|
268
|
+
link.href = themeConfig.font;
|
|
269
|
+
document.head.appendChild(link);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private _buildCard(container: HTMLElement): void {
|
|
274
|
+
const { width, height, class: className, style, animate, animationDuration } = this.state;
|
|
275
|
+
|
|
276
|
+
// Create wrapper
|
|
277
|
+
const wrapper = document.createElement('div');
|
|
278
|
+
wrapper.className = `jux-kpicard ${className}`;
|
|
279
|
+
wrapper.id = this._id;
|
|
280
|
+
wrapper.style.cssText = `
|
|
281
|
+
width: ${width}px;
|
|
282
|
+
height: ${height}px;
|
|
283
|
+
${style}
|
|
284
|
+
`;
|
|
285
|
+
|
|
286
|
+
// Apply theme colors and style mode
|
|
287
|
+
this._applyThemeAndStyle(wrapper);
|
|
288
|
+
|
|
289
|
+
// Build card content
|
|
290
|
+
const content = this._createContent();
|
|
291
|
+
wrapper.appendChild(content);
|
|
292
|
+
|
|
293
|
+
// Add animation
|
|
294
|
+
if (animate) {
|
|
295
|
+
wrapper.style.opacity = '0';
|
|
296
|
+
wrapper.style.transform = 'translateY(20px)';
|
|
297
|
+
wrapper.style.transition = `opacity ${animationDuration}ms ease-out, transform ${animationDuration}ms ease-out`;
|
|
298
|
+
|
|
299
|
+
requestAnimationFrame(() => {
|
|
300
|
+
setTimeout(() => {
|
|
301
|
+
wrapper.style.opacity = '1';
|
|
302
|
+
wrapper.style.transform = 'translateY(0)';
|
|
303
|
+
}, 50);
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
container.appendChild(wrapper);
|
|
308
|
+
this._element = wrapper;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private _createContent(): HTMLElement {
|
|
312
|
+
const { title, value, delta, prefix, suffix, animate, animationDuration, styleMode, width, height } = this.state;
|
|
313
|
+
const themeConfig = this._getThemeConfig();
|
|
314
|
+
|
|
315
|
+
// Calculate scale factor based on default dimensions (280x200)
|
|
316
|
+
const baseWidth = 280;
|
|
317
|
+
const baseHeight = 200;
|
|
318
|
+
const scaleFactor = Math.min(width / baseWidth, height / baseHeight);
|
|
319
|
+
|
|
320
|
+
const content = document.createElement('div');
|
|
321
|
+
content.className = 'jux-kpicard-content';
|
|
322
|
+
content.style.cssText = `
|
|
323
|
+
padding: ${24 * scaleFactor}px;
|
|
324
|
+
display: flex;
|
|
325
|
+
flex-direction: column;
|
|
326
|
+
height: 100%;
|
|
327
|
+
position: relative;
|
|
328
|
+
`;
|
|
329
|
+
|
|
330
|
+
// Title
|
|
331
|
+
const titleEl = document.createElement('div');
|
|
332
|
+
titleEl.className = 'jux-kpicard-title';
|
|
333
|
+
titleEl.textContent = title;
|
|
334
|
+
titleEl.style.cssText = `
|
|
335
|
+
font-size: ${16 * scaleFactor}px;
|
|
336
|
+
font-weight: 500;
|
|
337
|
+
color: ${styleMode === 'gradient' ? 'rgba(255, 255, 255, 0.95)' : '#6b7280'};
|
|
338
|
+
margin-bottom: ${16 * scaleFactor}px;
|
|
339
|
+
font-family: ${themeConfig.variables['--chart-font-family']};
|
|
340
|
+
`;
|
|
341
|
+
content.appendChild(titleEl);
|
|
342
|
+
|
|
343
|
+
// Value container
|
|
344
|
+
const valueContainer = document.createElement('div');
|
|
345
|
+
valueContainer.style.cssText = `
|
|
346
|
+
display: flex;
|
|
347
|
+
align-items: baseline;
|
|
348
|
+
margin-bottom: ${12 * scaleFactor}px;
|
|
349
|
+
`;
|
|
350
|
+
|
|
351
|
+
// Value
|
|
352
|
+
const valueEl = document.createElement('div');
|
|
353
|
+
valueEl.className = 'jux-kpicard-value';
|
|
354
|
+
valueEl.textContent = `${prefix}${value}${suffix}`;
|
|
355
|
+
valueEl.style.cssText = `
|
|
356
|
+
font-size: ${56 * scaleFactor}px;
|
|
357
|
+
font-weight: 800;
|
|
358
|
+
color: ${styleMode === 'gradient' ? '#ffffff' : '#1f2937'};
|
|
359
|
+
line-height: 1;
|
|
360
|
+
font-family: ${themeConfig.variables['--chart-font-family']};
|
|
361
|
+
${styleMode === 'glow' ? `text-shadow: 0 0 ${20 * scaleFactor}px ${themeConfig.colors[0]}40;` : ''}
|
|
362
|
+
`;
|
|
363
|
+
|
|
364
|
+
if (animate) {
|
|
365
|
+
valueEl.style.opacity = '0';
|
|
366
|
+
valueEl.style.transform = 'scale(0.8)';
|
|
367
|
+
valueEl.style.transition = `opacity ${animationDuration}ms ease-out ${animationDuration / 2}ms, transform ${animationDuration}ms cubic-bezier(0.34, 1.56, 0.64, 1) ${animationDuration / 2}ms`;
|
|
368
|
+
|
|
369
|
+
requestAnimationFrame(() => {
|
|
370
|
+
setTimeout(() => {
|
|
371
|
+
valueEl.style.opacity = '1';
|
|
372
|
+
valueEl.style.transform = 'scale(1)';
|
|
373
|
+
}, 100);
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
valueContainer.appendChild(valueEl);
|
|
378
|
+
content.appendChild(valueContainer);
|
|
379
|
+
|
|
380
|
+
// Delta with arrow
|
|
381
|
+
if (delta !== 0) {
|
|
382
|
+
const deltaContainer = document.createElement('div');
|
|
383
|
+
deltaContainer.className = 'jux-kpicard-delta';
|
|
384
|
+
deltaContainer.style.cssText = `
|
|
385
|
+
display: flex;
|
|
386
|
+
align-items: center;
|
|
387
|
+
gap: ${8 * scaleFactor}px;
|
|
388
|
+
`;
|
|
389
|
+
|
|
390
|
+
// Arrow SVG
|
|
391
|
+
const arrow = this._createArrowSVG(delta > 0, styleMode === 'gradient', scaleFactor);
|
|
392
|
+
deltaContainer.appendChild(arrow);
|
|
393
|
+
|
|
394
|
+
// Delta text
|
|
395
|
+
const deltaText = document.createElement('span');
|
|
396
|
+
deltaText.textContent = `${delta > 0 ? '+' : ''}${delta}%`;
|
|
397
|
+
const deltaColor = styleMode === 'gradient'
|
|
398
|
+
? (delta > 0 ? 'rgba(255, 255, 255, 0.95)' : 'rgba(255, 200, 200, 0.95)')
|
|
399
|
+
: (delta > 0 ? '#10b981' : '#ef4444');
|
|
400
|
+
|
|
401
|
+
deltaText.style.cssText = `
|
|
402
|
+
font-size: ${18 * scaleFactor}px;
|
|
403
|
+
font-weight: 700;
|
|
404
|
+
color: ${deltaColor};
|
|
405
|
+
font-family: ${themeConfig.variables['--chart-font-family']};
|
|
406
|
+
`;
|
|
407
|
+
|
|
408
|
+
if (animate) {
|
|
409
|
+
deltaContainer.style.opacity = '0';
|
|
410
|
+
deltaContainer.style.transform = 'translateX(-10px)';
|
|
411
|
+
deltaContainer.style.transition = `opacity ${animationDuration}ms ease-out ${animationDuration}ms, transform ${animationDuration}ms ease-out ${animationDuration}ms`;
|
|
412
|
+
|
|
413
|
+
requestAnimationFrame(() => {
|
|
414
|
+
setTimeout(() => {
|
|
415
|
+
deltaContainer.style.opacity = '1';
|
|
416
|
+
deltaContainer.style.transform = 'translateX(0)';
|
|
417
|
+
}, 150);
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
deltaContainer.appendChild(deltaText);
|
|
422
|
+
content.appendChild(deltaContainer);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return content;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
private _createArrowSVG(isUp: boolean, isGradientMode: boolean, scaleFactor: number = 1): SVGSVGElement {
|
|
429
|
+
const baseWidth = 120;
|
|
430
|
+
const baseHeight = 32;
|
|
431
|
+
const width = baseWidth * scaleFactor;
|
|
432
|
+
const height = baseHeight * scaleFactor;
|
|
433
|
+
|
|
434
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
435
|
+
svg.setAttribute('width', width.toString());
|
|
436
|
+
svg.setAttribute('height', height.toString());
|
|
437
|
+
svg.setAttribute('viewBox', `0 0 ${baseWidth} ${baseHeight}`);
|
|
438
|
+
svg.setAttribute('fill', 'none');
|
|
439
|
+
svg.style.flexShrink = '0';
|
|
440
|
+
|
|
441
|
+
const color = isGradientMode
|
|
442
|
+
? (isUp ? 'rgba(255, 255, 255, 0.9)' : 'rgba(255, 200, 200, 0.9)')
|
|
443
|
+
: (isUp ? '#10b981' : '#ef4444');
|
|
444
|
+
|
|
445
|
+
if (isUp) {
|
|
446
|
+
// Upward trending arrow - wider
|
|
447
|
+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
448
|
+
path.setAttribute('d', 'M4 26 L22 18 L40 22 L58 14 L76 18 L94 10');
|
|
449
|
+
path.setAttribute('stroke', color);
|
|
450
|
+
path.setAttribute('stroke-width', '3');
|
|
451
|
+
path.setAttribute('stroke-linecap', 'round');
|
|
452
|
+
path.setAttribute('stroke-linejoin', 'round');
|
|
453
|
+
path.setAttribute('fill', 'none');
|
|
454
|
+
svg.appendChild(path);
|
|
455
|
+
|
|
456
|
+
// Points at each vertex - bigger circles
|
|
457
|
+
const points = [
|
|
458
|
+
{ x: 4, y: 26 },
|
|
459
|
+
{ x: 22, y: 18 },
|
|
460
|
+
{ x: 40, y: 22 },
|
|
461
|
+
{ x: 58, y: 14 },
|
|
462
|
+
{ x: 76, y: 18 },
|
|
463
|
+
{ x: 94, y: 10 }
|
|
464
|
+
];
|
|
465
|
+
|
|
466
|
+
points.forEach(point => {
|
|
467
|
+
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
468
|
+
circle.setAttribute('cx', point.x.toString());
|
|
469
|
+
circle.setAttribute('cy', point.y.toString());
|
|
470
|
+
circle.setAttribute('r', '4.5');
|
|
471
|
+
circle.setAttribute('fill', color);
|
|
472
|
+
svg.appendChild(circle);
|
|
473
|
+
});
|
|
474
|
+
} else {
|
|
475
|
+
// Downward trending arrow - wider
|
|
476
|
+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
477
|
+
path.setAttribute('d', 'M4 6 L22 14 L40 10 L58 18 L76 14 L94 22');
|
|
478
|
+
path.setAttribute('stroke', color);
|
|
479
|
+
path.setAttribute('stroke-width', '3');
|
|
480
|
+
path.setAttribute('stroke-linecap', 'round');
|
|
481
|
+
path.setAttribute('stroke-linejoin', 'round');
|
|
482
|
+
path.setAttribute('fill', 'none');
|
|
483
|
+
svg.appendChild(path);
|
|
484
|
+
|
|
485
|
+
// Points at each vertex - bigger circles
|
|
486
|
+
const points = [
|
|
487
|
+
{ x: 4, y: 6 },
|
|
488
|
+
{ x: 22, y: 14 },
|
|
489
|
+
{ x: 40, y: 10 },
|
|
490
|
+
{ x: 58, y: 18 },
|
|
491
|
+
{ x: 76, y: 14 },
|
|
492
|
+
{ x: 94, y: 22 }
|
|
493
|
+
];
|
|
494
|
+
|
|
495
|
+
points.forEach(point => {
|
|
496
|
+
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
497
|
+
circle.setAttribute('cx', point.x.toString());
|
|
498
|
+
circle.setAttribute('cy', point.y.toString());
|
|
499
|
+
circle.setAttribute('r', '4.5');
|
|
500
|
+
circle.setAttribute('fill', color);
|
|
501
|
+
svg.appendChild(circle);
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return svg;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
private _getThemeConfig() {
|
|
509
|
+
const { theme } = this.state;
|
|
510
|
+
|
|
511
|
+
switch (theme) {
|
|
512
|
+
case 'google':
|
|
513
|
+
return googleTheme;
|
|
514
|
+
case 'seriesa':
|
|
515
|
+
return seriesaTheme;
|
|
516
|
+
case 'hr':
|
|
517
|
+
return hrTheme;
|
|
518
|
+
case 'figma':
|
|
519
|
+
return figmaTheme;
|
|
520
|
+
case 'notion':
|
|
521
|
+
return notionTheme;
|
|
522
|
+
case 'chalk':
|
|
523
|
+
return chalkTheme;
|
|
524
|
+
case 'mint':
|
|
525
|
+
return mintTheme;
|
|
526
|
+
default:
|
|
527
|
+
return googleTheme;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
private _applyThemeAndStyle(wrapper: HTMLElement): void {
|
|
532
|
+
const { styleMode, borderRadius, showAccentBar, width, height } = this.state;
|
|
533
|
+
const themeConfig = this._getThemeConfig();
|
|
534
|
+
const colors = themeConfig.colors;
|
|
535
|
+
|
|
536
|
+
// Calculate scale factor for accent bar
|
|
537
|
+
const baseWidth = 280;
|
|
538
|
+
const scaleFactor = width / baseWidth;
|
|
539
|
+
const accentBarHeight = 4 * scaleFactor;
|
|
540
|
+
|
|
541
|
+
const baseStyles = `
|
|
542
|
+
border-radius: ${borderRadius}px;
|
|
543
|
+
overflow: hidden;
|
|
544
|
+
position: relative;
|
|
545
|
+
font-family: ${themeConfig.variables['--chart-font-family']};
|
|
546
|
+
`;
|
|
547
|
+
|
|
548
|
+
if (styleMode === 'gradient') {
|
|
549
|
+
wrapper.style.cssText += `
|
|
550
|
+
${baseStyles}
|
|
551
|
+
background: linear-gradient(135deg, ${colors[0]} 0%, ${colors[1]} 50%, ${colors[2]} 100%);
|
|
552
|
+
box-shadow: 0 ${8 * scaleFactor}px ${32 * scaleFactor}px ${colors[0]}40;
|
|
553
|
+
`;
|
|
554
|
+
} else if (styleMode === 'glass') {
|
|
555
|
+
wrapper.style.cssText += `
|
|
556
|
+
${baseStyles}
|
|
557
|
+
background: linear-gradient(135deg, ${colors[0]}20 0%, ${colors[1]}20 100%);
|
|
558
|
+
backdrop-filter: blur(${12 * scaleFactor}px) saturate(180%);
|
|
559
|
+
border: ${1 * scaleFactor}px solid ${colors[0]}30;
|
|
560
|
+
box-shadow: 0 ${8 * scaleFactor}px ${32 * scaleFactor}px rgba(0, 0, 0, 0.1);
|
|
561
|
+
`;
|
|
562
|
+
} else if (styleMode === 'outline') {
|
|
563
|
+
wrapper.style.cssText += `
|
|
564
|
+
${baseStyles}
|
|
565
|
+
background: #ffffff;
|
|
566
|
+
border: ${3 * scaleFactor}px solid ${colors[0]};
|
|
567
|
+
box-shadow: 0 ${4 * scaleFactor}px ${12 * scaleFactor}px ${colors[0]}20;
|
|
568
|
+
`;
|
|
569
|
+
} else if (styleMode === 'glow') {
|
|
570
|
+
wrapper.style.cssText += `
|
|
571
|
+
${baseStyles}
|
|
572
|
+
background: #ffffff;
|
|
573
|
+
border: ${2 * scaleFactor}px solid ${colors[0]}40;
|
|
574
|
+
box-shadow: 0 0 ${30 * scaleFactor}px ${colors[0]}30, 0 ${4 * scaleFactor}px ${12 * scaleFactor}px rgba(0, 0, 0, 0.1);
|
|
575
|
+
`;
|
|
576
|
+
} else {
|
|
577
|
+
// default
|
|
578
|
+
wrapper.style.cssText += `
|
|
579
|
+
${baseStyles}
|
|
580
|
+
background: #ffffff;
|
|
581
|
+
border: ${1 * scaleFactor}px solid #e5e7eb;
|
|
582
|
+
box-shadow: 0 ${1 * scaleFactor}px ${3 * scaleFactor}px rgba(0, 0, 0, 0.1);
|
|
583
|
+
`;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Apply theme color accent bar (optional)
|
|
587
|
+
if (showAccentBar) {
|
|
588
|
+
const accentBar = document.createElement('div');
|
|
589
|
+
accentBar.style.cssText = `
|
|
590
|
+
position: absolute;
|
|
591
|
+
top: 0;
|
|
592
|
+
left: 0;
|
|
593
|
+
right: 0;
|
|
594
|
+
height: ${accentBarHeight}px;
|
|
595
|
+
background: linear-gradient(90deg, ${colors[0]}, ${colors[1]}, ${colors[2]});
|
|
596
|
+
border-radius: ${borderRadius}px ${borderRadius}px 0 0;
|
|
597
|
+
`;
|
|
598
|
+
wrapper.appendChild(accentBar);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
export function kpicard(id: string, options: KPICardOptions = {}): KPICard {
|
|
604
|
+
return new KPICard(id, options);
|
|
605
|
+
}
|
package/lib/jux.ts
CHANGED
|
@@ -50,7 +50,10 @@ import { req, Req, type RequestInfo } from './components/req.js';
|
|
|
50
50
|
import { heading, Heading, type HeadingOptions } from './components/heading.js';
|
|
51
51
|
import { paragraph, Paragraph, type ParagraphOptions } from './components/paragraph.js';
|
|
52
52
|
import { barchart, BarChart, type BarChartOptions, type BarChartDataPoint } from './components/barchart.js';
|
|
53
|
-
|
|
53
|
+
import { areachart, AreaChart, type AreaChartOptions, type AreaChartDataPoint } from './components/areachart.js';
|
|
54
|
+
import { areachartsmooth, AreaChartSmooth, type AreaChartSmoothOptions, AreaChartSmoothDataPoint } from './components/areachartsmooth.js';
|
|
55
|
+
import { doughnutchart, DoughnutChart, type DoughnutChartOptions, type DoughnutChartDataPoint } from './components/doughnutchart.js';
|
|
56
|
+
import { kpicard, KPICard, type KPICardOptions } from './components/kpicard.js';
|
|
54
57
|
|
|
55
58
|
/* -------------------------
|
|
56
59
|
* Type Exports
|
|
@@ -106,7 +109,14 @@ export type {
|
|
|
106
109
|
HeadingOptions,
|
|
107
110
|
ParagraphOptions,
|
|
108
111
|
BarChartOptions,
|
|
109
|
-
BarChartDataPoint
|
|
112
|
+
BarChartDataPoint,
|
|
113
|
+
AreaChartOptions,
|
|
114
|
+
AreaChartDataPoint,
|
|
115
|
+
AreaChartSmoothOptions,
|
|
116
|
+
AreaChartSmoothDataPoint,
|
|
117
|
+
DoughnutChartOptions,
|
|
118
|
+
DoughnutChartDataPoint,
|
|
119
|
+
KPICardOptions
|
|
110
120
|
};
|
|
111
121
|
|
|
112
122
|
/* -------------------------
|
|
@@ -158,7 +168,11 @@ export {
|
|
|
158
168
|
Req,
|
|
159
169
|
Heading,
|
|
160
170
|
Paragraph,
|
|
161
|
-
BarChart
|
|
171
|
+
BarChart,
|
|
172
|
+
AreaChart,
|
|
173
|
+
AreaChartSmooth,
|
|
174
|
+
DoughnutChart,
|
|
175
|
+
KPICard
|
|
162
176
|
};
|
|
163
177
|
|
|
164
178
|
/* -------------------------
|
|
@@ -216,6 +230,10 @@ export interface JuxAPI {
|
|
|
216
230
|
heading: typeof heading;
|
|
217
231
|
paragraph: typeof paragraph;
|
|
218
232
|
barchart: typeof barchart;
|
|
233
|
+
areachart: typeof areachart;
|
|
234
|
+
areachartsmooth: typeof areachartsmooth;
|
|
235
|
+
doughnutchart: typeof doughnutchart;
|
|
236
|
+
kpicard: typeof kpicard;
|
|
219
237
|
}
|
|
220
238
|
|
|
221
239
|
/* -------------------------
|
|
@@ -289,6 +307,10 @@ class Jux implements JuxAPI {
|
|
|
289
307
|
heading = heading;
|
|
290
308
|
paragraph = paragraph;
|
|
291
309
|
barchart = barchart;
|
|
310
|
+
areachart = areachart;
|
|
311
|
+
areachartsmooth = areachartsmooth;
|
|
312
|
+
doughnutchart = doughnutchart;
|
|
313
|
+
kpicard = kpicard;
|
|
292
314
|
}
|
|
293
315
|
|
|
294
316
|
/**
|