Skip to content

Commit

Permalink
feature: heatmap
Browse files Browse the repository at this point in the history
  • Loading branch information
johnfercher committed Feb 16, 2025
1 parent 4207ca1 commit 4c25b0b
Show file tree
Hide file tree
Showing 39 changed files with 2,000 additions and 271 deletions.
72 changes: 72 additions & 0 deletions cmd/heatmap/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package main

import (
"github.com/johnfercher/maroto/v2"
"github.com/johnfercher/maroto/v2/pkg/components/chart"
"github.com/johnfercher/maroto/v2/pkg/components/text"
"github.com/johnfercher/maroto/v2/pkg/config"
"github.com/johnfercher/maroto/v2/pkg/props"
"log"
)

func main() {
cfg := config.NewBuilder().
WithPageNumber().
WithDebug(true).
Build()

mrt := maroto.New(cfg)
m := maroto.NewMetricsDecorator(mrt)

xMax := 620
yMax := 200
max := 620 + 200
heat := buildHeat(xMax, yMax, max)

m.AddRows(text.NewRow(10, "HeatMap"))
m.AddRows(chart.NewHeatMapRow(100, "map", heat, props.HeatMap{
TransparentValues: []int{0},
}))

/*m.AddRow(100,
chart.NewHeatMapCol(6, "map", heat, props.HeatMap{
TransparentValues: []int{0},
Chart: &props.Chart{
Axis: true,
},
}),
chart.NewHeatMapCol(6, "map", heat, props.HeatMap{
TransparentValues: []int{0},
Chart: &props.Chart{
Axis: true,
},
}),
)*/

document, err := m.Generate()
if err != nil {
log.Fatal(err.Error())
}

err = document.Save("docs/assets/pdf/heatmap.pdf")
if err != nil {
log.Fatal(err.Error())
}

err = document.GetReport().Save("docs/assets/text/heatmap.txt")
if err != nil {
log.Fatal(err.Error())
}
}

func buildHeat(x, y, max int) [][]int {
var heat [][]int
for i := 0; i < x; i++ {
var line []int
for j := 0; j < y; j++ {
line = append(line, i+j)
}
heat = append(heat, line)
}
return heat
}
3 changes: 2 additions & 1 deletion docs/assets/examples/textgrid/v2/main.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package main

import (
"github.com/johnfercher/maroto/v2/pkg/consts/fontstyle"
"log"

"github.com/johnfercher/maroto/v2/pkg/consts/fontstyle"

"github.com/johnfercher/maroto/v2/pkg/core"

"github.com/johnfercher/maroto/v2"
Expand Down
Binary file added docs/assets/pdf/heatmap.pdf
Binary file not shown.
Binary file modified docs/assets/pdf/v2.pdf
Binary file not shown.
3 changes: 3 additions & 0 deletions docs/assets/text/heatmap.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
generate -> avg: 134.04ms, executions: [134.04ms]
add_rows -> avg: 7334.00ns, executions: [14.46μs, 0.21μs]
file_size -> 7.25Mb
10 changes: 5 additions & 5 deletions docs/assets/text/v2.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
generate -> avg: 18.06ms, executions: [18.06ms]
header -> avg: 356.00ns, executions: [356.00ns]
footer -> avg: 71.00ns, executions: [71.00ns]
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]
file_size -> 267.93Kb
generate -> avg: 26.67ms, executions: [26.67ms]
header -> avg: 21.00μs, executions: [21.00μs]
footer -> avg: 333.00ns, executions: [333.00ns]
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]
file_size -> 282.23Kb
4 changes: 4 additions & 0 deletions internal/providers/gofpdf/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type Dependencies struct {
Code core.Code
Image core.Image
Line core.Line
HeatMap core.HeatMap
Cache cache.Cache
CellWriter cellwriter.CellWriter
Cfg *entity.Config
Expand Down Expand Up @@ -68,6 +69,8 @@ func (b *builder) Build(cfg *entity.Config, cache cache.Cache) *Dependencies {
text := NewText(fpdf, math, font)
image := NewImage(fpdf, math)
line := NewLine(fpdf)
chart := NewChart(fpdf, line, text)
heatMap := NewHeatMap(fpdf, chart)
cellWriter := cellwriter.NewBuilder().
Build(fpdf)

Expand All @@ -78,6 +81,7 @@ func (b *builder) Build(cfg *entity.Config, cache cache.Cache) *Dependencies {
Code: code,
Image: image,
Line: line,
HeatMap: heatMap,
CellWriter: cellWriter,
Cfg: cfg,
Cache: cache,
Expand Down
44 changes: 44 additions & 0 deletions internal/providers/gofpdf/chart.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package gofpdf

import (
"github.com/johnfercher/maroto/v2/internal/providers/gofpdf/gofpdfwrapper"
"github.com/johnfercher/maroto/v2/pkg/consts/linestyle"
"github.com/johnfercher/maroto/v2/pkg/consts/orientation"
"github.com/johnfercher/maroto/v2/pkg/core"
"github.com/johnfercher/maroto/v2/pkg/core/entity"
"github.com/johnfercher/maroto/v2/pkg/props"
)

type chart struct {
pdf gofpdfwrapper.Fpdf
line core.Line
text core.Text
}

func NewChart(pdf gofpdfwrapper.Fpdf, line core.Line, text core.Text) *chart {
return &chart{
pdf: pdf,
line: line,
text: text,
}
}

func (c *chart) Add(cell *entity.Cell, margins *entity.Margins, prop *props.Chart) {

Check warning on line 26 in internal/providers/gofpdf/chart.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/chart.go#L26

Added line #L26 was not covered by tests
// X
c.line.Add(cell, &props.Line{
Orientation: orientation.Horizontal,
SizePercent: 88,
OffsetPercent: 94,
Style: linestyle.Solid,
Thickness: 0.5,
})

Check warning on line 34 in internal/providers/gofpdf/chart.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/chart.go#L28-L34

Added lines #L28 - L34 were not covered by tests

// Y
c.line.Add(cell, &props.Line{
Orientation: orientation.Vertical,
SizePercent: 88,
OffsetPercent: 6,
Style: linestyle.Solid,
Thickness: 0.5,
})

Check warning on line 43 in internal/providers/gofpdf/chart.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/chart.go#L37-L43

Added lines #L37 - L43 were not covered by tests
}
142 changes: 142 additions & 0 deletions internal/providers/gofpdf/heatmap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package gofpdf

import (
"errors"
"math"

"github.com/johnfercher/maroto/v2/pkg/props"

"github.com/johnfercher/maroto/v2/internal/providers/gofpdf/gofpdfwrapper"
"github.com/johnfercher/maroto/v2/pkg/core"
"github.com/johnfercher/maroto/v2/pkg/core/entity"
)

var ErrOutOfRange = errors.New("out of range")

type heatMap struct {
pdf gofpdfwrapper.Fpdf
defaultFillColor *props.Color
chart core.Chart
padding float64
}

func NewHeatMap(pdf gofpdfwrapper.Fpdf, chart core.Chart) *heatMap {
return &heatMap{
pdf: pdf,
chart: chart,
defaultFillColor: &props.WhiteColor,
padding: 0,
}
}

func (s heatMap) Add(heatMap [][]int, cell *entity.Cell, margins *entity.Margins, prop *props.HeatMap) {
if heatMap == nil || len(heatMap) == 0 || len(heatMap[0]) == 0 {

Check failure on line 33 in internal/providers/gofpdf/heatmap.go

View workflow job for this annotation

GitHub Actions / lint

S1009: should omit nil check; len() for [][]int is defined as zero (gosimple)

Check failure on line 33 in internal/providers/gofpdf/heatmap.go

View workflow job for this annotation

GitHub Actions / lint

S1009: should omit nil check; len() for [][]int is defined as zero (gosimple)

Check failure on line 33 in internal/providers/gofpdf/heatmap.go

View workflow job for this annotation

GitHub Actions / lint

S1009: should omit nil check; len() for [][]int is defined as zero (gosimple)
return

Check warning on line 34 in internal/providers/gofpdf/heatmap.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L32-L34

Added lines #L32 - L34 were not covered by tests
}

max := s.getMax(heatMap)
transparent := s.getTransparent(prop)
stepX, stepY := s.getSteps(heatMap, cell, prop)

Check warning on line 39 in internal/providers/gofpdf/heatmap.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L37-L39

Added lines #L37 - L39 were not covered by tests

for i := 0; i < len(heatMap)-1; i++ {
for j := 0; j < len(heatMap[i])-1; j++ {
if !transparent[heatMap[i][j]] {
r, g, b := s.GetHeatColor(heatMap[i][j], max, prop.InvertScale, prop.HalfColor)

Check warning on line 44 in internal/providers/gofpdf/heatmap.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L41-L44

Added lines #L41 - L44 were not covered by tests

x := float64(i)*stepX + cell.X + margins.Left
y := float64(j) * stepY

Check warning on line 47 in internal/providers/gofpdf/heatmap.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L46-L47

Added lines #L46 - L47 were not covered by tests

// Invert to draw from bottom to up
y = cell.Height + margins.Top + cell.Y - y - stepY

Check warning on line 50 in internal/providers/gofpdf/heatmap.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L50

Added line #L50 was not covered by tests

s.pdf.SetFillColor(r, g, b)
s.pdf.Rect(x, y, stepX, stepY, "F")
s.pdf.SetFillColor(s.defaultFillColor.Red, s.defaultFillColor.Green, s.defaultFillColor.Blue)

Check warning on line 54 in internal/providers/gofpdf/heatmap.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L52-L54

Added lines #L52 - L54 were not covered by tests
}
}
}

if prop.Chart != nil {
s.chart.Add(cell, margins, prop.Chart)

Check warning on line 60 in internal/providers/gofpdf/heatmap.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L59-L60

Added lines #L59 - L60 were not covered by tests
}
}

func (s heatMap) getSteps(heatMap [][]int, cell *entity.Cell, prop *props.HeatMap) (float64, float64) {

Check failure on line 64 in internal/providers/gofpdf/heatmap.go

View workflow job for this annotation

GitHub Actions / lint

`(heatMap).getSteps` - `prop` is unused (unparam)

Check failure on line 64 in internal/providers/gofpdf/heatmap.go

View workflow job for this annotation

GitHub Actions / lint

`(heatMap).getSteps` - `prop` is unused (unparam)

Check failure on line 64 in internal/providers/gofpdf/heatmap.go

View workflow job for this annotation

GitHub Actions / lint

`(heatMap).getSteps` - `prop` is unused (unparam)
xSize := len(heatMap)
stepX := (cell.Width) / float64(xSize-1)

Check warning on line 66 in internal/providers/gofpdf/heatmap.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L64-L66

Added lines #L64 - L66 were not covered by tests

ySize := len(heatMap[0])
stepY := (cell.Height) / float64(ySize-1)

Check warning on line 69 in internal/providers/gofpdf/heatmap.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L68-L69

Added lines #L68 - L69 were not covered by tests

return stepX, stepY

Check warning on line 71 in internal/providers/gofpdf/heatmap.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L71

Added line #L71 was not covered by tests
}

func (s heatMap) GetHeatColor(i int, total int, invertScale bool, halfColor bool) (int, int, int) {
hueMax := 360.0
if halfColor {
hueMax /= 2.0

Check warning on line 77 in internal/providers/gofpdf/heatmap.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L74-L77

Added lines #L74 - L77 were not covered by tests
}
step := hueMax / float64(total)
iStep := step * float64(i)

Check warning on line 80 in internal/providers/gofpdf/heatmap.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L79-L80

Added lines #L79 - L80 were not covered by tests

if invertScale {
iStep = 160 - iStep

Check warning on line 83 in internal/providers/gofpdf/heatmap.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L82-L83

Added lines #L82 - L83 were not covered by tests
}

r, g, b, _ := HSVToRGB(iStep, 1.0, 1.0)
return int(r), int(g), int(b)

Check warning on line 87 in internal/providers/gofpdf/heatmap.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L86-L87

Added lines #L86 - L87 were not covered by tests
}

func (s heatMap) getMax(matrix [][]int) int {
var max = 0

Check failure on line 91 in internal/providers/gofpdf/heatmap.go

View workflow job for this annotation

GitHub Actions / lint

File is not `gofumpt`-ed (gofumpt)

Check failure on line 91 in internal/providers/gofpdf/heatmap.go

View workflow job for this annotation

GitHub Actions / lint

File is not `gofumpt`-ed (gofumpt)

Check failure on line 91 in internal/providers/gofpdf/heatmap.go

View workflow job for this annotation

GitHub Actions / lint

File is not `gofumpt`-ed (gofumpt)
for _, row := range matrix {
for _, cell := range row {
if cell > max {
max = cell

Check warning on line 95 in internal/providers/gofpdf/heatmap.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L90-L95

Added lines #L90 - L95 were not covered by tests
}
}
}

return max

Check warning on line 100 in internal/providers/gofpdf/heatmap.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L100

Added line #L100 was not covered by tests
}

func (s heatMap) getTransparent(p *props.HeatMap) map[int]bool {
m := make(map[int]bool)
for _, t := range p.TransparentValues {
m[t] = true

Check warning on line 106 in internal/providers/gofpdf/heatmap.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L103-L106

Added lines #L103 - L106 were not covered by tests
}
return m

Check warning on line 108 in internal/providers/gofpdf/heatmap.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L108

Added line #L108 was not covered by tests
}

// HSVToRGB converts an HSV triple to an RGB triple.
// Source: https://github.com/Crazy3lf/colorconv/blob/master/colorconv.go
func HSVToRGB(h, s, v float64) (r, g, b uint8, err error) {
if h < 0 || h >= 360 ||
s < 0 || s > 1 ||
v < 0 || v > 1 {
return 0, 0, 0, ErrOutOfRange

Check warning on line 117 in internal/providers/gofpdf/heatmap.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L113-L117

Added lines #L113 - L117 were not covered by tests
}
// When 0 ≤ h < 360, 0 ≤ s ≤ 1 and 0 ≤ v ≤ 1:
C := v * s
X := C * (1 - math.Abs(math.Mod(h/60, 2)-1))
m := v - C
var Rnot, Gnot, Bnot float64
switch {
case 0 <= h && h < 60:
Rnot, Gnot, Bnot = C, X, 0
case 60 <= h && h < 120:
Rnot, Gnot, Bnot = X, C, 0
case 120 <= h && h < 180:
Rnot, Gnot, Bnot = 0, C, X
case 180 <= h && h < 240:
Rnot, Gnot, Bnot = 0, X, C
case 240 <= h && h < 300:
Rnot, Gnot, Bnot = X, 0, C
case 300 <= h && h < 360:
Rnot, Gnot, Bnot = C, 0, X

Check warning on line 136 in internal/providers/gofpdf/heatmap.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L120-L136

Added lines #L120 - L136 were not covered by tests
}
r = uint8(math.Round((Rnot + m) * 255))
g = uint8(math.Round((Gnot + m) * 255))
b = uint8(math.Round((Bnot + m) * 255))
return r, g, b, nil

Check warning on line 141 in internal/providers/gofpdf/heatmap.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L138-L141

Added lines #L138 - L141 were not covered by tests
}
6 changes: 6 additions & 0 deletions internal/providers/gofpdf/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type provider struct {
code core.Code
image core.Image
line core.Line
heatMap core.HeatMap
cache cache.Cache
cellWriter cellwriter.CellWriter
cfg *entity.Config
Expand All @@ -40,6 +41,7 @@ func New(dep *Dependencies) core.Provider {
code: dep.Code,
image: dep.Image,
line: dep.Line,
heatMap: dep.HeatMap,
cellWriter: dep.CellWriter,
cfg: dep.Cfg,
cache: dep.Cache,
Expand Down Expand Up @@ -268,6 +270,10 @@ func (g *provider) SetCompression(compression bool) {
g.fpdf.SetCompression(compression)
}

func (g *provider) AddHeatMap(heatMap [][]int, cell *entity.Cell, prop *props.HeatMap) {
g.heatMap.Add(heatMap, cell, g.cfg.Margins, prop)

Check warning on line 274 in internal/providers/gofpdf/provider.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/provider.go#L273-L274

Added lines #L273 - L274 were not covered by tests
}

func (g *provider) getBarcodeImageName(code string, prop *props.Barcode) string {
if prop == nil {
return code + string(barcode.Code128)
Expand Down
Loading

0 comments on commit 4c25b0b

Please sign in to comment.