tests
14
README.md
@ -3,19 +3,21 @@
|
||||
|
||||
Self-contained, plain Go implementation of calendar heatmap inspired by Github contribution activity.
|
||||
|
||||
Basic
|
||||

|
||||
|
||||
Colorscales
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
Without month separator
|
||||

|
||||

|
||||
|
||||
Without labels
|
||||

|
||||

|
||||
|
||||
Without labels, without separator
|
||||

|
||||

|
||||
|
||||
Example:
|
||||
|
||||
|
171
charts/charts_test.go
Normal file
@ -0,0 +1,171 @@
|
||||
package charts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/nikolaydubina/calendarheatmap/colorscales"
|
||||
)
|
||||
|
||||
func savePNG(t *testing.T, img image.Image, filename string) {
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
t.Errorf(fmt.Errorf("can not save: %w", err).Error())
|
||||
}
|
||||
if err := png.Encode(f, img); err != nil {
|
||||
t.Errorf(fmt.Errorf("can not encode png: %w", err).Error())
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
t.Errorf(fmt.Errorf("can not close: %w", err).Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicData(t *testing.T) {
|
||||
countByDay := map[int]int{
|
||||
137: 8, 138: 13, 139: 5, 140: 8, 141: 5, 142: 5, 143: 3, 144: 5,
|
||||
145: 6, 146: 3, 147: 5, 148: 8, 149: 2, 150: 3, 151: 8, 152: 5,
|
||||
153: 1, 154: 3, 155: 1, 156: 3, 157: 1, 158: 3, 159: 5, 161: 1,
|
||||
162: 2, 164: 9, 165: 7, 166: 4, 167: 1, 169: 1, 172: 2, 173: 1,
|
||||
175: 2, 176: 2, 177: 3, 178: 3, 179: 2, 180: 1, 181: 1, 182: 2,
|
||||
}
|
||||
|
||||
t.Run("basic", func(t *testing.T) {
|
||||
img := NewHeatmap(HeatmapConfig{
|
||||
Year: 2020,
|
||||
CountByDay: countByDay,
|
||||
ColorScale: colorscales.PuBu9,
|
||||
DrawMonthSeparator: true,
|
||||
DrawLabels: true,
|
||||
Margin: 3,
|
||||
BoxSize: 15,
|
||||
TextWidthLeft: 35,
|
||||
TextHightTop: 20,
|
||||
TextColor: color.RGBA{100, 100, 100, 255},
|
||||
BorderColor: color.RGBA{200, 200, 200, 255},
|
||||
})
|
||||
savePNG(t, img, "testdata/basic.png")
|
||||
})
|
||||
|
||||
t.Run("colorscale_1", func(t *testing.T) {
|
||||
img := NewHeatmap(HeatmapConfig{
|
||||
Year: 2020,
|
||||
CountByDay: countByDay,
|
||||
ColorScale: colorscales.GnBu9,
|
||||
DrawMonthSeparator: true,
|
||||
DrawLabels: true,
|
||||
Margin: 3,
|
||||
BoxSize: 15,
|
||||
TextWidthLeft: 35,
|
||||
TextHightTop: 20,
|
||||
TextColor: color.RGBA{100, 100, 100, 255},
|
||||
BorderColor: color.RGBA{200, 200, 200, 255},
|
||||
})
|
||||
savePNG(t, img, "testdata/colorscale_1.png")
|
||||
})
|
||||
|
||||
t.Run("colorscale_2", func(t *testing.T) {
|
||||
img := NewHeatmap(HeatmapConfig{
|
||||
Year: 2020,
|
||||
CountByDay: countByDay,
|
||||
ColorScale: colorscales.YlGn9,
|
||||
DrawMonthSeparator: true,
|
||||
DrawLabels: true,
|
||||
Margin: 3,
|
||||
BoxSize: 15,
|
||||
TextWidthLeft: 35,
|
||||
TextHightTop: 20,
|
||||
TextColor: color.RGBA{100, 100, 100, 255},
|
||||
BorderColor: color.RGBA{200, 200, 200, 255},
|
||||
})
|
||||
savePNG(t, img, "testdata/colorscale_2.png")
|
||||
})
|
||||
|
||||
t.Run("no separator", func(t *testing.T) {
|
||||
img := NewHeatmap(HeatmapConfig{
|
||||
Year: 2020,
|
||||
CountByDay: countByDay,
|
||||
ColorScale: colorscales.PuBu9,
|
||||
DrawMonthSeparator: false,
|
||||
DrawLabels: true,
|
||||
Margin: 3,
|
||||
BoxSize: 15,
|
||||
TextWidthLeft: 35,
|
||||
TextHightTop: 20,
|
||||
TextColor: color.RGBA{100, 100, 100, 255},
|
||||
BorderColor: color.RGBA{200, 200, 200, 255},
|
||||
})
|
||||
savePNG(t, img, "testdata/noseparator.png")
|
||||
})
|
||||
|
||||
t.Run("no labels", func(t *testing.T) {
|
||||
img := NewHeatmap(HeatmapConfig{
|
||||
Year: 2020,
|
||||
CountByDay: countByDay,
|
||||
ColorScale: colorscales.PuBu9,
|
||||
DrawMonthSeparator: true,
|
||||
DrawLabels: false,
|
||||
Margin: 3,
|
||||
BoxSize: 15,
|
||||
TextWidthLeft: 35,
|
||||
TextHightTop: 20,
|
||||
TextColor: color.RGBA{100, 100, 100, 255},
|
||||
BorderColor: color.RGBA{200, 200, 200, 255},
|
||||
})
|
||||
savePNG(t, img, "testdata/nolabels.png")
|
||||
})
|
||||
|
||||
t.Run("no separator, no labels", func(t *testing.T) {
|
||||
img := NewHeatmap(HeatmapConfig{
|
||||
Year: 2020,
|
||||
CountByDay: countByDay,
|
||||
ColorScale: colorscales.PuBu9,
|
||||
DrawMonthSeparator: true,
|
||||
DrawLabels: false,
|
||||
Margin: 3,
|
||||
BoxSize: 15,
|
||||
TextWidthLeft: 35,
|
||||
TextHightTop: 20,
|
||||
TextColor: color.RGBA{100, 100, 100, 255},
|
||||
BorderColor: color.RGBA{200, 200, 200, 255},
|
||||
})
|
||||
savePNG(t, img, "testdata/noseparator_nolabels.png")
|
||||
})
|
||||
|
||||
t.Run("empty data", func(t *testing.T) {
|
||||
img := NewHeatmap(HeatmapConfig{
|
||||
Year: 2020,
|
||||
CountByDay: map[int]int{},
|
||||
ColorScale: colorscales.PuBu9,
|
||||
DrawMonthSeparator: true,
|
||||
DrawLabels: false,
|
||||
Margin: 3,
|
||||
BoxSize: 15,
|
||||
TextWidthLeft: 35,
|
||||
TextHightTop: 20,
|
||||
TextColor: color.RGBA{100, 100, 100, 255},
|
||||
BorderColor: color.RGBA{200, 200, 200, 255},
|
||||
})
|
||||
savePNG(t, img, "testdata/empty_data.png")
|
||||
})
|
||||
|
||||
t.Run("nil data", func(t *testing.T) {
|
||||
img := NewHeatmap(HeatmapConfig{
|
||||
Year: 2020,
|
||||
CountByDay: nil,
|
||||
ColorScale: colorscales.PuBu9,
|
||||
DrawMonthSeparator: true,
|
||||
DrawLabels: false,
|
||||
Margin: 3,
|
||||
BoxSize: 15,
|
||||
TextWidthLeft: 35,
|
||||
TextHightTop: 20,
|
||||
TextColor: color.RGBA{100, 100, 100, 255},
|
||||
BorderColor: color.RGBA{200, 200, 200, 255},
|
||||
})
|
||||
savePNG(t, img, "testdata/nil_data.png")
|
||||
})
|
||||
}
|
@ -27,7 +27,8 @@ func NewDayIterator(year int, offset image.Point, countByDay map[int]int, boxSiz
|
||||
row = i
|
||||
}
|
||||
}
|
||||
maxCount := 0
|
||||
// in case CountByDay is empty, we need to make Value 0/1 -> 0
|
||||
maxCount := 1
|
||||
for _, q := range countByDay {
|
||||
if q > maxCount {
|
||||
maxCount = q
|
||||
|
114
charts/dayiter_test.go
Normal file
@ -0,0 +1,114 @@
|
||||
package charts
|
||||
|
||||
import (
|
||||
"image"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBasicDayIter(t *testing.T) {
|
||||
t.Run("num days correct", func(t *testing.T) {
|
||||
iter := NewDayIterator(
|
||||
2019,
|
||||
image.Point{X: 0, Y: 0},
|
||||
map[int]int{},
|
||||
5,
|
||||
3,
|
||||
)
|
||||
if iter == nil {
|
||||
t.Errorf("should not be nil")
|
||||
}
|
||||
if iter.Done() {
|
||||
t.Errorf("should not be done on start")
|
||||
}
|
||||
cnt := 1
|
||||
for ; !iter.Done(); iter.Next() {
|
||||
cnt++
|
||||
}
|
||||
cnt = cnt - 1
|
||||
if cnt != 365 {
|
||||
t.Errorf("2019 has 365 days, got %d", cnt)
|
||||
}
|
||||
if iter.Time().YearDay() != 1 || iter.Time().Year() != 2020 {
|
||||
t.Errorf("has to be day 1 of next year")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("num days correct, leap year", func(t *testing.T) {
|
||||
iter := NewDayIterator(
|
||||
2000,
|
||||
image.Point{X: 0, Y: 0},
|
||||
map[int]int{},
|
||||
5,
|
||||
3,
|
||||
)
|
||||
if iter == nil {
|
||||
t.Errorf("should not be nil")
|
||||
}
|
||||
if iter.Done() {
|
||||
t.Errorf("should not be done on start")
|
||||
}
|
||||
cnt := 1
|
||||
for ; !iter.Done(); iter.Next() {
|
||||
cnt++
|
||||
}
|
||||
cnt = cnt - 1
|
||||
if cnt != 366 {
|
||||
t.Errorf("2000 has 366 days, got %d", cnt)
|
||||
}
|
||||
if iter.Time().YearDay() != 1 || iter.Time().Year() != 2001 {
|
||||
t.Errorf("has to be day 1 of next year")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("value check, float", func(t *testing.T) {
|
||||
iter := NewDayIterator(
|
||||
2000,
|
||||
image.Point{X: 0, Y: 0},
|
||||
map[int]int{2: 2, 5: 1},
|
||||
5,
|
||||
3,
|
||||
)
|
||||
for ; !iter.Done(); iter.Next() {
|
||||
var exp float64
|
||||
switch iter.Time().YearDay() {
|
||||
case 2:
|
||||
exp = 1
|
||||
case 5:
|
||||
exp = 0.5
|
||||
}
|
||||
if iter.Value() != exp {
|
||||
t.Errorf("wrong day value")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("value check, empty counters", func(t *testing.T) {
|
||||
iter := NewDayIterator(
|
||||
2000,
|
||||
image.Point{X: 0, Y: 0},
|
||||
map[int]int{},
|
||||
5,
|
||||
3,
|
||||
)
|
||||
for ; !iter.Done(); iter.Next() {
|
||||
if iter.Value() != 0 {
|
||||
t.Errorf("wrong day value")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("value check, nil counters", func(t *testing.T) {
|
||||
iter := NewDayIterator(
|
||||
2000,
|
||||
image.Point{X: 0, Y: 0},
|
||||
nil,
|
||||
5,
|
||||
3,
|
||||
)
|
||||
for ; !iter.Done(); iter.Next() {
|
||||
if iter.Value() != 0 {
|
||||
t.Errorf("wrong day value")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
BIN
charts/testdata/empty_data.png
vendored
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
charts/testdata/nil_data.png
vendored
Normal file
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
BIN
charts/testdata/noseparator_nolabels.png
vendored
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
charts/testdata/nosepartor.png
vendored
Normal file
After Width: | Height: | Size: 2.1 KiB |
31
charts/utils_test.go
Normal file
@ -0,0 +1,31 @@
|
||||
package charts
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBasicDrawAxis(t *testing.T) {
|
||||
img := image.NewRGBA(image.Rect(0, 0, 10, 10))
|
||||
|
||||
t.Run("along x", func(t *testing.T) {
|
||||
drawLineAxis(img, image.Point{X: 0, Y: 0}, image.Point{X: 0, Y: 10}, color.Black)
|
||||
})
|
||||
|
||||
t.Run("along y", func(t *testing.T) {
|
||||
drawLineAxis(img, image.Point{X: 0, Y: 0}, image.Point{X: 10, Y: 0}, color.Black)
|
||||
})
|
||||
|
||||
t.Run("reverse x", func(t *testing.T) {
|
||||
drawLineAxis(img, image.Point{X: 0, Y: 10}, image.Point{X: 0, Y: 0}, color.Black)
|
||||
})
|
||||
|
||||
t.Run("reverse y", func(t *testing.T) {
|
||||
drawLineAxis(img, image.Point{X: 10, Y: 0}, image.Point{X: 0, Y: 0}, color.Black)
|
||||
})
|
||||
|
||||
t.Run("dot", func(t *testing.T) {
|
||||
drawLineAxis(img, image.Point{X: 0, Y: 0}, image.Point{X: 0, Y: 0}, color.Black)
|
||||
})
|
||||
}
|
Before Width: | Height: | Size: 1.3 KiB |
@ -1,11 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -x
|
||||
|
||||
go run ../main.go -colorscale=PuBu9 -output=chart_PuBu9.png
|
||||
go run ../main.go -colorscale=GnBu9 -output=chart_GnBu9.png
|
||||
go run ../main.go -colorscale=YlGn9 -output=chart_YlGn9.png
|
||||
|
||||
go run ../main.go -colorscale=PuBu9 -output=chart_PuBu9_nolabels.png -labels=false
|
||||
go run ../main.go -colorscale=PuBu9 -output=chart_PuBu9_noseparator.png -monthsep=false
|
||||
go run ../main.go -colorscale=PuBu9 -output=chart_PuBu9_noseparator_nolabels.png -monthsep=false -labels=false
|