juxscript 1.0.59 → 1.0.61
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 +32 -1
- package/bin/cli.js +13 -33
- package/docs/grid.png +0 -0
- package/juxconfig.example.js +24 -2
- package/lib/components/base/BaseComponent.ts +20 -0
- package/lib/components/chart.ts +231 -0
- package/lib/components/container.ts +76 -118
- package/lib/components/grid.ts +291 -0
- package/lib/components/input.ts +55 -1
- package/lib/jux.ts +10 -29
- package/lib/utils/fetch.ts +553 -0
- package/machinery/ast.js +347 -0
- package/machinery/bundleAssets.js +0 -0
- package/machinery/bundleJux.js +0 -0
- package/machinery/bundleVendors.js +0 -0
- package/machinery/compiler.js +427 -151
- package/machinery/server.js +16 -2
- package/machinery/ts-shim.js +46 -0
- package/package.json +5 -1
- package/presets/default/all.jux +2 -2
- package/presets/default/layout.css +120 -0
- package/lib/components/charts/areachart.ts +0 -315
- package/lib/components/charts/barchart.ts +0 -421
- package/lib/components/charts/doughnutchart.ts +0 -263
- package/lib/components/charts/lib/BaseChart.ts +0 -389
- package/lib/components/charts/lib/chart-types.ts +0 -159
- package/lib/components/charts/lib/chart-utils.ts +0 -160
- package/lib/components/charts/lib/chart.ts +0 -707
- package/lib/components/charts/lib/charts.js +0 -126
- package/lib/components/docs-data.json +0 -2075
- package/lib/components/kpicard.ts +0 -640
package/machinery/server.js
CHANGED
|
@@ -69,11 +69,25 @@ async function serve(httpPort = 3000, wsPort = 3001, distDir = './.jux-dist') {
|
|
|
69
69
|
next();
|
|
70
70
|
});
|
|
71
71
|
|
|
72
|
+
// ✅ ADD: Explicit MIME type for JavaScript files
|
|
73
|
+
app.use((req, res, next) => {
|
|
74
|
+
if (req.path.endsWith('.js')) {
|
|
75
|
+
res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
|
|
76
|
+
}
|
|
77
|
+
next();
|
|
78
|
+
});
|
|
79
|
+
|
|
72
80
|
// Serve static files
|
|
73
81
|
app.use(express.static(absoluteDistDir));
|
|
74
82
|
|
|
75
|
-
// SPA fallback
|
|
76
|
-
app.
|
|
83
|
+
// ✅ FIX: Only apply SPA fallback to routes, not static files
|
|
84
|
+
app.use((req, res, next) => {
|
|
85
|
+
// Skip SPA fallback for file extensions (static assets)
|
|
86
|
+
if (/\.[a-zA-Z0-9]+$/.test(req.path)) {
|
|
87
|
+
return res.status(404).send('File not found');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// SPA fallback for routes only
|
|
77
91
|
if (req.accepts('html')) {
|
|
78
92
|
const indexPath = path.join(absoluteDistDir, 'index.html');
|
|
79
93
|
if (fs.existsSync(indexPath)) {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import esbuild from 'esbuild';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* TypeScript Shim - Strips type annotations using esbuild
|
|
5
|
+
* This is rock-solid and handles ALL TypeScript syntax correctly
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Strip TypeScript type annotations from code using esbuild
|
|
10
|
+
*
|
|
11
|
+
* @param {string} code - TypeScript source code
|
|
12
|
+
* @returns {string} - JavaScript code with types removed
|
|
13
|
+
*/
|
|
14
|
+
export function stripTypes(code) {
|
|
15
|
+
try {
|
|
16
|
+
// ✅ Use esbuild to transform TypeScript → JavaScript
|
|
17
|
+
const result = esbuild.transformSync(code, {
|
|
18
|
+
loader: 'ts',
|
|
19
|
+
format: 'esm',
|
|
20
|
+
target: 'es2020'
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return result.code;
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.error('❌ Failed to strip TypeScript:', err.message);
|
|
26
|
+
// Return original code if transformation fails
|
|
27
|
+
return code;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check if a file is TypeScript based on extension
|
|
33
|
+
*/
|
|
34
|
+
export function isTypeScript(filePath) {
|
|
35
|
+
return filePath.endsWith('.ts') || filePath.endsWith('.tsx');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Load and parse a file, automatically stripping types if it's TypeScript
|
|
40
|
+
*/
|
|
41
|
+
export function loadAndStripTypes(filePath, fileContent) {
|
|
42
|
+
if (isTypeScript(filePath)) {
|
|
43
|
+
return stripTypes(fileContent);
|
|
44
|
+
}
|
|
45
|
+
return fileContent;
|
|
46
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "juxscript",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.61",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A JavaScript UX authorship platform",
|
|
6
6
|
"main": "lib/jux.js",
|
|
@@ -57,6 +57,9 @@
|
|
|
57
57
|
"find-jux-classes": "node scripts/find-jux-classes.js"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
+
"acorn": "^8.15.0",
|
|
61
|
+
"axios": "^1.6.0",
|
|
62
|
+
"chart.js": "^4.5.1",
|
|
60
63
|
"chokidar": "^3.5.3",
|
|
61
64
|
"esbuild": "^0.19.0",
|
|
62
65
|
"express": "^4.18.2",
|
|
@@ -67,6 +70,7 @@
|
|
|
67
70
|
"@types/express": "^4.17.17",
|
|
68
71
|
"@types/node": "^20.0.0",
|
|
69
72
|
"@types/ws": "^8.5.5",
|
|
73
|
+
"acorn-walk": "^8.3.4",
|
|
70
74
|
"jsdom": "^27.4.0",
|
|
71
75
|
"typescript": "^5.0.0"
|
|
72
76
|
}
|
package/presets/default/all.jux
CHANGED
|
@@ -21,9 +21,9 @@ jux.hero('all-hero', {
|
|
|
21
21
|
}).render("#appmain-content");
|
|
22
22
|
jux.divider().render("#appmain-content");
|
|
23
23
|
|
|
24
|
-
jux.heading('chart-heading').level(3).text('jux.chart').render("#appmain-content");
|
|
24
|
+
jux.heading('chart-heading').level(3).text('jux.chart, jux.calendar, jux.chatbox, jux.aiwidget, jux.media, jux.icons, jux.data!').render("#appmain-content");
|
|
25
25
|
jux.element('all-element')
|
|
26
|
-
.text('Need
|
|
26
|
+
.text('Need Elements! shadcn and tailwind analysis. Mobiles. ios/android - .text')
|
|
27
27
|
.render("#appmain-content");
|
|
28
28
|
jux.divider().render("#appmain-content");
|
|
29
29
|
|
|
@@ -1489,4 +1489,124 @@ p {
|
|
|
1489
1489
|
.jux-radio-options {
|
|
1490
1490
|
flex-direction: column;
|
|
1491
1491
|
}
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
/* ============================================
|
|
1495
|
+
GRID COMPONENT
|
|
1496
|
+
============================================ */
|
|
1497
|
+
.jux-grid {
|
|
1498
|
+
display: grid;
|
|
1499
|
+
position: relative;
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
.jux-grid-cell {
|
|
1503
|
+
position: relative;
|
|
1504
|
+
overflow: auto;
|
|
1505
|
+
min-width: 0; /* Prevent grid blowout */
|
|
1506
|
+
min-height: 0;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
/* ============================================
|
|
1510
|
+
GRID GRIDDER MODE (Blueprint Visualization)
|
|
1511
|
+
============================================ */
|
|
1512
|
+
.jux-grid-gridder {
|
|
1513
|
+
/* Blueprint-style background */
|
|
1514
|
+
background-color: #0a1929;
|
|
1515
|
+
background-image:
|
|
1516
|
+
linear-gradient(rgba(59, 130, 246, 0.1) 1px, transparent 1px),
|
|
1517
|
+
linear-gradient(90deg, rgba(59, 130, 246, 0.1) 1px, transparent 1px);
|
|
1518
|
+
background-size: 20px 20px;
|
|
1519
|
+
padding: 2px;
|
|
1520
|
+
border: 2px solid #3b82f6;
|
|
1521
|
+
box-shadow:
|
|
1522
|
+
0 0 0 1px rgba(59, 130, 246, 0.2),
|
|
1523
|
+
inset 0 0 20px rgba(59, 130, 246, 0.1);
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
.jux-grid-gridder .jux-grid-cell {
|
|
1527
|
+
/* Cell styling in gridder mode */
|
|
1528
|
+
background: rgba(15, 23, 42, 0.8);
|
|
1529
|
+
border: 1px solid rgba(59, 130, 246, 0.4);
|
|
1530
|
+
box-shadow: inset 0 0 10px rgba(59, 130, 246, 0.05);
|
|
1531
|
+
min-height: 80px; /* Ensure cells are visible */
|
|
1532
|
+
display: flex;
|
|
1533
|
+
align-items: center;
|
|
1534
|
+
justify-content: center;
|
|
1535
|
+
position: relative;
|
|
1536
|
+
transition: all 0.2s ease;
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
.jux-grid-gridder .jux-grid-cell:hover {
|
|
1540
|
+
background: rgba(30, 41, 59, 0.9);
|
|
1541
|
+
border-color: rgba(59, 130, 246, 0.8);
|
|
1542
|
+
box-shadow:
|
|
1543
|
+
inset 0 0 15px rgba(59, 130, 246, 0.15),
|
|
1544
|
+
0 0 10px rgba(59, 130, 246, 0.3);
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
/* Coordinate label (top-left corner) */
|
|
1548
|
+
.jux-grid-gridder .jux-grid-cell::before {
|
|
1549
|
+
content: attr(data-row) ',' attr(data-col);
|
|
1550
|
+
position: absolute;
|
|
1551
|
+
top: 4px;
|
|
1552
|
+
left: 4px;
|
|
1553
|
+
font-size: 10px;
|
|
1554
|
+
font-family: 'SF Mono', Monaco, 'Courier New', monospace;
|
|
1555
|
+
font-weight: 600;
|
|
1556
|
+
color: rgba(59, 130, 246, 0.8);
|
|
1557
|
+
background: rgba(15, 23, 42, 0.95);
|
|
1558
|
+
padding: 2px 6px;
|
|
1559
|
+
border-radius: 3px;
|
|
1560
|
+
border: 1px solid rgba(59, 130, 246, 0.3);
|
|
1561
|
+
letter-spacing: 0.5px;
|
|
1562
|
+
z-index: 10;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
/* ID label (bottom-right corner) */
|
|
1566
|
+
.jux-grid-gridder .jux-grid-cell::after {
|
|
1567
|
+
content: '#' attr(id);
|
|
1568
|
+
position: absolute;
|
|
1569
|
+
bottom: 4px;
|
|
1570
|
+
right: 4px;
|
|
1571
|
+
font-size: 9px;
|
|
1572
|
+
font-family: 'SF Mono', Monaco, 'Courier New', monospace;
|
|
1573
|
+
color: rgba(148, 163, 184, 0.6);
|
|
1574
|
+
background: rgba(15, 23, 42, 0.8);
|
|
1575
|
+
padding: 2px 5px;
|
|
1576
|
+
border-radius: 2px;
|
|
1577
|
+
border: 1px solid rgba(71, 85, 105, 0.3);
|
|
1578
|
+
max-width: calc(100% - 12px);
|
|
1579
|
+
overflow: hidden;
|
|
1580
|
+
text-overflow: ellipsis;
|
|
1581
|
+
white-space: nowrap;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
/* Grid lines (column separators) */
|
|
1585
|
+
.jux-grid-gridder .jux-grid-cell:not(:nth-child(1))::before {
|
|
1586
|
+
box-shadow: 0 0 5px rgba(59, 130, 246, 0.4);
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
/* Remove default debug styles when gridder is active */
|
|
1590
|
+
.jux-grid-gridder.jux-grid-debug .jux-grid-cell {
|
|
1591
|
+
/* Gridder overrides debug mode */
|
|
1592
|
+
border-color: rgba(59, 130, 246, 0.4);
|
|
1593
|
+
background: rgba(15, 23, 42, 0.8);
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
/* ============================================
|
|
1597
|
+
GRID DEBUG MODE (Legacy - simpler version)
|
|
1598
|
+
============================================ */
|
|
1599
|
+
.jux-grid-debug .jux-grid-cell {
|
|
1600
|
+
border: 1px dashed var(--color-border);
|
|
1601
|
+
background: var(--color-surface-base);
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
.jux-grid-debug .jux-grid-cell::before {
|
|
1605
|
+
content: attr(data-row) '-' attr(data-col);
|
|
1606
|
+
position: absolute;
|
|
1607
|
+
top: 2px;
|
|
1608
|
+
left: 2px;
|
|
1609
|
+
font-size: 10px;
|
|
1610
|
+
color: var(--color-text-tertiary);
|
|
1611
|
+
opacity: 0.5;
|
|
1492
1612
|
}
|
|
@@ -1,315 +0,0 @@
|
|
|
1
|
-
import { BaseChart, BaseChartState, ChartDataPoint } from './lib/BaseChart.js';
|
|
2
|
-
|
|
3
|
-
export interface AreaChartOptions {
|
|
4
|
-
data?: ChartDataPoint[];
|
|
5
|
-
title?: string;
|
|
6
|
-
subtitle?: string;
|
|
7
|
-
xAxisLabel?: string;
|
|
8
|
-
yAxisLabel?: string;
|
|
9
|
-
showTicksX?: boolean;
|
|
10
|
-
showTicksY?: boolean;
|
|
11
|
-
showScaleX?: boolean;
|
|
12
|
-
showScaleY?: boolean;
|
|
13
|
-
scaleXUnit?: string;
|
|
14
|
-
scaleYUnit?: string;
|
|
15
|
-
showLegend?: boolean;
|
|
16
|
-
legendOrientation?: 'horizontal' | 'vertical';
|
|
17
|
-
showDataTable?: boolean;
|
|
18
|
-
showDataLabels?: boolean;
|
|
19
|
-
animate?: boolean;
|
|
20
|
-
animationDuration?: number;
|
|
21
|
-
width?: number;
|
|
22
|
-
height?: number;
|
|
23
|
-
colors?: string[];
|
|
24
|
-
class?: string;
|
|
25
|
-
style?: string;
|
|
26
|
-
theme?: 'google' | 'seriesa' | 'hr' | 'figma' | 'notion' | 'chalk' | 'mint';
|
|
27
|
-
styleMode?: 'default' | 'gradient' | 'outline' | 'dashed' | 'glow' | 'glass';
|
|
28
|
-
borderRadius?: number;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
interface AreaChartState extends BaseChartState {
|
|
32
|
-
data: ChartDataPoint[];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export class AreaChart extends BaseChart<AreaChartState> {
|
|
36
|
-
constructor(id: string, options: AreaChartOptions = {}) {
|
|
37
|
-
const defaultColors = ['#3b82f6', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6'];
|
|
38
|
-
const randomColor = defaultColors[Math.floor(Math.random() * defaultColors.length)];
|
|
39
|
-
|
|
40
|
-
super(id, {
|
|
41
|
-
data: options.data ?? [],
|
|
42
|
-
title: options.title ?? '',
|
|
43
|
-
subtitle: options.subtitle ?? '',
|
|
44
|
-
xAxisLabel: options.xAxisLabel ?? '',
|
|
45
|
-
yAxisLabel: options.yAxisLabel ?? '',
|
|
46
|
-
showTicksX: options.showTicksX ?? true,
|
|
47
|
-
showTicksY: options.showTicksY ?? true,
|
|
48
|
-
showScaleX: options.showScaleX ?? true,
|
|
49
|
-
showScaleY: options.showScaleY ?? true,
|
|
50
|
-
scaleXUnit: options.scaleXUnit ?? '',
|
|
51
|
-
scaleYUnit: options.scaleYUnit ?? '',
|
|
52
|
-
showLegend: options.showLegend ?? false,
|
|
53
|
-
legendOrientation: options.legendOrientation ?? 'horizontal',
|
|
54
|
-
showDataTable: options.showDataTable ?? false,
|
|
55
|
-
showDataLabels: options.showDataLabels ?? true,
|
|
56
|
-
animate: options.animate ?? true,
|
|
57
|
-
animationDuration: options.animationDuration ?? 800,
|
|
58
|
-
width: options.width ?? 600,
|
|
59
|
-
height: options.height ?? 400,
|
|
60
|
-
colors: options.colors ?? [randomColor],
|
|
61
|
-
class: options.class ?? '',
|
|
62
|
-
style: options.style ?? '',
|
|
63
|
-
theme: options.theme,
|
|
64
|
-
styleMode: options.styleMode ?? 'default',
|
|
65
|
-
borderRadius: options.borderRadius ?? 4
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
protected _getChartClassName(): string {
|
|
70
|
-
return 'jux-areachart';
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
protected _createSVG(): SVGSVGElement {
|
|
74
|
-
const { data, width, height, colors, animate, animationDuration } = this.state;
|
|
75
|
-
|
|
76
|
-
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
77
|
-
svg.setAttribute('width', width.toString());
|
|
78
|
-
svg.setAttribute('height', height.toString());
|
|
79
|
-
svg.setAttribute('class', 'jux-areachart-svg');
|
|
80
|
-
|
|
81
|
-
if (!data.length) return svg;
|
|
82
|
-
|
|
83
|
-
if (animate) this._addAnimationStyles(svg);
|
|
84
|
-
|
|
85
|
-
const padding = { top: 40, right: 40, bottom: 60, left: 60 };
|
|
86
|
-
const chartWidth = width - padding.left - padding.right;
|
|
87
|
-
const chartHeight = height - padding.top - padding.bottom;
|
|
88
|
-
const maxValue = Math.max(...data.map(d => d.value));
|
|
89
|
-
|
|
90
|
-
this._renderAreaChart(svg, padding, chartWidth, chartHeight, maxValue);
|
|
91
|
-
|
|
92
|
-
return svg;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
protected _getBaseStyles(): string {
|
|
96
|
-
return `
|
|
97
|
-
.jux-areachart {
|
|
98
|
-
font-family: var(--chart-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif);
|
|
99
|
-
display: inline-block;
|
|
100
|
-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
101
|
-
border-radius: 12px;
|
|
102
|
-
background: white;
|
|
103
|
-
padding: 24px;
|
|
104
|
-
}
|
|
105
|
-
.jux-areachart-title {
|
|
106
|
-
margin: 0 0 0.5rem 0;
|
|
107
|
-
font-size: 1.25rem;
|
|
108
|
-
font-weight: 600;
|
|
109
|
-
}
|
|
110
|
-
.jux-areachart-subtitle {
|
|
111
|
-
margin: 0 0 1rem 0;
|
|
112
|
-
font-size: 0.875rem;
|
|
113
|
-
color: #6b7280;
|
|
114
|
-
}
|
|
115
|
-
.jux-areachart-legend {
|
|
116
|
-
display: flex;
|
|
117
|
-
flex-wrap: wrap;
|
|
118
|
-
gap: 1rem;
|
|
119
|
-
margin-top: 1rem;
|
|
120
|
-
justify-content: center;
|
|
121
|
-
}
|
|
122
|
-
.jux-areachart-legend-item {
|
|
123
|
-
display: flex;
|
|
124
|
-
align-items: center;
|
|
125
|
-
gap: 0.5rem;
|
|
126
|
-
}
|
|
127
|
-
.jux-areachart-legend-swatch {
|
|
128
|
-
width: 12px;
|
|
129
|
-
height: 12px;
|
|
130
|
-
border-radius: 2px;
|
|
131
|
-
}
|
|
132
|
-
.jux-areachart-legend-label {
|
|
133
|
-
font-size: 0.875rem;
|
|
134
|
-
color: #374151;
|
|
135
|
-
}
|
|
136
|
-
.jux-areachart-table {
|
|
137
|
-
width: 100%;
|
|
138
|
-
margin-top: 1rem;
|
|
139
|
-
border-collapse: collapse;
|
|
140
|
-
font-size: 0.875rem;
|
|
141
|
-
}
|
|
142
|
-
.jux-areachart-table thead th {
|
|
143
|
-
text-align: center;
|
|
144
|
-
padding: 0.5rem;
|
|
145
|
-
border-bottom: 2px solid #e5e7eb;
|
|
146
|
-
font-weight: 600;
|
|
147
|
-
}
|
|
148
|
-
.jux-areachart-table tbody td {
|
|
149
|
-
padding: 0.5rem;
|
|
150
|
-
border-bottom: 1px solid #f3f4f6;
|
|
151
|
-
text-align: center;
|
|
152
|
-
}
|
|
153
|
-
.jux-areachart-svg {
|
|
154
|
-
font-family: inherit;
|
|
155
|
-
}
|
|
156
|
-
`;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
private _addAnimationStyles(svg: SVGSVGElement): void {
|
|
160
|
-
const { animationDuration } = this.state;
|
|
161
|
-
const animationId = `area-grow-${this._id}`;
|
|
162
|
-
const style = document.createElementNS('http://www.w3.org/2000/svg', 'style');
|
|
163
|
-
|
|
164
|
-
style.textContent = `
|
|
165
|
-
@keyframes ${animationId} {
|
|
166
|
-
from { opacity: 0; transform: scaleY(0); }
|
|
167
|
-
to { opacity: 1; transform: scaleY(1); }
|
|
168
|
-
}
|
|
169
|
-
.jux-area-animated {
|
|
170
|
-
transform-origin: bottom;
|
|
171
|
-
animation: ${animationId} ${animationDuration}ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
|
172
|
-
}
|
|
173
|
-
`;
|
|
174
|
-
svg.appendChild(style);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
private _renderAreaChart(svg: SVGSVGElement, padding: any, chartWidth: number, chartHeight: number, maxValue: number): void {
|
|
178
|
-
const { data, width, height, colors, showDataLabels, animate, xAxisLabel, yAxisLabel, showTicksX, showTicksY, showScaleX, showScaleY, scaleYUnit } = this.state;
|
|
179
|
-
|
|
180
|
-
const yScale = chartHeight / maxValue;
|
|
181
|
-
const xStep = chartWidth / (data.length - 1 || 1);
|
|
182
|
-
const color = colors[0];
|
|
183
|
-
|
|
184
|
-
// Axes
|
|
185
|
-
if (showScaleY) {
|
|
186
|
-
this._renderAxis(svg, padding.left, padding.top, padding.left, height - padding.bottom);
|
|
187
|
-
if (yAxisLabel && showTicksY) {
|
|
188
|
-
this._renderAxisLabel(svg, yAxisLabel, 20, padding.top + chartHeight / 2, -90);
|
|
189
|
-
}
|
|
190
|
-
if (showTicksY) {
|
|
191
|
-
this._renderYTicks(svg, maxValue, yScale, padding, width, height, scaleYUnit, chartHeight);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if (showScaleX) {
|
|
196
|
-
this._renderAxis(svg, padding.left, height - padding.bottom, width - padding.right, height - padding.bottom);
|
|
197
|
-
if (xAxisLabel && showTicksX) {
|
|
198
|
-
this._renderAxisLabel(svg, xAxisLabel, padding.left + chartWidth / 2, height - 15, 0);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Build area path
|
|
203
|
-
let pathData = `M ${padding.left} ${height - padding.bottom}`;
|
|
204
|
-
data.forEach((point, i) => {
|
|
205
|
-
const x = padding.left + (i * xStep);
|
|
206
|
-
const y = height - padding.bottom - (point.value * yScale);
|
|
207
|
-
pathData += ` L ${x} ${y}`;
|
|
208
|
-
});
|
|
209
|
-
pathData += ` L ${padding.left + ((data.length - 1) * xStep)} ${height - padding.bottom} Z`;
|
|
210
|
-
|
|
211
|
-
const areaPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
212
|
-
areaPath.setAttribute('d', pathData);
|
|
213
|
-
areaPath.setAttribute('fill', color);
|
|
214
|
-
areaPath.setAttribute('fill-opacity', '0.3');
|
|
215
|
-
if (animate) areaPath.classList.add('jux-area-animated');
|
|
216
|
-
svg.appendChild(areaPath);
|
|
217
|
-
|
|
218
|
-
// Line path
|
|
219
|
-
let lineData = '';
|
|
220
|
-
data.forEach((point, i) => {
|
|
221
|
-
const x = padding.left + (i * xStep);
|
|
222
|
-
const y = height - padding.bottom - (point.value * yScale);
|
|
223
|
-
lineData += (i === 0 ? `M ${x} ${y}` : ` L ${x} ${y}`);
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
const linePath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
227
|
-
linePath.setAttribute('d', lineData);
|
|
228
|
-
linePath.setAttribute('stroke', color);
|
|
229
|
-
linePath.setAttribute('stroke-width', '3');
|
|
230
|
-
linePath.setAttribute('fill', 'none');
|
|
231
|
-
svg.appendChild(linePath);
|
|
232
|
-
|
|
233
|
-
// Data points
|
|
234
|
-
data.forEach((point, i) => {
|
|
235
|
-
const x = padding.left + (i * xStep);
|
|
236
|
-
const y = height - padding.bottom - (point.value * yScale);
|
|
237
|
-
|
|
238
|
-
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
239
|
-
circle.setAttribute('cx', x.toString());
|
|
240
|
-
circle.setAttribute('cy', y.toString());
|
|
241
|
-
circle.setAttribute('r', '4');
|
|
242
|
-
circle.setAttribute('fill', color);
|
|
243
|
-
circle.setAttribute('stroke', 'white');
|
|
244
|
-
circle.setAttribute('stroke-width', '2');
|
|
245
|
-
svg.appendChild(circle);
|
|
246
|
-
|
|
247
|
-
if (showDataLabels) {
|
|
248
|
-
const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
249
|
-
label.setAttribute('x', x.toString());
|
|
250
|
-
label.setAttribute('y', (y - 10).toString());
|
|
251
|
-
label.setAttribute('text-anchor', 'middle');
|
|
252
|
-
label.setAttribute('fill', '#374151');
|
|
253
|
-
label.setAttribute('font-size', '11');
|
|
254
|
-
label.setAttribute('font-weight', '600');
|
|
255
|
-
label.textContent = point.value.toString();
|
|
256
|
-
svg.appendChild(label);
|
|
257
|
-
}
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
private _renderAxis(svg: SVGSVGElement, x1: number, y1: number, x2: number, y2: number): void {
|
|
262
|
-
const axis = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
263
|
-
axis.setAttribute('x1', x1.toString());
|
|
264
|
-
axis.setAttribute('y1', y1.toString());
|
|
265
|
-
axis.setAttribute('x2', x2.toString());
|
|
266
|
-
axis.setAttribute('y2', y2.toString());
|
|
267
|
-
axis.setAttribute('stroke', '#9ca3af');
|
|
268
|
-
axis.setAttribute('stroke-width', '2');
|
|
269
|
-
svg.appendChild(axis);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
private _renderAxisLabel(svg: SVGSVGElement, text: string, x: number, y: number, rotate: number): void {
|
|
273
|
-
const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
274
|
-
label.setAttribute('x', x.toString());
|
|
275
|
-
label.setAttribute('y', y.toString());
|
|
276
|
-
label.setAttribute('text-anchor', 'middle');
|
|
277
|
-
if (rotate !== 0) label.setAttribute('transform', `rotate(${rotate}, ${x}, ${y})`);
|
|
278
|
-
label.setAttribute('fill', '#6b7280');
|
|
279
|
-
label.setAttribute('font-size', '12');
|
|
280
|
-
label.setAttribute('font-weight', '500');
|
|
281
|
-
label.textContent = text;
|
|
282
|
-
svg.appendChild(label);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
private _renderYTicks(svg: SVGSVGElement, maxValue: number, yScale: number, padding: any, width: number, height: number, unit: string, chartHeight: number): void {
|
|
286
|
-
const numTicks = 5;
|
|
287
|
-
for (let i = 0; i <= numTicks; i++) {
|
|
288
|
-
const value = (maxValue / numTicks) * i;
|
|
289
|
-
const y = height - padding.bottom - (value * yScale);
|
|
290
|
-
|
|
291
|
-
const gridLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
292
|
-
gridLine.setAttribute('x1', padding.left.toString());
|
|
293
|
-
gridLine.setAttribute('y1', y.toString());
|
|
294
|
-
gridLine.setAttribute('x2', (width - padding.right).toString());
|
|
295
|
-
gridLine.setAttribute('y2', y.toString());
|
|
296
|
-
gridLine.setAttribute('stroke', '#e5e7eb');
|
|
297
|
-
gridLine.setAttribute('stroke-width', '1');
|
|
298
|
-
gridLine.setAttribute('stroke-dasharray', '4,4');
|
|
299
|
-
svg.appendChild(gridLine);
|
|
300
|
-
|
|
301
|
-
const tickLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
302
|
-
tickLabel.setAttribute('x', (padding.left - 10).toString());
|
|
303
|
-
tickLabel.setAttribute('y', (y + 4).toString());
|
|
304
|
-
tickLabel.setAttribute('text-anchor', 'end');
|
|
305
|
-
tickLabel.setAttribute('fill', '#6b7280');
|
|
306
|
-
tickLabel.setAttribute('font-size', '11');
|
|
307
|
-
tickLabel.textContent = Math.round(value).toString() + (unit || '');
|
|
308
|
-
svg.appendChild(tickLabel);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
export function areachart(id: string, options: AreaChartOptions = {}): AreaChart {
|
|
314
|
-
return new AreaChart(id, options);
|
|
315
|
-
}
|