Construct a Data Dashboard Using HTML, CSS, and JavaScript

-

dashboard on your customers, clients, or fellow staff is becoming a vital a part of the skill set required by software developers, data scientists, ML practitioners, and data engineers. Even in the event you work totally on back-end processing, the info you’re processing normally must be “surfaced” to users in some unspecified time in the future. When you’re lucky, your organisation could have a dedicated front-end team to maintain that, but often it can be all the way down to you. 

Being a straight-up Python developer with no experience in HTML, JavaScript, etc., isn’t any longer an excuse, as many Python libraries, similar to Streamlit and Gradio, have emerged over the previous couple of years.

This text just isn’t about them, though, because one among those straight-up Python developers, and I’ve already done the Streamlit and Gradio thing. So it was time to roll up my sleeves and see if I could learn recent skills and create a dashboard with those old front-end development stalwarts: HTML, JavaScript, and CSS.

The information for our dashboard will come from a neighborhood SQLite database. I created a sales_data table in SQLite containing dummy sales data. Here is the info in tabular form.

Image by Creator

Below is a few code which you can use to follow along and create your individual SQLite database and table with the info as shown. 

In case you’re wondering why I’m only inserting a handful of records into my database, it’s not because I don’t think the code can handle large data volumes. It’s just that I desired to focus on the dashboard functionality moderately than being distracted by the info. Be at liberty to make use of the script I provide below so as to add additional records to the input data set in the event you like.

So, we stay within the Python world for only a bit longer as we arrange a SQLite DB programmatically.

import sqlite3

# Define the database name
DATABASE_NAME = "C:Usersthomaprojectsmy-dashboardsales_data.db"

# Connect with SQLite database
conn = sqlite3.connect(DATABASE_NAME)

# Create a cursor object
cursor = conn.cursor()

# SQL to create the 'sales' table
create_table_query = '''
CREATE TABLE IF NOT EXISTS sales (
    order_id INTEGER PRIMARY KEY,
    order_date TEXT,
    customer_id INTEGER,
    customer_name TEXT,
    product_id INTEGER,
    product_names TEXT,
    categories TEXT,
    quantity INTEGER,
    price REAL,
    total REAL
);
'''

# Execute the query to create the table
cursor.execute(create_table_query)

# Sample data to insert into the 'sales' table
sample_data = [
    (1, "2022-08-01", 245, "Customer_884", 201, "Smartphone", "Electronics", 3, 90.02, 270.06),
    (2, "2022-02-19", 701, "Customer_1672", 205, "Printer", "Electronics", 6, 12.74, 76.44),
    (3, "2017-01-01", 184, "Customer_21720", 208, "Notebook", "Stationery", 8, 48.35, 386.80),
    (4, "2013-03-09", 275, "Customer_23770", 200, "Laptop", "Electronics", 3, 74.85, 224.55),
    (5, "2022-04-23", 960, "Customer_23790", 210, "Cabinet", "Office", 6, 53.77, 322.62),
    (6, "2019-07-10", 197, "Customer_25587", 202, "Desk", "Office", 3, 47.17, 141.51),
    (7, "2014-11-12", 510, "Customer_6912", 204, "Monitor", "Electronics", 5, 22.5, 112.5),
    (8, "2016-07-12", 150, "Customer_17761", 200, "Laptop", "Electronics", 9, 49.33, 443.97)
]

# SQL to insert data into the 'sales' table
insert_data_query = '''
INSERT INTO sales (order_id, order_date, customer_id, customer_name, product_id, product_names, categories, quantity, price, total)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
'''

# Insert the sample data
cursor.executemany(insert_data_query, sample_data)

# Commit the transaction
conn.commit()

# Close the connection
conn.close()

print(f"Database '{DATABASE_NAME}' has been created and populated successfully.")

Dashboard Functionality

Our dashboard may have the next functionality.

  • Key Metrics. Total revenue, total orders, average order value, top category
  • Different Chart Types. Revenue Over Time (line chart), Revenue by Category (bar chart), Top Products by Revenue (horizontal bar chart)
  • Filtering. By date and category
  • Data Table. Display our data records in a paginated and searchable grid format.

Establishing our Environment

Next, now we have a series of steps to follow to establish our surroundings.

1/ Install Node.js.

Node.js is a runtime environment that lets you run JavaScript outside the browser, allowing you to make use of JavaScript to construct fast and scalable server-side applications.

So, ensure Node.js is installed in your system to enable you to run a neighborhood server and manage packages. You’ll be able to download it from the Node.js official website.

2/ Create a principal project folder and subfolders

Open your command terminal and run the next commands. I’m using Ubuntu on my Windows box for this, but you’ll be able to change it to suit your chosen command-line utility and system.

$ mkdir my-dashboard
$ cd my-dashboard
$ mkdir client
% mkdir server

3/ Initialise a Node project

$ npm init -y

This command robotically creates a default package.json file in your project directory without requiring user input.

The -y flag answers “yes” to all prompts, using the default values for fields like:

  • name
  • version
  • description
  • principal
  • scripts
  • creator
  • license

Here’s what my package file looked like.

{
  "name": "my-dashboard",
  "version": "1.0.0",
  "principal": "index.js",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1"
  },
  "keywords": [],
  "creator": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "express": "^4.21.2",
    "sqlite3": "^5.1.7"
  }
}

4/ Install Express and SQLite

SQLite is a light-weight, file-based relational database engine that stores all of your data in a single, portable file, eliminating the necessity for a separate server.

Express is a minimal, flexible web application framework for Node.js that simplifies the constructing of APIs and web servers through routing and middleware.

We will install each using the command below.

$ npm install express sqlite3

Now, we are able to start developing our code. For this project, we’ll need 4 code files: an index.html file, a server.js file, a client.js file, and a script.js file. 

Let’s undergo each of them step-by-step.

1) client/index.html




    
    
    
    
    
    Sales Performance Dashboard


    

Key Metrics

This HTML file establishes the fundamental visual elements of our Sales Performance Dashboard, including interactive filters for date and category, a bit displaying key sales metrics, a dropdown menu to pick out chart types, and a table for raw data. 

Bootstrap is used for styling. Flatpickr is used for date inputs. Chart.js is used for visualisations, and DataTables is used for tabular display. Interactivity is handled by an external script.js file, which we’ll examine shortly.

Bootstrap is a well-liked front-end framework, initially developed by Twitter, that helps you construct responsive and visually consistent web interfaces more easily and quickly.

DataTables is a jQuery-based plugin that enhances standard HTML

elements, transforming them into fully interactive, feature-rich tables.

Flatpickr is a light-weight, customizable JavaScript date and time picker. It lets users select dates (and optionally times) from a sleek pop-up calendar as a substitute of typing them manually.

Chart.js is a straightforward yet powerful JavaScript library for creating interactive, animated charts in web applications using the element.

2) client/style.css

/* client/style.css */
body {
    background-color: #f8f9fa;
    font-family: 'Arial', sans-serif;
}

h1 {
    text-align: center; /* Center the heading */
    margin-top: 20px; /* Add spacing above the heading */
    margin-bottom: 40px; /* Add spacing below the heading */
}

.container .filters {
    margin-top: 20px;
    margin-bottom: 60px !essential; /* Ensure larger spacing between filters and Key Metrics */
}

.container #key-metrics {
    margin-top: 40px !essential; /* Additional spacing above the Key Metrics section */
    margin-bottom: 20px; /* Optional spacing below */
}

.key-metrics div {
    margin: 10px 0;
    padding: 10px;
    background-color: #f4f4f4;
    border: 1px solid #ccc;
    border-radius: 4px;
}

/* Fix for DataTables Pagination Spacing */
.dataTables_wrapper .dataTables_paginate {
    text-align: center;
    margin-top: 10px;
}

.dataTables_wrapper .dataTables_paginate .paginate_button {
    margin: 0 12px;
    padding: 5px 10px;
    border: 1px solid #ddd;
    border-radius: 4px;
    background-color: #f9f9f9;
    color: #007bff;
    text-decoration: none;
    display: inline-block;
}

.dataTables_wrapper .dataTables_paginate .paginate_button:hover {
    background-color: #007bff;
    color: #fff;
    border: 1px solid #007bff;
}

.dataTables_wrapper .dataTables_paginate .paginate_button.current {
    font-weight: daring;
    color: #fff;
    background-color: #007bff;
    border-color: #007bff;
}

We use a cascading style sheet (CSS) to style the fundamental visual components of our dashboard, for instance, button and text colors, spacing between elements, etc. 

The style.css file gives the dashboard its appearance and overall look. It’s a clean, light theme with ample spacing and layout adjustments for clarity and readability. The style.css file also customises the looks of DataTables’ pagination buttons, making them more user-friendly and visually consistent with Bootstrap’s design.

3) server/server.js

const express = require('express');
const sqlite3 = require('sqlite3').verbose();
const path = require('path');
const app = express();
const PORT = 3000;

// Full path to your SQLite database
const DB_PATH = "C:Usersthomaprojectsmy-dashboardsales_data.db";

// Serve static files from the client directory
app.use(express.static(path.join(__dirname, '..', 'client')));

// Path to fetch data from SQLite database
app.get('/data', (req, res) => {
    const db = recent sqlite3.Database(DB_PATH, sqlite3.OPEN_READONLY, (err) => {
        if (err) {
            console.error("Error connecting to database:", err.message);
            res.status(500).json({ error: "Database connection failed" });
            return;
        }
    });

    // Query the database
    const query = "SELECT * FROM sales;"; // Replace 'sales' along with your table name
    db.all(query, [], (err, rows) => {
        if (err) {
            console.error("Error running query:", err.message);
            res.status(500).json({ error: "Query failed" });
        } else {
            res.json(rows); // Send the query result as JSON
        }
    });

    db.close((err) => {
        if (err) {
            console.error("Error closing database:", err.message);
        }
    });
});

// Catch-all path to serve the principal HTML file
app.get('*', (req, res) => {
    res.sendFile(path.join(__dirname, '..', 'client', 'index.html'));
});

// Start the server
app.listen(PORT, () => {
    console.log(`Server running at http://localhost:${PORT}`);
});

This Node.js script comprises the JavaScript code that sets up a basic Express server that powers the Sales Performance Dashboard. It does two principal things:

  1. Serves static files (like HTML, CSS, and JS) from the client subfolder so the frontend loads within the browser.
  2. Provides a /data endpoint that reads from a neighborhood SQLite database (sales_data.db) and returns your complete sales table as JSON, enabling dynamic data visualisations and tables on the frontend.

4) client/script.js

let chartInstance = null; // Global variable to store the present Chart.js instance

// Wait until the DOM is fully loaded
document.addEventListener('DOMContentLoaded', function () {
    // Fetch sales data from the backend API
    fetch('/data')
        .then((response) => response.json())
        .then((data) => {
            // Handle case where no data is returned
            if (!data || data.length === 0) {
                const app = document.getElementById('app');
                if (app) {
                    app.innerHTML = "

No data available.

"; } return; } // Initialize filters and dashboard content setupFilters(data); initializeDashboard(data); // Re-render charts when chart type changes document.getElementById('chart-type-selector').onchange = () => filterAndRenderData(data); }) .catch((error) => { // Handle fetch error console.error('Error fetching data:', error); const app = document.getElementById('app'); if (app) { app.innerHTML = "

Didn't fetch data.

"; } }); }); // Initialize Flatpickr date pickers and category filter function setupFilters(data) { // Convert date strings to JS Date objects const dates = data.map((item) => recent Date(item.order_date.split('/').reverse().join('-'))); const minDate = recent Date(Math.min(...dates)); const maxDate = recent Date(Math.max(...dates)); // Configure start date picker flatpickr("#start-date", { defaultDate: minDate.toISOString().slice(0, 10), dateFormat: "Y-m-d", altInput: true, altFormat: "F j, Y", onChange: function () { filterAndRenderData(data); }, }); // Configure end date picker flatpickr("#end-date", { defaultDate: maxDate.toISOString().slice(0, 10), dateFormat: "Y-m-d", altInput: true, altFormat: "F j, Y", onChange: function () { filterAndRenderData(data); }, }); // Arrange category dropdown change listener const categoryFilter = document.getElementById('category-filter'); if (categoryFilter) { categoryFilter.onchange = () => filterAndRenderData(data); } } // Initialize dashboard after filters are set function initializeDashboard(data) { populateCategoryFilter(data); // Populate category dropdown filterAndRenderData(data); // Initial render with all data } // Apply filters and update key metrics, chart, and table function filterAndRenderData(data) { const chartType = document.getElementById('chart-type-selector').value; const startDate = document.getElementById('start-date')._flatpickr.selectedDates[0]; const endDate = document.getElementById('end-date')._flatpickr.selectedDates[0]; const selectedCategory = document.getElementById('category-filter').value; // Filter data by date and category const filteredData = data.filter((item) => item.categories === selectedCategory) ); ); updateKeyMetrics(filteredData); // Update metrics like revenue and orders drawChart(filteredData, 'chart-canvas', chartType); // Render chart populateDataTable(filteredData); // Update table } // Update dashboard metrics (total revenue, order count, etc.) function updateKeyMetrics(data) { const totalRevenue = data.reduce((acc, item) => acc + parseFloat(item.total), 0); const totalOrders = data.length; const averageOrderValue = totalOrders > 0 ? totalRevenue / totalOrders : 0; // Calculate total revenue per category to search out top category const revenueByCategory = data.reduce((acc, item) => 0) + parseFloat(item.total); return acc; , {}); // Determine category with highest total revenue const topCategory = Object.keys(revenueByCategory).reduce( (a, b) => (revenueByCategory[a] > revenueByCategory[b] ? a : b), "None" ); // Display metrics within the DOM document.getElementById('total-revenue').textContent = `$${totalRevenue.toFixed(2)}`; document.getElementById('total-orders').textContent = `${totalOrders}`; document.getElementById('average-order-value').textContent = `$${averageOrderValue.toFixed(2)}`; document.getElementById('top-category').textContent = topCategory || 'None'; } // Draw the chosen chart type using Chart.js function drawChart(data, elementId, chartType) { const ctx = document.getElementById(elementId).getContext('second'); // Destroy previous chart if one exists if (chartInstance) { chartInstance.destroy(); } switch (chartType) { case 'revenueOverTime': // Line chart showing revenue by order date chartInstance = recent Chart(ctx, { type: 'line', data: { labels: data.map((item) => item.order_date), datasets: [{ label: 'Revenue Over Time', data: data.map((item) => parseFloat(item.total)), fill: false, borderColor: 'rgb(75, 192, 192)', tension: 0.1, }], }, options: { scales: { y: { beginAtZero: true }, }, }, }); break; case 'revenueByCategory': // Bar chart showing total revenue per category const categories = [...new Set(data.map((item) => item.categories))]; const revenueByCategory = categories.map((category) => { return { category, revenue: data .filter((item) => item.categories === category) .reduce((acc, item) => acc + parseFloat(item.total), 0), }; }); chartInstance = recent Chart(ctx, { type: 'bar', data: { labels: revenueByCategory.map((item) => item.category), datasets: [{ label: 'Revenue by Category', data: revenueByCategory.map((item) => item.revenue), backgroundColor: 'rgba(255, 99, 132, 0.2)', borderColor: 'rgba(255, 99, 132, 1)', borderWidth: 1, }], }, options: { scales: { y: { beginAtZero: true }, }, }, }); break; case 'topProducts': // Horizontal bar chart showing top 10 products by revenue const productRevenue = data.reduce((acc, item) => 0) + parseFloat(item.total); return acc; , {}); const topProducts = Object.entries(productRevenue) .sort((a, b) => b[1] - a[1]) .slice(0, 10); chartInstance = recent Chart(ctx, { type: 'bar', data: { labels: topProducts.map((item) => item[0]), // Product names datasets: [{ label: 'Top Products by Revenue', data: topProducts.map((item) => item[1]), // Revenue backgroundColor: 'rgba(54, 162, 235, 0.8)', borderColor: 'rgba(54, 162, 235, 1)', borderWidth: 1, }], }, options: { indexAxis: 'y', // Horizontal bars scales: { x: { beginAtZero: true }, }, }, }); break; } } // Display filtered data in a DataTable function populateDataTable(data) { const tableElement = $('#data-table'); // Destroy existing table if it exists if ($.fn.DataTable.isDataTable(tableElement)) { tableElement.DataTable().clear().destroy(); } // Create a brand new DataTable with relevant columns tableElement.DataTable({ data: data.map((item) => [ item.order_id, item.order_date, item.customer_id, item.product_names, item.categories, `$${parseFloat(item.total).toFixed(2)}`, ]), columns: [ { title: "Order ID" }, { title: "Order Date" }, { title: "Customer ID" }, { title: "Product" }, { title: "Category" }, { title: "Total" }, ], }); } // Populate the category filter dropdown with available categories function populateCategoryFilter(data) { const categoryFilter = document.getElementById('category-filter'); categoryFilter.innerHTML = ''; categoryFilter.appendChild(recent Option('All Categories', 'all', true, true)); // Extract unique categories const categories = recent Set(data.map((item) => item.categories)); categories.forEach((category) => { categoryFilter.appendChild(recent Option(category, category)); }); }

It’s our most complex code file, nevertheless it has to do loads. This JavaScript file powers the interactivity and data visualisation for the Sales Performance Dashboard. Briefly, it …

1/ Fetches sales data

  • When the page loads (DOMContentLoaded), it calls a backend API on the /data endpoint.
  • If no data is returned, a “No data available” message is displayed.

2/ Sets up filters

  • Uses Flatpickr date pickers to decide on a start and end date based on the dataset’s min/max order dates.
  • Adds a category dropdown, allowing users to filter by product category.
  • Adds a chart type selector to modify between different chart visualisations.

3/ Initialises the dashboard

  • Populates the category filter with available categories.
  • Runs the primary render with the total dataset.

4/ Applies filters and re-renders

  • Every time the user changes a filter (date range, category, or chart type), it:
    • Filters the dataset by date range and category.
    • Updates key metrics: total revenue, variety of orders, average order value, and top revenue category.
    • Redraws the chosen Chart.js chart.
    • Refreshes the data table.

5/ Draws charts with Chart.js

  • Revenue Over Time → Line chart showing revenue trends by date.
  • Revenue by Category → Bar chart aggregating total revenue per category.
  • Top Products → Horizontal bar chart showing the highest 10 products by revenue.

6/ Displays tabular data

  • Uses DataTables (a jQuery plugin) to render a table of filtered orders, with columns for order ID, date, customer ID, product, category, and total.

7/ Keeps the UI in sync

  • Destroys and recreates charts/tables when filters change to avoid duplicates.
  • Keeps metrics, charts, and tables consistent with the lively filters.

Running our dashboard

Now that now we have all our code sorted, it’s time to run the dashboard, so go to the server subfolder and kind in the next command.

$ node server.js

You’ll get a response to the above command, something like,

Server running at http://localhost:3000

Open an internet browser and visit http://localhost:3000. You must see your dashboard populated with data from the SQLite database, as shown within the image below.

Image by Creator

All of the filters, chart selection, etc, should work as advertised.

Summary

In this text, I’ve walked you thru creating a completely functional, interactive sales performance dashboard using core web technologies—HTML, CSS, JavaScript, Node.js, Express, and a neighborhood SQLite database.

We discussed the tech stack & setup. i.e.

  • Backend: Node.js, Express, SQLite
  • Frontend: HTML, Bootstrap (for layout), Chart.js (for charts), Flatpickr (date pickers), DataTables (for tabular data)
  • Folder structure as shown below.
my-dashboard/
├── client/
│   ├── index.html
│   ├── style.css
│   └── script.js
└── server/
    └── server.js

I showed you create and populate a SQLite database in code that we could use because the source data for our dashboard. We also discussed the environment setup and each the front-end and back-end development processes, and briefly touched on our data dashboard functionality.

Finally, I walked you thru and explained intimately the 4 code files we would have liked to create, after which showed you run the dashboard in a browser.


Towards Data Science is a community publication. Submit your insights to achieve our global audience and earn through the TDS Creator Payment Program.


Write for TDS

Related Articles

  • Building Hash Table

    Three common Python tricks make your program faster, I’ll explain the mechanisms

  • Photo by Fleur on Unsplash

    Our weekly collection of must-read Editors’ Picks and original features

  • Objects detection with YOLO, Image by author

    Let’s travel 8 years back in time

  • Photo by Szabolcs Toth on Unsplash
  • Recreating lessons learned from Cole Nussbaumer Knaflic’s book in Python using Matplotlib

  • Use Python Logging like a Pro

  • Tutorial on use WebSockets to construct real-time APIs in Go




ASK ANA

What are your thoughts on this topic?
Let us know in the comments below.

0 0 votes
Article Rating
guest
0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments

Share this article

Recent posts

0
Would love your thoughts, please comment.x
()
x