330 lines
9 KiB
Text
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>
|
|
}
|