@bonnard/cli 0.2.14 → 0.2.16
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 +2 -2
- package/dist/bin/bon.mjs +173 -2
- 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 +107 -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 +106 -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 +6 -1
- package/package.json +2 -2
- package/dist/docs/topics/dashboards.components.md +0 -227
- package/dist/docs/topics/dashboards.examples.md +0 -270
- package/dist/docs/topics/dashboards.inputs.md +0 -173
- package/dist/docs/topics/dashboards.md +0 -115
- package/dist/docs/topics/dashboards.queries.md +0 -112
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Browser / CDN Quickstart
|
|
2
|
+
|
|
3
|
+
> Load the Bonnard SDK via a `<script>` tag and query your semantic layer from any HTML page.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
Add the SDK script tag to your HTML. It exposes `window.Bonnard`.
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<script src="https://cdn.jsdelivr.net/npm/@bonnard/sdk/dist/bonnard.iife.js"></script>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Alternative CDN:
|
|
14
|
+
```html
|
|
15
|
+
<script src="https://unpkg.com/@bonnard/sdk/dist/bonnard.iife.js"></script>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Pin a specific version:
|
|
19
|
+
```html
|
|
20
|
+
<script src="https://cdn.jsdelivr.net/npm/@bonnard/sdk@0.4.2/dist/bonnard.iife.js"></script>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## First query
|
|
24
|
+
|
|
25
|
+
```html
|
|
26
|
+
<script src="https://cdn.jsdelivr.net/npm/@bonnard/sdk/dist/bonnard.iife.js"></script>
|
|
27
|
+
<script>
|
|
28
|
+
const bon = Bonnard.createClient({
|
|
29
|
+
apiKey: 'bon_pk_YOUR_KEY_HERE',
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
(async () => {
|
|
33
|
+
const { data } = await bon.query({
|
|
34
|
+
measures: ['orders.revenue', 'orders.count'],
|
|
35
|
+
dimensions: ['orders.city'],
|
|
36
|
+
orderBy: { 'orders.revenue': 'desc' },
|
|
37
|
+
limit: 10,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
console.log(data);
|
|
41
|
+
// [{ "orders.revenue": 125000, "orders.count": 340, "orders.city": "Berlin" }, ...]
|
|
42
|
+
})();
|
|
43
|
+
</script>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Note: the IIFE bundle uses a regular `<script>` tag (not `type="module"`), so top-level `await` is not available. Wrap async code in an IIFE or use `.then()`.
|
|
47
|
+
|
|
48
|
+
## Async patterns
|
|
49
|
+
|
|
50
|
+
### IIFE wrapper (recommended)
|
|
51
|
+
|
|
52
|
+
```html
|
|
53
|
+
<script>
|
|
54
|
+
(async () => {
|
|
55
|
+
const bon = Bonnard.createClient({ apiKey: 'bon_pk_...' });
|
|
56
|
+
const { data } = await bon.query({ measures: ['orders.revenue'] });
|
|
57
|
+
document.getElementById('revenue').textContent = data[0]['orders.revenue'];
|
|
58
|
+
})();
|
|
59
|
+
</script>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Promise chain
|
|
63
|
+
|
|
64
|
+
```html
|
|
65
|
+
<script>
|
|
66
|
+
const bon = Bonnard.createClient({ apiKey: 'bon_pk_...' });
|
|
67
|
+
|
|
68
|
+
bon.query({ measures: ['orders.revenue'] })
|
|
69
|
+
.then(({ data }) => {
|
|
70
|
+
document.getElementById('revenue').textContent = data[0]['orders.revenue'];
|
|
71
|
+
})
|
|
72
|
+
.catch(err => {
|
|
73
|
+
console.error('Query failed:', err.message);
|
|
74
|
+
});
|
|
75
|
+
</script>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Parallel queries
|
|
79
|
+
|
|
80
|
+
```html
|
|
81
|
+
<script>
|
|
82
|
+
(async () => {
|
|
83
|
+
const bon = Bonnard.createClient({ apiKey: 'bon_pk_...' });
|
|
84
|
+
|
|
85
|
+
const [kpis, byCity] = await Promise.all([
|
|
86
|
+
bon.query({ measures: ['orders.revenue', 'orders.count'] }),
|
|
87
|
+
bon.query({
|
|
88
|
+
measures: ['orders.revenue'],
|
|
89
|
+
dimensions: ['orders.city'],
|
|
90
|
+
orderBy: { 'orders.revenue': 'desc' },
|
|
91
|
+
}),
|
|
92
|
+
]);
|
|
93
|
+
|
|
94
|
+
// Render KPIs
|
|
95
|
+
document.getElementById('revenue').textContent = kpis.data[0]['orders.revenue'];
|
|
96
|
+
document.getElementById('count').textContent = kpis.data[0]['orders.count'];
|
|
97
|
+
|
|
98
|
+
// Render chart with byCity.data...
|
|
99
|
+
})();
|
|
100
|
+
</script>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Error handling
|
|
104
|
+
|
|
105
|
+
```html
|
|
106
|
+
<script>
|
|
107
|
+
(async () => {
|
|
108
|
+
const bon = Bonnard.createClient({ apiKey: 'bon_pk_...' });
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const { data } = await bon.query({
|
|
112
|
+
measures: ['orders.revenue'],
|
|
113
|
+
});
|
|
114
|
+
renderDashboard(data);
|
|
115
|
+
} catch (err) {
|
|
116
|
+
document.getElementById('error').textContent = err.message;
|
|
117
|
+
document.getElementById('error').style.display = 'block';
|
|
118
|
+
}
|
|
119
|
+
})();
|
|
120
|
+
</script>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Common errors:
|
|
124
|
+
- `"Unauthorized"` — invalid or expired API key
|
|
125
|
+
- `"Query failed"` — invalid measure/dimension names or query structure
|
|
126
|
+
- Network errors — API unreachable (CORS, connectivity)
|
|
127
|
+
|
|
128
|
+
## Custom base URL
|
|
129
|
+
|
|
130
|
+
By default the SDK points to `https://app.bonnard.dev`. Override for self-hosted or preview deployments:
|
|
131
|
+
|
|
132
|
+
```html
|
|
133
|
+
<script>
|
|
134
|
+
const bon = Bonnard.createClient({
|
|
135
|
+
apiKey: 'bon_pk_...',
|
|
136
|
+
baseUrl: 'https://your-deployment.vercel.app',
|
|
137
|
+
});
|
|
138
|
+
</script>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## What's on `window.Bonnard`
|
|
142
|
+
|
|
143
|
+
The IIFE bundle exposes two exports:
|
|
144
|
+
|
|
145
|
+
| Export | Purpose |
|
|
146
|
+
|--------|---------|
|
|
147
|
+
| `Bonnard.createClient(config)` | Create an SDK client instance |
|
|
148
|
+
| `Bonnard.toCubeQuery(options)` | Convert `QueryOptions` to a Cube-native query object (useful for debugging) |
|
|
149
|
+
|
|
150
|
+
## Field naming
|
|
151
|
+
|
|
152
|
+
All field names must be fully qualified with the view name:
|
|
153
|
+
|
|
154
|
+
```javascript
|
|
155
|
+
// Correct
|
|
156
|
+
bon.query({ measures: ['orders.revenue'], dimensions: ['orders.city'] });
|
|
157
|
+
|
|
158
|
+
// Wrong — will fail
|
|
159
|
+
bon.query({ measures: ['revenue'], dimensions: ['city'] });
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Discovering available fields
|
|
163
|
+
|
|
164
|
+
Use `explore()` to discover what views, measures, and dimensions are available:
|
|
165
|
+
|
|
166
|
+
```javascript
|
|
167
|
+
const meta = await bon.explore();
|
|
168
|
+
for (const view of meta.cubes) {
|
|
169
|
+
console.log(view.name);
|
|
170
|
+
console.log(' Measures:', view.measures.map(m => m.name));
|
|
171
|
+
console.log(' Dimensions:', view.dimensions.map(d => d.name));
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Next steps
|
|
176
|
+
|
|
177
|
+
- [sdk.chartjs](sdk.chartjs) — Build a dashboard with Chart.js
|
|
178
|
+
- [sdk.echarts](sdk.echarts) — Build a dashboard with ECharts
|
|
179
|
+
- [sdk.apexcharts](sdk.apexcharts) — Build a dashboard with ApexCharts
|
|
180
|
+
- [sdk.query-reference](sdk.query-reference) — Full query API reference
|
|
181
|
+
- [sdk.authentication](sdk.authentication) — Auth patterns for multi-tenant apps
|
|
@@ -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
|