diff --git a/Makefile b/Makefile index 1170035..c4b88d2 100644 --- a/Makefile +++ b/Makefile @@ -6,9 +6,9 @@ test: docs: build cat charts/testdata/basic.json | ./calendarheatmap > docs/basic.png - cat charts/testdata/basic.json | ./calendarheatmap -colorscale=purple-blue-9.csv > docs/colorscale-1.png - cat charts/testdata/basic.json | ./calendarheatmap -colorscale=green-blue-9.csv > docs/colorscale-2.png - cat charts/testdata/basic.json | ./calendarheatmap -colorscale=yellow-green-9.csv > docs/colorscale-3.png + CALENDAR_HEATMAP_ASSETS_PATH=assets cat charts/testdata/basic.json | ./calendarheatmap -colorscale=purple-blue-9.csv > docs/colorscale-1.png + CALENDAR_HEATMAP_ASSETS_PATH=assets cat charts/testdata/basic.json | ./calendarheatmap -colorscale=green-blue-9.csv > docs/colorscale-2.png + CALENDAR_HEATMAP_ASSETS_PATH=assets cat charts/testdata/basic.json | ./calendarheatmap -colorscale=yellow-green-9.csv > docs/colorscale-3.png cat charts/testdata/basic.json | ./calendarheatmap -locale=ko_KR > docs/korean.png cat charts/testdata/basic.json | ./calendarheatmap -locale=ko_KR -output=svg > docs/korean.svg cat charts/testdata/basic.json | ./calendarheatmap -labels=false > docs/nolabels.png diff --git a/README.md b/README.md index de79f91..ef0f929 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,14 @@ Self-contained, plain Go implementation of calendar heatmap inspired by GitHub contribution activity. ``` -$ go build +$ go install github.com/nikolaydubina/calendarheatmap@latest $ echo '{ "2020-05-16": 8, "2020-05-17": 13, "2020-05-18": 5, "2020-05-19": 8, "2020-05-20": 5 -}' | ./calendarheatmap > chart.png +}' | calendarheatmap > chart.png ``` Basic @@ -21,6 +21,7 @@ Basic ![basic](docs/basic.png) Colorscales + ![col1](docs/colorscale-1.png) ![col2](docs/colorscale-2.png) ![col2](docs/colorscale-3.png) @@ -30,11 +31,8 @@ UTF-8 SVG - ![svg](docs/korean.svg) - - Without month separator ![nosep](docs/noseparator.png) @@ -44,7 +42,6 @@ Without labels Without labels, without separator ![nosep_nolab](docs/noseparator_nolabels.png) - ## GitHub stars over time [![GitHub stars over time](https://starchart.cc/nikolaydubina/calendarheatmap.svg)](https://starchart.cc/nikolaydubina/calendarheatmap) diff --git a/charts/colorscale.go b/charts/colorscale.go index 6897c28..2c111c8 100644 --- a/charts/colorscale.go +++ b/charts/colorscale.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "image/color" + "io" "math" "os" "strconv" @@ -37,13 +38,9 @@ func uint8FromStr(s string) (uint8, error) { return uint8(v), nil } -// NewBasicColorscaleFromCSVFile loads basic colorscale from CSV file -func NewBasicColorscaleFromCSVFile(path string) (BasicColorScale, error) { - colorscaleReader, err := os.Open(path) - if err != nil { - return nil, fmt.Errorf("can not open file: %w", err) - } - rows, err := csv.NewReader(colorscaleReader).ReadAll() +// NewBasicColorscaleFromCSV creates colorscale from reader +func NewBasicColorscaleFromCSV(reader io.Reader) (BasicColorScale, error) { + rows, err := csv.NewReader(reader).ReadAll() if err != nil { return nil, fmt.Errorf("can not read CSV: %w", err) } @@ -83,3 +80,12 @@ func NewBasicColorscaleFromCSVFile(path string) (BasicColorScale, error) { } return colorscale, nil } + +// NewBasicColorscaleFromCSVFile loads basic colorscale from CSV file +func NewBasicColorscaleFromCSVFile(path string) (BasicColorScale, error) { + colorscaleReader, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("can not open file: %w", err) + } + return NewBasicColorscaleFromCSV(colorscaleReader) +} diff --git a/charts/text.go b/charts/text.go index d5ef0eb..df02b40 100644 --- a/charts/text.go +++ b/charts/text.go @@ -11,12 +11,8 @@ import ( "golang.org/x/image/math/fixed" ) -// LoadFontFaceFromFile loads font face from file -func LoadFontFaceFromFile(fontPath string) (font.Face, error) { - fontBytes, err := ioutil.ReadFile(fontPath) - if err != nil { - return nil, fmt.Errorf("can not open font file with error: %w", err) - } +// LoadFontFace loads font face from bytes +func LoadFontFace(fontBytes []byte) (font.Face, error) { f, err := opentype.Parse(fontBytes) if err != nil { return nil, fmt.Errorf("can not parse font file: %w", err) @@ -32,6 +28,15 @@ func LoadFontFaceFromFile(fontPath string) (font.Face, error) { return face, nil } +// LoadFontFaceFromFile loads font face from file +func LoadFontFaceFromFile(fontPath string) (font.Face, error) { + fontBytes, err := ioutil.ReadFile(fontPath) + if err != nil { + return nil, fmt.Errorf("can not open font file with error: %w", err) + } + return LoadFontFace(fontBytes) +} + // drawText inserts text into provided image at bottom left coordinate func drawText(fontFace font.Face, img *image.RGBA, offset image.Point, text string, color color.RGBA) { if fontFace == nil { diff --git a/go.mod b/go.mod index 09813ec..b4898f3 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module github.com/nikolaydubina/calendarheatmap -go 1.14 +go 1.16 require golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 diff --git a/main.go b/main.go index 610226b..67ca9ee 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "encoding/json" "flag" "image/color" @@ -10,9 +11,17 @@ import ( "path" "time" + _ "embed" + "github.com/nikolaydubina/calendarheatmap/charts" ) +//go:embed assets/fonts/Sunflower-Medium.ttf +var defaultFontFaceBytes []byte + +//go:embed assets/colorscales/green-blue-9.csv +var defaultColorScaleBytes []byte + func main() { var ( colorScale string @@ -20,7 +29,6 @@ func main() { locale string monthSep bool outputFormat string - assetsPath string ) flag.BoolVar(&labels, "labels", true, "labels for weekday and months") @@ -28,14 +36,29 @@ func main() { flag.StringVar(&colorScale, "colorscale", "green-blue-9.csv", "filename of colorscale") flag.StringVar(&locale, "locale", "en_US", "locale of labels (en_US, ko_KR)") flag.StringVar(&outputFormat, "output", "png", "output format (png, jpeg, gif, svg)") - flag.StringVar(&assetsPath, "assetspath", "", "absolute path, or relative path for executable, of calendarheatmap repo assets, if not set will try CALENDARHEATMAP_ASSETS env variable, if not will try 'assets'") flag.Parse() - if assetsPath == "" { - assetsPath = os.Getenv("CALENDAR_HEATMAP_ASSETS_PATH") - if assetsPath == "" { - assetsPath = "assets" + var colorscale charts.BasicColorScale + if assetsPath := os.Getenv("CALENDAR_HEATMAP_ASSETS_PATH"); assetsPath != "" { + var err error + colorscale, err = charts.NewBasicColorscaleFromCSVFile(path.Join(assetsPath, "colorscales", colorScale)) + if err != nil { + log.Fatal(err) } + } else { + var err error + if colorScale != "green-blue-9.csv" { + log.Printf("defaulting to colorscale %s since CALENDAR_HEATMAP_ASSETS_PATH is not set", "green-blue-9.csv") + } + colorscale, err = charts.NewBasicColorscaleFromCSV(bytes.NewBuffer(defaultColorScaleBytes)) + if err != nil { + log.Fatal(err) + } + } + + fontFace, err := charts.LoadFontFace(defaultFontFaceBytes) + if err != nil { + log.Fatal(err) } data, err := ioutil.ReadAll(os.Stdin) @@ -48,16 +71,6 @@ func main() { log.Fatal(err) } - colorscale, err := charts.NewBasicColorscaleFromCSVFile(path.Join(assetsPath, "colorscales", colorScale)) - if err != nil { - log.Fatal(err) - } - - fontFace, err := charts.LoadFontFaceFromFile(path.Join(assetsPath, "fonts", "Sunflower-Medium.ttf")) - if err != nil { - log.Fatal(err) - } - conf := charts.HeatmapConfig{ Counts: counts, ColorScale: colorscale,