Skip to content

Instantly share code, notes, and snippets.

@peterhellberg
Created September 29, 2023 14:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save peterhellberg/87404bed18555b6a01b672cc046a97cd to your computer and use it in GitHub Desktop.
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
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"`
}
@peterhellberg
Copy link
Author

Ansjovis

@peterhellberg
Copy link
Author

Changes from 2022 to 2023 for "Långkornigt ris"

@peterhellberg
Copy link
Author

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