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