Build a Ticket Sales Dashboard

Build an interactive event ticket sales dashboard with Chart.js.

Introduction

30 Second Summary

Every event organizer needs to know how tickets are selling. Whether it's a local meetup or a massive conference, turning raw sales numbers into clear visuals is how you spot trends and make decisions fast.

In this project, you will build a ticket sales dashboard for a fictional tech conference. You'll use plain HTML, CSS, and JavaScript alongside a charting library called Chart.js to turn raw data into interactive charts and summary cards.

What You'll Build

A single-page dashboard for "TechConf 2026" that displays summary metric cards and three interactive charts, all styled with a clean CSS Grid layout.

By the end of this project, you'll have:

  • A polished ticket sales dashboard displaying four summary metric cards and three interactive charts.
  • Working JavaScript code that aggregates raw sales data into meaningful metrics and visualizations.
  • A foundational understanding of Chart.js that transfers to any future data visualization project.
  • Secret Mission: add a dropdown filter that lets users view sales data for a specific ticket type.

Are there any prerequisites?

None. This guide walks you through installing everything you need, including VS Code and setting up your project files from scratch.

Before We Start

Before We Start...

Set Up Your Tools and Project

Before you start building charts and crunching data, you need a code editor and a clean project structure.

In this step, you will install VS Code, create a project folder with three starter files, and add the HTML boilerplate that connects your CSS, JavaScript, and Chart.js together.

In this step, get ready to:
  • Install VS Code as your code editor.
  • Create the project folder and starter files.
  • Set up the HTML boilerplate with Chart.js.
Install VS Code

VS Code is a free code editor you will use to write all of the HTML, CSS, and JavaScript for this project.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Visual Studio Code and press Enter.

✔️ VS Code is open

VS Code is already installed and ready to go. Skip ahead to the next substep.

ⓧ I need to install VS Code

  • Open https://code.visualstudio.com/ in your browser.
  • Click the download button for Mac.
  • Open the downloaded .zip file.
  • Drag the Visual Studio Code app into your Applications folder.
  • Press Cmd+Space to open Spotlight, type Visual Studio Code, and press Enter.

Seeing a Welcome tab?

When VS Code opens for the first time, it shows a Welcome tab with tips and shortcuts. You can close it by clicking the X on the tab.

macOS blocking the app?

If macOS says the app is from an unidentified developer, open System Settings, go to Privacy & Security, and click Open Anyway.

help me open VS Code on macOS when it's blocked by Gatekeeper

Create your project folder and files

Your dashboard project needs three files: index.html for the page structure, style.css for the styling, and script.js for the data and charts.

  • Open the built-in terminal in VS Code by clicking Terminal in the top menu bar, then clicking New Terminal.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir ticket-dashboard
cd ticket-dashboard
  • Create the three project files by running this command:
touch index.html style.css script.js
  • Confirm the files exist by running:
ls

You should see index.html, style.css, and script.js in the output.

Now you need to open this folder in VS Code so the files appear in the sidebar.

  • In VS Code, click File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop, select the ticket-dashboard folder, and click Open.

You should see all three files listed in the Explorer sidebar on the left.

Don't see the files in the sidebar?

Make sure you selected the ticket-dashboard folder itself, not a file inside it. The folder name should appear at the top of the Explorer sidebar.

If the sidebar is collapsed, click the top icon in the left icon bar (it looks like two overlapping files) to expand it.

help me see my project files in the VS Code sidebar

Set up the HTML boilerplate

Every web page starts with HTML boilerplate. This is the skeleton that tells the browser what files to load and how to render the page.

  • Click on index.html in the Explorer sidebar to open it in the editor.
  • Copy the following code and paste it into index.html:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TechConf 2026 - Ticket Sales Dashboard</title>
    <link rel="stylesheet" href="style.css">
    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.1"></script>
</head>
<body>
    <script src="script.js"></script>
</body>
</html>

What does this code do?

This is the foundation for your entire dashboard. Every piece connects a different part of the project.

  • <link rel="stylesheet" href="style.css"> connects your CSS file so the browser knows where to find the page styling.
  • The first <script> tag loads Chart.js version 4.5.1 from a CDN (a server that hosts popular libraries for free).
  • The second <script> tag at the bottom of <body> connects your JavaScript file. It goes last so the page structure loads before any scripts run.
  • Save index.html by pressing Cmd+S (macOS) or Ctrl+S (Windows).
  • Right-click index.html in the Explorer sidebar and select Reveal in Finder.
  • Double-click index.html in the Finder window to open it in your browser.

You should see a blank white page in your browser. Check the browser tab title. It should say TechConf 2026 - Ticket Sales Dashboard.

Seeing raw code instead of a blank page?

Make sure you saved the file with the .html extension. If the file is named something like index.html.txt, the browser will display the raw text instead of rendering it.

Also confirm you opened the file by double-clicking it in Finder, not by dragging it into the browser window.

help me open my HTML file in the browser correctly

✔️ Awesome, I've got everything!

Make sure you have saved index.html before moving on.

ⓧ I'd like to double check the full code

Here is the complete index.html at the end of this step.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TechConf 2026 - Ticket Sales Dashboard</title>
    <link rel="stylesheet" href="style.css">
    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.1"></script>
</head>
<body>
    <script src="script.js"></script>
</body>
</html>

Your project structure is ready and your HTML boilerplate is loading in the browser. Next up, you will build the dashboard layout with a header, summary card containers, and chart placeholders.

Build the Dashboard Layout

Your project files are created and your browser is ready. Now it's time to build the actual structure of your dashboard.

Every dashboard starts with two layers: the HTML that defines what goes on the page, and the CSS that controls how it looks. In this step, you'll create the skeleton of your dashboard and style it into a professional layout using CSS Grid.

In this step, get ready to:
  • Add the HTML structure for the header, summary cards section, and chart containers.
  • Style the dashboard with CSS Grid for a responsive, professional layout.
  • Preview the empty dashboard in your browser to confirm everything is in place.
Create the HTML structure

Your dashboard needs three main areas: a header displaying the event name, a section where JavaScript will inject summary cards later, and a section holding three <canvas> elements for your charts.

What is a canvas element?

Chart.js draws charts onto HTML <canvas> elements. Think of each canvas as a blank drawing surface. You give it an id so your JavaScript can find it and tell Chart.js where to render each chart.

  • In index.html, find the opening <body> tag.
  • Add the following code between the <body> tag and the <script src="script.js"></script> line:
    <header>
        <h1>🎟️ TechConf 2026 - Ticket Sales Dashboard</h1>
    </header>
    <main>
        <section class="summary-cards" id="summaryCards">
        </section>
        <section class="charts">
            <div class="chart-container">
                <canvas id="salesByTypeChart"></canvas>
            </div>
            <div class="chart-container">
                <canvas id="dailySalesChart"></canvas>
            </div>
            <div class="chart-container">
                <canvas id="revenueBreakdownChart"></canvas>
            </div>
        </section>
    </main>

What does this code do?

  • <header> displays the event name at the top of the page.
  • <section class="summary-cards" id="summaryCards"> is an empty container. JavaScript will fill it with metric cards in a later step.
  • <section class="charts"> holds three chart-container divs, each with a <canvas> element for the bar chart, line chart, and doughnut chart.
  • Each canvas has a unique id (salesByTypeChart, dailySalesChart, revenueBreakdownChart) so JavaScript can target each one individually.
  • Save index.html.
  • Refresh your browser. You should see the raw text "TechConf 2026 - Ticket Sales Dashboard" at the top of the page, unstyled.

Don't see any text on the page?

  • Make sure you saved index.html. Unsaved changes won't show in the browser.
  • Check that the new code is between the <body> tag and the <script> tag, not inside the <head>.
  • Confirm you're refreshing the correct browser tab. The tab title should say "TechConf 2026".

Why isn't my HTML showing up in the browser?

✔️ Awesome, I've got everything!

Great. Double check you've saved index.html before moving on.

ⓧ I'd like to double check the full code

Here is the complete index.html file so far. Make sure yours matches exactly.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TechConf 2026 - Ticket Sales Dashboard</title>
    <link rel="stylesheet" href="style.css">
    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.1"></script>
</head>
<body>
    <header>
        <h1>🎟️ TechConf 2026 - Ticket Sales Dashboard</h1>
    </header>
    <main>
        <section class="summary-cards" id="summaryCards">
        </section>
        <section class="charts">
            <div class="chart-container">
                <canvas id="salesByTypeChart"></canvas>
            </div>
            <div class="chart-container">
                <canvas id="dailySalesChart"></canvas>
            </div>
            <div class="chart-container">
                <canvas id="revenueBreakdownChart"></canvas>
            </div>
        </section>
    </main>
    <script src="script.js"></script>
</body>
</html>
Style the page foundation

Right now, the page is plain white with unstyled text. Your CSS will transform it into a polished, dark-themed header on a light gray background.

  • Open style.css and add these base styles for the page:
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    background-color: #f0f2f5;
    color: #333;
}

header {
    background-color: #1a1a2e;
    color: #fff;
    padding: 20px 30px;
    text-align: center;
}

header h1 {
    font-size: 1.5rem;
}

main {
    max-width: 1200px;
    margin: 20px auto;
    padding: 0 20px;
}

What does this code do?

  • The * selector is a CSS reset. It removes default browser margins and padding so every element starts from a clean slate.
  • box-sizing: border-box makes padding count as part of an element's total width, preventing layout surprises.
  • The header gets a dark navy background (#1a1a2e) with white text, and the page background is set to a light gray (#f0f2f5).
  • The main container is capped at 1200px wide and centered with margin: 20px auto so the dashboard content doesn't stretch across ultra-wide screens.
  • Save style.css and refresh your browser.
  • You should see a dark navy header with white text at the top, and a light gray background behind the rest of the page.

Still seeing a plain white page?

  • Confirm that your index.html has <link rel="stylesheet" href="style.css"> inside the <head> section.
  • Check that style.css is in the same folder as index.html. If it's inside a subfolder, the link won't find it.

Why isn't my CSS styling showing up?

Add the dashboard grid layout styles

The header looks good, but the card and chart areas below it are invisible. CSS Grid will arrange the summary cards and chart containers into a responsive grid that automatically wraps on smaller screens.

  • In style.css, add these grid layout styles below the code you just wrote:
.summary-cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 20px;
    margin-bottom: 30px;
}

.card {
    background-color: #fff;
    border-radius: 10px;
    padding: 20px;
    text-align: center;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.card h3 {
    font-size: 0.9rem;
    color: #666;
    margin-bottom: 8px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.card p {
    font-size: 1.8rem;
    font-weight: bold;
    color: #1a1a2e;
}

.charts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
    gap: 20px;
}

.chart-container {
    background-color: #fff;
    border-radius: 10px;
    padding: 20px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

What does this code do?

  • repeat(auto-fit, minmax(200px, 1fr)) is the key CSS Grid pattern here. It creates as many columns as will fit, where each column is at least 200px wide and stretches to fill available space.
  • The .card styles give each summary metric a white background, rounded corners, and a subtle shadow. These cards won't appear until JavaScript creates them in the next step.
  • The .chart-container uses the same visual treatment (white, rounded, shadowed) so every dashboard panel looks consistent.

Why CSS Grid instead of Flexbox?

CSS Grid is built for two-dimensional layouts where you want items to snap into rows and columns automatically. The auto-fit keyword handles responsiveness without a single media query. Flexbox works great for single rows or columns, but Grid is the natural fit for dashboard-style layouts.

  • Save style.css and refresh your browser.
  • You should see three white, rounded containers below the header. These are your chart containers, ready for Chart.js to draw into.

Don't see the white containers?

  • Make sure you saved both index.html and style.css. Unsaved files show a dot or circle on the file tab in VS Code.
  • Check that the class names in your CSS match the class names in your HTML exactly. For example, .chart-container in your CSS must match class="chart-container" in your HTML.
  • The chart containers might appear very thin because the canvases inside them are empty. Chart.js will size them properly once it renders charts in a later step.

Why aren't my CSS Grid containers appearing?

✔️ Awesome, I've got everything!

Great. Double check you've saved style.css before moving on.

ⓧ I'd like to double check the full code

Here is the complete style.css file. Make sure yours matches exactly.

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    background-color: #f0f2f5;
    color: #333;
}

header {
    background-color: #1a1a2e;
    color: #fff;
    padding: 20px 30px;
    text-align: center;
}

header h1 {
    font-size: 1.5rem;
}

main {
    max-width: 1200px;
    margin: 20px auto;
    padding: 0 20px;
}

.summary-cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 20px;
    margin-bottom: 30px;
}

.card {
    background-color: #fff;
    border-radius: 10px;
    padding: 20px;
    text-align: center;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.card h3 {
    font-size: 0.9rem;
    color: #666;
    margin-bottom: 8px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.card p {
    font-size: 1.8rem;
    font-weight: bold;
    color: #1a1a2e;
}

.charts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
    gap: 20px;
}

.chart-container {
    background-color: #fff;
    border-radius: 10px;
    padding: 20px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
Preview the empty dashboard

Take a moment to look at what you've built so far. Your browser should show a dark navy header with the event name, and white rounded containers below it on a light gray background.

The summary cards section is empty because JavaScript hasn't created any cards yet. The chart containers appear as white boxes, ready for Chart.js to fill in later steps.

Why build the layout first?

Building the HTML structure and CSS before any JavaScript lets you confirm the page looks right before adding complexity. If something breaks later, you'll know the layout itself is solid.

Your dashboard skeleton is styled and ready. Next up, you'll bring it to life with real ticket sales data and summary metric cards.

Add Ticket Sales Data and Summary Cards

Your dashboard layout is styled and ready, with a header and empty white containers waiting for content. Now it's time to fill those containers with real data.

Every dashboard starts with a dataset. In this step, you'll create sample ticket sales records in JavaScript, crunch them into meaningful numbers, and display those numbers as summary cards at the top of the page.

In this step, get ready to:
  • Create an array of ticket sales data for TechConf 2026.
  • Calculate summary metrics like total tickets sold and total revenue.
  • Render four summary cards that display the metrics on the dashboard.
Create the sample sales data

Before you can calculate metrics or draw charts, you need a dataset to work with. You'll create an array of objects where each object represents one day of ticket sales.

  • Open script.js in VS Code and add the ticket sales data by pasting this code:
const ticketSales = [
    { date: '2026-04-01', type: 'Early Bird', quantity: 45, price: 29 },
    { date: '2026-04-02', type: 'Early Bird', quantity: 38, price: 29 },
    { date: '2026-04-03', type: 'Early Bird', quantity: 22, price: 29 },
    { date: '2026-04-04', type: 'General', quantity: 30, price: 49 },
    { date: '2026-04-05', type: 'General', quantity: 55, price: 49 },
    { date: '2026-04-06', type: 'General', quantity: 40, price: 49 },
    { date: '2026-04-07', type: 'VIP', quantity: 12, price: 99 },
    { date: '2026-04-08', type: 'General', quantity: 48, price: 49 },
    { date: '2026-04-09', type: 'VIP', quantity: 18, price: 99 },
    { date: '2026-04-10', type: 'General', quantity: 60, price: 49 },
    { date: '2026-04-11', type: 'VIP', quantity: 15, price: 99 },
    { date: '2026-04-12', type: 'Early Bird', quantity: 10, price: 29 },
];

What does this data look like?

The ticketSales array contains 12 objects, each representing a single day of sales. Every object has four properties:

  • date tracks which day the sales happened.
  • type identifies the ticket tier: Early Bird ($29), General ($49), or VIP ($99).
  • quantity is how many tickets were sold that day.
  • price is the cost per ticket for that tier.
  • Save script.js.

You won't see any changes in the browser yet. The next section uses this data to calculate summary metrics.

Seeing a console error?

If you open your browser's developer console (right-click the page, select Inspect, then click the Console tab) and see a syntax error, check that every object in the array has a comma after it. Also make sure the array ends with ]; and not just ].

Help me fix a syntax error in my ticketSales array

Calculate summary metrics

Raw data on its own isn't very useful for a dashboard. You need to crunch it down into high-level numbers that tell a story at a glance.

  • In script.js, add the following code below the ticketSales array to calculate your summary metrics:
// Calculate summary metrics
const totalTickets = ticketSales.reduce((sum, sale) => sum + sale.quantity, 0);
const totalRevenue = ticketSales.reduce((sum, sale) => sum + (sale.quantity * sale.price), 0);
const eventCapacity = 500;
const ticketsRemaining = eventCapacity - totalTickets;

What does this code do?

  • reduce() loops through every item in the array and accumulates a single result. The first reduce() call adds up all the quantity values to get the total tickets sold.
  • The second reduce() multiplies each entry's quantity * price and sums the results to get total revenue.
  • eventCapacity is a fixed number representing how many tickets the event can sell. ticketsRemaining subtracts the sold tickets from that cap.
  • Save script.js.

You won't see a change in the browser yet. The next section renders these metrics as cards on the dashboard.

Not sure if the math is working?

You can test the calculations by temporarily adding console.log(totalTickets, totalRevenue) at the bottom of your file, saving, and refreshing the browser. Open the console (Inspect tab) and you should see 393 and 18027. Remove the console.log line before continuing.

Help me debug my reduce() calculations

Render the summary cards

Your metrics exist in JavaScript, but the browser can't see them yet. You need to create HTML elements dynamically and inject them into the page using DOM manipulation.

  • In script.js, add the following code below the ticketsRemaining line to render the summary cards:
// Render summary cards
const summaryCards = document.getElementById('summaryCards');
const metrics = [
    { label: 'Total Tickets Sold', value: totalTickets.toLocaleString() },
    { label: 'Total Revenue', value: '$' + totalRevenue.toLocaleString() },
    { label: 'Tickets Remaining', value: ticketsRemaining.toLocaleString() },
    { label: 'Event Capacity', value: eventCapacity.toLocaleString() },
];

metrics.forEach(metric => {
    const card = document.createElement('div');
    card.className = 'card';
    card.innerHTML = `<h3>${metric.label}</h3><p>${metric.value}</p>`;
    summaryCards.appendChild(card);
});

What does this code do?

  • document.getElementById('summaryCards') grabs the empty <section> you created in your HTML. This is the container the cards will be injected into.
  • The metrics array holds four objects, each with a label (the card title) and a value (the formatted number). toLocaleString() adds commas to large numbers for readability.
  • forEach() loops through each metric, creates a <div> with the class card, fills it with an <h3> and <p> tag, and appends it to the page.
  • Save script.js and refresh your browser.

You should see four white summary cards appear below the header. Each card displays an uppercase label and a large, bold number.

Don't see the summary cards?

  • Make sure you saved script.js. Unsaved changes won't appear in the browser.
  • Check that the ID in document.getElementById('summaryCards') matches id="summaryCards" in your HTML exactly. It's case-sensitive.
  • Open the browser console (right-click the page, select Inspect, click Console) and look for red error messages. A typo in the JavaScript will stop the entire script from running.

Help me figure out why my summary cards aren't showing up

✔️ Awesome, I've got everything!

Great. Double check you've saved script.js before moving on.

ⓧ I'd like to double check the full code

Here is the complete script.js file so far. Compare it with yours line by line.

const ticketSales = [
    { date: '2026-04-01', type: 'Early Bird', quantity: 45, price: 29 },
    { date: '2026-04-02', type: 'Early Bird', quantity: 38, price: 29 },
    { date: '2026-04-03', type: 'Early Bird', quantity: 22, price: 29 },
    { date: '2026-04-04', type: 'General', quantity: 30, price: 49 },
    { date: '2026-04-05', type: 'General', quantity: 55, price: 49 },
    { date: '2026-04-06', type: 'General', quantity: 40, price: 49 },
    { date: '2026-04-07', type: 'VIP', quantity: 12, price: 99 },
    { date: '2026-04-08', type: 'General', quantity: 48, price: 49 },
    { date: '2026-04-09', type: 'VIP', quantity: 18, price: 99 },
    { date: '2026-04-10', type: 'General', quantity: 60, price: 49 },
    { date: '2026-04-11', type: 'VIP', quantity: 15, price: 99 },
    { date: '2026-04-12', type: 'Early Bird', quantity: 10, price: 29 },
];

// Calculate summary metrics
const totalTickets = ticketSales.reduce((sum, sale) => sum + sale.quantity, 0);
const totalRevenue = ticketSales.reduce((sum, sale) => sum + (sale.quantity * sale.price), 0);
const eventCapacity = 500;
const ticketsRemaining = eventCapacity - totalTickets;

// Render summary cards
const summaryCards = document.getElementById('summaryCards');
const metrics = [
    { label: 'Total Tickets Sold', value: totalTickets.toLocaleString() },
    { label: 'Total Revenue', value: '$' + totalRevenue.toLocaleString() },
    { label: 'Tickets Remaining', value: ticketsRemaining.toLocaleString() },
    { label: 'Event Capacity', value: eventCapacity.toLocaleString() },
];

metrics.forEach(metric => {
    const card = document.createElement('div');
    card.className = 'card';
    card.innerHTML = `<h3>${metric.label}</h3><p>${metric.value}</p>`;
    summaryCards.appendChild(card);
});

Your dashboard now has real data powering it. Next up, you'll bring the charts section to life with three interactive Chart.js visualizations.

Visualize Sales with Charts

Your summary cards are now showing real ticket sales metrics. The next goal is to fill those empty white chart containers with interactive visualizations.

Each chart needs two things: aggregated data prepared from your ticketSales array, and a Chart.js configuration object that tells it how to render. You'll build three charts that each tell a different story about the sales data.

In this step, get ready to:
  • Create a bar chart comparing tickets sold across ticket types.
  • Create a line chart showing the daily sales trend over time.
  • Create a doughnut chart breaking down revenue by ticket type.
Create the bar chart

Before you can chart anything, you need to transform the raw sales entries into totals grouped by ticket type. This aggregation step turns 12 individual rows into three clean numbers that Chart.js can plot.

  • In script.js, scroll to the bottom of the file (below the metrics.forEach block).
  • Aggregate the ticket sales data by type by copying the following code at the bottom of the file:
// Aggregate sales by ticket type
const salesByType = {};
ticketSales.forEach(sale => {
    if (!salesByType[sale.type]) {
        salesByType[sale.type] = { quantity: 0, revenue: 0 };
    }
    salesByType[sale.type].quantity += sale.quantity;
    salesByType[sale.type].revenue += sale.quantity * sale.price;
});

const typeLabels = Object.keys(salesByType);
const typeQuantities = typeLabels.map(type => salesByType[type].quantity);
const typeRevenues = typeLabels.map(type => salesByType[type].revenue);

What does this code do?

  • salesByType starts as an empty object. The forEach loop groups every sale by its type property, accumulating the total quantity and revenue for each ticket type.
  • typeLabels extracts the ticket type names (Early Bird, General, VIP). typeQuantities and typeRevenues pull out the matching numbers into arrays that Chart.js can use directly.
  • Save script.js.
  • You won't see a change in the browser yet. The next code block uses this aggregated data to create the bar chart.

Now that the data is prepared, you can pass it to Chart.js. Every chart follows the same pattern: call new Chart() with a canvas element and a configuration object.

  • Create the bar chart by copying this code below the aggregation block you just added:
// Bar chart: Tickets sold by type
new Chart(document.getElementById('salesByTypeChart'), {
    type: 'bar',
    data: {
        labels: typeLabels,
        datasets: [{
            label: 'Tickets Sold',
            data: typeQuantities,
            backgroundColor: ['#e74c3c', '#3498db', '#f39c12'],
            borderRadius: 6,
        }]
    },
    options: {
        responsive: true,
        plugins: {
            title: {
                display: true,
                text: 'Tickets Sold by Type',
                font: { size: 16 }
            }
        },
        scales: {
            y: { beginAtZero: true }
        }
    }
});

What does this code do?

  • new Chart() takes two arguments: the canvas element to draw on, and a configuration object describing the chart.
  • type: 'bar' tells Chart.js to render a bar chart.
  • The data object has labels (the x-axis categories) and datasets (the values to plot). backgroundColor assigns a unique color to each bar.
  • borderRadius: 6 rounds the top corners of each bar for a more polished look.
  • Save script.js and refresh your browser.
  • You should see a bar chart in the first white container with three colored bars for Early Bird (red), General (blue), and VIP (orange). Hover over a bar to see a tooltip with the exact ticket count.

Don't see the bar chart?

  • Make sure you saved script.js before refreshing.
  • Open your browser's developer console (press Cmd+Option+J on macOS or Ctrl+Shift+J on Windows) and look for red error messages. A common cause is a missing closing brace or bracket in the chart configuration.
  • Check that the canvas ID salesByTypeChart in your JavaScript matches the id attribute in your HTML exactly.

Why isn't my Chart.js bar chart rendering?

Why is every chart built the same way?

Every Chart.js chart uses the same constructor: new Chart(canvas, { type, data, options }). The only things that change between charts are the type string, the shape of the data, and the styling options.

Once you understand this pattern from the bar chart, the line and doughnut charts will feel familiar.

Create the line chart

The bar chart shows totals per ticket type. A line chart can reveal a different story: how sales volume changed day by day.

Just like before, you need to aggregate the raw data first. This time you'll group by date instead of ticket type.

  • In script.js, add this aggregation code below the bar chart block:
// Aggregate daily sales
const dailySales = {};
ticketSales.forEach(sale => {
    if (!dailySales[sale.date]) {
        dailySales[sale.date] = 0;
    }
    dailySales[sale.date] += sale.quantity;
});

const dateLabels = Object.keys(dailySales).sort();
const dailyQuantities = dateLabels.map(date => dailySales[date]);

What does this code do?

  • dailySales groups ticket quantities by date. If a date already exists as a key, the quantity gets added to its running total.
  • .sort() on the date strings puts them in chronological order so the line chart reads left to right.
  • Save script.js.
  • You won't see a change in the browser yet. The next code block uses this daily data to create the line chart.
  • Create the line chart by copying this code below the daily aggregation block:
// Line chart: Daily sales trend
new Chart(document.getElementById('dailySalesChart'), {
    type: 'line',
    data: {
        labels: dateLabels.map(d => {
            const date = new Date(d + 'T00:00:00');
            return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
        }),
        datasets: [{
            label: 'Tickets Sold',
            data: dailyQuantities,
            borderColor: '#3498db',
            backgroundColor: 'rgba(52, 152, 219, 0.1)',
            fill: true,
            tension: 0.3,
            pointRadius: 5,
            pointBackgroundColor: '#3498db',
        }]
    },
    options: {
        responsive: true,
        plugins: {
            title: {
                display: true,
                text: 'Daily Sales Trend',
                font: { size: 16 }
            }
        },
        scales: {
            y: { beginAtZero: true }
        }
    }
});

What does this code do?

  • The labels array converts raw date strings like 2026-04-01 into readable labels like "Apr 1". Adding T00:00:00 prevents timezone offset issues that could shift dates by a day.
  • fill: true shades the area beneath the line. tension: 0.3 smooths the line into a gentle curve instead of sharp angles.
  • pointRadius: 5 makes each data point visible as a dot, so you can hover over individual days to see the exact count.
  • Save script.js and refresh your browser.
  • You should see a smooth blue line chart in the second container. The shaded area beneath the line shows the daily trend from April 1 through April 12.

Line chart not showing up?

  • Check the browser console for errors. A missing comma after a property (like after pointBackgroundColor: '#3498db') is a common typo.
  • Confirm the canvas ID dailySalesChart in your JavaScript matches the id in your HTML.

Why isn't my Chart.js line chart appearing?

Create the doughnut chart

The final chart shows how revenue is distributed across ticket types. A doughnut chart is ideal for this because it highlights proportions at a glance.

You already have the data you need. The typeLabels and typeRevenues arrays from the bar chart aggregation work here too.

  • Create the doughnut chart by copying this code at the bottom of script.js:
// Doughnut chart: Revenue breakdown by type
new Chart(document.getElementById('revenueBreakdownChart'), {
    type: 'doughnut',
    data: {
        labels: typeLabels,
        datasets: [{
            data: typeRevenues,
            backgroundColor: ['#e74c3c', '#3498db', '#f39c12'],
            hoverOffset: 10,
        }]
    },
    options: {
        responsive: true,
        plugins: {
            title: {
                display: true,
                text: 'Revenue Breakdown by Ticket Type',
                font: { size: 16 }
            }
        }
    }
});

What does this code do?

  • type: 'doughnut' renders a ring-shaped chart where each segment's size represents its share of the total revenue.
  • hoverOffset: 10 makes a segment expand slightly when you hover over it, giving a nice interactive feel.
  • Notice this chart has no scales option. Doughnut charts don't use axes since they represent proportions, not plotted values.
  • Save script.js and refresh your browser.
  • You should see a colourful doughnut chart in the third container with three segments for Early Bird, General, and VIP. Hover over each segment to see the exact revenue amount in a tooltip.

Doughnut chart missing or overlapping?

  • Make sure you're targeting the canvas with ID revenueBreakdownChart. A typo here means Chart.js can't find the canvas.
  • If the chart appears squashed, check that the closing }); for the line chart above is correct. A missing bracket causes the doughnut code to be misinterpreted.

Why is my Chart.js doughnut chart not rendering correctly?

Time to double check your code. Make sure everything in script.js matches the complete file below.

✔️ Awesome, I've got everything!

Great. Double check you've saved script.js before moving on.

ⓧ I'd like to double check the full code

const ticketSales = [
    { date: '2026-04-01', type: 'Early Bird', quantity: 45, price: 29 },
    { date: '2026-04-02', type: 'Early Bird', quantity: 38, price: 29 },
    { date: '2026-04-03', type: 'Early Bird', quantity: 22, price: 29 },
    { date: '2026-04-04', type: 'General', quantity: 30, price: 49 },
    { date: '2026-04-05', type: 'General', quantity: 55, price: 49 },
    { date: '2026-04-06', type: 'General', quantity: 40, price: 49 },
    { date: '2026-04-07', type: 'VIP', quantity: 12, price: 99 },
    { date: '2026-04-08', type: 'General', quantity: 48, price: 49 },
    { date: '2026-04-09', type: 'VIP', quantity: 18, price: 99 },
    { date: '2026-04-10', type: 'General', quantity: 60, price: 49 },
    { date: '2026-04-11', type: 'VIP', quantity: 15, price: 99 },
    { date: '2026-04-12', type: 'Early Bird', quantity: 10, price: 29 },
];

// Calculate summary metrics
const totalTickets = ticketSales.reduce((sum, sale) => sum + sale.quantity, 0);
const totalRevenue = ticketSales.reduce((sum, sale) => sum + (sale.quantity * sale.price), 0);
const eventCapacity = 500;
const ticketsRemaining = eventCapacity - totalTickets;

// Render summary cards
const summaryCards = document.getElementById('summaryCards');
const metrics = [
    { label: 'Total Tickets Sold', value: totalTickets.toLocaleString() },
    { label: 'Total Revenue', value: '$' + totalRevenue.toLocaleString() },
    { label: 'Tickets Remaining', value: ticketsRemaining.toLocaleString() },
    { label: 'Event Capacity', value: eventCapacity.toLocaleString() },
];

metrics.forEach(metric => {
    const card = document.createElement('div');
    card.className = 'card';
    card.innerHTML = `<h3>${metric.label}</h3><p>${metric.value}</p>`;
    summaryCards.appendChild(card);
});

// Aggregate sales by ticket type
const salesByType = {};
ticketSales.forEach(sale => {
    if (!salesByType[sale.type]) {
        salesByType[sale.type] = { quantity: 0, revenue: 0 };
    }
    salesByType[sale.type].quantity += sale.quantity;
    salesByType[sale.type].revenue += sale.quantity * sale.price;
});

const typeLabels = Object.keys(salesByType);
const typeQuantities = typeLabels.map(type => salesByType[type].quantity);
const typeRevenues = typeLabels.map(type => salesByType[type].revenue);

// Bar chart: Tickets sold by type
new Chart(document.getElementById('salesByTypeChart'), {
    type: 'bar',
    data: {
        labels: typeLabels,
        datasets: [{
            label: 'Tickets Sold',
            data: typeQuantities,
            backgroundColor: ['#e74c3c', '#3498db', '#f39c12'],
            borderRadius: 6,
        }]
    },
    options: {
        responsive: true,
        plugins: {
            title: {
                display: true,
                text: 'Tickets Sold by Type',
                font: { size: 16 }
            }
        },
        scales: {
            y: { beginAtZero: true }
        }
    }
});

// Aggregate daily sales
const dailySales = {};
ticketSales.forEach(sale => {
    if (!dailySales[sale.date]) {
        dailySales[sale.date] = 0;
    }
    dailySales[sale.date] += sale.quantity;
});

const dateLabels = Object.keys(dailySales).sort();
const dailyQuantities = dateLabels.map(date => dailySales[date]);

// Line chart: Daily sales trend
new Chart(document.getElementById('dailySalesChart'), {
    type: 'line',
    data: {
        labels: dateLabels.map(d => {
            const date = new Date(d + 'T00:00:00');
            return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
        }),
        datasets: [{
            label: 'Tickets Sold',
            data: dailyQuantities,
            borderColor: '#3498db',
            backgroundColor: 'rgba(52, 152, 219, 0.1)',
            fill: true,
            tension: 0.3,
            pointRadius: 5,
            pointBackgroundColor: '#3498db',
        }]
    },
    options: {
        responsive: true,
        plugins: {
            title: {
                display: true,
                text: 'Daily Sales Trend',
                font: { size: 16 }
            }
        },
        scales: {
            y: { beginAtZero: true }
        }
    }
});

// Doughnut chart: Revenue breakdown by type
new Chart(document.getElementById('revenueBreakdownChart'), {
    type: 'doughnut',
    data: {
        labels: typeLabels,
        datasets: [{
            data: typeRevenues,
            backgroundColor: ['#e74c3c', '#3498db', '#f39c12'],
            hoverOffset: 10,
        }]
    },
    options: {
        responsive: true,
        plugins: {
            title: {
                display: true,
                text: 'Revenue Breakdown by Ticket Type',
                font: { size: 16 }
            }
        }
    }
});

Your dashboard is fully functional with summary cards and three interactive charts. Next up, you'll take it further with a Secret Mission that adds filtering to your dashboard.

Secret mission

Add a Ticket Type Filter

Your dashboard shows all ticket data at once. But what if event organizers want to drill down into a single ticket type? Add a dropdown filter that dynamically updates every card and chart on the page.

Before you go

Clean Up Your Resources

This project runs entirely on your local machine, so there are no ongoing costs or cloud resources to worry about. Decide whether to keep your project files, or delete them entirely.

Resources you used:

  • VS Code (installed application).
  • The ticket-dashboard project folder containing index.html, style.css, and script.js.
  • Chart.js loaded via CDN (no local installation needed).

Keep everything running

No action needed. Choose this if you want to keep building on your dashboard or reference the code later.

  • Your ticket-dashboard folder stays on your Desktop with all three files intact.
  • VS Code remains installed and ready to use for future projects.

Pause - I'll come back to this later

Since everything is local files, there is nothing to pause. Your project files sit on your Desktop and take up almost no space.

  • Close the ticket-dashboard folder in VS Code by clicking File, then Close Folder.
  • Close the browser tab showing your dashboard.
  • When you are ready to come back, open the ticket-dashboard folder in VS Code and double-click index.html in Finder to reopen it in your browser.

Delete - I don't want to use this again

Remove all project files and optionally uninstall VS Code.

  • Close the ticket-dashboard folder in VS Code.
  • Open Finder and navigate to your Desktop.
  • Drag the ticket-dashboard folder to the Trash.
  • To uninstall VS Code, open your Applications folder in Finder and drag Visual Studio Code to the Trash.
  • Empty the Trash to free up disk space.

Mission accomplished

Nice Work!

You just built a real, interactive data dashboard from scratch! Starting from an empty folder, you turned raw ticket sales data into a polished visual dashboard for TechConf 2026.

Here's what you accomplished:

  • Built a structured HTML page and styled it into a professional dashboard layout using CSS Grid.
  • Wrote JavaScript to aggregate raw sales data into summary metrics and render them as dynamic cards on the page.
  • Created three interactive Chart.js visualizations: a bar chart, a line chart, and a doughnut chart with tooltips and animations.
  • Secret Mission: Added a dropdown filter to dynamically update the entire dashboard by ticket type.

Ready to quiz yourself?