update 3d chart visualization
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -1184,8 +1185,11 @@ var usCities = []ui.USHeatmapPoint{
|
||||
func chartsSection() func() *VNode {
|
||||
seed := NewSignal(0)
|
||||
threeD := NewSignal(false)
|
||||
depth := NewSignal(16.0)
|
||||
tilt := NewSignal(0.6)
|
||||
barC, donutC, areaC, lineC := ui.NewChart(), ui.NewChart(), ui.NewChart(), ui.NewChart()
|
||||
horizC, stackC := ui.NewChart(), ui.NewChart()
|
||||
threeDBar, threeDLine := ui.NewChart(), ui.NewChart()
|
||||
heat := ui.NewUSHeatmap()
|
||||
|
||||
return func() *VNode {
|
||||
@@ -1224,16 +1228,18 @@ func chartsSection() func() *VNode {
|
||||
row("grid gap-6 lg:grid-cols-12",
|
||||
Div(Attr("class", "lg:col-span-7"), barC.Render(ui.ChartProps{Kind: ui.ChartBar, Title: "Requests & errors this week", Labels: chartDaysWasm, Series: []ui.ChartSeries{requests, errs}, Height: 260, ThreeD: threeD.Get()})),
|
||||
Div(Attr("class", "lg:col-span-5"), donutC.Render(ui.ChartProps{Kind: ui.ChartDonut, Title: "Traffic by source", Labels: pieLabelsWasm, Series: []ui.ChartSeries{pie}, Height: 260, ThreeD: threeD.Get()})),
|
||||
Div(Attr("class", "lg:col-span-7"), areaC.Render(ui.ChartProps{Kind: ui.ChartArea, Title: "Requests, smoothed", Smooth: true, Labels: chartDaysWasm, Series: []ui.ChartSeries{requests}, Height: 220})),
|
||||
Div(Attr("class", "lg:col-span-5"), lineC.Render(ui.ChartProps{Kind: ui.ChartLine, Title: "Requests & errors", Labels: chartDaysWasm, Series: []ui.ChartSeries{requests, errs}, Height: 220})),
|
||||
Div(Attr("class", "lg:col-span-7"), areaC.Render(ui.ChartProps{Kind: ui.ChartArea, Title: "Requests, smoothed", Smooth: true, Labels: chartDaysWasm, Series: []ui.ChartSeries{requests}, Height: 220, ThreeD: threeD.Get()})),
|
||||
Div(Attr("class", "lg:col-span-5"), lineC.Render(ui.ChartProps{Kind: ui.ChartLine, Title: "Requests & errors", Labels: chartDaysWasm, Series: []ui.ChartSeries{requests, errs}, Height: 220, ThreeD: threeD.Get()})),
|
||||
),
|
||||
row("mt-4 flex items-center gap-3",
|
||||
ui.Button(ui.ButtonProps{Color: ui.ButtonPrimary, Small: true, Text: "New data", OnClick: func() { seed.Set(seed.Get() + 1) }}),
|
||||
ui.Button(ui.ButtonProps{Color: ui.ButtonNeutral, Small: true, Text: threeDLabel, OnClick: func() { threeD.Set(!threeD.Get()) }}),
|
||||
Span(Attr("class", "text-ss text-ink-muted"), Text("Each chart takes a Title; a multi-series chart also draws a legend. Click a legend key (Errors, or a donut slice) to hide it — the scale and marks recompute. 3D extrudes the bars and tilts the donut.")),
|
||||
Span(Attr("class", "text-ss text-ink-muted"), Text("Each chart takes a Title; a multi-series chart also draws a legend. Click a legend key (Errors, or a donut slice) to hide it — the scale and marks recompute. 3D gives every plot a grid with depth — bars extrude, a line or area draws flat over it, the donut tilts.")),
|
||||
),
|
||||
),
|
||||
|
||||
chart3DDemo(threeDBar, threeDLine, depth, tilt, requests, errs),
|
||||
|
||||
demo("Horizontal bars, and stacked",
|
||||
row("grid gap-6 lg:grid-cols-2",
|
||||
horizC.Render(ui.ChartProps{Kind: ui.ChartBar, Horizontal: true, Labels: chartDaysWasm, Series: []ui.ChartSeries{requests, errs}, Height: 260}),
|
||||
@@ -1357,6 +1363,54 @@ func orElse(s, fallback string) string {
|
||||
}
|
||||
|
||||
// row is a flex/grid container helper (appends *VNode children as Mods).
|
||||
// chart3DDemo is the "3D grid" panel: an extruded bar chart and a line chart (which rides
|
||||
// the back wall, un-extruded) plus depth/tilt range sliders that drive both. Called from
|
||||
// inside the render closure so reading depth/tilt makes it reactive and the charts re-render
|
||||
// when a slider moves.
|
||||
func chart3DDemo(barChart, lineChart *ui.Chart, depth, tilt *Signal[float64], requests, errs ui.ChartSeries) *VNode {
|
||||
dep, t := depth.Get(), tilt.Get()
|
||||
tv := t // addressable copy for the *float64 Tilt prop (both charts read the same value)
|
||||
slider := func(label, valText, min, max, step, value string, on func(Event)) *VNode {
|
||||
return El("label", Attr("class", "flex flex-col gap-1.5"),
|
||||
Span(Attr("class", "flex items-center justify-between text-ss text-ink-soft"),
|
||||
El("code", Attr("class", "font-mono"), Text(label)),
|
||||
Span(Attr("class", "font-mono tabular-nums text-ink-muted"), Text(valText)),
|
||||
),
|
||||
Input(Attr("type", "range"), Attr("min", min), Attr("max", max), Attr("step", step),
|
||||
Attr("value", value), Attr("class", "w-full accent-accent"), OnEvent(EVENT_INPUT, on)),
|
||||
)
|
||||
}
|
||||
setFrom := func(sig *Signal[float64]) func(Event) {
|
||||
return func(e Event) {
|
||||
if v, err := strconv.ParseFloat(e.Value(), 64); err == nil {
|
||||
sig.Set(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
return demo("3D grid — bars extrude into it, lines draw flat on the front",
|
||||
row("grid items-center gap-6 lg:grid-cols-12",
|
||||
Div(Attr("class", "lg:col-span-4"),
|
||||
barChart.Render(ui.ChartProps{Kind: ui.ChartBar, Title: "Bars", Labels: chartDaysWasm,
|
||||
Series: []ui.ChartSeries{requests}, Height: 240, ThreeD: true, Depth: dep, Tilt: &tv}),
|
||||
),
|
||||
Div(Attr("class", "lg:col-span-4"),
|
||||
lineChart.Render(ui.ChartProps{Kind: ui.ChartLine, Title: "Lines", Labels: chartDaysWasm,
|
||||
Series: []ui.ChartSeries{requests, errs}, Height: 240, ThreeD: true, Depth: dep, Tilt: &tv}),
|
||||
),
|
||||
Div(Attr("class", "flex flex-col justify-center gap-5 lg:col-span-4"),
|
||||
slider("depth", strconv.Itoa(int(dep))+"px", "0", "40", "1", strconv.FormatFloat(dep, 'f', 2, 64), setFrom(depth)),
|
||||
slider("tilt", strconv.FormatFloat(t, 'f', 2, 64), "0", "1", "0.05", strconv.FormatFloat(t, 'f', 2, 64), setFrom(tilt)),
|
||||
),
|
||||
),
|
||||
Span(Attr("class", "mt-3 block text-ss text-ink-muted"),
|
||||
Text("The same depth and tilt give the grid itself perspective: a floor recedes from the value-0 "+
|
||||
"baseline into a back wall. Bars extrude into that space; a line or area stays flat on the front "+
|
||||
"plane, drawn over the grid and read against the front axis — the stroke is not itself extruded, "+
|
||||
"since depth on a hairline reads as noise. tilt runs 1 (head-on, near-flat) to 0 (bird's-eye); depth "+
|
||||
"is the sweep length in px.")),
|
||||
)
|
||||
}
|
||||
|
||||
func row(class string, children ...*VNode) *VNode {
|
||||
mods := []Mod{Attr("class", class)}
|
||||
for _, c := range children {
|
||||
|
||||
@@ -1390,6 +1390,8 @@ const US_CITIES = [
|
||||
function Charts() {
|
||||
const [seed, setSeed] = createSignal(0);
|
||||
const [threeD, setThreeD] = createSignal(false);
|
||||
const [depth, setDepth] = createSignal(16);
|
||||
const [tilt, setTilt] = createSignal(0.6);
|
||||
|
||||
// Two series over the same week. seed() reshuffles them so you can watch the SVG
|
||||
// move — no teardown, no new instance, just the marks that changed.
|
||||
@@ -1425,10 +1427,10 @@ function Charts() {
|
||||
<Chart kind="donut" title="Traffic by source" labels={pieLabels} series={[pieData()]} height={260} threeD={threeD()} />
|
||||
</div>
|
||||
<div class="lg:col-span-7">
|
||||
<Chart kind="area" curve="smooth" title="Requests, smoothed" labels={CHART_DAYS} series={[requests()]} height={220} />
|
||||
<Chart kind="area" curve="smooth" title="Requests, smoothed" labels={CHART_DAYS} series={[requests()]} height={220} threeD={threeD()} />
|
||||
</div>
|
||||
<div class="lg:col-span-5">
|
||||
<Chart kind="line" title="Requests & errors" labels={CHART_DAYS} series={[requests(), errors()]} height={220} />
|
||||
<Chart kind="line" title="Requests & errors" labels={CHART_DAYS} series={[requests(), errors()]} height={220} threeD={threeD()} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex items-center gap-3">
|
||||
@@ -1441,17 +1443,59 @@ function Charts() {
|
||||
<span class="text-ss text-ink-muted">
|
||||
Each chart carries a <code class="font-mono">title</code>; a multi-series chart also draws a
|
||||
legend. Click a legend key — say <em>Errors</em> or a donut slice — to hide it, and the scale
|
||||
and marks recompute from what's left. The <code class="font-mono">threeD</code> prop extrudes
|
||||
the bars and tilts the donut (line and area stay flat — depth reads as noise on a curve).
|
||||
and marks recompute from what's left. The <code class="font-mono">threeD</code> prop gives
|
||||
every plot a 3D grid — bars extrude, a line or area draws flat over it, the donut tilts.
|
||||
</span>
|
||||
</div>
|
||||
</Panel>
|
||||
|
||||
<Panel title="3D grid — bars extrude into it, lines draw flat on the front">
|
||||
<div class="grid items-center gap-6 lg:grid-cols-12">
|
||||
<div class="lg:col-span-4">
|
||||
<Chart kind="bar" title="Bars" labels={CHART_DAYS}
|
||||
series={[requests()]} height={240} threeD depth={depth()} tilt={tilt()} />
|
||||
</div>
|
||||
<div class="lg:col-span-4">
|
||||
<Chart kind="line" title="Lines" labels={CHART_DAYS}
|
||||
series={[requests(), errors()]} height={240} threeD depth={depth()} tilt={tilt()} />
|
||||
</div>
|
||||
<div class="flex flex-col justify-center gap-5 lg:col-span-4">
|
||||
<label class="flex flex-col gap-1.5">
|
||||
<span class="flex items-center justify-between text-ss text-ink-soft">
|
||||
<code class="font-mono">depth</code>
|
||||
<span class="font-mono tabular-nums text-ink-muted">{depth()}px</span>
|
||||
</span>
|
||||
<input type="range" min="0" max="40" step="1" value={depth()}
|
||||
class="w-full accent-accent"
|
||||
oninput={(e) => setDepth(+e.currentTarget.value)} />
|
||||
</label>
|
||||
<label class="flex flex-col gap-1.5">
|
||||
<span class="flex items-center justify-between text-ss text-ink-soft">
|
||||
<code class="font-mono">tilt</code>
|
||||
<span class="font-mono tabular-nums text-ink-muted">{tilt().toFixed(2)}</span>
|
||||
</span>
|
||||
<input type="range" min="0" max="1" step="0.05" value={tilt()}
|
||||
class="w-full accent-accent"
|
||||
oninput={(e) => setTilt(+e.currentTarget.value)} />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-3 text-ss text-ink-muted">
|
||||
The same <code class="font-mono">depth</code> and <code class="font-mono">tilt</code> give the grid
|
||||
itself perspective: a floor recedes from the value-0 baseline into a back wall. Bars extrude into
|
||||
that space; a line or area stays flat on the front plane, drawn over the grid and read against the
|
||||
front axis — the stroke is <em>not</em> itself extruded, since depth on a hairline reads as noise.{" "}
|
||||
<code class="font-mono">tilt</code> runs 1 (head-on, near-flat) to 0 (bird's-eye);{" "}
|
||||
<code class="font-mono">depth</code> is the sweep length in px.
|
||||
</p>
|
||||
</Panel>
|
||||
|
||||
<Prose>
|
||||
One <code class="font-mono">kind</code> prop picks the form —{" "}
|
||||
<code class="font-mono">"line" | "area" | "bar" | "pie" | "donut"</code> — and{" "}
|
||||
<code class="font-mono">stacked</code>, <code class="font-mono">horizontal</code>,{" "}
|
||||
<code class="font-mono">curve</code>, <code class="font-mono">threeD</code>,{" "}
|
||||
<code class="font-mono">curve</code>, <code class="font-mono">threeD</code>{" "}
|
||||
(with <code class="font-mono">depth</code> / <code class="font-mono">tilt</code>),{" "}
|
||||
<code class="font-mono">title</code>, <code class="font-mono">palette</code> and{" "}
|
||||
<code class="font-mono">valueFormat</code> refine it. The legend it draws for a multi-series or
|
||||
pie/donut chart is interactive — clicking a key toggles that series or slice. The width is measured
|
||||
|
||||
Reference in New Issue
Block a user