Chartjs
Build HTML dashboards with Chart.js and the Bonnard SDK. No build step required.
Chart.js is the recommended chart library for HTML dashboards — smallest payload (~65KB gzip), most LLM training data, and excellent documentation.
Starter template
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.
Use explore() to discover available views and fields — see Query Reference.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@bonnard/sdk/dist/bonnard.iife.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #09090b; color: #fafafa; padding: 24px;
}
h1 { font-size: 24px; font-weight: 600; margin-bottom: 24px; }
.error { color: #ef4444; background: #1c0a0a; padding: 12px; border-radius: 8px; margin-bottom: 16px; display: none; }
.kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px; margin-bottom: 32px; }
.kpi { background: #18181b; border: 1px solid #27272a; border-radius: 12px; padding: 20px; }
.kpi-label { font-size: 14px; color: #a1a1aa; margin-bottom: 8px; }
.kpi-value { font-size: 32px; font-weight: 600; }
.kpi-value.loading { color: #3f3f46; }
.charts { display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 24px; }
.chart-card { background: #18181b; border: 1px solid #27272a; border-radius: 12px; padding: 20px; }
.chart-title { font-size: 16px; font-weight: 500; margin-bottom: 16px; }
.chart-container { position: relative; height: 300px; }
</style>
</head>
<body>
<h1>Dashboard</h1>
<div id="error" class="error"></div>
<div class="kpis">
<div class="kpi">
<div class="kpi-label">Revenue</div>
<div class="kpi-value loading" id="kpi-revenue">--</div>
</div>
<div class="kpi">
<div class="kpi-label">Orders</div>
<div class="kpi-value loading" id="kpi-orders">--</div>
</div>
<div class="kpi">
<div class="kpi-label">Avg Value</div>
<div class="kpi-value loading" id="kpi-avg">--</div>
</div>
</div>
<div class="charts">
<div class="chart-card">
<div class="chart-title">Revenue by City</div>
<div class="chart-container"><canvas id="bar-chart"></canvas></div>
</div>
<div class="chart-card">
<div class="chart-title">Revenue Trend</div>
<div class="chart-container"><canvas id="line-chart"></canvas></div>
</div>
</div>
<script>
const bon = Bonnard.createClient({
apiKey: 'bon_pk_YOUR_KEY_HERE',
});
// --- Helpers ---
function showError(msg) {
const el = document.getElementById('error');
el.textContent = msg;
el.style.display = 'block';
}
function formatNumber(v) {
return new Intl.NumberFormat().format(v);
}
function formatCurrency(v) {
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }).format(v);
}
// Chart.js defaults for dark mode
Chart.defaults.color = '#a1a1aa';
Chart.defaults.borderColor = '#27272a';
// --- Load data ---
(async () => {
try {
// KPIs
const kpis = await bon.query({
measures: ['orders.revenue', 'orders.count', 'orders.avg_value'],
});
if (kpis.data.length > 0) {
const row = kpis.data[0];
document.getElementById('kpi-revenue').textContent = formatCurrency(row['orders.revenue']);
document.getElementById('kpi-revenue').classList.remove('loading');
document.getElementById('kpi-orders').textContent = formatNumber(row['orders.count']);
document.getElementById('kpi-orders').classList.remove('loading');
document.getElementById('kpi-avg').textContent = formatCurrency(row['orders.avg_value']);
document.getElementById('kpi-avg').classList.remove('loading');
}
// Bar chart — revenue by city
const byCity = await bon.query({
measures: ['orders.revenue'],
dimensions: ['orders.city'],
orderBy: { 'orders.revenue': 'desc' },
limit: 10,
});
new Chart(document.getElementById('bar-chart'), {
type: 'bar',
data: {
labels: byCity.data.map(d => d['orders.city']),
datasets: [{
label: 'Revenue',
data: byCity.data.map(d => d['orders.revenue']),
backgroundColor: '#3b82f6',
borderRadius: 4,
}],
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } },
scales: {
y: {
ticks: { callback: v => formatCurrency(v) },
grid: { color: '#27272a' },
},
x: { grid: { display: false } },
},
},
});
// Line chart — revenue trend
const trend = await bon.query({
measures: ['orders.revenue'],
timeDimension: {
dimension: 'orders.created_at',
granularity: 'month',
dateRange: 'last 12 months',
},
});
new Chart(document.getElementById('line-chart'), {
type: 'line',
data: {
labels: trend.data.map(d => {
const date = new Date(d['orders.created_at']);
return date.toLocaleDateString('en-US', { month: 'short', year: '2-digit' });
}),
datasets: [{
label: 'Revenue',
data: trend.data.map(d => d['orders.revenue']),
borderColor: '#3b82f6',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
fill: true,
tension: 0.3,
pointRadius: 3,
}],
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } },
scales: {
y: {
ticks: { callback: v => formatCurrency(v) },
grid: { color: '#27272a' },
},
x: { grid: { display: false } },
},
},
});
} catch (err) {
showError('Failed to load dashboard: ' + err.message);
}
})();
</script>
</body>
</html>Chart types
Bar chart
new Chart(ctx, {
type: 'bar',
data: {
labels: data.map(d => d['view.dimension']),
datasets: [{
label: 'Revenue',
data: data.map(d => d['view.measure']),
backgroundColor: '#3b82f6',
borderRadius: 4,
}],
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } },
},
});Horizontal bar chart
new Chart(ctx, {
type: 'bar',
data: { /* same as above */ },
options: {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
},
});Line chart
new Chart(ctx, {
type: 'line',
data: {
labels: data.map(d => new Date(d['view.date']).toLocaleDateString()),
datasets: [{
label: 'Revenue',
data: data.map(d => d['view.measure']),
borderColor: '#3b82f6',
tension: 0.3,
}],
},
});Area chart (filled line)
datasets: [{
label: 'Revenue',
data: data.map(d => d['view.measure']),
borderColor: '#3b82f6',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
fill: true,
}]Pie / doughnut chart
new Chart(ctx, {
type: 'doughnut', // or 'pie'
data: {
labels: data.map(d => d['view.dimension']),
datasets: [{
data: data.map(d => d['view.measure']),
backgroundColor: ['#3b82f6', '#22c55e', '#f59e0b', '#ef4444', '#8b5cf6'],
}],
},
});Multi-series line chart
// Query with a grouping dimension
const { data } = await bon.query({
measures: ['orders.revenue'],
dimensions: ['orders.city'],
timeDimension: { dimension: 'orders.created_at', granularity: 'month' },
});
// Group data by city
const cities = [...new Set(data.map(d => d['orders.city']))];
const dates = [...new Set(data.map(d => d['orders.created_at']))];
new Chart(ctx, {
type: 'line',
data: {
labels: dates.map(d => new Date(d).toLocaleDateString()),
datasets: cities.map((city, i) => ({
label: city,
data: dates.map(date =>
data.find(d => d['orders.city'] === city && d['orders.created_at'] === date)?.['orders.revenue'] || 0
),
borderColor: ['#3b82f6', '#22c55e', '#f59e0b', '#ef4444', '#8b5cf6'][i % 5],
})),
},
});Dark mode setup
Set Chart.js defaults before creating charts:
Chart.defaults.color = '#a1a1aa'; // label/tick color
Chart.defaults.borderColor = '#27272a'; // grid line colorColor palette
Recommended palette for multi-series charts:
const COLORS = ['#3b82f6', '#22c55e', '#f59e0b', '#ef4444', '#8b5cf6', '#ec4899', '#14b8a6', '#f97316'];See also
- Browser — Browser / CDN quickstart
- Query Reference — Full query API
- Echarts — ECharts alternative
- Apexcharts — ApexCharts alternative