Adding missing partials

This commit is contained in:
Ronan Berder
2025-05-04 15:58:33 +08:00
parent 7749810bb1
commit e09d22c003
2 changed files with 848 additions and 0 deletions
+36
View File
@@ -0,0 +1,36 @@
<div class="flex h-14 w-full items-center gap-2 px-4">
<button
type="button"
aria-label="Hide sidebar"
data-tooltip="Hide sidebar"
:data-tooltip="sidebarOpen ? 'Hide sidebar' : 'Show sidebar'"
data-side="bottom"
data-align="start"
class="btn-sm-icon-ghost mr-auto size-7 -ml-1.5"
x-init
x-data="{ sidebarOpen: true }"
@click="
sidebarOpen = !sidebarOpen;
$dispatch('sidebar:toggle');
"
>
{% lucide "panel-left" %}
</button>
<select class="select h-8 leading-none">
<option value="light">Default</option>
<option value="light">Other</option>
</select>
<button
type="button"
aria-label="Toggle theme"
data-tooltip="Toggle theme"
data-side="bottom"
data-align="end"
class="btn-icon-outline size-8"
x-init
@click="$store.theme.toggle()"
>
<span x-show="$store.theme.isDark">{% lucide "sun" %}</span>
<span x-show="!$store.theme.isDark">{% lucide "moon" %}</span>
</button>
</div>
+812
View File
@@ -0,0 +1,812 @@
---
layout: layouts/page.njk
title: Chart
description: Displays the path to the current resource using a hierarchy of links.
toc:
- label: Usage
id: usage
---
<div class="grid gap-6">
<div class="card">
<header>
<h2>Area Chart</h2>
<p>Showing total visitors for the last 6 months</p>
</header>
<section>
<canvas id="chart-area-default"></canvas>
</section>
<footer>
<div class="grid gap-2 text-sm">
<div class="flex items-center gap-2 leading-none font-medium">
Trending up by 5.2% this month
{% lucide "trending-up", { "class": "size-4" } %}
</div>
<div class="text-muted-foreground flex items-center gap-2 leading-none">January - June 2024</div>
</div>
</footer>
</div>
<div class="card">
<header>
<h2>Area Chart - Stacked</h2>
<p>Showing total visitors for the last 6 months</p>
</header>
<section>
<canvas id="chart-area-stacked"></canvas>
</section>
<footer>
<div class="grid gap-2 text-sm">
<div class="flex items-center gap-2 leading-none font-medium">
Trending up by 5.2% this month
{% lucide "trending-up", { "class": "size-4" } %}
</div>
<div class="text-muted-foreground flex items-center gap-2 leading-none">January - June 2024</div>
</div>
</footer>
</div>
<div class="card">
<header>
<h2>Bar Chart - Multiple</h2>
<p>Showing total visitors for the last 6 months</p>
</header>
<section>
<canvas id="chart-bar-multiple"></canvas>
</section>
<footer>
<div class="grid gap-2 text-sm">
<div class="flex items-center gap-2 leading-none font-medium">
Trending up by 5.2% this month
{% lucide "trending-up", { "class": "size-4" } %}
</div>
<div class="text-muted-foreground flex items-center gap-2 leading-none">January - June 2024</div>
</div>
</footer>
</div>
<div class="card">
<header>
<h2>Bar Chart - Interactive</h2>
<p>Showing total visitors for the last 6 months</p>
</header>
<section>
<canvas id="chart-bar-interactive"></canvas>
</section>
</div>
<div class="card">
<header>
<h2>Bar Chart - Interactive</h2>
<p>Showing total visitors for the last 6 months</p>
</header>
<section class="h-[150px]">
<canvas id="chart-line-interactive"></canvas>
</section>
</div>
<div class="card">
<header>
<h2>Doughnut Chart</h2>
<p>Showing total visitors for the last 6 months</p>
</header>
<section>
<canvas id="chart-doughnut"></canvas>
</section>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
// Helper function to MULTIPLY the alpha channel of oklch colors by a factor
function setAlpha(colorString, alphaFactor) {
colorString = colorString.trim();
if (colorString.startsWith('oklch(')) {
const match = colorString.match(/^oklch\(([^\)]+?)\s*(?:\/\s*([\d.]+)(%?)\s*)?\)$/);
if (match && match[1]) {
const lchPart = match[1];
let currentAlpha = 1.0;
if (match[2]) {
currentAlpha = parseFloat(match[2]);
if (match[3] === '%') {
currentAlpha /= 100;
}
}
let newAlpha = currentAlpha * alphaFactor;
newAlpha = Math.max(0, Math.min(1, newAlpha));
return `oklch(${lchPart} / ${newAlpha.toFixed(3)})`;
} else {
console.warn('Failed to parse oklch string:', colorString);
return colorString;
}
}
return colorString;
}
const computedStyle = getComputedStyle(document.documentElement);
const themeColors = {
background: computedStyle.getPropertyValue('--background').trim(),
border: computedStyle.getPropertyValue('--border').trim(),
mutedForeground: computedStyle.getPropertyValue('--muted-foreground').trim(),
foreground: computedStyle.getPropertyValue('--foreground').trim(),
chart1: computedStyle.getPropertyValue('--chart-1').trim(),
chart2: computedStyle.getPropertyValue('--chart-2').trim(),
chart3: computedStyle.getPropertyValue('--chart-3').trim(),
chart4: computedStyle.getPropertyValue('--chart-4').trim(),
chart5: computedStyle.getPropertyValue('--chart-5').trim(),
};
const getOrCreateTooltip = (chart) => {
let tooltipEl = chart.canvas.parentNode.querySelector('div[role="tooltip"]');
if (!tooltipEl) {
const tooltipHTML = `
<div
role="tooltip"
class="border-border/50 bg-background grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl opacity-0 absolute pointer-events-none transition-all duration-400"
>
<h3 class="font-medium"></h3>
<ul class="grid gap-1 mt-1"></ul>
</div>`;
const parser = new DOMParser();
const doc = parser.parseFromString(tooltipHTML.trim(), 'text/html');
tooltipEl = doc.body.firstChild;
chart.canvas.parentNode.appendChild(tooltipEl);
}
return tooltipEl;
};
const externalTooltipHandler = (context) => {
const {chart, tooltip} = context;
const tooltipEl = getOrCreateTooltip(chart);
const titleEl = tooltipEl.querySelector('h3');
const bodyEl = tooltipEl.querySelector('ul');
// Hide if no tooltip
if (tooltip.opacity === 0) {
tooltipEl.classList.remove('opacity-100');
tooltipEl.classList.add('opacity-0');
return;
}
let bodyHtml = '';
if (tooltip.body) {
const titleLines = tooltip.title || [];
const bodyLines = tooltip.body.map(b => b.lines);
if (titleEl) titleEl.textContent = titleLines[0] || '';
if (titleEl) titleEl.className = titleLines.length > 0 ? 'font-medium' : '';
bodyLines.forEach((body, i) => {
const colors = tooltip.labelColors[i];
const parts = body[0].split(': ');
const label = parts[0] || '';
const value = parts[1] || '';
console.log(colors)
bodyHtml += `<li class="flex w-full flex-wrap items-center justify-between leading-none gap-x-2">
<div class="flex items-center gap-x-1.5">
<div class="shrink-0 rounded-[2px] size-2.5 block" style="background:${colors.backgroundColor}"></div>
<span class="text-muted-foreground">${label}</span>
</div>
<span class="text-foreground font-medium font-mono tabular-nums">${value}</span>
</li>`;
});
}
if (bodyEl) bodyEl.innerHTML = bodyHtml;
const {offsetLeft: positionX, offsetTop: positionY} = chart.canvas;
const tooltipHeight = tooltipEl.offsetHeight;
const tooltipWidth = tooltipEl.offsetWidth;
const caretX = tooltip.caretX;
const caretY = tooltip.caretY;
const xAlign = tooltip.xAlign;
const yAlign = tooltip.yAlign;
const tooltipOffset = 8;
let left = positionX + caretX;
let top = positionY + caretY;
if (xAlign === 'center') {
left -= tooltipWidth / 2;
} else if (xAlign === 'right') {
left -= (tooltipWidth + tooltipOffset);
} else { // xAlign === 'left'
left += tooltipOffset;
}
if (yAlign === 'center') {
top -= tooltipHeight / 2;
} else if (yAlign === 'top') {
top -= (tooltipHeight + tooltipOffset);
} else {
top += tooltipOffset;
}
const canvasBounds = chart.canvas.getBoundingClientRect();
if (left < 0) {
left = 0;
} else if (left + tooltipWidth > canvasBounds.width) {
left = canvasBounds.width - tooltipWidth;
}
if (top < 0) {
top = 0;
} else if (top + tooltipHeight > window.innerHeight) {
const flippedTop = positionY + caretY - tooltipHeight - tooltipOffset;
if (flippedTop >= 0) top = flippedTop;
}
tooltipEl.classList.remove('opacity-0');
tooltipEl.classList.add('opacity-100');
tooltipEl.style.left = left + 'px';
tooltipEl.style.top = top + 'px';
};
let ctx = document.getElementById('chart-area-default');
let data = {
labels: ['January', 'February', 'March', 'April', 'May', 'June'],
datasets: [{
label: 'Desktop',
data: [186, 305, 237, 73, 209, 214],
borderWidth: 1,
backgroundColor: setAlpha(themeColors.chart1, 0.2),
borderColor: themeColors.chart1,
pointRadius: 0,
pointHoverRadius: 4,
pointHoverBackgroundColor: themeColors.chart1,
pointHoverBorderColor: themeColors.chart1,
fill: 'origin'
}]
};
let config = {
type: 'line',
data: data,
options: {
tension: 0.4,
interaction: {
mode: 'nearest',
axis: 'x',
intersect: false
},
scales: {
y: {
border: { display: false },
grid: {
color: setAlpha(themeColors.border, 0.5),
lineWidth: 1
},
ticks: { display: false }
},
x: {
grid: { display: false },
ticks: { color: themeColors.mutedForeground }
}
},
plugins: {
legend: {
display: false,
},
tooltip: {
enabled: false,
position: 'nearest',
external: externalTooltipHandler,
}
}
}
};
new Chart(ctx, config);
ctx = document.getElementById('chart-area-stacked');
data = {
labels: ['January', 'February', 'March', 'April', 'May', 'June'],
datasets: [{
label: 'Desktop',
data: [186, 305, 237, 73, 209, 214],
borderWidth: 1,
backgroundColor: setAlpha(themeColors.chart1, 0.2),
borderColor: themeColors.chart1,
pointRadius: 0,
pointHoverRadius: 4,
pointHoverBackgroundColor: themeColors.chart1,
pointHoverBorderColor: themeColors.chart1,
fill: 'origin'
}, {
label: 'Mobile',
data: [80, 200, 120, 190, 130, 140],
borderWidth: 1,
backgroundColor: setAlpha(themeColors.chart2, 0.2),
borderColor: themeColors.chart2,
pointRadius: 0,
pointHoverRadius: 4,
pointHoverBackgroundColor: themeColors.chart2,
pointHoverBorderColor: themeColors.chart2,
fill: 'origin'
}]
};
config = {
type: 'line',
data: data,
options: {
tension: 0.4,
interaction: {
mode: 'nearest',
axis: 'x',
intersect: false
},
scales: {
y: {
border: { display: false },
grid: {
color: setAlpha(themeColors.border, 0.5),
lineWidth: 1
},
ticks: { display: false }
},
x: {
grid: { display: false },
ticks: { color: themeColors.mutedForeground }
}
},
plugins: {
legend: {
display: false,
},
tooltip: {
enabled: false,
position: 'nearest',
external: externalTooltipHandler,
}
}
}
};
new Chart(ctx, config);
ctx = document.getElementById('chart-bar-multiple');
data = {
labels: ['January', 'February', 'March', 'April', 'May', 'June'],
datasets: [{
label: 'Desktop',
data: [186, 305, 237, 73, 209, 214],
backgroundColor: themeColors.chart1,
borderWidth: 0,
borderRadius: 4,
barPercentage: 0.7,
categoryPercentage: 0.8
}, {
label: 'Mobile',
data: [80, 200, 120, 190, 130, 140],
backgroundColor: themeColors.chart2,
borderWidth: 0,
borderRadius: 4,
barPercentage: 0.7,
categoryPercentage: 0.8
}]
};
config = {
type: 'bar',
data: data,
options: {
interaction: {
mode: 'index',
axis: 'x',
intersect: false
},
scales: {
y: {
border: { display: false },
grid: {
color: setAlpha(themeColors.border, 0.5),
lineWidth: 1
},
ticks: { display: false }
},
x: {
grid: { display: false },
ticks: { color: themeColors.mutedForeground }
}
},
plugins: {
legend: {
display: false,
},
tooltip: {
enabled: false,
position: 'nearest',
external: externalTooltipHandler,
}
}
}
};
new Chart(ctx, config);
const chartData = [
{ date: "2024-04-01", desktop: 222, mobile: 150 },
{ date: "2024-04-02", desktop: 97, mobile: 180 },
{ date: "2024-04-03", desktop: 167, mobile: 120 },
{ date: "2024-04-04", desktop: 242, mobile: 260 },
{ date: "2024-04-05", desktop: 373, mobile: 290 },
{ date: "2024-04-06", desktop: 301, mobile: 340 },
{ date: "2024-04-07", desktop: 245, mobile: 180 },
{ date: "2024-04-08", desktop: 409, mobile: 320 },
{ date: "2024-04-09", desktop: 59, mobile: 110 },
{ date: "2024-04-10", desktop: 261, mobile: 190 },
{ date: "2024-04-11", desktop: 327, mobile: 350 },
{ date: "2024-04-12", desktop: 292, mobile: 210 },
{ date: "2024-04-13", desktop: 342, mobile: 380 },
{ date: "2024-04-14", desktop: 137, mobile: 220 },
{ date: "2024-04-15", desktop: 120, mobile: 170 },
{ date: "2024-04-16", desktop: 138, mobile: 190 },
{ date: "2024-04-17", desktop: 446, mobile: 360 },
{ date: "2024-04-18", desktop: 364, mobile: 410 },
{ date: "2024-04-19", desktop: 243, mobile: 180 },
{ date: "2024-04-20", desktop: 89, mobile: 150 },
{ date: "2024-04-21", desktop: 137, mobile: 200 },
{ date: "2024-04-22", desktop: 224, mobile: 170 },
{ date: "2024-04-23", desktop: 138, mobile: 230 },
{ date: "2024-04-24", desktop: 387, mobile: 290 },
{ date: "2024-04-25", desktop: 215, mobile: 250 },
{ date: "2024-04-26", desktop: 75, mobile: 130 },
{ date: "2024-04-27", desktop: 383, mobile: 420 },
{ date: "2024-04-28", desktop: 122, mobile: 180 },
{ date: "2024-04-29", desktop: 315, mobile: 240 },
{ date: "2024-04-30", desktop: 454, mobile: 380 },
{ date: "2024-05-01", desktop: 165, mobile: 220 },
{ date: "2024-05-02", desktop: 293, mobile: 310 },
{ date: "2024-05-03", desktop: 247, mobile: 190 },
{ date: "2024-05-04", desktop: 385, mobile: 420 },
{ date: "2024-05-05", desktop: 481, mobile: 390 },
{ date: "2024-05-06", desktop: 498, mobile: 520 },
{ date: "2024-05-07", desktop: 388, mobile: 300 },
{ date: "2024-05-08", desktop: 149, mobile: 210 },
{ date: "2024-05-09", desktop: 227, mobile: 180 },
{ date: "2024-05-10", desktop: 293, mobile: 330 },
{ date: "2024-05-11", desktop: 335, mobile: 270 },
{ date: "2024-05-12", desktop: 197, mobile: 240 },
{ date: "2024-05-13", desktop: 197, mobile: 160 },
{ date: "2024-05-14", desktop: 448, mobile: 490 },
{ date: "2024-05-15", desktop: 473, mobile: 380 },
{ date: "2024-05-16", desktop: 338, mobile: 400 },
{ date: "2024-05-17", desktop: 499, mobile: 420 },
{ date: "2024-05-18", desktop: 315, mobile: 350 },
{ date: "2024-05-19", desktop: 235, mobile: 180 },
{ date: "2024-05-20", desktop: 177, mobile: 230 },
{ date: "2024-05-21", desktop: 82, mobile: 140 },
{ date: "2024-05-22", desktop: 81, mobile: 120 },
{ date: "2024-05-23", desktop: 252, mobile: 290 },
{ date: "2024-05-24", desktop: 294, mobile: 220 },
{ date: "2024-05-25", desktop: 201, mobile: 250 },
{ date: "2024-05-26", desktop: 213, mobile: 170 },
{ date: "2024-05-27", desktop: 420, mobile: 460 },
{ date: "2024-05-28", desktop: 233, mobile: 190 },
{ date: "2024-05-29", desktop: 78, mobile: 130 },
{ date: "2024-05-30", desktop: 340, mobile: 280 },
{ date: "2024-05-31", desktop: 178, mobile: 230 },
{ date: "2024-06-01", desktop: 178, mobile: 200 },
{ date: "2024-06-02", desktop: 470, mobile: 410 },
{ date: "2024-06-03", desktop: 103, mobile: 160 },
{ date: "2024-06-04", desktop: 439, mobile: 380 },
{ date: "2024-06-05", desktop: 88, mobile: 140 },
{ date: "2024-06-06", desktop: 294, mobile: 250 },
{ date: "2024-06-07", desktop: 323, mobile: 370 },
{ date: "2024-06-08", desktop: 385, mobile: 320 },
{ date: "2024-06-09", desktop: 438, mobile: 480 },
{ date: "2024-06-10", desktop: 155, mobile: 200 },
{ date: "2024-06-11", desktop: 92, mobile: 150 },
{ date: "2024-06-12", desktop: 492, mobile: 420 },
{ date: "2024-06-13", desktop: 81, mobile: 130 },
{ date: "2024-06-14", desktop: 426, mobile: 380 },
{ date: "2024-06-15", desktop: 307, mobile: 350 },
{ date: "2024-06-16", desktop: 371, mobile: 310 },
{ date: "2024-06-17", desktop: 475, mobile: 520 },
{ date: "2024-06-18", desktop: 107, mobile: 170 },
{ date: "2024-06-19", desktop: 341, mobile: 290 },
{ date: "2024-06-20", desktop: 408, mobile: 450 },
{ date: "2024-06-21", desktop: 169, mobile: 210 },
{ date: "2024-06-22", desktop: 317, mobile: 270 },
{ date: "2024-06-23", desktop: 480, mobile: 530 },
{ date: "2024-06-24", desktop: 132, mobile: 180 },
{ date: "2024-06-25", desktop: 141, mobile: 190 },
{ date: "2024-06-26", desktop: 434, mobile: 380 },
{ date: "2024-06-27", desktop: 448, mobile: 490 },
{ date: "2024-06-28", desktop: 149, mobile: 200 },
{ date: "2024-06-29", desktop: 103, mobile: 160 },
{ date: "2024-06-30", desktop: 446, mobile: 400 },
]
data = {
labels: chartData.map(d => d.date),
datasets: [{
label: 'Desktop',
data: chartData.map(d => d.desktop),
backgroundColor: themeColors.chart2,
borderWidth: 0,
borderRadius: 0, // Stacking usually looks better without individual radius
}, {
label: 'Mobile',
data: chartData.map(d => d.mobile),
backgroundColor: themeColors.chart1,
borderWidth: 0,
borderRadius: 0, // Stacking usually looks better without individual radius
}]
};
ctx = document.getElementById('chart-bar-interactive');
// Restore helper function to format date for labels/tooltips
const formatDateLabel = (dateString) => {
try {
const date = new Date(dateString + 'T00:00:00');
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); // e.g., "Apr 1"
} catch (e) {
return dateString; // Fallback
}
};
// Restore data preparation with formatted date labels
data = {
labels: chartData.map(d => formatDateLabel(d.date)),
datasets: [{
label: 'Desktop',
data: chartData.map(d => d.desktop),
backgroundColor: themeColors.chart4,
borderWidth: 0,
borderRadius: 0,
}, {
label: 'Mobile',
data: chartData.map(d => d.mobile),
backgroundColor: themeColors.chart5,
borderWidth: 0,
borderRadius: 0,
}]
};
config = {
type: 'bar',
data: data,
options: {
interaction: {
mode: 'index',
axis: 'x',
intersect: false
},
scales: {
y: {
stacked: true,
border: { display: false },
grid: {
color: setAlpha(themeColors.border, 0.5),
lineWidth: 1
},
ticks: { display: false }
},
x: {
stacked: true,
grid: { display: false },
ticks: {
color: themeColors.mutedForeground,
callback: function(value, index, ticks) {
const label = this.getLabelForValue(value);
return index % 7 === 0 ? label : null;
},
maxRotation: 0,
minRotation: 0
}
}
},
plugins: {
legend: {
display: false,
},
tooltip: {
enabled: false,
position: 'nearest',
external: externalTooltipHandler,
}
}
}
};
new Chart(ctx, config);
// Same chart but with line chart
// We start off from the previous config and just change the type to line
ctx = document.getElementById('chart-line-interactive');
// Prepare data with formatted date labels AND offset for Mobile
const desktopData = chartData.map(d => d.desktop + 400); // Store raw desktop data
const mobileDataOffset = chartData.map(d => d.mobile); // Store offset mobile data
// Prepare data with gradient background
data = {
labels: chartData.map(d => formatDateLabel(d.date)),
datasets: [{
label: 'Desktop',
data: desktopData,
borderColor: themeColors.chart1,
// Make backgroundColor scriptable
backgroundColor: function(context) {
const chart = context.chart;
const { ctx, chartArea } = chart;
// Use the helper function defined above
return createGradient(ctx, chartArea, themeColors.chart1);
},
borderWidth: 1,
pointRadius: 0,
pointHoverRadius: 4,
pointHoverBackgroundColor: themeColors.chart1,
pointHoverBorderColor: themeColors.chart1,
fill: 'start' // Fill from the start (usually y=0 or axis baseline)
}, {
label: 'Mobile (Offset)', // Adjusted label
data: mobileDataOffset,
borderColor: themeColors.chart2,
// Make backgroundColor scriptable
backgroundColor: function(context) {
const chart = context.chart;
const { ctx, chartArea } = chart;
// Use the helper function defined above
return createGradient(ctx, chartArea, themeColors.chart2);
},
borderWidth: 1,
pointRadius: 0,
pointHoverRadius: 4,
pointHoverBackgroundColor: themeColors.chart2,
pointHoverBorderColor: themeColors.chart2,
fill: 'start'
}]
};
// Add the createGradient helper function before the data object
function createGradient(ctx, chartArea, color) {
if (!chartArea) {
return setAlpha(color, 0.1); // Fallback during initial setup
}
const gradient = ctx.createLinearGradient(0, chartArea.top, 0, chartArea.bottom);
gradient.addColorStop(0, setAlpha(color, 0.5)); // Start color at top
gradient.addColorStop(1, setAlpha(color, 0)); // End color (transparent) at bottom
return gradient;
}
// Calculate min/max for Y-axis scaling using the PLOTTED data
const allPlottedValues = [...desktopData, ...mobileDataOffset]; // Combine actual plotted values
const dataMin = Math.min(...allPlottedValues);
const dataMax = Math.max(...allPlottedValues);
const yPadding = (dataMax - dataMin) * 0.1; // Add 10% padding
config = {
type: 'line',
data: data,
options: {
maintainAspectRatio: false,
aspectRatio: 1,
tension: 0.4, // Keep tension from original line config
interaction: {
mode: 'index', // Use 'index' mode for better hover on multiple lines
axis: 'x',
intersect: false
},
scales: {
y: {
border: { display: false },
grid: {
color: setAlpha(themeColors.border, 0.5),
lineWidth: 1
},
ticks: { display: false },
// Set min and max for Y-axis
min: Math.max(0, dataMin - yPadding), // Ensure min is not negative
max: dataMax + yPadding
},
x: {
grid: { display: false },
ticks: {
color: themeColors.mutedForeground,
// Restore callback to reduce ticks
callback: function(value, index, ticks) {
const label = this.getLabelForValue(value);
return index % 7 === 0 ? label : null;
},
maxRotation: 0,
minRotation: 0
}
}
},
plugins: {
legend: { display: false },
tooltip: {
enabled: false,
position: 'nearest',
external: externalTooltipHandler,
}
}
}
};
new Chart(ctx, config);
const chartDataDoughnut = [
{ browser: "chrome", visitors: 275, fill: themeColors.chart1 }, // Use resolved colors
{ browser: "safari", visitors: 200, fill: themeColors.chart2 },
{ browser: "firefox", visitors: 287, fill: themeColors.chart3 },
{ browser: "edge", visitors: 173, fill: themeColors.chart4 },
{ browser: "other", visitors: 190, fill: themeColors.chart5 },
]
const totalVisitors = chartDataDoughnut.reduce((sum, item) => sum + item.visitors, 0);
// Plugin to draw text in the center
const centerTextPlugin = {
id: 'centerText',
afterDraw: (chart) => {
const { ctx, chartArea } = chart;
if (!chartArea) return;
const centerX = chartArea.left + (chartArea.right - chartArea.left) / 2;
const centerY = chartArea.top + (chartArea.bottom - chartArea.top) / 2;
ctx.save();
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = '1.5rem sans-serif'; // Adjust font size as needed
ctx.fillStyle = themeColors.foreground; // Use foreground color
ctx.fillText(totalVisitors.toLocaleString(), centerX, centerY); // Format number if desired
ctx.restore();
}
};
// Prepare data for the doughnut chart
const doughnutData = {
labels: chartDataDoughnut.map(d => d.browser),
datasets: [{
label: 'Visitors by Browser',
data: chartDataDoughnut.map(d => d.visitors),
backgroundColor: chartDataDoughnut.map(d => d.fill),
borderColor: themeColors.background, // Use background for border
borderWidth: 2, // Add border between segments
hoverBorderColor: themeColors.background,
hoverBorderWidth: 2
}]
};
// Configure the doughnut chart
const doughnutConfig = {
type: 'doughnut',
data: doughnutData,
options: {
cutout: '80%', // Adjust hole size
plugins: {
legend: {
display: false, // Hide default legend
},
tooltip: {
enabled: false, // Keep this false as we use external handler
position: 'nearest', // Keep this for external handler logic
external: externalTooltipHandler, // Use the existing handler
},
centerText: {} // Keep the custom plugin for center text
}
},
plugins: [centerTextPlugin] // Register the plugin
};
// Assume you have a canvas with id="chart-doughnut" in your HTML
const doughnutCtx = document.getElementById('chart-doughnut').getContext('2d');
new Chart(doughnutCtx, doughnutConfig);
</script>