📊 Chart Integration #

Complete guide to integrating and customizing charts in Admindek using Chart.js and the shared AdmindekChart preset.

Overview #

Admindek uses Chart.js 4 for every chart in the template: dashboards, widgets, the Chart.js showcase page and the sparklines. Chart.js is MIT licensed, and so are the three plugins the showcase page adds (matrix, financial and zoom).

On top of Chart.js sits one small file, src/assets/js/chart-theme.js. It:

  • Sets Chart.defaults from the template's CSS variables (font, text, grid, surface and palette colours)
  • Repaints every chart when the customizer switches dark/light mode, RTL/LTR or the preset colour
  • Exposes window.AdmindekChart with helpers: create(), radial(), sparkline(), gradient(), alpha(), color(), palette(), refresh() and merge()
  • Registers two tiny plugins: centerText (label in the middle of a doughnut) and valueLabels (values on bars and slices)
PageMenuScript
chart/chart-chartjs.htmlCharts → Chart.jssrc/assets/js/chart_maps/chart-chartjs.js
chart/chart-peity.htmlCharts → Sparkline Chartsrc/assets/js/chart_maps/chart-sparkline.js

See both pages in the live demo: Chart.js showcase and Sparklines. The old chart/chart-apex.html URL redirects to chart/chart-chartjs.html.

How Charts Are Loaded #

Chart.js is not bundled by Vite. It is loaded as a plain <script> on the pages that need it, in this order:

<!-- 1. Chart.js -->
<script src="../assets/js/plugins/chart.umd.min.js"></script>
<!-- 2. The Admindek theme preset (defines window.AdmindekChart) -->
<script src="../assets/js/chart-theme.js"></script>
<!-- 3. The page's chart script -->
<script src="../assets/js/widgets/bar-chart.js"></script>

Pages in the root folder (such as index.html) use assets/js/... without the ../.

The showcase page chart/chart-chartjs.html also loads the three MIT plugins, after Chart.js and before chart-theme.js:

<script src="../assets/js/plugins/chart.umd.min.js"></script>
<script src="../assets/js/plugins/chartjs-chart-matrix.min.js"></script>     <!-- heatmaps -->
<script src="../assets/js/plugins/chartjs-chart-financial.min.js"></script>  <!-- candlestick -->
<script src="../assets/js/plugins/chartjs-plugin-zoom.min.js"></script>      <!-- zoom -->
<script src="../assets/js/chart-theme.js"></script>
<script src="../assets/js/chart_maps/chart-chartjs.js"></script>

The vendor files are copied from node_modules into dist/assets/js/plugins/ by plugins/vite-plugin-copy-assets.js:

PackageFileSize
chart.jschart.umd.min.js~204 KB minified, ~69 KB gzipped
chartjs-chart-matrixchartjs-chart-matrix.min.js~4 KB
chartjs-chart-financialchartjs-chart-financial.min.js~12 KB
chartjs-plugin-zoomchartjs-plugin-zoom.min.js~15 KB

Basic Chart Setup #

1. HTML Container #

Charts render into an ordinary <div> with an id. AdmindekChart.create() adds the <canvas> for you.

<div class="card">
  <div class="card-header">
    <h5 class="mb-0">Sales Analytics</h5>
  </div>
  <div class="card-body">
    <div id="bar-chart"></div>
  </div>
</div>

2. JavaScript Configuration #

// src/assets/js/widgets/bar-chart.js
'use strict';
document.addEventListener('DOMContentLoaded', function () {
  setTimeout(function () {
    var from = ['#1de9b6', '#a389d4', '#04a9f5', '#f44236'];
    var to = ['#1dc4e9', '#899ed4', '#049df5', '#f48f36'];
    AdmindekChart.create(
      '#bar-chart',
      {
        type: 'bar',
        data: {
          labels: ['Sport', 'Music', 'Travel', 'News'],
          datasets: [
            {
              label: 'News',
              data: [53, 13, 30, 4],
              // one two-colour gradient per bar
              backgroundColor: function (ctx) {
                return AdmindekChart.gradient([to[ctx.dataIndex], from[ctx.dataIndex]])(ctx);
              },
              categoryPercentage: 0.7,
              barPercentage: 0.5
            }
          ]
        },
        options: {
          plugins: {
            legend: { display: false },
            tooltip: {
              displayColors: false,
              callbacks: {
                title: function () {
                  return '';
                }
              }
            }
          },
          scales: {
            y: { display: false, beginAtZero: true }
          }
        }
      },
      { height: 250 }
    );
  }, 500);
});

This is a simplified version of the bar chart in src/assets/js/widgets/bar-chart.js (the shipped file computes each gradient across the bar itself rather than the whole chart area). The config is a standard Chart.js configuration (type, data, options). The third argument is Admindek-specific: it sizes the container.

The AdmindekChart API #

HelperReturnsPurpose
create(target, config, opts)Chart or nullCreate a chart inside a container
radial(target, options)Chart or nullRadial progress / multi-ring progress (a doughnut per ring)
sparkline(extra)options objectOptions for axis-less, legend-less, tooltip-less mini charts
gradient(colors, opts)scriptable colourCanvas gradient across the chart area
alpha(color, a)rgba() stringAdd transparency to a colour or theme token
color(name)stringCurrent value of a theme token ('primary', 'text', 'track' ...)
palette()arrayDefault series colours, starting with the active preset colour
refresh()–Re-read the theme and repaint every chart
merge(target, source)objectChart.helpers.merge (deep merge of option objects)

create(target, config, opts) #

const chart = AdmindekChart.create('#revenue-chart', config, { height: 300 });
  • target: a selector, an element, or a <canvas>. Returns null if the element does not exist, so a missing container never throws.
  • config: a normal Chart.js config.
  • opts.height: number (px) or CSS length. Sets the container's height and turns off maintainAspectRatio (unless you set it yourself). Without a height, Chart.js keeps its default aspect ratio.
  • opts.width: number (px) or CSS length. Fixes the container's width, useful for tiny inline charts; an inline container becomes inline-block.
  • opts.label: accessible label for the canvas. The container's own aria-label attribute is used if present.

The canvas gets class="admindek-chart" and role="img". Calling create() again on the same container destroys the previous chart first, so re-rendering is safe.

gradient(colors, opts) #

Returns a scriptable colour, so pass it directly as backgroundColor or borderColor:

AdmindekChart.gradient('#4680ff');                          // vertical fade, 0.4 -> 0 opacity (area fills)
AdmindekChart.gradient('#4680ff', { from: 0.6, to: 0.05 }); // custom opacities
AdmindekChart.gradient(['#1de9b6', '#1dc4e9']);             // two-colour gradient (bars, rings)
AdmindekChart.gradient(['#1dc4e9', '#1de9b6'], { direction: 'horizontal' });
AdmindekChart.gradient('primary');                          // theme token, re-read on preset change

alpha(color, a), color(name) and palette() #

AdmindekChart.alpha('#4680ff', 0.2);   // 'rgba(70, 128, 255, 0.2)'
AdmindekChart.alpha('primary', 0.2);   // works with theme tokens too
AdmindekChart.color('primary');        // current --bs-primary, e.g. '#4680ff'
AdmindekChart.color('#ff0000');        // unknown names are returned unchanged
AdmindekChart.palette();               // [primary, success, warning, danger, indigo, info, orange, pink]

alpha() accepts #rgb, #rrggbb, rgb() and rgba() values.

Theme tokens available to color(), alpha() and gradient():

TokenSource
primary, secondary, success, danger, warning--bs-primary ... --bs-warning
info, indigo, teal, orange, pink, purple--bs-cyan, --bs-indigo, --bs-teal, --bs-orange, --bs-pink, --bs-purple
text, heading--bs-body-color, --pc-heading-color
grid, borderGrid line colour, --bs-border-color
surfaceThe card background (used for slice borders)
trackEmpty part of progress rings
tooltipBg, tooltipText, tooltipBorderTooltip colours
font--bs-body-font-family
dark, rtlBooleans: current mode and direction

sparkline(extra) #

Returns options with axes, legend and tooltip hidden, no points and a 2px line. Pass extra options to deep-merge them in:

// Plain sparkline
{ type: 'line', data: data, options: AdmindekChart.sparkline() }
 
// Sparkline with the tooltip turned back on
{ type: 'line', data: data, options: AdmindekChart.sparkline({ plugins: { tooltip: { enabled: true } } }) }

radial(target, options) #

Builds radial progress bars (a doughnut per ring with a background track) and calls create() for you.

AdmindekChart.radial('#goal', {
  values: [76],          // one number per ring, outer ring first
  labels: ['Completed'],
  colors: ['success'],   // theme tokens, colours or gradient() results
  height: 250,
  text: '76%',           // centre label (string or function(chart))
  sub: 'Completed'       // smaller second line
});
OptionDefaultDescription
values[]One value per ring (outer ring first)
max100Value that fills a ring
colorspalette()Ring colours: tokens, colour strings or gradient() results
labelsSeries 1...Ring names, used in tooltips
height, width–Passed to create()
cutout'72%' (one ring), '35%' (several)Size of the hole
circumference360Degrees to sweep; 180 for a half gauge
rotation0Start angle; -90 with circumference: 180 for a half gauge
text, sub–Centre label and sub-label (string or function(chart))
textSize, textColorauto, heading colourCentre label styling
roundedtrueRounded ring ends
trackColor'track' tokenColour of the unfilled part
padding4Layout padding
tooltiptruefalse disables the tooltip
suffix'%'Appended to the value in the tooltip
label–Accessible label for the canvas
options–Extra Chart.js options, deep-merged

refresh() #

Re-reads the CSS variables and repaints every chart. It runs automatically on theme changes (see Dark Mode, RTL and Preset Colours); call it yourself only if you change CSS variables some other way.

Built-in Plugins #

chart-theme.js registers two small plugins globally. They are off unless you configure them.

centerText #

Draws a label in the middle of a doughnut or pie:

options: {
  plugins: {
    centerText: {
      text: '12.4K',          // string or function (chart) => string
      sub: 'Total Visits',    // optional second line
      color: null,            // defaults to the heading colour
      subColor: null,         // defaults to the text colour
      size: null,             // font size in px, auto-sized when null
      weight: '600'
    }
  }
}

valueLabels #

Draws values on bars and slices:

options: {
  plugins: {
    valueLabels: {
      display: true,
      color: '#ffffff',       // or function ({ datasetIndex, dataIndex }) => colour
      size: 11,
      // Optional. Default: the raw value on bars, the percentage of the
      // visible total on pie, doughnut and polar area charts.
      formatter: function (value, ctx) {
        return value + ' (' + Math.round((value / ctx.total) * 100) + '%)';
      }
    }
  }
}

Return '' or null from formatter to skip a label.

What the Theme Preset Sets #

You do not need to repeat these in every chart:

  • Fonts and colours: body font at 12px, text and grid colours from the theme
  • Scales: no axis border or tick marks, subtle horizontal grid lines, no vertical grid on category axes
  • Lines: tension: 0.4, 2px width, points hidden until hover
  • Bars: 4px border radius
  • Doughnut/pie slices: 2px border in the card colour, small hover offset
  • Legend: bottom, circular point markers
  • Tooltips: theme colours, rounded, point-style markers; line and bar charts use shared (index) tooltips
  • Animation: 800ms, easeOutQuart

Any option you set in a chart's config overrides the preset for that chart.

Chart Types #

Line and Area Chart (with Gradient) #

An area chart is a line chart with fill: true:

AdmindekChart.create(
  '#revenue-chart',
  {
    type: 'line',
    data: {
      labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'],
      datasets: [
        {
          label: 'Revenue',
          data: [31, 40, 28, 51, 42, 109, 100],
          borderColor: '#4680ff',
          backgroundColor: AdmindekChart.gradient('#4680ff', { from: 0.35, to: 0.05 }),
          fill: true
        },
        {
          label: 'Profit',
          data: [11, 32, 45, 32, 34, 52, 41],
          borderColor: '#2ecc71',
          backgroundColor: AdmindekChart.gradient('#2ecc71', { from: 0.35, to: 0.05 }),
          fill: true
        }
      ]
    },
    options: {
      plugins: { legend: { position: 'top', align: 'end' } }
    }
  },
  { height: 300 }
);

For a plain line chart, leave out fill and backgroundColor. Use tension: 0 for straight segments and borderDash: [5, 5] for a dashed series (the dashboard's forecast line does this).

Bar Chart #

AdmindekChart.create(
  '#products-chart',
  {
    type: 'bar',
    data: {
      labels: ['Q1', 'Q2', 'Q3', 'Q4', 'Q5', 'Q6'],
      datasets: [{ label: 'Products Sold', data: [44, 55, 57, 56, 61, 58], backgroundColor: '#4680ff' }]
    },
    options: {
      plugins: { legend: { display: false } },
      scales: { y: { beginAtZero: true } }
    }
  },
  { height: 350 }
);

Stacked bars: stack both axes.

data: {
  labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
  datasets: [
    { label: 'Online', data: [12, 19, 14, 17, 22], backgroundColor: '#4680ff' },
    { label: 'In store', data: [8, 11, 9, 12, 10], backgroundColor: '#2ecc71' }
  ]
},
options: {
  scales: { x: { stacked: true }, y: { stacked: true } }
}

Horizontal bars: set indexAxis: 'y'.

options: {
  indexAxis: 'y',
  plugins: { valueLabels: { display: true } } // values inside the bars
}

Doughnut and Pie Chart #

AdmindekChart.create(
  '#device-chart',
  {
    type: 'doughnut', // or 'pie'
    data: {
      labels: ['Desktop', 'Mobile', 'Tablet', 'Smart TV', 'Others'],
      datasets: [{ data: [44, 55, 41, 17, 15], backgroundColor: AdmindekChart.palette().slice(0, 5) }]
    },
    options: {
      cutout: '70%',
      plugins: {
        centerText: {
          text: function (chart) {
            return chart.data.datasets[0].data.reduce(function (a, b) {
              return a + b;
            }, 0);
          },
          sub: 'Total'
        }
      }
    }
  },
  { height: 300 }
);

A simple "value vs remainder" ring (as in src/assets/js/widgets/happy-sad-ball.js) colours the second slice with the track token:

backgroundColor: function (ctx) {
  return ctx.dataIndex === 0 ? '#1de9b6' : AdmindekChart.color('track');
}

Radial Progress #

// Single ring with a gradient (src/assets/js/widgets/chart-sale.js)
AdmindekChart.radial('#chart-sale', {
  values: [85],
  labels: ['Sales'],
  colors: [AdmindekChart.gradient(['#1de9b6', '#1dc4e9'], { direction: 'horizontal' })],
  height: 220,
  cutout: '89%',
  text: '85',
  textSize: 36
});
 
// Several concentric rings
AdmindekChart.radial('#goals', {
  values: [76, 67, 61],
  labels: ['Sales', 'Leads', 'Visits'],
  colors: ['primary', 'success', 'warning'],
  height: 260
});
 
// Half gauge
AdmindekChart.radial('#uptime', {
  values: [99.9],
  labels: ['Uptime'],
  circumference: 180,
  rotation: -90,
  height: 180,
  text: '99.9%'
});

Sparkline #

Small trend charts in KPI cards (from the dashboard in src/html/index.html):

AdmindekChart.create(
  '#total-revenue-chart',
  {
    type: 'line',
    data: {
      labels: ['', '', '', '', '', '', ''],
      datasets: [{ data: [31, 40, 28, 51, 42, 85, 77], borderColor: '#ffffff', borderWidth: 2, tension: 0 }]
    },
    options: AdmindekChart.sparkline()
  },
  { width: 80, height: 50 }
);

Bar sparklines work the same way with type: 'bar'. Chart.js needs a labels entry per value, so use empty strings when there are no category names.

Updating Data Live #

Keep the instance returned by create(), change its data and call update():

const live = AdmindekChart.create(
  '#realtime-chart',
  {
    type: 'line',
    data: {
      labels: [],
      datasets: [
        {
          label: 'Live Data',
          data: [],
          borderColor: () => AdmindekChart.color('primary'),
          backgroundColor: AdmindekChart.gradient('primary'),
          fill: true
        }
      ]
    },
    options: { scales: { y: { min: 0, max: 100 } } }
  },
  { height: 350 }
);
 
setInterval(function () {
  live.data.labels.push(new Date().toLocaleTimeString());
  live.data.datasets[0].data.push(Math.floor(Math.random() * 100));
 
  // keep the last 20 points
  if (live.data.labels.length > 20) {
    live.data.labels.shift();
    live.data.datasets[0].data.shift();
  }
  live.update();
}, 2000);
  • Replace a whole series: chart.data.datasets[0].data = newData; chart.update();
  • Skip the animation: chart.update('none');
  • Show or hide a series: chart.setDatasetVisibility(1, false); chart.update();

Loading Data from an API #

async function createApiChart() {
  const response = await fetch('/api/sales-data');
  if (!response.ok) return;
  const data = await response.json(); // { labels: [...], values: [...] }
 
  AdmindekChart.create(
    '#api-chart',
    {
      type: 'line',
      data: {
        labels: data.labels,
        datasets: [{ label: 'Sales', data: data.values, borderColor: '#4680ff' }]
      }
    },
    { height: 350 }
  );
}

Dark Mode, RTL and Preset Colours #

Charts follow the customizer without any extra code. chart-theme.js watches the data-pc-theme, data-pc-direction and data-pc-preset attributes on <body> and the dir attribute on <html> with a MutationObserver. When one changes, it re-reads the CSS variables, updates Chart.defaults and repaints every chart on the page.

What updates automatically:

  • Text, grid, tooltip and legend colours, and slice borders (they come from the preset defaults)
  • Anything given as a theme token to gradient(), alpha() or radial(), e.g. gradient('primary')
  • Scriptable colours that call AdmindekChart.color() when drawing, e.g. borderColor: () => AdmindekChart.color('primary')

What stays fixed:

  • Hard-coded colours such as '#4680ff'
  • A value read once when the chart is created, e.g. borderColor: AdmindekChart.color('primary'). Wrap it in a function to make it follow the preset colour.

RTL: legend and tooltip text switch to right-to-left. Axes are not mirrored; set reverse: true on the x scale if you want the category order flipped in RTL layouts.

Charts inside Bootstrap tabs, modals, collapses, offcanvas panels and dropdowns are resized when those are shown, so they do not stay at zero width.

Adding the Matrix, Financial and Zoom Plugins #

The three plugins are copied to assets/js/plugins/ by the build, but only chart/chart-chartjs.html loads them. To use one on another page, add its script after chart.umd.min.js and before chart-theme.js:

<script src="../assets/js/plugins/chart.umd.min.js"></script>
<script src="../assets/js/plugins/chartjs-chart-matrix.min.js"></script>
<script src="../assets/js/chart-theme.js"></script>

The UMD builds register themselves with Chart.js, so no Chart.register() call is needed.

Heatmap (chartjs-chart-matrix) #

const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'];
const hours = ['9am', '12pm', '3pm', '6pm'];
const cells = [];
days.forEach((day) => hours.forEach((hour) => cells.push({ x: hour, y: day, v: Math.round(Math.random() * 100) })));
 
AdmindekChart.create(
  '#heatmap',
  {
    type: 'matrix',
    data: {
      datasets: [
        {
          label: 'Visits',
          data: cells,
          backgroundColor: (ctx) => AdmindekChart.alpha('primary', 0.15 + (((ctx.raw && ctx.raw.v) || 0) / 100) * 0.85),
          borderColor: () => AdmindekChart.color('surface'),
          borderWidth: 2,
          width: ({ chart }) => (chart.chartArea || {}).width / hours.length - 2,
          height: ({ chart }) => (chart.chartArea || {}).height / days.length - 2
        }
      ]
    },
    options: {
      plugins: {
        legend: { display: false },
        tooltip: {
          callbacks: {
            title: () => '',
            label: (item) => item.raw.y + ' ' + item.raw.x + ': ' + item.raw.v
          }
        }
      },
      scales: {
        x: { type: 'category', labels: hours, offset: true, grid: { display: false } },
        y: { type: 'category', labels: days, offset: true, grid: { display: false } }
      }
    }
  },
  { height: 300 }
);

Candlestick (chartjs-chart-financial) #

The plugin adds the candlestick and ohlc chart types. Each point is { x, o, h, l, c }:

AdmindekChart.create(
  '#candlestick',
  {
    type: 'candlestick',
    data: {
      datasets: [
        {
          label: 'ACME',
          data: [
            { x: 1, o: 6629, h: 6650, l: 6623, c: 6633 },
            { x: 2, o: 6632, h: 6643, l: 6620, c: 6630 },
            { x: 3, o: 6630, h: 6648, l: 6618, c: 6640 }
          ]
        }
      ]
    },
    options: {
      plugins: { legend: { display: false } },
      scales: { x: { type: 'linear', ticks: { stepSize: 1 } } }
    }
  },
  { height: 350 }
);

By default the plugin puts a timeseries axis on x, which needs a Chart.js date adapter. The template does not ship one, so either use a linear or category x axis as above (with numbers or your own tick labels), or add an MIT date adapter such as chartjs-adapter-date-fns before chart-theme.js and use real timestamps.

Zoom (chartjs-plugin-zoom) #

const chart = AdmindekChart.create(
  '#zoom-chart',
  {
    type: 'line',
    data: { labels: labels, datasets: [{ label: 'Visitors', data: values, borderColor: '#4680ff' }] },
    options: {
      plugins: {
        zoom: {
          zoom: {
            wheel: { enabled: true },  // mouse wheel
            drag: { enabled: true },   // drag a box to zoom
            mode: 'x'
          }
        }
      }
    }
  },
  { height: 350 }
);
 
// Reset from a button
document.querySelector('#reset-zoom').addEventListener('click', function () {
  chart.resetZoom();
});

Wheel and drag zoom work as-is. Panning and pinch-zoom need Hammer.js, which the template does not copy to assets/js/plugins/; add it before the zoom plugin if you need them.

Destroying and Recreating Charts #

// Recreate: create() on the same container destroys the old chart first
AdmindekChart.create('#sales-chart', monthlyConfig, { height: 300 });
AdmindekChart.create('#sales-chart', weeklyConfig, { height: 300 });
 
// Look up an existing chart from its container
const canvas = document.querySelector('#sales-chart canvas');
const existing = Chart.getChart(canvas); // undefined if there is none
 
// Remove a chart completely (the canvas stays, empty)
if (existing) existing.destroy();

Prefer chart.update() over recreating when only the data changes; it animates and is cheaper. Do not call new Chart() on a canvas that already has a chart: Chart.js throws "Canvas is already in use".

Performance Optimization #

Lazy Loading Charts #

Create charts only when their container scrolls into view:

const chartConfigs = {
  'traffic-chart': () => AdmindekChart.create('#traffic-chart', trafficConfig, { height: 300 }),
  'orders-chart': () => AdmindekChart.create('#orders-chart', ordersConfig, { height: 300 })
};
 
const chartObserver = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (!entry.isIntersecting) return;
    const build = chartConfigs[entry.target.id];
    if (build) build();
    chartObserver.unobserve(entry.target);
  });
});
 
Object.keys(chartConfigs).forEach((id) => {
  const el = document.getElementById(id);
  if (el) chartObserver.observe(el);
});

Tips #

  • Load chart.umd.min.js and the plugins only on pages that have charts.
  • Use chart.update('none') for frequent updates.
  • For long series, set animation: false and keep points hidden (the preset already sets radius: 0).

Troubleshooting #

Chart not rendering:

console.log(typeof Chart);         // "function" - chart.umd.min.js loaded
console.log(typeof AdmindekChart); // "object"   - chart-theme.js loaded after it
console.log(document.querySelector('#chart-id')); // not null

AdmindekChart.create() returns null for a missing container.

Chart is 0px tall: pass { height: 300 } to create(), or give the container a CSS height if you set maintainAspectRatio: false yourself.

"Canvas is already in use": create charts with AdmindekChart.create(), which destroys the previous chart in that container, or update the existing instance instead of creating a new one.

Colours don't follow the preset colour: use theme tokens (gradient('primary')) or functions (() => AdmindekChart.color('primary')) instead of fixed values.

See Common Issues for more.


Summary #

Chart integration in Admindek provides:

  • Chart.js 4 (MIT) with MIT plugins for heatmaps, candlesticks and zoom
  • One theme preset (chart-theme.js) so every chart matches the template
  • Automatic repainting on dark/light, RTL and preset-colour changes
  • Small helpers (create, radial, sparkline, gradient) that cut chart code to the data and a few options
  • Built-in centre labels and value labels for doughnuts and bars
  • Live updates through the standard Chart.js update() API