Files
via/internal/examples/realtimechart/main.go
Ryan Hamamura 532651552a refactor: simplify OnInterval API to auto-start and return stop func
Replace the exported OnIntervalRoutine struct (Start/Stop/UpdateInterval)
with a single function that auto-starts the goroutine and returns an
idempotent stop closure. Uses close(channel) instead of send-on-channel,
fixing a potential deadlock when the goroutine exits via context disposal.

Closes #5 item 4.
2026-02-12 12:27:50 -10:00

178 lines
4.2 KiB
Go

package main
import (
"fmt"
"math/rand"
"time"
"github.com/ryanhamamura/via"
// "github.com/go-via/via-plugin-picocss/picocss"
"github.com/ryanhamamura/via/h"
)
func main() {
v := via.New()
v.Config(via.Options{
LogLevel: via.LogLevelDebug,
DevMode: true,
Plugins: []via.Plugin{
// picocss.Default,
},
})
v.AppendToHead(
h.Script(h.Src("https://unpkg.com/echarts@6.0.0/dist/echarts.min.js")),
)
v.Page("/", func(c *via.Context) {
isLive := true
isLiveSig := c.Signal("on")
refreshRate := c.Signal("24")
computedTickDuration := func() time.Duration {
return 1000 / time.Duration(refreshRate.Int()) * time.Millisecond
}
var stopUpdate func()
startInterval := func() {
stopUpdate = c.OnInterval(computedTickDuration(), func() {
ts := time.Now().UnixMilli()
val := rand.ExpFloat64() * 10
c.ExecScript(fmt.Sprintf(`
if (myChart) {
myChart.appendData({seriesIndex: 0, data: [[%d, %f]]});
myChart.setOption({},{notMerge:false,lazyUpdate:true});
};
`, ts, val))
})
}
startInterval()
updateRefreshRate := c.Action(func() {
stopUpdate()
startInterval()
})
toggleIsLive := c.Action(func() {
isLive = isLiveSig.Bool()
if isLive {
startInterval()
} else {
stopUpdate()
}
})
c.View(func() h.H {
return h.Div(h.Style("overflow-x:hidden"),
h.Section(h.Class("container"),
h.Nav(
h.Ul(h.Li(h.H3(h.Text("⚡Via")))),
h.Ul(
h.Li(h.A(h.H5(h.Text("About")), h.Href("https://github.com/go-via/via"))),
h.Li(h.A(h.H5(h.Text("Resources")), h.Href("https://github.com/orgs/go-via/repositories"))),
h.Li(h.A(h.H5(h.Text("Say hi!")), h.Href("http://github.com/go-via/via/discussions"))),
),
),
),
h.Div(
h.Div(h.ID("chart"), h.DataIgnoreMorph(), h.Style("width:100%;height:400px;"),
h.Script(h.Raw(`
var prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
var myChart = echarts.init(document.getElementById('chart'), prefersDark.matches ? 'dark' : 'light');
var option = {
backgroundColor: prefersDark.matches ? 'transparent' : '#ffffff',
animationDurationUpdate: 0, // affects updates/redraws
tooltip: {
trigger: 'axis',
position: function (pt) {
return [pt[0], '10%'];
},
syncStrategy: 'closestSampledPoint',
backgroundColor: prefersDark.matches ? '#13171fc0' : '#eeeeeec0',
extraCssText: 'backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px);'
},
title: {
left: 'center',
text: '📈 Real-Time Chart Example'
},
xAxis: {
type: 'time',
boundaryGap: false,
axisLabel: {
hideOverlap: true
}
},
yAxis: {
type: 'value',
boundaryGap: [0, '100%'],
min: 0,
max: 100
},
dataZoom: [
{
type: 'inside',
start: 1,
end: 100
},
{
start: 0,
end: 100
}
],
series: [
{
name: 'Fake Data',
type: 'line',
symbol: 'none',
sampling: 'max',
itemStyle: {
color: '#e8ae01'
},
lineStyle: { color: '#e8ae01'},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: '#fecc63'
},
{
offset: 1,
color: '#c79400'
}
])
},
large: true,
data: []
}
]
};
option && myChart.setOption(option);
`)),
),
h.Section(
h.Article(
h.H5(h.Text("Controls")),
h.Hr(),
h.Div(h.Class("grid"),
h.FieldSet(
h.Legend(h.Text("Live Data")),
h.Input(h.Type("checkbox"), h.Role("switch"), isLiveSig.Bind(), toggleIsLive.OnChange()),
),
h.Label(h.Text("Refresh Rate (Hz) ― "), refreshRate.Text(),
h.Input(h.Type("range"), h.Attr("min", "1"), h.Attr("max", "200"), refreshRate.Bind(), updateRefreshRate.OnChange()),
),
),
),
),
),
)
})
})
v.Start()
}