@bonnard/cli 0.2.15 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -2
- package/dist/bin/bon.mjs +384 -32
- package/dist/docs/topics/dashboards.md +21 -24
- package/dist/docs/topics/sdk.apexcharts.md +281 -0
- package/dist/docs/topics/sdk.authentication.md +130 -0
- package/dist/docs/topics/sdk.browser.md +181 -0
- package/dist/docs/topics/sdk.chartjs.md +327 -0
- package/dist/docs/topics/sdk.echarts.md +297 -0
- package/dist/docs/topics/sdk.md +95 -0
- package/dist/docs/topics/sdk.query-reference.md +307 -0
- package/dist/templates/claude/skills/bonnard-build-dashboard/SKILL.md +145 -0
- package/dist/templates/claude/skills/bonnard-get-started/SKILL.md +1 -1
- package/dist/templates/claude/skills/bonnard-metabase-migrate/SKILL.md +1 -1
- package/dist/templates/cursor/rules/bonnard-build-dashboard.mdc +144 -0
- package/dist/templates/cursor/rules/bonnard-get-started.mdc +1 -1
- package/dist/templates/cursor/rules/bonnard-metabase-migrate.mdc +1 -1
- package/dist/templates/shared/bonnard.md +7 -1
- package/dist/viewer.html +261 -0
- package/package.json +11 -2
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# Chart.js + Bonnard SDK
|
|
2
|
+
|
|
3
|
+
> Build HTML dashboards with Chart.js and the Bonnard SDK. No build step required.
|
|
4
|
+
|
|
5
|
+
Chart.js is the recommended chart library for HTML dashboards — smallest payload (~65KB gzip), most LLM training data, and excellent documentation.
|
|
6
|
+
|
|
7
|
+
## Starter template
|
|
8
|
+
|
|
9
|
+
Copy this complete HTML file as a starting point. Replace `bon_pk_YOUR_KEY_HERE` with your publishable API key, and update the view/measure/dimension names to match your schema.
|
|
10
|
+
|
|
11
|
+
Use `explore()` to discover available views and fields — see [sdk.query-reference](sdk.query-reference).
|
|
12
|
+
|
|
13
|
+
```html
|
|
14
|
+
<!DOCTYPE html>
|
|
15
|
+
<html lang="en">
|
|
16
|
+
<head>
|
|
17
|
+
<meta charset="UTF-8">
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
19
|
+
<title>Dashboard</title>
|
|
20
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js"></script>
|
|
21
|
+
<script src="https://cdn.jsdelivr.net/npm/@bonnard/sdk/dist/bonnard.iife.js"></script>
|
|
22
|
+
<style>
|
|
23
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
24
|
+
body {
|
|
25
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
26
|
+
background: #09090b; color: #fafafa; padding: 24px;
|
|
27
|
+
}
|
|
28
|
+
h1 { font-size: 24px; font-weight: 600; margin-bottom: 24px; }
|
|
29
|
+
.error { color: #ef4444; background: #1c0a0a; padding: 12px; border-radius: 8px; margin-bottom: 16px; display: none; }
|
|
30
|
+
.kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px; margin-bottom: 32px; }
|
|
31
|
+
.kpi { background: #18181b; border: 1px solid #27272a; border-radius: 12px; padding: 20px; }
|
|
32
|
+
.kpi-label { font-size: 14px; color: #a1a1aa; margin-bottom: 8px; }
|
|
33
|
+
.kpi-value { font-size: 32px; font-weight: 600; }
|
|
34
|
+
.kpi-value.loading { color: #3f3f46; }
|
|
35
|
+
.charts { display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 24px; }
|
|
36
|
+
.chart-card { background: #18181b; border: 1px solid #27272a; border-radius: 12px; padding: 20px; }
|
|
37
|
+
.chart-title { font-size: 16px; font-weight: 500; margin-bottom: 16px; }
|
|
38
|
+
.chart-container { position: relative; height: 300px; }
|
|
39
|
+
</style>
|
|
40
|
+
</head>
|
|
41
|
+
<body>
|
|
42
|
+
<h1>Dashboard</h1>
|
|
43
|
+
<div id="error" class="error"></div>
|
|
44
|
+
|
|
45
|
+
<div class="kpis">
|
|
46
|
+
<div class="kpi">
|
|
47
|
+
<div class="kpi-label">Revenue</div>
|
|
48
|
+
<div class="kpi-value loading" id="kpi-revenue">--</div>
|
|
49
|
+
</div>
|
|
50
|
+
<div class="kpi">
|
|
51
|
+
<div class="kpi-label">Orders</div>
|
|
52
|
+
<div class="kpi-value loading" id="kpi-orders">--</div>
|
|
53
|
+
</div>
|
|
54
|
+
<div class="kpi">
|
|
55
|
+
<div class="kpi-label">Avg Value</div>
|
|
56
|
+
<div class="kpi-value loading" id="kpi-avg">--</div>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div class="charts">
|
|
61
|
+
<div class="chart-card">
|
|
62
|
+
<div class="chart-title">Revenue by City</div>
|
|
63
|
+
<div class="chart-container"><canvas id="bar-chart"></canvas></div>
|
|
64
|
+
</div>
|
|
65
|
+
<div class="chart-card">
|
|
66
|
+
<div class="chart-title">Revenue Trend</div>
|
|
67
|
+
<div class="chart-container"><canvas id="line-chart"></canvas></div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<script>
|
|
72
|
+
const bon = Bonnard.createClient({
|
|
73
|
+
apiKey: 'bon_pk_YOUR_KEY_HERE',
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// --- Helpers ---
|
|
77
|
+
function showError(msg) {
|
|
78
|
+
const el = document.getElementById('error');
|
|
79
|
+
el.textContent = msg;
|
|
80
|
+
el.style.display = 'block';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function formatNumber(v) {
|
|
84
|
+
return new Intl.NumberFormat().format(v);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function formatCurrency(v) {
|
|
88
|
+
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }).format(v);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Chart.js defaults for dark mode
|
|
92
|
+
Chart.defaults.color = '#a1a1aa';
|
|
93
|
+
Chart.defaults.borderColor = '#27272a';
|
|
94
|
+
|
|
95
|
+
// --- Load data ---
|
|
96
|
+
(async () => {
|
|
97
|
+
try {
|
|
98
|
+
// KPIs
|
|
99
|
+
const kpis = await bon.query({
|
|
100
|
+
measures: ['orders.revenue', 'orders.count', 'orders.avg_value'],
|
|
101
|
+
});
|
|
102
|
+
if (kpis.data.length > 0) {
|
|
103
|
+
const row = kpis.data[0];
|
|
104
|
+
document.getElementById('kpi-revenue').textContent = formatCurrency(row['orders.revenue']);
|
|
105
|
+
document.getElementById('kpi-revenue').classList.remove('loading');
|
|
106
|
+
document.getElementById('kpi-orders').textContent = formatNumber(row['orders.count']);
|
|
107
|
+
document.getElementById('kpi-orders').classList.remove('loading');
|
|
108
|
+
document.getElementById('kpi-avg').textContent = formatCurrency(row['orders.avg_value']);
|
|
109
|
+
document.getElementById('kpi-avg').classList.remove('loading');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Bar chart — revenue by city
|
|
113
|
+
const byCity = await bon.query({
|
|
114
|
+
measures: ['orders.revenue'],
|
|
115
|
+
dimensions: ['orders.city'],
|
|
116
|
+
orderBy: { 'orders.revenue': 'desc' },
|
|
117
|
+
limit: 10,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
new Chart(document.getElementById('bar-chart'), {
|
|
121
|
+
type: 'bar',
|
|
122
|
+
data: {
|
|
123
|
+
labels: byCity.data.map(d => d['orders.city']),
|
|
124
|
+
datasets: [{
|
|
125
|
+
label: 'Revenue',
|
|
126
|
+
data: byCity.data.map(d => d['orders.revenue']),
|
|
127
|
+
backgroundColor: '#3b82f6',
|
|
128
|
+
borderRadius: 4,
|
|
129
|
+
}],
|
|
130
|
+
},
|
|
131
|
+
options: {
|
|
132
|
+
responsive: true,
|
|
133
|
+
maintainAspectRatio: false,
|
|
134
|
+
plugins: { legend: { display: false } },
|
|
135
|
+
scales: {
|
|
136
|
+
y: {
|
|
137
|
+
ticks: { callback: v => formatCurrency(v) },
|
|
138
|
+
grid: { color: '#27272a' },
|
|
139
|
+
},
|
|
140
|
+
x: { grid: { display: false } },
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Line chart — revenue trend
|
|
146
|
+
const trend = await bon.query({
|
|
147
|
+
measures: ['orders.revenue'],
|
|
148
|
+
timeDimension: {
|
|
149
|
+
dimension: 'orders.created_at',
|
|
150
|
+
granularity: 'month',
|
|
151
|
+
dateRange: 'last 12 months',
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
new Chart(document.getElementById('line-chart'), {
|
|
156
|
+
type: 'line',
|
|
157
|
+
data: {
|
|
158
|
+
labels: trend.data.map(d => {
|
|
159
|
+
const date = new Date(d['orders.created_at']);
|
|
160
|
+
return date.toLocaleDateString('en-US', { month: 'short', year: '2-digit' });
|
|
161
|
+
}),
|
|
162
|
+
datasets: [{
|
|
163
|
+
label: 'Revenue',
|
|
164
|
+
data: trend.data.map(d => d['orders.revenue']),
|
|
165
|
+
borderColor: '#3b82f6',
|
|
166
|
+
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
|
167
|
+
fill: true,
|
|
168
|
+
tension: 0.3,
|
|
169
|
+
pointRadius: 3,
|
|
170
|
+
}],
|
|
171
|
+
},
|
|
172
|
+
options: {
|
|
173
|
+
responsive: true,
|
|
174
|
+
maintainAspectRatio: false,
|
|
175
|
+
plugins: { legend: { display: false } },
|
|
176
|
+
scales: {
|
|
177
|
+
y: {
|
|
178
|
+
ticks: { callback: v => formatCurrency(v) },
|
|
179
|
+
grid: { color: '#27272a' },
|
|
180
|
+
},
|
|
181
|
+
x: { grid: { display: false } },
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
} catch (err) {
|
|
186
|
+
showError('Failed to load dashboard: ' + err.message);
|
|
187
|
+
}
|
|
188
|
+
})();
|
|
189
|
+
</script>
|
|
190
|
+
</body>
|
|
191
|
+
</html>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Chart types
|
|
195
|
+
|
|
196
|
+
### Bar chart
|
|
197
|
+
|
|
198
|
+
```javascript
|
|
199
|
+
new Chart(ctx, {
|
|
200
|
+
type: 'bar',
|
|
201
|
+
data: {
|
|
202
|
+
labels: data.map(d => d['view.dimension']),
|
|
203
|
+
datasets: [{
|
|
204
|
+
label: 'Revenue',
|
|
205
|
+
data: data.map(d => d['view.measure']),
|
|
206
|
+
backgroundColor: '#3b82f6',
|
|
207
|
+
borderRadius: 4,
|
|
208
|
+
}],
|
|
209
|
+
},
|
|
210
|
+
options: {
|
|
211
|
+
responsive: true,
|
|
212
|
+
maintainAspectRatio: false,
|
|
213
|
+
plugins: { legend: { display: false } },
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Horizontal bar chart
|
|
219
|
+
|
|
220
|
+
```javascript
|
|
221
|
+
new Chart(ctx, {
|
|
222
|
+
type: 'bar',
|
|
223
|
+
data: { /* same as above */ },
|
|
224
|
+
options: {
|
|
225
|
+
indexAxis: 'y',
|
|
226
|
+
responsive: true,
|
|
227
|
+
maintainAspectRatio: false,
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Line chart
|
|
233
|
+
|
|
234
|
+
```javascript
|
|
235
|
+
new Chart(ctx, {
|
|
236
|
+
type: 'line',
|
|
237
|
+
data: {
|
|
238
|
+
labels: data.map(d => new Date(d['view.date']).toLocaleDateString()),
|
|
239
|
+
datasets: [{
|
|
240
|
+
label: 'Revenue',
|
|
241
|
+
data: data.map(d => d['view.measure']),
|
|
242
|
+
borderColor: '#3b82f6',
|
|
243
|
+
tension: 0.3,
|
|
244
|
+
}],
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Area chart (filled line)
|
|
250
|
+
|
|
251
|
+
```javascript
|
|
252
|
+
datasets: [{
|
|
253
|
+
label: 'Revenue',
|
|
254
|
+
data: data.map(d => d['view.measure']),
|
|
255
|
+
borderColor: '#3b82f6',
|
|
256
|
+
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
|
257
|
+
fill: true,
|
|
258
|
+
}]
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Pie / doughnut chart
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
new Chart(ctx, {
|
|
265
|
+
type: 'doughnut', // or 'pie'
|
|
266
|
+
data: {
|
|
267
|
+
labels: data.map(d => d['view.dimension']),
|
|
268
|
+
datasets: [{
|
|
269
|
+
data: data.map(d => d['view.measure']),
|
|
270
|
+
backgroundColor: ['#3b82f6', '#22c55e', '#f59e0b', '#ef4444', '#8b5cf6'],
|
|
271
|
+
}],
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Multi-series line chart
|
|
277
|
+
|
|
278
|
+
```javascript
|
|
279
|
+
// Query with a grouping dimension
|
|
280
|
+
const { data } = await bon.query({
|
|
281
|
+
measures: ['orders.revenue'],
|
|
282
|
+
dimensions: ['orders.city'],
|
|
283
|
+
timeDimension: { dimension: 'orders.created_at', granularity: 'month' },
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// Group data by city
|
|
287
|
+
const cities = [...new Set(data.map(d => d['orders.city']))];
|
|
288
|
+
const dates = [...new Set(data.map(d => d['orders.created_at']))];
|
|
289
|
+
|
|
290
|
+
new Chart(ctx, {
|
|
291
|
+
type: 'line',
|
|
292
|
+
data: {
|
|
293
|
+
labels: dates.map(d => new Date(d).toLocaleDateString()),
|
|
294
|
+
datasets: cities.map((city, i) => ({
|
|
295
|
+
label: city,
|
|
296
|
+
data: dates.map(date =>
|
|
297
|
+
data.find(d => d['orders.city'] === city && d['orders.created_at'] === date)?.['orders.revenue'] || 0
|
|
298
|
+
),
|
|
299
|
+
borderColor: ['#3b82f6', '#22c55e', '#f59e0b', '#ef4444', '#8b5cf6'][i % 5],
|
|
300
|
+
})),
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Dark mode setup
|
|
306
|
+
|
|
307
|
+
Set Chart.js defaults before creating charts:
|
|
308
|
+
|
|
309
|
+
```javascript
|
|
310
|
+
Chart.defaults.color = '#a1a1aa'; // label/tick color
|
|
311
|
+
Chart.defaults.borderColor = '#27272a'; // grid line color
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## Color palette
|
|
315
|
+
|
|
316
|
+
Recommended palette for multi-series charts:
|
|
317
|
+
|
|
318
|
+
```javascript
|
|
319
|
+
const COLORS = ['#3b82f6', '#22c55e', '#f59e0b', '#ef4444', '#8b5cf6', '#ec4899', '#14b8a6', '#f97316'];
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## See also
|
|
323
|
+
|
|
324
|
+
- [sdk.browser](sdk.browser) — Browser / CDN quickstart
|
|
325
|
+
- [sdk.query-reference](sdk.query-reference) — Full query API
|
|
326
|
+
- [sdk.echarts](sdk.echarts) — ECharts alternative
|
|
327
|
+
- [sdk.apexcharts](sdk.apexcharts) — ApexCharts alternative
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
# ECharts + Bonnard SDK
|
|
2
|
+
|
|
3
|
+
> Build HTML dashboards with Apache ECharts and the Bonnard SDK. No build step required.
|
|
4
|
+
|
|
5
|
+
ECharts offers rich interactivity (tooltips, zoom, legend toggling), built-in dark theme, and extensive chart types. Larger payload than Chart.js (~160KB gzip) but more powerful.
|
|
6
|
+
|
|
7
|
+
## Starter template
|
|
8
|
+
|
|
9
|
+
Copy this complete HTML file as a starting point. Replace `bon_pk_YOUR_KEY_HERE` with your publishable API key, and update the view/measure/dimension names to match your schema.
|
|
10
|
+
|
|
11
|
+
Use `explore()` to discover available views and fields — see [sdk.query-reference](sdk.query-reference).
|
|
12
|
+
|
|
13
|
+
```html
|
|
14
|
+
<!DOCTYPE html>
|
|
15
|
+
<html lang="en">
|
|
16
|
+
<head>
|
|
17
|
+
<meta charset="UTF-8">
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
19
|
+
<title>Dashboard</title>
|
|
20
|
+
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
|
|
21
|
+
<script src="https://cdn.jsdelivr.net/npm/@bonnard/sdk/dist/bonnard.iife.js"></script>
|
|
22
|
+
<style>
|
|
23
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
24
|
+
body {
|
|
25
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
26
|
+
background: #09090b; color: #fafafa; padding: 24px;
|
|
27
|
+
}
|
|
28
|
+
h1 { font-size: 24px; font-weight: 600; margin-bottom: 24px; }
|
|
29
|
+
.error { color: #ef4444; background: #1c0a0a; padding: 12px; border-radius: 8px; margin-bottom: 16px; display: none; }
|
|
30
|
+
.kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px; margin-bottom: 32px; }
|
|
31
|
+
.kpi { background: #18181b; border: 1px solid #27272a; border-radius: 12px; padding: 20px; }
|
|
32
|
+
.kpi-label { font-size: 14px; color: #a1a1aa; margin-bottom: 8px; }
|
|
33
|
+
.kpi-value { font-size: 32px; font-weight: 600; }
|
|
34
|
+
.kpi-value.loading { color: #3f3f46; }
|
|
35
|
+
.charts { display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 24px; }
|
|
36
|
+
.chart-card { background: #18181b; border: 1px solid #27272a; border-radius: 12px; padding: 20px; }
|
|
37
|
+
.chart-title { font-size: 16px; font-weight: 500; margin-bottom: 16px; }
|
|
38
|
+
.chart-container { height: 300px; }
|
|
39
|
+
</style>
|
|
40
|
+
</head>
|
|
41
|
+
<body>
|
|
42
|
+
<h1>Dashboard</h1>
|
|
43
|
+
<div id="error" class="error"></div>
|
|
44
|
+
|
|
45
|
+
<div class="kpis">
|
|
46
|
+
<div class="kpi">
|
|
47
|
+
<div class="kpi-label">Revenue</div>
|
|
48
|
+
<div class="kpi-value loading" id="kpi-revenue">--</div>
|
|
49
|
+
</div>
|
|
50
|
+
<div class="kpi">
|
|
51
|
+
<div class="kpi-label">Orders</div>
|
|
52
|
+
<div class="kpi-value loading" id="kpi-orders">--</div>
|
|
53
|
+
</div>
|
|
54
|
+
<div class="kpi">
|
|
55
|
+
<div class="kpi-label">Avg Value</div>
|
|
56
|
+
<div class="kpi-value loading" id="kpi-avg">--</div>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div class="charts">
|
|
61
|
+
<div class="chart-card">
|
|
62
|
+
<div class="chart-title">Revenue by City</div>
|
|
63
|
+
<div class="chart-container" id="bar-chart"></div>
|
|
64
|
+
</div>
|
|
65
|
+
<div class="chart-card">
|
|
66
|
+
<div class="chart-title">Revenue Trend</div>
|
|
67
|
+
<div class="chart-container" id="line-chart"></div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<script>
|
|
72
|
+
const bon = Bonnard.createClient({
|
|
73
|
+
apiKey: 'bon_pk_YOUR_KEY_HERE',
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// --- Helpers ---
|
|
77
|
+
function showError(msg) {
|
|
78
|
+
const el = document.getElementById('error');
|
|
79
|
+
el.textContent = msg;
|
|
80
|
+
el.style.display = 'block';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function formatNumber(v) {
|
|
84
|
+
return new Intl.NumberFormat().format(v);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function formatCurrency(v) {
|
|
88
|
+
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }).format(v);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Resize all charts on window resize
|
|
92
|
+
const charts = [];
|
|
93
|
+
window.addEventListener('resize', () => charts.forEach(c => c.resize()));
|
|
94
|
+
|
|
95
|
+
// --- Load data ---
|
|
96
|
+
(async () => {
|
|
97
|
+
try {
|
|
98
|
+
// KPIs
|
|
99
|
+
const kpis = await bon.query({
|
|
100
|
+
measures: ['orders.revenue', 'orders.count', 'orders.avg_value'],
|
|
101
|
+
});
|
|
102
|
+
if (kpis.data.length > 0) {
|
|
103
|
+
const row = kpis.data[0];
|
|
104
|
+
document.getElementById('kpi-revenue').textContent = formatCurrency(row['orders.revenue']);
|
|
105
|
+
document.getElementById('kpi-revenue').classList.remove('loading');
|
|
106
|
+
document.getElementById('kpi-orders').textContent = formatNumber(row['orders.count']);
|
|
107
|
+
document.getElementById('kpi-orders').classList.remove('loading');
|
|
108
|
+
document.getElementById('kpi-avg').textContent = formatCurrency(row['orders.avg_value']);
|
|
109
|
+
document.getElementById('kpi-avg').classList.remove('loading');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Bar chart — revenue by city
|
|
113
|
+
const byCity = await bon.query({
|
|
114
|
+
measures: ['orders.revenue'],
|
|
115
|
+
dimensions: ['orders.city'],
|
|
116
|
+
orderBy: { 'orders.revenue': 'desc' },
|
|
117
|
+
limit: 10,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const barChart = echarts.init(document.getElementById('bar-chart'), 'dark');
|
|
121
|
+
charts.push(barChart);
|
|
122
|
+
barChart.setOption({
|
|
123
|
+
tooltip: { trigger: 'axis', formatter: p => `${p[0].name}: ${formatCurrency(p[0].value)}` },
|
|
124
|
+
xAxis: { type: 'category', data: byCity.data.map(d => d['orders.city']) },
|
|
125
|
+
yAxis: { type: 'value', axisLabel: { formatter: v => formatCurrency(v) } },
|
|
126
|
+
series: [{
|
|
127
|
+
type: 'bar',
|
|
128
|
+
data: byCity.data.map(d => d['orders.revenue']),
|
|
129
|
+
itemStyle: { color: '#3b82f6', borderRadius: [4, 4, 0, 0] },
|
|
130
|
+
}],
|
|
131
|
+
grid: { left: 80, right: 20, top: 20, bottom: 40 },
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Line chart — revenue trend
|
|
135
|
+
const trend = await bon.query({
|
|
136
|
+
measures: ['orders.revenue'],
|
|
137
|
+
timeDimension: {
|
|
138
|
+
dimension: 'orders.created_at',
|
|
139
|
+
granularity: 'month',
|
|
140
|
+
dateRange: 'last 12 months',
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const lineChart = echarts.init(document.getElementById('line-chart'), 'dark');
|
|
145
|
+
charts.push(lineChart);
|
|
146
|
+
lineChart.setOption({
|
|
147
|
+
tooltip: { trigger: 'axis', formatter: p => `${p[0].name}: ${formatCurrency(p[0].value)}` },
|
|
148
|
+
xAxis: {
|
|
149
|
+
type: 'category',
|
|
150
|
+
data: trend.data.map(d => {
|
|
151
|
+
const date = new Date(d['orders.created_at']);
|
|
152
|
+
return date.toLocaleDateString('en-US', { month: 'short', year: '2-digit' });
|
|
153
|
+
}),
|
|
154
|
+
},
|
|
155
|
+
yAxis: { type: 'value', axisLabel: { formatter: v => formatCurrency(v) } },
|
|
156
|
+
series: [{
|
|
157
|
+
type: 'line',
|
|
158
|
+
data: trend.data.map(d => d['orders.revenue']),
|
|
159
|
+
smooth: true,
|
|
160
|
+
itemStyle: { color: '#3b82f6' },
|
|
161
|
+
areaStyle: { color: 'rgba(59, 130, 246, 0.15)' },
|
|
162
|
+
}],
|
|
163
|
+
grid: { left: 80, right: 20, top: 20, bottom: 40 },
|
|
164
|
+
});
|
|
165
|
+
} catch (err) {
|
|
166
|
+
showError('Failed to load dashboard: ' + err.message);
|
|
167
|
+
}
|
|
168
|
+
})();
|
|
169
|
+
</script>
|
|
170
|
+
</body>
|
|
171
|
+
</html>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Chart types
|
|
175
|
+
|
|
176
|
+
### Bar chart
|
|
177
|
+
|
|
178
|
+
```javascript
|
|
179
|
+
const chart = echarts.init(el, 'dark');
|
|
180
|
+
chart.setOption({
|
|
181
|
+
xAxis: { type: 'category', data: data.map(d => d['view.dimension']) },
|
|
182
|
+
yAxis: { type: 'value' },
|
|
183
|
+
series: [{ type: 'bar', data: data.map(d => d['view.measure']) }],
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Horizontal bar chart
|
|
188
|
+
|
|
189
|
+
```javascript
|
|
190
|
+
chart.setOption({
|
|
191
|
+
xAxis: { type: 'value' },
|
|
192
|
+
yAxis: { type: 'category', data: data.map(d => d['view.dimension']) },
|
|
193
|
+
series: [{ type: 'bar', data: data.map(d => d['view.measure']) }],
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Line chart
|
|
198
|
+
|
|
199
|
+
```javascript
|
|
200
|
+
chart.setOption({
|
|
201
|
+
xAxis: { type: 'category', data: labels },
|
|
202
|
+
yAxis: { type: 'value' },
|
|
203
|
+
series: [{ type: 'line', data: values, smooth: true }],
|
|
204
|
+
});
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Area chart
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
series: [{
|
|
211
|
+
type: 'line',
|
|
212
|
+
data: values,
|
|
213
|
+
smooth: true,
|
|
214
|
+
areaStyle: { color: 'rgba(59, 130, 246, 0.15)' },
|
|
215
|
+
}]
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Pie chart
|
|
219
|
+
|
|
220
|
+
```javascript
|
|
221
|
+
chart.setOption({
|
|
222
|
+
tooltip: { trigger: 'item' },
|
|
223
|
+
series: [{
|
|
224
|
+
type: 'pie',
|
|
225
|
+
radius: ['40%', '70%'], // doughnut — use '60%' for full pie
|
|
226
|
+
data: data.map(d => ({
|
|
227
|
+
name: d['view.dimension'],
|
|
228
|
+
value: d['view.measure'],
|
|
229
|
+
})),
|
|
230
|
+
}],
|
|
231
|
+
});
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Multi-series line chart
|
|
235
|
+
|
|
236
|
+
```javascript
|
|
237
|
+
const cities = [...new Set(data.map(d => d['orders.city']))];
|
|
238
|
+
const dates = [...new Set(data.map(d => d['orders.created_at']))];
|
|
239
|
+
|
|
240
|
+
chart.setOption({
|
|
241
|
+
tooltip: { trigger: 'axis' },
|
|
242
|
+
legend: { data: cities },
|
|
243
|
+
xAxis: { type: 'category', data: dates.map(d => new Date(d).toLocaleDateString()) },
|
|
244
|
+
yAxis: { type: 'value' },
|
|
245
|
+
series: cities.map(city => ({
|
|
246
|
+
name: city,
|
|
247
|
+
type: 'line',
|
|
248
|
+
smooth: true,
|
|
249
|
+
data: dates.map(date =>
|
|
250
|
+
data.find(d => d['orders.city'] === city && d['orders.created_at'] === date)?.['orders.revenue'] || 0
|
|
251
|
+
),
|
|
252
|
+
})),
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Dark mode
|
|
257
|
+
|
|
258
|
+
ECharts has a built-in dark theme — pass `'dark'` as the second argument to `echarts.init()`:
|
|
259
|
+
|
|
260
|
+
```javascript
|
|
261
|
+
const chart = echarts.init(document.getElementById('chart'), 'dark');
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
The dark theme sets appropriate background, text, axis, and tooltip colors automatically.
|
|
265
|
+
|
|
266
|
+
## Resize handling
|
|
267
|
+
|
|
268
|
+
ECharts charts don't auto-resize. Add a resize listener:
|
|
269
|
+
|
|
270
|
+
```javascript
|
|
271
|
+
const charts = [];
|
|
272
|
+
|
|
273
|
+
// After creating each chart:
|
|
274
|
+
charts.push(chart);
|
|
275
|
+
|
|
276
|
+
// Global resize handler:
|
|
277
|
+
window.addEventListener('resize', () => charts.forEach(c => c.resize()));
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Color palette
|
|
281
|
+
|
|
282
|
+
```javascript
|
|
283
|
+
const COLORS = ['#3b82f6', '#22c55e', '#f59e0b', '#ef4444', '#8b5cf6', '#ec4899', '#14b8a6', '#f97316'];
|
|
284
|
+
|
|
285
|
+
// Apply to a series:
|
|
286
|
+
series: data.map((d, i) => ({
|
|
287
|
+
// ...
|
|
288
|
+
itemStyle: { color: COLORS[i % COLORS.length] },
|
|
289
|
+
}))
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## See also
|
|
293
|
+
|
|
294
|
+
- [sdk.browser](sdk.browser) — Browser / CDN quickstart
|
|
295
|
+
- [sdk.query-reference](sdk.query-reference) — Full query API
|
|
296
|
+
- [sdk.chartjs](sdk.chartjs) — Chart.js alternative (smaller payload)
|
|
297
|
+
- [sdk.apexcharts](sdk.apexcharts) — ApexCharts alternative
|