Created
September 29, 2023 14:34
-
-
Save peterhellberg/87404bed18555b6a01b672cc046a97cd to your computer and use it in GitHub Desktop.
Using go-chart to generate a graph based on the PRO Prisundersökning data converted using https://gist.github.com/peterhellberg/ceed862a83c29a80b5bbd4631c24fb27
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"encoding/json" | |
"fmt" | |
"os" | |
"strings" | |
_ "embed" | |
"github.com/wcharczuk/go-chart/v2" | |
"github.com/wcharczuk/go-chart/v2/drawing" | |
) | |
var ( | |
orange = drawing.ColorFromHex("FF6600") | |
blue = drawing.ColorFromHex("0099FF") | |
gray = drawing.ColorFromHex("363636") | |
) | |
func main() { | |
var butiker []butik | |
if err := json.Unmarshal(data, &butiker); err != nil { | |
panic(err) | |
} | |
totals := make([]float64, len(butiker)) | |
var combinedTotal float64 | |
for i, butik := range butiker { | |
totals[i] = butik.Total | |
combinedTotal += butik.Total | |
} | |
averageTotal := combinedTotal / float64(len(totals)) | |
zero := chart.GridLine{ | |
Value: averageTotal, | |
Style: chart.Style{ | |
StrokeWidth: 15, | |
StrokeColor: orange.WithAlpha(30), | |
StrokeDashArray: []float64{20, 20}, | |
}, | |
} | |
S, N := 0, 36 | |
for n := S; n < N; n++ { | |
var varorNamn string | |
priser := make([]float64, len(butiker)) | |
for i, butik := range butiker { | |
priser[i] = butik.Varor[n].Pris | |
varorNamn = butik.Varor[n].Namn | |
} | |
mainSeries := chart.ContinuousSeries{ | |
YAxis: chart.YAxisSecondary, | |
Style: chart.Style{ | |
StrokeColor: orange.WithAlpha(80), | |
StrokeWidth: 32, | |
DotColor: orange, | |
DotWidth: 6, | |
}, | |
XValues: chart.LinearRange(1, float64(len(totals))), | |
YValues: chart.ValueSequence(totals...).Values(), | |
} | |
priserSeries := chart.ContinuousSeries{ | |
Style: chart.Style{ | |
StrokeColor: blue.WithAlpha(100), | |
StrokeWidth: 3, | |
DotColor: blue.WithAlpha(130), | |
DotWidth: 9, | |
}, | |
XValues: chart.LinearRange(1, float64(len(totals))), | |
YValues: chart.ValueSequence(priser...).Values(), | |
} | |
graph := chart.Chart{ | |
Title: "Hela matkassens pris jämfört med " + varorNamn, | |
TitleStyle: chart.Style{ | |
FontSize: 20, | |
FontColor: gray, | |
}, | |
DPI: 216, | |
Width: 3072, | |
Height: 1536, | |
XAxis: chart.XAxis{ | |
Style: chart.Style{ | |
Hidden: true, | |
}, | |
}, | |
YAxisSecondary: chart.YAxis{ | |
Zero: zero, | |
Name: "Hela matkassen", | |
NameStyle: chart.Style{ | |
FontSize: -16, | |
FontColor: orange, | |
}, | |
Style: chart.Style{ | |
FontSize: 14, | |
StrokeWidth: 4, | |
StrokeColor: orange, | |
FontColor: orange, | |
}, | |
ValueFormatter: krFormatter, | |
}, | |
YAxis: chart.YAxis{ | |
Name: varorNamn, | |
NameStyle: chart.Style{ | |
FontSize: 16, | |
FontColor: blue, | |
}, | |
Style: chart.Style{ | |
FontSize: 14, | |
StrokeWidth: 4, | |
StrokeColor: blue, | |
FontColor: blue, | |
}, | |
ValueFormatter: krFormatter, | |
}, | |
Background: chart.Style{ | |
Padding: chart.NewBox(80, 60, 20, 20), | |
}, | |
Series: []chart.Series{ | |
priserSeries, | |
mainSeries, | |
}, | |
} | |
fn := strings.ToLower( | |
fmt.Sprintf("pro-prisundersokning-total-vs-%s.png", | |
strings.ReplaceAll(varorNamn, " ", "-"), | |
), | |
) | |
f, err := os.Create(fn) | |
if err != nil { | |
panic(err) | |
} | |
defer f.Close() | |
if err := graph.Render(chart.PNG, f); err != nil { | |
panic(err) | |
} | |
} | |
} | |
func krFormatter(v interface{}) string { | |
return chart.FloatValueFormatterWithFormat(v, " %.0f kr") | |
} | |
//go:embed data.json | |
var data []byte | |
type butik struct { | |
Butiksnamn string `json:"butiksnamn"` | |
Distrikt string `json:"distrikt"` | |
Typ string `json:"typ"` | |
AntalVaror int `json:"antal_varor"` | |
Total float64 `json:"total"` | |
Varor []vara `json:"varor"` | |
} | |
type vara struct { | |
Namn string `json:"namn"` | |
Pris float64 `json:"pris"` | |
} |
Author
peterhellberg
commented
Sep 29, 2023
HTML table for 2022 and 2023 images
package main
import (
"fmt"
"log"
"net/http"
"os"
"slices"
"text/template"
)
func main() {
images22 := yearImages("./2022/")
images23 := yearImages("./2023/")
var products []Product
for _, name := range imageUnion(images22, images23) {
products = append(products, Product{
Name: name,
In2022: in(name, images22),
In2023: in(name, images23),
})
}
fmt.Println(len(products), products)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
t.Execute(w, products)
})
images := http.FileServer(http.Dir("."))
http.Handle("/images/", http.StripPrefix("/images/", images))
log.Fatal(http.ListenAndServe(":9876", nil))
}
func in(name string, images []string) bool {
for _, n := range images {
if name == n {
return true
}
}
return false
}
type Product struct {
Name string
In2022 bool
In2023 bool
}
func imageUnion(names ...[]string) []string {
union := slices.Concat(names...)
slices.Sort(union)
union = slices.Compact(union)
return union
}
func yearImages(dir string) []string {
var images []string
files, err := os.ReadDir(dir)
if err != nil {
return nil
}
for _, f := range files {
images = append(images, f.Name())
}
return images
}
var t = template.Must(template.New("").Parse(`<!doctype html>
<html lang="en" data-theme="light">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="apple-mobile-web-app-capable" content="yes">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@next/css/pico.fluid.classless.min.css">
<title>2022 vs 2023</title>
</head>
<body>
<main>
<article>
<header>
<h1><a href="/" style="text-decoration: none; color: #2d3138">PRO Prisundersökning 📝</a></h1>
</header>
<table>
<tr><th><h3>2022</h3></th><th><h3>2023</h3></th></tr>
{{ range . -}}
<tr>
<td>
{{if .In2022 }}<img src="/images/2022/{{.Name}}" />{{else}}{{end}}
</td>
<td>
{{if .In2023 }}<img src="/images/2023/{{.Name}}" />{{else}}{{end}}
</td>
</tr>
{{ end }}
</table>
</article>
</main>
</body>
</html>`))
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment