Skip to content

Commit

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

import (
"github.com/johnfercher/maroto/v2/pkg/props"
"log"
"math/rand"

"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"
)

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

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

xMax := 620
yMax := 200
max := 10
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, rand.Intn(max))
}
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: 117.76ms, executions: [117.76ms]
add_rows -> avg: 6229.00ns, executions: [12.29μs, 0.17μs]
file_size -> 6.54Mb
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
}
132 changes: 132 additions & 0 deletions internal/providers/gofpdf/heatmap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
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)

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 + s.padding
y := float64(j)*stepY + cell.Y + margins.Top + s.padding

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

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 51 in internal/providers/gofpdf/heatmap.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L49-L51

Added lines #L49 - L51 were not covered by tests
}
}
}

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

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

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L56-L57

Added lines #L56 - L57 were not covered by tests
}
}

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

Check failure on line 61 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 61 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 61 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 63 in internal/providers/gofpdf/heatmap.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L61-L63

Added lines #L61 - L63 were not covered by tests

ySize := len(heatMap[0])
stepY := (cell.Height) / float64(ySize-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#L65-L66

Added lines #L65 - L66 were not covered by tests

return stepX, stepY

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

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L68

Added line #L68 was not covered by tests
}

func (s heatMap) GetHeatColor(i int, total int) (int, int, int) {
hueMax := 160.0
step := hueMax / float64(total)
iStep := step * float64(i)

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

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L71 - L74 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 77 in internal/providers/gofpdf/heatmap.go

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L76 - L77 were not covered by tests
}

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

Check failure on line 81 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 81 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 81 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 85 in internal/providers/gofpdf/heatmap.go

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L80 - L85 were not covered by tests
}
}
}

return max

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

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L90

Added line #L90 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 96 in internal/providers/gofpdf/heatmap.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L93-L96

Added lines #L93 - L96 were not covered by tests
}
return m

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

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L98

Added line #L98 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 107 in internal/providers/gofpdf/heatmap.go

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L103 - L107 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 126 in internal/providers/gofpdf/heatmap.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L110-L126

Added lines #L110 - L126 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 131 in internal/providers/gofpdf/heatmap.go

View check run for this annotation

Codecov / codecov/patch

internal/providers/gofpdf/heatmap.go#L128-L131

Added lines #L128 - L131 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 36eb7ba

Please sign in to comment.