From 6319ef01eb1ca8ec89663506868a4804ef7405a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Mon, 5 Oct 2026 14:59:20 -0400 Subject: [PATCH] wip-c2-3 --- density_plot.go | 144 +++++++++++++++++ density_plot_test.go | 89 +++++++++++ messages_charts2.go | 11 +- .../snapshots/darwin/density_plot-dark.png | Bin 0 -> 22659 bytes .../snapshots/darwin/density_plot-light.png | Bin 0 -> 21917 bytes .../snapshots/darwin/violin_plot-dark.png | Bin 0 -> 18828 bytes .../snapshots/darwin/violin_plot-light.png | Bin 0 -> 18215 bytes violin_plot.go | 150 ++++++++++++++++++ violin_plot_test.go | 45 ++++++ 9 files changed, 434 insertions(+), 5 deletions(-) create mode 100644 density_plot.go create mode 100644 density_plot_test.go create mode 100644 testdata/snapshots/darwin/density_plot-dark.png create mode 100644 testdata/snapshots/darwin/density_plot-light.png create mode 100644 testdata/snapshots/darwin/violin_plot-dark.png create mode 100644 testdata/snapshots/darwin/violin_plot-light.png create mode 100644 violin_plot.go create mode 100644 violin_plot_test.go diff --git a/density_plot.go b/density_plot.go new file mode 100644 index 0000000..a62d0ed --- /dev/null +++ b/density_plot.go @@ -0,0 +1,144 @@ +package mujica + +import ( + "fmt" + "math" + + "github.com/egoist/mygo/ui" +) + +// DensityPlotOptions configure a DensityPlot. +type DensityPlotOptions struct { + Frame ChartFrame + // Bandwidth is the Gaussian kernel's width; 0 uses Silverman's rule per group. + Bandwidth float64 + // Points is how many x values the curves are sampled at; 0 means 128. + Points int + Format func(v float64) string +} + +// densitySample is finite values, sorted out of NaN and infinities. +func densitySample(values []float64) []float64 { + var out []float64 + for _, v := range values { + if !math.IsNaN(v) && !math.IsInf(v, 0) { + out = append(out, v) + } + } + return out +} + +// densityBandwidth is Silverman's rule of thumb, or 1 for flat samples. +func densityBandwidth(v []float64) float64 { + n := float64(len(v)) + mean := 0.0 + for _, x := range v { + mean += x + } + mean /= n + sd := 0.0 + for _, x := range v { + sd += (x - mean) * (x - mean) + } + sd = math.Sqrt(sd / max(n-1, 1)) + if sd == 0 { + return 1 + } + return 1.06 * sd * math.Pow(n, -0.2) +} + +// densityKDE estimates the density of v at each x with a Gaussian kernel of width h. +func densityKDE(v []float64, h float64, xs []float64) []float64 { + out := make([]float64, len(xs)) + norm := 1 / (float64(len(v)) * h * math.Sqrt(2*math.Pi)) + for i, x := range xs { + s := 0.0 + for _, p := range v { + u := (x - p) / h + s += math.Exp(-0.5 * u * u) + } + out[i] = s * norm + } + return out +} + +// densityCurves samples every group's density on one x grid, three bandwidths past the data. +func densityCurves(groups []BoxGroup, bandwidth float64, points int) (xs []float64, series []ChartSeries) { + lo, hi := math.Inf(1), math.Inf(-1) + samples := make([][]float64, len(groups)) + widths := make([]float64, len(groups)) + for i, g := range groups { + samples[i] = densitySample(g.Values) + if len(samples[i]) == 0 { + continue + } + widths[i] = bandwidth + if widths[i] == 0 { + widths[i] = densityBandwidth(samples[i]) + } + for _, v := range samples[i] { + lo, hi = min(lo, v-3*widths[i]), max(hi, v+3*widths[i]) + } + } + if math.IsInf(lo, 1) { + return nil, nil + } + xs = make([]float64, points) + for i := range xs { + xs[i] = lo + (hi-lo)*float64(i)/float64(points-1) + } + for i, g := range groups { + ys := make([]float64, points) + if len(samples[i]) > 0 { + ys = densityKDE(samples[i], widths[i], xs) + } + series = append(series, ChartSeries{Name: g.Name, Values: ys}) + } + return xs, series +} + +// DensityPlot draws each group's kernel density curve over one shared axis. +func DensityPlot(c *ui.Context, groups []BoxGroup, opts DensityPlotOptions) ChartResult { + if opts.Bandwidth < 0 || opts.Points < 0 || opts.Points == 1 { + panic("mujica: DensityPlot needs Bandwidth ≥ 0 and Points of 0 or 2 or more") + } + points := opts.Points + if points == 0 { + points = 128 + } + ch := newChart(c, opts.Frame) + ids := "" + for _, g := range groups { + ids += g.Name + chartID(g.Values) + ";" + } + type curves struct { + xs []float64 + series []ChartSeries + } + cv := chartMemo(ch, "kde", fmt.Sprint(ids, opts.Bandwidth, points), func() curves { + xs, s := densityCurves(groups, opts.Bandwidth, points) + return curves{xs, s} + }) + n := len(cv.xs) + series := cv.series + if n == 0 { + series = nil + } + tops, bases := make([][]float64, len(series)), make([][]float64, len(series)) + for i, s := range series { + tops[i], bases[i] = s.Values, make([]float64, n) + } + var ax chartX + if n > 0 { + ax = chartMemo(ch, "x", ids+fmt.Sprint(opts.Bandwidth, points), func() chartX { return newChartX(n, cv.xs, nil) }) + } + lo, hi, ok := chartYRange(ch, series, true, nil) + if !ok { + n = 0 + } + format := opts.Format + if format == nil { + format = func(v float64) string { return fmt.Sprintf("%.3g", v) } + } + return chartLines(ch, chartLineData{series: series, tops: tops, bases: bases, ax: ax, lo: lo, hi: hi, n: n, format: format}) +} diff --git a/density_plot_test.go b/density_plot_test.go new file mode 100644 index 0000000..3cfee28 --- /dev/null +++ b/density_plot_test.go @@ -0,0 +1,89 @@ +package mujica + +import ( + "math" + "strings" + "testing" + + "github.com/egoist/mygo/ui" +) + +func densityGroups() []BoxGroup { + a, b := make([]float64, 200), make([]float64, 200) + for i := range a { + u := float64(i)/200 - 0.5 + a[i] = 40 + 30*u*math.Abs(u) + b[i] = 60 + 12*math.Sin(float64(i)) + } + return []BoxGroup{{Name: "Matins", Values: a}, {Name: "Vespers", Values: b}} +} + +func TestDensityKDEIntegratesToOne(t *testing.T) { + v := []float64{1, 2, 2, 3, 7} + xs := make([]float64, 2001) + for i := range xs { + xs[i] = -10 + 30*float64(i)/2000 + } + d := densityKDE(v, densityBandwidth(v), xs) + sum := 0.0 + for _, y := range d { + sum += y * 30 / 2000 + } + if math.Abs(sum-1) > 0.01 { + t.Fatalf("density integrates to %v", sum) + } + if bw := densityBandwidth([]float64{4, 4}); bw != 1 { + t.Fatalf("flat bandwidth %v", bw) + } +} + +func TestDensityPlotReadoutAndBandwidth(t *testing.T) { + bw := 0.0 + h := &chartHarness{build: func(c *ui.Context) ChartResult { + return DensityPlot(c, densityGroups(), DensityPlotOptions{Frame: ChartFrame{Label: "Ages"}, Bandwidth: bw, Points: 64}) + }} + tt := ui.NewTester(h.view, 520, 420) + tabTo(t, tt, "Ages") + tt.Key(0, ui.KeyRight) + for range 31 { + tt.Key(0, ui.KeyRight) + } + first := chartReadout(tt) + if !strings.Contains(first, "Matins ") || !strings.Contains(first, "Vespers ") { + t.Fatalf("readout %q", first) + } + bw = 1 + tt.Frame() + if chartReadout(tt) == first { + t.Fatal("bandwidth did not change the curve") + } + if err := tt.Click("Vespers"); err != nil || strings.Contains(chartReadout(tt), "Vespers") { + t.Fatalf("legend did not hide a group: %q", chartReadout(tt)) + } +} + +func TestDensityPlotBadAndEmpty(t *testing.T) { + h := &chartHarness{build: func(c *ui.Context) ChartResult { + return DensityPlot(c, []BoxGroup{{Name: "a", Values: []float64{math.NaN()}}}, DensityPlotOptions{Frame: ChartFrame{Label: "Ages"}}) + }} + if tt := ui.NewTester(h.view, 520, 420); !tt.HasText("No data") { + t.Fatal("NaN sample is not empty") + } + defer func() { + if recover() == nil { + t.Fatal("no panic for Points 1") + } + }() + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + DensityPlot(c, densityGroups(), DensityPlotOptions{Frame: ChartFrame{Label: "x"}, Points: 1}) + }, 300, 300) +} + +func TestDensityPlotSnapshot(t *testing.T) { + snapshot(t, "density_plot", 520, 340, func(c *ui.Context) { + ui.Column(c).Padding(16).Children(func() { + DensityPlot(c, densityGroups(), DensityPlotOptions{Frame: ChartFrame{Label: "Ages"}}) + }) + }) +} diff --git a/messages_charts2.go b/messages_charts2.go index 35545ad..aa8fced 100644 --- a/messages_charts2.go +++ b/messages_charts2.go @@ -2,10 +2,11 @@ package mujica func init() { addMessages(map[string][2]string{ - "chart_legend.more": {"+%d more", "另有 %d 项"}, - "chart_legend.fewer": {"Show fewer", "收起"}, - "chart_brush.window": {"%s: %s to %s", "%s:%s 至 %s"}, - "chart_brush.start": {"Range start %s", "区间起点 %s"}, - "chart_brush.end": {"Range end %s", "区间终点 %s"}, + "chart_legend.more": {"+%d more", "另有 %d 项"}, + "chart_legend.fewer": {"Show fewer", "收起"}, + "chart_brush.window": {"%s: %s to %s", "%s:%s 至 %s"}, + "chart_brush.start": {"Range start %s", "区间起点 %s"}, + "chart_brush.end": {"Range end %s", "区间终点 %s"}, + "violin_plot.readout": {"%s · median %s · quartiles %s–%s · %d values", "%s · 中位数 %s · 四分位 %s–%s · %d 个值"}, }) } diff --git a/testdata/snapshots/darwin/density_plot-dark.png b/testdata/snapshots/darwin/density_plot-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..a3cb242f57d91e976eca71839debcc1afa4793b0 GIT binary patch literal 22659 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/density_plot-light.png b/testdata/snapshots/darwin/density_plot-light.png new file mode 100644 index 0000000000000000000000000000000000000000..9059fc0df15ff4016d067f3fd054ccdecd1ab870 GIT binary patch literal 21917 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/violin_plot-dark.png b/testdata/snapshots/darwin/violin_plot-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..e271f5212baf09441ce189c792f48cae2828f1ed GIT binary patch literal 18828 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/violin_plot-light.png b/testdata/snapshots/darwin/violin_plot-light.png new file mode 100644 index 0000000000000000000000000000000000000000..c68f1b2d09667eb73ec4fc0b57cae9423834c609 GIT binary patch literal 18215 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 Q>;P#A1@Wr?H>ZRq0L%vcwEzGB literal 0 HcmV?d00001 diff --git a/violin_plot.go b/violin_plot.go new file mode 100644 index 0000000..00323fc --- /dev/null +++ b/violin_plot.go @@ -0,0 +1,150 @@ +package mujica + +import ( + "fmt" + "math" + + "github.com/egoist/mygo/ui" + + "mujicaui/internal" + "mujicaui/theme" +) + +// ViolinPlotOptions configure a ViolinPlot. +type ViolinPlotOptions struct { + Frame ChartFrame + // Bandwidth is the kernel width; 0 uses Silverman's rule per group. + Bandwidth float64 + Format func(v float64) string +} + +// violinShape is one group's density outline over its value range, and its quartiles. +type violinShape struct { + ys, dens []float64 + stats boxStats +} + +// violinPoints is how many values each outline is sampled at. +const violinPoints = 64 + +// ViolinPlot mirrors each group's density around its middle, with the median and quartiles marked. +func ViolinPlot(c *ui.Context, groups []BoxGroup, opts ViolinPlotOptions) ChartResult { + if opts.Bandwidth < 0 { + panic("mujica: ViolinPlot Bandwidth must be ≥ 0") + } + ch := newChart(c, opts.Frame) + k := Tokens(c) + pal := internal.Palette(k) + size := FontSize(c, theme.CaptionSize) + ids := "" + for _, g := range groups { + if g.Name == "" { + panic("mujica: ViolinPlot groups need names") + } + ids += chartID(g.Values) + ";" + } + shapes := chartMemo(ch, "violins", fmt.Sprint(ids, opts.Bandwidth), func() []violinShape { + out := make([]violinShape, len(groups)) + for i, g := range groups { + v := densitySample(g.Values) + if len(v) == 0 { + continue + } + h := opts.Bandwidth + if h == 0 { + h = densityBandwidth(v) + } + s := boxSummary(v) + lo, hi := s.lo, s.hi + for _, o := range s.outliers { + lo, hi = min(lo, o), max(hi, o) + } + ys := make([]float64, violinPoints) + for j := range ys { + ys[j] = lo + (hi-lo)*float64(j)/float64(violinPoints-1) + } + out[i] = violinShape{ys: ys, dens: densityKDE(v, h, ys), stats: s} + } + return out + }) + lo, hi := math.Inf(1), math.Inf(-1) + for _, s := range shapes { + if s.stats.n > 0 { + lo, hi = min(lo, s.ys[0]), max(hi, s.ys[len(s.ys)-1]) + } + } + var step float64 + n := len(groups) + if math.IsInf(lo, 1) { + n = 0 + } else { + lo, hi, step = internal.Nice(lo, hi, 4) + } + val := func(v float64) string { return chartValue(c, v, opts.Format) } + geom := func(r ui.Rect) (ui.Rect, internal.Band, internal.Linear) { + plot := chartInset(r, 52, 26) + return plot, internal.Band{N: len(groups), R0: plot.X, R1: plot.X + plot.W, Pad: 0.15}, + internal.Linear{D0: lo, D1: hi, R0: float64(plot.Y + plot.H), R1: float64(plot.Y)} + } + spec := ChartSpec{Marks: n} + spec.Readout = func(i int) string { + s := shapes[i].stats + if s.n == 0 { + return groups[i].Name + " · " + msg(c, "chart.missing") + } + return fmt.Sprintf(msg(c, "violin_plot.readout"), groups[i].Name, val(s.median), val(s.q1), val(s.q3), s.n) + } + spec.Pick = func(r ui.Rect, x, y float32) int { + plot, band, _ := geom(r) + if !plot.Contains(x, y) { + return -1 + } + return min(len(groups)-1, int((x-plot.X)/band.Step())) + } + spec.Draw = func(p *ui.Painter, r ui.Rect, active int) { + plot, band, ys := geom(r) + internal.AxisY(p, plot, ys, internal.Ticks(lo, hi, step), func(v float64) string { return internal.FormatTick(v, step) }, size, k) + var xs []float32 + var names []string + for i, g := range groups { + xs, names = append(xs, band.Center(i)), append(names, g.Name) + } + internal.AxisX(p, plot, xs, names, size, k) + for i, s := range shapes { + if s.stats.n == 0 { + continue + } + // Each violin fills its band at its own widest point. + cx, half, top := band.Center(i), band.Width()/2, 0.0 + for _, d := range s.dens { + top = max(top, d) + } + var outline ui.Path + for j, y := range s.ys { + x := cx + half*float32(s.dens[j]/top) + if j == 0 { + outline.MoveTo(x, ys.Map(y)) + } else { + outline.LineTo(x, ys.Map(y)) + } + } + for j := len(s.ys) - 1; j >= 0; j-- { + outline.LineTo(cx-half*float32(s.dens[j]/top), ys.Map(s.ys[j])) + } + outline.Close() + col := pal[i%len(pal)] + p.FillPath(&outline, col.Alpha(0.22)) + w := float32(1.5) + if i == active { + w = 3 + } + p.StrokePath(&outline, w, col) + // The quartile bar and a diamond at the median read without color. + p.Fill(ui.Rect{X: cx - 3, Y: ys.Map(s.stats.q3), W: 6, H: max(ys.Map(s.stats.q1)-ys.Map(s.stats.q3), 1)}, k.Text, 1) + p.FillPath(internal.Diamond(internal.Snap(p, cx), internal.Snap(p, ys.Map(s.stats.median)), 10), k.Surface) + p.FillPath(internal.Diamond(internal.Snap(p, cx), internal.Snap(p, ys.Map(s.stats.median)), 7), k.Text) + } + } + res, _ := ch.build(spec) + return res +} diff --git a/violin_plot_test.go b/violin_plot_test.go new file mode 100644 index 0000000..3b8a573 --- /dev/null +++ b/violin_plot_test.go @@ -0,0 +1,45 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +func TestViolinPlotReadout(t *testing.T) { + h := &chartHarness{build: func(c *ui.Context) ChartResult { + return ViolinPlot(c, append(densityGroups(), boxGroups[0]), ViolinPlotOptions{Frame: ChartFrame{Label: "Hymns"}}) + }} + tt := ui.NewTester(h.view, 520, 420) + tabTo(t, tt, "Hymns") + tt.Key(0, ui.KeyEnd) + if got := chartReadout(tt); got != "Choir · median 5.5 · quartiles 3.25–7.75 · 10 values" { + t.Fatalf("readout %q", got) + } + tt.Key(0, ui.KeyEscape) + b := h.res.Element.Bounds() + tt.Move(b.X+52+(b.W-64)/6, b.Y+120) + if h.res.Point != 0 { + t.Fatalf("hover %d", h.res.Point) + } +} + +func TestViolinPlotBad(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatal("no panic for an unnamed group") + } + }() + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ViolinPlot(c, []BoxGroup{{Values: []float64{1}}}, ViolinPlotOptions{Frame: ChartFrame{Label: "x"}}) + }, 300, 300) +} + +func TestViolinPlotSnapshot(t *testing.T) { + snapshot(t, "violin_plot", 520, 320, func(c *ui.Context) { + ui.Column(c).Padding(16).Children(func() { + ViolinPlot(c, densityGroups(), ViolinPlotOptions{Frame: ChartFrame{Label: "Hymns"}}) + }) + }) +}