scanfile/server/web/templui/components/chart/chart.templ
2025-06-03 15:44:56 +02:00

330 lines
9 KiB
Text

// templui component chart - version: main installed by templui v0.71.0
package chart
import "git.jmbit.de/jmb/scanfile/server/web/templui/utils"
type Variant string
const (
VariantBar Variant = "bar"
VariantLine Variant = "line"
VariantPie Variant = "pie"
VariantDoughnut Variant = "doughnut"
VariantRadar Variant = "radar"
)
type Dataset struct {
Label string `json:"label"`
Data []float64 `json:"data"`
BorderWidth int `json:"borderWidth,omitempty"`
BorderColor interface{} `json:"borderColor,omitempty"`
BackgroundColor interface{} `json:"backgroundColor,omitempty"`
Tension float64 `json:"tension,omitempty"`
Fill bool `json:"fill,omitempty"`
Stepped bool `json:"stepped,omitempty"`
}
type Options struct {
Responsive bool `json:"responsive,omitempty"`
Legend bool `json:"legend,omitempty"`
}
type Data struct {
Labels []string `json:"labels"`
Datasets []Dataset `json:"datasets"`
}
type Config struct {
Type Variant `json:"type"`
Data Data `json:"data"`
Options Options `json:"options,omitempty"`
ShowLegend bool `json:"showLegend,omitempty"`
ShowXAxis bool `json:"showXAxis"`
ShowYAxis bool `json:"showYAxis"`
ShowXLabels bool `json:"showXLabels"`
ShowYLabels bool `json:"showYLabels"`
ShowXGrid bool `json:"showXGrid"`
ShowYGrid bool `json:"showYGrid"`
Horizontal bool `json:"horizontal"`
Stacked bool `json:"stacked"`
}
// Erweiterung des Props um ID und Attributes
type Props struct {
ID string
Variant Variant
Data Data
Options Options
ShowLegend bool
ShowXAxis bool
ShowYAxis bool
ShowXLabels bool
ShowYLabels bool
ShowXGrid bool
ShowYGrid bool
Horizontal bool
Stacked bool
Class string
Attributes templ.Attributes
}
templ Chart(props ...Props) {
@Script()
{{ var p Props }}
if len(props) > 0 {
{{ p = props[0] }}
}
if p.ID == "" {
{{ p.ID = "chart-" + utils.RandomID() }}
}
{{ canvasId := p.ID + "-canvas" }}
{{ dataId := p.ID + "-data" }}
<div
id={ p.ID }
class={
utils.TwMerge(
"chart-container relative",
p.Class),
}
{ p.Attributes... }
>
<canvas id={ canvasId } data-chart-id={ dataId }></canvas>
</div>
{{
chartConfig := Config{
Type: p.Variant,
Data: p.Data,
Options: p.Options,
ShowLegend: p.ShowLegend,
ShowXAxis: p.ShowXAxis,
ShowYAxis: p.ShowYAxis,
ShowXLabels: p.ShowXLabels,
ShowYLabels: p.ShowYLabels,
ShowXGrid: p.ShowXGrid,
ShowYGrid: p.ShowYGrid,
Horizontal: p.Horizontal,
Stacked: p.Stacked,
}
}}
@templ.JSONScript(dataId, chartConfig)
}
templ Script() {
<script defer nonce={ templ.GetNonce(ctx) } src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
<script nonce={ templ.GetNonce(ctx) }>
window.chartInstances = window.chartInstances || {};
(function() { // IIFE
if (!window.chartScriptInitialized) {
function getThemeColors() {
const style = getComputedStyle(document.documentElement);
return {
foreground: style.getPropertyValue('--foreground').trim() || '#000',
background: style.getPropertyValue('--background').trim() || '#fff',
mutedForeground: style.getPropertyValue('--muted-foreground').trim() || '#666',
border: style.getPropertyValue('--border').trim() || '#ccc'
};
}
function initChart(canvas) {
if (!canvas || !canvas.id || !canvas.hasAttribute('data-chart-id')) return;
if (window.chartInstances[canvas.id]) {
cleanupChart(canvas);
}
const dataId = canvas.getAttribute('data-chart-id');
const dataElement = document.getElementById(dataId);
if (!dataElement) return;
try {
const chartConfig = JSON.parse(dataElement.textContent);
const colors = getThemeColors();
Chart.defaults.elements.point.radius = 0;
Chart.defaults.elements.point.hoverRadius = 5;
const isComplexChart = ["pie", "doughnut", "bar", "radar"].includes(chartConfig.type);
const legendOptions = {
display: chartConfig.showLegend || false,
labels: { color: colors.foreground }
};
const tooltipOptions = {
backgroundColor: colors.background,
bodyColor: colors.mutedForeground,
titleColor: colors.foreground,
borderColor: colors.border,
borderWidth: 1
};
const scalesOptions = chartConfig.type === "radar"
? {
r: {
grid: {
color: colors.border,
display: chartConfig.showYGrid !== false
},
ticks: {
color: colors.mutedForeground,
backdropColor: "transparent",
display: chartConfig.showYLabels !== false
},
angleLines: {
color: colors.border,
display: chartConfig.showXGrid !== false
},
pointLabels: {
color: colors.foreground,
font: { size: 12 }
},
border: {
display: chartConfig.showYAxis !== false,
color: colors.border
},
beginAtZero: true
}
}
: {
x: {
beginAtZero: true,
display: chartConfig.showXLabels !== false ||
chartConfig.showXGrid !== false ||
chartConfig.showXAxis !== false,
border: {
display: chartConfig.showXAxis !== false,
color: colors.border
},
ticks: {
display: chartConfig.showXLabels !== false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showXGrid !== false,
color: colors.border
},
stacked: chartConfig.stacked || false
},
y: {
offset: true,
beginAtZero: true,
display: chartConfig.showYLabels !== false ||
chartConfig.showYGrid !== false ||
chartConfig.showYAxis !== false,
border: {
display: chartConfig.showYAxis !== false,
color: colors.border
},
ticks: {
display: chartConfig.showYLabels !== false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showYGrid !== false,
color: colors.border
},
stacked: chartConfig.stacked || false
}
};
const finalChartConfig = {
...chartConfig,
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
intersect: isComplexChart ? true : false,
axis: "xy",
mode: isComplexChart ? "nearest" : "index"
},
indexAxis: chartConfig.horizontal ? "y" : "x",
plugins: {
legend: legendOptions,
tooltip: tooltipOptions
},
scales: scalesOptions
}
};
window.chartInstances[canvas.id] = new Chart(canvas, finalChartConfig);
} catch (e) {}
}
function cleanupChart(canvas) {
if (!canvas || !canvas.id || !window.chartInstances[canvas.id]) return;
try {
window.chartInstances[canvas.id].destroy();
} finally {
delete window.chartInstances[canvas.id];
}
}
function initAllComponents(root = document) {
if (typeof Chart === "undefined") return;
for (const canvas of root.querySelectorAll("canvas[data-chart-id]")) {
initChart(canvas);
}
}
function waitForChartAndInit() {
if (typeof Chart !== "undefined") {
initAllComponents();
} else {
setTimeout(waitForChartAndInit, 100);
}
}
document.addEventListener("DOMContentLoaded", waitForChartAndInit);
document.body.addEventListener("htmx:beforeSwap", (event) => {
const el = event.detail.target || event.detail.elt;;
if (el instanceof Element) {
for (const canvas of el.querySelectorAll("canvas[data-chart-id]")) {
cleanupChart(canvas);
}
if (el.matches("canvas[data-chart-id]")) {
cleanupChart(el);
}
}
});
document.body.addEventListener("htmx:afterSwap", (event) => {
const target = event.detail.target || event.detail.elt;;
if (target instanceof Element) {
function tryInit(attempt = 1) {
if (typeof Chart !== "undefined") {
initAllComponents(target);
} else if (attempt < 10) {
setTimeout(() => tryInit(attempt + 1), 100);
}
}
tryInit();
}
});
const observer = new MutationObserver(() => {
let timeout;
clearTimeout(timeout);
timeout = setTimeout(() => {
for (const canvas of document.querySelectorAll("canvas[data-chart-id]")) {
if (window.chartInstances[canvas.id]) {
cleanupChart(canvas);
initChart(canvas);
}
}
}, 50);
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class", "style"]
});
window.chartScriptInitialized = true;
}
})(); // End of IIFE
</script>
}