Skip to content

Data Visualization -- D3.js, Chart.js & Grafana Dashboards

DodaTech Updated 2026-06-22 5 min read

In this tutorial, you'll learn about Data Visualization. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

Data visualization transforms raw analytics data into actionable insights using tools like D3.js for custom charts, Chart.js for quick deployment, and Grafana for real-time operational dashboards.

What You'll Learn

In this tutorial, you will learn how to build interactive data visualizations for analytics dashboards using D3.js for custom SVG charts, Chart.js for rapid charting, and Grafana for server-side dashboarding with Prometheus and SQL data sources.

Why It Matters

Raw data in spreadsheets reveals nothing. A well-designed chart can communicate trends, outliers, and patterns in milliseconds. For analytics platforms, visualization is the difference between data that sits in a database and data that drives decisions.

Real-World Use

Doda Browser uses Grafana dashboards to monitor page load performance across 40+ geographic regions. Each chart combines real-time Prometheus metrics with PostgreSQL analytics data, giving the team instant visibility into regional performance degradations.

Visualization Pipeline

flowchart LR
    A[(Data Source)] --> B[Data Processing]
    B --> C{Visualization Tool}
    C -->|Real-time| D[Grafana]
    C -->|Custom UI| E[D3.js]
    C -->|Rapid Charts| F[Chart.js]
    D --> G[Alerting]
    E --> H[Interactive Dashboard]
    F --> I[Embedded Reports]

Building a Bar Chart with Chart.js

Chart.js provides the fastest path from data to a rendered chart:

const ctx = document.getElementById("analyticsChart").getContext("2d");
const chart = new Chart(ctx, {
  type: "bar",
  data: {
    labels: ["Page Views", "Sessions", "Users", "Bounce Rate"],
    datasets: [{
      label: "Last 7 Days",
      data: [45230, 38910, 28450, 34.2],
      backgroundColor: "rgba(59, 130, 246, 0.7)",
      borderColor: "rgb(59, 130, 246)",
      borderWidth: 1,
    }, {
      label: "Previous 7 Days",
      data: [42100, 36200, 27100, 36.8],
      backgroundColor: "rgba(239, 68, 68, 0.7)",
      borderColor: "rgb(239, 68, 68)",
      borderWidth: 1,
    }],
  },
  options: {
    responsive: true,
    plugins: {
      title: { display: true, text: "Weekly Analytics Comparison" },
    },
    scales: {
      y: { beginAtZero: true },
    },
  },
});

Expected behavior: A responsive bar chart renders with two datasets side by side. The chart updates when the data array changes, and the title displays above the chart.

Custom Visualization with D3.js

D3.js gives full control over every visual element:

const data = [
  { date: "2026-06-16", visitors: 1200 },
  { date: "2026-06-17", visitors: 1450 },
  { date: "2026-06-18", visitors: 1320 },
  { date: "2026-06-19", visitors: 1810 },
  { date: "2026-06-20", visitors: 1620 },
  { date: "2026-06-21", visitors: 1580 },
  { date: "2026-06-22", visitors: 1740 },
];

const width = 600, height = 300;
const margin = { top: 20, right: 20, bottom: 30, left: 50 };

const x = d3.scaleBand()
  .domain(data.map(d => d.date))
  .range([margin.left, width - margin.right])
  .padding(0.2);

const y = d3.scaleLinear()
  .domain([0, d3.max(data, d => d.visitors)])
  .range([height - margin.bottom, margin.top]);

const svg = d3.select("#chart")
  .append("svg")
  .attr("width", width)
  .attr("height", height);

svg.selectAll("rect")
  .data(data)
  .enter().append("rect")
  .attr("x", d => x(d.date))
  .attr("y", d => y(d.visitors))
  .attr("width", x.bandwidth())
  .attr("height", d => y(0) - y(d.visitors))
  .attr("fill", "steelblue");

Expected behavior: An SVG bar chart renders inside the #chart div. Each bar represents one day of Visitor data with widths and heights calculated from the scale functions.

Grafana Dashboard with Prometheus Data Source

Configure a Grafana dashboard panel querying Prometheus:

# Grafana panel query: requests per second by status code
sum by (status_code) (rate(http_requests_total[5m]))

Expected behavior: Grafana renders a stacked time-series graph showing request rates grouped by status code (2xx, 3xx, 4xx, 5xx). The query refreshes every 30 seconds by default.

Tool Comparison

Feature Chart.js D3.js Grafana Apache ECharts
Chart types 8 built-in Unlimited 15+ built-in 20+ built-in
Learning curve Low High Medium Medium
Interactivity Medium Full Dashboard-level High
Real-time data Manual refresh Manual refresh Built-in Manual refresh
Data source Client-side Client-side DB, Prom, Loki Client-side
Bundle size 70KB 280KB Server-side 100KB+

Common Errors

1. Chart Not Rendering Because Canvas Context is Null

The canvas element must exist in the DOM before Chart.js tries to access its context. Place chart initialization inside a DOMContentLoaded event listener.

2. D3.js Data Binding Without Key Functions

Without a key function in .data(data, d => d.id), D3 matches by index, which causes incorrect enter-update-exit behavior when data order changes.

3. Grafana Query Returning No Data

Check the data source time range matches the panel time range. A query for the last 5 minutes returns nothing if the panel shows the last 24 hours.

4. Missing Null Checks in D3 Scales

If your data contains null or undefined values, D3 scale domains break. Filter or default missing values before passing data to scales.

5. Responsive Charts Without Container Resize Handlers

Chart.js offers responsive: true but if the container has a fixed height in CSS, the chart may overflow. Set the container height dynamically or use aspect ratio.

Practice Questions

1. What are the main differences between Chart.js and D3.js? Chart.js provides pre-built chart types with minimal configuration. D3.js gives full control over SVG rendering but requires more code and a steeper learning curve.

2. How does Grafana query Prometheus data? Grafana uses PromQL queries in dashboard panels. Each panel defines a query, time range, and visualization type. Grafana sends the query to the Prometheus data source and renders the result.

3. Why use a key function in D3.js data binding? A key function ensures each data element is consistently matched to the same DOM element across updates, preserving transitions and preventing incorrect enter-exit behavior.

4. What is the benefit of stacked vs grouped bar charts? Stacked bars show part-to-whole relationships (total = sum of parts). Grouped bars emphasize comparison between categories. Choose based on whether totals or individual values matter more.

5. Challenge: Build a real-time Grafana dashboard with three panels: a time-series graph of requests per second, a bar chart of top 5 endpoints, and a single stat showing current error rate percentage. Configure alerts if error rate exceeds 5%.

Mini Project

Create an analytics dashboard for a web application that combines Chart.js for weekly trend charts, D3.js for a custom heatmap showing hourly traffic distribution across days of the week, and embed the dashboard into a Grafana iframe for operations team monitoring. Include export-to-PNG functionality on each chart.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro