Skip to content

Commit 06e4a42

Browse files
committed
feature: heatmap
1 parent 4207ca1 commit 06e4a42

File tree

11 files changed

+250
-5
lines changed

11 files changed

+250
-5
lines changed

cmd/heatmap/main.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package main
2+
3+
import (
4+
"github.com/johnfercher/maroto/v2"
5+
"github.com/johnfercher/maroto/v2/pkg/components/chart"
6+
"github.com/johnfercher/maroto/v2/pkg/components/text"
7+
"github.com/johnfercher/maroto/v2/pkg/config"
8+
"log"
9+
"math/rand"
10+
)
11+
12+
func main() {
13+
cfg := config.NewBuilder().
14+
WithPageNumber().
15+
WithDebug(true).
16+
Build()
17+
18+
mrt := maroto.New(cfg)
19+
m := maroto.NewMetricsDecorator(mrt)
20+
21+
xMax := 10
22+
yMax := 10
23+
max := 10
24+
heat := buildHeat(xMax, yMax, max)
25+
26+
m.AddRows(text.NewRow(10, "HeatMap"))
27+
m.AddRows(chart.NewHeatMapRow(100, "map", heat, max))
28+
29+
document, err := m.Generate()
30+
if err != nil {
31+
log.Fatal(err.Error())
32+
}
33+
34+
err = document.Save("docs/assets/pdf/heatmap.pdf")
35+
if err != nil {
36+
log.Fatal(err.Error())
37+
}
38+
39+
err = document.GetReport().Save("docs/assets/text/heatmap.txt")
40+
if err != nil {
41+
log.Fatal(err.Error())
42+
}
43+
}
44+
45+
func buildHeat(x, y, max int) [][]int {
46+
var heat [][]int
47+
for i := 0; i < x; i++ {
48+
var line []int
49+
for j := 0; j < y; j++ {
50+
line = append(line, rand.Intn(max))
51+
}
52+
heat = append(heat, line)
53+
}
54+
return heat
55+
}

docs/assets/pdf/heatmap.pdf

6.32 KB
Binary file not shown.

docs/assets/pdf/v2.pdf

14 KB
Binary file not shown.

docs/assets/text/heatmap.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
generate -> avg: 646.00μs, executions: [646.00μs]
2+
add_rows -> avg: 5854.50ns, executions: [11.58μs, 0.12μs]
3+
file_size -> 6.47Kb

docs/assets/text/v2.txt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
generate -> avg: 18.06ms, executions: [18.06ms]
2-
header -> avg: 356.00ns, executions: [356.00ns]
3-
footer -> avg: 71.00ns, executions: [71.00ns]
4-
add_row -> avg: 112.55ns, executions: [0.11μs, 0.15μs, 0.05μs, 0.08μs, 0.02μs, 0.02μs, 0.53μs, 0.06μs, 0.02μs, 0.07μs, 0.02μs, 0.02μs, 0.02μs, 0.06μs, 0.02μs, 0.02μs, 0.03μs, 1.41μs, 0.06μs, 0.02μs, 0.06μs, 0.02μs, 0.02μs, 0.01μs, 0.07μs, 0.02μs, 0.02μs, 0.03μs, 0.29μs, 1.74μs, 0.02μs, 0.07μs, 0.02μs, 0.02μs, 0.02μs, 0.06μs, 0.02μs, 0.02μs, 0.02μs, 0.24μs, 0.05μs, 0.02μs, 0.07μs, 0.02μs, 0.01μs, 0.02μs, 0.06μs, 0.03μs, 0.01μs, 0.01μs, 0.24μs, 0.05μs, 0.01μs, 0.05μs, 0.02μs]
5-
file_size -> 267.93Kb
1+
generate -> avg: 26.67ms, executions: [26.67ms]
2+
header -> avg: 21.00μs, executions: [21.00μs]
3+
footer -> avg: 333.00ns, executions: [333.00ns]
4+
add_rows -> avg: 157.56ns, executions: [0.29μs, 0.50μs, 0.29μs, 0.50μs, 0.08μs, 0.12μs, 1.21μs, 0.17μs, 0.04μs, 0.08μs, 0.04μs, 0.04μs, 0.04μs, 0.21μs, 0.04μs, 0.04μs, 0.04μs, 0.67μs, 0.17μs, 0.04μs, 0.12μs, 0.04μs, 0.04μs, 0.04μs, 0.17μs, 0.04μs, 0.04μs, 0.04μs, 0.50μs, 0.12μs, 0.04μs, 0.12μs, 0.04μs, 0.21μs, 0.04μs, 0.12μs, 0.04μs, 0.04μs, 0.08μs, 0.50μs, 0.17μs, 0.04μs, 0.08μs, 0.04μs, 0.04μs, 0.04μs, 0.33μs, 0.04μs, 0.04μs, 0.04μs, 0.46μs, 0.08μs, 0.04μs, 0.12μs, 0.04μs]
5+
file_size -> 282.23Kb

internal/providers/gofpdf/builder.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type Dependencies struct {
2020
Code core.Code
2121
Image core.Image
2222
Line core.Line
23+
HeatMap core.HeatMap
2324
Cache cache.Cache
2425
CellWriter cellwriter.CellWriter
2526
Cfg *entity.Config
@@ -68,6 +69,7 @@ func (b *builder) Build(cfg *entity.Config, cache cache.Cache) *Dependencies {
6869
text := NewText(fpdf, math, font)
6970
image := NewImage(fpdf, math)
7071
line := NewLine(fpdf)
72+
heatMap := NewHeatMap(fpdf, math)
7173
cellWriter := cellwriter.NewBuilder().
7274
Build(fpdf)
7375

@@ -78,6 +80,7 @@ func (b *builder) Build(cfg *entity.Config, cache cache.Cache) *Dependencies {
7880
Code: code,
7981
Image: image,
8082
Line: line,
83+
HeatMap: heatMap,
8184
CellWriter: cellWriter,
8285
Cfg: cfg,
8386
Cache: cache,
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package gofpdf
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"github.com/johnfercher/maroto/v2/pkg/props"
7+
"math"
8+
9+
"github.com/johnfercher/maroto/v2/internal/providers/gofpdf/gofpdfwrapper"
10+
"github.com/johnfercher/maroto/v2/pkg/core"
11+
"github.com/johnfercher/maroto/v2/pkg/core/entity"
12+
)
13+
14+
var ErrOutOfRange = errors.New("out of range")
15+
16+
type heatMap struct {
17+
pdf gofpdfwrapper.Fpdf
18+
math core.Math
19+
defaultFillColor *props.Color
20+
}
21+
22+
func NewHeatMap(pdf gofpdfwrapper.Fpdf, math core.Math) *heatMap {
23+
return &heatMap{
24+
pdf: pdf,
25+
math: math,
26+
defaultFillColor: &props.WhiteColor,
27+
}
28+
}
29+
30+
func (s heatMap) Add(heatMap [][]int, max int, cell *entity.Cell, margins *entity.Margins) {
31+
if heatMap == nil {
32+
return
33+
}
34+
35+
ySize := len(heatMap)
36+
if ySize == 0 {
37+
return
38+
}
39+
stepY := (cell.Height) / float64(ySize-1)
40+
41+
xSize := len(heatMap[0])
42+
if xSize == 0 {
43+
return
44+
}
45+
stepX := (cell.Width) / float64(xSize-1)
46+
47+
for i := 0; i < len(heatMap)-1; i++ {
48+
for j := 0; j < len(heatMap[i])-1; j++ {
49+
r, g, b := s.GetHeatColor(heatMap[i][j], max)
50+
51+
x := float64(i)*stepX + cell.X + margins.Left
52+
y := float64(j)*stepY + cell.Y + margins.Top
53+
width := stepX
54+
height := stepY
55+
56+
fmt.Println(x, y, width, height)
57+
58+
s.pdf.SetFillColor(r, g, b)
59+
s.pdf.Rect(x, y, width, height, "F")
60+
s.pdf.SetFillColor(s.defaultFillColor.Red, s.defaultFillColor.Green, s.defaultFillColor.Blue)
61+
}
62+
}
63+
}
64+
65+
func (s heatMap) GetHeatColor(i int, total int) (int, int, int) {
66+
hueMax := 160.0
67+
step := hueMax / float64(total)
68+
iStep := step * float64(i)
69+
70+
r, g, b, _ := HSVToRGB(iStep, 1.0, 1.0)
71+
return int(r), int(g), int(b)
72+
}
73+
74+
// HSVToRGB converts an HSV triple to an RGB triple.
75+
// Source: https://github.com/Crazy3lf/colorconv/blob/master/colorconv.go
76+
func HSVToRGB(h, s, v float64) (r, g, b uint8, err error) {
77+
if h < 0 || h >= 360 ||
78+
s < 0 || s > 1 ||
79+
v < 0 || v > 1 {
80+
return 0, 0, 0, ErrOutOfRange
81+
}
82+
// When 0 ≤ h < 360, 0 ≤ s ≤ 1 and 0 ≤ v ≤ 1:
83+
C := v * s
84+
X := C * (1 - math.Abs(math.Mod(h/60, 2)-1))
85+
m := v - C
86+
var Rnot, Gnot, Bnot float64
87+
switch {
88+
case 0 <= h && h < 60:
89+
Rnot, Gnot, Bnot = C, X, 0
90+
case 60 <= h && h < 120:
91+
Rnot, Gnot, Bnot = X, C, 0
92+
case 120 <= h && h < 180:
93+
Rnot, Gnot, Bnot = 0, C, X
94+
case 180 <= h && h < 240:
95+
Rnot, Gnot, Bnot = 0, X, C
96+
case 240 <= h && h < 300:
97+
Rnot, Gnot, Bnot = X, 0, C
98+
case 300 <= h && h < 360:
99+
Rnot, Gnot, Bnot = C, 0, X
100+
}
101+
r = uint8(math.Round((Rnot + m) * 255))
102+
g = uint8(math.Round((Gnot + m) * 255))
103+
b = uint8(math.Round((Bnot + m) * 255))
104+
return r, g, b, nil
105+
}

internal/providers/gofpdf/provider.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type provider struct {
2626
code core.Code
2727
image core.Image
2828
line core.Line
29+
heatMap core.HeatMap
2930
cache cache.Cache
3031
cellWriter cellwriter.CellWriter
3132
cfg *entity.Config
@@ -40,6 +41,7 @@ func New(dep *Dependencies) core.Provider {
4041
code: dep.Code,
4142
image: dep.Image,
4243
line: dep.Line,
44+
heatMap: dep.HeatMap,
4345
cellWriter: dep.CellWriter,
4446
cfg: dep.Cfg,
4547
cache: dep.Cache,
@@ -268,6 +270,10 @@ func (g *provider) SetCompression(compression bool) {
268270
g.fpdf.SetCompression(compression)
269271
}
270272

273+
func (g *provider) AddHeatMap(heatMap [][]int, max int, cell *entity.Cell) {
274+
g.heatMap.Add(heatMap, max, cell, g.cfg.Margins)
275+
}
276+
271277
func (g *provider) getBarcodeImageName(code string, prop *props.Barcode) string {
272278
if prop == nil {
273279
return code + string(barcode.Code128)

pkg/components/chart/heatmap.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package chart
2+
3+
import (
4+
"github.com/johnfercher/go-tree/node"
5+
"github.com/johnfercher/maroto/v2/pkg/components/col"
6+
"github.com/johnfercher/maroto/v2/pkg/components/row"
7+
"github.com/johnfercher/maroto/v2/pkg/core"
8+
"github.com/johnfercher/maroto/v2/pkg/core/entity"
9+
"github.com/johnfercher/maroto/v2/pkg/props"
10+
)
11+
12+
type HeatMap struct {
13+
name string
14+
heat [][]int
15+
max int
16+
prop props.Rect
17+
config *entity.Config
18+
}
19+
20+
func NewHeatMap(name string, heat [][]int, max int, ps ...props.Rect) core.Component {
21+
prop := props.Rect{}
22+
if len(ps) > 0 {
23+
prop = ps[0]
24+
}
25+
prop.MakeValid()
26+
27+
return &HeatMap{
28+
name: name,
29+
prop: prop,
30+
heat: heat,
31+
max: max,
32+
}
33+
}
34+
35+
func NewHeatMapCol(size int, name string, heat [][]int, max int, ps ...props.Rect) core.Col {
36+
heatMap := NewHeatMap(name, heat, max, ps...)
37+
return col.New(size).Add(heatMap)
38+
}
39+
40+
func NewHeatMapRow(height float64, name string, heat [][]int, max int, ps ...props.Rect) core.Row {
41+
heatMap := NewHeatMap(name, heat, max, ps...)
42+
c := col.New().Add(heatMap)
43+
return row.New(height).Add(c)
44+
}
45+
46+
func (b *HeatMap) Render(provider core.Provider, cell *entity.Cell) {
47+
provider.AddHeatMap(b.heat, b.max, cell)
48+
}
49+
50+
func (b *HeatMap) GetStructure() *node.Node[core.Structure] {
51+
str := core.Structure{
52+
Type: "heatmap",
53+
Value: b.name,
54+
Details: b.prop.ToMap(),
55+
}
56+
57+
return node.New(str)
58+
}
59+
60+
func (b *HeatMap) GetHeight(provider core.Provider, cell *entity.Cell) float64 {
61+
return float64(len(b.heat))
62+
}
63+
64+
func (b *HeatMap) SetConfig(config *entity.Config) {
65+
b.config = config
66+
}

pkg/core/components.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type Image interface {
2828
GetImageInfo(img *entity.Image, extension extension.Type) (*gofpdf.ImageInfoType, uuid.UUID)
2929
}
3030

31+
// Line is the abstraction which deals with lines in a PDF.
3132
type Line interface {
3233
Add(cell *entity.Cell, prop *props.Line)
3334
}
@@ -52,3 +53,8 @@ type Font interface {
5253
SetColor(color *props.Color)
5354
GetColor() *props.Color
5455
}
56+
57+
// HeatMap is the abstraction which deals with heapmap charts.
58+
type HeatMap interface {
59+
Add(heatMap [][]int, max int, cell *entity.Cell, margins *entity.Margins)
60+
}

0 commit comments

Comments
 (0)