186 lines
4.3 KiB
Go
186 lines
4.3 KiB
Go
|
package gg
|
||
|
|
||
|
import (
|
||
|
"image"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
// PlotArgs holds the arguments for drawing line graphs (charts)
|
||
|
type PlotArgs struct {
|
||
|
|
||
|
// curve name
|
||
|
L string // label
|
||
|
|
||
|
// lines
|
||
|
C string // line: color
|
||
|
A float64 // line: alpha (0, 1]. A<1e-14 => A=1.0
|
||
|
Ls string // line: style
|
||
|
Lw float64 // line: width
|
||
|
|
||
|
// markers
|
||
|
M string // marker: type, e.g. "o", "s", "+", "x", "img:filename.png"
|
||
|
Ms int // marker: size
|
||
|
Me int // marker: mark-every
|
||
|
Mec string // marker: edge color
|
||
|
Mew float64 // marker: edge width
|
||
|
Void bool // marker: void marker (draw edge only)
|
||
|
|
||
|
// internal
|
||
|
markerImg image.Image // marker image
|
||
|
markerName string // filename corresponding to loaded image
|
||
|
}
|
||
|
|
||
|
// DrawMarker draws marker
|
||
|
func (o *PlotArgs) DrawMarker(dc *Context, x, y int) {
|
||
|
|
||
|
// skip if marker type is empty
|
||
|
if o.M == "" {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// markersize and half-markersize
|
||
|
s := o.markerSize()
|
||
|
h := s / 2
|
||
|
|
||
|
// draw marker
|
||
|
switch o.M {
|
||
|
|
||
|
// circle
|
||
|
case "o":
|
||
|
if !o.Void {
|
||
|
o.Circle(dc, true, false, x, y, h)
|
||
|
}
|
||
|
o.Circle(dc, true, true, x, y, h)
|
||
|
|
||
|
// square
|
||
|
case "s":
|
||
|
if !o.Void {
|
||
|
o.Rect(dc, true, false, x-h, y-h, s, s)
|
||
|
}
|
||
|
o.Rect(dc, true, true, x-h, y-h, s, s)
|
||
|
|
||
|
// cross
|
||
|
case "+":
|
||
|
o.Line(dc, true, true, x-h, y, x+h, y)
|
||
|
o.Line(dc, true, true, x, y-h, x, y+h)
|
||
|
|
||
|
// x
|
||
|
case "x":
|
||
|
o.Line(dc, true, true, x-h, y-h, x+h, y+h)
|
||
|
o.Line(dc, true, true, x-h, y+h, x+h, y-h)
|
||
|
|
||
|
// use image as marker
|
||
|
default:
|
||
|
if o.checkMarkerImage() {
|
||
|
dc.DrawImageAnchored(o.markerImg, x, y, 0.5, 0.5)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Activate activates properties of lines/shapes
|
||
|
func (o *PlotArgs) Activate(dc *Context, marker, edge bool) {
|
||
|
|
||
|
// color
|
||
|
clr := o.C
|
||
|
if marker && edge && o.Mec != "" {
|
||
|
clr = o.Mec
|
||
|
}
|
||
|
r, g, b, _ := parseHexColor(clr)
|
||
|
|
||
|
// alpha
|
||
|
alpha := o.A
|
||
|
if alpha < 1e-14 || marker {
|
||
|
alpha = 1.0
|
||
|
}
|
||
|
|
||
|
// set color
|
||
|
dc.SetRGBA255(r, g, b, int(alpha*255))
|
||
|
}
|
||
|
|
||
|
// Circle draws Circle
|
||
|
func (o *PlotArgs) Circle(dc *Context, marker, edge bool, x, y, r int) {
|
||
|
o.Activate(dc, marker, edge)
|
||
|
dc.DrawCircle(float64(x), float64(y), float64(r))
|
||
|
if edge {
|
||
|
dc.Stroke()
|
||
|
} else {
|
||
|
dc.Fill()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Rect draws rectangle
|
||
|
func (o *PlotArgs) Rect(dc *Context, marker, edge bool, x, y, w, h int) {
|
||
|
o.Activate(dc, marker, edge)
|
||
|
dc.DrawRectangle(float64(x), float64(y), float64(w), float64(h))
|
||
|
if edge {
|
||
|
dc.Stroke()
|
||
|
} else {
|
||
|
dc.Fill()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Line draws Line
|
||
|
func (o *PlotArgs) Line(dc *Context, marker, edge bool, x1, y1, x2, y2 int) {
|
||
|
o.Activate(dc, marker, edge)
|
||
|
dc.DrawLine(float64(x1), float64(y1), float64(x2), float64(y2))
|
||
|
if edge {
|
||
|
dc.Stroke()
|
||
|
} else {
|
||
|
dc.Fill()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// checkMarkerImage loads marker image if not loaded already
|
||
|
func (o *PlotArgs) checkMarkerImage() (useImage bool) {
|
||
|
useImage = strings.HasPrefix(o.M, "img:")
|
||
|
if useImage {
|
||
|
fn := strings.TrimPrefix(o.M, "img:")
|
||
|
if o.markerImg == nil || o.markerName != fn { // load only if not loaded already
|
||
|
var err error
|
||
|
o.markerImg, err = LoadPNG(fn)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
o.markerName = fn
|
||
|
}
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// markerSize returns the size of marker
|
||
|
func (o *PlotArgs) markerSize() int {
|
||
|
if o.checkMarkerImage() {
|
||
|
return o.markerImg.Bounds().Dy()
|
||
|
}
|
||
|
if o.Ms == 0 {
|
||
|
return 8 // default value
|
||
|
}
|
||
|
return o.Ms
|
||
|
}
|
||
|
|
||
|
// colors /////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
// GetColor returns a color from a default palette
|
||
|
// use palette < 0 for automatic color
|
||
|
func GetColor(i, palette int) string {
|
||
|
if palette < 0 || palette >= len(palettes) {
|
||
|
return ""
|
||
|
}
|
||
|
p := palettes[palette]
|
||
|
return p[i%len(p)]
|
||
|
}
|
||
|
|
||
|
// palettes holds color palettes
|
||
|
var palettes = [][]string{
|
||
|
{"#003fff", "#35b052", "#e8000b", "#8a2be2", "#ffc400", "#00d7ff"},
|
||
|
{"blue", "green", "magenta", "orange", "red", "cyan", "black", "#de9700", "#89009d", "#7ad473", "#737ad4", "#d473ce", "#7e6322", "#462222", "#98ac9d", "#37a3e8", "yellow"},
|
||
|
{"#4c72b0", "#55a868", "#c44e52", "#8172b2", "#ccb974", "#64b5cd"},
|
||
|
{"#9b59b6", "#3498db", "#95a5a6", "#e74c3c", "#34495e", "#2ecc71"},
|
||
|
{"#e41a1c", "#377eb8", "#4daf4a", "#984ea3", "#ff7f00", "#ffff33"},
|
||
|
{"#7fc97f", "#beaed4", "#fdc086", "#ffff99", "#386cb0", "#f0027f", "#bf5b17"},
|
||
|
{"#001c7f", "#017517", "#8c0900", "#7600a1", "#b8860b", "#006374"},
|
||
|
{"#0072b2", "#009e73", "#d55e00", "#cc79a7", "#f0e442", "#56b4e9"},
|
||
|
{"#4878cf", "#6acc65", "#d65f5f", "#b47cc7", "#c4ad66", "#77bedb"},
|
||
|
{"#92c6ff", "#97f0aa", "#ff9f9a", "#d0bbff", "#fffea3", "#b0e0e6"},
|
||
|
}
|