1
0
mirror of https://github.com/nikolaydubina/calendarheatmap.git synced 2025-02-14 16:59:55 +02:00

moved out from main function

This commit is contained in:
Nikolay 2020-07-02 20:54:02 +08:00
parent 22d4ce1d4f
commit 7b3eb62310
6 changed files with 128 additions and 69 deletions

View File

@ -7,9 +7,6 @@ import (
"time"
"github.com/nikolaydubina/calendarheatmap/colorscales"
"golang.org/x/image/font"
"golang.org/x/image/font/basicfont"
"golang.org/x/image/math/fixed"
)
var weekdaysPos = map[time.Weekday]int{
@ -37,6 +34,26 @@ var monthLabel = map[time.Month]string{
time.December: "Dec",
}
var weekdayOrder = [7]time.Weekday{
time.Monday,
time.Tuesday,
time.Wednesday,
time.Thursday,
time.Friday,
time.Saturday,
time.Sunday,
}
var weekdayLabel = map[time.Weekday]string{
time.Monday: "Mon",
time.Tuesday: "Tue",
time.Wednesday: "Wed",
time.Thursday: "Thu",
time.Friday: "Fri",
time.Saturday: "Sat",
time.Sunday: "Sun",
}
const (
numWeeksYear = 52
numWeekCols = numWeeksYear + 1 // 53 * 7 = 371 > 366
@ -84,6 +101,7 @@ func NewHeatmap(conf HeatmapConfig) image.Image {
yearStartDate := time.Date(conf.Year, 1, 1, 1, 1, 1, 1, time.UTC)
vIdx := weekdaysPos[yearStartDate.Weekday()]
// draw each cell as block
for day := yearStartDate; day.Year() == conf.Year; day = day.Add(time.Hour * 24) {
y = (boxSize+margin)*vIdx + textHightTop
@ -93,39 +111,11 @@ func NewHeatmap(conf HeatmapConfig) image.Image {
draw.Draw(img, r, &image.Uniform{color}, image.ZP, draw.Src)
if conf.DrawMonthSeparator {
if day.Day() == 1 && day.Month() != time.January {
marginSep := margin / 2
closeLeft := image.Point{X: x - marginSep - 1, Y: y - marginSep - 1}
closeRight := image.Point{X: x + boxSize + marginSep, Y: y - marginSep - 1}
farLeft := image.Point{X: x - marginSep - 1, Y: height}
farRight := image.Point{X: x + boxSize + marginSep, Y: textHightTop}
drawLineAxis(img, farLeft, closeLeft, borderColor) // left line
if vIdx != 0 {
drawLineAxis(img, closeRight, farRight, borderColor) // right line
drawLineAxis(img, closeLeft, closeRight, borderColor) // top line
}
}
drawMonthSeparator(img, day, image.Point{X: x, Y: y}, textHightTop, height)
}
if conf.DrawLabels {
switch day.Weekday() {
case time.Monday:
addLabel(img, textWidthLeft-25, textHightTop+15, "Mon")
case time.Wednesday:
addLabel(img, textWidthLeft-25, textHightTop+15+(boxSize+margin)*2, "Wed")
case time.Friday:
addLabel(img, textWidthLeft-25, textHightTop+15+(boxSize+margin)*4, "Fri")
}
if day.Day() == 1 {
monthLabelX := x
if weekdaysPos[day.Weekday()] != 0 {
monthLabelX += boxSize + margin
}
addLabel(img, monthLabelX, textHightTop-5, monthLabel[day.Month()])
}
drawMonthLabels(img, day, image.Point{X: x, Y: y})
}
vIdx++
@ -135,48 +125,62 @@ func NewHeatmap(conf HeatmapConfig) image.Image {
}
}
if conf.DrawLabels {
drawWeekdayLabels(
img,
image.Point{X: textWidthLeft, Y: textHightTop},
map[time.Weekday]bool{
time.Monday: true,
time.Wednesday: true,
time.Friday: true,
},
)
}
return img
}
// drawLineAxis draws line parallel to X or Y axis
func drawLineAxis(img draw.Image, a image.Point, b image.Point, col color.Color) {
switch {
// do not attempt to draw dot
case a == b:
return
// vertical
case a.X == b.X:
y1, y2 := a.Y, b.Y
if y1 > y2 {
y1, y2 = y2, y1
// drawMonthSeparator progressively draws months separator into provided image
func drawMonthSeparator(img *image.RGBA, day time.Time, point image.Point, minY int, maxY int) {
if day.Day() == 1 && day.Month() != time.January {
marginSep := margin / 2
closeLeft := image.Point{X: point.X - marginSep - 1, Y: point.Y - marginSep - 1}
closeRight := image.Point{X: point.X + boxSize + marginSep, Y: point.Y - marginSep - 1}
farLeft := image.Point{X: point.X - marginSep - 1, Y: maxY}
farRight := image.Point{X: point.X + boxSize + marginSep, Y: minY}
drawLineAxis(img, farLeft, closeLeft, borderColor) // left line
if day.Weekday() != time.Monday {
drawLineAxis(img, closeRight, farRight, borderColor) // right line
drawLineAxis(img, closeLeft, closeRight, borderColor) // top line
}
for q := y1; q <= y2; q++ {
img.Set(a.X, q, col)
}
// horizontal
case a.Y == b.Y:
x1, x2 := a.X, b.X
if x1 > x2 {
x1, x2 = x2, x1
}
for q := x1; q <= x2; q++ {
img.Set(q, a.Y, col)
}
default:
panic("input line is not parallel to axis. not implemented")
}
}
func addLabel(img *image.RGBA, x, y int, label string) {
point := fixed.Point26_6{
X: fixed.Int26_6(x * 64),
Y: fixed.Int26_6(y * 64),
// drawMonthLabels progressively draws text of months based on current day into provided image
func drawMonthLabels(img *image.RGBA, day time.Time, point image.Point) {
if day.Day() == 1 {
monthLabelX := point.X
if weekdaysPos[day.Weekday()] != 0 {
monthLabelX += boxSize + margin
}
drawText(img, image.Point{X: monthLabelX, Y: textHightTop - 5}, monthLabel[day.Month()])
}
}
// drawWeekdayLabel draws column of same width labels for weekdays
// All weekday labels assumed to have same width, which really depends on font.
// offset argument is top right corner of where to insert column of weekday labels.
func drawWeekdayLabels(img *image.RGBA, offset image.Point, weekdays map[time.Weekday]bool) {
width := 25
height := 10
x := offset.X - width
y := offset.Y + height
for _, w := range weekdayOrder {
if weekdays[w] {
drawText(img, image.Point{X: x, Y: y}, weekdayLabel[w])
}
y += boxSize + margin
}
d := &font.Drawer{
Dst: img,
Src: image.NewUniform(textColor),
Face: basicfont.Face7x13,
Dot: point,
}
d.DrawString(label)
}

55
charts/utils.go Normal file
View File

@ -0,0 +1,55 @@
package charts
import (
"image"
"image/color"
"image/draw"
"golang.org/x/image/font"
"golang.org/x/image/font/basicfont"
"golang.org/x/image/math/fixed"
)
// drawLineAxis draws line parallel to X or Y axis
func drawLineAxis(img draw.Image, a image.Point, b image.Point, col color.Color) {
switch {
// do not attempt to draw dot
case a == b:
return
// vertical
case a.X == b.X:
y1, y2 := a.Y, b.Y
if y1 > y2 {
y1, y2 = y2, y1
}
for q := y1; q <= y2; q++ {
img.Set(a.X, q, col)
}
// horizontal
case a.Y == b.Y:
x1, x2 := a.X, b.X
if x1 > x2 {
x1, x2 = x2, x1
}
for q := x1; q <= x2; q++ {
img.Set(q, a.Y, col)
}
default:
panic("input line is not parallel to axis. not implemented")
}
}
// drawText inserts text into provided image at bottom left coordinate
func drawText(img *image.RGBA, offset image.Point, text string) {
point := fixed.Point26_6{
X: fixed.Int26_6(offset.X * 64),
Y: fixed.Int26_6(offset.Y * 64),
}
d := &font.Drawer{
Dst: img,
Src: image.NewUniform(textColor),
Face: basicfont.Face7x13,
Dot: point,
}
d.DrawString(text)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB