From 2b9824579bd7f6ddbaae3a71cc3e9ebd27b7e500 Mon Sep 17 00:00:00 2001 From: Pierre Baud Date: Tue, 23 Sep 2025 09:31:09 +0200 Subject: [PATCH 01/17] feat: add scatterplot svg library --- packages/p/pierre115/gnov/README.md | 50 ++++++ packages/p/pierre115/gnov/gnomod.toml | 2 + packages/p/pierre115/gnov/linearyflag.gno | 68 ++++++++ packages/p/pierre115/gnov/scatterplot.gno | 152 ++++++++++++++++++ .../pierre115/gnov/test/scatterplot_test.gno | 61 +++++++ packages/p/pierre115/gnov/type.gno | 16 ++ 6 files changed, 349 insertions(+) create mode 100644 packages/p/pierre115/gnov/README.md create mode 100644 packages/p/pierre115/gnov/gnomod.toml create mode 100644 packages/p/pierre115/gnov/linearyflag.gno create mode 100644 packages/p/pierre115/gnov/scatterplot.gno create mode 100644 packages/p/pierre115/gnov/test/scatterplot_test.gno create mode 100644 packages/p/pierre115/gnov/type.gno diff --git a/packages/p/pierre115/gnov/README.md b/packages/p/pierre115/gnov/README.md new file mode 100644 index 0000000..02ded05 --- /dev/null +++ b/packages/p/pierre115/gnov/README.md @@ -0,0 +1,50 @@ +# GNOV Scatterplot + +The `gnov` package allows you to render a scatter plot as an SVG image. It takes a list of `(x, y)` points and draws them as circles on a 2D canvas. You can also apply optional flags to display regression lines or curves. + +## Function + +```go +Testscatter(POINTS, "TITLE", "X_AXIS_TITLE", "Y_AXIS_TITLE", "FLAG") +``` + +## ARGS + +`POINTS` strcuture is set with the following arguments : + ```go + X, Y float64 + Color string + Label string +``` + +`TITLE` is a string + +`X_AXIS_TITLE` is a string + +`Y_AXIS_TITLE` is a string + + +## Structure + +`Usecase` + +```go +ScatterPlot{ + Points: []Point{ + {X: 100, Y: 00, Label: "A"}, + {X: 101, Y: 20, Label: "B"}, + {X: 102, Y: 40, Label: "C"}, + {X: 103, Y: 60, Label: "D"}, + }, + Title: "Evolution of sales", + XAxis: "Années", + YAxis: "Ventes", + Flag: "re", + } +``` + + +## Flags + +Actually there is one existing flags : `Lineary Regression flag` that display the `regression line` of the scatterplot can be actived by the flag `re`. +Each flag shows the `equation` of the regression in the top left of the Scatterplot. diff --git a/packages/p/pierre115/gnov/gnomod.toml b/packages/p/pierre115/gnov/gnomod.toml new file mode 100644 index 0000000..c43929e --- /dev/null +++ b/packages/p/pierre115/gnov/gnomod.toml @@ -0,0 +1,2 @@ +module = "gno.land/p/pierre115/gnov" +gno = "0.9" diff --git a/packages/p/pierre115/gnov/linearyflag.gno b/packages/p/pierre115/gnov/linearyflag.gno new file mode 100644 index 0000000..3c67843 --- /dev/null +++ b/packages/p/pierre115/gnov/linearyflag.gno @@ -0,0 +1,68 @@ +package gnov + +import ( + "math" + + "gno.land/p/nt/ufmt" +) + +// Function to get slope and intercept with linear way +func LinearRegression(points []Point) (slope, intercept float64) { + n := float64(len(points)) + sumX := float64(0) + sumY := float64(0) + sumXY := float64(0) + sumX2 := float64(0) + + for _, p := range points { + sumX += p.X + sumY += p.Y + sumXY += p.X * p.Y + sumX2 += p.X * p.X + } + + slope = (n*sumXY - sumX*sumY) / (n*sumX2 - sumX*sumX) + intercept = (sumY - slope*sumX) / n + return +} + +// Calcul the regression line and return as SVG string +func RenderReFlag(points []Point, minX, maxX, minY, maxY float64, canvasWidth, canvasHeight int) string { + slope, intercept := LinearRegression(points) + + xStart := minX + xEnd := maxX + yStart := slope*xStart + intercept + yEnd := slope*xEnd + intercept + + // Normalize with the canvas data + nx1 := 40 + (xStart-minX)/(maxX-minX)*float64(canvasWidth-60) + ny1 := float64(canvasHeight-40) - (yStart-minY)/(maxY-minY)*float64(canvasHeight-60) + nx2 := 40 + (xEnd-minX)/(maxX-minX)*float64(canvasWidth-60) + ny2 := float64(canvasHeight-40) - (yEnd-minY)/(maxY-minY)*float64(canvasHeight-60) + + // calcul of angle + dx := nx2 - nx1 + dy := ny2 - ny1 + angleRad := math.Atan2(dy, dx) + angleDeg := angleRad * 180 / math.Pi + + svgOut := "" + + // SVG Rectangle as regression line + svgOut += ufmt.Sprintf( + ``, + int(nx1), int(ny1), + int(math.Hypot(nx2-nx1, ny2-ny1)), + angleDeg, int(nx1), int(ny1), + ) + + // Equation of the line + equation := ufmt.Sprintf("y = %.2fx + %.2f", slope, intercept) + svgOut += ufmt.Sprintf( + `Equation : %s`, + equation, + ) + + return svgOut +} diff --git a/packages/p/pierre115/gnov/scatterplot.gno b/packages/p/pierre115/gnov/scatterplot.gno new file mode 100644 index 0000000..74620c0 --- /dev/null +++ b/packages/p/pierre115/gnov/scatterplot.gno @@ -0,0 +1,152 @@ +// Package gnov provides functionality to render a scatter plot as an SVG image. +// It takes a list of points (x,y) and draws them as circles on a 2D . +// You can also apply Flags. +package gnov + +import ( + "math" + + "gno.land/p/nt/ufmt" +) + +func niceStep(rangeVal float64, maxTicks int) float64 { + var niceBase float64 + rawStep := rangeVal / float64(maxTicks) + exponent := math.Floor(math.Log10(rawStep)) + fraction := rawStep / math.Pow(10, exponent) + + switch { + case fraction < 1.5: + niceBase = 1 + case fraction < 3: + niceBase = 2 + case fraction < 7: + niceBase = 5 + default: + niceBase = 10 + } + + return niceBase * math.Pow(10, exponent) +} + +// Use the ideal steps for X and Y axis +func StepXY(maxX, maxY, minX, minY float64) (float64 ,float64, float64, float64, float64, float64) { + + rangeX := maxX - minX + stepX := niceStep(rangeX, 10) + startX := math.Floor(minX/stepX) * stepX + endX := math.Ceil(maxX/stepX) * stepX + rangeY := maxY - minY + stepY := niceStep(rangeY, 10) + startY := math.Floor(minY/stepY) * stepY + endY := math.Ceil(maxY/stepY) * stepY + + return stepX, startX, endX, stepY, startY, endY +} + +// Draw axes and axes titles, return SVG strings +func RenderAxes(X_axe string, Y_axe string, Width, Height int, maxX, maxY, minX, minY float64) string { + svgOut := "" + + // Axe Y + svgOut += ufmt.Sprintf(``, 40, Height-40, Width-60, 1) + svgOut += ufmt.Sprintf( + `%s`, + 10, Height/2, 12, 15, Height/2, Y_axe, + ) + + // Axe X + svgOut += ufmt.Sprintf(``, 40, 20, 1, Height-60) + svgOut += ufmt.Sprintf( + `%s`, + Width/2, Height-10, 12, X_axe, + ) + + // Scale helpers + scaleX := func(val float64) float64 { + return 40 + (val-minX)/(maxX-minX)*float64(Width-60) + } + scaleY := func(val float64) float64 { + return float64(Height-40) - (val-minY)/(maxY-minY)*float64(Height-60) + } + + // Nicesteps calcul for graduation + stepX, startX, endX, stepY, startY, endY := StepXY(maxX, maxY, minX, minY) + + // Graduation X + for val := startX; val <= endX; val += stepX { + nx := scaleX(val) + y := float64(Height - 40) + svgOut += ufmt.Sprintf(``, int(nx), int(y), 1, 5) + svgOut += ufmt.Sprintf(`%.1f`, int(nx), int(y)+18, 11, val) + } + + // Graduation Y + for val := startY; val <= endY; val += stepY { + ny := scaleY(val) + x := float64(40) + svgOut += ufmt.Sprintf(``, int(x)-5, int(ny), 5, 1) + svgOut += ufmt.Sprintf(`%.1f`, int(x)-8, int(ny)+4, 11, val) + } + + return svgOut +} + +// Returns an img svg markup as a string, including a markdown header if a non-empty title is provided. +// The function need a Point struct, two axes titles and a flag. +// You can see existings flags in the Readme.md +func (sp ScatterPlot) String() string { + + const ( + Width = 750 + Height = 600 + pointRadius = 2 + ) + + if len(sp.Points) == 0 { + return "\nscatterplot fails: no data provided" + } + + // calcul min/max + minX, minY := sp.Points[0].X, sp.Points[0].Y + maxX, maxY := sp.Points[0].X, sp.Points[0].Y + + for _, p := range sp.Points { + if p.X > maxX { maxX = p.X } + if p.X < minX { minX = p.X } + if p.Y > maxY { maxY = p.Y } + if p.Y < minY { minY = p.Y } + } + + svgOut := "" + svgOut += RenderAxes(sp.XAxis, sp.YAxis, Width, Height, maxX, maxY, minX, minY) + + // Draw Points and labels + for _, p := range sp.Points { + nx := 40 + (p.X-minX)/(maxX-minX)*float64(Width-60) + ny := float64(Height-40) - (p.Y-minY)/(maxY-minY)*float64(Height-60) + + svgOut += ufmt.Sprintf(``, int(nx), int(ny), pointRadius, p.Color) + + if p.Label != "" { + svgOut += ufmt.Sprintf( + `%s`, + int(nx)+5, int(ny)+12, p.Label, + ) + } + } + + // Flags : + if sp.Flag == "re" { + svgOut += RenderReFlag(sp.Points, minX, maxX, minY, maxY, Width, Height) + } + + // Draw Title + if sp.Title != "" { + svgOut += ufmt.Sprintf(`%s`, + Width/2, 20, sp.Title, + ) + } + + return svgOut +} diff --git a/packages/p/pierre115/gnov/test/scatterplot_test.gno b/packages/p/pierre115/gnov/test/scatterplot_test.gno new file mode 100644 index 0000000..6af1ff2 --- /dev/null +++ b/packages/p/pierre115/gnov/test/scatterplot_test.gno @@ -0,0 +1,61 @@ +package gnov + +import ( + "strings" + "testing" +) + +// Test for basic scatter plot rendering +func TestScatterPlot(t *testing.T) { + sp := ScatterPlot{ + Points: []Point{ + {X: 0, Y: 0, Label: "A", Color: "red"}, + {X: 1, Y: 2, Label: "B", Color: "blue"}, + }, + Title: "Test Scatter", + XAxis: "X Axis", + YAxis: "Y Axis", + } + + got := sp.String() + + if got == "" { + t.Fatal("Render output is empty") + } + if !strings.Contains(got, "Test Scatter") { + t.Error("title not found in render output") + } + if !strings.Contains(got, "X Axis") { + t.Error("X axis label not found") + } + if !strings.Contains(got, "Y Axis") { + t.Error("Y axis label not found") + } + if !strings.Contains(got, " Date: Mon, 29 Sep 2025 11:43:55 +0200 Subject: [PATCH 02/17] [ADD]: README.md and increasing variables in ScatterPlot structure --- packages/p/pierre115/gnov/README.md | 36 +++++++------- packages/p/pierre115/gnov/scatterplot.gno | 49 ++++++++++++++----- .../gnov/{test => }/scatterplot_test.gno | 2 +- 3 files changed, 57 insertions(+), 30 deletions(-) rename packages/p/pierre115/gnov/{test => }/scatterplot_test.gno (98%) diff --git a/packages/p/pierre115/gnov/README.md b/packages/p/pierre115/gnov/README.md index 02ded05..aa78677 100644 --- a/packages/p/pierre115/gnov/README.md +++ b/packages/p/pierre115/gnov/README.md @@ -2,29 +2,29 @@ The `gnov` package allows you to render a scatter plot as an SVG image. It takes a list of `(x, y)` points and draws them as circles on a 2D canvas. You can also apply optional flags to display regression lines or curves. -## Function +## API references ```go Testscatter(POINTS, "TITLE", "X_AXIS_TITLE", "Y_AXIS_TITLE", "FLAG") ``` -## ARGS - `POINTS` strcuture is set with the following arguments : ```go - X, Y float64 - Color string - Label string + type Point struct { + X, Y float64 //Coordinate of the point + Color string // Color of the point + Label string // Associate a label to the point + } ``` +`TITLE`, `X_AXIS_TITLE`, `Y_AXIS_TITLE` are strings. -`TITLE` is a string +`FlagRe` is a `Boolean` value: `true` to enable and false by default. -`X_AXIS_TITLE` is a string +`Maxticks` is an `int` n used to divide the axis into n graduation marks. -`Y_AXIS_TITLE` is a string +`Width` and `height` are `int` to personalize the size of the scatterplot. - -## Structure +## Usage `Usecase` @@ -36,15 +36,17 @@ ScatterPlot{ {X: 102, Y: 40, Label: "C"}, {X: 103, Y: 60, Label: "D"}, }, - Title: "Evolution of sales", - XAxis: "Années", - YAxis: "Ventes", - Flag: "re", + Title: "Sales Growth", + XAxis: "Years", + YAxis: "Sales", + FlagRe: true, + maxticks: 20, + width: 800, + height: 800, } ``` - ## Flags -Actually there is one existing flags : `Lineary Regression flag` that display the `regression line` of the scatterplot can be actived by the flag `re`. +`Lineary Regression flag` that display the `regression line` of the scatterplot can be actived by the bool `true`. Each flag shows the `equation` of the regression in the top left of the Scatterplot. diff --git a/packages/p/pierre115/gnov/scatterplot.gno b/packages/p/pierre115/gnov/scatterplot.gno index 74620c0..4f86367 100644 --- a/packages/p/pierre115/gnov/scatterplot.gno +++ b/packages/p/pierre115/gnov/scatterplot.gno @@ -9,8 +9,32 @@ import ( "gno.land/p/nt/ufmt" ) -func niceStep(rangeVal float64, maxTicks int) float64 { +// Get max ticks for axis +func (sp ScatterPlot) GetMaxTicks() int { + if sp.maxTicks == 0 { + return 10 + } + return sp.maxTicks +} + +// Get width and height of the svg +func (sp ScatterPlot) GetWidth() int { + if sp.Width == 0 { + return 750 + } + return sp.Width +} + +func (sp ScatterPlot) GetHeight() int { + if sp.Height == 0 { + return 600 + } + return sp.Height +} + +func niceStep(sp ScatterPlot, rangeVal float64) float64 { var niceBase float64 + maxTicks := sp.GetMaxTicks() rawStep := rangeVal / float64(maxTicks) exponent := math.Floor(math.Log10(rawStep)) fraction := rawStep / math.Pow(10, exponent) @@ -30,14 +54,14 @@ func niceStep(rangeVal float64, maxTicks int) float64 { } // Use the ideal steps for X and Y axis -func StepXY(maxX, maxY, minX, minY float64) (float64 ,float64, float64, float64, float64, float64) { +func StepXY(sp ScatterPlot, maxX, maxY, minX, minY float64) (float64 ,float64, float64, float64, float64, float64) { rangeX := maxX - minX - stepX := niceStep(rangeX, 10) + stepX := niceStep(sp, rangeX) startX := math.Floor(minX/stepX) * stepX endX := math.Ceil(maxX/stepX) * stepX rangeY := maxY - minY - stepY := niceStep(rangeY, 10) + stepY := niceStep(sp, rangeY) startY := math.Floor(minY/stepY) * stepY endY := math.Ceil(maxY/stepY) * stepY @@ -45,21 +69,21 @@ func StepXY(maxX, maxY, minX, minY float64) (float64 ,float64, float64, float64, } // Draw axes and axes titles, return SVG strings -func RenderAxes(X_axe string, Y_axe string, Width, Height int, maxX, maxY, minX, minY float64) string { +func RenderAxes(sp ScatterPlot, Width, Height int, maxX, maxY, minX, minY float64) string { svgOut := "" // Axe Y svgOut += ufmt.Sprintf(``, 40, Height-40, Width-60, 1) svgOut += ufmt.Sprintf( `%s`, - 10, Height/2, 12, 15, Height/2, Y_axe, + 10, Height/2, 12, 15, Height/2, sp.YAxis, ) // Axe X svgOut += ufmt.Sprintf(``, 40, 20, 1, Height-60) svgOut += ufmt.Sprintf( `%s`, - Width/2, Height-10, 12, X_axe, + Width/2, Height-10, 12, sp.XAxis, ) // Scale helpers @@ -71,7 +95,7 @@ func RenderAxes(X_axe string, Y_axe string, Width, Height int, maxX, maxY, minX, } // Nicesteps calcul for graduation - stepX, startX, endX, stepY, startY, endY := StepXY(maxX, maxY, minX, minY) + stepX, startX, endX, stepY, startY, endY := StepXY(sp, maxX, maxY, minX, minY) // Graduation X for val := startX; val <= endX; val += stepX { @@ -98,11 +122,12 @@ func RenderAxes(X_axe string, Y_axe string, Width, Height int, maxX, maxY, minX, func (sp ScatterPlot) String() string { const ( - Width = 750 - Height = 600 pointRadius = 2 ) + Width := sp.GetWidth() + Height := sp.GetHeight() + if len(sp.Points) == 0 { return "\nscatterplot fails: no data provided" } @@ -119,7 +144,7 @@ func (sp ScatterPlot) String() string { } svgOut := "" - svgOut += RenderAxes(sp.XAxis, sp.YAxis, Width, Height, maxX, maxY, minX, minY) + svgOut += RenderAxes(sp, Width, Height, maxX, maxY, minX, minY) // Draw Points and labels for _, p := range sp.Points { @@ -137,7 +162,7 @@ func (sp ScatterPlot) String() string { } // Flags : - if sp.Flag == "re" { + if sp.FlagRe == true { svgOut += RenderReFlag(sp.Points, minX, maxX, minY, maxY, Width, Height) } diff --git a/packages/p/pierre115/gnov/test/scatterplot_test.gno b/packages/p/pierre115/gnov/scatterplot_test.gno similarity index 98% rename from packages/p/pierre115/gnov/test/scatterplot_test.gno rename to packages/p/pierre115/gnov/scatterplot_test.gno index 6af1ff2..cead69c 100644 --- a/packages/p/pierre115/gnov/test/scatterplot_test.gno +++ b/packages/p/pierre115/gnov/scatterplot_test.gno @@ -47,7 +47,7 @@ func TestScatterPlotReFlag(t *testing.T) { Title: "With Regression", XAxis: "X", YAxis: "Y", - Flag: "re", + FlagRe: true, } got := sp.String() From 89a79cf2cdb3484e61f98b564f7b8923f3010150 Mon Sep 17 00:00:00 2001 From: Pierre Baud Date: Mon, 29 Sep 2025 11:46:16 +0200 Subject: [PATCH 03/17] [FIX]: type.gno --- packages/p/pierre115/gnov/type.gno | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/p/pierre115/gnov/type.gno b/packages/p/pierre115/gnov/type.gno index 3f08d5b..e2426a4 100644 --- a/packages/p/pierre115/gnov/type.gno +++ b/packages/p/pierre115/gnov/type.gno @@ -2,15 +2,17 @@ package gnov // Points structure type Point struct { - X, Y float64 - Color string - Label string + X, Y float64 //Coordinate of the point + Color string // Color of the point + Label string // Associate a label to the point } // Scatterplot structure type ScatterPlot struct { - Points []Point - Title string - XAxis, YAxis string - Flag string + Points []Point // Points structure (points clouds) + Title string // Title of the scatter plot + XAxis, YAxis string // Title of X and Y axis + FlagRe bool // Optional flag for regression "re" + maxTicks int // Optional max ticks for axis (default 10) + Width, Height int // Optional width and height of the svg (default 750x600) } \ No newline at end of file From 7deb6170d085fcc3db46699ad91e1109747313a7 Mon Sep 17 00:00:00 2001 From: Pierre Baud Date: Mon, 29 Sep 2025 15:11:53 +0200 Subject: [PATCH 04/17] Feat: Better description of gnov --- packages/p/pierre115/gnov/scatterplot.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/p/pierre115/gnov/scatterplot.gno b/packages/p/pierre115/gnov/scatterplot.gno index 4f86367..4736426 100644 --- a/packages/p/pierre115/gnov/scatterplot.gno +++ b/packages/p/pierre115/gnov/scatterplot.gno @@ -1,4 +1,4 @@ -// Package gnov provides functionality to render a scatter plot as an SVG image. +// Package gnov (Gno Visual) provides functionality to render a scatter plot as an SVG image. // It takes a list of points (x,y) and draws them as circles on a 2D . // You can also apply Flags. package gnov From b0d54134961096a6519373862137dd7b24038783 Mon Sep 17 00:00:00 2001 From: Pierre Baud Date: Fri, 3 Oct 2025 10:29:13 +0200 Subject: [PATCH 05/17] test CI PR --- packages/p/pierre115/gnov/type.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/p/pierre115/gnov/type.gno b/packages/p/pierre115/gnov/type.gno index e2426a4..423c755 100644 --- a/packages/p/pierre115/gnov/type.gno +++ b/packages/p/pierre115/gnov/type.gno @@ -15,4 +15,4 @@ type ScatterPlot struct { FlagRe bool // Optional flag for regression "re" maxTicks int // Optional max ticks for axis (default 10) Width, Height int // Optional width and height of the svg (default 750x600) -} \ No newline at end of file +} From 0ec5293cbb4a9c9e72b330c7024d42cd2c9b7455 Mon Sep 17 00:00:00 2001 From: Pierre Baud Date: Mon, 6 Oct 2025 10:11:29 +0200 Subject: [PATCH 06/17] Fix: CI Checks fmt --- packages/p/pierre115/gnov/scatterplot.gno | 122 ++++++++++-------- .../p/pierre115/gnov/scatterplot_test.gno | 2 +- packages/p/pierre115/gnov/type.gno | 14 +- 3 files changed, 73 insertions(+), 65 deletions(-) diff --git a/packages/p/pierre115/gnov/scatterplot.gno b/packages/p/pierre115/gnov/scatterplot.gno index 4736426..6bfa18b 100644 --- a/packages/p/pierre115/gnov/scatterplot.gno +++ b/packages/p/pierre115/gnov/scatterplot.gno @@ -12,7 +12,7 @@ import ( // Get max ticks for axis func (sp ScatterPlot) GetMaxTicks() int { if sp.maxTicks == 0 { - return 10 + return 10 } return sp.maxTicks } @@ -20,7 +20,7 @@ func (sp ScatterPlot) GetMaxTicks() int { // Get width and height of the svg func (sp ScatterPlot) GetWidth() int { if sp.Width == 0 { - return 750 + return 750 } return sp.Width } @@ -41,76 +41,76 @@ func niceStep(sp ScatterPlot, rangeVal float64) float64 { switch { case fraction < 1.5: - niceBase = 1 + niceBase = 1 case fraction < 3: - niceBase = 2 - case fraction < 7: - niceBase = 5 + niceBase = 2 + case fraction < 7: + niceBase = 5 default: - niceBase = 10 + niceBase = 10 } return niceBase * math.Pow(10, exponent) } // Use the ideal steps for X and Y axis -func StepXY(sp ScatterPlot, maxX, maxY, minX, minY float64) (float64 ,float64, float64, float64, float64, float64) { +func StepXY(sp ScatterPlot, maxX, maxY, minX, minY float64) (float64, float64, float64, float64, float64, float64) { - rangeX := maxX - minX - stepX := niceStep(sp, rangeX) - startX := math.Floor(minX/stepX) * stepX - endX := math.Ceil(maxX/stepX) * stepX - rangeY := maxY - minY - stepY := niceStep(sp, rangeY) - startY := math.Floor(minY/stepY) * stepY - endY := math.Ceil(maxY/stepY) * stepY + rangeX := maxX - minX + stepX := niceStep(sp, rangeX) + startX := math.Floor(minX/stepX) * stepX + endX := math.Ceil(maxX/stepX) * stepX + rangeY := maxY - minY + stepY := niceStep(sp, rangeY) + startY := math.Floor(minY/stepY) * stepY + endY := math.Ceil(maxY/stepY) * stepY return stepX, startX, endX, stepY, startY, endY } // Draw axes and axes titles, return SVG strings func RenderAxes(sp ScatterPlot, Width, Height int, maxX, maxY, minX, minY float64) string { - svgOut := "" + svgOut := "" // Axe Y - svgOut += ufmt.Sprintf(``, 40, Height-40, Width-60, 1) - svgOut += ufmt.Sprintf( - `%s`, - 10, Height/2, 12, 15, Height/2, sp.YAxis, - ) + svgOut += ufmt.Sprintf(``, 40, Height-40, Width-60, 1) + svgOut += ufmt.Sprintf( + `%s`, + 10, Height/2, 12, 15, Height/2, sp.YAxis, + ) // Axe X - svgOut += ufmt.Sprintf(``, 40, 20, 1, Height-60) - svgOut += ufmt.Sprintf( - `%s`, - Width/2, Height-10, 12, sp.XAxis, - ) + svgOut += ufmt.Sprintf(``, 40, 20, 1, Height-60) + svgOut += ufmt.Sprintf( + `%s`, + Width/2, Height-10, 12, sp.XAxis, + ) // Scale helpers scaleX := func(val float64) float64 { - return 40 + (val-minX)/(maxX-minX)*float64(Width-60) + return 40 + (val-minX)/(maxX-minX)*float64(Width-60) } scaleY := func(val float64) float64 { - return float64(Height-40) - (val-minY)/(maxY-minY)*float64(Height-60) + return float64(Height-40) - (val-minY)/(maxY-minY)*float64(Height-60) } - // Nicesteps calcul for graduation + // N icesteps calcul for graduation stepX, startX, endX, stepY, startY, endY := StepXY(sp, maxX, maxY, minX, minY) // Graduation X for val := startX; val <= endX; val += stepX { - nx := scaleX(val) - y := float64(Height - 40) - svgOut += ufmt.Sprintf(``, int(nx), int(y), 1, 5) - svgOut += ufmt.Sprintf(`%.1f`, int(nx), int(y)+18, 11, val) + nx := scaleX(val) + y := float64(Height - 40) + svgOut += ufmt.Sprintf(``, int(nx), int(y), 1, 5) + svgOut += ufmt.Sprintf(`%.1f`, int(nx), int(y)+18, 11, val) } // Graduation Y for val := startY; val <= endY; val += stepY { - ny := scaleY(val) - x := float64(40) - svgOut += ufmt.Sprintf(``, int(x)-5, int(ny), 5, 1) - svgOut += ufmt.Sprintf(`%.1f`, int(x)-8, int(ny)+4, 11, val) + ny := scaleY(val) + x := float64(40) + svgOut += ufmt.Sprintf(``, int(x)-5, int(ny), 5, 1) + svgOut += ufmt.Sprintf(`%.1f`, int(x)-8, int(ny)+4, 11, val) } return svgOut @@ -122,14 +122,14 @@ func RenderAxes(sp ScatterPlot, Width, Height int, maxX, maxY, minX, minY float6 func (sp ScatterPlot) String() string { const ( - pointRadius = 2 + pointRadius = 2 ) Width := sp.GetWidth() Height := sp.GetHeight() if len(sp.Points) == 0 { - return "\nscatterplot fails: no data provided" + return "\nscatterplot fails: no data provided" } // calcul min/max @@ -137,10 +137,18 @@ func (sp ScatterPlot) String() string { maxX, maxY := sp.Points[0].X, sp.Points[0].Y for _, p := range sp.Points { - if p.X > maxX { maxX = p.X } - if p.X < minX { minX = p.X } - if p.Y > maxY { maxY = p.Y } - if p.Y < minY { minY = p.Y } + if p.X > maxX { + maxX = p.X + } + if p.X < minX { + minX = p.X + } + if p.Y > maxY { + maxY = p.Y + } + if p.Y < minY {)pp + minY = p.Y + } } svgOut := "" @@ -148,29 +156,29 @@ func (sp ScatterPlot) String() string { // Draw Points and labels for _, p := range sp.Points { - nx := 40 + (p.X-minX)/(maxX-minX)*float64(Width-60) - ny := float64(Height-40) - (p.Y-minY)/(maxY-minY)*float64(Height-60) + nx := 40 + (p.X-minX)/(maxX-minX)*float64(Width-60) + ny := float64(Height-40) - (p.Y-minY)/(maxY-minY)*float64(Height-60) - svgOut += ufmt.Sprintf(``, int(nx), int(ny), pointRadius, p.Color) + svgOut += ufmt.Sprintf(``, int(nx), int(ny), pointRadius, p.Color) - if p.Label != "" { - svgOut += ufmt.Sprintf( - `%s`, - int(nx)+5, int(ny)+12, p.Label, - ) - } + if p.Label != "" { + svgOut += ufmt.Sprintf( + `%s`, + int(nx)+5, int(ny)+12, p.Label, + ) + } } // Flags : if sp.FlagRe == true { - svgOut += RenderReFlag(sp.Points, minX, maxX, minY, maxY, Width, Height) + svgOut += RenderReFlag(sp.Points, minX, maxX, minY, maxY, Width, Height) } // Draw Title if sp.Title != "" { - svgOut += ufmt.Sprintf(`%s`, - Width/2, 20, sp.Title, - ) + svgOut += ufmt.Sprintf(`%s`, + Width/2, 20, sp.Title, + ) } return svgOut diff --git a/packages/p/pierre115/gnov/scatterplot_test.gno b/packages/p/pierre115/gnov/scatterplot_test.gno index cead69c..18dff2f 100644 --- a/packages/p/pierre115/gnov/scatterplot_test.gno +++ b/packages/p/pierre115/gnov/scatterplot_test.gno @@ -47,7 +47,7 @@ func TestScatterPlotReFlag(t *testing.T) { Title: "With Regression", XAxis: "X", YAxis: "Y", - FlagRe: true, + FlagRe: true, } got := sp.String() diff --git a/packages/p/pierre115/gnov/type.gno b/packages/p/pierre115/gnov/type.gno index 423c755..0ffad8d 100644 --- a/packages/p/pierre115/gnov/type.gno +++ b/packages/p/pierre115/gnov/type.gno @@ -3,16 +3,16 @@ package gnov // Points structure type Point struct { X, Y float64 //Coordinate of the point - Color string // Color of the point - Label string // Associate a label to the point + Color string // Color of the point + Label string // Associate a label to the point } // Scatterplot structure type ScatterPlot struct { - Points []Point // Points structure (points clouds) - Title string // Title of the scatter plot - XAxis, YAxis string // Title of X and Y axis - FlagRe bool // Optional flag for regression "re" - maxTicks int // Optional max ticks for axis (default 10) + Points []Point // Points structure (points clouds) + Title string // Title of the scatter plot + XAxis, YAxis string // Title of X and Y axis + FlagRe bool // Optional flag for regression "re" + maxTicks int // Optional max ticks for axis (default 10) Width, Height int // Optional width and height of the svg (default 750x600) } From 2a1700e4670b9e9280d66a509245f4d99a32b8fd Mon Sep 17 00:00:00 2001 From: Pierre Baud Date: Mon, 6 Oct 2025 10:49:12 +0200 Subject: [PATCH 07/17] Fix: Coding style --- packages/p/pierre115/gnov/scatterplot.gno | 14 ++++++++++---- packages/p/pierre115/gnov/type.gno | 4 ++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/p/pierre115/gnov/scatterplot.gno b/packages/p/pierre115/gnov/scatterplot.gno index 6bfa18b..026fcad 100644 --- a/packages/p/pierre115/gnov/scatterplot.gno +++ b/packages/p/pierre115/gnov/scatterplot.gno @@ -9,10 +9,16 @@ import ( "gno.land/p/nt/ufmt" ) +const ( + DefaultWidth = 750 + DefaultHeight = 500 + DefaultMaxTicks = 10 +) + // Get max ticks for axis func (sp ScatterPlot) GetMaxTicks() int { if sp.maxTicks == 0 { - return 10 + return DefaultMaxTicks } return sp.maxTicks } @@ -20,14 +26,14 @@ func (sp ScatterPlot) GetMaxTicks() int { // Get width and height of the svg func (sp ScatterPlot) GetWidth() int { if sp.Width == 0 { - return 750 + return DefaultWidth } return sp.Width } func (sp ScatterPlot) GetHeight() int { if sp.Height == 0 { - return 600 + return DefaultHeight } return sp.Height } @@ -146,7 +152,7 @@ func (sp ScatterPlot) String() string { if p.Y > maxY { maxY = p.Y } - if p.Y < minY {)pp + if p.Y < minY { minY = p.Y } } diff --git a/packages/p/pierre115/gnov/type.gno b/packages/p/pierre115/gnov/type.gno index 0ffad8d..09c20d1 100644 --- a/packages/p/pierre115/gnov/type.gno +++ b/packages/p/pierre115/gnov/type.gno @@ -2,7 +2,7 @@ package gnov // Points structure type Point struct { - X, Y float64 //Coordinate of the point + X, Y float64 // Coordinate of the point Color string // Color of the point Label string // Associate a label to the point } @@ -12,7 +12,7 @@ type ScatterPlot struct { Points []Point // Points structure (points clouds) Title string // Title of the scatter plot XAxis, YAxis string // Title of X and Y axis - FlagRe bool // Optional flag for regression "re" + FlagRe bool // Optional flag for linear regression maxTicks int // Optional max ticks for axis (default 10) Width, Height int // Optional width and height of the svg (default 750x600) } From 4b25d1c51a75e0c5bcada4b4d328d13036cd5f57 Mon Sep 17 00:00:00 2001 From: Pierre Baud Date: Tue, 7 Oct 2025 16:47:23 +0200 Subject: [PATCH 08/17] Fix: CI const -> type.gno --- packages/p/pierre115/gnov/scatterplot.gno | 6 +++--- packages/p/pierre115/gnov/type.gno | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/p/pierre115/gnov/scatterplot.gno b/packages/p/pierre115/gnov/scatterplot.gno index 026fcad..d2a122e 100644 --- a/packages/p/pierre115/gnov/scatterplot.gno +++ b/packages/p/pierre115/gnov/scatterplot.gno @@ -10,15 +10,15 @@ import ( ) const ( - DefaultWidth = 750 - DefaultHeight = 500 + DefaultWidth = 750 + DefaultHeight = 500 DefaultMaxTicks = 10 ) // Get max ticks for axis func (sp ScatterPlot) GetMaxTicks() int { if sp.maxTicks == 0 { - return DefaultMaxTicks + return DefaultMaxTicks } return sp.maxTicks } diff --git a/packages/p/pierre115/gnov/type.gno b/packages/p/pierre115/gnov/type.gno index 09c20d1..6967ec5 100644 --- a/packages/p/pierre115/gnov/type.gno +++ b/packages/p/pierre115/gnov/type.gno @@ -12,7 +12,7 @@ type ScatterPlot struct { Points []Point // Points structure (points clouds) Title string // Title of the scatter plot XAxis, YAxis string // Title of X and Y axis - FlagRe bool // Optional flag for linear regression - maxTicks int // Optional max ticks for axis (default 10) - Width, Height int // Optional width and height of the svg (default 750x600) + FlagRe bool // Optional flag for linear regression + maxTicks int // Optional max ticks for axis (default 10) + Width, Height int // Optional width and height of the svg (default 750x600) } From 2bc577702f738a8fc413373cbec072a54b81654b Mon Sep 17 00:00:00 2001 From: Pierre Baud Date: Wed, 8 Oct 2025 10:45:29 +0200 Subject: [PATCH 09/17] Fix: CI check type.gno --- packages/p/pierre115/gnov/scatterplot.gno | 101 +++++++++++----------- packages/p/pierre115/gnov/type.gno | 12 +-- 2 files changed, 56 insertions(+), 57 deletions(-) diff --git a/packages/p/pierre115/gnov/scatterplot.gno b/packages/p/pierre115/gnov/scatterplot.gno index d2a122e..535db58 100644 --- a/packages/p/pierre115/gnov/scatterplot.gno +++ b/packages/p/pierre115/gnov/scatterplot.gno @@ -23,7 +23,7 @@ func (sp ScatterPlot) GetMaxTicks() int { return sp.maxTicks } -// Get width and height of the svg +// Get width of the svg func (sp ScatterPlot) GetWidth() int { if sp.Width == 0 { return DefaultWidth @@ -31,6 +31,7 @@ func (sp ScatterPlot) GetWidth() int { return sp.Width } +// Get height of the svg func (sp ScatterPlot) GetHeight() int { if sp.Height == 0 { return DefaultHeight @@ -76,28 +77,28 @@ func StepXY(sp ScatterPlot, maxX, maxY, minX, minY float64) (float64, float64, f // Draw axes and axes titles, return SVG strings func RenderAxes(sp ScatterPlot, Width, Height int, maxX, maxY, minX, minY float64) string { - svgOut := "" + svgOut := "" // Axe Y - svgOut += ufmt.Sprintf(``, 40, Height-40, Width-60, 1) - svgOut += ufmt.Sprintf( - `%s`, - 10, Height/2, 12, 15, Height/2, sp.YAxis, - ) + svgOut += ufmt.Sprintf(``, 40, Height-40, Width-60, 1) + svgOut += ufmt.Sprintf( + `%s`, + 10, Height/2, 12, 15, Height/2, sp.YAxis, + ) // Axe X - svgOut += ufmt.Sprintf(``, 40, 20, 1, Height-60) - svgOut += ufmt.Sprintf( - `%s`, - Width/2, Height-10, 12, sp.XAxis, - ) + svgOut += ufmt.Sprintf(``, 40, 20, 1, Height-60) + svgOut += ufmt.Sprintf( + `%s`, + Width/2, Height-10, 12, sp.XAxis, + ) // Scale helpers scaleX := func(val float64) float64 { - return 40 + (val-minX)/(maxX-minX)*float64(Width-60) + return 40 + (val-minX)/(maxX-minX)*float64(Width-60) } scaleY := func(val float64) float64 { - return float64(Height-40) - (val-minY)/(maxY-minY)*float64(Height-60) + return float64(Height-40) - (val-minY)/(maxY-minY)*float64(Height-60) } // N icesteps calcul for graduation @@ -105,18 +106,18 @@ func RenderAxes(sp ScatterPlot, Width, Height int, maxX, maxY, minX, minY float6 // Graduation X for val := startX; val <= endX; val += stepX { - nx := scaleX(val) - y := float64(Height - 40) - svgOut += ufmt.Sprintf(``, int(nx), int(y), 1, 5) - svgOut += ufmt.Sprintf(`%.1f`, int(nx), int(y)+18, 11, val) + nx := scaleX(val) + y := float64(Height - 40) + svgOut += ufmt.Sprintf(``, int(nx), int(y), 1, 5) + svgOut += ufmt.Sprintf(`%.1f`, int(nx), int(y)+18, 11, val) } // Graduation Y for val := startY; val <= endY; val += stepY { - ny := scaleY(val) - x := float64(40) - svgOut += ufmt.Sprintf(``, int(x)-5, int(ny), 5, 1) - svgOut += ufmt.Sprintf(`%.1f`, int(x)-8, int(ny)+4, 11, val) + ny := scaleY(val) + x := float64(40) + svgOut += ufmt.Sprintf(``, int(x)-5, int(ny), 5, 1) + svgOut += ufmt.Sprintf(`%.1f`, int(x)-8, int(ny)+4, 11, val) } return svgOut @@ -128,14 +129,14 @@ func RenderAxes(sp ScatterPlot, Width, Height int, maxX, maxY, minX, minY float6 func (sp ScatterPlot) String() string { const ( - pointRadius = 2 + pointRadius = 2 ) Width := sp.GetWidth() Height := sp.GetHeight() if len(sp.Points) == 0 { - return "\nscatterplot fails: no data provided" + return "\nscatterplot fails: no data provided" } // calcul min/max @@ -143,18 +144,18 @@ func (sp ScatterPlot) String() string { maxX, maxY := sp.Points[0].X, sp.Points[0].Y for _, p := range sp.Points { - if p.X > maxX { - maxX = p.X - } - if p.X < minX { - minX = p.X - } - if p.Y > maxY { - maxY = p.Y - } - if p.Y < minY { - minY = p.Y - } + if p.X > maxX { + maxX = p.X + } + if p.X < minX { + minX = p.X + } + if p.Y > maxY { + maxY = p.Y + } + if p.Y < minY { + minY = p.Y + } } svgOut := "" @@ -162,29 +163,27 @@ func (sp ScatterPlot) String() string { // Draw Points and labels for _, p := range sp.Points { - nx := 40 + (p.X-minX)/(maxX-minX)*float64(Width-60) - ny := float64(Height-40) - (p.Y-minY)/(maxY-minY)*float64(Height-60) - - svgOut += ufmt.Sprintf(``, int(nx), int(ny), pointRadius, p.Color) - - if p.Label != "" { - svgOut += ufmt.Sprintf( - `%s`, - int(nx)+5, int(ny)+12, p.Label, - ) - } + nx := 40 + (p.X-minX)/(maxX-minX)*float64(Width-60) + ny := float64(Height-40) - (p.Y-minY)/(maxY-minY)*float64(Height-60) + svgOut += ufmt.Sprintf(``, int(nx), int(ny), pointRadius, p.Color) + if p.Label != "" { + svgOut += ufmt.Sprintf( + `%s`, + int(nx)+5, int(ny)+12, p.Label, + ) + } } // Flags : if sp.FlagRe == true { - svgOut += RenderReFlag(sp.Points, minX, maxX, minY, maxY, Width, Height) + svgOut += RenderReFlag(sp.Points, minX, maxX, minY, maxY, Width, Height) } // Draw Title if sp.Title != "" { - svgOut += ufmt.Sprintf(`%s`, - Width/2, 20, sp.Title, - ) + svgOut += ufmt.Sprintf(`%s`, + Width/2, 20, sp.Title, + ) } return svgOut diff --git a/packages/p/pierre115/gnov/type.gno b/packages/p/pierre115/gnov/type.gno index 6967ec5..8f75450 100644 --- a/packages/p/pierre115/gnov/type.gno +++ b/packages/p/pierre115/gnov/type.gno @@ -9,10 +9,10 @@ type Point struct { // Scatterplot structure type ScatterPlot struct { - Points []Point // Points structure (points clouds) - Title string // Title of the scatter plot - XAxis, YAxis string // Title of X and Y axis - FlagRe bool // Optional flag for linear regression - maxTicks int // Optional max ticks for axis (default 10) - Width, Height int // Optional width and height of the svg (default 750x600) + Points []Point // Points structure (points clouds) + Title string // Title of the scatter plot + XAxis, YAxis string // Title of X and Y axis + FlagRe bool // Optional flag for linear regression + maxTicks int // Optional max ticks for axis (default 10) + Width, Height int // Optional width and height of the svg (default 750x600) } From 5069504ccedb290b65e543274f6f813486e6d657 Mon Sep 17 00:00:00 2001 From: Pierre Baud Date: Wed, 8 Oct 2025 14:02:15 +0200 Subject: [PATCH 10/17] Fix: CI check scatterplot.gno --- packages/p/pierre115/gnov/scatterplot.gno | 270 +++++++++++----------- 1 file changed, 135 insertions(+), 135 deletions(-) diff --git a/packages/p/pierre115/gnov/scatterplot.gno b/packages/p/pierre115/gnov/scatterplot.gno index 535db58..a53c9d5 100644 --- a/packages/p/pierre115/gnov/scatterplot.gno +++ b/packages/p/pierre115/gnov/scatterplot.gno @@ -10,117 +10,117 @@ import ( ) const ( - DefaultWidth = 750 - DefaultHeight = 500 - DefaultMaxTicks = 10 + DefaultWidth = 750 + DefaultHeight = 500 + DefaultMaxTicks = 10 ) // Get max ticks for axis func (sp ScatterPlot) GetMaxTicks() int { - if sp.maxTicks == 0 { - return DefaultMaxTicks - } - return sp.maxTicks + if sp.maxTicks == 0 { + return DefaultMaxTicks + } + return sp.maxTicks } // Get width of the svg func (sp ScatterPlot) GetWidth() int { - if sp.Width == 0 { - return DefaultWidth - } - return sp.Width + if sp.Width == 0 { + return DefaultWidth + } + return sp.Width } // Get height of the svg func (sp ScatterPlot) GetHeight() int { - if sp.Height == 0 { - return DefaultHeight - } - return sp.Height + if sp.Height == 0 { + return DefaultHeight + } + return sp.Height } func niceStep(sp ScatterPlot, rangeVal float64) float64 { - var niceBase float64 - maxTicks := sp.GetMaxTicks() - rawStep := rangeVal / float64(maxTicks) - exponent := math.Floor(math.Log10(rawStep)) - fraction := rawStep / math.Pow(10, exponent) - - switch { - case fraction < 1.5: - niceBase = 1 - case fraction < 3: - niceBase = 2 - case fraction < 7: - niceBase = 5 - default: - niceBase = 10 - } - - return niceBase * math.Pow(10, exponent) + var niceBase float64 + maxTicks := sp.GetMaxTicks() + rawStep := rangeVal / float64(maxTicks) + exponent := math.Floor(math.Log10(rawStep)) + fraction := rawStep / math.Pow(10, exponent) + + switch { + case fraction < 1.5: + niceBase = 1 + case fraction < 3: + niceBase = 2 + case fraction < 7: + niceBase = 5 + default: + niceBase = 10 + } + + return niceBase * math.Pow(10, exponent) } // Use the ideal steps for X and Y axis func StepXY(sp ScatterPlot, maxX, maxY, minX, minY float64) (float64, float64, float64, float64, float64, float64) { - rangeX := maxX - minX - stepX := niceStep(sp, rangeX) - startX := math.Floor(minX/stepX) * stepX - endX := math.Ceil(maxX/stepX) * stepX - rangeY := maxY - minY - stepY := niceStep(sp, rangeY) - startY := math.Floor(minY/stepY) * stepY - endY := math.Ceil(maxY/stepY) * stepY + rangeX := maxX - minX + stepX := niceStep(sp, rangeX) + startX := math.Floor(minX/stepX) * stepX + endX := math.Ceil(maxX/stepX) * stepX + rangeY := maxY - minY + stepY := niceStep(sp, rangeY) + startY := math.Floor(minY/stepY) * stepY + endY := math.Ceil(maxY/stepY) * stepY return stepX, startX, endX, stepY, startY, endY } // Draw axes and axes titles, return SVG strings func RenderAxes(sp ScatterPlot, Width, Height int, maxX, maxY, minX, minY float64) string { - svgOut := "" + svgOut := "" // Axe Y - svgOut += ufmt.Sprintf(``, 40, Height-40, Width-60, 1) - svgOut += ufmt.Sprintf( - `%s`, - 10, Height/2, 12, 15, Height/2, sp.YAxis, - ) + svgOut += ufmt.Sprintf(``, 40, Height-40, Width-60, 1) + svgOut += ufmt.Sprintf( + `%s`, + 10, Height/2, 12, 15, Height/2, sp.YAxis, + ) // Axe X - svgOut += ufmt.Sprintf(``, 40, 20, 1, Height-60) - svgOut += ufmt.Sprintf( - `%s`, - Width/2, Height-10, 12, sp.XAxis, - ) - - // Scale helpers - scaleX := func(val float64) float64 { - return 40 + (val-minX)/(maxX-minX)*float64(Width-60) - } - scaleY := func(val float64) float64 { - return float64(Height-40) - (val-minY)/(maxY-minY)*float64(Height-60) - } - - // N icesteps calcul for graduation - stepX, startX, endX, stepY, startY, endY := StepXY(sp, maxX, maxY, minX, minY) - - // Graduation X - for val := startX; val <= endX; val += stepX { - nx := scaleX(val) - y := float64(Height - 40) - svgOut += ufmt.Sprintf(``, int(nx), int(y), 1, 5) - svgOut += ufmt.Sprintf(`%.1f`, int(nx), int(y)+18, 11, val) - } - - // Graduation Y - for val := startY; val <= endY; val += stepY { - ny := scaleY(val) - x := float64(40) - svgOut += ufmt.Sprintf(``, int(x)-5, int(ny), 5, 1) - svgOut += ufmt.Sprintf(`%.1f`, int(x)-8, int(ny)+4, 11, val) - } - - return svgOut + svgOut += ufmt.Sprintf(``, 40, 20, 1, Height-60) + svgOut += ufmt.Sprintf( + `%s`, + Width/2, Height-10, 12, sp.XAxis, + ) + + // Scale helpers + scaleX := func(val float64) float64 { + return 40 + (val-minX)/(maxX-minX)*float64(Width-60) + } + scaleY := func(val float64) float64 { + return float64(Height-40) - (val-minY)/(maxY-minY)*float64(Height-60) + } + + // N icesteps calcul for graduation + stepX, startX, endX, stepY, startY, endY := StepXY(sp, maxX, maxY, minX, minY) + + // Graduation X + for val := startX; val <= endX; val += stepX { + nx := scaleX(val) + y := float64(Height - 40) + svgOut += ufmt.Sprintf(``, int(nx), int(y), 1, 5) + svgOut += ufmt.Sprintf(`%.1f`, int(nx), int(y)+18, 11, val) + } + + // Graduation Y + for val := startY; val <= endY; val += stepY { + ny := scaleY(val) + x := float64(40) + svgOut += ufmt.Sprintf(``, int(x)-5, int(ny), 5, 1) + svgOut += ufmt.Sprintf(`%.1f`, int(x)-8, int(ny)+4, 11, val) + } + + return svgOut } // Returns an img svg markup as a string, including a markdown header if a non-empty title is provided. @@ -129,62 +129,62 @@ func RenderAxes(sp ScatterPlot, Width, Height int, maxX, maxY, minX, minY float6 func (sp ScatterPlot) String() string { const ( - pointRadius = 2 - ) - - Width := sp.GetWidth() - Height := sp.GetHeight() - - if len(sp.Points) == 0 { - return "\nscatterplot fails: no data provided" - } - - // calcul min/max - minX, minY := sp.Points[0].X, sp.Points[0].Y - maxX, maxY := sp.Points[0].X, sp.Points[0].Y - - for _, p := range sp.Points { - if p.X > maxX { - maxX = p.X - } - if p.X < minX { - minX = p.X - } - if p.Y > maxY { - maxY = p.Y - } - if p.Y < minY { - minY = p.Y - } - } - - svgOut := "" - svgOut += RenderAxes(sp, Width, Height, maxX, maxY, minX, minY) - - // Draw Points and labels - for _, p := range sp.Points { - nx := 40 + (p.X-minX)/(maxX-minX)*float64(Width-60) - ny := float64(Height-40) - (p.Y-minY)/(maxY-minY)*float64(Height-60) - svgOut += ufmt.Sprintf(``, int(nx), int(ny), pointRadius, p.Color) - if p.Label != "" { - svgOut += ufmt.Sprintf( - `%s`, - int(nx)+5, int(ny)+12, p.Label, - ) - } - } - - // Flags : - if sp.FlagRe == true { - svgOut += RenderReFlag(sp.Points, minX, maxX, minY, maxY, Width, Height) - } - - // Draw Title + pointRadius = 2 + ) + + Width := sp.GetWidth() + Height := sp.GetHeight() + + if len(sp.Points) == 0 { + return "\nscatterplot fails: no data provided" + } + + // calcul min/max + minX, minY := sp.Points[0].X, sp.Points[0].Y + maxX, maxY := sp.Points[0].X, sp.Points[0].Y + + for _, p := range sp.Points { + if p.X > maxX { + maxX = p.X + } + if p.X < minX { + minX = p.X + } + if p.Y > maxY { + maxY = p.Y + } + if p.Y < minY { + minY = p.Y + } + } + + svgOut := "" + svgOut += RenderAxes(sp, Width, Height, maxX, maxY, minX, minY) + + // Draw Points and labels + for _, p := range sp.Points { + nx := 40 + (p.X-minX)/(maxX-minX)*float64(Width-60) + ny := float64(Height-40) - (p.Y-minY)/(maxY-minY)*float64(Height-60) + svgOut += ufmt.Sprintf(``, int(nx), int(ny), pointRadius, p.Color) + if p.Label != "" { + svgOut += ufmt.Sprintf( + `%s`, + int(nx)+5, int(ny)+12, p.Label, + ) + } + } + + // Flags : + if sp.FlagRe == true { + svgOut += RenderReFlag(sp.Points, minX, maxX, minY, maxY, Width, Height) + } + + // Draw Title if sp.Title != "" { - svgOut += ufmt.Sprintf(`%s`, - Width/2, 20, sp.Title, + svgOut += ufmt.Sprintf(`%s`, + Width/2, 20, sp.Title, ) } - return svgOut + return svgOut } From b0c0115e4c561a89be1dbbd39afe994203fc41af Mon Sep 17 00:00:00 2001 From: Pierre Baud Date: Wed, 8 Oct 2025 14:03:31 +0200 Subject: [PATCH 11/17] Fix: CI check scatterplot_test.gno --- packages/p/pierre115/gnov/scatterplot_test.gno | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/p/pierre115/gnov/scatterplot_test.gno b/packages/p/pierre115/gnov/scatterplot_test.gno index 18dff2f..33a4a0e 100644 --- a/packages/p/pierre115/gnov/scatterplot_test.gno +++ b/packages/p/pierre115/gnov/scatterplot_test.gno @@ -44,9 +44,9 @@ func TestScatterPlotReFlag(t *testing.T) { {X: 1, Y: 2}, {X: 2, Y: 4}, }, - Title: "With Regression", - XAxis: "X", - YAxis: "Y", + Title: "With Regression", + XAxis: "X", + YAxis: "Y", FlagRe: true, } From d0a6c07704fb95e1204160d43e8825a4b5c46834 Mon Sep 17 00:00:00 2001 From: Pierre Baud Date: Mon, 20 Oct 2025 11:10:50 +0200 Subject: [PATCH 12/17] Add: godoc for niceStep function --- packages/p/pierre115/gnov/scatterplot.gno | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/p/pierre115/gnov/scatterplot.gno b/packages/p/pierre115/gnov/scatterplot.gno index a53c9d5..9ce4d82 100644 --- a/packages/p/pierre115/gnov/scatterplot.gno +++ b/packages/p/pierre115/gnov/scatterplot.gno @@ -1,7 +1,7 @@ -// Package gnov (Gno Visual) provides functionality to render a scatter plot as an SVG image. +// Package scatterplot provides functionality to render a scatter plot as an SVG image. // It takes a list of points (x,y) and draws them as circles on a 2D . // You can also apply Flags. -package gnov +package scatterplot import ( "math" @@ -39,6 +39,7 @@ func (sp ScatterPlot) GetHeight() int { return sp.Height } +// Calculates a visually pleasing axis step size based on the data range and maximum number of ticks. func niceStep(sp ScatterPlot, rangeVal float64) float64 { var niceBase float64 maxTicks := sp.GetMaxTicks() From d69d20baa8f4f770f7946ca6085ab4fad3babfa7 Mon Sep 17 00:00:00 2001 From: Pierre Baud Date: Mon, 20 Oct 2025 11:15:41 +0200 Subject: [PATCH 13/17] Fix: Godoc format corrected --- packages/p/pierre115/gnov/scatterplot.gno | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/p/pierre115/gnov/scatterplot.gno b/packages/p/pierre115/gnov/scatterplot.gno index 9ce4d82..99ccb13 100644 --- a/packages/p/pierre115/gnov/scatterplot.gno +++ b/packages/p/pierre115/gnov/scatterplot.gno @@ -39,7 +39,7 @@ func (sp ScatterPlot) GetHeight() int { return sp.Height } -// Calculates a visually pleasing axis step size based on the data range and maximum number of ticks. +// NiceStep calculates a visually pleasing axis step size based on the data range and maximum number of ticks. func niceStep(sp ScatterPlot, rangeVal float64) float64 { var niceBase float64 maxTicks := sp.GetMaxTicks() @@ -61,7 +61,7 @@ func niceStep(sp ScatterPlot, rangeVal float64) float64 { return niceBase * math.Pow(10, exponent) } -// Use the ideal steps for X and Y axis +// StepXY use the ideal steps calculated by nicestep function for both axis func StepXY(sp ScatterPlot, maxX, maxY, minX, minY float64) (float64, float64, float64, float64, float64, float64) { rangeX := maxX - minX From 9efd5ee46238154f332dfce1f661484c949ede7d Mon Sep 17 00:00:00 2001 From: Pierre Baud Date: Mon, 20 Oct 2025 14:35:04 +0200 Subject: [PATCH 14/17] Name change and Godoc for RenderAxes --- packages/p/pierre115/gnov/README.md | 4 ++-- packages/p/pierre115/gnov/gnomod.toml | 2 +- packages/p/pierre115/gnov/linearyflag.gno | 2 +- packages/p/pierre115/gnov/scatterplot.gno | 4 ++-- packages/p/pierre115/gnov/scatterplot_test.gno | 2 +- packages/p/pierre115/gnov/type.gno | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/p/pierre115/gnov/README.md b/packages/p/pierre115/gnov/README.md index aa78677..93bde77 100644 --- a/packages/p/pierre115/gnov/README.md +++ b/packages/p/pierre115/gnov/README.md @@ -1,6 +1,6 @@ -# GNOV Scatterplot +# Scatterplot -The `gnov` package allows you to render a scatter plot as an SVG image. It takes a list of `(x, y)` points and draws them as circles on a 2D canvas. You can also apply optional flags to display regression lines or curves. +The `scatterplot` package allows you to render a scatter plot as an SVG image. It takes a list of `(x, y)` points and draws them as circles on a 2D canvas. You can also apply optional flags to display regression lines or curves. ## API references diff --git a/packages/p/pierre115/gnov/gnomod.toml b/packages/p/pierre115/gnov/gnomod.toml index c43929e..ef45a70 100644 --- a/packages/p/pierre115/gnov/gnomod.toml +++ b/packages/p/pierre115/gnov/gnomod.toml @@ -1,2 +1,2 @@ -module = "gno.land/p/pierre115/gnov" +module = "gno.land/p/pierre115/scatterplot" gno = "0.9" diff --git a/packages/p/pierre115/gnov/linearyflag.gno b/packages/p/pierre115/gnov/linearyflag.gno index 3c67843..7835d8d 100644 --- a/packages/p/pierre115/gnov/linearyflag.gno +++ b/packages/p/pierre115/gnov/linearyflag.gno @@ -1,4 +1,4 @@ -package gnov +package scatterplot import ( "math" diff --git a/packages/p/pierre115/gnov/scatterplot.gno b/packages/p/pierre115/gnov/scatterplot.gno index 99ccb13..68faca1 100644 --- a/packages/p/pierre115/gnov/scatterplot.gno +++ b/packages/p/pierre115/gnov/scatterplot.gno @@ -76,7 +76,7 @@ func StepXY(sp ScatterPlot, maxX, maxY, minX, minY float64) (float64, float64, f return stepX, startX, endX, stepY, startY, endY } -// Draw axes and axes titles, return SVG strings +// RenderAxes renders the chart axes and their corresponding titles, returning the result as SVG-formatted strings for display. func RenderAxes(sp ScatterPlot, Width, Height int, maxX, maxY, minX, minY float64) string { svgOut := "" @@ -102,7 +102,7 @@ func RenderAxes(sp ScatterPlot, Width, Height int, maxX, maxY, minX, minY float6 return float64(Height-40) - (val-minY)/(maxY-minY)*float64(Height-60) } - // N icesteps calcul for graduation + // Nicesteps calcul for graduation stepX, startX, endX, stepY, startY, endY := StepXY(sp, maxX, maxY, minX, minY) // Graduation X diff --git a/packages/p/pierre115/gnov/scatterplot_test.gno b/packages/p/pierre115/gnov/scatterplot_test.gno index 33a4a0e..ddff8d0 100644 --- a/packages/p/pierre115/gnov/scatterplot_test.gno +++ b/packages/p/pierre115/gnov/scatterplot_test.gno @@ -1,4 +1,4 @@ -package gnov +package scatterplot import ( "strings" diff --git a/packages/p/pierre115/gnov/type.gno b/packages/p/pierre115/gnov/type.gno index 8f75450..2501e09 100644 --- a/packages/p/pierre115/gnov/type.gno +++ b/packages/p/pierre115/gnov/type.gno @@ -1,4 +1,4 @@ -package gnov +package scatterplot // Points structure type Point struct { From 8efe040a5dba12b2da2b128f301eb555822f6389 Mon Sep 17 00:00:00 2001 From: Pierre Baud Date: Mon, 20 Oct 2025 16:58:27 +0200 Subject: [PATCH 15/17] Fix: Name and README.md --- packages/p/pierre115/gnov/README.md | 52 ----------- packages/p/pierre115/gnovisual/README.md | 87 +++++++++++++++++++ .../pierre115/{gnov => gnovisual}/gnomod.toml | 0 .../{gnov => gnovisual}/linearyflag.gno | 0 .../{gnov => gnovisual}/scatterplot.gno | 0 .../{gnov => gnovisual}/scatterplot_test.gno | 0 .../p/pierre115/{gnov => gnovisual}/type.gno | 0 7 files changed, 87 insertions(+), 52 deletions(-) delete mode 100644 packages/p/pierre115/gnov/README.md create mode 100644 packages/p/pierre115/gnovisual/README.md rename packages/p/pierre115/{gnov => gnovisual}/gnomod.toml (100%) rename packages/p/pierre115/{gnov => gnovisual}/linearyflag.gno (100%) rename packages/p/pierre115/{gnov => gnovisual}/scatterplot.gno (100%) rename packages/p/pierre115/{gnov => gnovisual}/scatterplot_test.gno (100%) rename packages/p/pierre115/{gnov => gnovisual}/type.gno (100%) diff --git a/packages/p/pierre115/gnov/README.md b/packages/p/pierre115/gnov/README.md deleted file mode 100644 index 93bde77..0000000 --- a/packages/p/pierre115/gnov/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# Scatterplot - -The `scatterplot` package allows you to render a scatter plot as an SVG image. It takes a list of `(x, y)` points and draws them as circles on a 2D canvas. You can also apply optional flags to display regression lines or curves. - -## API references - -```go -Testscatter(POINTS, "TITLE", "X_AXIS_TITLE", "Y_AXIS_TITLE", "FLAG") -``` - -`POINTS` strcuture is set with the following arguments : - ```go - type Point struct { - X, Y float64 //Coordinate of the point - Color string // Color of the point - Label string // Associate a label to the point - } -``` -`TITLE`, `X_AXIS_TITLE`, `Y_AXIS_TITLE` are strings. - -`FlagRe` is a `Boolean` value: `true` to enable and false by default. - -`Maxticks` is an `int` n used to divide the axis into n graduation marks. - -`Width` and `height` are `int` to personalize the size of the scatterplot. - -## Usage - -`Usecase` - -```go -ScatterPlot{ - Points: []Point{ - {X: 100, Y: 00, Label: "A"}, - {X: 101, Y: 20, Label: "B"}, - {X: 102, Y: 40, Label: "C"}, - {X: 103, Y: 60, Label: "D"}, - }, - Title: "Sales Growth", - XAxis: "Years", - YAxis: "Sales", - FlagRe: true, - maxticks: 20, - width: 800, - height: 800, - } -``` - -## Flags - -`Lineary Regression flag` that display the `regression line` of the scatterplot can be actived by the bool `true`. -Each flag shows the `equation` of the regression in the top left of the Scatterplot. diff --git a/packages/p/pierre115/gnovisual/README.md b/packages/p/pierre115/gnovisual/README.md new file mode 100644 index 0000000..013c00c --- /dev/null +++ b/packages/p/pierre115/gnovisual/README.md @@ -0,0 +1,87 @@ +# scatterplot + +Package `scatterplot` renders scatter plots as SVG images. It takes a list of (x, y) points and draws them as circles on a 2D canvas, with optional support for displaying a linear regression line. + +## Usage + +```go +import "gno.land/p/demo/scatterplot" + +func main() { + plot := scatterplot.ScatterPlot{ + Points: []scatterplot.Point{ + {X: 100, Y: 0, Label: "A"}, + {X: 101, Y: 20, Label: "B"}, + {X: 102, Y: 40, Label: "C"}, + {X: 103, Y: 60, Label: "D"}, + }, + Title: "Sales Growth", + XAxis: "Years", + YAxis: "Sales", + FlagRe: true, + Maxticks: 20, + Width: 800, + Height: 800, + } + + svg := plot.Render() + // Use the SVG output +} +``` + +## API + +### Point + +```go +type Point struct { + X, Y float64 // Coordinates of the point + Color string // Color of the point + Label string // Label associated with the point +} +``` + +### ScatterPlot + +```go +type ScatterPlot struct { + Points []Point // Data points to plot + Title string // Plot title + XAxis string // X-axis label + YAxis string // Y-axis label + FlagRe bool // Enable linear regression line (default: false) + Maxticks int // Number of tick marks on axes + Width int // Plot width in pixels + Height int // Plot height in pixels +} +``` + +## Features + +### Linear Regression + +Set `FlagRe` to `true` to display a linear regression line fitted to your data points. The regression equation will be displayed in the top-left corner of the plot. + +## Example + +Here's a complete example showing sales growth over time: + +```go +plot := scatterplot.ScatterPlot{ + Points: []scatterplot.Point{ + {X: 2020, Y: 150, Label: "Q1"}, + {X: 2021, Y: 230, Label: "Q2"}, + {X: 2022, Y: 310, Label: "Q3"}, + {X: 2023, Y: 405, Label: "Q4"}, + }, + Title: "Quarterly Sales Performance", + XAxis: "Year", + YAxis: "Revenue (k$)", + FlagRe: true, + Maxticks: 10, + Width: 600, + Height: 400, +} +``` + +This will generate an SVG scatter plot with a regression line showing the trend in sales growth. \ No newline at end of file diff --git a/packages/p/pierre115/gnov/gnomod.toml b/packages/p/pierre115/gnovisual/gnomod.toml similarity index 100% rename from packages/p/pierre115/gnov/gnomod.toml rename to packages/p/pierre115/gnovisual/gnomod.toml diff --git a/packages/p/pierre115/gnov/linearyflag.gno b/packages/p/pierre115/gnovisual/linearyflag.gno similarity index 100% rename from packages/p/pierre115/gnov/linearyflag.gno rename to packages/p/pierre115/gnovisual/linearyflag.gno diff --git a/packages/p/pierre115/gnov/scatterplot.gno b/packages/p/pierre115/gnovisual/scatterplot.gno similarity index 100% rename from packages/p/pierre115/gnov/scatterplot.gno rename to packages/p/pierre115/gnovisual/scatterplot.gno diff --git a/packages/p/pierre115/gnov/scatterplot_test.gno b/packages/p/pierre115/gnovisual/scatterplot_test.gno similarity index 100% rename from packages/p/pierre115/gnov/scatterplot_test.gno rename to packages/p/pierre115/gnovisual/scatterplot_test.gno diff --git a/packages/p/pierre115/gnov/type.gno b/packages/p/pierre115/gnovisual/type.gno similarity index 100% rename from packages/p/pierre115/gnov/type.gno rename to packages/p/pierre115/gnovisual/type.gno From ce78e786c70ffd610efa29d93c01e1454db7394e Mon Sep 17 00:00:00 2001 From: Pierre Baud Date: Mon, 20 Oct 2025 17:07:58 +0200 Subject: [PATCH 16/17] Fix: Files name --- packages/p/pierre115/gnovisual/README.md | 4 ++-- packages/p/pierre115/gnovisual/gnomod.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/p/pierre115/gnovisual/README.md b/packages/p/pierre115/gnovisual/README.md index 013c00c..76b11d5 100644 --- a/packages/p/pierre115/gnovisual/README.md +++ b/packages/p/pierre115/gnovisual/README.md @@ -1,11 +1,11 @@ -# scatterplot +# GnoVisual Package `scatterplot` renders scatter plots as SVG images. It takes a list of (x, y) points and draws them as circles on a 2D canvas, with optional support for displaying a linear regression line. ## Usage ```go -import "gno.land/p/demo/scatterplot" +import "gno.land/p/pierre115/gnovisual/scatterplot" func main() { plot := scatterplot.ScatterPlot{ diff --git a/packages/p/pierre115/gnovisual/gnomod.toml b/packages/p/pierre115/gnovisual/gnomod.toml index ef45a70..0d571b4 100644 --- a/packages/p/pierre115/gnovisual/gnomod.toml +++ b/packages/p/pierre115/gnovisual/gnomod.toml @@ -1,2 +1,2 @@ -module = "gno.land/p/pierre115/scatterplot" +module = "gno.land/p/pierre115/gnovisual/scatterplot" gno = "0.9" From 052e8b18a276569df1d8712c170fc0d0a7648e5c Mon Sep 17 00:00:00 2001 From: Pierre Baud Date: Mon, 10 Nov 2025 17:32:06 +0100 Subject: [PATCH 17/17] Fix: Name of the package --- packages/p/pierre115/gnovisual/README.md | 4 ++-- packages/p/pierre115/gnovisual/gnomod.toml | 2 +- packages/p/pierre115/gnovisual/linearyflag.gno | 2 +- packages/p/pierre115/gnovisual/scatterplot.gno | 4 ++-- packages/p/pierre115/gnovisual/scatterplot_test.gno | 2 +- packages/p/pierre115/gnovisual/type.gno | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/p/pierre115/gnovisual/README.md b/packages/p/pierre115/gnovisual/README.md index 76b11d5..7bb746d 100644 --- a/packages/p/pierre115/gnovisual/README.md +++ b/packages/p/pierre115/gnovisual/README.md @@ -1,11 +1,11 @@ # GnoVisual -Package `scatterplot` renders scatter plots as SVG images. It takes a list of (x, y) points and draws them as circles on a 2D canvas, with optional support for displaying a linear regression line. +Package `gnovisual` renders scatter plots as SVG images. It takes a list of (x, y) points and draws them as circles on a 2D canvas, with optional support for displaying a linear regression line. ## Usage ```go -import "gno.land/p/pierre115/gnovisual/scatterplot" +import "gno.land/p/pierre115/gnovisual" func main() { plot := scatterplot.ScatterPlot{ diff --git a/packages/p/pierre115/gnovisual/gnomod.toml b/packages/p/pierre115/gnovisual/gnomod.toml index 0d571b4..d744224 100644 --- a/packages/p/pierre115/gnovisual/gnomod.toml +++ b/packages/p/pierre115/gnovisual/gnomod.toml @@ -1,2 +1,2 @@ -module = "gno.land/p/pierre115/gnovisual/scatterplot" +module = "gno.land/p/pierre115/gnovisual" gno = "0.9" diff --git a/packages/p/pierre115/gnovisual/linearyflag.gno b/packages/p/pierre115/gnovisual/linearyflag.gno index 7835d8d..d492f6e 100644 --- a/packages/p/pierre115/gnovisual/linearyflag.gno +++ b/packages/p/pierre115/gnovisual/linearyflag.gno @@ -1,4 +1,4 @@ -package scatterplot +package gnovisual import ( "math" diff --git a/packages/p/pierre115/gnovisual/scatterplot.gno b/packages/p/pierre115/gnovisual/scatterplot.gno index 68faca1..7390e92 100644 --- a/packages/p/pierre115/gnovisual/scatterplot.gno +++ b/packages/p/pierre115/gnovisual/scatterplot.gno @@ -1,7 +1,7 @@ -// Package scatterplot provides functionality to render a scatter plot as an SVG image. +// Package gnovisual provides functionality to render a scatter plot as an SVG image. // It takes a list of points (x,y) and draws them as circles on a 2D . // You can also apply Flags. -package scatterplot +package gnovisual import ( "math" diff --git a/packages/p/pierre115/gnovisual/scatterplot_test.gno b/packages/p/pierre115/gnovisual/scatterplot_test.gno index ddff8d0..5a805ff 100644 --- a/packages/p/pierre115/gnovisual/scatterplot_test.gno +++ b/packages/p/pierre115/gnovisual/scatterplot_test.gno @@ -1,4 +1,4 @@ -package scatterplot +package gnovisual import ( "strings" diff --git a/packages/p/pierre115/gnovisual/type.gno b/packages/p/pierre115/gnovisual/type.gno index 2501e09..98ad90b 100644 --- a/packages/p/pierre115/gnovisual/type.gno +++ b/packages/p/pierre115/gnovisual/type.gno @@ -1,4 +1,4 @@ -package scatterplot +package gnovisual // Points structure type Point struct {