package showcase
import "github.com/axzilla/templui/components"
templ ChartDefault() {
@components.Card(components.CardProps{Class: "max-w-sm"}) {
@components.CardContent() {
@components.Chart(components.ChartProps{
Variant: components.ChartVariantBar,
ShowYGrid: true,
ShowXLabels: true,
Data: components.ChartData{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []components.ChartDataset{
{
Data: []float64{12, 19, 12, 5, 2, 3},
},
},
},
})
}
}
}
package components
import "github.com/axzilla/templui/utils"
type ChartVariant string
const (
ChartVariantBar ChartVariant = "bar"
ChartVariantLine ChartVariant = "line"
ChartVariantPie ChartVariant = "pie"
ChartVariantDoughnut ChartVariant = "doughnut"
ChartVariantRadar ChartVariant = "radar"
)
type ChartDataset struct {
Label string `json:"label"`
Data []float64 `json:"data"`
BorderWidth int `json:"borderWidth,omitempty"`
BorderColor string `json:"borderColor,omitempty"`
BackgroundColor string `json:"backgroundColor,omitempty"`
Tension float64 `json:"tension,omitempty"`
Fill bool `json:"fill,omitempty"`
Stepped bool `json:"stepped,omitempty"`
}
type ChartOptions struct {
Responsive bool `json:"responsive,omitempty"`
Legend bool `json:"legend,omitempty"`
}
type ChartData struct {
Labels []string `json:"labels"`
Datasets []ChartDataset `json:"datasets"`
}
type ChartConfig struct {
Type ChartVariant `json:"type"`
Data ChartData `json:"data"`
Options ChartOptions `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 ChartProps um ID und Attributes
type ChartProps struct {
ID string
Variant ChartVariant
Data ChartData
Options ChartOptions
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 ...ChartProps) {
{{ var p ChartProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
// Wenn keine ID gesetzt, generiere eine zufällige
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 := ChartConfig{
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 ChartScripts() {
{{ handle := templ.NewOnceHandle() }}
@handle.Once() {
<!-- Load Chart.js library -->
<script defer nonce={ templ.GetNonce(ctx) } src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
<!-- Initialize charts -->
<script nonce={ templ.GetNonce(ctx) }>
document.addEventListener('DOMContentLoaded', function() {
const chartInstances = {};
function getThemeColors() {
const computedStyle = getComputedStyle(document.documentElement);
return {
foreground: computedStyle.getPropertyValue('--foreground').trim(),
background: computedStyle.getPropertyValue('--background').trim(),
mutedForeground: computedStyle.getPropertyValue('--muted-foreground').trim(),
border: computedStyle.getPropertyValue('--border').trim()
};
}
function initCharts() {
const colors = getThemeColors();
document.querySelectorAll('.chart-container').forEach(container => {
const canvas = container.querySelector('canvas');
if (!canvas) return;
const dataId = canvas.getAttribute('data-chart-id');
const dataElement = document.getElementById(dataId);
if (!dataElement) return;
if (chartInstances[canvas.id]) {
chartInstances[canvas.id].destroy();
}
const chartConfig = JSON.parse(dataElement.textContent);
Chart.defaults.elements.point.radius = 0;
Chart.defaults.elements.point.hoverRadius = 5;
const chart = new Chart(canvas, {
...chartConfig,
options: {
interaction: {
intersect: false,
axis: "xy",
mode: chartConfig.type === "pie" ||
chartConfig.type === "doughnut" ||
chartConfig.type === "bar" ||
chartConfig.type === "radar" ? "nearest" : "index"
},
indexAxis: chartConfig.horizontal ? "y" : "x",
plugins: {
legend: {
display: chartConfig.showLegend || false,
labels: {
color: colors.foreground
}
},
tooltip: {
backgroundColor: colors.background,
bodyColor: colors.mutedForeground,
titleColor: colors.foreground,
borderColor: colors.border,
borderWidth: 1
}
},
scales: chartConfig.type === "radar" ? {
r: {
grid: {
color: colors.border,
display: chartConfig.showYGrid || true
},
ticks: {
color: colors.mutedForeground,
backdropColor: 'transparent',
display: chartConfig.showYLabels || true
},
angleLines: {
color: colors.border,
display: chartConfig.showXGrid || true
},
pointLabels: {
color: colors.foreground,
font: {
size: 12
}
},
border: {
display: chartConfig.showYAxis || false,
color: colors.border
},
beginAtZero: true
}
} : {
x: {
beginAtZero: true,
display: chartConfig.showXLabels || chartConfig.showXGrid || chartConfig.showXAxis,
border: {
display: chartConfig.showXAxis || false
},
ticks: {
display: chartConfig.showXLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showXGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
},
y: {
offset: true,
beginAtZero: true,
display: chartConfig.showYLabels || chartConfig.showYGrid || chartConfig.showYAxis,
border: {
display: chartConfig.showYAxis || false
},
ticks: {
display: chartConfig.showYLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showYGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
}
}
}
});
chartInstances[canvas.id] = chart;
});
}
initCharts();
window.addEventListener('resize', function() {
for (let id in chartInstances) {
chartInstances[id].resize();
}
});
const observer = new MutationObserver(() => {
setTimeout(initCharts, 50);
});
observer.observe(document.documentElement, { attributes: true });
document.addEventListener('alpine:initialized', () => {
document.addEventListener('theme-changed', initCharts);
});
});
</script>
}
}
Usage
1. Add the script to your page/layout:
// Option A: All components (recommended)
@utils.ComponentScripts()
// Option B: Just Chart
@components.ChartScript()
2. Use the component:
@components.Chart(components.ChartProps{...})
Examples
Bar Chart - Multiple
package showcase
import "github.com/axzilla/templui/components"
templ ChartBarMultiple() {
@components.Card(components.CardProps{Class: "max-w-sm"}) {
@components.CardContent() {
@components.Chart(components.ChartProps{
Variant: components.ChartVariantBar,
ShowYGrid: true,
ShowXLabels: true,
Data: components.ChartData{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []components.ChartDataset{
{
Label: "Mobile",
Data: []float64{12, 19, 12, 5, 2, 3},
},
{
Label: "Desktop",
Data: []float64{3, 9, 18, 3, 21, 13},
},
},
},
})
}
}
}
package components
import "github.com/axzilla/templui/utils"
type ChartVariant string
const (
ChartVariantBar ChartVariant = "bar"
ChartVariantLine ChartVariant = "line"
ChartVariantPie ChartVariant = "pie"
ChartVariantDoughnut ChartVariant = "doughnut"
ChartVariantRadar ChartVariant = "radar"
)
type ChartDataset struct {
Label string `json:"label"`
Data []float64 `json:"data"`
BorderWidth int `json:"borderWidth,omitempty"`
BorderColor string `json:"borderColor,omitempty"`
BackgroundColor string `json:"backgroundColor,omitempty"`
Tension float64 `json:"tension,omitempty"`
Fill bool `json:"fill,omitempty"`
Stepped bool `json:"stepped,omitempty"`
}
type ChartOptions struct {
Responsive bool `json:"responsive,omitempty"`
Legend bool `json:"legend,omitempty"`
}
type ChartData struct {
Labels []string `json:"labels"`
Datasets []ChartDataset `json:"datasets"`
}
type ChartConfig struct {
Type ChartVariant `json:"type"`
Data ChartData `json:"data"`
Options ChartOptions `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 ChartProps um ID und Attributes
type ChartProps struct {
ID string
Variant ChartVariant
Data ChartData
Options ChartOptions
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 ...ChartProps) {
{{ var p ChartProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
// Wenn keine ID gesetzt, generiere eine zufällige
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 := ChartConfig{
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 ChartScripts() {
{{ handle := templ.NewOnceHandle() }}
@handle.Once() {
<!-- Load Chart.js library -->
<script defer nonce={ templ.GetNonce(ctx) } src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
<!-- Initialize charts -->
<script nonce={ templ.GetNonce(ctx) }>
document.addEventListener('DOMContentLoaded', function() {
const chartInstances = {};
function getThemeColors() {
const computedStyle = getComputedStyle(document.documentElement);
return {
foreground: computedStyle.getPropertyValue('--foreground').trim(),
background: computedStyle.getPropertyValue('--background').trim(),
mutedForeground: computedStyle.getPropertyValue('--muted-foreground').trim(),
border: computedStyle.getPropertyValue('--border').trim()
};
}
function initCharts() {
const colors = getThemeColors();
document.querySelectorAll('.chart-container').forEach(container => {
const canvas = container.querySelector('canvas');
if (!canvas) return;
const dataId = canvas.getAttribute('data-chart-id');
const dataElement = document.getElementById(dataId);
if (!dataElement) return;
if (chartInstances[canvas.id]) {
chartInstances[canvas.id].destroy();
}
const chartConfig = JSON.parse(dataElement.textContent);
Chart.defaults.elements.point.radius = 0;
Chart.defaults.elements.point.hoverRadius = 5;
const chart = new Chart(canvas, {
...chartConfig,
options: {
interaction: {
intersect: false,
axis: "xy",
mode: chartConfig.type === "pie" ||
chartConfig.type === "doughnut" ||
chartConfig.type === "bar" ||
chartConfig.type === "radar" ? "nearest" : "index"
},
indexAxis: chartConfig.horizontal ? "y" : "x",
plugins: {
legend: {
display: chartConfig.showLegend || false,
labels: {
color: colors.foreground
}
},
tooltip: {
backgroundColor: colors.background,
bodyColor: colors.mutedForeground,
titleColor: colors.foreground,
borderColor: colors.border,
borderWidth: 1
}
},
scales: chartConfig.type === "radar" ? {
r: {
grid: {
color: colors.border,
display: chartConfig.showYGrid || true
},
ticks: {
color: colors.mutedForeground,
backdropColor: 'transparent',
display: chartConfig.showYLabels || true
},
angleLines: {
color: colors.border,
display: chartConfig.showXGrid || true
},
pointLabels: {
color: colors.foreground,
font: {
size: 12
}
},
border: {
display: chartConfig.showYAxis || false,
color: colors.border
},
beginAtZero: true
}
} : {
x: {
beginAtZero: true,
display: chartConfig.showXLabels || chartConfig.showXGrid || chartConfig.showXAxis,
border: {
display: chartConfig.showXAxis || false
},
ticks: {
display: chartConfig.showXLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showXGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
},
y: {
offset: true,
beginAtZero: true,
display: chartConfig.showYLabels || chartConfig.showYGrid || chartConfig.showYAxis,
border: {
display: chartConfig.showYAxis || false
},
ticks: {
display: chartConfig.showYLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showYGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
}
}
}
});
chartInstances[canvas.id] = chart;
});
}
initCharts();
window.addEventListener('resize', function() {
for (let id in chartInstances) {
chartInstances[id].resize();
}
});
const observer = new MutationObserver(() => {
setTimeout(initCharts, 50);
});
observer.observe(document.documentElement, { attributes: true });
document.addEventListener('alpine:initialized', () => {
document.addEventListener('theme-changed', initCharts);
});
});
</script>
}
}
Bar Chart - Horizontal
package showcase
import "github.com/axzilla/templui/components"
templ ChartBarHorizontal() {
@components.Card(components.CardProps{Class: "max-w-sm"}) {
@components.CardContent() {
@components.Chart(components.ChartProps{
Variant: components.ChartVariantBar,
Horizontal: true,
ShowXGrid: true,
ShowYLabels: true,
Data: components.ChartData{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []components.ChartDataset{
{
Data: []float64{12, 19, 12, 5, 2, 3},
},
},
},
})
}
}
}
package components
import "github.com/axzilla/templui/utils"
type ChartVariant string
const (
ChartVariantBar ChartVariant = "bar"
ChartVariantLine ChartVariant = "line"
ChartVariantPie ChartVariant = "pie"
ChartVariantDoughnut ChartVariant = "doughnut"
ChartVariantRadar ChartVariant = "radar"
)
type ChartDataset struct {
Label string `json:"label"`
Data []float64 `json:"data"`
BorderWidth int `json:"borderWidth,omitempty"`
BorderColor string `json:"borderColor,omitempty"`
BackgroundColor string `json:"backgroundColor,omitempty"`
Tension float64 `json:"tension,omitempty"`
Fill bool `json:"fill,omitempty"`
Stepped bool `json:"stepped,omitempty"`
}
type ChartOptions struct {
Responsive bool `json:"responsive,omitempty"`
Legend bool `json:"legend,omitempty"`
}
type ChartData struct {
Labels []string `json:"labels"`
Datasets []ChartDataset `json:"datasets"`
}
type ChartConfig struct {
Type ChartVariant `json:"type"`
Data ChartData `json:"data"`
Options ChartOptions `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 ChartProps um ID und Attributes
type ChartProps struct {
ID string
Variant ChartVariant
Data ChartData
Options ChartOptions
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 ...ChartProps) {
{{ var p ChartProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
// Wenn keine ID gesetzt, generiere eine zufällige
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 := ChartConfig{
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 ChartScripts() {
{{ handle := templ.NewOnceHandle() }}
@handle.Once() {
<!-- Load Chart.js library -->
<script defer nonce={ templ.GetNonce(ctx) } src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
<!-- Initialize charts -->
<script nonce={ templ.GetNonce(ctx) }>
document.addEventListener('DOMContentLoaded', function() {
const chartInstances = {};
function getThemeColors() {
const computedStyle = getComputedStyle(document.documentElement);
return {
foreground: computedStyle.getPropertyValue('--foreground').trim(),
background: computedStyle.getPropertyValue('--background').trim(),
mutedForeground: computedStyle.getPropertyValue('--muted-foreground').trim(),
border: computedStyle.getPropertyValue('--border').trim()
};
}
function initCharts() {
const colors = getThemeColors();
document.querySelectorAll('.chart-container').forEach(container => {
const canvas = container.querySelector('canvas');
if (!canvas) return;
const dataId = canvas.getAttribute('data-chart-id');
const dataElement = document.getElementById(dataId);
if (!dataElement) return;
if (chartInstances[canvas.id]) {
chartInstances[canvas.id].destroy();
}
const chartConfig = JSON.parse(dataElement.textContent);
Chart.defaults.elements.point.radius = 0;
Chart.defaults.elements.point.hoverRadius = 5;
const chart = new Chart(canvas, {
...chartConfig,
options: {
interaction: {
intersect: false,
axis: "xy",
mode: chartConfig.type === "pie" ||
chartConfig.type === "doughnut" ||
chartConfig.type === "bar" ||
chartConfig.type === "radar" ? "nearest" : "index"
},
indexAxis: chartConfig.horizontal ? "y" : "x",
plugins: {
legend: {
display: chartConfig.showLegend || false,
labels: {
color: colors.foreground
}
},
tooltip: {
backgroundColor: colors.background,
bodyColor: colors.mutedForeground,
titleColor: colors.foreground,
borderColor: colors.border,
borderWidth: 1
}
},
scales: chartConfig.type === "radar" ? {
r: {
grid: {
color: colors.border,
display: chartConfig.showYGrid || true
},
ticks: {
color: colors.mutedForeground,
backdropColor: 'transparent',
display: chartConfig.showYLabels || true
},
angleLines: {
color: colors.border,
display: chartConfig.showXGrid || true
},
pointLabels: {
color: colors.foreground,
font: {
size: 12
}
},
border: {
display: chartConfig.showYAxis || false,
color: colors.border
},
beginAtZero: true
}
} : {
x: {
beginAtZero: true,
display: chartConfig.showXLabels || chartConfig.showXGrid || chartConfig.showXAxis,
border: {
display: chartConfig.showXAxis || false
},
ticks: {
display: chartConfig.showXLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showXGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
},
y: {
offset: true,
beginAtZero: true,
display: chartConfig.showYLabels || chartConfig.showYGrid || chartConfig.showYAxis,
border: {
display: chartConfig.showYAxis || false
},
ticks: {
display: chartConfig.showYLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showYGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
}
}
}
});
chartInstances[canvas.id] = chart;
});
}
initCharts();
window.addEventListener('resize', function() {
for (let id in chartInstances) {
chartInstances[id].resize();
}
});
const observer = new MutationObserver(() => {
setTimeout(initCharts, 50);
});
observer.observe(document.documentElement, { attributes: true });
document.addEventListener('alpine:initialized', () => {
document.addEventListener('theme-changed', initCharts);
});
});
</script>
}
}
Bar Chart - Negative
package showcase
import "github.com/axzilla/templui/components"
templ ChartBarNegative() {
@components.Card(components.CardProps{Class: "max-w-sm"}) {
@components.CardContent() {
@components.Chart(components.ChartProps{
Variant: components.ChartVariantBar,
ShowYGrid: true,
ShowXLabels: true,
Data: components.ChartData{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []components.ChartDataset{
{
Data: []float64{12, 19, -12, 5, -2, 3},
},
},
},
})
}
}
}
package components
import "github.com/axzilla/templui/utils"
type ChartVariant string
const (
ChartVariantBar ChartVariant = "bar"
ChartVariantLine ChartVariant = "line"
ChartVariantPie ChartVariant = "pie"
ChartVariantDoughnut ChartVariant = "doughnut"
ChartVariantRadar ChartVariant = "radar"
)
type ChartDataset struct {
Label string `json:"label"`
Data []float64 `json:"data"`
BorderWidth int `json:"borderWidth,omitempty"`
BorderColor string `json:"borderColor,omitempty"`
BackgroundColor string `json:"backgroundColor,omitempty"`
Tension float64 `json:"tension,omitempty"`
Fill bool `json:"fill,omitempty"`
Stepped bool `json:"stepped,omitempty"`
}
type ChartOptions struct {
Responsive bool `json:"responsive,omitempty"`
Legend bool `json:"legend,omitempty"`
}
type ChartData struct {
Labels []string `json:"labels"`
Datasets []ChartDataset `json:"datasets"`
}
type ChartConfig struct {
Type ChartVariant `json:"type"`
Data ChartData `json:"data"`
Options ChartOptions `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 ChartProps um ID und Attributes
type ChartProps struct {
ID string
Variant ChartVariant
Data ChartData
Options ChartOptions
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 ...ChartProps) {
{{ var p ChartProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
// Wenn keine ID gesetzt, generiere eine zufällige
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 := ChartConfig{
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 ChartScripts() {
{{ handle := templ.NewOnceHandle() }}
@handle.Once() {
<!-- Load Chart.js library -->
<script defer nonce={ templ.GetNonce(ctx) } src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
<!-- Initialize charts -->
<script nonce={ templ.GetNonce(ctx) }>
document.addEventListener('DOMContentLoaded', function() {
const chartInstances = {};
function getThemeColors() {
const computedStyle = getComputedStyle(document.documentElement);
return {
foreground: computedStyle.getPropertyValue('--foreground').trim(),
background: computedStyle.getPropertyValue('--background').trim(),
mutedForeground: computedStyle.getPropertyValue('--muted-foreground').trim(),
border: computedStyle.getPropertyValue('--border').trim()
};
}
function initCharts() {
const colors = getThemeColors();
document.querySelectorAll('.chart-container').forEach(container => {
const canvas = container.querySelector('canvas');
if (!canvas) return;
const dataId = canvas.getAttribute('data-chart-id');
const dataElement = document.getElementById(dataId);
if (!dataElement) return;
if (chartInstances[canvas.id]) {
chartInstances[canvas.id].destroy();
}
const chartConfig = JSON.parse(dataElement.textContent);
Chart.defaults.elements.point.radius = 0;
Chart.defaults.elements.point.hoverRadius = 5;
const chart = new Chart(canvas, {
...chartConfig,
options: {
interaction: {
intersect: false,
axis: "xy",
mode: chartConfig.type === "pie" ||
chartConfig.type === "doughnut" ||
chartConfig.type === "bar" ||
chartConfig.type === "radar" ? "nearest" : "index"
},
indexAxis: chartConfig.horizontal ? "y" : "x",
plugins: {
legend: {
display: chartConfig.showLegend || false,
labels: {
color: colors.foreground
}
},
tooltip: {
backgroundColor: colors.background,
bodyColor: colors.mutedForeground,
titleColor: colors.foreground,
borderColor: colors.border,
borderWidth: 1
}
},
scales: chartConfig.type === "radar" ? {
r: {
grid: {
color: colors.border,
display: chartConfig.showYGrid || true
},
ticks: {
color: colors.mutedForeground,
backdropColor: 'transparent',
display: chartConfig.showYLabels || true
},
angleLines: {
color: colors.border,
display: chartConfig.showXGrid || true
},
pointLabels: {
color: colors.foreground,
font: {
size: 12
}
},
border: {
display: chartConfig.showYAxis || false,
color: colors.border
},
beginAtZero: true
}
} : {
x: {
beginAtZero: true,
display: chartConfig.showXLabels || chartConfig.showXGrid || chartConfig.showXAxis,
border: {
display: chartConfig.showXAxis || false
},
ticks: {
display: chartConfig.showXLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showXGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
},
y: {
offset: true,
beginAtZero: true,
display: chartConfig.showYLabels || chartConfig.showYGrid || chartConfig.showYAxis,
border: {
display: chartConfig.showYAxis || false
},
ticks: {
display: chartConfig.showYLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showYGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
}
}
}
});
chartInstances[canvas.id] = chart;
});
}
initCharts();
window.addEventListener('resize', function() {
for (let id in chartInstances) {
chartInstances[id].resize();
}
});
const observer = new MutationObserver(() => {
setTimeout(initCharts, 50);
});
observer.observe(document.documentElement, { attributes: true });
document.addEventListener('alpine:initialized', () => {
document.addEventListener('theme-changed', initCharts);
});
});
</script>
}
}
Bar Chart - Stacked
package showcase
import "github.com/axzilla/templui/components"
templ ChartBarStacked() {
@components.Card(components.CardProps{Class: "max-w-sm"}) {
@components.CardContent() {
@components.Chart(components.ChartProps{
Variant: components.ChartVariantBar,
ShowYGrid: true,
ShowXLabels: true,
Stacked: true,
Data: components.ChartData{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []components.ChartDataset{
{
Label: "Mobile",
Data: []float64{12, 19, 12, 5, 2, 3},
},
{
Label: "Desktop",
Data: []float64{3, 9, 18, 3, 21, 13},
},
},
},
})
}
}
}
package components
import "github.com/axzilla/templui/utils"
type ChartVariant string
const (
ChartVariantBar ChartVariant = "bar"
ChartVariantLine ChartVariant = "line"
ChartVariantPie ChartVariant = "pie"
ChartVariantDoughnut ChartVariant = "doughnut"
ChartVariantRadar ChartVariant = "radar"
)
type ChartDataset struct {
Label string `json:"label"`
Data []float64 `json:"data"`
BorderWidth int `json:"borderWidth,omitempty"`
BorderColor string `json:"borderColor,omitempty"`
BackgroundColor string `json:"backgroundColor,omitempty"`
Tension float64 `json:"tension,omitempty"`
Fill bool `json:"fill,omitempty"`
Stepped bool `json:"stepped,omitempty"`
}
type ChartOptions struct {
Responsive bool `json:"responsive,omitempty"`
Legend bool `json:"legend,omitempty"`
}
type ChartData struct {
Labels []string `json:"labels"`
Datasets []ChartDataset `json:"datasets"`
}
type ChartConfig struct {
Type ChartVariant `json:"type"`
Data ChartData `json:"data"`
Options ChartOptions `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 ChartProps um ID und Attributes
type ChartProps struct {
ID string
Variant ChartVariant
Data ChartData
Options ChartOptions
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 ...ChartProps) {
{{ var p ChartProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
// Wenn keine ID gesetzt, generiere eine zufällige
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 := ChartConfig{
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 ChartScripts() {
{{ handle := templ.NewOnceHandle() }}
@handle.Once() {
<!-- Load Chart.js library -->
<script defer nonce={ templ.GetNonce(ctx) } src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
<!-- Initialize charts -->
<script nonce={ templ.GetNonce(ctx) }>
document.addEventListener('DOMContentLoaded', function() {
const chartInstances = {};
function getThemeColors() {
const computedStyle = getComputedStyle(document.documentElement);
return {
foreground: computedStyle.getPropertyValue('--foreground').trim(),
background: computedStyle.getPropertyValue('--background').trim(),
mutedForeground: computedStyle.getPropertyValue('--muted-foreground').trim(),
border: computedStyle.getPropertyValue('--border').trim()
};
}
function initCharts() {
const colors = getThemeColors();
document.querySelectorAll('.chart-container').forEach(container => {
const canvas = container.querySelector('canvas');
if (!canvas) return;
const dataId = canvas.getAttribute('data-chart-id');
const dataElement = document.getElementById(dataId);
if (!dataElement) return;
if (chartInstances[canvas.id]) {
chartInstances[canvas.id].destroy();
}
const chartConfig = JSON.parse(dataElement.textContent);
Chart.defaults.elements.point.radius = 0;
Chart.defaults.elements.point.hoverRadius = 5;
const chart = new Chart(canvas, {
...chartConfig,
options: {
interaction: {
intersect: false,
axis: "xy",
mode: chartConfig.type === "pie" ||
chartConfig.type === "doughnut" ||
chartConfig.type === "bar" ||
chartConfig.type === "radar" ? "nearest" : "index"
},
indexAxis: chartConfig.horizontal ? "y" : "x",
plugins: {
legend: {
display: chartConfig.showLegend || false,
labels: {
color: colors.foreground
}
},
tooltip: {
backgroundColor: colors.background,
bodyColor: colors.mutedForeground,
titleColor: colors.foreground,
borderColor: colors.border,
borderWidth: 1
}
},
scales: chartConfig.type === "radar" ? {
r: {
grid: {
color: colors.border,
display: chartConfig.showYGrid || true
},
ticks: {
color: colors.mutedForeground,
backdropColor: 'transparent',
display: chartConfig.showYLabels || true
},
angleLines: {
color: colors.border,
display: chartConfig.showXGrid || true
},
pointLabels: {
color: colors.foreground,
font: {
size: 12
}
},
border: {
display: chartConfig.showYAxis || false,
color: colors.border
},
beginAtZero: true
}
} : {
x: {
beginAtZero: true,
display: chartConfig.showXLabels || chartConfig.showXGrid || chartConfig.showXAxis,
border: {
display: chartConfig.showXAxis || false
},
ticks: {
display: chartConfig.showXLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showXGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
},
y: {
offset: true,
beginAtZero: true,
display: chartConfig.showYLabels || chartConfig.showYGrid || chartConfig.showYAxis,
border: {
display: chartConfig.showYAxis || false
},
ticks: {
display: chartConfig.showYLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showYGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
}
}
}
});
chartInstances[canvas.id] = chart;
});
}
initCharts();
window.addEventListener('resize', function() {
for (let id in chartInstances) {
chartInstances[id].resize();
}
});
const observer = new MutationObserver(() => {
setTimeout(initCharts, 50);
});
observer.observe(document.documentElement, { attributes: true });
document.addEventListener('alpine:initialized', () => {
document.addEventListener('theme-changed', initCharts);
});
});
</script>
}
}
Line Chart
package showcase
import "github.com/axzilla/templui/components"
templ ChartLine() {
@components.Card(components.CardProps{Class: "max-w-sm"}) {
@components.CardContent() {
@components.Chart(components.ChartProps{
Variant: components.ChartVariantLine,
ShowYGrid: true,
ShowXLabels: true,
Data: components.ChartData{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []components.ChartDataset{
{
Data: []float64{12, 3, 9, 3, 12, 7},
Tension: 0.5,
},
},
},
})
}
}
}
package components
import "github.com/axzilla/templui/utils"
type ChartVariant string
const (
ChartVariantBar ChartVariant = "bar"
ChartVariantLine ChartVariant = "line"
ChartVariantPie ChartVariant = "pie"
ChartVariantDoughnut ChartVariant = "doughnut"
ChartVariantRadar ChartVariant = "radar"
)
type ChartDataset struct {
Label string `json:"label"`
Data []float64 `json:"data"`
BorderWidth int `json:"borderWidth,omitempty"`
BorderColor string `json:"borderColor,omitempty"`
BackgroundColor string `json:"backgroundColor,omitempty"`
Tension float64 `json:"tension,omitempty"`
Fill bool `json:"fill,omitempty"`
Stepped bool `json:"stepped,omitempty"`
}
type ChartOptions struct {
Responsive bool `json:"responsive,omitempty"`
Legend bool `json:"legend,omitempty"`
}
type ChartData struct {
Labels []string `json:"labels"`
Datasets []ChartDataset `json:"datasets"`
}
type ChartConfig struct {
Type ChartVariant `json:"type"`
Data ChartData `json:"data"`
Options ChartOptions `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 ChartProps um ID und Attributes
type ChartProps struct {
ID string
Variant ChartVariant
Data ChartData
Options ChartOptions
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 ...ChartProps) {
{{ var p ChartProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
// Wenn keine ID gesetzt, generiere eine zufällige
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 := ChartConfig{
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 ChartScripts() {
{{ handle := templ.NewOnceHandle() }}
@handle.Once() {
<!-- Load Chart.js library -->
<script defer nonce={ templ.GetNonce(ctx) } src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
<!-- Initialize charts -->
<script nonce={ templ.GetNonce(ctx) }>
document.addEventListener('DOMContentLoaded', function() {
const chartInstances = {};
function getThemeColors() {
const computedStyle = getComputedStyle(document.documentElement);
return {
foreground: computedStyle.getPropertyValue('--foreground').trim(),
background: computedStyle.getPropertyValue('--background').trim(),
mutedForeground: computedStyle.getPropertyValue('--muted-foreground').trim(),
border: computedStyle.getPropertyValue('--border').trim()
};
}
function initCharts() {
const colors = getThemeColors();
document.querySelectorAll('.chart-container').forEach(container => {
const canvas = container.querySelector('canvas');
if (!canvas) return;
const dataId = canvas.getAttribute('data-chart-id');
const dataElement = document.getElementById(dataId);
if (!dataElement) return;
if (chartInstances[canvas.id]) {
chartInstances[canvas.id].destroy();
}
const chartConfig = JSON.parse(dataElement.textContent);
Chart.defaults.elements.point.radius = 0;
Chart.defaults.elements.point.hoverRadius = 5;
const chart = new Chart(canvas, {
...chartConfig,
options: {
interaction: {
intersect: false,
axis: "xy",
mode: chartConfig.type === "pie" ||
chartConfig.type === "doughnut" ||
chartConfig.type === "bar" ||
chartConfig.type === "radar" ? "nearest" : "index"
},
indexAxis: chartConfig.horizontal ? "y" : "x",
plugins: {
legend: {
display: chartConfig.showLegend || false,
labels: {
color: colors.foreground
}
},
tooltip: {
backgroundColor: colors.background,
bodyColor: colors.mutedForeground,
titleColor: colors.foreground,
borderColor: colors.border,
borderWidth: 1
}
},
scales: chartConfig.type === "radar" ? {
r: {
grid: {
color: colors.border,
display: chartConfig.showYGrid || true
},
ticks: {
color: colors.mutedForeground,
backdropColor: 'transparent',
display: chartConfig.showYLabels || true
},
angleLines: {
color: colors.border,
display: chartConfig.showXGrid || true
},
pointLabels: {
color: colors.foreground,
font: {
size: 12
}
},
border: {
display: chartConfig.showYAxis || false,
color: colors.border
},
beginAtZero: true
}
} : {
x: {
beginAtZero: true,
display: chartConfig.showXLabels || chartConfig.showXGrid || chartConfig.showXAxis,
border: {
display: chartConfig.showXAxis || false
},
ticks: {
display: chartConfig.showXLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showXGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
},
y: {
offset: true,
beginAtZero: true,
display: chartConfig.showYLabels || chartConfig.showYGrid || chartConfig.showYAxis,
border: {
display: chartConfig.showYAxis || false
},
ticks: {
display: chartConfig.showYLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showYGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
}
}
}
});
chartInstances[canvas.id] = chart;
});
}
initCharts();
window.addEventListener('resize', function() {
for (let id in chartInstances) {
chartInstances[id].resize();
}
});
const observer = new MutationObserver(() => {
setTimeout(initCharts, 50);
});
observer.observe(document.documentElement, { attributes: true });
document.addEventListener('alpine:initialized', () => {
document.addEventListener('theme-changed', initCharts);
});
});
</script>
}
}
Line Chart - Linear
package showcase
import "github.com/axzilla/templui/components"
templ ChartLineLinear() {
@components.Card(components.CardProps{Class: "max-w-sm"}) {
@components.CardContent() {
@components.Chart(components.ChartProps{
Variant: components.ChartVariantLine,
ShowYGrid: true,
ShowXLabels: true,
Data: components.ChartData{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []components.ChartDataset{
{
Data: []float64{12, 3, 9, 3, 12, 7},
},
},
},
})
}
}
}
package components
import "github.com/axzilla/templui/utils"
type ChartVariant string
const (
ChartVariantBar ChartVariant = "bar"
ChartVariantLine ChartVariant = "line"
ChartVariantPie ChartVariant = "pie"
ChartVariantDoughnut ChartVariant = "doughnut"
ChartVariantRadar ChartVariant = "radar"
)
type ChartDataset struct {
Label string `json:"label"`
Data []float64 `json:"data"`
BorderWidth int `json:"borderWidth,omitempty"`
BorderColor string `json:"borderColor,omitempty"`
BackgroundColor string `json:"backgroundColor,omitempty"`
Tension float64 `json:"tension,omitempty"`
Fill bool `json:"fill,omitempty"`
Stepped bool `json:"stepped,omitempty"`
}
type ChartOptions struct {
Responsive bool `json:"responsive,omitempty"`
Legend bool `json:"legend,omitempty"`
}
type ChartData struct {
Labels []string `json:"labels"`
Datasets []ChartDataset `json:"datasets"`
}
type ChartConfig struct {
Type ChartVariant `json:"type"`
Data ChartData `json:"data"`
Options ChartOptions `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 ChartProps um ID und Attributes
type ChartProps struct {
ID string
Variant ChartVariant
Data ChartData
Options ChartOptions
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 ...ChartProps) {
{{ var p ChartProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
// Wenn keine ID gesetzt, generiere eine zufällige
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 := ChartConfig{
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 ChartScripts() {
{{ handle := templ.NewOnceHandle() }}
@handle.Once() {
<!-- Load Chart.js library -->
<script defer nonce={ templ.GetNonce(ctx) } src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
<!-- Initialize charts -->
<script nonce={ templ.GetNonce(ctx) }>
document.addEventListener('DOMContentLoaded', function() {
const chartInstances = {};
function getThemeColors() {
const computedStyle = getComputedStyle(document.documentElement);
return {
foreground: computedStyle.getPropertyValue('--foreground').trim(),
background: computedStyle.getPropertyValue('--background').trim(),
mutedForeground: computedStyle.getPropertyValue('--muted-foreground').trim(),
border: computedStyle.getPropertyValue('--border').trim()
};
}
function initCharts() {
const colors = getThemeColors();
document.querySelectorAll('.chart-container').forEach(container => {
const canvas = container.querySelector('canvas');
if (!canvas) return;
const dataId = canvas.getAttribute('data-chart-id');
const dataElement = document.getElementById(dataId);
if (!dataElement) return;
if (chartInstances[canvas.id]) {
chartInstances[canvas.id].destroy();
}
const chartConfig = JSON.parse(dataElement.textContent);
Chart.defaults.elements.point.radius = 0;
Chart.defaults.elements.point.hoverRadius = 5;
const chart = new Chart(canvas, {
...chartConfig,
options: {
interaction: {
intersect: false,
axis: "xy",
mode: chartConfig.type === "pie" ||
chartConfig.type === "doughnut" ||
chartConfig.type === "bar" ||
chartConfig.type === "radar" ? "nearest" : "index"
},
indexAxis: chartConfig.horizontal ? "y" : "x",
plugins: {
legend: {
display: chartConfig.showLegend || false,
labels: {
color: colors.foreground
}
},
tooltip: {
backgroundColor: colors.background,
bodyColor: colors.mutedForeground,
titleColor: colors.foreground,
borderColor: colors.border,
borderWidth: 1
}
},
scales: chartConfig.type === "radar" ? {
r: {
grid: {
color: colors.border,
display: chartConfig.showYGrid || true
},
ticks: {
color: colors.mutedForeground,
backdropColor: 'transparent',
display: chartConfig.showYLabels || true
},
angleLines: {
color: colors.border,
display: chartConfig.showXGrid || true
},
pointLabels: {
color: colors.foreground,
font: {
size: 12
}
},
border: {
display: chartConfig.showYAxis || false,
color: colors.border
},
beginAtZero: true
}
} : {
x: {
beginAtZero: true,
display: chartConfig.showXLabels || chartConfig.showXGrid || chartConfig.showXAxis,
border: {
display: chartConfig.showXAxis || false
},
ticks: {
display: chartConfig.showXLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showXGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
},
y: {
offset: true,
beginAtZero: true,
display: chartConfig.showYLabels || chartConfig.showYGrid || chartConfig.showYAxis,
border: {
display: chartConfig.showYAxis || false
},
ticks: {
display: chartConfig.showYLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showYGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
}
}
}
});
chartInstances[canvas.id] = chart;
});
}
initCharts();
window.addEventListener('resize', function() {
for (let id in chartInstances) {
chartInstances[id].resize();
}
});
const observer = new MutationObserver(() => {
setTimeout(initCharts, 50);
});
observer.observe(document.documentElement, { attributes: true });
document.addEventListener('alpine:initialized', () => {
document.addEventListener('theme-changed', initCharts);
});
});
</script>
}
}
Line Chart - Step
package showcase
import "github.com/axzilla/templui/components"
templ ChartLineStep() {
@components.Card(components.CardProps{Class: "max-w-sm"}) {
@components.CardContent() {
@components.Chart(components.ChartProps{
Variant: components.ChartVariantLine,
ShowYGrid: true,
ShowXLabels: true,
Data: components.ChartData{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []components.ChartDataset{
{
Data: []float64{12, 3, 9, 3, 12, 7},
Stepped: true,
},
},
},
})
}
}
}
package components
import "github.com/axzilla/templui/utils"
type ChartVariant string
const (
ChartVariantBar ChartVariant = "bar"
ChartVariantLine ChartVariant = "line"
ChartVariantPie ChartVariant = "pie"
ChartVariantDoughnut ChartVariant = "doughnut"
ChartVariantRadar ChartVariant = "radar"
)
type ChartDataset struct {
Label string `json:"label"`
Data []float64 `json:"data"`
BorderWidth int `json:"borderWidth,omitempty"`
BorderColor string `json:"borderColor,omitempty"`
BackgroundColor string `json:"backgroundColor,omitempty"`
Tension float64 `json:"tension,omitempty"`
Fill bool `json:"fill,omitempty"`
Stepped bool `json:"stepped,omitempty"`
}
type ChartOptions struct {
Responsive bool `json:"responsive,omitempty"`
Legend bool `json:"legend,omitempty"`
}
type ChartData struct {
Labels []string `json:"labels"`
Datasets []ChartDataset `json:"datasets"`
}
type ChartConfig struct {
Type ChartVariant `json:"type"`
Data ChartData `json:"data"`
Options ChartOptions `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 ChartProps um ID und Attributes
type ChartProps struct {
ID string
Variant ChartVariant
Data ChartData
Options ChartOptions
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 ...ChartProps) {
{{ var p ChartProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
// Wenn keine ID gesetzt, generiere eine zufällige
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 := ChartConfig{
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 ChartScripts() {
{{ handle := templ.NewOnceHandle() }}
@handle.Once() {
<!-- Load Chart.js library -->
<script defer nonce={ templ.GetNonce(ctx) } src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
<!-- Initialize charts -->
<script nonce={ templ.GetNonce(ctx) }>
document.addEventListener('DOMContentLoaded', function() {
const chartInstances = {};
function getThemeColors() {
const computedStyle = getComputedStyle(document.documentElement);
return {
foreground: computedStyle.getPropertyValue('--foreground').trim(),
background: computedStyle.getPropertyValue('--background').trim(),
mutedForeground: computedStyle.getPropertyValue('--muted-foreground').trim(),
border: computedStyle.getPropertyValue('--border').trim()
};
}
function initCharts() {
const colors = getThemeColors();
document.querySelectorAll('.chart-container').forEach(container => {
const canvas = container.querySelector('canvas');
if (!canvas) return;
const dataId = canvas.getAttribute('data-chart-id');
const dataElement = document.getElementById(dataId);
if (!dataElement) return;
if (chartInstances[canvas.id]) {
chartInstances[canvas.id].destroy();
}
const chartConfig = JSON.parse(dataElement.textContent);
Chart.defaults.elements.point.radius = 0;
Chart.defaults.elements.point.hoverRadius = 5;
const chart = new Chart(canvas, {
...chartConfig,
options: {
interaction: {
intersect: false,
axis: "xy",
mode: chartConfig.type === "pie" ||
chartConfig.type === "doughnut" ||
chartConfig.type === "bar" ||
chartConfig.type === "radar" ? "nearest" : "index"
},
indexAxis: chartConfig.horizontal ? "y" : "x",
plugins: {
legend: {
display: chartConfig.showLegend || false,
labels: {
color: colors.foreground
}
},
tooltip: {
backgroundColor: colors.background,
bodyColor: colors.mutedForeground,
titleColor: colors.foreground,
borderColor: colors.border,
borderWidth: 1
}
},
scales: chartConfig.type === "radar" ? {
r: {
grid: {
color: colors.border,
display: chartConfig.showYGrid || true
},
ticks: {
color: colors.mutedForeground,
backdropColor: 'transparent',
display: chartConfig.showYLabels || true
},
angleLines: {
color: colors.border,
display: chartConfig.showXGrid || true
},
pointLabels: {
color: colors.foreground,
font: {
size: 12
}
},
border: {
display: chartConfig.showYAxis || false,
color: colors.border
},
beginAtZero: true
}
} : {
x: {
beginAtZero: true,
display: chartConfig.showXLabels || chartConfig.showXGrid || chartConfig.showXAxis,
border: {
display: chartConfig.showXAxis || false
},
ticks: {
display: chartConfig.showXLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showXGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
},
y: {
offset: true,
beginAtZero: true,
display: chartConfig.showYLabels || chartConfig.showYGrid || chartConfig.showYAxis,
border: {
display: chartConfig.showYAxis || false
},
ticks: {
display: chartConfig.showYLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showYGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
}
}
}
});
chartInstances[canvas.id] = chart;
});
}
initCharts();
window.addEventListener('resize', function() {
for (let id in chartInstances) {
chartInstances[id].resize();
}
});
const observer = new MutationObserver(() => {
setTimeout(initCharts, 50);
});
observer.observe(document.documentElement, { attributes: true });
document.addEventListener('alpine:initialized', () => {
document.addEventListener('theme-changed', initCharts);
});
});
</script>
}
}
Line Chart - Multiple
package showcase
import "github.com/axzilla/templui/components"
templ ChartLineMultiple() {
@components.Card(components.CardProps{Class: "max-w-sm"}) {
@components.CardContent() {
@components.Chart(components.ChartProps{
Variant: components.ChartVariantLine,
ShowYGrid: true,
ShowXLabels: true,
Data: components.ChartData{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []components.ChartDataset{
{
Label: "Mobile",
Data: []float64{12, 3, 9, 3, 12, 7},
Tension: 0.5,
},
{
Label: "Desktop",
Data: []float64{7, 14, 12, 21, 2, 9},
Tension: 0.5,
},
},
},
})
}
}
}
package components
import "github.com/axzilla/templui/utils"
type ChartVariant string
const (
ChartVariantBar ChartVariant = "bar"
ChartVariantLine ChartVariant = "line"
ChartVariantPie ChartVariant = "pie"
ChartVariantDoughnut ChartVariant = "doughnut"
ChartVariantRadar ChartVariant = "radar"
)
type ChartDataset struct {
Label string `json:"label"`
Data []float64 `json:"data"`
BorderWidth int `json:"borderWidth,omitempty"`
BorderColor string `json:"borderColor,omitempty"`
BackgroundColor string `json:"backgroundColor,omitempty"`
Tension float64 `json:"tension,omitempty"`
Fill bool `json:"fill,omitempty"`
Stepped bool `json:"stepped,omitempty"`
}
type ChartOptions struct {
Responsive bool `json:"responsive,omitempty"`
Legend bool `json:"legend,omitempty"`
}
type ChartData struct {
Labels []string `json:"labels"`
Datasets []ChartDataset `json:"datasets"`
}
type ChartConfig struct {
Type ChartVariant `json:"type"`
Data ChartData `json:"data"`
Options ChartOptions `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 ChartProps um ID und Attributes
type ChartProps struct {
ID string
Variant ChartVariant
Data ChartData
Options ChartOptions
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 ...ChartProps) {
{{ var p ChartProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
// Wenn keine ID gesetzt, generiere eine zufällige
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 := ChartConfig{
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 ChartScripts() {
{{ handle := templ.NewOnceHandle() }}
@handle.Once() {
<!-- Load Chart.js library -->
<script defer nonce={ templ.GetNonce(ctx) } src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
<!-- Initialize charts -->
<script nonce={ templ.GetNonce(ctx) }>
document.addEventListener('DOMContentLoaded', function() {
const chartInstances = {};
function getThemeColors() {
const computedStyle = getComputedStyle(document.documentElement);
return {
foreground: computedStyle.getPropertyValue('--foreground').trim(),
background: computedStyle.getPropertyValue('--background').trim(),
mutedForeground: computedStyle.getPropertyValue('--muted-foreground').trim(),
border: computedStyle.getPropertyValue('--border').trim()
};
}
function initCharts() {
const colors = getThemeColors();
document.querySelectorAll('.chart-container').forEach(container => {
const canvas = container.querySelector('canvas');
if (!canvas) return;
const dataId = canvas.getAttribute('data-chart-id');
const dataElement = document.getElementById(dataId);
if (!dataElement) return;
if (chartInstances[canvas.id]) {
chartInstances[canvas.id].destroy();
}
const chartConfig = JSON.parse(dataElement.textContent);
Chart.defaults.elements.point.radius = 0;
Chart.defaults.elements.point.hoverRadius = 5;
const chart = new Chart(canvas, {
...chartConfig,
options: {
interaction: {
intersect: false,
axis: "xy",
mode: chartConfig.type === "pie" ||
chartConfig.type === "doughnut" ||
chartConfig.type === "bar" ||
chartConfig.type === "radar" ? "nearest" : "index"
},
indexAxis: chartConfig.horizontal ? "y" : "x",
plugins: {
legend: {
display: chartConfig.showLegend || false,
labels: {
color: colors.foreground
}
},
tooltip: {
backgroundColor: colors.background,
bodyColor: colors.mutedForeground,
titleColor: colors.foreground,
borderColor: colors.border,
borderWidth: 1
}
},
scales: chartConfig.type === "radar" ? {
r: {
grid: {
color: colors.border,
display: chartConfig.showYGrid || true
},
ticks: {
color: colors.mutedForeground,
backdropColor: 'transparent',
display: chartConfig.showYLabels || true
},
angleLines: {
color: colors.border,
display: chartConfig.showXGrid || true
},
pointLabels: {
color: colors.foreground,
font: {
size: 12
}
},
border: {
display: chartConfig.showYAxis || false,
color: colors.border
},
beginAtZero: true
}
} : {
x: {
beginAtZero: true,
display: chartConfig.showXLabels || chartConfig.showXGrid || chartConfig.showXAxis,
border: {
display: chartConfig.showXAxis || false
},
ticks: {
display: chartConfig.showXLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showXGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
},
y: {
offset: true,
beginAtZero: true,
display: chartConfig.showYLabels || chartConfig.showYGrid || chartConfig.showYAxis,
border: {
display: chartConfig.showYAxis || false
},
ticks: {
display: chartConfig.showYLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showYGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
}
}
}
});
chartInstances[canvas.id] = chart;
});
}
initCharts();
window.addEventListener('resize', function() {
for (let id in chartInstances) {
chartInstances[id].resize();
}
});
const observer = new MutationObserver(() => {
setTimeout(initCharts, 50);
});
observer.observe(document.documentElement, { attributes: true });
document.addEventListener('alpine:initialized', () => {
document.addEventListener('theme-changed', initCharts);
});
});
</script>
}
}
Area Chart
package showcase
import "github.com/axzilla/templui/components"
templ ChartArea() {
@components.Card(components.CardProps{Class: "max-w-sm"}) {
@components.CardContent() {
@components.Chart(components.ChartProps{
Variant: components.ChartVariantLine,
ShowYGrid: true,
ShowXLabels: true,
Data: components.ChartData{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []components.ChartDataset{
{
Data: []float64{3, 9, 3, 12, 7, 8},
Tension: 0.5,
BorderWidth: 1,
Fill: true,
},
},
},
})
}
}
}
package components
import "github.com/axzilla/templui/utils"
type ChartVariant string
const (
ChartVariantBar ChartVariant = "bar"
ChartVariantLine ChartVariant = "line"
ChartVariantPie ChartVariant = "pie"
ChartVariantDoughnut ChartVariant = "doughnut"
ChartVariantRadar ChartVariant = "radar"
)
type ChartDataset struct {
Label string `json:"label"`
Data []float64 `json:"data"`
BorderWidth int `json:"borderWidth,omitempty"`
BorderColor string `json:"borderColor,omitempty"`
BackgroundColor string `json:"backgroundColor,omitempty"`
Tension float64 `json:"tension,omitempty"`
Fill bool `json:"fill,omitempty"`
Stepped bool `json:"stepped,omitempty"`
}
type ChartOptions struct {
Responsive bool `json:"responsive,omitempty"`
Legend bool `json:"legend,omitempty"`
}
type ChartData struct {
Labels []string `json:"labels"`
Datasets []ChartDataset `json:"datasets"`
}
type ChartConfig struct {
Type ChartVariant `json:"type"`
Data ChartData `json:"data"`
Options ChartOptions `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 ChartProps um ID und Attributes
type ChartProps struct {
ID string
Variant ChartVariant
Data ChartData
Options ChartOptions
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 ...ChartProps) {
{{ var p ChartProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
// Wenn keine ID gesetzt, generiere eine zufällige
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 := ChartConfig{
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 ChartScripts() {
{{ handle := templ.NewOnceHandle() }}
@handle.Once() {
<!-- Load Chart.js library -->
<script defer nonce={ templ.GetNonce(ctx) } src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
<!-- Initialize charts -->
<script nonce={ templ.GetNonce(ctx) }>
document.addEventListener('DOMContentLoaded', function() {
const chartInstances = {};
function getThemeColors() {
const computedStyle = getComputedStyle(document.documentElement);
return {
foreground: computedStyle.getPropertyValue('--foreground').trim(),
background: computedStyle.getPropertyValue('--background').trim(),
mutedForeground: computedStyle.getPropertyValue('--muted-foreground').trim(),
border: computedStyle.getPropertyValue('--border').trim()
};
}
function initCharts() {
const colors = getThemeColors();
document.querySelectorAll('.chart-container').forEach(container => {
const canvas = container.querySelector('canvas');
if (!canvas) return;
const dataId = canvas.getAttribute('data-chart-id');
const dataElement = document.getElementById(dataId);
if (!dataElement) return;
if (chartInstances[canvas.id]) {
chartInstances[canvas.id].destroy();
}
const chartConfig = JSON.parse(dataElement.textContent);
Chart.defaults.elements.point.radius = 0;
Chart.defaults.elements.point.hoverRadius = 5;
const chart = new Chart(canvas, {
...chartConfig,
options: {
interaction: {
intersect: false,
axis: "xy",
mode: chartConfig.type === "pie" ||
chartConfig.type === "doughnut" ||
chartConfig.type === "bar" ||
chartConfig.type === "radar" ? "nearest" : "index"
},
indexAxis: chartConfig.horizontal ? "y" : "x",
plugins: {
legend: {
display: chartConfig.showLegend || false,
labels: {
color: colors.foreground
}
},
tooltip: {
backgroundColor: colors.background,
bodyColor: colors.mutedForeground,
titleColor: colors.foreground,
borderColor: colors.border,
borderWidth: 1
}
},
scales: chartConfig.type === "radar" ? {
r: {
grid: {
color: colors.border,
display: chartConfig.showYGrid || true
},
ticks: {
color: colors.mutedForeground,
backdropColor: 'transparent',
display: chartConfig.showYLabels || true
},
angleLines: {
color: colors.border,
display: chartConfig.showXGrid || true
},
pointLabels: {
color: colors.foreground,
font: {
size: 12
}
},
border: {
display: chartConfig.showYAxis || false,
color: colors.border
},
beginAtZero: true
}
} : {
x: {
beginAtZero: true,
display: chartConfig.showXLabels || chartConfig.showXGrid || chartConfig.showXAxis,
border: {
display: chartConfig.showXAxis || false
},
ticks: {
display: chartConfig.showXLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showXGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
},
y: {
offset: true,
beginAtZero: true,
display: chartConfig.showYLabels || chartConfig.showYGrid || chartConfig.showYAxis,
border: {
display: chartConfig.showYAxis || false
},
ticks: {
display: chartConfig.showYLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showYGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
}
}
}
});
chartInstances[canvas.id] = chart;
});
}
initCharts();
window.addEventListener('resize', function() {
for (let id in chartInstances) {
chartInstances[id].resize();
}
});
const observer = new MutationObserver(() => {
setTimeout(initCharts, 50);
});
observer.observe(document.documentElement, { attributes: true });
document.addEventListener('alpine:initialized', () => {
document.addEventListener('theme-changed', initCharts);
});
});
</script>
}
}
Area Chart - Linear
package showcase
import "github.com/axzilla/templui/components"
templ ChartAreaLinear() {
@components.Card(components.CardProps{Class: "max-w-sm"}) {
@components.CardContent() {
@components.Chart(components.ChartProps{
Variant: components.ChartVariantLine,
ShowYGrid: true,
ShowXLabels: true,
Data: components.ChartData{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []components.ChartDataset{
{
Data: []float64{3, 9, 3, 12, 7, 8},
BorderWidth: 1,
Fill: true,
},
},
},
})
}
}
}
package components
import "github.com/axzilla/templui/utils"
type ChartVariant string
const (
ChartVariantBar ChartVariant = "bar"
ChartVariantLine ChartVariant = "line"
ChartVariantPie ChartVariant = "pie"
ChartVariantDoughnut ChartVariant = "doughnut"
ChartVariantRadar ChartVariant = "radar"
)
type ChartDataset struct {
Label string `json:"label"`
Data []float64 `json:"data"`
BorderWidth int `json:"borderWidth,omitempty"`
BorderColor string `json:"borderColor,omitempty"`
BackgroundColor string `json:"backgroundColor,omitempty"`
Tension float64 `json:"tension,omitempty"`
Fill bool `json:"fill,omitempty"`
Stepped bool `json:"stepped,omitempty"`
}
type ChartOptions struct {
Responsive bool `json:"responsive,omitempty"`
Legend bool `json:"legend,omitempty"`
}
type ChartData struct {
Labels []string `json:"labels"`
Datasets []ChartDataset `json:"datasets"`
}
type ChartConfig struct {
Type ChartVariant `json:"type"`
Data ChartData `json:"data"`
Options ChartOptions `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 ChartProps um ID und Attributes
type ChartProps struct {
ID string
Variant ChartVariant
Data ChartData
Options ChartOptions
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 ...ChartProps) {
{{ var p ChartProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
// Wenn keine ID gesetzt, generiere eine zufällige
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 := ChartConfig{
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 ChartScripts() {
{{ handle := templ.NewOnceHandle() }}
@handle.Once() {
<!-- Load Chart.js library -->
<script defer nonce={ templ.GetNonce(ctx) } src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
<!-- Initialize charts -->
<script nonce={ templ.GetNonce(ctx) }>
document.addEventListener('DOMContentLoaded', function() {
const chartInstances = {};
function getThemeColors() {
const computedStyle = getComputedStyle(document.documentElement);
return {
foreground: computedStyle.getPropertyValue('--foreground').trim(),
background: computedStyle.getPropertyValue('--background').trim(),
mutedForeground: computedStyle.getPropertyValue('--muted-foreground').trim(),
border: computedStyle.getPropertyValue('--border').trim()
};
}
function initCharts() {
const colors = getThemeColors();
document.querySelectorAll('.chart-container').forEach(container => {
const canvas = container.querySelector('canvas');
if (!canvas) return;
const dataId = canvas.getAttribute('data-chart-id');
const dataElement = document.getElementById(dataId);
if (!dataElement) return;
if (chartInstances[canvas.id]) {
chartInstances[canvas.id].destroy();
}
const chartConfig = JSON.parse(dataElement.textContent);
Chart.defaults.elements.point.radius = 0;
Chart.defaults.elements.point.hoverRadius = 5;
const chart = new Chart(canvas, {
...chartConfig,
options: {
interaction: {
intersect: false,
axis: "xy",
mode: chartConfig.type === "pie" ||
chartConfig.type === "doughnut" ||
chartConfig.type === "bar" ||
chartConfig.type === "radar" ? "nearest" : "index"
},
indexAxis: chartConfig.horizontal ? "y" : "x",
plugins: {
legend: {
display: chartConfig.showLegend || false,
labels: {
color: colors.foreground
}
},
tooltip: {
backgroundColor: colors.background,
bodyColor: colors.mutedForeground,
titleColor: colors.foreground,
borderColor: colors.border,
borderWidth: 1
}
},
scales: chartConfig.type === "radar" ? {
r: {
grid: {
color: colors.border,
display: chartConfig.showYGrid || true
},
ticks: {
color: colors.mutedForeground,
backdropColor: 'transparent',
display: chartConfig.showYLabels || true
},
angleLines: {
color: colors.border,
display: chartConfig.showXGrid || true
},
pointLabels: {
color: colors.foreground,
font: {
size: 12
}
},
border: {
display: chartConfig.showYAxis || false,
color: colors.border
},
beginAtZero: true
}
} : {
x: {
beginAtZero: true,
display: chartConfig.showXLabels || chartConfig.showXGrid || chartConfig.showXAxis,
border: {
display: chartConfig.showXAxis || false
},
ticks: {
display: chartConfig.showXLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showXGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
},
y: {
offset: true,
beginAtZero: true,
display: chartConfig.showYLabels || chartConfig.showYGrid || chartConfig.showYAxis,
border: {
display: chartConfig.showYAxis || false
},
ticks: {
display: chartConfig.showYLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showYGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
}
}
}
});
chartInstances[canvas.id] = chart;
});
}
initCharts();
window.addEventListener('resize', function() {
for (let id in chartInstances) {
chartInstances[id].resize();
}
});
const observer = new MutationObserver(() => {
setTimeout(initCharts, 50);
});
observer.observe(document.documentElement, { attributes: true });
document.addEventListener('alpine:initialized', () => {
document.addEventListener('theme-changed', initCharts);
});
});
</script>
}
}
Area Chart - Step
package showcase
import "github.com/axzilla/templui/components"
templ ChartAreaStep() {
@components.Card(components.CardProps{Class: "max-w-sm"}) {
@components.CardContent() {
@components.Chart(components.ChartProps{
Variant: components.ChartVariantLine,
ShowYGrid: true,
ShowXLabels: true,
Data: components.ChartData{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []components.ChartDataset{
{
Data: []float64{3, 9, 3, 12, 7, 8},
BorderWidth: 1,
Fill: true,
Stepped: true,
},
},
},
})
}
}
}
package components
import "github.com/axzilla/templui/utils"
type ChartVariant string
const (
ChartVariantBar ChartVariant = "bar"
ChartVariantLine ChartVariant = "line"
ChartVariantPie ChartVariant = "pie"
ChartVariantDoughnut ChartVariant = "doughnut"
ChartVariantRadar ChartVariant = "radar"
)
type ChartDataset struct {
Label string `json:"label"`
Data []float64 `json:"data"`
BorderWidth int `json:"borderWidth,omitempty"`
BorderColor string `json:"borderColor,omitempty"`
BackgroundColor string `json:"backgroundColor,omitempty"`
Tension float64 `json:"tension,omitempty"`
Fill bool `json:"fill,omitempty"`
Stepped bool `json:"stepped,omitempty"`
}
type ChartOptions struct {
Responsive bool `json:"responsive,omitempty"`
Legend bool `json:"legend,omitempty"`
}
type ChartData struct {
Labels []string `json:"labels"`
Datasets []ChartDataset `json:"datasets"`
}
type ChartConfig struct {
Type ChartVariant `json:"type"`
Data ChartData `json:"data"`
Options ChartOptions `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 ChartProps um ID und Attributes
type ChartProps struct {
ID string
Variant ChartVariant
Data ChartData
Options ChartOptions
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 ...ChartProps) {
{{ var p ChartProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
// Wenn keine ID gesetzt, generiere eine zufällige
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 := ChartConfig{
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 ChartScripts() {
{{ handle := templ.NewOnceHandle() }}
@handle.Once() {
<!-- Load Chart.js library -->
<script defer nonce={ templ.GetNonce(ctx) } src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
<!-- Initialize charts -->
<script nonce={ templ.GetNonce(ctx) }>
document.addEventListener('DOMContentLoaded', function() {
const chartInstances = {};
function getThemeColors() {
const computedStyle = getComputedStyle(document.documentElement);
return {
foreground: computedStyle.getPropertyValue('--foreground').trim(),
background: computedStyle.getPropertyValue('--background').trim(),
mutedForeground: computedStyle.getPropertyValue('--muted-foreground').trim(),
border: computedStyle.getPropertyValue('--border').trim()
};
}
function initCharts() {
const colors = getThemeColors();
document.querySelectorAll('.chart-container').forEach(container => {
const canvas = container.querySelector('canvas');
if (!canvas) return;
const dataId = canvas.getAttribute('data-chart-id');
const dataElement = document.getElementById(dataId);
if (!dataElement) return;
if (chartInstances[canvas.id]) {
chartInstances[canvas.id].destroy();
}
const chartConfig = JSON.parse(dataElement.textContent);
Chart.defaults.elements.point.radius = 0;
Chart.defaults.elements.point.hoverRadius = 5;
const chart = new Chart(canvas, {
...chartConfig,
options: {
interaction: {
intersect: false,
axis: "xy",
mode: chartConfig.type === "pie" ||
chartConfig.type === "doughnut" ||
chartConfig.type === "bar" ||
chartConfig.type === "radar" ? "nearest" : "index"
},
indexAxis: chartConfig.horizontal ? "y" : "x",
plugins: {
legend: {
display: chartConfig.showLegend || false,
labels: {
color: colors.foreground
}
},
tooltip: {
backgroundColor: colors.background,
bodyColor: colors.mutedForeground,
titleColor: colors.foreground,
borderColor: colors.border,
borderWidth: 1
}
},
scales: chartConfig.type === "radar" ? {
r: {
grid: {
color: colors.border,
display: chartConfig.showYGrid || true
},
ticks: {
color: colors.mutedForeground,
backdropColor: 'transparent',
display: chartConfig.showYLabels || true
},
angleLines: {
color: colors.border,
display: chartConfig.showXGrid || true
},
pointLabels: {
color: colors.foreground,
font: {
size: 12
}
},
border: {
display: chartConfig.showYAxis || false,
color: colors.border
},
beginAtZero: true
}
} : {
x: {
beginAtZero: true,
display: chartConfig.showXLabels || chartConfig.showXGrid || chartConfig.showXAxis,
border: {
display: chartConfig.showXAxis || false
},
ticks: {
display: chartConfig.showXLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showXGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
},
y: {
offset: true,
beginAtZero: true,
display: chartConfig.showYLabels || chartConfig.showYGrid || chartConfig.showYAxis,
border: {
display: chartConfig.showYAxis || false
},
ticks: {
display: chartConfig.showYLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showYGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
}
}
}
});
chartInstances[canvas.id] = chart;
});
}
initCharts();
window.addEventListener('resize', function() {
for (let id in chartInstances) {
chartInstances[id].resize();
}
});
const observer = new MutationObserver(() => {
setTimeout(initCharts, 50);
});
observer.observe(document.documentElement, { attributes: true });
document.addEventListener('alpine:initialized', () => {
document.addEventListener('theme-changed', initCharts);
});
});
</script>
}
}
Area Chart - Stacked
package showcase
import "github.com/axzilla/templui/components"
templ ChartAreaStacked() {
@components.Card(components.CardProps{Class: "max-w-sm"}) {
@components.CardContent() {
@components.Chart(components.ChartProps{
Variant: components.ChartVariantLine,
ShowYGrid: true,
ShowXLabels: true,
Data: components.ChartData{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []components.ChartDataset{
{
Data: []float64{3, 9, 3, 12, 7, 8},
BorderWidth: 1,
Fill: true,
Tension: 0.5,
Label: "Mobile",
},
{
Data: []float64{7, 16, 5, 20, 14, 15},
BorderWidth: 1,
Fill: true,
Tension: 0.5,
Label: "Mobile",
},
},
},
})
}
}
}
package components
import "github.com/axzilla/templui/utils"
type ChartVariant string
const (
ChartVariantBar ChartVariant = "bar"
ChartVariantLine ChartVariant = "line"
ChartVariantPie ChartVariant = "pie"
ChartVariantDoughnut ChartVariant = "doughnut"
ChartVariantRadar ChartVariant = "radar"
)
type ChartDataset struct {
Label string `json:"label"`
Data []float64 `json:"data"`
BorderWidth int `json:"borderWidth,omitempty"`
BorderColor string `json:"borderColor,omitempty"`
BackgroundColor string `json:"backgroundColor,omitempty"`
Tension float64 `json:"tension,omitempty"`
Fill bool `json:"fill,omitempty"`
Stepped bool `json:"stepped,omitempty"`
}
type ChartOptions struct {
Responsive bool `json:"responsive,omitempty"`
Legend bool `json:"legend,omitempty"`
}
type ChartData struct {
Labels []string `json:"labels"`
Datasets []ChartDataset `json:"datasets"`
}
type ChartConfig struct {
Type ChartVariant `json:"type"`
Data ChartData `json:"data"`
Options ChartOptions `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 ChartProps um ID und Attributes
type ChartProps struct {
ID string
Variant ChartVariant
Data ChartData
Options ChartOptions
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 ...ChartProps) {
{{ var p ChartProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
// Wenn keine ID gesetzt, generiere eine zufällige
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 := ChartConfig{
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 ChartScripts() {
{{ handle := templ.NewOnceHandle() }}
@handle.Once() {
<!-- Load Chart.js library -->
<script defer nonce={ templ.GetNonce(ctx) } src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
<!-- Initialize charts -->
<script nonce={ templ.GetNonce(ctx) }>
document.addEventListener('DOMContentLoaded', function() {
const chartInstances = {};
function getThemeColors() {
const computedStyle = getComputedStyle(document.documentElement);
return {
foreground: computedStyle.getPropertyValue('--foreground').trim(),
background: computedStyle.getPropertyValue('--background').trim(),
mutedForeground: computedStyle.getPropertyValue('--muted-foreground').trim(),
border: computedStyle.getPropertyValue('--border').trim()
};
}
function initCharts() {
const colors = getThemeColors();
document.querySelectorAll('.chart-container').forEach(container => {
const canvas = container.querySelector('canvas');
if (!canvas) return;
const dataId = canvas.getAttribute('data-chart-id');
const dataElement = document.getElementById(dataId);
if (!dataElement) return;
if (chartInstances[canvas.id]) {
chartInstances[canvas.id].destroy();
}
const chartConfig = JSON.parse(dataElement.textContent);
Chart.defaults.elements.point.radius = 0;
Chart.defaults.elements.point.hoverRadius = 5;
const chart = new Chart(canvas, {
...chartConfig,
options: {
interaction: {
intersect: false,
axis: "xy",
mode: chartConfig.type === "pie" ||
chartConfig.type === "doughnut" ||
chartConfig.type === "bar" ||
chartConfig.type === "radar" ? "nearest" : "index"
},
indexAxis: chartConfig.horizontal ? "y" : "x",
plugins: {
legend: {
display: chartConfig.showLegend || false,
labels: {
color: colors.foreground
}
},
tooltip: {
backgroundColor: colors.background,
bodyColor: colors.mutedForeground,
titleColor: colors.foreground,
borderColor: colors.border,
borderWidth: 1
}
},
scales: chartConfig.type === "radar" ? {
r: {
grid: {
color: colors.border,
display: chartConfig.showYGrid || true
},
ticks: {
color: colors.mutedForeground,
backdropColor: 'transparent',
display: chartConfig.showYLabels || true
},
angleLines: {
color: colors.border,
display: chartConfig.showXGrid || true
},
pointLabels: {
color: colors.foreground,
font: {
size: 12
}
},
border: {
display: chartConfig.showYAxis || false,
color: colors.border
},
beginAtZero: true
}
} : {
x: {
beginAtZero: true,
display: chartConfig.showXLabels || chartConfig.showXGrid || chartConfig.showXAxis,
border: {
display: chartConfig.showXAxis || false
},
ticks: {
display: chartConfig.showXLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showXGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
},
y: {
offset: true,
beginAtZero: true,
display: chartConfig.showYLabels || chartConfig.showYGrid || chartConfig.showYAxis,
border: {
display: chartConfig.showYAxis || false
},
ticks: {
display: chartConfig.showYLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showYGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
}
}
}
});
chartInstances[canvas.id] = chart;
});
}
initCharts();
window.addEventListener('resize', function() {
for (let id in chartInstances) {
chartInstances[id].resize();
}
});
const observer = new MutationObserver(() => {
setTimeout(initCharts, 50);
});
observer.observe(document.documentElement, { attributes: true });
document.addEventListener('alpine:initialized', () => {
document.addEventListener('theme-changed', initCharts);
});
});
</script>
}
}
Pie Chart
package showcase
import "github.com/axzilla/templui/components"
templ ChartPie() {
@components.Card(components.CardProps{Class: "max-w-sm"}) {
@components.CardContent() {
@components.Chart(components.ChartProps{
Variant: components.ChartVariantPie,
Data: components.ChartData{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []components.ChartDataset{
{
Data: []float64{3, 9, 3, 12, 7, 8},
},
},
},
})
}
}
}
package components
import "github.com/axzilla/templui/utils"
type ChartVariant string
const (
ChartVariantBar ChartVariant = "bar"
ChartVariantLine ChartVariant = "line"
ChartVariantPie ChartVariant = "pie"
ChartVariantDoughnut ChartVariant = "doughnut"
ChartVariantRadar ChartVariant = "radar"
)
type ChartDataset struct {
Label string `json:"label"`
Data []float64 `json:"data"`
BorderWidth int `json:"borderWidth,omitempty"`
BorderColor string `json:"borderColor,omitempty"`
BackgroundColor string `json:"backgroundColor,omitempty"`
Tension float64 `json:"tension,omitempty"`
Fill bool `json:"fill,omitempty"`
Stepped bool `json:"stepped,omitempty"`
}
type ChartOptions struct {
Responsive bool `json:"responsive,omitempty"`
Legend bool `json:"legend,omitempty"`
}
type ChartData struct {
Labels []string `json:"labels"`
Datasets []ChartDataset `json:"datasets"`
}
type ChartConfig struct {
Type ChartVariant `json:"type"`
Data ChartData `json:"data"`
Options ChartOptions `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 ChartProps um ID und Attributes
type ChartProps struct {
ID string
Variant ChartVariant
Data ChartData
Options ChartOptions
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 ...ChartProps) {
{{ var p ChartProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
// Wenn keine ID gesetzt, generiere eine zufällige
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 := ChartConfig{
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 ChartScripts() {
{{ handle := templ.NewOnceHandle() }}
@handle.Once() {
<!-- Load Chart.js library -->
<script defer nonce={ templ.GetNonce(ctx) } src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
<!-- Initialize charts -->
<script nonce={ templ.GetNonce(ctx) }>
document.addEventListener('DOMContentLoaded', function() {
const chartInstances = {};
function getThemeColors() {
const computedStyle = getComputedStyle(document.documentElement);
return {
foreground: computedStyle.getPropertyValue('--foreground').trim(),
background: computedStyle.getPropertyValue('--background').trim(),
mutedForeground: computedStyle.getPropertyValue('--muted-foreground').trim(),
border: computedStyle.getPropertyValue('--border').trim()
};
}
function initCharts() {
const colors = getThemeColors();
document.querySelectorAll('.chart-container').forEach(container => {
const canvas = container.querySelector('canvas');
if (!canvas) return;
const dataId = canvas.getAttribute('data-chart-id');
const dataElement = document.getElementById(dataId);
if (!dataElement) return;
if (chartInstances[canvas.id]) {
chartInstances[canvas.id].destroy();
}
const chartConfig = JSON.parse(dataElement.textContent);
Chart.defaults.elements.point.radius = 0;
Chart.defaults.elements.point.hoverRadius = 5;
const chart = new Chart(canvas, {
...chartConfig,
options: {
interaction: {
intersect: false,
axis: "xy",
mode: chartConfig.type === "pie" ||
chartConfig.type === "doughnut" ||
chartConfig.type === "bar" ||
chartConfig.type === "radar" ? "nearest" : "index"
},
indexAxis: chartConfig.horizontal ? "y" : "x",
plugins: {
legend: {
display: chartConfig.showLegend || false,
labels: {
color: colors.foreground
}
},
tooltip: {
backgroundColor: colors.background,
bodyColor: colors.mutedForeground,
titleColor: colors.foreground,
borderColor: colors.border,
borderWidth: 1
}
},
scales: chartConfig.type === "radar" ? {
r: {
grid: {
color: colors.border,
display: chartConfig.showYGrid || true
},
ticks: {
color: colors.mutedForeground,
backdropColor: 'transparent',
display: chartConfig.showYLabels || true
},
angleLines: {
color: colors.border,
display: chartConfig.showXGrid || true
},
pointLabels: {
color: colors.foreground,
font: {
size: 12
}
},
border: {
display: chartConfig.showYAxis || false,
color: colors.border
},
beginAtZero: true
}
} : {
x: {
beginAtZero: true,
display: chartConfig.showXLabels || chartConfig.showXGrid || chartConfig.showXAxis,
border: {
display: chartConfig.showXAxis || false
},
ticks: {
display: chartConfig.showXLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showXGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
},
y: {
offset: true,
beginAtZero: true,
display: chartConfig.showYLabels || chartConfig.showYGrid || chartConfig.showYAxis,
border: {
display: chartConfig.showYAxis || false
},
ticks: {
display: chartConfig.showYLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showYGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
}
}
}
});
chartInstances[canvas.id] = chart;
});
}
initCharts();
window.addEventListener('resize', function() {
for (let id in chartInstances) {
chartInstances[id].resize();
}
});
const observer = new MutationObserver(() => {
setTimeout(initCharts, 50);
});
observer.observe(document.documentElement, { attributes: true });
document.addEventListener('alpine:initialized', () => {
document.addEventListener('theme-changed', initCharts);
});
});
</script>
}
}
Pie Chart - Stacked
package showcase
import "github.com/axzilla/templui/components"
templ ChartPieStacked() {
@components.Card(components.CardProps{Class: "max-w-sm"}) {
@components.CardContent() {
@components.Chart(components.ChartProps{
Variant: components.ChartVariantPie,
Data: components.ChartData{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []components.ChartDataset{
{
Data: []float64{3, 9, 3, 12, 7, 8},
Label: "Mobile",
},
{
Data: []float64{7, 16, 5, 20, 14, 15},
Label: "Desktop",
},
},
},
})
}
}
}
package components
import "github.com/axzilla/templui/utils"
type ChartVariant string
const (
ChartVariantBar ChartVariant = "bar"
ChartVariantLine ChartVariant = "line"
ChartVariantPie ChartVariant = "pie"
ChartVariantDoughnut ChartVariant = "doughnut"
ChartVariantRadar ChartVariant = "radar"
)
type ChartDataset struct {
Label string `json:"label"`
Data []float64 `json:"data"`
BorderWidth int `json:"borderWidth,omitempty"`
BorderColor string `json:"borderColor,omitempty"`
BackgroundColor string `json:"backgroundColor,omitempty"`
Tension float64 `json:"tension,omitempty"`
Fill bool `json:"fill,omitempty"`
Stepped bool `json:"stepped,omitempty"`
}
type ChartOptions struct {
Responsive bool `json:"responsive,omitempty"`
Legend bool `json:"legend,omitempty"`
}
type ChartData struct {
Labels []string `json:"labels"`
Datasets []ChartDataset `json:"datasets"`
}
type ChartConfig struct {
Type ChartVariant `json:"type"`
Data ChartData `json:"data"`
Options ChartOptions `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 ChartProps um ID und Attributes
type ChartProps struct {
ID string
Variant ChartVariant
Data ChartData
Options ChartOptions
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 ...ChartProps) {
{{ var p ChartProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
// Wenn keine ID gesetzt, generiere eine zufällige
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 := ChartConfig{
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 ChartScripts() {
{{ handle := templ.NewOnceHandle() }}
@handle.Once() {
<!-- Load Chart.js library -->
<script defer nonce={ templ.GetNonce(ctx) } src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
<!-- Initialize charts -->
<script nonce={ templ.GetNonce(ctx) }>
document.addEventListener('DOMContentLoaded', function() {
const chartInstances = {};
function getThemeColors() {
const computedStyle = getComputedStyle(document.documentElement);
return {
foreground: computedStyle.getPropertyValue('--foreground').trim(),
background: computedStyle.getPropertyValue('--background').trim(),
mutedForeground: computedStyle.getPropertyValue('--muted-foreground').trim(),
border: computedStyle.getPropertyValue('--border').trim()
};
}
function initCharts() {
const colors = getThemeColors();
document.querySelectorAll('.chart-container').forEach(container => {
const canvas = container.querySelector('canvas');
if (!canvas) return;
const dataId = canvas.getAttribute('data-chart-id');
const dataElement = document.getElementById(dataId);
if (!dataElement) return;
if (chartInstances[canvas.id]) {
chartInstances[canvas.id].destroy();
}
const chartConfig = JSON.parse(dataElement.textContent);
Chart.defaults.elements.point.radius = 0;
Chart.defaults.elements.point.hoverRadius = 5;
const chart = new Chart(canvas, {
...chartConfig,
options: {
interaction: {
intersect: false,
axis: "xy",
mode: chartConfig.type === "pie" ||
chartConfig.type === "doughnut" ||
chartConfig.type === "bar" ||
chartConfig.type === "radar" ? "nearest" : "index"
},
indexAxis: chartConfig.horizontal ? "y" : "x",
plugins: {
legend: {
display: chartConfig.showLegend || false,
labels: {
color: colors.foreground
}
},
tooltip: {
backgroundColor: colors.background,
bodyColor: colors.mutedForeground,
titleColor: colors.foreground,
borderColor: colors.border,
borderWidth: 1
}
},
scales: chartConfig.type === "radar" ? {
r: {
grid: {
color: colors.border,
display: chartConfig.showYGrid || true
},
ticks: {
color: colors.mutedForeground,
backdropColor: 'transparent',
display: chartConfig.showYLabels || true
},
angleLines: {
color: colors.border,
display: chartConfig.showXGrid || true
},
pointLabels: {
color: colors.foreground,
font: {
size: 12
}
},
border: {
display: chartConfig.showYAxis || false,
color: colors.border
},
beginAtZero: true
}
} : {
x: {
beginAtZero: true,
display: chartConfig.showXLabels || chartConfig.showXGrid || chartConfig.showXAxis,
border: {
display: chartConfig.showXAxis || false
},
ticks: {
display: chartConfig.showXLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showXGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
},
y: {
offset: true,
beginAtZero: true,
display: chartConfig.showYLabels || chartConfig.showYGrid || chartConfig.showYAxis,
border: {
display: chartConfig.showYAxis || false
},
ticks: {
display: chartConfig.showYLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showYGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
}
}
}
});
chartInstances[canvas.id] = chart;
});
}
initCharts();
window.addEventListener('resize', function() {
for (let id in chartInstances) {
chartInstances[id].resize();
}
});
const observer = new MutationObserver(() => {
setTimeout(initCharts, 50);
});
observer.observe(document.documentElement, { attributes: true });
document.addEventListener('alpine:initialized', () => {
document.addEventListener('theme-changed', initCharts);
});
});
</script>
}
}
Pie Chart - Legend
package showcase
import "github.com/axzilla/templui/components"
templ ChartPieLegend() {
@components.Card(components.CardProps{Class: "max-w-sm"}) {
@components.CardContent() {
@components.Chart(components.ChartProps{
Variant: components.ChartVariantPie,
ShowLegend: true,
Data: components.ChartData{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []components.ChartDataset{
{
Data: []float64{7, 16, 5, 20, 14, 15},
},
},
},
})
}
}
}
package components
import "github.com/axzilla/templui/utils"
type ChartVariant string
const (
ChartVariantBar ChartVariant = "bar"
ChartVariantLine ChartVariant = "line"
ChartVariantPie ChartVariant = "pie"
ChartVariantDoughnut ChartVariant = "doughnut"
ChartVariantRadar ChartVariant = "radar"
)
type ChartDataset struct {
Label string `json:"label"`
Data []float64 `json:"data"`
BorderWidth int `json:"borderWidth,omitempty"`
BorderColor string `json:"borderColor,omitempty"`
BackgroundColor string `json:"backgroundColor,omitempty"`
Tension float64 `json:"tension,omitempty"`
Fill bool `json:"fill,omitempty"`
Stepped bool `json:"stepped,omitempty"`
}
type ChartOptions struct {
Responsive bool `json:"responsive,omitempty"`
Legend bool `json:"legend,omitempty"`
}
type ChartData struct {
Labels []string `json:"labels"`
Datasets []ChartDataset `json:"datasets"`
}
type ChartConfig struct {
Type ChartVariant `json:"type"`
Data ChartData `json:"data"`
Options ChartOptions `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 ChartProps um ID und Attributes
type ChartProps struct {
ID string
Variant ChartVariant
Data ChartData
Options ChartOptions
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 ...ChartProps) {
{{ var p ChartProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
// Wenn keine ID gesetzt, generiere eine zufällige
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 := ChartConfig{
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 ChartScripts() {
{{ handle := templ.NewOnceHandle() }}
@handle.Once() {
<!-- Load Chart.js library -->
<script defer nonce={ templ.GetNonce(ctx) } src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
<!-- Initialize charts -->
<script nonce={ templ.GetNonce(ctx) }>
document.addEventListener('DOMContentLoaded', function() {
const chartInstances = {};
function getThemeColors() {
const computedStyle = getComputedStyle(document.documentElement);
return {
foreground: computedStyle.getPropertyValue('--foreground').trim(),
background: computedStyle.getPropertyValue('--background').trim(),
mutedForeground: computedStyle.getPropertyValue('--muted-foreground').trim(),
border: computedStyle.getPropertyValue('--border').trim()
};
}
function initCharts() {
const colors = getThemeColors();
document.querySelectorAll('.chart-container').forEach(container => {
const canvas = container.querySelector('canvas');
if (!canvas) return;
const dataId = canvas.getAttribute('data-chart-id');
const dataElement = document.getElementById(dataId);
if (!dataElement) return;
if (chartInstances[canvas.id]) {
chartInstances[canvas.id].destroy();
}
const chartConfig = JSON.parse(dataElement.textContent);
Chart.defaults.elements.point.radius = 0;
Chart.defaults.elements.point.hoverRadius = 5;
const chart = new Chart(canvas, {
...chartConfig,
options: {
interaction: {
intersect: false,
axis: "xy",
mode: chartConfig.type === "pie" ||
chartConfig.type === "doughnut" ||
chartConfig.type === "bar" ||
chartConfig.type === "radar" ? "nearest" : "index"
},
indexAxis: chartConfig.horizontal ? "y" : "x",
plugins: {
legend: {
display: chartConfig.showLegend || false,
labels: {
color: colors.foreground
}
},
tooltip: {
backgroundColor: colors.background,
bodyColor: colors.mutedForeground,
titleColor: colors.foreground,
borderColor: colors.border,
borderWidth: 1
}
},
scales: chartConfig.type === "radar" ? {
r: {
grid: {
color: colors.border,
display: chartConfig.showYGrid || true
},
ticks: {
color: colors.mutedForeground,
backdropColor: 'transparent',
display: chartConfig.showYLabels || true
},
angleLines: {
color: colors.border,
display: chartConfig.showXGrid || true
},
pointLabels: {
color: colors.foreground,
font: {
size: 12
}
},
border: {
display: chartConfig.showYAxis || false,
color: colors.border
},
beginAtZero: true
}
} : {
x: {
beginAtZero: true,
display: chartConfig.showXLabels || chartConfig.showXGrid || chartConfig.showXAxis,
border: {
display: chartConfig.showXAxis || false
},
ticks: {
display: chartConfig.showXLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showXGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
},
y: {
offset: true,
beginAtZero: true,
display: chartConfig.showYLabels || chartConfig.showYGrid || chartConfig.showYAxis,
border: {
display: chartConfig.showYAxis || false
},
ticks: {
display: chartConfig.showYLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showYGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
}
}
}
});
chartInstances[canvas.id] = chart;
});
}
initCharts();
window.addEventListener('resize', function() {
for (let id in chartInstances) {
chartInstances[id].resize();
}
});
const observer = new MutationObserver(() => {
setTimeout(initCharts, 50);
});
observer.observe(document.documentElement, { attributes: true });
document.addEventListener('alpine:initialized', () => {
document.addEventListener('theme-changed', initCharts);
});
});
</script>
}
}
Doughnut Chart
package showcase
import "github.com/axzilla/templui/components"
templ ChartDoughnut() {
@components.Card(components.CardProps{Class: "max-w-sm"}) {
@components.CardContent() {
@components.Chart(components.ChartProps{
Variant: components.ChartVariantDoughnut,
Data: components.ChartData{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []components.ChartDataset{
{
Data: []float64{7, 16, 5, 20, 14, 15},
},
},
},
})
}
}
}
package components
import "github.com/axzilla/templui/utils"
type ChartVariant string
const (
ChartVariantBar ChartVariant = "bar"
ChartVariantLine ChartVariant = "line"
ChartVariantPie ChartVariant = "pie"
ChartVariantDoughnut ChartVariant = "doughnut"
ChartVariantRadar ChartVariant = "radar"
)
type ChartDataset struct {
Label string `json:"label"`
Data []float64 `json:"data"`
BorderWidth int `json:"borderWidth,omitempty"`
BorderColor string `json:"borderColor,omitempty"`
BackgroundColor string `json:"backgroundColor,omitempty"`
Tension float64 `json:"tension,omitempty"`
Fill bool `json:"fill,omitempty"`
Stepped bool `json:"stepped,omitempty"`
}
type ChartOptions struct {
Responsive bool `json:"responsive,omitempty"`
Legend bool `json:"legend,omitempty"`
}
type ChartData struct {
Labels []string `json:"labels"`
Datasets []ChartDataset `json:"datasets"`
}
type ChartConfig struct {
Type ChartVariant `json:"type"`
Data ChartData `json:"data"`
Options ChartOptions `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 ChartProps um ID und Attributes
type ChartProps struct {
ID string
Variant ChartVariant
Data ChartData
Options ChartOptions
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 ...ChartProps) {
{{ var p ChartProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
// Wenn keine ID gesetzt, generiere eine zufällige
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 := ChartConfig{
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 ChartScripts() {
{{ handle := templ.NewOnceHandle() }}
@handle.Once() {
<!-- Load Chart.js library -->
<script defer nonce={ templ.GetNonce(ctx) } src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
<!-- Initialize charts -->
<script nonce={ templ.GetNonce(ctx) }>
document.addEventListener('DOMContentLoaded', function() {
const chartInstances = {};
function getThemeColors() {
const computedStyle = getComputedStyle(document.documentElement);
return {
foreground: computedStyle.getPropertyValue('--foreground').trim(),
background: computedStyle.getPropertyValue('--background').trim(),
mutedForeground: computedStyle.getPropertyValue('--muted-foreground').trim(),
border: computedStyle.getPropertyValue('--border').trim()
};
}
function initCharts() {
const colors = getThemeColors();
document.querySelectorAll('.chart-container').forEach(container => {
const canvas = container.querySelector('canvas');
if (!canvas) return;
const dataId = canvas.getAttribute('data-chart-id');
const dataElement = document.getElementById(dataId);
if (!dataElement) return;
if (chartInstances[canvas.id]) {
chartInstances[canvas.id].destroy();
}
const chartConfig = JSON.parse(dataElement.textContent);
Chart.defaults.elements.point.radius = 0;
Chart.defaults.elements.point.hoverRadius = 5;
const chart = new Chart(canvas, {
...chartConfig,
options: {
interaction: {
intersect: false,
axis: "xy",
mode: chartConfig.type === "pie" ||
chartConfig.type === "doughnut" ||
chartConfig.type === "bar" ||
chartConfig.type === "radar" ? "nearest" : "index"
},
indexAxis: chartConfig.horizontal ? "y" : "x",
plugins: {
legend: {
display: chartConfig.showLegend || false,
labels: {
color: colors.foreground
}
},
tooltip: {
backgroundColor: colors.background,
bodyColor: colors.mutedForeground,
titleColor: colors.foreground,
borderColor: colors.border,
borderWidth: 1
}
},
scales: chartConfig.type === "radar" ? {
r: {
grid: {
color: colors.border,
display: chartConfig.showYGrid || true
},
ticks: {
color: colors.mutedForeground,
backdropColor: 'transparent',
display: chartConfig.showYLabels || true
},
angleLines: {
color: colors.border,
display: chartConfig.showXGrid || true
},
pointLabels: {
color: colors.foreground,
font: {
size: 12
}
},
border: {
display: chartConfig.showYAxis || false,
color: colors.border
},
beginAtZero: true
}
} : {
x: {
beginAtZero: true,
display: chartConfig.showXLabels || chartConfig.showXGrid || chartConfig.showXAxis,
border: {
display: chartConfig.showXAxis || false
},
ticks: {
display: chartConfig.showXLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showXGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
},
y: {
offset: true,
beginAtZero: true,
display: chartConfig.showYLabels || chartConfig.showYGrid || chartConfig.showYAxis,
border: {
display: chartConfig.showYAxis || false
},
ticks: {
display: chartConfig.showYLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showYGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
}
}
}
});
chartInstances[canvas.id] = chart;
});
}
initCharts();
window.addEventListener('resize', function() {
for (let id in chartInstances) {
chartInstances[id].resize();
}
});
const observer = new MutationObserver(() => {
setTimeout(initCharts, 50);
});
observer.observe(document.documentElement, { attributes: true });
document.addEventListener('alpine:initialized', () => {
document.addEventListener('theme-changed', initCharts);
});
});
</script>
}
}
Doughnut Chart - Stacked
package showcase
import "github.com/axzilla/templui/components"
templ ChartDoughnutStacked() {
@components.Card(components.CardProps{Class: "max-w-sm"}) {
@components.CardContent() {
@components.Chart(components.ChartProps{
Variant: components.ChartVariantDoughnut,
Data: components.ChartData{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []components.ChartDataset{
{
Data: []float64{3, 9, 3, 12, 7, 8},
Label: "Mobile",
},
{
Data: []float64{7, 16, 5, 20, 14, 15},
Label: "Desktop",
},
},
},
})
}
}
}
package components
import "github.com/axzilla/templui/utils"
type ChartVariant string
const (
ChartVariantBar ChartVariant = "bar"
ChartVariantLine ChartVariant = "line"
ChartVariantPie ChartVariant = "pie"
ChartVariantDoughnut ChartVariant = "doughnut"
ChartVariantRadar ChartVariant = "radar"
)
type ChartDataset struct {
Label string `json:"label"`
Data []float64 `json:"data"`
BorderWidth int `json:"borderWidth,omitempty"`
BorderColor string `json:"borderColor,omitempty"`
BackgroundColor string `json:"backgroundColor,omitempty"`
Tension float64 `json:"tension,omitempty"`
Fill bool `json:"fill,omitempty"`
Stepped bool `json:"stepped,omitempty"`
}
type ChartOptions struct {
Responsive bool `json:"responsive,omitempty"`
Legend bool `json:"legend,omitempty"`
}
type ChartData struct {
Labels []string `json:"labels"`
Datasets []ChartDataset `json:"datasets"`
}
type ChartConfig struct {
Type ChartVariant `json:"type"`
Data ChartData `json:"data"`
Options ChartOptions `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 ChartProps um ID und Attributes
type ChartProps struct {
ID string
Variant ChartVariant
Data ChartData
Options ChartOptions
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 ...ChartProps) {
{{ var p ChartProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
// Wenn keine ID gesetzt, generiere eine zufällige
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 := ChartConfig{
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 ChartScripts() {
{{ handle := templ.NewOnceHandle() }}
@handle.Once() {
<!-- Load Chart.js library -->
<script defer nonce={ templ.GetNonce(ctx) } src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
<!-- Initialize charts -->
<script nonce={ templ.GetNonce(ctx) }>
document.addEventListener('DOMContentLoaded', function() {
const chartInstances = {};
function getThemeColors() {
const computedStyle = getComputedStyle(document.documentElement);
return {
foreground: computedStyle.getPropertyValue('--foreground').trim(),
background: computedStyle.getPropertyValue('--background').trim(),
mutedForeground: computedStyle.getPropertyValue('--muted-foreground').trim(),
border: computedStyle.getPropertyValue('--border').trim()
};
}
function initCharts() {
const colors = getThemeColors();
document.querySelectorAll('.chart-container').forEach(container => {
const canvas = container.querySelector('canvas');
if (!canvas) return;
const dataId = canvas.getAttribute('data-chart-id');
const dataElement = document.getElementById(dataId);
if (!dataElement) return;
if (chartInstances[canvas.id]) {
chartInstances[canvas.id].destroy();
}
const chartConfig = JSON.parse(dataElement.textContent);
Chart.defaults.elements.point.radius = 0;
Chart.defaults.elements.point.hoverRadius = 5;
const chart = new Chart(canvas, {
...chartConfig,
options: {
interaction: {
intersect: false,
axis: "xy",
mode: chartConfig.type === "pie" ||
chartConfig.type === "doughnut" ||
chartConfig.type === "bar" ||
chartConfig.type === "radar" ? "nearest" : "index"
},
indexAxis: chartConfig.horizontal ? "y" : "x",
plugins: {
legend: {
display: chartConfig.showLegend || false,
labels: {
color: colors.foreground
}
},
tooltip: {
backgroundColor: colors.background,
bodyColor: colors.mutedForeground,
titleColor: colors.foreground,
borderColor: colors.border,
borderWidth: 1
}
},
scales: chartConfig.type === "radar" ? {
r: {
grid: {
color: colors.border,
display: chartConfig.showYGrid || true
},
ticks: {
color: colors.mutedForeground,
backdropColor: 'transparent',
display: chartConfig.showYLabels || true
},
angleLines: {
color: colors.border,
display: chartConfig.showXGrid || true
},
pointLabels: {
color: colors.foreground,
font: {
size: 12
}
},
border: {
display: chartConfig.showYAxis || false,
color: colors.border
},
beginAtZero: true
}
} : {
x: {
beginAtZero: true,
display: chartConfig.showXLabels || chartConfig.showXGrid || chartConfig.showXAxis,
border: {
display: chartConfig.showXAxis || false
},
ticks: {
display: chartConfig.showXLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showXGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
},
y: {
offset: true,
beginAtZero: true,
display: chartConfig.showYLabels || chartConfig.showYGrid || chartConfig.showYAxis,
border: {
display: chartConfig.showYAxis || false
},
ticks: {
display: chartConfig.showYLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showYGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
}
}
}
});
chartInstances[canvas.id] = chart;
});
}
initCharts();
window.addEventListener('resize', function() {
for (let id in chartInstances) {
chartInstances[id].resize();
}
});
const observer = new MutationObserver(() => {
setTimeout(initCharts, 50);
});
observer.observe(document.documentElement, { attributes: true });
document.addEventListener('alpine:initialized', () => {
document.addEventListener('theme-changed', initCharts);
});
});
</script>
}
}
Doughnut Chart - Legend
package showcase
import "github.com/axzilla/templui/components"
templ ChartDoughnutLegend() {
@components.Card(components.CardProps{Class: "max-w-sm"}) {
@components.CardContent() {
@components.Chart(components.ChartProps{
Variant: components.ChartVariantDoughnut,
ShowLegend: true,
Data: components.ChartData{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []components.ChartDataset{
{
Data: []float64{3, 9, 3, 12, 7, 8},
Label: "Mobile",
},
},
},
})
}
}
}
package components
import "github.com/axzilla/templui/utils"
type ChartVariant string
const (
ChartVariantBar ChartVariant = "bar"
ChartVariantLine ChartVariant = "line"
ChartVariantPie ChartVariant = "pie"
ChartVariantDoughnut ChartVariant = "doughnut"
ChartVariantRadar ChartVariant = "radar"
)
type ChartDataset struct {
Label string `json:"label"`
Data []float64 `json:"data"`
BorderWidth int `json:"borderWidth,omitempty"`
BorderColor string `json:"borderColor,omitempty"`
BackgroundColor string `json:"backgroundColor,omitempty"`
Tension float64 `json:"tension,omitempty"`
Fill bool `json:"fill,omitempty"`
Stepped bool `json:"stepped,omitempty"`
}
type ChartOptions struct {
Responsive bool `json:"responsive,omitempty"`
Legend bool `json:"legend,omitempty"`
}
type ChartData struct {
Labels []string `json:"labels"`
Datasets []ChartDataset `json:"datasets"`
}
type ChartConfig struct {
Type ChartVariant `json:"type"`
Data ChartData `json:"data"`
Options ChartOptions `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 ChartProps um ID und Attributes
type ChartProps struct {
ID string
Variant ChartVariant
Data ChartData
Options ChartOptions
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 ...ChartProps) {
{{ var p ChartProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
// Wenn keine ID gesetzt, generiere eine zufällige
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 := ChartConfig{
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 ChartScripts() {
{{ handle := templ.NewOnceHandle() }}
@handle.Once() {
<!-- Load Chart.js library -->
<script defer nonce={ templ.GetNonce(ctx) } src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
<!-- Initialize charts -->
<script nonce={ templ.GetNonce(ctx) }>
document.addEventListener('DOMContentLoaded', function() {
const chartInstances = {};
function getThemeColors() {
const computedStyle = getComputedStyle(document.documentElement);
return {
foreground: computedStyle.getPropertyValue('--foreground').trim(),
background: computedStyle.getPropertyValue('--background').trim(),
mutedForeground: computedStyle.getPropertyValue('--muted-foreground').trim(),
border: computedStyle.getPropertyValue('--border').trim()
};
}
function initCharts() {
const colors = getThemeColors();
document.querySelectorAll('.chart-container').forEach(container => {
const canvas = container.querySelector('canvas');
if (!canvas) return;
const dataId = canvas.getAttribute('data-chart-id');
const dataElement = document.getElementById(dataId);
if (!dataElement) return;
if (chartInstances[canvas.id]) {
chartInstances[canvas.id].destroy();
}
const chartConfig = JSON.parse(dataElement.textContent);
Chart.defaults.elements.point.radius = 0;
Chart.defaults.elements.point.hoverRadius = 5;
const chart = new Chart(canvas, {
...chartConfig,
options: {
interaction: {
intersect: false,
axis: "xy",
mode: chartConfig.type === "pie" ||
chartConfig.type === "doughnut" ||
chartConfig.type === "bar" ||
chartConfig.type === "radar" ? "nearest" : "index"
},
indexAxis: chartConfig.horizontal ? "y" : "x",
plugins: {
legend: {
display: chartConfig.showLegend || false,
labels: {
color: colors.foreground
}
},
tooltip: {
backgroundColor: colors.background,
bodyColor: colors.mutedForeground,
titleColor: colors.foreground,
borderColor: colors.border,
borderWidth: 1
}
},
scales: chartConfig.type === "radar" ? {
r: {
grid: {
color: colors.border,
display: chartConfig.showYGrid || true
},
ticks: {
color: colors.mutedForeground,
backdropColor: 'transparent',
display: chartConfig.showYLabels || true
},
angleLines: {
color: colors.border,
display: chartConfig.showXGrid || true
},
pointLabels: {
color: colors.foreground,
font: {
size: 12
}
},
border: {
display: chartConfig.showYAxis || false,
color: colors.border
},
beginAtZero: true
}
} : {
x: {
beginAtZero: true,
display: chartConfig.showXLabels || chartConfig.showXGrid || chartConfig.showXAxis,
border: {
display: chartConfig.showXAxis || false
},
ticks: {
display: chartConfig.showXLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showXGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
},
y: {
offset: true,
beginAtZero: true,
display: chartConfig.showYLabels || chartConfig.showYGrid || chartConfig.showYAxis,
border: {
display: chartConfig.showYAxis || false
},
ticks: {
display: chartConfig.showYLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showYGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
}
}
}
});
chartInstances[canvas.id] = chart;
});
}
initCharts();
window.addEventListener('resize', function() {
for (let id in chartInstances) {
chartInstances[id].resize();
}
});
const observer = new MutationObserver(() => {
setTimeout(initCharts, 50);
});
observer.observe(document.documentElement, { attributes: true });
document.addEventListener('alpine:initialized', () => {
document.addEventListener('theme-changed', initCharts);
});
});
</script>
}
}
Radar Chart
package showcase
import "github.com/axzilla/templui/components"
templ ChartRadar() {
@components.Card(components.CardProps{Class: "max-w-sm"}) {
@components.CardContent() {
@components.Chart(components.ChartProps{
Variant: components.ChartVariantRadar,
Data: components.ChartData{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []components.ChartDataset{
{
Data: []float64{3, 9, 3, 12, 7, 8},
},
},
},
})
}
}
}
package components
import "github.com/axzilla/templui/utils"
type ChartVariant string
const (
ChartVariantBar ChartVariant = "bar"
ChartVariantLine ChartVariant = "line"
ChartVariantPie ChartVariant = "pie"
ChartVariantDoughnut ChartVariant = "doughnut"
ChartVariantRadar ChartVariant = "radar"
)
type ChartDataset struct {
Label string `json:"label"`
Data []float64 `json:"data"`
BorderWidth int `json:"borderWidth,omitempty"`
BorderColor string `json:"borderColor,omitempty"`
BackgroundColor string `json:"backgroundColor,omitempty"`
Tension float64 `json:"tension,omitempty"`
Fill bool `json:"fill,omitempty"`
Stepped bool `json:"stepped,omitempty"`
}
type ChartOptions struct {
Responsive bool `json:"responsive,omitempty"`
Legend bool `json:"legend,omitempty"`
}
type ChartData struct {
Labels []string `json:"labels"`
Datasets []ChartDataset `json:"datasets"`
}
type ChartConfig struct {
Type ChartVariant `json:"type"`
Data ChartData `json:"data"`
Options ChartOptions `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 ChartProps um ID und Attributes
type ChartProps struct {
ID string
Variant ChartVariant
Data ChartData
Options ChartOptions
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 ...ChartProps) {
{{ var p ChartProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
// Wenn keine ID gesetzt, generiere eine zufällige
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 := ChartConfig{
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 ChartScripts() {
{{ handle := templ.NewOnceHandle() }}
@handle.Once() {
<!-- Load Chart.js library -->
<script defer nonce={ templ.GetNonce(ctx) } src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
<!-- Initialize charts -->
<script nonce={ templ.GetNonce(ctx) }>
document.addEventListener('DOMContentLoaded', function() {
const chartInstances = {};
function getThemeColors() {
const computedStyle = getComputedStyle(document.documentElement);
return {
foreground: computedStyle.getPropertyValue('--foreground').trim(),
background: computedStyle.getPropertyValue('--background').trim(),
mutedForeground: computedStyle.getPropertyValue('--muted-foreground').trim(),
border: computedStyle.getPropertyValue('--border').trim()
};
}
function initCharts() {
const colors = getThemeColors();
document.querySelectorAll('.chart-container').forEach(container => {
const canvas = container.querySelector('canvas');
if (!canvas) return;
const dataId = canvas.getAttribute('data-chart-id');
const dataElement = document.getElementById(dataId);
if (!dataElement) return;
if (chartInstances[canvas.id]) {
chartInstances[canvas.id].destroy();
}
const chartConfig = JSON.parse(dataElement.textContent);
Chart.defaults.elements.point.radius = 0;
Chart.defaults.elements.point.hoverRadius = 5;
const chart = new Chart(canvas, {
...chartConfig,
options: {
interaction: {
intersect: false,
axis: "xy",
mode: chartConfig.type === "pie" ||
chartConfig.type === "doughnut" ||
chartConfig.type === "bar" ||
chartConfig.type === "radar" ? "nearest" : "index"
},
indexAxis: chartConfig.horizontal ? "y" : "x",
plugins: {
legend: {
display: chartConfig.showLegend || false,
labels: {
color: colors.foreground
}
},
tooltip: {
backgroundColor: colors.background,
bodyColor: colors.mutedForeground,
titleColor: colors.foreground,
borderColor: colors.border,
borderWidth: 1
}
},
scales: chartConfig.type === "radar" ? {
r: {
grid: {
color: colors.border,
display: chartConfig.showYGrid || true
},
ticks: {
color: colors.mutedForeground,
backdropColor: 'transparent',
display: chartConfig.showYLabels || true
},
angleLines: {
color: colors.border,
display: chartConfig.showXGrid || true
},
pointLabels: {
color: colors.foreground,
font: {
size: 12
}
},
border: {
display: chartConfig.showYAxis || false,
color: colors.border
},
beginAtZero: true
}
} : {
x: {
beginAtZero: true,
display: chartConfig.showXLabels || chartConfig.showXGrid || chartConfig.showXAxis,
border: {
display: chartConfig.showXAxis || false
},
ticks: {
display: chartConfig.showXLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showXGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
},
y: {
offset: true,
beginAtZero: true,
display: chartConfig.showYLabels || chartConfig.showYGrid || chartConfig.showYAxis,
border: {
display: chartConfig.showYAxis || false
},
ticks: {
display: chartConfig.showYLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showYGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
}
}
}
});
chartInstances[canvas.id] = chart;
});
}
initCharts();
window.addEventListener('resize', function() {
for (let id in chartInstances) {
chartInstances[id].resize();
}
});
const observer = new MutationObserver(() => {
setTimeout(initCharts, 50);
});
observer.observe(document.documentElement, { attributes: true });
document.addEventListener('alpine:initialized', () => {
document.addEventListener('theme-changed', initCharts);
});
});
</script>
}
}
Radar Chart - Stacked
package showcase
import "github.com/axzilla/templui/components"
templ CharRadarStacked() {
@components.Card(components.CardProps{Class: "max-w-sm"}) {
@components.CardContent() {
@components.Chart(components.ChartProps{
Variant: components.ChartVariantRadar,
Data: components.ChartData{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []components.ChartDataset{
{
Data: []float64{15, 9, 3, 12, 7, 8},
Label: "Mobile",
},
{
Data: []float64{7, 16, 5, 20, 14, 15},
Label: "Desktop",
},
},
},
})
}
}
}
package components
import "github.com/axzilla/templui/utils"
type ChartVariant string
const (
ChartVariantBar ChartVariant = "bar"
ChartVariantLine ChartVariant = "line"
ChartVariantPie ChartVariant = "pie"
ChartVariantDoughnut ChartVariant = "doughnut"
ChartVariantRadar ChartVariant = "radar"
)
type ChartDataset struct {
Label string `json:"label"`
Data []float64 `json:"data"`
BorderWidth int `json:"borderWidth,omitempty"`
BorderColor string `json:"borderColor,omitempty"`
BackgroundColor string `json:"backgroundColor,omitempty"`
Tension float64 `json:"tension,omitempty"`
Fill bool `json:"fill,omitempty"`
Stepped bool `json:"stepped,omitempty"`
}
type ChartOptions struct {
Responsive bool `json:"responsive,omitempty"`
Legend bool `json:"legend,omitempty"`
}
type ChartData struct {
Labels []string `json:"labels"`
Datasets []ChartDataset `json:"datasets"`
}
type ChartConfig struct {
Type ChartVariant `json:"type"`
Data ChartData `json:"data"`
Options ChartOptions `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 ChartProps um ID und Attributes
type ChartProps struct {
ID string
Variant ChartVariant
Data ChartData
Options ChartOptions
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 ...ChartProps) {
{{ var p ChartProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
// Wenn keine ID gesetzt, generiere eine zufällige
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 := ChartConfig{
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 ChartScripts() {
{{ handle := templ.NewOnceHandle() }}
@handle.Once() {
<!-- Load Chart.js library -->
<script defer nonce={ templ.GetNonce(ctx) } src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
<!-- Initialize charts -->
<script nonce={ templ.GetNonce(ctx) }>
document.addEventListener('DOMContentLoaded', function() {
const chartInstances = {};
function getThemeColors() {
const computedStyle = getComputedStyle(document.documentElement);
return {
foreground: computedStyle.getPropertyValue('--foreground').trim(),
background: computedStyle.getPropertyValue('--background').trim(),
mutedForeground: computedStyle.getPropertyValue('--muted-foreground').trim(),
border: computedStyle.getPropertyValue('--border').trim()
};
}
function initCharts() {
const colors = getThemeColors();
document.querySelectorAll('.chart-container').forEach(container => {
const canvas = container.querySelector('canvas');
if (!canvas) return;
const dataId = canvas.getAttribute('data-chart-id');
const dataElement = document.getElementById(dataId);
if (!dataElement) return;
if (chartInstances[canvas.id]) {
chartInstances[canvas.id].destroy();
}
const chartConfig = JSON.parse(dataElement.textContent);
Chart.defaults.elements.point.radius = 0;
Chart.defaults.elements.point.hoverRadius = 5;
const chart = new Chart(canvas, {
...chartConfig,
options: {
interaction: {
intersect: false,
axis: "xy",
mode: chartConfig.type === "pie" ||
chartConfig.type === "doughnut" ||
chartConfig.type === "bar" ||
chartConfig.type === "radar" ? "nearest" : "index"
},
indexAxis: chartConfig.horizontal ? "y" : "x",
plugins: {
legend: {
display: chartConfig.showLegend || false,
labels: {
color: colors.foreground
}
},
tooltip: {
backgroundColor: colors.background,
bodyColor: colors.mutedForeground,
titleColor: colors.foreground,
borderColor: colors.border,
borderWidth: 1
}
},
scales: chartConfig.type === "radar" ? {
r: {
grid: {
color: colors.border,
display: chartConfig.showYGrid || true
},
ticks: {
color: colors.mutedForeground,
backdropColor: 'transparent',
display: chartConfig.showYLabels || true
},
angleLines: {
color: colors.border,
display: chartConfig.showXGrid || true
},
pointLabels: {
color: colors.foreground,
font: {
size: 12
}
},
border: {
display: chartConfig.showYAxis || false,
color: colors.border
},
beginAtZero: true
}
} : {
x: {
beginAtZero: true,
display: chartConfig.showXLabels || chartConfig.showXGrid || chartConfig.showXAxis,
border: {
display: chartConfig.showXAxis || false
},
ticks: {
display: chartConfig.showXLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showXGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
},
y: {
offset: true,
beginAtZero: true,
display: chartConfig.showYLabels || chartConfig.showYGrid || chartConfig.showYAxis,
border: {
display: chartConfig.showYAxis || false
},
ticks: {
display: chartConfig.showYLabels || false,
color: colors.mutedForeground
},
grid: {
display: chartConfig.showYGrid || false,
color: colors.border
},
stacked: chartConfig.stacked
}
}
}
});
chartInstances[canvas.id] = chart;
});
}
initCharts();
window.addEventListener('resize', function() {
for (let id in chartInstances) {
chartInstances[id].resize();
}
});
const observer = new MutationObserver(() => {
setTimeout(initCharts, 50);
});
observer.observe(document.documentElement, { attributes: true });
document.addEventListener('alpine:initialized', () => {
document.addEventListener('theme-changed', initCharts);
});
});
</script>
}
}