package showcase
import (
"github.com/axzilla/templui/internal/components/card"
"github.com/axzilla/templui/internal/components/chart"
)
templ ChartDefault() {
@card.Card(card.Props{Class: "max-w-sm"}) {
@card.Content() {
@chart.Chart(chart.Props{
Variant: chart.VariantBar,
ShowYGrid: true,
ShowXLabels: true,
Data: chart.Data{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []chart.Dataset{
{
Data: []float64{12, 19, 12, 5, 2, 3},
},
},
},
})
}
}
}
Installation
templui add chart
Copy and paste the following code into your project:
package chart import "github.com/axzilla/templui/internal/utils" type Variant string const ( VariantBar Variant = "bar" VariantLine Variant = "line" VariantPie Variant = "pie" VariantDoughnut Variant = "doughnut" VariantRadar Variant = "radar" ) type Dataset struct { Label string `json:"label"` Data []float64 `json:"data"` BorderWidth int `json:"borderWidth,omitempty"` BorderColor interface{} `json:"borderColor,omitempty"` BackgroundColor interface{} `json:"backgroundColor,omitempty"` Tension float64 `json:"tension,omitempty"` Fill bool `json:"fill,omitempty"` Stepped bool `json:"stepped,omitempty"` } type Options struct { Responsive bool `json:"responsive,omitempty"` Legend bool `json:"legend,omitempty"` } type Data struct { Labels []string `json:"labels"` Datasets []Dataset `json:"datasets"` } type Config struct { Type Variant `json:"type"` Data Data `json:"data"` Options Options `json:"options,omitempty"` ShowLegend bool `json:"showLegend,omitempty"` ShowXAxis bool `json:"showXAxis"` ShowYAxis bool `json:"showYAxis"` ShowXLabels bool `json:"showXLabels"` ShowYLabels bool `json:"showYLabels"` ShowXGrid bool `json:"showXGrid"` ShowYGrid bool `json:"showYGrid"` Horizontal bool `json:"horizontal"` Stacked bool `json:"stacked"` } // Erweiterung des Props um ID und Attributes type Props struct { ID string Variant Variant Data Data Options Options ShowLegend bool ShowXAxis bool ShowYAxis bool ShowXLabels bool ShowYLabels bool ShowXGrid bool ShowYGrid bool Horizontal bool Stacked bool Class string Attributes templ.Attributes } templ Chart(props ...Props) { @Script() {{ var p Props }} if len(props) > 0 { {{ p = props[0] }} } if p.ID == "" { {{ p.ID = "chart-" + utils.RandomID() }} } {{ canvasId := p.ID + "-canvas" }} {{ dataId := p.ID + "-data" }} <div id={ p.ID } class={ utils.TwMerge( "chart-container relative", p.Class), } { p.Attributes... } > <canvas id={ canvasId } data-chart-id={ dataId }></canvas> </div> {{ chartConfig := Config{ Type: p.Variant, Data: p.Data, Options: p.Options, ShowLegend: p.ShowLegend, ShowXAxis: p.ShowXAxis, ShowYAxis: p.ShowYAxis, ShowXLabels: p.ShowXLabels, ShowYLabels: p.ShowYLabels, ShowXGrid: p.ShowXGrid, ShowYGrid: p.ShowYGrid, Horizontal: p.Horizontal, Stacked: p.Stacked, } }} @templ.JSONScript(dataId, chartConfig) } templ Script() { <script defer nonce={ templ.GetNonce(ctx) } src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script> <script nonce={ templ.GetNonce(ctx) }> window.chartInstances = window.chartInstances || {}; (function() { // IIFE if (!window.chartScriptInitialized) { function getThemeColors() { const style = getComputedStyle(document.documentElement); return { foreground: style.getPropertyValue('--foreground').trim() || '#000', background: style.getPropertyValue('--background').trim() || '#fff', mutedForeground: style.getPropertyValue('--muted-foreground').trim() || '#666', border: style.getPropertyValue('--border').trim() || '#ccc' }; } function initChart(canvas) { if (!canvas || !canvas.id || !canvas.hasAttribute('data-chart-id')) return; if (window.chartInstances[canvas.id]) { cleanupChart(canvas); } const dataId = canvas.getAttribute('data-chart-id'); const dataElement = document.getElementById(dataId); if (!dataElement) return; try { const chartConfig = JSON.parse(dataElement.textContent); const colors = getThemeColors(); Chart.defaults.elements.point.radius = 0; Chart.defaults.elements.point.hoverRadius = 5; const isComplexChart = ["pie", "doughnut", "bar", "radar"].includes(chartConfig.type); const legendOptions = { display: chartConfig.showLegend || false, labels: { color: colors.foreground } }; const tooltipOptions = { backgroundColor: colors.background, bodyColor: colors.mutedForeground, titleColor: colors.foreground, borderColor: colors.border, borderWidth: 1 }; const scalesOptions = chartConfig.type === "radar" ? { r: { grid: { color: colors.border, display: chartConfig.showYGrid !== false }, ticks: { color: colors.mutedForeground, backdropColor: "transparent", display: chartConfig.showYLabels !== false }, angleLines: { color: colors.border, display: chartConfig.showXGrid !== false }, pointLabels: { color: colors.foreground, font: { size: 12 } }, border: { display: chartConfig.showYAxis !== false, color: colors.border }, beginAtZero: true } } : { x: { beginAtZero: true, display: chartConfig.showXLabels !== false || chartConfig.showXGrid !== false || chartConfig.showXAxis !== false, border: { display: chartConfig.showXAxis !== false, color: colors.border }, ticks: { display: chartConfig.showXLabels !== false, color: colors.mutedForeground }, grid: { display: chartConfig.showXGrid !== false, color: colors.border }, stacked: chartConfig.stacked || false }, y: { offset: true, beginAtZero: true, display: chartConfig.showYLabels !== false || chartConfig.showYGrid !== false || chartConfig.showYAxis !== false, border: { display: chartConfig.showYAxis !== false, color: colors.border }, ticks: { display: chartConfig.showYLabels !== false, color: colors.mutedForeground }, grid: { display: chartConfig.showYGrid !== false, color: colors.border }, stacked: chartConfig.stacked || false } }; const finalChartConfig = { ...chartConfig, options: { responsive: true, maintainAspectRatio: false, interaction: { intersect: isComplexChart ? true : false, axis: "xy", mode: isComplexChart ? "nearest" : "index" }, indexAxis: chartConfig.horizontal ? "y" : "x", plugins: { legend: legendOptions, tooltip: tooltipOptions }, scales: scalesOptions } }; window.chartInstances[canvas.id] = new Chart(canvas, finalChartConfig); } catch (e) {} } function cleanupChart(canvas) { if (!canvas || !canvas.id || !window.chartInstances[canvas.id]) return; try { window.chartInstances[canvas.id].destroy(); } finally { delete window.chartInstances[canvas.id]; } } function initAllComponents(root = document) { if (typeof Chart === "undefined") return; for (const canvas of root.querySelectorAll("canvas[data-chart-id]")) { initChart(canvas); } } function waitForChartAndInit() { if (typeof Chart !== "undefined") { initAllComponents(); } else { setTimeout(waitForChartAndInit, 100); } } document.addEventListener("DOMContentLoaded", waitForChartAndInit); document.body.addEventListener("htmx:beforeSwap", (event) => { const el = event.detail.elt; if (el instanceof Element) { for (const canvas of el.querySelectorAll("canvas[data-chart-id]")) { cleanupChart(canvas); } if (el.matches("canvas[data-chart-id]")) { cleanupChart(el); } } }); document.body.addEventListener("htmx:afterSwap", (event) => { const target = event.detail.elt; if (target instanceof Element) { function tryInit(attempt = 1) { if (typeof Chart !== "undefined") { initAllComponents(target); } else if (attempt < 10) { setTimeout(() => tryInit(attempt + 1), 100); } } tryInit(); } }); const observer = new MutationObserver(() => { let timeout; clearTimeout(timeout); timeout = setTimeout(() => { for (const canvas of document.querySelectorAll("canvas[data-chart-id]")) { if (window.chartInstances[canvas.id]) { cleanupChart(canvas); initChart(canvas); } } }, 50); }); observer.observe(document.documentElement, { attributes: true, attributeFilter: ["class", "style"] }); window.chartScriptInitialized = true; } })(); // End of IIFE </script> }
Update the import paths to match your project setup.
Examples
Bar Chart - Multiple
package showcase
import (
"github.com/axzilla/templui/internal/components/card"
"github.com/axzilla/templui/internal/components/chart"
)
templ ChartBarMultiple() {
@card.Card(card.Props{Class: "max-w-sm"}) {
@card.Content() {
@chart.Chart(chart.Props{
Variant: chart.VariantBar,
ShowYGrid: true,
ShowXLabels: true,
Data: chart.Data{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []chart.Dataset{
{
Label: "Mobile",
Data: []float64{12, 19, 12, 5, 2, 3},
},
{
Label: "Desktop",
Data: []float64{3, 9, 18, 3, 21, 13},
},
},
},
})
}
}
}
Bar Chart - Horizontal
package showcase
import (
"github.com/axzilla/templui/internal/components/card"
"github.com/axzilla/templui/internal/components/chart"
)
templ ChartBarHorizontal() {
@card.Card(card.Props{Class: "max-w-sm"}) {
@card.Content() {
@chart.Chart(chart.Props{
Variant: chart.VariantBar,
Horizontal: true,
ShowXGrid: true,
ShowYLabels: true,
Data: chart.Data{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []chart.Dataset{
{
Data: []float64{12, 19, 12, 5, 2, 3},
},
},
},
})
}
}
}
Bar Chart - Negative
package showcase
import (
"github.com/axzilla/templui/internal/components/card"
"github.com/axzilla/templui/internal/components/chart"
)
templ ChartBarNegative() {
@card.Card(card.Props{Class: "max-w-sm"}) {
@card.Content() {
@chart.Chart(chart.Props{
Variant: chart.VariantBar,
ShowYGrid: true,
ShowXLabels: true,
Data: chart.Data{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []chart.Dataset{
{
Data: []float64{12, 19, -12, 5, -2, 3},
},
},
},
})
}
}
}
Bar Chart - Stacked
package showcase
import (
"github.com/axzilla/templui/internal/components/card"
"github.com/axzilla/templui/internal/components/chart"
)
templ ChartBarStacked() {
@card.Card(card.Props{Class: "max-w-sm"}) {
@card.Content() {
@chart.Chart(chart.Props{
Variant: chart.VariantBar,
ShowYGrid: true,
ShowXLabels: true,
Stacked: true,
Data: chart.Data{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []chart.Dataset{
{
Label: "Mobile",
Data: []float64{12, 19, 12, 5, 2, 3},
},
{
Label: "Desktop",
Data: []float64{3, 9, 18, 3, 21, 13},
},
},
},
})
}
}
}
Line Chart
package showcase
import (
"github.com/axzilla/templui/internal/components/card"
"github.com/axzilla/templui/internal/components/chart"
)
templ ChartLine() {
@card.Card(card.Props{Class: "max-w-sm"}) {
@card.Content() {
@chart.Chart(chart.Props{
Variant: chart.VariantLine,
ShowYGrid: true,
ShowXLabels: true,
Data: chart.Data{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []chart.Dataset{
{
Data: []float64{12, 3, 9, 3, 12, 7},
Tension: 0.5,
},
},
},
})
}
}
}
Line Chart - Linear
package showcase
import (
"github.com/axzilla/templui/internal/components/card"
"github.com/axzilla/templui/internal/components/chart"
)
templ ChartLineLinear() {
@card.Card(card.Props{Class: "max-w-sm"}) {
@card.Content() {
@chart.Chart(chart.Props{
Variant: chart.VariantLine,
ShowYGrid: true,
ShowXLabels: true,
Data: chart.Data{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []chart.Dataset{
{
Data: []float64{12, 3, 9, 3, 12, 7},
},
},
},
})
}
}
}
Line Chart - Step
package showcase
import (
"github.com/axzilla/templui/internal/components/card"
"github.com/axzilla/templui/internal/components/chart"
)
templ ChartLineStep() {
@card.Card(card.Props{Class: "max-w-sm"}) {
@card.Content() {
@chart.Chart(chart.Props{
Variant: chart.VariantLine,
ShowYGrid: true,
ShowXLabels: true,
Data: chart.Data{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []chart.Dataset{
{
Data: []float64{12, 3, 9, 3, 12, 7},
Stepped: true,
},
},
},
})
}
}
}
Line Chart - Multiple
package showcase
import (
"github.com/axzilla/templui/internal/components/card"
"github.com/axzilla/templui/internal/components/chart"
)
templ ChartLineMultiple() {
@card.Card(card.Props{Class: "max-w-sm"}) {
@card.Content() {
@chart.Chart(chart.Props{
Variant: chart.VariantLine,
ShowYGrid: true,
ShowXLabels: true,
Data: chart.Data{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []chart.Dataset{
{
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,
},
},
},
})
}
}
}
Area Chart
package showcase
import (
"github.com/axzilla/templui/internal/components/card"
"github.com/axzilla/templui/internal/components/chart"
)
templ ChartArea() {
@card.Card(card.Props{Class: "max-w-sm"}) {
@card.Content() {
@chart.Chart(chart.Props{
Variant: chart.VariantLine,
ShowYGrid: true,
ShowXLabels: true,
Data: chart.Data{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []chart.Dataset{
{
Data: []float64{3, 9, 3, 12, 7, 8},
Tension: 0.5,
BorderWidth: 1,
Fill: true,
},
},
},
})
}
}
}
Area Chart - Linear
package showcase
import (
"github.com/axzilla/templui/internal/components/card"
"github.com/axzilla/templui/internal/components/chart"
)
templ ChartAreaLinear() {
@card.Card(card.Props{Class: "max-w-sm"}) {
@card.Content() {
@chart.Chart(chart.Props{
Variant: chart.VariantLine,
ShowYGrid: true,
ShowXLabels: true,
Data: chart.Data{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []chart.Dataset{
{
Data: []float64{3, 9, 3, 12, 7, 8},
BorderWidth: 1,
Fill: true,
},
},
},
})
}
}
}
Area Chart - Step
package showcase
import (
"github.com/axzilla/templui/internal/components/card"
"github.com/axzilla/templui/internal/components/chart"
)
templ ChartAreaStep() {
@card.Card(card.Props{Class: "max-w-sm"}) {
@card.Content() {
@chart.Chart(chart.Props{
Variant: chart.VariantLine,
ShowYGrid: true,
ShowXLabels: true,
Data: chart.Data{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []chart.Dataset{
{
Data: []float64{3, 9, 3, 12, 7, 8},
BorderWidth: 1,
Fill: true,
Stepped: true,
},
},
},
})
}
}
}
Area Chart - Stacked
package showcase
import (
"github.com/axzilla/templui/internal/components/card"
"github.com/axzilla/templui/internal/components/chart"
)
templ ChartAreaStacked() {
@card.Card(card.Props{Class: "max-w-sm"}) {
@card.Content() {
@chart.Chart(chart.Props{
Variant: chart.VariantLine,
ShowYGrid: true,
ShowXLabels: true,
Data: chart.Data{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []chart.Dataset{
{
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",
},
},
},
})
}
}
}
Pie Chart
package showcase
import (
"github.com/axzilla/templui/internal/components/card"
"github.com/axzilla/templui/internal/components/chart"
)
templ ChartPie() {
@card.Card(card.Props{Class: "max-w-sm"}) {
@card.Content() {
@chart.Chart(chart.Props{
Variant: chart.VariantPie,
Data: chart.Data{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []chart.Dataset{
{
Data: []float64{3, 9, 3, 12, 7, 8},
},
},
},
})
}
}
}
Pie Chart - Stacked
package showcase
import (
"github.com/axzilla/templui/internal/components/card"
"github.com/axzilla/templui/internal/components/chart"
)
templ ChartPieStacked() {
@card.Card(card.Props{Class: "max-w-sm"}) {
@card.Content() {
@chart.Chart(chart.Props{
Variant: chart.VariantPie,
Data: chart.Data{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []chart.Dataset{
{
Data: []float64{3, 9, 3, 12, 7, 8},
Label: "Mobile",
},
{
Data: []float64{7, 16, 5, 20, 14, 15},
Label: "Desktop",
},
},
},
})
}
}
}
Pie Chart - Legend
package showcase
import (
"github.com/axzilla/templui/internal/components/card"
"github.com/axzilla/templui/internal/components/chart"
)
templ ChartPieLegend() {
@card.Card(card.Props{Class: "max-w-sm"}) {
@card.Content() {
@chart.Chart(chart.Props{
Variant: chart.VariantPie,
ShowLegend: true,
Data: chart.Data{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []chart.Dataset{
{
Data: []float64{7, 16, 5, 20, 14, 15},
},
},
},
})
}
}
}
Doughnut Chart
package showcase
import (
"github.com/axzilla/templui/internal/components/card"
"github.com/axzilla/templui/internal/components/chart"
)
templ ChartDoughnut() {
@card.Card(card.Props{Class: "max-w-sm"}) {
@card.Content() {
@chart.Chart(chart.Props{
Variant: chart.VariantDoughnut,
Data: chart.Data{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []chart.Dataset{
{
Data: []float64{7, 16, 5, 20, 14, 15},
},
},
},
})
}
}
}
Doughnut Chart - Stacked
package showcase
import (
"github.com/axzilla/templui/internal/components/card"
"github.com/axzilla/templui/internal/components/chart"
)
templ ChartDoughnutStacked() {
@card.Card(card.Props{Class: "max-w-sm"}) {
@card.Content() {
@chart.Chart(chart.Props{
Variant: chart.VariantDoughnut,
Data: chart.Data{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []chart.Dataset{
{
Data: []float64{3, 9, 3, 12, 7, 8},
Label: "Mobile",
},
{
Data: []float64{7, 16, 5, 20, 14, 15},
Label: "Desktop",
},
},
},
})
}
}
}
Doughnut Chart - Legend
package showcase
import (
"github.com/axzilla/templui/internal/components/card"
"github.com/axzilla/templui/internal/components/chart"
)
templ ChartDoughnutLegend() {
@card.Card(card.Props{Class: "max-w-sm"}) {
@card.Content() {
@chart.Chart(chart.Props{
Variant: chart.VariantDoughnut,
ShowLegend: true,
Data: chart.Data{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []chart.Dataset{
{
Data: []float64{3, 9, 3, 12, 7, 8},
Label: "Mobile",
},
},
},
})
}
}
}
Radar Chart
package showcase
import (
"github.com/axzilla/templui/internal/components/card"
"github.com/axzilla/templui/internal/components/chart"
)
templ ChartRadar() {
@card.Card(card.Props{Class: "max-w-sm"}) {
@card.Content() {
@chart.Chart(chart.Props{
Variant: chart.VariantRadar,
Data: chart.Data{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []chart.Dataset{
{
Data: []float64{3, 9, 3, 12, 7, 8},
},
},
},
})
}
}
}
Radar Chart - Stacked
package showcase
import (
"github.com/axzilla/templui/internal/components/card"
"github.com/axzilla/templui/internal/components/chart"
)
templ CharRadarStacked() {
@card.Card(card.Props{Class: "max-w-sm"}) {
@card.Content() {
@chart.Chart(chart.Props{
Variant: chart.VariantRadar,
Data: chart.Data{
Labels: []string{"Jan", "Feb", "March", "April", "May", "June"},
Datasets: []chart.Dataset{
{
Data: []float64{15, 9, 3, 12, 7, 8},
Label: "Mobile",
},
{
Data: []float64{7, 16, 5, 20, 14, 15},
Label: "Desktop",
},
},
},
})
}
}
}