mirror of
https://github.com/hunvreus/basecoat.git
synced 2026-11-11 04:38:18 +00:00
Adding missing partials
This commit is contained in:
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user