Add response time badge and chart
This commit is contained in:
		| @ -3,6 +3,7 @@ package controller | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| @ -11,19 +12,27 @@ import ( | ||||
| 	"github.com/gorilla/mux" | ||||
| ) | ||||
|  | ||||
| // badgeHandler handles the automatic generation of badge based on the group name and service name passed. | ||||
| const ( | ||||
| 	badgeColorHexAwesome  = "#40cc11" | ||||
| 	badgeColorHexGreat    = "#94cc11" | ||||
| 	badgeColorHexGood     = "#ccd311" | ||||
| 	badgeColorHexPassable = "#ccb311" | ||||
| 	badgeColorHexBad      = "#cc8111" | ||||
| 	badgeColorHexVeryBad  = "#c7130a" | ||||
| ) | ||||
|  | ||||
| // uptimeBadgeHandler handles the automatic generation of badge based on the group name and service name passed. | ||||
| // | ||||
| // Valid values for {duration}: 7d, 24h, 1h | ||||
| // Pattern for {identifier}: <KEY>.svg | ||||
| func badgeHandler(writer http.ResponseWriter, request *http.Request) { | ||||
| func uptimeBadgeHandler(writer http.ResponseWriter, request *http.Request) { | ||||
| 	variables := mux.Vars(request) | ||||
| 	duration := variables["duration"] | ||||
| 	var from time.Time | ||||
| 	switch duration { | ||||
| 	case "7d": | ||||
| 		from = time.Now().Add(-time.Hour * 24 * 7) | ||||
| 		from = time.Now().Add(-7 * 24 * time.Hour) | ||||
| 	case "24h": | ||||
| 		from = time.Now().Add(-time.Hour * 24) | ||||
| 		from = time.Now().Add(-24 * time.Hour) | ||||
| 	case "1h": | ||||
| 		from = time.Now().Add(-time.Hour) | ||||
| 	default: | ||||
| @ -31,8 +40,13 @@ func badgeHandler(writer http.ResponseWriter, request *http.Request) { | ||||
| 		_, _ = writer.Write([]byte("Durations supported: 7d, 24h, 1h")) | ||||
| 		return | ||||
| 	} | ||||
| 	identifier := variables["identifier"] | ||||
| 	key := strings.TrimSuffix(identifier, ".svg") | ||||
| 	var key string | ||||
| 	if identifier := variables["identifier"]; len(identifier) > 0 { | ||||
| 		// XXX: Remove this conditional statement in v3.0.0 and rely on variables["key"] instead | ||||
| 		key = strings.TrimSuffix(identifier, ".svg") | ||||
| 	} else { | ||||
| 		key = variables["key"] | ||||
| 	} | ||||
| 	uptime, err := storage.Get().GetUptimeByKey(key, from, time.Now()) | ||||
| 	if err != nil { | ||||
| 		if err == common.ErrServiceNotFound { | ||||
| @ -50,10 +64,10 @@ func badgeHandler(writer http.ResponseWriter, request *http.Request) { | ||||
| 	writer.Header().Set("Date", formattedDate) | ||||
| 	writer.Header().Set("Expires", formattedDate) | ||||
| 	writer.Header().Set("Content-Type", "image/svg+xml") | ||||
| 	_, _ = writer.Write(generateSVG(duration, uptime)) | ||||
| 	_, _ = writer.Write(generateUptimeBadgeSVG(duration, uptime)) | ||||
| } | ||||
|  | ||||
| func generateSVG(duration string, uptime float64) []byte { | ||||
| func generateUptimeBadgeSVG(duration string, uptime float64) []byte { | ||||
| 	var labelWidth, valueWidth, valueWidthAdjustment int | ||||
| 	switch duration { | ||||
| 	case "7d": | ||||
| @ -106,15 +120,118 @@ func generateSVG(duration string, uptime float64) []byte { | ||||
|  | ||||
| func getBadgeColorFromUptime(uptime float64) string { | ||||
| 	if uptime >= 0.975 { | ||||
| 		return "#40cc11" | ||||
| 		return badgeColorHexAwesome | ||||
| 	} else if uptime >= 0.95 { | ||||
| 		return "#94cc11" | ||||
| 		return badgeColorHexGreat | ||||
| 	} else if uptime >= 0.9 { | ||||
| 		return "#ccc311" | ||||
| 		return badgeColorHexGood | ||||
| 	} else if uptime >= 0.8 { | ||||
| 		return "#ccb311" | ||||
| 		return badgeColorHexPassable | ||||
| 	} else if uptime >= 0.65 { | ||||
| 		return "#cc8111" | ||||
| 		return badgeColorHexBad | ||||
| 	} | ||||
| 	return "#c7130a" | ||||
| 	return badgeColorHexVeryBad | ||||
| } | ||||
|  | ||||
| // responseTimeBadgeHandler handles the automatic generation of badge based on the group name and service name passed. | ||||
| // | ||||
| // Valid values for {duration}: 7d, 24h, 1h | ||||
| func responseTimeBadgeHandler(writer http.ResponseWriter, request *http.Request) { | ||||
| 	variables := mux.Vars(request) | ||||
| 	duration := variables["duration"] | ||||
| 	var from time.Time | ||||
| 	switch duration { | ||||
| 	case "7d": | ||||
| 		from = time.Now().Add(-7 * 24 * time.Hour) | ||||
| 	case "24h": | ||||
| 		from = time.Now().Add(-24 * time.Hour) | ||||
| 	case "1h": | ||||
| 		from = time.Now().Add(-time.Hour) | ||||
| 	default: | ||||
| 		writer.WriteHeader(http.StatusBadRequest) | ||||
| 		_, _ = writer.Write([]byte("Durations supported: 7d, 24h, 1h")) | ||||
| 		return | ||||
| 	} | ||||
| 	key := variables["key"] | ||||
| 	averageResponseTime, err := storage.Get().GetAverageResponseTimeByKey(key, from, time.Now()) | ||||
| 	if err != nil { | ||||
| 		if err == common.ErrServiceNotFound { | ||||
| 			writer.WriteHeader(http.StatusNotFound) | ||||
| 		} else if err == common.ErrInvalidTimeRange { | ||||
| 			writer.WriteHeader(http.StatusBadRequest) | ||||
| 		} else { | ||||
| 			writer.WriteHeader(http.StatusInternalServerError) | ||||
| 		} | ||||
| 		_, _ = writer.Write([]byte(err.Error())) | ||||
| 		return | ||||
| 	} | ||||
| 	formattedDate := time.Now().Format(http.TimeFormat) | ||||
| 	writer.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") | ||||
| 	writer.Header().Set("Date", formattedDate) | ||||
| 	writer.Header().Set("Expires", formattedDate) | ||||
| 	writer.Header().Set("Content-Type", "image/svg+xml") | ||||
| 	_, _ = writer.Write(generateResponseTimeBadgeSVG(duration, averageResponseTime)) | ||||
| } | ||||
|  | ||||
| func generateResponseTimeBadgeSVG(duration string, averageResponseTime int) []byte { | ||||
| 	var labelWidth, valueWidth int | ||||
| 	switch duration { | ||||
| 	case "7d": | ||||
| 		labelWidth = 105 | ||||
| 	case "24h": | ||||
| 		labelWidth = 110 | ||||
| 	case "1h": | ||||
| 		labelWidth = 105 | ||||
| 	default: | ||||
| 	} | ||||
| 	color := getBadgeColorFromResponseTime(averageResponseTime) | ||||
| 	sanitizedValue := strconv.Itoa(averageResponseTime) + "ms" | ||||
| 	valueWidth = len(sanitizedValue) * 11 | ||||
| 	width := labelWidth + valueWidth | ||||
| 	labelX := labelWidth / 2 | ||||
| 	valueX := labelWidth + (valueWidth / 2) | ||||
| 	svg := []byte(fmt.Sprintf(`<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="20"> | ||||
|   <linearGradient id="b" x2="0" y2="100%%"> | ||||
|     <stop offset="0" stop-color="#bbb" stop-opacity=".1"/> | ||||
|     <stop offset="1" stop-opacity=".1"/> | ||||
|   </linearGradient> | ||||
|   <mask id="a"> | ||||
|     <rect width="%d" height="20" rx="3" fill="#fff"/> | ||||
|   </mask> | ||||
|   <g mask="url(#a)"> | ||||
|     <path fill="#555" d="M0 0h%dv20H0z"/> | ||||
|     <path fill="%s" d="M%d 0h%dv20H%dz"/> | ||||
|     <path fill="url(#b)" d="M0 0h%dv20H0z"/> | ||||
|   </g> | ||||
|   <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> | ||||
|     <text x="%d" y="15" fill="#010101" fill-opacity=".3"> | ||||
|       response time %s | ||||
|     </text> | ||||
|     <text x="%d" y="14"> | ||||
|       response time %s | ||||
|     </text> | ||||
|     <text x="%d" y="15" fill="#010101" fill-opacity=".3"> | ||||
|       %s | ||||
|     </text> | ||||
|     <text x="%d" y="14"> | ||||
|       %s | ||||
|     </text> | ||||
|   </g> | ||||
| </svg>`, width, width, labelWidth, color, labelWidth, valueWidth, labelWidth, width, labelX, duration, labelX, duration, valueX, sanitizedValue, valueX, sanitizedValue)) | ||||
| 	return svg | ||||
| } | ||||
|  | ||||
| func getBadgeColorFromResponseTime(responseTime int) string { | ||||
| 	if responseTime <= 50 { | ||||
| 		return badgeColorHexAwesome | ||||
| 	} else if responseTime <= 200 { | ||||
| 		return badgeColorHexGreat | ||||
| 	} else if responseTime <= 300 { | ||||
| 		return badgeColorHexGood | ||||
| 	} else if responseTime <= 500 { | ||||
| 		return badgeColorHexPassable | ||||
| 	} else if responseTime <= 750 { | ||||
| 		return badgeColorHexBad | ||||
| 	} | ||||
| 	return badgeColorHexVeryBad | ||||
| } | ||||
|  | ||||
| @ -1,32 +1,116 @@ | ||||
| package controller | ||||
|  | ||||
| import ( | ||||
| 	"strconv" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestGetBadgeColorFromUptime(t *testing.T) { | ||||
| 	if getBadgeColorFromUptime(1) != "#40cc11" { | ||||
| 		t.Error("expected #40cc11 from an uptime of 1, got", getBadgeColorFromUptime(1)) | ||||
| 	scenarios := []struct { | ||||
| 		Uptime        float64 | ||||
| 		ExpectedColor string | ||||
| 	}{ | ||||
| 		{ | ||||
| 			Uptime:        1, | ||||
| 			ExpectedColor: badgeColorHexAwesome, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Uptime:        0.99, | ||||
| 			ExpectedColor: badgeColorHexAwesome, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Uptime:        0.97, | ||||
| 			ExpectedColor: badgeColorHexGreat, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Uptime:        0.95, | ||||
| 			ExpectedColor: badgeColorHexGreat, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Uptime:        0.93, | ||||
| 			ExpectedColor: badgeColorHexGood, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Uptime:        0.9, | ||||
| 			ExpectedColor: badgeColorHexGood, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Uptime:        0.85, | ||||
| 			ExpectedColor: badgeColorHexPassable, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Uptime:        0.7, | ||||
| 			ExpectedColor: badgeColorHexBad, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Uptime:        0.65, | ||||
| 			ExpectedColor: badgeColorHexBad, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Uptime:        0.6, | ||||
| 			ExpectedColor: badgeColorHexVeryBad, | ||||
| 		}, | ||||
| 	} | ||||
| 	if getBadgeColorFromUptime(0.95) != "#94cc11" { | ||||
| 		t.Error("expected #94cc11 from an uptime of 0.95, got", getBadgeColorFromUptime(0.95)) | ||||
| 	} | ||||
| 	if getBadgeColorFromUptime(0.9) != "#ccc311" { | ||||
| 		t.Error("expected #c9cc11 from an uptime of 0.9, got", getBadgeColorFromUptime(0.9)) | ||||
| 	} | ||||
| 	if getBadgeColorFromUptime(0.85) != "#ccb311" { | ||||
| 		t.Error("expected #ccb311 from an uptime of 0.85, got", getBadgeColorFromUptime(0.85)) | ||||
| 	} | ||||
| 	if getBadgeColorFromUptime(0.75) != "#cc8111" { | ||||
| 		t.Error("expected #cc8111 from an uptime of 0.75, got", getBadgeColorFromUptime(0.75)) | ||||
| 	} | ||||
| 	if getBadgeColorFromUptime(0.6) != "#c7130a" { | ||||
| 		t.Error("expected #c7130a from an uptime of 0.6, got", getBadgeColorFromUptime(0.6)) | ||||
| 	} | ||||
| 	if getBadgeColorFromUptime(0.25) != "#c7130a" { | ||||
| 		t.Error("expected #c7130a from an uptime of 0.25, got", getBadgeColorFromUptime(0.25)) | ||||
| 	} | ||||
| 	if getBadgeColorFromUptime(0) != "#c7130a" { | ||||
| 		t.Error("expected #c7130a from an uptime of 0, got", getBadgeColorFromUptime(0)) | ||||
| 	for _, scenario := range scenarios { | ||||
| 		t.Run("uptime-"+strconv.Itoa(int(scenario.Uptime*100)), func(t *testing.T) { | ||||
| 			if getBadgeColorFromUptime(scenario.Uptime) != scenario.ExpectedColor { | ||||
| 				t.Errorf("expected %s from %f, got %v", scenario.ExpectedColor, scenario.Uptime, getBadgeColorFromUptime(scenario.Uptime)) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestGetBadgeColorFromResponseTime(t *testing.T) { | ||||
| 	scenarios := []struct { | ||||
| 		ResponseTime  int | ||||
| 		ExpectedColor string | ||||
| 	}{ | ||||
| 		{ | ||||
| 			ResponseTime:  10, | ||||
| 			ExpectedColor: badgeColorHexAwesome, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ResponseTime:  50, | ||||
| 			ExpectedColor: badgeColorHexAwesome, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ResponseTime:  75, | ||||
| 			ExpectedColor: badgeColorHexGreat, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ResponseTime:  150, | ||||
| 			ExpectedColor: badgeColorHexGreat, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ResponseTime:  201, | ||||
| 			ExpectedColor: badgeColorHexGood, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ResponseTime:  300, | ||||
| 			ExpectedColor: badgeColorHexGood, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ResponseTime:  301, | ||||
| 			ExpectedColor: badgeColorHexPassable, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ResponseTime:  450, | ||||
| 			ExpectedColor: badgeColorHexPassable, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ResponseTime:  700, | ||||
| 			ExpectedColor: badgeColorHexBad, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ResponseTime:  1500, | ||||
| 			ExpectedColor: badgeColorHexVeryBad, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, scenario := range scenarios { | ||||
| 		t.Run("response-time-"+strconv.Itoa(scenario.ResponseTime), func(t *testing.T) { | ||||
| 			if getBadgeColorFromResponseTime(scenario.ResponseTime) != scenario.ExpectedColor { | ||||
| 				t.Errorf("expected %s from %d, got %v", scenario.ExpectedColor, scenario.ResponseTime, getBadgeColorFromResponseTime(scenario.ResponseTime)) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
							
								
								
									
										110
									
								
								controller/chart.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								controller/chart.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,110 @@ | ||||
| package controller | ||||
|  | ||||
| import ( | ||||
| 	"math" | ||||
| 	"net/http" | ||||
| 	"sort" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/TwinProduction/gatus/storage" | ||||
| 	"github.com/TwinProduction/gatus/storage/store/common" | ||||
| 	"github.com/gorilla/mux" | ||||
| 	"github.com/wcharczuk/go-chart/v2" | ||||
| 	"github.com/wcharczuk/go-chart/v2/drawing" | ||||
| ) | ||||
|  | ||||
| const timeFormat = "3:04PM" | ||||
|  | ||||
| var ( | ||||
| 	gridStyle = chart.Style{ | ||||
| 		StrokeColor: drawing.Color{R: 119, G: 119, B: 119, A: 40}, | ||||
| 		StrokeWidth: 1.0, | ||||
| 	} | ||||
| 	axisStyle = chart.Style{ | ||||
| 		FontColor: drawing.Color{R: 119, G: 119, B: 119, A: 255}, | ||||
| 	} | ||||
| 	transparentStyle = chart.Style{ | ||||
| 		FillColor: drawing.Color{R: 255, G: 255, B: 255, A: 0}, | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| func responseTimeChartHandler(writer http.ResponseWriter, r *http.Request) { | ||||
| 	vars := mux.Vars(r) | ||||
| 	duration := vars["duration"] | ||||
| 	var from time.Time | ||||
| 	switch duration { | ||||
| 	case "7d": | ||||
| 		from = time.Now().Add(-24 * 7 * time.Hour) | ||||
| 	case "24h": | ||||
| 		from = time.Now().Add(-24 * time.Hour) | ||||
| 	default: | ||||
| 		writer.WriteHeader(http.StatusBadRequest) | ||||
| 		_, _ = writer.Write([]byte("Durations supported: 7d, 24h")) | ||||
| 		return | ||||
| 	} | ||||
| 	hourlyAverageResponseTime, err := storage.Get().GetHourlyAverageResponseTimeByKey(vars["key"], from, time.Now()) | ||||
| 	if err != nil { | ||||
| 		if err == common.ErrServiceNotFound { | ||||
| 			writer.WriteHeader(http.StatusNotFound) | ||||
| 		} else if err == common.ErrInvalidTimeRange { | ||||
| 			writer.WriteHeader(http.StatusBadRequest) | ||||
| 		} else { | ||||
| 			writer.WriteHeader(http.StatusInternalServerError) | ||||
| 		} | ||||
| 		_, _ = writer.Write([]byte(err.Error())) | ||||
| 		return | ||||
| 	} | ||||
| 	series := chart.TimeSeries{ | ||||
| 		Name: "Average response time per hour", | ||||
| 		Style: chart.Style{ | ||||
| 			StrokeWidth: 1.5, | ||||
| 			DotWidth:    2.0, | ||||
| 		}, | ||||
| 	} | ||||
| 	keys := make([]int, 0, len(hourlyAverageResponseTime)) | ||||
| 	for hourlyTimestamp := range hourlyAverageResponseTime { | ||||
| 		keys = append(keys, int(hourlyTimestamp)) | ||||
| 	} | ||||
| 	sort.Ints(keys) | ||||
| 	var maxAverageResponseTime float64 | ||||
| 	for _, key := range keys { | ||||
| 		averageResponseTime := float64(hourlyAverageResponseTime[int64(key)]) | ||||
| 		if maxAverageResponseTime < averageResponseTime { | ||||
| 			maxAverageResponseTime = averageResponseTime | ||||
| 		} | ||||
| 		series.XValues = append(series.XValues, time.Unix(int64(key), 0)) | ||||
| 		series.YValues = append(series.YValues, averageResponseTime) | ||||
| 	} | ||||
| 	graph := chart.Chart{ | ||||
| 		Canvas:     transparentStyle, | ||||
| 		Background: transparentStyle, | ||||
| 		Width:      1280, | ||||
| 		Height:     300, | ||||
| 		XAxis: chart.XAxis{ | ||||
| 			ValueFormatter: chart.TimeValueFormatterWithFormat(timeFormat), | ||||
| 			GridMajorStyle: gridStyle, | ||||
| 			GridMinorStyle: gridStyle, | ||||
| 			Style:          axisStyle, | ||||
| 			NameStyle:      axisStyle, | ||||
| 		}, | ||||
| 		YAxis: chart.YAxis{ | ||||
| 			Name:           "Average response time", | ||||
| 			GridMajorStyle: gridStyle, | ||||
| 			GridMinorStyle: gridStyle, | ||||
| 			Style:          axisStyle, | ||||
| 			NameStyle:      axisStyle, | ||||
| 			Range: &chart.ContinuousRange{ | ||||
| 				Min: 0, | ||||
| 				Max: math.Ceil(maxAverageResponseTime * 1.25), | ||||
| 			}, | ||||
| 		}, | ||||
| 		Series: []chart.Series{series}, | ||||
| 	} | ||||
| 	writer.Header().Set("Content-Type", "image/svg+xml") | ||||
| 	if err := graph.Render(chart.SVG, writer); err != nil { | ||||
| 		writer.Header().Set("Content-Type", "text/plain") | ||||
| 		writer.WriteHeader(http.StatusInternalServerError) | ||||
| 		_, _ = writer.Write([]byte(err.Error())) | ||||
| 		return | ||||
| 	} | ||||
| } | ||||
| @ -75,9 +75,18 @@ func CreateRouter(securityConfig *security.Config, enabledMetrics bool) *mux.Rou | ||||
| 	} | ||||
| 	router.Handle("/health", health.Handler().WithJSON(true)).Methods("GET") | ||||
| 	router.HandleFunc("/favicon.ico", favIconHandler).Methods("GET") | ||||
| 	router.HandleFunc("/api/v1/statuses", secureIfNecessary(securityConfig, serviceStatusesHandler)).Methods("GET") // No GzipHandler for this one, because we cache the content | ||||
| 	// Deprecated endpoints | ||||
| 	router.HandleFunc("/api/v1/statuses", secureIfNecessary(securityConfig, serviceStatusesHandler)).Methods("GET") // No GzipHandler for this one, because we cache the content as Gzipped already | ||||
| 	router.HandleFunc("/api/v1/statuses/{key}", secureIfNecessary(securityConfig, GzipHandlerFunc(serviceStatusHandler))).Methods("GET") | ||||
| 	router.HandleFunc("/api/v1/badges/uptime/{duration}/{identifier}", badgeHandler).Methods("GET") | ||||
| 	router.HandleFunc("/api/v1/badges/uptime/{duration}/{identifier}", uptimeBadgeHandler).Methods("GET") | ||||
| 	// New endpoints | ||||
| 	router.HandleFunc("/api/v1/services/statuses", secureIfNecessary(securityConfig, serviceStatusesHandler)).Methods("GET") // No GzipHandler for this one, because we cache the content as Gzipped already | ||||
| 	router.HandleFunc("/api/v1/services/{key}/statuses", secureIfNecessary(securityConfig, GzipHandlerFunc(serviceStatusHandler))).Methods("GET") | ||||
| 	// TODO: router.HandleFunc("/api/v1/services/{key}/uptimes", secureIfNecessary(securityConfig, GzipHandlerFunc(serviceUptimesHandler))).Methods("GET") | ||||
| 	// TODO: router.HandleFunc("/api/v1/services/{key}/events", secureIfNecessary(securityConfig, GzipHandlerFunc(serviceEventsHandler))).Methods("GET") | ||||
| 	router.HandleFunc("/api/v1/services/{key}/uptimes/{duration}/badge.svg", uptimeBadgeHandler).Methods("GET") | ||||
| 	router.HandleFunc("/api/v1/services/{key}/response-times/{duration}/badge.svg", responseTimeBadgeHandler).Methods("GET") | ||||
| 	router.HandleFunc("/api/v1/services/{key}/response-times/{duration}/chart.svg", responseTimeChartHandler).Methods("GET") | ||||
| 	// SPA | ||||
| 	router.HandleFunc("/services/{service}", spaHandler).Methods("GET") | ||||
| 	// Everything else falls back on static content | ||||
|  | ||||
| @ -123,60 +123,167 @@ func TestCreateRouter(t *testing.T) { | ||||
| 			ExpectedCode: http.StatusOK, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "badges-1h", | ||||
| 			Name:         "old-badge-1h", | ||||
| 			Path:         "/api/v1/badges/uptime/1h/core_frontend.svg", | ||||
| 			ExpectedCode: http.StatusOK, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "badges-24h", | ||||
| 			Name:         "old-badge-24h", | ||||
| 			Path:         "/api/v1/badges/uptime/24h/core_backend.svg", | ||||
| 			ExpectedCode: http.StatusOK, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "badges-7d", | ||||
| 			Name:         "old-badge-7d", | ||||
| 			Path:         "/api/v1/badges/uptime/7d/core_frontend.svg", | ||||
| 			ExpectedCode: http.StatusOK, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "badges-with-invalid-duration", | ||||
| 			Name:         "old-badge-with-invalid-duration", | ||||
| 			Path:         "/api/v1/badges/uptime/3d/core_backend.svg", | ||||
| 			ExpectedCode: http.StatusBadRequest, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "badges-for-invalid-key", | ||||
| 			Name:         "old-badge-for-invalid-key", | ||||
| 			Path:         "/api/v1/badges/uptime/7d/invalid_key.svg", | ||||
| 			ExpectedCode: http.StatusNotFound, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "service-statuses", | ||||
| 			Name:         "badge-uptime-1h", | ||||
| 			Path:         "/api/v1/services/core_frontend/uptimes/1h/badge.svg", | ||||
| 			ExpectedCode: http.StatusOK, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "badge-uptime-24h", | ||||
| 			Path:         "/api/v1/services/core_backend/uptimes/24h/badge.svg", | ||||
| 			ExpectedCode: http.StatusOK, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "badge-uptime-7d", | ||||
| 			Path:         "/api/v1/services/core_frontend/uptimes/7d/badge.svg", | ||||
| 			ExpectedCode: http.StatusOK, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "badge-uptime-with-invalid-duration", | ||||
| 			Path:         "/api/v1/services/core_backend/uptimes/3d/badge.svg", | ||||
| 			ExpectedCode: http.StatusBadRequest, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "badge-uptime-for-invalid-key", | ||||
| 			Path:         "/api/v1/services/invalid_key/uptimes/7d/badge.svg", | ||||
| 			ExpectedCode: http.StatusNotFound, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "badge-response-time-1h", | ||||
| 			Path:         "/api/v1/services/core_frontend/response-times/1h/badge.svg", | ||||
| 			ExpectedCode: http.StatusOK, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "badge-response-time-24h", | ||||
| 			Path:         "/api/v1/services/core_backend/response-times/24h/badge.svg", | ||||
| 			ExpectedCode: http.StatusOK, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "badge-response-time-7d", | ||||
| 			Path:         "/api/v1/services/core_frontend/response-times/7d/badge.svg", | ||||
| 			ExpectedCode: http.StatusOK, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "badge-response-time-with-invalid-duration", | ||||
| 			Path:         "/api/v1/services/core_backend/response-times/3d/badge.svg", | ||||
| 			ExpectedCode: http.StatusBadRequest, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "badge-response-time-for-invalid-key", | ||||
| 			Path:         "/api/v1/services/invalid_key/response-times/7d/badge.svg", | ||||
| 			ExpectedCode: http.StatusNotFound, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "chart-response-time-24h", | ||||
| 			Path:         "/api/v1/services/core_backend/response-times/24h/chart.svg", | ||||
| 			ExpectedCode: http.StatusOK, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "chart-response-time-7d", | ||||
| 			Path:         "/api/v1/services/core_frontend/response-times/7d/chart.svg", | ||||
| 			ExpectedCode: http.StatusOK, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "chart-response-time-with-invalid-duration", | ||||
| 			Path:         "/api/v1/services/core_backend/response-times/3d/chart.svg", | ||||
| 			ExpectedCode: http.StatusBadRequest, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "chart-response-time-for-invalid-key", | ||||
| 			Path:         "/api/v1/services/invalid_key/response-times/7d/chart.svg", | ||||
| 			ExpectedCode: http.StatusNotFound, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "old-service-statuses", | ||||
| 			Path:         "/api/v1/statuses", | ||||
| 			ExpectedCode: http.StatusOK, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "service-statuses-gzip", | ||||
| 			Name:         "old-service-statuses-gzip", | ||||
| 			Path:         "/api/v1/statuses", | ||||
| 			ExpectedCode: http.StatusOK, | ||||
| 			Gzip:         true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "old-service-statuses-pagination", | ||||
| 			Path:         "/api/v1/statuses?page=1&pageSize=20", | ||||
| 			ExpectedCode: http.StatusOK, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "old-service-status", | ||||
| 			Path:         "/api/v1/statuses/core_frontend", | ||||
| 			ExpectedCode: http.StatusOK, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "old-service-status-gzip", | ||||
| 			Path:         "/api/v1/statuses/core_frontend", | ||||
| 			ExpectedCode: http.StatusOK, | ||||
| 			Gzip:         true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "old-service-status-for-invalid-key", | ||||
| 			Path:         "/api/v1/statuses/invalid_key", | ||||
| 			ExpectedCode: http.StatusNotFound, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "service-statuses", | ||||
| 			Path:         "/api/v1/services/statuses", | ||||
| 			ExpectedCode: http.StatusOK, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "service-statuses-gzip", | ||||
| 			Path:         "/api/v1/services/statuses", | ||||
| 			ExpectedCode: http.StatusOK, | ||||
| 			Gzip:         true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "service-statuses-pagination", | ||||
| 			Path:         "/api/v1/statuses?page=1&pageSize=20", | ||||
| 			Path:         "/api/v1/services/statuses?page=1&pageSize=20", | ||||
| 			ExpectedCode: http.StatusOK, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "service-status", | ||||
| 			Path:         "/api/v1/statuses/core_frontend", | ||||
| 			Path:         "/api/v1/services/core_frontend/statuses", | ||||
| 			ExpectedCode: http.StatusOK, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "service-status-gzip", | ||||
| 			Path:         "/api/v1/statuses/core_frontend", | ||||
| 			Path:         "/api/v1/services/core_frontend/statuses", | ||||
| 			ExpectedCode: http.StatusOK, | ||||
| 			Gzip:         true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "service-status-pagination", | ||||
| 			Path:         "/api/v1/services/core_frontend/statuses?page=1&pageSize=20", | ||||
| 			ExpectedCode: http.StatusOK, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:         "service-status-for-invalid-key", | ||||
| 			Path:         "/api/v1/statuses/invalid_key", | ||||
| 			Path:         "/api/v1/services/invalid_key/statuses", | ||||
| 			ExpectedCode: http.StatusNotFound, | ||||
| 		}, | ||||
| 		{ | ||||
|  | ||||
							
								
								
									
										20
									
								
								vendor/github.com/golang/freetype/AUTHORS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								vendor/github.com/golang/freetype/AUTHORS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| # This is the official list of Freetype-Go authors for copyright purposes. | ||||
| # This file is distinct from the CONTRIBUTORS files. | ||||
| # See the latter for an explanation. | ||||
| # | ||||
| # Freetype-Go is derived from Freetype, which is written in C. The latter | ||||
| # is copyright 1996-2010 David Turner, Robert Wilhelm, and Werner Lemberg. | ||||
|  | ||||
| # Names should be added to this file as | ||||
| #	Name or Organization <email address> | ||||
| # The email address is not required for organizations. | ||||
|  | ||||
| # Please keep the list sorted. | ||||
|  | ||||
| Google Inc. | ||||
| Jeff R. Allen <jra@nella.org> | ||||
| Maksim Kochkin <maxxarts@gmail.com> | ||||
| Michael Fogleman <fogleman@gmail.com> | ||||
| Rémy Oudompheng <oudomphe@phare.normalesup.org> | ||||
| Roger Peppe <rogpeppe@gmail.com> | ||||
| Steven Edwards <steven@stephenwithav.com> | ||||
							
								
								
									
										38
									
								
								vendor/github.com/golang/freetype/CONTRIBUTORS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								vendor/github.com/golang/freetype/CONTRIBUTORS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | ||||
| # This is the official list of people who can contribute | ||||
| # (and typically have contributed) code to the Freetype-Go repository. | ||||
| # The AUTHORS file lists the copyright holders; this file | ||||
| # lists people.  For example, Google employees are listed here | ||||
| # but not in AUTHORS, because Google holds the copyright. | ||||
| # | ||||
| # The submission process automatically checks to make sure | ||||
| # that people submitting code are listed in this file (by email address). | ||||
| # | ||||
| # Names should be added to this file only after verifying that | ||||
| # the individual or the individual's organization has agreed to | ||||
| # the appropriate Contributor License Agreement, found here: | ||||
| # | ||||
| #     http://code.google.com/legal/individual-cla-v1.0.html | ||||
| #     http://code.google.com/legal/corporate-cla-v1.0.html | ||||
| # | ||||
| # The agreement for individuals can be filled out on the web. | ||||
| # | ||||
| # When adding J Random Contributor's name to this file, | ||||
| # either J's name or J's organization's name should be | ||||
| # added to the AUTHORS file, depending on whether the | ||||
| # individual or corporate CLA was used. | ||||
|  | ||||
| # Names should be added to this file like so: | ||||
| #     Name <email address> | ||||
|  | ||||
| # Please keep the list sorted. | ||||
|  | ||||
| Andrew Gerrand <adg@golang.org> | ||||
| Jeff R. Allen <jra@nella.org> <jeff.allen@gmail.com> | ||||
| Maksim Kochkin <maxxarts@gmail.com> | ||||
| Michael Fogleman <fogleman@gmail.com> | ||||
| Nigel Tao <nigeltao@golang.org> | ||||
| Rémy Oudompheng <oudomphe@phare.normalesup.org> <remyoudompheng@gmail.com> | ||||
| Rob Pike <r@golang.org> | ||||
| Roger Peppe <rogpeppe@gmail.com> | ||||
| Russ Cox <rsc@golang.org> | ||||
| Steven Edwards <steven@stephenwithav.com> | ||||
							
								
								
									
										12
									
								
								vendor/github.com/golang/freetype/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								vendor/github.com/golang/freetype/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| Use of the Freetype-Go software is subject to your choice of exactly one of | ||||
| the following two licenses: | ||||
|   * The FreeType License, which is similar to the original BSD license with | ||||
|     an advertising clause, or | ||||
|   * The GNU General Public License (GPL), version 2 or later. | ||||
|  | ||||
| The text of these licenses are available in the licenses/ftl.txt and the | ||||
| licenses/gpl.txt files respectively. They are also available at | ||||
| http://freetype.sourceforge.net/license.html | ||||
|  | ||||
| The Luxi fonts in the testdata directory are licensed separately. See the | ||||
| testdata/COPYING file for details. | ||||
							
								
								
									
										245
									
								
								vendor/github.com/golang/freetype/raster/geom.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										245
									
								
								vendor/github.com/golang/freetype/raster/geom.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,245 @@ | ||||
| // Copyright 2010 The Freetype-Go Authors. All rights reserved. | ||||
| // Use of this source code is governed by your choice of either the | ||||
| // FreeType License or the GNU General Public License version 2 (or | ||||
| // any later version), both of which can be found in the LICENSE file. | ||||
|  | ||||
| package raster | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"math" | ||||
|  | ||||
| 	"golang.org/x/image/math/fixed" | ||||
| ) | ||||
|  | ||||
| // maxAbs returns the maximum of abs(a) and abs(b). | ||||
| func maxAbs(a, b fixed.Int26_6) fixed.Int26_6 { | ||||
| 	if a < 0 { | ||||
| 		a = -a | ||||
| 	} | ||||
| 	if b < 0 { | ||||
| 		b = -b | ||||
| 	} | ||||
| 	if a < b { | ||||
| 		return b | ||||
| 	} | ||||
| 	return a | ||||
| } | ||||
|  | ||||
| // pNeg returns the vector -p, or equivalently p rotated by 180 degrees. | ||||
| func pNeg(p fixed.Point26_6) fixed.Point26_6 { | ||||
| 	return fixed.Point26_6{-p.X, -p.Y} | ||||
| } | ||||
|  | ||||
| // pDot returns the dot product p·q. | ||||
| func pDot(p fixed.Point26_6, q fixed.Point26_6) fixed.Int52_12 { | ||||
| 	px, py := int64(p.X), int64(p.Y) | ||||
| 	qx, qy := int64(q.X), int64(q.Y) | ||||
| 	return fixed.Int52_12(px*qx + py*qy) | ||||
| } | ||||
|  | ||||
| // pLen returns the length of the vector p. | ||||
| func pLen(p fixed.Point26_6) fixed.Int26_6 { | ||||
| 	// TODO(nigeltao): use fixed point math. | ||||
| 	x := float64(p.X) | ||||
| 	y := float64(p.Y) | ||||
| 	return fixed.Int26_6(math.Sqrt(x*x + y*y)) | ||||
| } | ||||
|  | ||||
| // pNorm returns the vector p normalized to the given length, or zero if p is | ||||
| // degenerate. | ||||
| func pNorm(p fixed.Point26_6, length fixed.Int26_6) fixed.Point26_6 { | ||||
| 	d := pLen(p) | ||||
| 	if d == 0 { | ||||
| 		return fixed.Point26_6{} | ||||
| 	} | ||||
| 	s, t := int64(length), int64(d) | ||||
| 	x := int64(p.X) * s / t | ||||
| 	y := int64(p.Y) * s / t | ||||
| 	return fixed.Point26_6{fixed.Int26_6(x), fixed.Int26_6(y)} | ||||
| } | ||||
|  | ||||
| // pRot45CW returns the vector p rotated clockwise by 45 degrees. | ||||
| // | ||||
| // Note that the Y-axis grows downwards, so {1, 0}.Rot45CW is {1/√2, 1/√2}. | ||||
| func pRot45CW(p fixed.Point26_6) fixed.Point26_6 { | ||||
| 	// 181/256 is approximately 1/√2, or sin(π/4). | ||||
| 	px, py := int64(p.X), int64(p.Y) | ||||
| 	qx := (+px - py) * 181 / 256 | ||||
| 	qy := (+px + py) * 181 / 256 | ||||
| 	return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)} | ||||
| } | ||||
|  | ||||
| // pRot90CW returns the vector p rotated clockwise by 90 degrees. | ||||
| // | ||||
| // Note that the Y-axis grows downwards, so {1, 0}.Rot90CW is {0, 1}. | ||||
| func pRot90CW(p fixed.Point26_6) fixed.Point26_6 { | ||||
| 	return fixed.Point26_6{-p.Y, p.X} | ||||
| } | ||||
|  | ||||
| // pRot135CW returns the vector p rotated clockwise by 135 degrees. | ||||
| // | ||||
| // Note that the Y-axis grows downwards, so {1, 0}.Rot135CW is {-1/√2, 1/√2}. | ||||
| func pRot135CW(p fixed.Point26_6) fixed.Point26_6 { | ||||
| 	// 181/256 is approximately 1/√2, or sin(π/4). | ||||
| 	px, py := int64(p.X), int64(p.Y) | ||||
| 	qx := (-px - py) * 181 / 256 | ||||
| 	qy := (+px - py) * 181 / 256 | ||||
| 	return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)} | ||||
| } | ||||
|  | ||||
| // pRot45CCW returns the vector p rotated counter-clockwise by 45 degrees. | ||||
| // | ||||
| // Note that the Y-axis grows downwards, so {1, 0}.Rot45CCW is {1/√2, -1/√2}. | ||||
| func pRot45CCW(p fixed.Point26_6) fixed.Point26_6 { | ||||
| 	// 181/256 is approximately 1/√2, or sin(π/4). | ||||
| 	px, py := int64(p.X), int64(p.Y) | ||||
| 	qx := (+px + py) * 181 / 256 | ||||
| 	qy := (-px + py) * 181 / 256 | ||||
| 	return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)} | ||||
| } | ||||
|  | ||||
| // pRot90CCW returns the vector p rotated counter-clockwise by 90 degrees. | ||||
| // | ||||
| // Note that the Y-axis grows downwards, so {1, 0}.Rot90CCW is {0, -1}. | ||||
| func pRot90CCW(p fixed.Point26_6) fixed.Point26_6 { | ||||
| 	return fixed.Point26_6{p.Y, -p.X} | ||||
| } | ||||
|  | ||||
| // pRot135CCW returns the vector p rotated counter-clockwise by 135 degrees. | ||||
| // | ||||
| // Note that the Y-axis grows downwards, so {1, 0}.Rot135CCW is {-1/√2, -1/√2}. | ||||
| func pRot135CCW(p fixed.Point26_6) fixed.Point26_6 { | ||||
| 	// 181/256 is approximately 1/√2, or sin(π/4). | ||||
| 	px, py := int64(p.X), int64(p.Y) | ||||
| 	qx := (-px + py) * 181 / 256 | ||||
| 	qy := (-px - py) * 181 / 256 | ||||
| 	return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)} | ||||
| } | ||||
|  | ||||
| // An Adder accumulates points on a curve. | ||||
| type Adder interface { | ||||
| 	// Start starts a new curve at the given point. | ||||
| 	Start(a fixed.Point26_6) | ||||
| 	// Add1 adds a linear segment to the current curve. | ||||
| 	Add1(b fixed.Point26_6) | ||||
| 	// Add2 adds a quadratic segment to the current curve. | ||||
| 	Add2(b, c fixed.Point26_6) | ||||
| 	// Add3 adds a cubic segment to the current curve. | ||||
| 	Add3(b, c, d fixed.Point26_6) | ||||
| } | ||||
|  | ||||
| // A Path is a sequence of curves, and a curve is a start point followed by a | ||||
| // sequence of linear, quadratic or cubic segments. | ||||
| type Path []fixed.Int26_6 | ||||
|  | ||||
| // String returns a human-readable representation of a Path. | ||||
| func (p Path) String() string { | ||||
| 	s := "" | ||||
| 	for i := 0; i < len(p); { | ||||
| 		if i != 0 { | ||||
| 			s += " " | ||||
| 		} | ||||
| 		switch p[i] { | ||||
| 		case 0: | ||||
| 			s += "S0" + fmt.Sprint([]fixed.Int26_6(p[i+1:i+3])) | ||||
| 			i += 4 | ||||
| 		case 1: | ||||
| 			s += "A1" + fmt.Sprint([]fixed.Int26_6(p[i+1:i+3])) | ||||
| 			i += 4 | ||||
| 		case 2: | ||||
| 			s += "A2" + fmt.Sprint([]fixed.Int26_6(p[i+1:i+5])) | ||||
| 			i += 6 | ||||
| 		case 3: | ||||
| 			s += "A3" + fmt.Sprint([]fixed.Int26_6(p[i+1:i+7])) | ||||
| 			i += 8 | ||||
| 		default: | ||||
| 			panic("freetype/raster: bad path") | ||||
| 		} | ||||
| 	} | ||||
| 	return s | ||||
| } | ||||
|  | ||||
| // Clear cancels any previous calls to p.Start or p.AddXxx. | ||||
| func (p *Path) Clear() { | ||||
| 	*p = (*p)[:0] | ||||
| } | ||||
|  | ||||
| // Start starts a new curve at the given point. | ||||
| func (p *Path) Start(a fixed.Point26_6) { | ||||
| 	*p = append(*p, 0, a.X, a.Y, 0) | ||||
| } | ||||
|  | ||||
| // Add1 adds a linear segment to the current curve. | ||||
| func (p *Path) Add1(b fixed.Point26_6) { | ||||
| 	*p = append(*p, 1, b.X, b.Y, 1) | ||||
| } | ||||
|  | ||||
| // Add2 adds a quadratic segment to the current curve. | ||||
| func (p *Path) Add2(b, c fixed.Point26_6) { | ||||
| 	*p = append(*p, 2, b.X, b.Y, c.X, c.Y, 2) | ||||
| } | ||||
|  | ||||
| // Add3 adds a cubic segment to the current curve. | ||||
| func (p *Path) Add3(b, c, d fixed.Point26_6) { | ||||
| 	*p = append(*p, 3, b.X, b.Y, c.X, c.Y, d.X, d.Y, 3) | ||||
| } | ||||
|  | ||||
| // AddPath adds the Path q to p. | ||||
| func (p *Path) AddPath(q Path) { | ||||
| 	*p = append(*p, q...) | ||||
| } | ||||
|  | ||||
| // AddStroke adds a stroked Path. | ||||
| func (p *Path) AddStroke(q Path, width fixed.Int26_6, cr Capper, jr Joiner) { | ||||
| 	Stroke(p, q, width, cr, jr) | ||||
| } | ||||
|  | ||||
| // firstPoint returns the first point in a non-empty Path. | ||||
| func (p Path) firstPoint() fixed.Point26_6 { | ||||
| 	return fixed.Point26_6{p[1], p[2]} | ||||
| } | ||||
|  | ||||
| // lastPoint returns the last point in a non-empty Path. | ||||
| func (p Path) lastPoint() fixed.Point26_6 { | ||||
| 	return fixed.Point26_6{p[len(p)-3], p[len(p)-2]} | ||||
| } | ||||
|  | ||||
| // addPathReversed adds q reversed to p. | ||||
| // For example, if q consists of a linear segment from A to B followed by a | ||||
| // quadratic segment from B to C to D, then the values of q looks like: | ||||
| // index: 01234567890123 | ||||
| // value: 0AA01BB12CCDD2 | ||||
| // So, when adding q backwards to p, we want to Add2(C, B) followed by Add1(A). | ||||
| func addPathReversed(p Adder, q Path) { | ||||
| 	if len(q) == 0 { | ||||
| 		return | ||||
| 	} | ||||
| 	i := len(q) - 1 | ||||
| 	for { | ||||
| 		switch q[i] { | ||||
| 		case 0: | ||||
| 			return | ||||
| 		case 1: | ||||
| 			i -= 4 | ||||
| 			p.Add1( | ||||
| 				fixed.Point26_6{q[i-2], q[i-1]}, | ||||
| 			) | ||||
| 		case 2: | ||||
| 			i -= 6 | ||||
| 			p.Add2( | ||||
| 				fixed.Point26_6{q[i+2], q[i+3]}, | ||||
| 				fixed.Point26_6{q[i-2], q[i-1]}, | ||||
| 			) | ||||
| 		case 3: | ||||
| 			i -= 8 | ||||
| 			p.Add3( | ||||
| 				fixed.Point26_6{q[i+4], q[i+5]}, | ||||
| 				fixed.Point26_6{q[i+2], q[i+3]}, | ||||
| 				fixed.Point26_6{q[i-2], q[i-1]}, | ||||
| 			) | ||||
| 		default: | ||||
| 			panic("freetype/raster: bad path") | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										287
									
								
								vendor/github.com/golang/freetype/raster/paint.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										287
									
								
								vendor/github.com/golang/freetype/raster/paint.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,287 @@ | ||||
| // Copyright 2010 The Freetype-Go Authors. All rights reserved. | ||||
| // Use of this source code is governed by your choice of either the | ||||
| // FreeType License or the GNU General Public License version 2 (or | ||||
| // any later version), both of which can be found in the LICENSE file. | ||||
|  | ||||
| package raster | ||||
|  | ||||
| import ( | ||||
| 	"image" | ||||
| 	"image/color" | ||||
| 	"image/draw" | ||||
| 	"math" | ||||
| ) | ||||
|  | ||||
| // A Span is a horizontal segment of pixels with constant alpha. X0 is an | ||||
| // inclusive bound and X1 is exclusive, the same as for slices. A fully opaque | ||||
| // Span has Alpha == 0xffff. | ||||
| type Span struct { | ||||
| 	Y, X0, X1 int | ||||
| 	Alpha     uint32 | ||||
| } | ||||
|  | ||||
| // A Painter knows how to paint a batch of Spans. Rasterization may involve | ||||
| // Painting multiple batches, and done will be true for the final batch. The | ||||
| // Spans' Y values are monotonically increasing during a rasterization. Paint | ||||
| // may use all of ss as scratch space during the call. | ||||
| type Painter interface { | ||||
| 	Paint(ss []Span, done bool) | ||||
| } | ||||
|  | ||||
| // The PainterFunc type adapts an ordinary function to the Painter interface. | ||||
| type PainterFunc func(ss []Span, done bool) | ||||
|  | ||||
| // Paint just delegates the call to f. | ||||
| func (f PainterFunc) Paint(ss []Span, done bool) { f(ss, done) } | ||||
|  | ||||
| // An AlphaOverPainter is a Painter that paints Spans onto a *image.Alpha using | ||||
| // the Over Porter-Duff composition operator. | ||||
| type AlphaOverPainter struct { | ||||
| 	Image *image.Alpha | ||||
| } | ||||
|  | ||||
| // Paint satisfies the Painter interface. | ||||
| func (r AlphaOverPainter) Paint(ss []Span, done bool) { | ||||
| 	b := r.Image.Bounds() | ||||
| 	for _, s := range ss { | ||||
| 		if s.Y < b.Min.Y { | ||||
| 			continue | ||||
| 		} | ||||
| 		if s.Y >= b.Max.Y { | ||||
| 			return | ||||
| 		} | ||||
| 		if s.X0 < b.Min.X { | ||||
| 			s.X0 = b.Min.X | ||||
| 		} | ||||
| 		if s.X1 > b.Max.X { | ||||
| 			s.X1 = b.Max.X | ||||
| 		} | ||||
| 		if s.X0 >= s.X1 { | ||||
| 			continue | ||||
| 		} | ||||
| 		base := (s.Y-r.Image.Rect.Min.Y)*r.Image.Stride - r.Image.Rect.Min.X | ||||
| 		p := r.Image.Pix[base+s.X0 : base+s.X1] | ||||
| 		a := int(s.Alpha >> 8) | ||||
| 		for i, c := range p { | ||||
| 			v := int(c) | ||||
| 			p[i] = uint8((v*255 + (255-v)*a) / 255) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewAlphaOverPainter creates a new AlphaOverPainter for the given image. | ||||
| func NewAlphaOverPainter(m *image.Alpha) AlphaOverPainter { | ||||
| 	return AlphaOverPainter{m} | ||||
| } | ||||
|  | ||||
| // An AlphaSrcPainter is a Painter that paints Spans onto a *image.Alpha using | ||||
| // the Src Porter-Duff composition operator. | ||||
| type AlphaSrcPainter struct { | ||||
| 	Image *image.Alpha | ||||
| } | ||||
|  | ||||
| // Paint satisfies the Painter interface. | ||||
| func (r AlphaSrcPainter) Paint(ss []Span, done bool) { | ||||
| 	b := r.Image.Bounds() | ||||
| 	for _, s := range ss { | ||||
| 		if s.Y < b.Min.Y { | ||||
| 			continue | ||||
| 		} | ||||
| 		if s.Y >= b.Max.Y { | ||||
| 			return | ||||
| 		} | ||||
| 		if s.X0 < b.Min.X { | ||||
| 			s.X0 = b.Min.X | ||||
| 		} | ||||
| 		if s.X1 > b.Max.X { | ||||
| 			s.X1 = b.Max.X | ||||
| 		} | ||||
| 		if s.X0 >= s.X1 { | ||||
| 			continue | ||||
| 		} | ||||
| 		base := (s.Y-r.Image.Rect.Min.Y)*r.Image.Stride - r.Image.Rect.Min.X | ||||
| 		p := r.Image.Pix[base+s.X0 : base+s.X1] | ||||
| 		color := uint8(s.Alpha >> 8) | ||||
| 		for i := range p { | ||||
| 			p[i] = color | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewAlphaSrcPainter creates a new AlphaSrcPainter for the given image. | ||||
| func NewAlphaSrcPainter(m *image.Alpha) AlphaSrcPainter { | ||||
| 	return AlphaSrcPainter{m} | ||||
| } | ||||
|  | ||||
| // An RGBAPainter is a Painter that paints Spans onto a *image.RGBA. | ||||
| type RGBAPainter struct { | ||||
| 	// Image is the image to compose onto. | ||||
| 	Image *image.RGBA | ||||
| 	// Op is the Porter-Duff composition operator. | ||||
| 	Op draw.Op | ||||
| 	// cr, cg, cb and ca are the 16-bit color to paint the spans. | ||||
| 	cr, cg, cb, ca uint32 | ||||
| } | ||||
|  | ||||
| // Paint satisfies the Painter interface. | ||||
| func (r *RGBAPainter) Paint(ss []Span, done bool) { | ||||
| 	b := r.Image.Bounds() | ||||
| 	for _, s := range ss { | ||||
| 		if s.Y < b.Min.Y { | ||||
| 			continue | ||||
| 		} | ||||
| 		if s.Y >= b.Max.Y { | ||||
| 			return | ||||
| 		} | ||||
| 		if s.X0 < b.Min.X { | ||||
| 			s.X0 = b.Min.X | ||||
| 		} | ||||
| 		if s.X1 > b.Max.X { | ||||
| 			s.X1 = b.Max.X | ||||
| 		} | ||||
| 		if s.X0 >= s.X1 { | ||||
| 			continue | ||||
| 		} | ||||
| 		// This code mimics drawGlyphOver in $GOROOT/src/image/draw/draw.go. | ||||
| 		ma := s.Alpha | ||||
| 		const m = 1<<16 - 1 | ||||
| 		i0 := (s.Y-r.Image.Rect.Min.Y)*r.Image.Stride + (s.X0-r.Image.Rect.Min.X)*4 | ||||
| 		i1 := i0 + (s.X1-s.X0)*4 | ||||
| 		if r.Op == draw.Over { | ||||
| 			for i := i0; i < i1; i += 4 { | ||||
| 				dr := uint32(r.Image.Pix[i+0]) | ||||
| 				dg := uint32(r.Image.Pix[i+1]) | ||||
| 				db := uint32(r.Image.Pix[i+2]) | ||||
| 				da := uint32(r.Image.Pix[i+3]) | ||||
| 				a := (m - (r.ca * ma / m)) * 0x101 | ||||
| 				r.Image.Pix[i+0] = uint8((dr*a + r.cr*ma) / m >> 8) | ||||
| 				r.Image.Pix[i+1] = uint8((dg*a + r.cg*ma) / m >> 8) | ||||
| 				r.Image.Pix[i+2] = uint8((db*a + r.cb*ma) / m >> 8) | ||||
| 				r.Image.Pix[i+3] = uint8((da*a + r.ca*ma) / m >> 8) | ||||
| 			} | ||||
| 		} else { | ||||
| 			for i := i0; i < i1; i += 4 { | ||||
| 				r.Image.Pix[i+0] = uint8(r.cr * ma / m >> 8) | ||||
| 				r.Image.Pix[i+1] = uint8(r.cg * ma / m >> 8) | ||||
| 				r.Image.Pix[i+2] = uint8(r.cb * ma / m >> 8) | ||||
| 				r.Image.Pix[i+3] = uint8(r.ca * ma / m >> 8) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // SetColor sets the color to paint the spans. | ||||
| func (r *RGBAPainter) SetColor(c color.Color) { | ||||
| 	r.cr, r.cg, r.cb, r.ca = c.RGBA() | ||||
| } | ||||
|  | ||||
| // NewRGBAPainter creates a new RGBAPainter for the given image. | ||||
| func NewRGBAPainter(m *image.RGBA) *RGBAPainter { | ||||
| 	return &RGBAPainter{Image: m} | ||||
| } | ||||
|  | ||||
| // A MonochromePainter wraps another Painter, quantizing each Span's alpha to | ||||
| // be either fully opaque or fully transparent. | ||||
| type MonochromePainter struct { | ||||
| 	Painter   Painter | ||||
| 	y, x0, x1 int | ||||
| } | ||||
|  | ||||
| // Paint delegates to the wrapped Painter after quantizing each Span's alpha | ||||
| // value and merging adjacent fully opaque Spans. | ||||
| func (m *MonochromePainter) Paint(ss []Span, done bool) { | ||||
| 	// We compact the ss slice, discarding any Spans whose alpha quantizes to zero. | ||||
| 	j := 0 | ||||
| 	for _, s := range ss { | ||||
| 		if s.Alpha >= 0x8000 { | ||||
| 			if m.y == s.Y && m.x1 == s.X0 { | ||||
| 				m.x1 = s.X1 | ||||
| 			} else { | ||||
| 				ss[j] = Span{m.y, m.x0, m.x1, 1<<16 - 1} | ||||
| 				j++ | ||||
| 				m.y, m.x0, m.x1 = s.Y, s.X0, s.X1 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	if done { | ||||
| 		// Flush the accumulated Span. | ||||
| 		finalSpan := Span{m.y, m.x0, m.x1, 1<<16 - 1} | ||||
| 		if j < len(ss) { | ||||
| 			ss[j] = finalSpan | ||||
| 			j++ | ||||
| 			m.Painter.Paint(ss[:j], true) | ||||
| 		} else if j == len(ss) { | ||||
| 			m.Painter.Paint(ss, false) | ||||
| 			if cap(ss) > 0 { | ||||
| 				ss = ss[:1] | ||||
| 			} else { | ||||
| 				ss = make([]Span, 1) | ||||
| 			} | ||||
| 			ss[0] = finalSpan | ||||
| 			m.Painter.Paint(ss, true) | ||||
| 		} else { | ||||
| 			panic("unreachable") | ||||
| 		} | ||||
| 		// Reset the accumulator, so that this Painter can be re-used. | ||||
| 		m.y, m.x0, m.x1 = 0, 0, 0 | ||||
| 	} else { | ||||
| 		m.Painter.Paint(ss[:j], false) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewMonochromePainter creates a new MonochromePainter that wraps the given | ||||
| // Painter. | ||||
| func NewMonochromePainter(p Painter) *MonochromePainter { | ||||
| 	return &MonochromePainter{Painter: p} | ||||
| } | ||||
|  | ||||
| // A GammaCorrectionPainter wraps another Painter, performing gamma-correction | ||||
| // on each Span's alpha value. | ||||
| type GammaCorrectionPainter struct { | ||||
| 	// Painter is the wrapped Painter. | ||||
| 	Painter Painter | ||||
| 	// a is the precomputed alpha values for linear interpolation, with fully | ||||
| 	// opaque == 0xffff. | ||||
| 	a [256]uint16 | ||||
| 	// gammaIsOne is whether gamma correction is a no-op. | ||||
| 	gammaIsOne bool | ||||
| } | ||||
|  | ||||
| // Paint delegates to the wrapped Painter after performing gamma-correction on | ||||
| // each Span. | ||||
| func (g *GammaCorrectionPainter) Paint(ss []Span, done bool) { | ||||
| 	if !g.gammaIsOne { | ||||
| 		const n = 0x101 | ||||
| 		for i, s := range ss { | ||||
| 			if s.Alpha == 0 || s.Alpha == 0xffff { | ||||
| 				continue | ||||
| 			} | ||||
| 			p, q := s.Alpha/n, s.Alpha%n | ||||
| 			// The resultant alpha is a linear interpolation of g.a[p] and g.a[p+1]. | ||||
| 			a := uint32(g.a[p])*(n-q) + uint32(g.a[p+1])*q | ||||
| 			ss[i].Alpha = (a + n/2) / n | ||||
| 		} | ||||
| 	} | ||||
| 	g.Painter.Paint(ss, done) | ||||
| } | ||||
|  | ||||
| // SetGamma sets the gamma value. | ||||
| func (g *GammaCorrectionPainter) SetGamma(gamma float64) { | ||||
| 	g.gammaIsOne = gamma == 1 | ||||
| 	if g.gammaIsOne { | ||||
| 		return | ||||
| 	} | ||||
| 	for i := 0; i < 256; i++ { | ||||
| 		a := float64(i) / 0xff | ||||
| 		a = math.Pow(a, gamma) | ||||
| 		g.a[i] = uint16(0xffff * a) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewGammaCorrectionPainter creates a new GammaCorrectionPainter that wraps | ||||
| // the given Painter. | ||||
| func NewGammaCorrectionPainter(p Painter, gamma float64) *GammaCorrectionPainter { | ||||
| 	g := &GammaCorrectionPainter{Painter: p} | ||||
| 	g.SetGamma(gamma) | ||||
| 	return g | ||||
| } | ||||
							
								
								
									
										601
									
								
								vendor/github.com/golang/freetype/raster/raster.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										601
									
								
								vendor/github.com/golang/freetype/raster/raster.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,601 @@ | ||||
| // Copyright 2010 The Freetype-Go Authors. All rights reserved. | ||||
| // Use of this source code is governed by your choice of either the | ||||
| // FreeType License or the GNU General Public License version 2 (or | ||||
| // any later version), both of which can be found in the LICENSE file. | ||||
|  | ||||
| // Package raster provides an anti-aliasing 2-D rasterizer. | ||||
| // | ||||
| // It is part of the larger Freetype suite of font-related packages, but the | ||||
| // raster package is not specific to font rasterization, and can be used | ||||
| // standalone without any other Freetype package. | ||||
| // | ||||
| // Rasterization is done by the same area/coverage accumulation algorithm as | ||||
| // the Freetype "smooth" module, and the Anti-Grain Geometry library. A | ||||
| // description of the area/coverage algorithm is at | ||||
| // http://projects.tuxee.net/cl-vectors/section-the-cl-aa-algorithm | ||||
| package raster // import "github.com/golang/freetype/raster" | ||||
|  | ||||
| import ( | ||||
| 	"strconv" | ||||
|  | ||||
| 	"golang.org/x/image/math/fixed" | ||||
| ) | ||||
|  | ||||
| // A cell is part of a linked list (for a given yi co-ordinate) of accumulated | ||||
| // area/coverage for the pixel at (xi, yi). | ||||
| type cell struct { | ||||
| 	xi          int | ||||
| 	area, cover int | ||||
| 	next        int | ||||
| } | ||||
|  | ||||
| type Rasterizer struct { | ||||
| 	// If false, the default behavior is to use the even-odd winding fill | ||||
| 	// rule during Rasterize. | ||||
| 	UseNonZeroWinding bool | ||||
| 	// An offset (in pixels) to the painted spans. | ||||
| 	Dx, Dy int | ||||
|  | ||||
| 	// The width of the Rasterizer. The height is implicit in len(cellIndex). | ||||
| 	width int | ||||
| 	// splitScaleN is the scaling factor used to determine how many times | ||||
| 	// to decompose a quadratic or cubic segment into a linear approximation. | ||||
| 	splitScale2, splitScale3 int | ||||
|  | ||||
| 	// The current pen position. | ||||
| 	a fixed.Point26_6 | ||||
| 	// The current cell and its area/coverage being accumulated. | ||||
| 	xi, yi      int | ||||
| 	area, cover int | ||||
|  | ||||
| 	// Saved cells. | ||||
| 	cell []cell | ||||
| 	// Linked list of cells, one per row. | ||||
| 	cellIndex []int | ||||
| 	// Buffers. | ||||
| 	cellBuf      [256]cell | ||||
| 	cellIndexBuf [64]int | ||||
| 	spanBuf      [64]Span | ||||
| } | ||||
|  | ||||
| // findCell returns the index in r.cell for the cell corresponding to | ||||
| // (r.xi, r.yi). The cell is created if necessary. | ||||
| func (r *Rasterizer) findCell() int { | ||||
| 	if r.yi < 0 || r.yi >= len(r.cellIndex) { | ||||
| 		return -1 | ||||
| 	} | ||||
| 	xi := r.xi | ||||
| 	if xi < 0 { | ||||
| 		xi = -1 | ||||
| 	} else if xi > r.width { | ||||
| 		xi = r.width | ||||
| 	} | ||||
| 	i, prev := r.cellIndex[r.yi], -1 | ||||
| 	for i != -1 && r.cell[i].xi <= xi { | ||||
| 		if r.cell[i].xi == xi { | ||||
| 			return i | ||||
| 		} | ||||
| 		i, prev = r.cell[i].next, i | ||||
| 	} | ||||
| 	c := len(r.cell) | ||||
| 	if c == cap(r.cell) { | ||||
| 		buf := make([]cell, c, 4*c) | ||||
| 		copy(buf, r.cell) | ||||
| 		r.cell = buf[0 : c+1] | ||||
| 	} else { | ||||
| 		r.cell = r.cell[0 : c+1] | ||||
| 	} | ||||
| 	r.cell[c] = cell{xi, 0, 0, i} | ||||
| 	if prev == -1 { | ||||
| 		r.cellIndex[r.yi] = c | ||||
| 	} else { | ||||
| 		r.cell[prev].next = c | ||||
| 	} | ||||
| 	return c | ||||
| } | ||||
|  | ||||
| // saveCell saves any accumulated r.area/r.cover for (r.xi, r.yi). | ||||
| func (r *Rasterizer) saveCell() { | ||||
| 	if r.area != 0 || r.cover != 0 { | ||||
| 		i := r.findCell() | ||||
| 		if i != -1 { | ||||
| 			r.cell[i].area += r.area | ||||
| 			r.cell[i].cover += r.cover | ||||
| 		} | ||||
| 		r.area = 0 | ||||
| 		r.cover = 0 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // setCell sets the (xi, yi) cell that r is accumulating area/coverage for. | ||||
| func (r *Rasterizer) setCell(xi, yi int) { | ||||
| 	if r.xi != xi || r.yi != yi { | ||||
| 		r.saveCell() | ||||
| 		r.xi, r.yi = xi, yi | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // scan accumulates area/coverage for the yi'th scanline, going from | ||||
| // x0 to x1 in the horizontal direction (in 26.6 fixed point co-ordinates) | ||||
| // and from y0f to y1f fractional vertical units within that scanline. | ||||
| func (r *Rasterizer) scan(yi int, x0, y0f, x1, y1f fixed.Int26_6) { | ||||
| 	// Break the 26.6 fixed point X co-ordinates into integral and fractional parts. | ||||
| 	x0i := int(x0) / 64 | ||||
| 	x0f := x0 - fixed.Int26_6(64*x0i) | ||||
| 	x1i := int(x1) / 64 | ||||
| 	x1f := x1 - fixed.Int26_6(64*x1i) | ||||
|  | ||||
| 	// A perfectly horizontal scan. | ||||
| 	if y0f == y1f { | ||||
| 		r.setCell(x1i, yi) | ||||
| 		return | ||||
| 	} | ||||
| 	dx, dy := x1-x0, y1f-y0f | ||||
| 	// A single cell scan. | ||||
| 	if x0i == x1i { | ||||
| 		r.area += int((x0f + x1f) * dy) | ||||
| 		r.cover += int(dy) | ||||
| 		return | ||||
| 	} | ||||
| 	// There are at least two cells. Apart from the first and last cells, | ||||
| 	// all intermediate cells go through the full width of the cell, | ||||
| 	// or 64 units in 26.6 fixed point format. | ||||
| 	var ( | ||||
| 		p, q, edge0, edge1 fixed.Int26_6 | ||||
| 		xiDelta            int | ||||
| 	) | ||||
| 	if dx > 0 { | ||||
| 		p, q = (64-x0f)*dy, dx | ||||
| 		edge0, edge1, xiDelta = 0, 64, 1 | ||||
| 	} else { | ||||
| 		p, q = x0f*dy, -dx | ||||
| 		edge0, edge1, xiDelta = 64, 0, -1 | ||||
| 	} | ||||
| 	yDelta, yRem := p/q, p%q | ||||
| 	if yRem < 0 { | ||||
| 		yDelta -= 1 | ||||
| 		yRem += q | ||||
| 	} | ||||
| 	// Do the first cell. | ||||
| 	xi, y := x0i, y0f | ||||
| 	r.area += int((x0f + edge1) * yDelta) | ||||
| 	r.cover += int(yDelta) | ||||
| 	xi, y = xi+xiDelta, y+yDelta | ||||
| 	r.setCell(xi, yi) | ||||
| 	if xi != x1i { | ||||
| 		// Do all the intermediate cells. | ||||
| 		p = 64 * (y1f - y + yDelta) | ||||
| 		fullDelta, fullRem := p/q, p%q | ||||
| 		if fullRem < 0 { | ||||
| 			fullDelta -= 1 | ||||
| 			fullRem += q | ||||
| 		} | ||||
| 		yRem -= q | ||||
| 		for xi != x1i { | ||||
| 			yDelta = fullDelta | ||||
| 			yRem += fullRem | ||||
| 			if yRem >= 0 { | ||||
| 				yDelta += 1 | ||||
| 				yRem -= q | ||||
| 			} | ||||
| 			r.area += int(64 * yDelta) | ||||
| 			r.cover += int(yDelta) | ||||
| 			xi, y = xi+xiDelta, y+yDelta | ||||
| 			r.setCell(xi, yi) | ||||
| 		} | ||||
| 	} | ||||
| 	// Do the last cell. | ||||
| 	yDelta = y1f - y | ||||
| 	r.area += int((edge0 + x1f) * yDelta) | ||||
| 	r.cover += int(yDelta) | ||||
| } | ||||
|  | ||||
| // Start starts a new curve at the given point. | ||||
| func (r *Rasterizer) Start(a fixed.Point26_6) { | ||||
| 	r.setCell(int(a.X/64), int(a.Y/64)) | ||||
| 	r.a = a | ||||
| } | ||||
|  | ||||
| // Add1 adds a linear segment to the current curve. | ||||
| func (r *Rasterizer) Add1(b fixed.Point26_6) { | ||||
| 	x0, y0 := r.a.X, r.a.Y | ||||
| 	x1, y1 := b.X, b.Y | ||||
| 	dx, dy := x1-x0, y1-y0 | ||||
| 	// Break the 26.6 fixed point Y co-ordinates into integral and fractional | ||||
| 	// parts. | ||||
| 	y0i := int(y0) / 64 | ||||
| 	y0f := y0 - fixed.Int26_6(64*y0i) | ||||
| 	y1i := int(y1) / 64 | ||||
| 	y1f := y1 - fixed.Int26_6(64*y1i) | ||||
|  | ||||
| 	if y0i == y1i { | ||||
| 		// There is only one scanline. | ||||
| 		r.scan(y0i, x0, y0f, x1, y1f) | ||||
|  | ||||
| 	} else if dx == 0 { | ||||
| 		// This is a vertical line segment. We avoid calling r.scan and instead | ||||
| 		// manipulate r.area and r.cover directly. | ||||
| 		var ( | ||||
| 			edge0, edge1 fixed.Int26_6 | ||||
| 			yiDelta      int | ||||
| 		) | ||||
| 		if dy > 0 { | ||||
| 			edge0, edge1, yiDelta = 0, 64, 1 | ||||
| 		} else { | ||||
| 			edge0, edge1, yiDelta = 64, 0, -1 | ||||
| 		} | ||||
| 		x0i, yi := int(x0)/64, y0i | ||||
| 		x0fTimes2 := (int(x0) - (64 * x0i)) * 2 | ||||
| 		// Do the first pixel. | ||||
| 		dcover := int(edge1 - y0f) | ||||
| 		darea := int(x0fTimes2 * dcover) | ||||
| 		r.area += darea | ||||
| 		r.cover += dcover | ||||
| 		yi += yiDelta | ||||
| 		r.setCell(x0i, yi) | ||||
| 		// Do all the intermediate pixels. | ||||
| 		dcover = int(edge1 - edge0) | ||||
| 		darea = int(x0fTimes2 * dcover) | ||||
| 		for yi != y1i { | ||||
| 			r.area += darea | ||||
| 			r.cover += dcover | ||||
| 			yi += yiDelta | ||||
| 			r.setCell(x0i, yi) | ||||
| 		} | ||||
| 		// Do the last pixel. | ||||
| 		dcover = int(y1f - edge0) | ||||
| 		darea = int(x0fTimes2 * dcover) | ||||
| 		r.area += darea | ||||
| 		r.cover += dcover | ||||
|  | ||||
| 	} else { | ||||
| 		// There are at least two scanlines. Apart from the first and last | ||||
| 		// scanlines, all intermediate scanlines go through the full height of | ||||
| 		// the row, or 64 units in 26.6 fixed point format. | ||||
| 		var ( | ||||
| 			p, q, edge0, edge1 fixed.Int26_6 | ||||
| 			yiDelta            int | ||||
| 		) | ||||
| 		if dy > 0 { | ||||
| 			p, q = (64-y0f)*dx, dy | ||||
| 			edge0, edge1, yiDelta = 0, 64, 1 | ||||
| 		} else { | ||||
| 			p, q = y0f*dx, -dy | ||||
| 			edge0, edge1, yiDelta = 64, 0, -1 | ||||
| 		} | ||||
| 		xDelta, xRem := p/q, p%q | ||||
| 		if xRem < 0 { | ||||
| 			xDelta -= 1 | ||||
| 			xRem += q | ||||
| 		} | ||||
| 		// Do the first scanline. | ||||
| 		x, yi := x0, y0i | ||||
| 		r.scan(yi, x, y0f, x+xDelta, edge1) | ||||
| 		x, yi = x+xDelta, yi+yiDelta | ||||
| 		r.setCell(int(x)/64, yi) | ||||
| 		if yi != y1i { | ||||
| 			// Do all the intermediate scanlines. | ||||
| 			p = 64 * dx | ||||
| 			fullDelta, fullRem := p/q, p%q | ||||
| 			if fullRem < 0 { | ||||
| 				fullDelta -= 1 | ||||
| 				fullRem += q | ||||
| 			} | ||||
| 			xRem -= q | ||||
| 			for yi != y1i { | ||||
| 				xDelta = fullDelta | ||||
| 				xRem += fullRem | ||||
| 				if xRem >= 0 { | ||||
| 					xDelta += 1 | ||||
| 					xRem -= q | ||||
| 				} | ||||
| 				r.scan(yi, x, edge0, x+xDelta, edge1) | ||||
| 				x, yi = x+xDelta, yi+yiDelta | ||||
| 				r.setCell(int(x)/64, yi) | ||||
| 			} | ||||
| 		} | ||||
| 		// Do the last scanline. | ||||
| 		r.scan(yi, x, edge0, x1, y1f) | ||||
| 	} | ||||
| 	// The next lineTo starts from b. | ||||
| 	r.a = b | ||||
| } | ||||
|  | ||||
| // Add2 adds a quadratic segment to the current curve. | ||||
| func (r *Rasterizer) Add2(b, c fixed.Point26_6) { | ||||
| 	// Calculate nSplit (the number of recursive decompositions) based on how | ||||
| 	// 'curvy' it is. Specifically, how much the middle point b deviates from | ||||
| 	// (a+c)/2. | ||||
| 	dev := maxAbs(r.a.X-2*b.X+c.X, r.a.Y-2*b.Y+c.Y) / fixed.Int26_6(r.splitScale2) | ||||
| 	nsplit := 0 | ||||
| 	for dev > 0 { | ||||
| 		dev /= 4 | ||||
| 		nsplit++ | ||||
| 	} | ||||
| 	// dev is 32-bit, and nsplit++ every time we shift off 2 bits, so maxNsplit | ||||
| 	// is 16. | ||||
| 	const maxNsplit = 16 | ||||
| 	if nsplit > maxNsplit { | ||||
| 		panic("freetype/raster: Add2 nsplit too large: " + strconv.Itoa(nsplit)) | ||||
| 	} | ||||
| 	// Recursively decompose the curve nSplit levels deep. | ||||
| 	var ( | ||||
| 		pStack [2*maxNsplit + 3]fixed.Point26_6 | ||||
| 		sStack [maxNsplit + 1]int | ||||
| 		i      int | ||||
| 	) | ||||
| 	sStack[0] = nsplit | ||||
| 	pStack[0] = c | ||||
| 	pStack[1] = b | ||||
| 	pStack[2] = r.a | ||||
| 	for i >= 0 { | ||||
| 		s := sStack[i] | ||||
| 		p := pStack[2*i:] | ||||
| 		if s > 0 { | ||||
| 			// Split the quadratic curve p[:3] into an equivalent set of two | ||||
| 			// shorter curves: p[:3] and p[2:5]. The new p[4] is the old p[2], | ||||
| 			// and p[0] is unchanged. | ||||
| 			mx := p[1].X | ||||
| 			p[4].X = p[2].X | ||||
| 			p[3].X = (p[4].X + mx) / 2 | ||||
| 			p[1].X = (p[0].X + mx) / 2 | ||||
| 			p[2].X = (p[1].X + p[3].X) / 2 | ||||
| 			my := p[1].Y | ||||
| 			p[4].Y = p[2].Y | ||||
| 			p[3].Y = (p[4].Y + my) / 2 | ||||
| 			p[1].Y = (p[0].Y + my) / 2 | ||||
| 			p[2].Y = (p[1].Y + p[3].Y) / 2 | ||||
| 			// The two shorter curves have one less split to do. | ||||
| 			sStack[i] = s - 1 | ||||
| 			sStack[i+1] = s - 1 | ||||
| 			i++ | ||||
| 		} else { | ||||
| 			// Replace the level-0 quadratic with a two-linear-piece | ||||
| 			// approximation. | ||||
| 			midx := (p[0].X + 2*p[1].X + p[2].X) / 4 | ||||
| 			midy := (p[0].Y + 2*p[1].Y + p[2].Y) / 4 | ||||
| 			r.Add1(fixed.Point26_6{midx, midy}) | ||||
| 			r.Add1(p[0]) | ||||
| 			i-- | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Add3 adds a cubic segment to the current curve. | ||||
| func (r *Rasterizer) Add3(b, c, d fixed.Point26_6) { | ||||
| 	// Calculate nSplit (the number of recursive decompositions) based on how | ||||
| 	// 'curvy' it is. | ||||
| 	dev2 := maxAbs(r.a.X-3*(b.X+c.X)+d.X, r.a.Y-3*(b.Y+c.Y)+d.Y) / fixed.Int26_6(r.splitScale2) | ||||
| 	dev3 := maxAbs(r.a.X-2*b.X+d.X, r.a.Y-2*b.Y+d.Y) / fixed.Int26_6(r.splitScale3) | ||||
| 	nsplit := 0 | ||||
| 	for dev2 > 0 || dev3 > 0 { | ||||
| 		dev2 /= 8 | ||||
| 		dev3 /= 4 | ||||
| 		nsplit++ | ||||
| 	} | ||||
| 	// devN is 32-bit, and nsplit++ every time we shift off 2 bits, so | ||||
| 	// maxNsplit is 16. | ||||
| 	const maxNsplit = 16 | ||||
| 	if nsplit > maxNsplit { | ||||
| 		panic("freetype/raster: Add3 nsplit too large: " + strconv.Itoa(nsplit)) | ||||
| 	} | ||||
| 	// Recursively decompose the curve nSplit levels deep. | ||||
| 	var ( | ||||
| 		pStack [3*maxNsplit + 4]fixed.Point26_6 | ||||
| 		sStack [maxNsplit + 1]int | ||||
| 		i      int | ||||
| 	) | ||||
| 	sStack[0] = nsplit | ||||
| 	pStack[0] = d | ||||
| 	pStack[1] = c | ||||
| 	pStack[2] = b | ||||
| 	pStack[3] = r.a | ||||
| 	for i >= 0 { | ||||
| 		s := sStack[i] | ||||
| 		p := pStack[3*i:] | ||||
| 		if s > 0 { | ||||
| 			// Split the cubic curve p[:4] into an equivalent set of two | ||||
| 			// shorter curves: p[:4] and p[3:7]. The new p[6] is the old p[3], | ||||
| 			// and p[0] is unchanged. | ||||
| 			m01x := (p[0].X + p[1].X) / 2 | ||||
| 			m12x := (p[1].X + p[2].X) / 2 | ||||
| 			m23x := (p[2].X + p[3].X) / 2 | ||||
| 			p[6].X = p[3].X | ||||
| 			p[5].X = m23x | ||||
| 			p[1].X = m01x | ||||
| 			p[2].X = (m01x + m12x) / 2 | ||||
| 			p[4].X = (m12x + m23x) / 2 | ||||
| 			p[3].X = (p[2].X + p[4].X) / 2 | ||||
| 			m01y := (p[0].Y + p[1].Y) / 2 | ||||
| 			m12y := (p[1].Y + p[2].Y) / 2 | ||||
| 			m23y := (p[2].Y + p[3].Y) / 2 | ||||
| 			p[6].Y = p[3].Y | ||||
| 			p[5].Y = m23y | ||||
| 			p[1].Y = m01y | ||||
| 			p[2].Y = (m01y + m12y) / 2 | ||||
| 			p[4].Y = (m12y + m23y) / 2 | ||||
| 			p[3].Y = (p[2].Y + p[4].Y) / 2 | ||||
| 			// The two shorter curves have one less split to do. | ||||
| 			sStack[i] = s - 1 | ||||
| 			sStack[i+1] = s - 1 | ||||
| 			i++ | ||||
| 		} else { | ||||
| 			// Replace the level-0 cubic with a two-linear-piece approximation. | ||||
| 			midx := (p[0].X + 3*(p[1].X+p[2].X) + p[3].X) / 8 | ||||
| 			midy := (p[0].Y + 3*(p[1].Y+p[2].Y) + p[3].Y) / 8 | ||||
| 			r.Add1(fixed.Point26_6{midx, midy}) | ||||
| 			r.Add1(p[0]) | ||||
| 			i-- | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // AddPath adds the given Path. | ||||
| func (r *Rasterizer) AddPath(p Path) { | ||||
| 	for i := 0; i < len(p); { | ||||
| 		switch p[i] { | ||||
| 		case 0: | ||||
| 			r.Start( | ||||
| 				fixed.Point26_6{p[i+1], p[i+2]}, | ||||
| 			) | ||||
| 			i += 4 | ||||
| 		case 1: | ||||
| 			r.Add1( | ||||
| 				fixed.Point26_6{p[i+1], p[i+2]}, | ||||
| 			) | ||||
| 			i += 4 | ||||
| 		case 2: | ||||
| 			r.Add2( | ||||
| 				fixed.Point26_6{p[i+1], p[i+2]}, | ||||
| 				fixed.Point26_6{p[i+3], p[i+4]}, | ||||
| 			) | ||||
| 			i += 6 | ||||
| 		case 3: | ||||
| 			r.Add3( | ||||
| 				fixed.Point26_6{p[i+1], p[i+2]}, | ||||
| 				fixed.Point26_6{p[i+3], p[i+4]}, | ||||
| 				fixed.Point26_6{p[i+5], p[i+6]}, | ||||
| 			) | ||||
| 			i += 8 | ||||
| 		default: | ||||
| 			panic("freetype/raster: bad path") | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // AddStroke adds a stroked Path. | ||||
| func (r *Rasterizer) AddStroke(q Path, width fixed.Int26_6, cr Capper, jr Joiner) { | ||||
| 	Stroke(r, q, width, cr, jr) | ||||
| } | ||||
|  | ||||
| // areaToAlpha converts an area value to a uint32 alpha value. A completely | ||||
| // filled pixel corresponds to an area of 64*64*2, and an alpha of 0xffff. The | ||||
| // conversion of area values greater than this depends on the winding rule: | ||||
| // even-odd or non-zero. | ||||
| func (r *Rasterizer) areaToAlpha(area int) uint32 { | ||||
| 	// The C Freetype implementation (version 2.3.12) does "alpha := area>>1" | ||||
| 	// without the +1. Round-to-nearest gives a more symmetric result than | ||||
| 	// round-down. The C implementation also returns 8-bit alpha, not 16-bit | ||||
| 	// alpha. | ||||
| 	a := (area + 1) >> 1 | ||||
| 	if a < 0 { | ||||
| 		a = -a | ||||
| 	} | ||||
| 	alpha := uint32(a) | ||||
| 	if r.UseNonZeroWinding { | ||||
| 		if alpha > 0x0fff { | ||||
| 			alpha = 0x0fff | ||||
| 		} | ||||
| 	} else { | ||||
| 		alpha &= 0x1fff | ||||
| 		if alpha > 0x1000 { | ||||
| 			alpha = 0x2000 - alpha | ||||
| 		} else if alpha == 0x1000 { | ||||
| 			alpha = 0x0fff | ||||
| 		} | ||||
| 	} | ||||
| 	// alpha is now in the range [0x0000, 0x0fff]. Convert that 12-bit alpha to | ||||
| 	// 16-bit alpha. | ||||
| 	return alpha<<4 | alpha>>8 | ||||
| } | ||||
|  | ||||
| // Rasterize converts r's accumulated curves into Spans for p. The Spans passed | ||||
| // to p are non-overlapping, and sorted by Y and then X. They all have non-zero | ||||
| // width (and 0 <= X0 < X1 <= r.width) and non-zero A, except for the final | ||||
| // Span, which has Y, X0, X1 and A all equal to zero. | ||||
| func (r *Rasterizer) Rasterize(p Painter) { | ||||
| 	r.saveCell() | ||||
| 	s := 0 | ||||
| 	for yi := 0; yi < len(r.cellIndex); yi++ { | ||||
| 		xi, cover := 0, 0 | ||||
| 		for c := r.cellIndex[yi]; c != -1; c = r.cell[c].next { | ||||
| 			if cover != 0 && r.cell[c].xi > xi { | ||||
| 				alpha := r.areaToAlpha(cover * 64 * 2) | ||||
| 				if alpha != 0 { | ||||
| 					xi0, xi1 := xi, r.cell[c].xi | ||||
| 					if xi0 < 0 { | ||||
| 						xi0 = 0 | ||||
| 					} | ||||
| 					if xi1 >= r.width { | ||||
| 						xi1 = r.width | ||||
| 					} | ||||
| 					if xi0 < xi1 { | ||||
| 						r.spanBuf[s] = Span{yi + r.Dy, xi0 + r.Dx, xi1 + r.Dx, alpha} | ||||
| 						s++ | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			cover += r.cell[c].cover | ||||
| 			alpha := r.areaToAlpha(cover*64*2 - r.cell[c].area) | ||||
| 			xi = r.cell[c].xi + 1 | ||||
| 			if alpha != 0 { | ||||
| 				xi0, xi1 := r.cell[c].xi, xi | ||||
| 				if xi0 < 0 { | ||||
| 					xi0 = 0 | ||||
| 				} | ||||
| 				if xi1 >= r.width { | ||||
| 					xi1 = r.width | ||||
| 				} | ||||
| 				if xi0 < xi1 { | ||||
| 					r.spanBuf[s] = Span{yi + r.Dy, xi0 + r.Dx, xi1 + r.Dx, alpha} | ||||
| 					s++ | ||||
| 				} | ||||
| 			} | ||||
| 			if s > len(r.spanBuf)-2 { | ||||
| 				p.Paint(r.spanBuf[:s], false) | ||||
| 				s = 0 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	p.Paint(r.spanBuf[:s], true) | ||||
| } | ||||
|  | ||||
| // Clear cancels any previous calls to r.Start or r.AddXxx. | ||||
| func (r *Rasterizer) Clear() { | ||||
| 	r.a = fixed.Point26_6{} | ||||
| 	r.xi = 0 | ||||
| 	r.yi = 0 | ||||
| 	r.area = 0 | ||||
| 	r.cover = 0 | ||||
| 	r.cell = r.cell[:0] | ||||
| 	for i := 0; i < len(r.cellIndex); i++ { | ||||
| 		r.cellIndex[i] = -1 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // SetBounds sets the maximum width and height of the rasterized image and | ||||
| // calls Clear. The width and height are in pixels, not fixed.Int26_6 units. | ||||
| func (r *Rasterizer) SetBounds(width, height int) { | ||||
| 	if width < 0 { | ||||
| 		width = 0 | ||||
| 	} | ||||
| 	if height < 0 { | ||||
| 		height = 0 | ||||
| 	} | ||||
| 	// Use the same ssN heuristic as the C Freetype (version 2.4.0) | ||||
| 	// implementation. | ||||
| 	ss2, ss3 := 32, 16 | ||||
| 	if width > 24 || height > 24 { | ||||
| 		ss2, ss3 = 2*ss2, 2*ss3 | ||||
| 		if width > 120 || height > 120 { | ||||
| 			ss2, ss3 = 2*ss2, 2*ss3 | ||||
| 		} | ||||
| 	} | ||||
| 	r.width = width | ||||
| 	r.splitScale2 = ss2 | ||||
| 	r.splitScale3 = ss3 | ||||
| 	r.cell = r.cellBuf[:0] | ||||
| 	if height > len(r.cellIndexBuf) { | ||||
| 		r.cellIndex = make([]int, height) | ||||
| 	} else { | ||||
| 		r.cellIndex = r.cellIndexBuf[:height] | ||||
| 	} | ||||
| 	r.Clear() | ||||
| } | ||||
|  | ||||
| // NewRasterizer creates a new Rasterizer with the given bounds. | ||||
| func NewRasterizer(width, height int) *Rasterizer { | ||||
| 	r := new(Rasterizer) | ||||
| 	r.SetBounds(width, height) | ||||
| 	return r | ||||
| } | ||||
							
								
								
									
										483
									
								
								vendor/github.com/golang/freetype/raster/stroke.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										483
									
								
								vendor/github.com/golang/freetype/raster/stroke.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,483 @@ | ||||
| // Copyright 2010 The Freetype-Go Authors. All rights reserved. | ||||
| // Use of this source code is governed by your choice of either the | ||||
| // FreeType License or the GNU General Public License version 2 (or | ||||
| // any later version), both of which can be found in the LICENSE file. | ||||
|  | ||||
| package raster | ||||
|  | ||||
| import ( | ||||
| 	"golang.org/x/image/math/fixed" | ||||
| ) | ||||
|  | ||||
| // Two points are considered practically equal if the square of the distance | ||||
| // between them is less than one quarter (i.e. 1024 / 4096). | ||||
| const epsilon = fixed.Int52_12(1024) | ||||
|  | ||||
| // A Capper signifies how to begin or end a stroked path. | ||||
| type Capper interface { | ||||
| 	// Cap adds a cap to p given a pivot point and the normal vector of a | ||||
| 	// terminal segment. The normal's length is half of the stroke width. | ||||
| 	Cap(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6) | ||||
| } | ||||
|  | ||||
| // The CapperFunc type adapts an ordinary function to be a Capper. | ||||
| type CapperFunc func(Adder, fixed.Int26_6, fixed.Point26_6, fixed.Point26_6) | ||||
|  | ||||
| func (f CapperFunc) Cap(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6) { | ||||
| 	f(p, halfWidth, pivot, n1) | ||||
| } | ||||
|  | ||||
| // A Joiner signifies how to join interior nodes of a stroked path. | ||||
| type Joiner interface { | ||||
| 	// Join adds a join to the two sides of a stroked path given a pivot | ||||
| 	// point and the normal vectors of the trailing and leading segments. | ||||
| 	// Both normals have length equal to half of the stroke width. | ||||
| 	Join(lhs, rhs Adder, halfWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6) | ||||
| } | ||||
|  | ||||
| // The JoinerFunc type adapts an ordinary function to be a Joiner. | ||||
| type JoinerFunc func(lhs, rhs Adder, halfWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6) | ||||
|  | ||||
| func (f JoinerFunc) Join(lhs, rhs Adder, halfWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6) { | ||||
| 	f(lhs, rhs, halfWidth, pivot, n0, n1) | ||||
| } | ||||
|  | ||||
| // RoundCapper adds round caps to a stroked path. | ||||
| var RoundCapper Capper = CapperFunc(roundCapper) | ||||
|  | ||||
| func roundCapper(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6) { | ||||
| 	// The cubic Bézier approximation to a circle involves the magic number | ||||
| 	// (√2 - 1) * 4/3, which is approximately 35/64. | ||||
| 	const k = 35 | ||||
| 	e := pRot90CCW(n1) | ||||
| 	side := pivot.Add(e) | ||||
| 	start, end := pivot.Sub(n1), pivot.Add(n1) | ||||
| 	d, e := n1.Mul(k), e.Mul(k) | ||||
| 	p.Add3(start.Add(e), side.Sub(d), side) | ||||
| 	p.Add3(side.Add(d), end.Add(e), end) | ||||
| } | ||||
|  | ||||
| // ButtCapper adds butt caps to a stroked path. | ||||
| var ButtCapper Capper = CapperFunc(buttCapper) | ||||
|  | ||||
| func buttCapper(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6) { | ||||
| 	p.Add1(pivot.Add(n1)) | ||||
| } | ||||
|  | ||||
| // SquareCapper adds square caps to a stroked path. | ||||
| var SquareCapper Capper = CapperFunc(squareCapper) | ||||
|  | ||||
| func squareCapper(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6) { | ||||
| 	e := pRot90CCW(n1) | ||||
| 	side := pivot.Add(e) | ||||
| 	p.Add1(side.Sub(n1)) | ||||
| 	p.Add1(side.Add(n1)) | ||||
| 	p.Add1(pivot.Add(n1)) | ||||
| } | ||||
|  | ||||
| // RoundJoiner adds round joins to a stroked path. | ||||
| var RoundJoiner Joiner = JoinerFunc(roundJoiner) | ||||
|  | ||||
| func roundJoiner(lhs, rhs Adder, haflWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6) { | ||||
| 	dot := pDot(pRot90CW(n0), n1) | ||||
| 	if dot >= 0 { | ||||
| 		addArc(lhs, pivot, n0, n1) | ||||
| 		rhs.Add1(pivot.Sub(n1)) | ||||
| 	} else { | ||||
| 		lhs.Add1(pivot.Add(n1)) | ||||
| 		addArc(rhs, pivot, pNeg(n0), pNeg(n1)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // BevelJoiner adds bevel joins to a stroked path. | ||||
| var BevelJoiner Joiner = JoinerFunc(bevelJoiner) | ||||
|  | ||||
| func bevelJoiner(lhs, rhs Adder, haflWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6) { | ||||
| 	lhs.Add1(pivot.Add(n1)) | ||||
| 	rhs.Add1(pivot.Sub(n1)) | ||||
| } | ||||
|  | ||||
| // addArc adds a circular arc from pivot+n0 to pivot+n1 to p. The shorter of | ||||
| // the two possible arcs is taken, i.e. the one spanning <= 180 degrees. The | ||||
| // two vectors n0 and n1 must be of equal length. | ||||
| func addArc(p Adder, pivot, n0, n1 fixed.Point26_6) { | ||||
| 	// r2 is the square of the length of n0. | ||||
| 	r2 := pDot(n0, n0) | ||||
| 	if r2 < epsilon { | ||||
| 		// The arc radius is so small that we collapse to a straight line. | ||||
| 		p.Add1(pivot.Add(n1)) | ||||
| 		return | ||||
| 	} | ||||
| 	// We approximate the arc by 0, 1, 2 or 3 45-degree quadratic segments plus | ||||
| 	// a final quadratic segment from s to n1. Each 45-degree segment has | ||||
| 	// control points {1, 0}, {1, tan(π/8)} and {1/√2, 1/√2} suitably scaled, | ||||
| 	// rotated and translated. tan(π/8) is approximately 27/64. | ||||
| 	const tpo8 = 27 | ||||
| 	var s fixed.Point26_6 | ||||
| 	// We determine which octant the angle between n0 and n1 is in via three | ||||
| 	// dot products. m0, m1 and m2 are n0 rotated clockwise by 45, 90 and 135 | ||||
| 	// degrees. | ||||
| 	m0 := pRot45CW(n0) | ||||
| 	m1 := pRot90CW(n0) | ||||
| 	m2 := pRot90CW(m0) | ||||
| 	if pDot(m1, n1) >= 0 { | ||||
| 		if pDot(n0, n1) >= 0 { | ||||
| 			if pDot(m2, n1) <= 0 { | ||||
| 				// n1 is between 0 and 45 degrees clockwise of n0. | ||||
| 				s = n0 | ||||
| 			} else { | ||||
| 				// n1 is between 45 and 90 degrees clockwise of n0. | ||||
| 				p.Add2(pivot.Add(n0).Add(m1.Mul(tpo8)), pivot.Add(m0)) | ||||
| 				s = m0 | ||||
| 			} | ||||
| 		} else { | ||||
| 			pm1, n0t := pivot.Add(m1), n0.Mul(tpo8) | ||||
| 			p.Add2(pivot.Add(n0).Add(m1.Mul(tpo8)), pivot.Add(m0)) | ||||
| 			p.Add2(pm1.Add(n0t), pm1) | ||||
| 			if pDot(m0, n1) >= 0 { | ||||
| 				// n1 is between 90 and 135 degrees clockwise of n0. | ||||
| 				s = m1 | ||||
| 			} else { | ||||
| 				// n1 is between 135 and 180 degrees clockwise of n0. | ||||
| 				p.Add2(pm1.Sub(n0t), pivot.Add(m2)) | ||||
| 				s = m2 | ||||
| 			} | ||||
| 		} | ||||
| 	} else { | ||||
| 		if pDot(n0, n1) >= 0 { | ||||
| 			if pDot(m0, n1) >= 0 { | ||||
| 				// n1 is between 0 and 45 degrees counter-clockwise of n0. | ||||
| 				s = n0 | ||||
| 			} else { | ||||
| 				// n1 is between 45 and 90 degrees counter-clockwise of n0. | ||||
| 				p.Add2(pivot.Add(n0).Sub(m1.Mul(tpo8)), pivot.Sub(m2)) | ||||
| 				s = pNeg(m2) | ||||
| 			} | ||||
| 		} else { | ||||
| 			pm1, n0t := pivot.Sub(m1), n0.Mul(tpo8) | ||||
| 			p.Add2(pivot.Add(n0).Sub(m1.Mul(tpo8)), pivot.Sub(m2)) | ||||
| 			p.Add2(pm1.Add(n0t), pm1) | ||||
| 			if pDot(m2, n1) <= 0 { | ||||
| 				// n1 is between 90 and 135 degrees counter-clockwise of n0. | ||||
| 				s = pNeg(m1) | ||||
| 			} else { | ||||
| 				// n1 is between 135 and 180 degrees counter-clockwise of n0. | ||||
| 				p.Add2(pm1.Sub(n0t), pivot.Sub(m0)) | ||||
| 				s = pNeg(m0) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	// The final quadratic segment has two endpoints s and n1 and the middle | ||||
| 	// control point is a multiple of s.Add(n1), i.e. it is on the angle | ||||
| 	// bisector of those two points. The multiple ranges between 128/256 and | ||||
| 	// 150/256 as the angle between s and n1 ranges between 0 and 45 degrees. | ||||
| 	// | ||||
| 	// When the angle is 0 degrees (i.e. s and n1 are coincident) then | ||||
| 	// s.Add(n1) is twice s and so the middle control point of the degenerate | ||||
| 	// quadratic segment should be half s.Add(n1), and half = 128/256. | ||||
| 	// | ||||
| 	// When the angle is 45 degrees then 150/256 is the ratio of the lengths of | ||||
| 	// the two vectors {1, tan(π/8)} and {1 + 1/√2, 1/√2}. | ||||
| 	// | ||||
| 	// d is the normalized dot product between s and n1. Since the angle ranges | ||||
| 	// between 0 and 45 degrees then d ranges between 256/256 and 181/256. | ||||
| 	d := 256 * pDot(s, n1) / r2 | ||||
| 	multiple := fixed.Int26_6(150-(150-128)*(d-181)/(256-181)) >> 2 | ||||
| 	p.Add2(pivot.Add(s.Add(n1).Mul(multiple)), pivot.Add(n1)) | ||||
| } | ||||
|  | ||||
| // midpoint returns the midpoint of two Points. | ||||
| func midpoint(a, b fixed.Point26_6) fixed.Point26_6 { | ||||
| 	return fixed.Point26_6{(a.X + b.X) / 2, (a.Y + b.Y) / 2} | ||||
| } | ||||
|  | ||||
| // angleGreaterThan45 returns whether the angle between two vectors is more | ||||
| // than 45 degrees. | ||||
| func angleGreaterThan45(v0, v1 fixed.Point26_6) bool { | ||||
| 	v := pRot45CCW(v0) | ||||
| 	return pDot(v, v1) < 0 || pDot(pRot90CW(v), v1) < 0 | ||||
| } | ||||
|  | ||||
| // interpolate returns the point (1-t)*a + t*b. | ||||
| func interpolate(a, b fixed.Point26_6, t fixed.Int52_12) fixed.Point26_6 { | ||||
| 	s := 1<<12 - t | ||||
| 	x := s*fixed.Int52_12(a.X) + t*fixed.Int52_12(b.X) | ||||
| 	y := s*fixed.Int52_12(a.Y) + t*fixed.Int52_12(b.Y) | ||||
| 	return fixed.Point26_6{fixed.Int26_6(x >> 12), fixed.Int26_6(y >> 12)} | ||||
| } | ||||
|  | ||||
| // curviest2 returns the value of t for which the quadratic parametric curve | ||||
| // (1-t)²*a + 2*t*(1-t).b + t²*c has maximum curvature. | ||||
| // | ||||
| // The curvature of the parametric curve f(t) = (x(t), y(t)) is | ||||
| // |x′y″-y′x″| / (x′²+y′²)^(3/2). | ||||
| // | ||||
| // Let d = b-a and e = c-2*b+a, so that f′(t) = 2*d+2*e*t and f″(t) = 2*e. | ||||
| // The curvature's numerator is (2*dx+2*ex*t)*(2*ey)-(2*dy+2*ey*t)*(2*ex), | ||||
| // which simplifies to 4*dx*ey-4*dy*ex, which is constant with respect to t. | ||||
| // | ||||
| // Thus, curvature is extreme where the denominator is extreme, i.e. where | ||||
| // (x′²+y′²) is extreme. The first order condition is that | ||||
| // 2*x′*x″+2*y′*y″ = 0, or (dx+ex*t)*ex + (dy+ey*t)*ey = 0. | ||||
| // Solving for t gives t = -(dx*ex+dy*ey) / (ex*ex+ey*ey). | ||||
| func curviest2(a, b, c fixed.Point26_6) fixed.Int52_12 { | ||||
| 	dx := int64(b.X - a.X) | ||||
| 	dy := int64(b.Y - a.Y) | ||||
| 	ex := int64(c.X - 2*b.X + a.X) | ||||
| 	ey := int64(c.Y - 2*b.Y + a.Y) | ||||
| 	if ex == 0 && ey == 0 { | ||||
| 		return 2048 | ||||
| 	} | ||||
| 	return fixed.Int52_12(-4096 * (dx*ex + dy*ey) / (ex*ex + ey*ey)) | ||||
| } | ||||
|  | ||||
| // A stroker holds state for stroking a path. | ||||
| type stroker struct { | ||||
| 	// p is the destination that records the stroked path. | ||||
| 	p Adder | ||||
| 	// u is the half-width of the stroke. | ||||
| 	u fixed.Int26_6 | ||||
| 	// cr and jr specify how to end and connect path segments. | ||||
| 	cr Capper | ||||
| 	jr Joiner | ||||
| 	// r is the reverse path. Stroking a path involves constructing two | ||||
| 	// parallel paths 2*u apart. The first path is added immediately to p, | ||||
| 	// the second path is accumulated in r and eventually added in reverse. | ||||
| 	r Path | ||||
| 	// a is the most recent segment point. anorm is the segment normal of | ||||
| 	// length u at that point. | ||||
| 	a, anorm fixed.Point26_6 | ||||
| } | ||||
|  | ||||
| // addNonCurvy2 adds a quadratic segment to the stroker, where the segment | ||||
| // defined by (k.a, b, c) achieves maximum curvature at either k.a or c. | ||||
| func (k *stroker) addNonCurvy2(b, c fixed.Point26_6) { | ||||
| 	// We repeatedly divide the segment at its middle until it is straight | ||||
| 	// enough to approximate the stroke by just translating the control points. | ||||
| 	// ds and ps are stacks of depths and points. t is the top of the stack. | ||||
| 	const maxDepth = 5 | ||||
| 	var ( | ||||
| 		ds [maxDepth + 1]int | ||||
| 		ps [2*maxDepth + 3]fixed.Point26_6 | ||||
| 		t  int | ||||
| 	) | ||||
| 	// Initially the ps stack has one quadratic segment of depth zero. | ||||
| 	ds[0] = 0 | ||||
| 	ps[2] = k.a | ||||
| 	ps[1] = b | ||||
| 	ps[0] = c | ||||
| 	anorm := k.anorm | ||||
| 	var cnorm fixed.Point26_6 | ||||
|  | ||||
| 	for { | ||||
| 		depth := ds[t] | ||||
| 		a := ps[2*t+2] | ||||
| 		b := ps[2*t+1] | ||||
| 		c := ps[2*t+0] | ||||
| 		ab := b.Sub(a) | ||||
| 		bc := c.Sub(b) | ||||
| 		abIsSmall := pDot(ab, ab) < fixed.Int52_12(1<<12) | ||||
| 		bcIsSmall := pDot(bc, bc) < fixed.Int52_12(1<<12) | ||||
| 		if abIsSmall && bcIsSmall { | ||||
| 			// Approximate the segment by a circular arc. | ||||
| 			cnorm = pRot90CCW(pNorm(bc, k.u)) | ||||
| 			mac := midpoint(a, c) | ||||
| 			addArc(k.p, mac, anorm, cnorm) | ||||
| 			addArc(&k.r, mac, pNeg(anorm), pNeg(cnorm)) | ||||
| 		} else if depth < maxDepth && angleGreaterThan45(ab, bc) { | ||||
| 			// Divide the segment in two and push both halves on the stack. | ||||
| 			mab := midpoint(a, b) | ||||
| 			mbc := midpoint(b, c) | ||||
| 			t++ | ||||
| 			ds[t+0] = depth + 1 | ||||
| 			ds[t-1] = depth + 1 | ||||
| 			ps[2*t+2] = a | ||||
| 			ps[2*t+1] = mab | ||||
| 			ps[2*t+0] = midpoint(mab, mbc) | ||||
| 			ps[2*t-1] = mbc | ||||
| 			continue | ||||
| 		} else { | ||||
| 			// Translate the control points. | ||||
| 			bnorm := pRot90CCW(pNorm(c.Sub(a), k.u)) | ||||
| 			cnorm = pRot90CCW(pNorm(bc, k.u)) | ||||
| 			k.p.Add2(b.Add(bnorm), c.Add(cnorm)) | ||||
| 			k.r.Add2(b.Sub(bnorm), c.Sub(cnorm)) | ||||
| 		} | ||||
| 		if t == 0 { | ||||
| 			k.a, k.anorm = c, cnorm | ||||
| 			return | ||||
| 		} | ||||
| 		t-- | ||||
| 		anorm = cnorm | ||||
| 	} | ||||
| 	panic("unreachable") | ||||
| } | ||||
|  | ||||
| // Add1 adds a linear segment to the stroker. | ||||
| func (k *stroker) Add1(b fixed.Point26_6) { | ||||
| 	bnorm := pRot90CCW(pNorm(b.Sub(k.a), k.u)) | ||||
| 	if len(k.r) == 0 { | ||||
| 		k.p.Start(k.a.Add(bnorm)) | ||||
| 		k.r.Start(k.a.Sub(bnorm)) | ||||
| 	} else { | ||||
| 		k.jr.Join(k.p, &k.r, k.u, k.a, k.anorm, bnorm) | ||||
| 	} | ||||
| 	k.p.Add1(b.Add(bnorm)) | ||||
| 	k.r.Add1(b.Sub(bnorm)) | ||||
| 	k.a, k.anorm = b, bnorm | ||||
| } | ||||
|  | ||||
| // Add2 adds a quadratic segment to the stroker. | ||||
| func (k *stroker) Add2(b, c fixed.Point26_6) { | ||||
| 	ab := b.Sub(k.a) | ||||
| 	bc := c.Sub(b) | ||||
| 	abnorm := pRot90CCW(pNorm(ab, k.u)) | ||||
| 	if len(k.r) == 0 { | ||||
| 		k.p.Start(k.a.Add(abnorm)) | ||||
| 		k.r.Start(k.a.Sub(abnorm)) | ||||
| 	} else { | ||||
| 		k.jr.Join(k.p, &k.r, k.u, k.a, k.anorm, abnorm) | ||||
| 	} | ||||
|  | ||||
| 	// Approximate nearly-degenerate quadratics by linear segments. | ||||
| 	abIsSmall := pDot(ab, ab) < epsilon | ||||
| 	bcIsSmall := pDot(bc, bc) < epsilon | ||||
| 	if abIsSmall || bcIsSmall { | ||||
| 		acnorm := pRot90CCW(pNorm(c.Sub(k.a), k.u)) | ||||
| 		k.p.Add1(c.Add(acnorm)) | ||||
| 		k.r.Add1(c.Sub(acnorm)) | ||||
| 		k.a, k.anorm = c, acnorm | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// The quadratic segment (k.a, b, c) has a point of maximum curvature. | ||||
| 	// If this occurs at an end point, we process the segment as a whole. | ||||
| 	t := curviest2(k.a, b, c) | ||||
| 	if t <= 0 || 4096 <= t { | ||||
| 		k.addNonCurvy2(b, c) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Otherwise, we perform a de Casteljau decomposition at the point of | ||||
| 	// maximum curvature and process the two straighter parts. | ||||
| 	mab := interpolate(k.a, b, t) | ||||
| 	mbc := interpolate(b, c, t) | ||||
| 	mabc := interpolate(mab, mbc, t) | ||||
|  | ||||
| 	// If the vectors ab and bc are close to being in opposite directions, | ||||
| 	// then the decomposition can become unstable, so we approximate the | ||||
| 	// quadratic segment by two linear segments joined by an arc. | ||||
| 	bcnorm := pRot90CCW(pNorm(bc, k.u)) | ||||
| 	if pDot(abnorm, bcnorm) < -fixed.Int52_12(k.u)*fixed.Int52_12(k.u)*2047/2048 { | ||||
| 		pArc := pDot(abnorm, bc) < 0 | ||||
|  | ||||
| 		k.p.Add1(mabc.Add(abnorm)) | ||||
| 		if pArc { | ||||
| 			z := pRot90CW(abnorm) | ||||
| 			addArc(k.p, mabc, abnorm, z) | ||||
| 			addArc(k.p, mabc, z, bcnorm) | ||||
| 		} | ||||
| 		k.p.Add1(mabc.Add(bcnorm)) | ||||
| 		k.p.Add1(c.Add(bcnorm)) | ||||
|  | ||||
| 		k.r.Add1(mabc.Sub(abnorm)) | ||||
| 		if !pArc { | ||||
| 			z := pRot90CW(abnorm) | ||||
| 			addArc(&k.r, mabc, pNeg(abnorm), z) | ||||
| 			addArc(&k.r, mabc, z, pNeg(bcnorm)) | ||||
| 		} | ||||
| 		k.r.Add1(mabc.Sub(bcnorm)) | ||||
| 		k.r.Add1(c.Sub(bcnorm)) | ||||
|  | ||||
| 		k.a, k.anorm = c, bcnorm | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Process the decomposed parts. | ||||
| 	k.addNonCurvy2(mab, mabc) | ||||
| 	k.addNonCurvy2(mbc, c) | ||||
| } | ||||
|  | ||||
| // Add3 adds a cubic segment to the stroker. | ||||
| func (k *stroker) Add3(b, c, d fixed.Point26_6) { | ||||
| 	panic("freetype/raster: stroke unimplemented for cubic segments") | ||||
| } | ||||
|  | ||||
| // stroke adds the stroked Path q to p, where q consists of exactly one curve. | ||||
| func (k *stroker) stroke(q Path) { | ||||
| 	// Stroking is implemented by deriving two paths each k.u apart from q. | ||||
| 	// The left-hand-side path is added immediately to k.p; the right-hand-side | ||||
| 	// path is accumulated in k.r. Once we've finished adding the LHS to k.p, | ||||
| 	// we add the RHS in reverse order. | ||||
| 	k.r = make(Path, 0, len(q)) | ||||
| 	k.a = fixed.Point26_6{q[1], q[2]} | ||||
| 	for i := 4; i < len(q); { | ||||
| 		switch q[i] { | ||||
| 		case 1: | ||||
| 			k.Add1( | ||||
| 				fixed.Point26_6{q[i+1], q[i+2]}, | ||||
| 			) | ||||
| 			i += 4 | ||||
| 		case 2: | ||||
| 			k.Add2( | ||||
| 				fixed.Point26_6{q[i+1], q[i+2]}, | ||||
| 				fixed.Point26_6{q[i+3], q[i+4]}, | ||||
| 			) | ||||
| 			i += 6 | ||||
| 		case 3: | ||||
| 			k.Add3( | ||||
| 				fixed.Point26_6{q[i+1], q[i+2]}, | ||||
| 				fixed.Point26_6{q[i+3], q[i+4]}, | ||||
| 				fixed.Point26_6{q[i+5], q[i+6]}, | ||||
| 			) | ||||
| 			i += 8 | ||||
| 		default: | ||||
| 			panic("freetype/raster: bad path") | ||||
| 		} | ||||
| 	} | ||||
| 	if len(k.r) == 0 { | ||||
| 		return | ||||
| 	} | ||||
| 	// TODO(nigeltao): if q is a closed curve then we should join the first and | ||||
| 	// last segments instead of capping them. | ||||
| 	k.cr.Cap(k.p, k.u, q.lastPoint(), pNeg(k.anorm)) | ||||
| 	addPathReversed(k.p, k.r) | ||||
| 	pivot := q.firstPoint() | ||||
| 	k.cr.Cap(k.p, k.u, pivot, pivot.Sub(fixed.Point26_6{k.r[1], k.r[2]})) | ||||
| } | ||||
|  | ||||
| // Stroke adds q stroked with the given width to p. The result is typically | ||||
| // self-intersecting and should be rasterized with UseNonZeroWinding. | ||||
| // cr and jr may be nil, which defaults to a RoundCapper or RoundJoiner. | ||||
| func Stroke(p Adder, q Path, width fixed.Int26_6, cr Capper, jr Joiner) { | ||||
| 	if len(q) == 0 { | ||||
| 		return | ||||
| 	} | ||||
| 	if cr == nil { | ||||
| 		cr = RoundCapper | ||||
| 	} | ||||
| 	if jr == nil { | ||||
| 		jr = RoundJoiner | ||||
| 	} | ||||
| 	if q[0] != 0 { | ||||
| 		panic("freetype/raster: bad path") | ||||
| 	} | ||||
| 	s := stroker{p: p, u: width / 2, cr: cr, jr: jr} | ||||
| 	i := 0 | ||||
| 	for j := 4; j < len(q); { | ||||
| 		switch q[j] { | ||||
| 		case 0: | ||||
| 			s.stroke(q[i:j]) | ||||
| 			i, j = j, j+4 | ||||
| 		case 1: | ||||
| 			j += 4 | ||||
| 		case 2: | ||||
| 			j += 6 | ||||
| 		case 3: | ||||
| 			j += 8 | ||||
| 		default: | ||||
| 			panic("freetype/raster: bad path") | ||||
| 		} | ||||
| 	} | ||||
| 	s.stroke(q[i:]) | ||||
| } | ||||
							
								
								
									
										507
									
								
								vendor/github.com/golang/freetype/truetype/face.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										507
									
								
								vendor/github.com/golang/freetype/truetype/face.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,507 @@ | ||||
| // Copyright 2015 The Freetype-Go Authors. All rights reserved. | ||||
| // Use of this source code is governed by your choice of either the | ||||
| // FreeType License or the GNU General Public License version 2 (or | ||||
| // any later version), both of which can be found in the LICENSE file. | ||||
|  | ||||
| package truetype | ||||
|  | ||||
| import ( | ||||
| 	"image" | ||||
| 	"math" | ||||
|  | ||||
| 	"github.com/golang/freetype/raster" | ||||
| 	"golang.org/x/image/font" | ||||
| 	"golang.org/x/image/math/fixed" | ||||
| ) | ||||
|  | ||||
| func powerOf2(i int) bool { | ||||
| 	return i != 0 && (i&(i-1)) == 0 | ||||
| } | ||||
|  | ||||
| // Options are optional arguments to NewFace. | ||||
| type Options struct { | ||||
| 	// Size is the font size in points, as in "a 10 point font size". | ||||
| 	// | ||||
| 	// A zero value means to use a 12 point font size. | ||||
| 	Size float64 | ||||
|  | ||||
| 	// DPI is the dots-per-inch resolution. | ||||
| 	// | ||||
| 	// A zero value means to use 72 DPI. | ||||
| 	DPI float64 | ||||
|  | ||||
| 	// Hinting is how to quantize the glyph nodes. | ||||
| 	// | ||||
| 	// A zero value means to use no hinting. | ||||
| 	Hinting font.Hinting | ||||
|  | ||||
| 	// GlyphCacheEntries is the number of entries in the glyph mask image | ||||
| 	// cache. | ||||
| 	// | ||||
| 	// If non-zero, it must be a power of 2. | ||||
| 	// | ||||
| 	// A zero value means to use 512 entries. | ||||
| 	GlyphCacheEntries int | ||||
|  | ||||
| 	// SubPixelsX is the number of sub-pixel locations a glyph's dot is | ||||
| 	// quantized to, in the horizontal direction. For example, a value of 8 | ||||
| 	// means that the dot is quantized to 1/8th of a pixel. This quantization | ||||
| 	// only affects the glyph mask image, not its bounding box or advance | ||||
| 	// width. A higher value gives a more faithful glyph image, but reduces the | ||||
| 	// effectiveness of the glyph cache. | ||||
| 	// | ||||
| 	// If non-zero, it must be a power of 2, and be between 1 and 64 inclusive. | ||||
| 	// | ||||
| 	// A zero value means to use 4 sub-pixel locations. | ||||
| 	SubPixelsX int | ||||
|  | ||||
| 	// SubPixelsY is the number of sub-pixel locations a glyph's dot is | ||||
| 	// quantized to, in the vertical direction. For example, a value of 8 | ||||
| 	// means that the dot is quantized to 1/8th of a pixel. This quantization | ||||
| 	// only affects the glyph mask image, not its bounding box or advance | ||||
| 	// width. A higher value gives a more faithful glyph image, but reduces the | ||||
| 	// effectiveness of the glyph cache. | ||||
| 	// | ||||
| 	// If non-zero, it must be a power of 2, and be between 1 and 64 inclusive. | ||||
| 	// | ||||
| 	// A zero value means to use 1 sub-pixel location. | ||||
| 	SubPixelsY int | ||||
| } | ||||
|  | ||||
| func (o *Options) size() float64 { | ||||
| 	if o != nil && o.Size > 0 { | ||||
| 		return o.Size | ||||
| 	} | ||||
| 	return 12 | ||||
| } | ||||
|  | ||||
| func (o *Options) dpi() float64 { | ||||
| 	if o != nil && o.DPI > 0 { | ||||
| 		return o.DPI | ||||
| 	} | ||||
| 	return 72 | ||||
| } | ||||
|  | ||||
| func (o *Options) hinting() font.Hinting { | ||||
| 	if o != nil { | ||||
| 		switch o.Hinting { | ||||
| 		case font.HintingVertical, font.HintingFull: | ||||
| 			// TODO: support vertical hinting. | ||||
| 			return font.HintingFull | ||||
| 		} | ||||
| 	} | ||||
| 	return font.HintingNone | ||||
| } | ||||
|  | ||||
| func (o *Options) glyphCacheEntries() int { | ||||
| 	if o != nil && powerOf2(o.GlyphCacheEntries) { | ||||
| 		return o.GlyphCacheEntries | ||||
| 	} | ||||
| 	// 512 is 128 * 4 * 1, which lets us cache 128 glyphs at 4 * 1 subpixel | ||||
| 	// locations in the X and Y direction. | ||||
| 	return 512 | ||||
| } | ||||
|  | ||||
| func (o *Options) subPixelsX() (value uint32, halfQuantum, mask fixed.Int26_6) { | ||||
| 	if o != nil { | ||||
| 		switch o.SubPixelsX { | ||||
| 		case 1, 2, 4, 8, 16, 32, 64: | ||||
| 			return subPixels(o.SubPixelsX) | ||||
| 		} | ||||
| 	} | ||||
| 	// This default value of 4 isn't based on anything scientific, merely as | ||||
| 	// small a number as possible that looks almost as good as no quantization, | ||||
| 	// or returning subPixels(64). | ||||
| 	return subPixels(4) | ||||
| } | ||||
|  | ||||
| func (o *Options) subPixelsY() (value uint32, halfQuantum, mask fixed.Int26_6) { | ||||
| 	if o != nil { | ||||
| 		switch o.SubPixelsX { | ||||
| 		case 1, 2, 4, 8, 16, 32, 64: | ||||
| 			return subPixels(o.SubPixelsX) | ||||
| 		} | ||||
| 	} | ||||
| 	// This default value of 1 isn't based on anything scientific, merely that | ||||
| 	// vertical sub-pixel glyph rendering is pretty rare. Baseline locations | ||||
| 	// can usually afford to snap to the pixel grid, so the vertical direction | ||||
| 	// doesn't have the deal with the horizontal's fractional advance widths. | ||||
| 	return subPixels(1) | ||||
| } | ||||
|  | ||||
| // subPixels returns q and the bias and mask that leads to q quantized | ||||
| // sub-pixel locations per full pixel. | ||||
| // | ||||
| // For example, q == 4 leads to a bias of 8 and a mask of 0xfffffff0, or -16, | ||||
| // because we want to round fractions of fixed.Int26_6 as: | ||||
| //	-  0 to  7 rounds to 0. | ||||
| //	-  8 to 23 rounds to 16. | ||||
| //	- 24 to 39 rounds to 32. | ||||
| //	- 40 to 55 rounds to 48. | ||||
| //	- 56 to 63 rounds to 64. | ||||
| // which means to add 8 and then bitwise-and with -16, in two's complement | ||||
| // representation. | ||||
| // | ||||
| // When q ==  1, we want bias == 32 and mask == -64. | ||||
| // When q ==  2, we want bias == 16 and mask == -32. | ||||
| // When q ==  4, we want bias ==  8 and mask == -16. | ||||
| // ... | ||||
| // When q == 64, we want bias ==  0 and mask ==  -1. (The no-op case). | ||||
| // The pattern is clear. | ||||
| func subPixels(q int) (value uint32, bias, mask fixed.Int26_6) { | ||||
| 	return uint32(q), 32 / fixed.Int26_6(q), -64 / fixed.Int26_6(q) | ||||
| } | ||||
|  | ||||
| // glyphCacheEntry caches the arguments and return values of rasterize. | ||||
| type glyphCacheEntry struct { | ||||
| 	key glyphCacheKey | ||||
| 	val glyphCacheVal | ||||
| } | ||||
|  | ||||
| type glyphCacheKey struct { | ||||
| 	index  Index | ||||
| 	fx, fy uint8 | ||||
| } | ||||
|  | ||||
| type glyphCacheVal struct { | ||||
| 	advanceWidth fixed.Int26_6 | ||||
| 	offset       image.Point | ||||
| 	gw           int | ||||
| 	gh           int | ||||
| } | ||||
|  | ||||
| type indexCacheEntry struct { | ||||
| 	rune  rune | ||||
| 	index Index | ||||
| } | ||||
|  | ||||
| // NewFace returns a new font.Face for the given Font. | ||||
| func NewFace(f *Font, opts *Options) font.Face { | ||||
| 	a := &face{ | ||||
| 		f:          f, | ||||
| 		hinting:    opts.hinting(), | ||||
| 		scale:      fixed.Int26_6(0.5 + (opts.size() * opts.dpi() * 64 / 72)), | ||||
| 		glyphCache: make([]glyphCacheEntry, opts.glyphCacheEntries()), | ||||
| 	} | ||||
| 	a.subPixelX, a.subPixelBiasX, a.subPixelMaskX = opts.subPixelsX() | ||||
| 	a.subPixelY, a.subPixelBiasY, a.subPixelMaskY = opts.subPixelsY() | ||||
|  | ||||
| 	// Fill the cache with invalid entries. Valid glyph cache entries have fx | ||||
| 	// and fy in the range [0, 64). Valid index cache entries have rune >= 0. | ||||
| 	for i := range a.glyphCache { | ||||
| 		a.glyphCache[i].key.fy = 0xff | ||||
| 	} | ||||
| 	for i := range a.indexCache { | ||||
| 		a.indexCache[i].rune = -1 | ||||
| 	} | ||||
|  | ||||
| 	// Set the rasterizer's bounds to be big enough to handle the largest glyph. | ||||
| 	b := f.Bounds(a.scale) | ||||
| 	xmin := +int(b.Min.X) >> 6 | ||||
| 	ymin := -int(b.Max.Y) >> 6 | ||||
| 	xmax := +int(b.Max.X+63) >> 6 | ||||
| 	ymax := -int(b.Min.Y-63) >> 6 | ||||
| 	a.maxw = xmax - xmin | ||||
| 	a.maxh = ymax - ymin | ||||
| 	a.masks = image.NewAlpha(image.Rect(0, 0, a.maxw, a.maxh*len(a.glyphCache))) | ||||
| 	a.r.SetBounds(a.maxw, a.maxh) | ||||
| 	a.p = facePainter{a} | ||||
|  | ||||
| 	return a | ||||
| } | ||||
|  | ||||
| type face struct { | ||||
| 	f             *Font | ||||
| 	hinting       font.Hinting | ||||
| 	scale         fixed.Int26_6 | ||||
| 	subPixelX     uint32 | ||||
| 	subPixelBiasX fixed.Int26_6 | ||||
| 	subPixelMaskX fixed.Int26_6 | ||||
| 	subPixelY     uint32 | ||||
| 	subPixelBiasY fixed.Int26_6 | ||||
| 	subPixelMaskY fixed.Int26_6 | ||||
| 	masks         *image.Alpha | ||||
| 	glyphCache    []glyphCacheEntry | ||||
| 	r             raster.Rasterizer | ||||
| 	p             raster.Painter | ||||
| 	paintOffset   int | ||||
| 	maxw          int | ||||
| 	maxh          int | ||||
| 	glyphBuf      GlyphBuf | ||||
| 	indexCache    [indexCacheLen]indexCacheEntry | ||||
|  | ||||
| 	// TODO: clip rectangle? | ||||
| } | ||||
|  | ||||
| const indexCacheLen = 256 | ||||
|  | ||||
| func (a *face) index(r rune) Index { | ||||
| 	const mask = indexCacheLen - 1 | ||||
| 	c := &a.indexCache[r&mask] | ||||
| 	if c.rune == r { | ||||
| 		return c.index | ||||
| 	} | ||||
| 	i := a.f.Index(r) | ||||
| 	c.rune = r | ||||
| 	c.index = i | ||||
| 	return i | ||||
| } | ||||
|  | ||||
| // Close satisfies the font.Face interface. | ||||
| func (a *face) Close() error { return nil } | ||||
|  | ||||
| // Metrics satisfies the font.Face interface. | ||||
| func (a *face) Metrics() font.Metrics { | ||||
| 	scale := float64(a.scale) | ||||
| 	fupe := float64(a.f.FUnitsPerEm()) | ||||
| 	return font.Metrics{ | ||||
| 		Height:  a.scale, | ||||
| 		Ascent:  fixed.Int26_6(math.Ceil(scale * float64(+a.f.ascent) / fupe)), | ||||
| 		Descent: fixed.Int26_6(math.Ceil(scale * float64(-a.f.descent) / fupe)), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Kern satisfies the font.Face interface. | ||||
| func (a *face) Kern(r0, r1 rune) fixed.Int26_6 { | ||||
| 	i0 := a.index(r0) | ||||
| 	i1 := a.index(r1) | ||||
| 	kern := a.f.Kern(a.scale, i0, i1) | ||||
| 	if a.hinting != font.HintingNone { | ||||
| 		kern = (kern + 32) &^ 63 | ||||
| 	} | ||||
| 	return kern | ||||
| } | ||||
|  | ||||
| // Glyph satisfies the font.Face interface. | ||||
| func (a *face) Glyph(dot fixed.Point26_6, r rune) ( | ||||
| 	dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) { | ||||
|  | ||||
| 	// Quantize to the sub-pixel granularity. | ||||
| 	dotX := (dot.X + a.subPixelBiasX) & a.subPixelMaskX | ||||
| 	dotY := (dot.Y + a.subPixelBiasY) & a.subPixelMaskY | ||||
|  | ||||
| 	// Split the coordinates into their integer and fractional parts. | ||||
| 	ix, fx := int(dotX>>6), dotX&0x3f | ||||
| 	iy, fy := int(dotY>>6), dotY&0x3f | ||||
|  | ||||
| 	index := a.index(r) | ||||
| 	cIndex := uint32(index) | ||||
| 	cIndex = cIndex*a.subPixelX - uint32(fx/a.subPixelMaskX) | ||||
| 	cIndex = cIndex*a.subPixelY - uint32(fy/a.subPixelMaskY) | ||||
| 	cIndex &= uint32(len(a.glyphCache) - 1) | ||||
| 	a.paintOffset = a.maxh * int(cIndex) | ||||
| 	k := glyphCacheKey{ | ||||
| 		index: index, | ||||
| 		fx:    uint8(fx), | ||||
| 		fy:    uint8(fy), | ||||
| 	} | ||||
| 	var v glyphCacheVal | ||||
| 	if a.glyphCache[cIndex].key != k { | ||||
| 		var ok bool | ||||
| 		v, ok = a.rasterize(index, fx, fy) | ||||
| 		if !ok { | ||||
| 			return image.Rectangle{}, nil, image.Point{}, 0, false | ||||
| 		} | ||||
| 		a.glyphCache[cIndex] = glyphCacheEntry{k, v} | ||||
| 	} else { | ||||
| 		v = a.glyphCache[cIndex].val | ||||
| 	} | ||||
|  | ||||
| 	dr.Min = image.Point{ | ||||
| 		X: ix + v.offset.X, | ||||
| 		Y: iy + v.offset.Y, | ||||
| 	} | ||||
| 	dr.Max = image.Point{ | ||||
| 		X: dr.Min.X + v.gw, | ||||
| 		Y: dr.Min.Y + v.gh, | ||||
| 	} | ||||
| 	return dr, a.masks, image.Point{Y: a.paintOffset}, v.advanceWidth, true | ||||
| } | ||||
|  | ||||
| func (a *face) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) { | ||||
| 	if err := a.glyphBuf.Load(a.f, a.scale, a.index(r), a.hinting); err != nil { | ||||
| 		return fixed.Rectangle26_6{}, 0, false | ||||
| 	} | ||||
| 	xmin := +a.glyphBuf.Bounds.Min.X | ||||
| 	ymin := -a.glyphBuf.Bounds.Max.Y | ||||
| 	xmax := +a.glyphBuf.Bounds.Max.X | ||||
| 	ymax := -a.glyphBuf.Bounds.Min.Y | ||||
| 	if xmin > xmax || ymin > ymax { | ||||
| 		return fixed.Rectangle26_6{}, 0, false | ||||
| 	} | ||||
| 	return fixed.Rectangle26_6{ | ||||
| 		Min: fixed.Point26_6{ | ||||
| 			X: xmin, | ||||
| 			Y: ymin, | ||||
| 		}, | ||||
| 		Max: fixed.Point26_6{ | ||||
| 			X: xmax, | ||||
| 			Y: ymax, | ||||
| 		}, | ||||
| 	}, a.glyphBuf.AdvanceWidth, true | ||||
| } | ||||
|  | ||||
| func (a *face) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) { | ||||
| 	if err := a.glyphBuf.Load(a.f, a.scale, a.index(r), a.hinting); err != nil { | ||||
| 		return 0, false | ||||
| 	} | ||||
| 	return a.glyphBuf.AdvanceWidth, true | ||||
| } | ||||
|  | ||||
| // rasterize returns the advance width, integer-pixel offset to render at, and | ||||
| // the width and height of the given glyph at the given sub-pixel offsets. | ||||
| // | ||||
| // The 26.6 fixed point arguments fx and fy must be in the range [0, 1). | ||||
| func (a *face) rasterize(index Index, fx, fy fixed.Int26_6) (v glyphCacheVal, ok bool) { | ||||
| 	if err := a.glyphBuf.Load(a.f, a.scale, index, a.hinting); err != nil { | ||||
| 		return glyphCacheVal{}, false | ||||
| 	} | ||||
| 	// Calculate the integer-pixel bounds for the glyph. | ||||
| 	xmin := int(fx+a.glyphBuf.Bounds.Min.X) >> 6 | ||||
| 	ymin := int(fy-a.glyphBuf.Bounds.Max.Y) >> 6 | ||||
| 	xmax := int(fx+a.glyphBuf.Bounds.Max.X+0x3f) >> 6 | ||||
| 	ymax := int(fy-a.glyphBuf.Bounds.Min.Y+0x3f) >> 6 | ||||
| 	if xmin > xmax || ymin > ymax { | ||||
| 		return glyphCacheVal{}, false | ||||
| 	} | ||||
| 	// A TrueType's glyph's nodes can have negative co-ordinates, but the | ||||
| 	// rasterizer clips anything left of x=0 or above y=0. xmin and ymin are | ||||
| 	// the pixel offsets, based on the font's FUnit metrics, that let a | ||||
| 	// negative co-ordinate in TrueType space be non-negative in rasterizer | ||||
| 	// space. xmin and ymin are typically <= 0. | ||||
| 	fx -= fixed.Int26_6(xmin << 6) | ||||
| 	fy -= fixed.Int26_6(ymin << 6) | ||||
| 	// Rasterize the glyph's vectors. | ||||
| 	a.r.Clear() | ||||
| 	pixOffset := a.paintOffset * a.maxw | ||||
| 	clear(a.masks.Pix[pixOffset : pixOffset+a.maxw*a.maxh]) | ||||
| 	e0 := 0 | ||||
| 	for _, e1 := range a.glyphBuf.Ends { | ||||
| 		a.drawContour(a.glyphBuf.Points[e0:e1], fx, fy) | ||||
| 		e0 = e1 | ||||
| 	} | ||||
| 	a.r.Rasterize(a.p) | ||||
| 	return glyphCacheVal{ | ||||
| 		a.glyphBuf.AdvanceWidth, | ||||
| 		image.Point{xmin, ymin}, | ||||
| 		xmax - xmin, | ||||
| 		ymax - ymin, | ||||
| 	}, true | ||||
| } | ||||
|  | ||||
| func clear(pix []byte) { | ||||
| 	for i := range pix { | ||||
| 		pix[i] = 0 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // drawContour draws the given closed contour with the given offset. | ||||
| func (a *face) drawContour(ps []Point, dx, dy fixed.Int26_6) { | ||||
| 	if len(ps) == 0 { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// The low bit of each point's Flags value is whether the point is on the | ||||
| 	// curve. Truetype fonts only have quadratic Bézier curves, not cubics. | ||||
| 	// Thus, two consecutive off-curve points imply an on-curve point in the | ||||
| 	// middle of those two. | ||||
| 	// | ||||
| 	// See http://chanae.walon.org/pub/ttf/ttf_glyphs.htm for more details. | ||||
|  | ||||
| 	// ps[0] is a truetype.Point measured in FUnits and positive Y going | ||||
| 	// upwards. start is the same thing measured in fixed point units and | ||||
| 	// positive Y going downwards, and offset by (dx, dy). | ||||
| 	start := fixed.Point26_6{ | ||||
| 		X: dx + ps[0].X, | ||||
| 		Y: dy - ps[0].Y, | ||||
| 	} | ||||
| 	var others []Point | ||||
| 	if ps[0].Flags&0x01 != 0 { | ||||
| 		others = ps[1:] | ||||
| 	} else { | ||||
| 		last := fixed.Point26_6{ | ||||
| 			X: dx + ps[len(ps)-1].X, | ||||
| 			Y: dy - ps[len(ps)-1].Y, | ||||
| 		} | ||||
| 		if ps[len(ps)-1].Flags&0x01 != 0 { | ||||
| 			start = last | ||||
| 			others = ps[:len(ps)-1] | ||||
| 		} else { | ||||
| 			start = fixed.Point26_6{ | ||||
| 				X: (start.X + last.X) / 2, | ||||
| 				Y: (start.Y + last.Y) / 2, | ||||
| 			} | ||||
| 			others = ps | ||||
| 		} | ||||
| 	} | ||||
| 	a.r.Start(start) | ||||
| 	q0, on0 := start, true | ||||
| 	for _, p := range others { | ||||
| 		q := fixed.Point26_6{ | ||||
| 			X: dx + p.X, | ||||
| 			Y: dy - p.Y, | ||||
| 		} | ||||
| 		on := p.Flags&0x01 != 0 | ||||
| 		if on { | ||||
| 			if on0 { | ||||
| 				a.r.Add1(q) | ||||
| 			} else { | ||||
| 				a.r.Add2(q0, q) | ||||
| 			} | ||||
| 		} else { | ||||
| 			if on0 { | ||||
| 				// No-op. | ||||
| 			} else { | ||||
| 				mid := fixed.Point26_6{ | ||||
| 					X: (q0.X + q.X) / 2, | ||||
| 					Y: (q0.Y + q.Y) / 2, | ||||
| 				} | ||||
| 				a.r.Add2(q0, mid) | ||||
| 			} | ||||
| 		} | ||||
| 		q0, on0 = q, on | ||||
| 	} | ||||
| 	// Close the curve. | ||||
| 	if on0 { | ||||
| 		a.r.Add1(start) | ||||
| 	} else { | ||||
| 		a.r.Add2(q0, start) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // facePainter is like a raster.AlphaSrcPainter, with an additional Y offset | ||||
| // (face.paintOffset) to the painted spans. | ||||
| type facePainter struct { | ||||
| 	a *face | ||||
| } | ||||
|  | ||||
| func (p facePainter) Paint(ss []raster.Span, done bool) { | ||||
| 	m := p.a.masks | ||||
| 	b := m.Bounds() | ||||
| 	b.Min.Y = p.a.paintOffset | ||||
| 	b.Max.Y = p.a.paintOffset + p.a.maxh | ||||
| 	for _, s := range ss { | ||||
| 		s.Y += p.a.paintOffset | ||||
| 		if s.Y < b.Min.Y { | ||||
| 			continue | ||||
| 		} | ||||
| 		if s.Y >= b.Max.Y { | ||||
| 			return | ||||
| 		} | ||||
| 		if s.X0 < b.Min.X { | ||||
| 			s.X0 = b.Min.X | ||||
| 		} | ||||
| 		if s.X1 > b.Max.X { | ||||
| 			s.X1 = b.Max.X | ||||
| 		} | ||||
| 		if s.X0 >= s.X1 { | ||||
| 			continue | ||||
| 		} | ||||
| 		base := (s.Y-m.Rect.Min.Y)*m.Stride - m.Rect.Min.X | ||||
| 		p := m.Pix[base+s.X0 : base+s.X1] | ||||
| 		color := uint8(s.Alpha >> 8) | ||||
| 		for i := range p { | ||||
| 			p[i] = color | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										522
									
								
								vendor/github.com/golang/freetype/truetype/glyph.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										522
									
								
								vendor/github.com/golang/freetype/truetype/glyph.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,522 @@ | ||||
| // Copyright 2010 The Freetype-Go Authors. All rights reserved. | ||||
| // Use of this source code is governed by your choice of either the | ||||
| // FreeType License or the GNU General Public License version 2 (or | ||||
| // any later version), both of which can be found in the LICENSE file. | ||||
|  | ||||
| package truetype | ||||
|  | ||||
| import ( | ||||
| 	"golang.org/x/image/font" | ||||
| 	"golang.org/x/image/math/fixed" | ||||
| ) | ||||
|  | ||||
| // TODO: implement VerticalHinting. | ||||
|  | ||||
| // A Point is a co-ordinate pair plus whether it is 'on' a contour or an 'off' | ||||
| // control point. | ||||
| type Point struct { | ||||
| 	X, Y fixed.Int26_6 | ||||
| 	// The Flags' LSB means whether or not this Point is 'on' the contour. | ||||
| 	// Other bits are reserved for internal use. | ||||
| 	Flags uint32 | ||||
| } | ||||
|  | ||||
| // A GlyphBuf holds a glyph's contours. A GlyphBuf can be re-used to load a | ||||
| // series of glyphs from a Font. | ||||
| type GlyphBuf struct { | ||||
| 	// AdvanceWidth is the glyph's advance width. | ||||
| 	AdvanceWidth fixed.Int26_6 | ||||
| 	// Bounds is the glyph's bounding box. | ||||
| 	Bounds fixed.Rectangle26_6 | ||||
| 	// Points contains all Points from all contours of the glyph. If hinting | ||||
| 	// was used to load a glyph then Unhinted contains those Points before they | ||||
| 	// were hinted, and InFontUnits contains those Points before they were | ||||
| 	// hinted and scaled. | ||||
| 	Points, Unhinted, InFontUnits []Point | ||||
| 	// Ends is the point indexes of the end point of each contour. The length | ||||
| 	// of Ends is the number of contours in the glyph. The i'th contour | ||||
| 	// consists of points Points[Ends[i-1]:Ends[i]], where Ends[-1] is | ||||
| 	// interpreted to mean zero. | ||||
| 	Ends []int | ||||
|  | ||||
| 	font    *Font | ||||
| 	scale   fixed.Int26_6 | ||||
| 	hinting font.Hinting | ||||
| 	hinter  hinter | ||||
| 	// phantomPoints are the co-ordinates of the synthetic phantom points | ||||
| 	// used for hinting and bounding box calculations. | ||||
| 	phantomPoints [4]Point | ||||
| 	// pp1x is the X co-ordinate of the first phantom point. The '1' is | ||||
| 	// using 1-based indexing; pp1x is almost always phantomPoints[0].X. | ||||
| 	// TODO: eliminate this and consistently use phantomPoints[0].X. | ||||
| 	pp1x fixed.Int26_6 | ||||
| 	// metricsSet is whether the glyph's metrics have been set yet. For a | ||||
| 	// compound glyph, a sub-glyph may override the outer glyph's metrics. | ||||
| 	metricsSet bool | ||||
| 	// tmp is a scratch buffer. | ||||
| 	tmp []Point | ||||
| } | ||||
|  | ||||
| // Flags for decoding a glyph's contours. These flags are documented at | ||||
| // http://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html. | ||||
| const ( | ||||
| 	flagOnCurve = 1 << iota | ||||
| 	flagXShortVector | ||||
| 	flagYShortVector | ||||
| 	flagRepeat | ||||
| 	flagPositiveXShortVector | ||||
| 	flagPositiveYShortVector | ||||
|  | ||||
| 	// The remaining flags are for internal use. | ||||
| 	flagTouchedX | ||||
| 	flagTouchedY | ||||
| ) | ||||
|  | ||||
| // The same flag bits (0x10 and 0x20) are overloaded to have two meanings, | ||||
| // dependent on the value of the flag{X,Y}ShortVector bits. | ||||
| const ( | ||||
| 	flagThisXIsSame = flagPositiveXShortVector | ||||
| 	flagThisYIsSame = flagPositiveYShortVector | ||||
| ) | ||||
|  | ||||
| // Load loads a glyph's contours from a Font, overwriting any previously loaded | ||||
| // contours for this GlyphBuf. scale is the number of 26.6 fixed point units in | ||||
| // 1 em, i is the glyph index, and h is the hinting policy. | ||||
| func (g *GlyphBuf) Load(f *Font, scale fixed.Int26_6, i Index, h font.Hinting) error { | ||||
| 	g.Points = g.Points[:0] | ||||
| 	g.Unhinted = g.Unhinted[:0] | ||||
| 	g.InFontUnits = g.InFontUnits[:0] | ||||
| 	g.Ends = g.Ends[:0] | ||||
| 	g.font = f | ||||
| 	g.hinting = h | ||||
| 	g.scale = scale | ||||
| 	g.pp1x = 0 | ||||
| 	g.phantomPoints = [4]Point{} | ||||
| 	g.metricsSet = false | ||||
|  | ||||
| 	if h != font.HintingNone { | ||||
| 		if err := g.hinter.init(f, scale); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	if err := g.load(0, i, true); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	// TODO: this selection of either g.pp1x or g.phantomPoints[0].X isn't ideal, | ||||
| 	// and should be cleaned up once we have all the testScaling tests passing, | ||||
| 	// plus additional tests for Freetype-Go's bounding boxes matching C Freetype's. | ||||
| 	pp1x := g.pp1x | ||||
| 	if h != font.HintingNone { | ||||
| 		pp1x = g.phantomPoints[0].X | ||||
| 	} | ||||
| 	if pp1x != 0 { | ||||
| 		for i := range g.Points { | ||||
| 			g.Points[i].X -= pp1x | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	advanceWidth := g.phantomPoints[1].X - g.phantomPoints[0].X | ||||
| 	if h != font.HintingNone { | ||||
| 		if len(f.hdmx) >= 8 { | ||||
| 			if n := u32(f.hdmx, 4); n > 3+uint32(i) { | ||||
| 				for hdmx := f.hdmx[8:]; uint32(len(hdmx)) >= n; hdmx = hdmx[n:] { | ||||
| 					if fixed.Int26_6(hdmx[0]) == scale>>6 { | ||||
| 						advanceWidth = fixed.Int26_6(hdmx[2+i]) << 6 | ||||
| 						break | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		advanceWidth = (advanceWidth + 32) &^ 63 | ||||
| 	} | ||||
| 	g.AdvanceWidth = advanceWidth | ||||
|  | ||||
| 	// Set g.Bounds to the 'control box', which is the bounding box of the | ||||
| 	// Bézier curves' control points. This is easier to calculate, no smaller | ||||
| 	// than and often equal to the tightest possible bounding box of the curves | ||||
| 	// themselves. This approach is what C Freetype does. We can't just scale | ||||
| 	// the nominal bounding box in the glyf data as the hinting process and | ||||
| 	// phantom point adjustment may move points outside of that box. | ||||
| 	if len(g.Points) == 0 { | ||||
| 		g.Bounds = fixed.Rectangle26_6{} | ||||
| 	} else { | ||||
| 		p := g.Points[0] | ||||
| 		g.Bounds.Min.X = p.X | ||||
| 		g.Bounds.Max.X = p.X | ||||
| 		g.Bounds.Min.Y = p.Y | ||||
| 		g.Bounds.Max.Y = p.Y | ||||
| 		for _, p := range g.Points[1:] { | ||||
| 			if g.Bounds.Min.X > p.X { | ||||
| 				g.Bounds.Min.X = p.X | ||||
| 			} else if g.Bounds.Max.X < p.X { | ||||
| 				g.Bounds.Max.X = p.X | ||||
| 			} | ||||
| 			if g.Bounds.Min.Y > p.Y { | ||||
| 				g.Bounds.Min.Y = p.Y | ||||
| 			} else if g.Bounds.Max.Y < p.Y { | ||||
| 				g.Bounds.Max.Y = p.Y | ||||
| 			} | ||||
| 		} | ||||
| 		// Snap the box to the grid, if hinting is on. | ||||
| 		if h != font.HintingNone { | ||||
| 			g.Bounds.Min.X &^= 63 | ||||
| 			g.Bounds.Min.Y &^= 63 | ||||
| 			g.Bounds.Max.X += 63 | ||||
| 			g.Bounds.Max.X &^= 63 | ||||
| 			g.Bounds.Max.Y += 63 | ||||
| 			g.Bounds.Max.Y &^= 63 | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (g *GlyphBuf) load(recursion uint32, i Index, useMyMetrics bool) (err error) { | ||||
| 	// The recursion limit here is arbitrary, but defends against malformed glyphs. | ||||
| 	if recursion >= 32 { | ||||
| 		return UnsupportedError("excessive compound glyph recursion") | ||||
| 	} | ||||
| 	// Find the relevant slice of g.font.glyf. | ||||
| 	var g0, g1 uint32 | ||||
| 	if g.font.locaOffsetFormat == locaOffsetFormatShort { | ||||
| 		g0 = 2 * uint32(u16(g.font.loca, 2*int(i))) | ||||
| 		g1 = 2 * uint32(u16(g.font.loca, 2*int(i)+2)) | ||||
| 	} else { | ||||
| 		g0 = u32(g.font.loca, 4*int(i)) | ||||
| 		g1 = u32(g.font.loca, 4*int(i)+4) | ||||
| 	} | ||||
|  | ||||
| 	// Decode the contour count and nominal bounding box, from the first | ||||
| 	// 10 bytes of the glyf data. boundsYMin and boundsXMax, at offsets 4 | ||||
| 	// and 6, are unused. | ||||
| 	glyf, ne, boundsXMin, boundsYMax := []byte(nil), 0, fixed.Int26_6(0), fixed.Int26_6(0) | ||||
| 	if g0+10 <= g1 { | ||||
| 		glyf = g.font.glyf[g0:g1] | ||||
| 		ne = int(int16(u16(glyf, 0))) | ||||
| 		boundsXMin = fixed.Int26_6(int16(u16(glyf, 2))) | ||||
| 		boundsYMax = fixed.Int26_6(int16(u16(glyf, 8))) | ||||
| 	} | ||||
|  | ||||
| 	// Create the phantom points. | ||||
| 	uhm, pp1x := g.font.unscaledHMetric(i), fixed.Int26_6(0) | ||||
| 	uvm := g.font.unscaledVMetric(i, boundsYMax) | ||||
| 	g.phantomPoints = [4]Point{ | ||||
| 		{X: boundsXMin - uhm.LeftSideBearing}, | ||||
| 		{X: boundsXMin - uhm.LeftSideBearing + uhm.AdvanceWidth}, | ||||
| 		{X: uhm.AdvanceWidth / 2, Y: boundsYMax + uvm.TopSideBearing}, | ||||
| 		{X: uhm.AdvanceWidth / 2, Y: boundsYMax + uvm.TopSideBearing - uvm.AdvanceHeight}, | ||||
| 	} | ||||
| 	if len(glyf) == 0 { | ||||
| 		g.addPhantomsAndScale(len(g.Points), len(g.Points), true, true) | ||||
| 		copy(g.phantomPoints[:], g.Points[len(g.Points)-4:]) | ||||
| 		g.Points = g.Points[:len(g.Points)-4] | ||||
| 		// TODO: also trim g.InFontUnits and g.Unhinted? | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	// Load and hint the contours. | ||||
| 	if ne < 0 { | ||||
| 		if ne != -1 { | ||||
| 			// http://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html says that | ||||
| 			// "the values -2, -3, and so forth, are reserved for future use." | ||||
| 			return UnsupportedError("negative number of contours") | ||||
| 		} | ||||
| 		pp1x = g.font.scale(g.scale * (boundsXMin - uhm.LeftSideBearing)) | ||||
| 		if err := g.loadCompound(recursion, uhm, i, glyf, useMyMetrics); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} else { | ||||
| 		np0, ne0 := len(g.Points), len(g.Ends) | ||||
| 		program := g.loadSimple(glyf, ne) | ||||
| 		g.addPhantomsAndScale(np0, np0, true, true) | ||||
| 		pp1x = g.Points[len(g.Points)-4].X | ||||
| 		if g.hinting != font.HintingNone { | ||||
| 			if len(program) != 0 { | ||||
| 				err := g.hinter.run( | ||||
| 					program, | ||||
| 					g.Points[np0:], | ||||
| 					g.Unhinted[np0:], | ||||
| 					g.InFontUnits[np0:], | ||||
| 					g.Ends[ne0:], | ||||
| 				) | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
| 			// Drop the four phantom points. | ||||
| 			g.InFontUnits = g.InFontUnits[:len(g.InFontUnits)-4] | ||||
| 			g.Unhinted = g.Unhinted[:len(g.Unhinted)-4] | ||||
| 		} | ||||
| 		if useMyMetrics { | ||||
| 			copy(g.phantomPoints[:], g.Points[len(g.Points)-4:]) | ||||
| 		} | ||||
| 		g.Points = g.Points[:len(g.Points)-4] | ||||
| 		if np0 != 0 { | ||||
| 			// The hinting program expects the []Ends values to be indexed | ||||
| 			// relative to the inner glyph, not the outer glyph, so we delay | ||||
| 			// adding np0 until after the hinting program (if any) has run. | ||||
| 			for i := ne0; i < len(g.Ends); i++ { | ||||
| 				g.Ends[i] += np0 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	if useMyMetrics && !g.metricsSet { | ||||
| 		g.metricsSet = true | ||||
| 		g.pp1x = pp1x | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // loadOffset is the initial offset for loadSimple and loadCompound. The first | ||||
| // 10 bytes are the number of contours and the bounding box. | ||||
| const loadOffset = 10 | ||||
|  | ||||
| func (g *GlyphBuf) loadSimple(glyf []byte, ne int) (program []byte) { | ||||
| 	offset := loadOffset | ||||
| 	for i := 0; i < ne; i++ { | ||||
| 		g.Ends = append(g.Ends, 1+int(u16(glyf, offset))) | ||||
| 		offset += 2 | ||||
| 	} | ||||
|  | ||||
| 	// Note the TrueType hinting instructions. | ||||
| 	instrLen := int(u16(glyf, offset)) | ||||
| 	offset += 2 | ||||
| 	program = glyf[offset : offset+instrLen] | ||||
| 	offset += instrLen | ||||
|  | ||||
| 	if ne == 0 { | ||||
| 		return program | ||||
| 	} | ||||
|  | ||||
| 	np0 := len(g.Points) | ||||
| 	np1 := np0 + int(g.Ends[len(g.Ends)-1]) | ||||
|  | ||||
| 	// Decode the flags. | ||||
| 	for i := np0; i < np1; { | ||||
| 		c := uint32(glyf[offset]) | ||||
| 		offset++ | ||||
| 		g.Points = append(g.Points, Point{Flags: c}) | ||||
| 		i++ | ||||
| 		if c&flagRepeat != 0 { | ||||
| 			count := glyf[offset] | ||||
| 			offset++ | ||||
| 			for ; count > 0; count-- { | ||||
| 				g.Points = append(g.Points, Point{Flags: c}) | ||||
| 				i++ | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Decode the co-ordinates. | ||||
| 	var x int16 | ||||
| 	for i := np0; i < np1; i++ { | ||||
| 		f := g.Points[i].Flags | ||||
| 		if f&flagXShortVector != 0 { | ||||
| 			dx := int16(glyf[offset]) | ||||
| 			offset++ | ||||
| 			if f&flagPositiveXShortVector == 0 { | ||||
| 				x -= dx | ||||
| 			} else { | ||||
| 				x += dx | ||||
| 			} | ||||
| 		} else if f&flagThisXIsSame == 0 { | ||||
| 			x += int16(u16(glyf, offset)) | ||||
| 			offset += 2 | ||||
| 		} | ||||
| 		g.Points[i].X = fixed.Int26_6(x) | ||||
| 	} | ||||
| 	var y int16 | ||||
| 	for i := np0; i < np1; i++ { | ||||
| 		f := g.Points[i].Flags | ||||
| 		if f&flagYShortVector != 0 { | ||||
| 			dy := int16(glyf[offset]) | ||||
| 			offset++ | ||||
| 			if f&flagPositiveYShortVector == 0 { | ||||
| 				y -= dy | ||||
| 			} else { | ||||
| 				y += dy | ||||
| 			} | ||||
| 		} else if f&flagThisYIsSame == 0 { | ||||
| 			y += int16(u16(glyf, offset)) | ||||
| 			offset += 2 | ||||
| 		} | ||||
| 		g.Points[i].Y = fixed.Int26_6(y) | ||||
| 	} | ||||
|  | ||||
| 	return program | ||||
| } | ||||
|  | ||||
| func (g *GlyphBuf) loadCompound(recursion uint32, uhm HMetric, i Index, | ||||
| 	glyf []byte, useMyMetrics bool) error { | ||||
|  | ||||
| 	// Flags for decoding a compound glyph. These flags are documented at | ||||
| 	// http://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html. | ||||
| 	const ( | ||||
| 		flagArg1And2AreWords = 1 << iota | ||||
| 		flagArgsAreXYValues | ||||
| 		flagRoundXYToGrid | ||||
| 		flagWeHaveAScale | ||||
| 		flagUnused | ||||
| 		flagMoreComponents | ||||
| 		flagWeHaveAnXAndYScale | ||||
| 		flagWeHaveATwoByTwo | ||||
| 		flagWeHaveInstructions | ||||
| 		flagUseMyMetrics | ||||
| 		flagOverlapCompound | ||||
| 	) | ||||
| 	np0, ne0 := len(g.Points), len(g.Ends) | ||||
| 	offset := loadOffset | ||||
| 	for { | ||||
| 		flags := u16(glyf, offset) | ||||
| 		component := Index(u16(glyf, offset+2)) | ||||
| 		dx, dy, transform, hasTransform := fixed.Int26_6(0), fixed.Int26_6(0), [4]int16{}, false | ||||
| 		if flags&flagArg1And2AreWords != 0 { | ||||
| 			dx = fixed.Int26_6(int16(u16(glyf, offset+4))) | ||||
| 			dy = fixed.Int26_6(int16(u16(glyf, offset+6))) | ||||
| 			offset += 8 | ||||
| 		} else { | ||||
| 			dx = fixed.Int26_6(int16(int8(glyf[offset+4]))) | ||||
| 			dy = fixed.Int26_6(int16(int8(glyf[offset+5]))) | ||||
| 			offset += 6 | ||||
| 		} | ||||
| 		if flags&flagArgsAreXYValues == 0 { | ||||
| 			return UnsupportedError("compound glyph transform vector") | ||||
| 		} | ||||
| 		if flags&(flagWeHaveAScale|flagWeHaveAnXAndYScale|flagWeHaveATwoByTwo) != 0 { | ||||
| 			hasTransform = true | ||||
| 			switch { | ||||
| 			case flags&flagWeHaveAScale != 0: | ||||
| 				transform[0] = int16(u16(glyf, offset+0)) | ||||
| 				transform[3] = transform[0] | ||||
| 				offset += 2 | ||||
| 			case flags&flagWeHaveAnXAndYScale != 0: | ||||
| 				transform[0] = int16(u16(glyf, offset+0)) | ||||
| 				transform[3] = int16(u16(glyf, offset+2)) | ||||
| 				offset += 4 | ||||
| 			case flags&flagWeHaveATwoByTwo != 0: | ||||
| 				transform[0] = int16(u16(glyf, offset+0)) | ||||
| 				transform[1] = int16(u16(glyf, offset+2)) | ||||
| 				transform[2] = int16(u16(glyf, offset+4)) | ||||
| 				transform[3] = int16(u16(glyf, offset+6)) | ||||
| 				offset += 8 | ||||
| 			} | ||||
| 		} | ||||
| 		savedPP := g.phantomPoints | ||||
| 		np0 := len(g.Points) | ||||
| 		componentUMM := useMyMetrics && (flags&flagUseMyMetrics != 0) | ||||
| 		if err := g.load(recursion+1, component, componentUMM); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if flags&flagUseMyMetrics == 0 { | ||||
| 			g.phantomPoints = savedPP | ||||
| 		} | ||||
| 		if hasTransform { | ||||
| 			for j := np0; j < len(g.Points); j++ { | ||||
| 				p := &g.Points[j] | ||||
| 				newX := 0 + | ||||
| 					fixed.Int26_6((int64(p.X)*int64(transform[0])+1<<13)>>14) + | ||||
| 					fixed.Int26_6((int64(p.Y)*int64(transform[2])+1<<13)>>14) | ||||
| 				newY := 0 + | ||||
| 					fixed.Int26_6((int64(p.X)*int64(transform[1])+1<<13)>>14) + | ||||
| 					fixed.Int26_6((int64(p.Y)*int64(transform[3])+1<<13)>>14) | ||||
| 				p.X, p.Y = newX, newY | ||||
| 			} | ||||
| 		} | ||||
| 		dx = g.font.scale(g.scale * dx) | ||||
| 		dy = g.font.scale(g.scale * dy) | ||||
| 		if flags&flagRoundXYToGrid != 0 { | ||||
| 			dx = (dx + 32) &^ 63 | ||||
| 			dy = (dy + 32) &^ 63 | ||||
| 		} | ||||
| 		for j := np0; j < len(g.Points); j++ { | ||||
| 			p := &g.Points[j] | ||||
| 			p.X += dx | ||||
| 			p.Y += dy | ||||
| 		} | ||||
| 		// TODO: also adjust g.InFontUnits and g.Unhinted? | ||||
| 		if flags&flagMoreComponents == 0 { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	instrLen := 0 | ||||
| 	if g.hinting != font.HintingNone && offset+2 <= len(glyf) { | ||||
| 		instrLen = int(u16(glyf, offset)) | ||||
| 		offset += 2 | ||||
| 	} | ||||
|  | ||||
| 	g.addPhantomsAndScale(np0, len(g.Points), false, instrLen > 0) | ||||
| 	points, ends := g.Points[np0:], g.Ends[ne0:] | ||||
| 	g.Points = g.Points[:len(g.Points)-4] | ||||
| 	for j := range points { | ||||
| 		points[j].Flags &^= flagTouchedX | flagTouchedY | ||||
| 	} | ||||
|  | ||||
| 	if instrLen == 0 { | ||||
| 		if !g.metricsSet { | ||||
| 			copy(g.phantomPoints[:], points[len(points)-4:]) | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	// Hint the compound glyph. | ||||
| 	program := glyf[offset : offset+instrLen] | ||||
| 	// Temporarily adjust the ends to be relative to this compound glyph. | ||||
| 	if np0 != 0 { | ||||
| 		for i := range ends { | ||||
| 			ends[i] -= np0 | ||||
| 		} | ||||
| 	} | ||||
| 	// Hinting instructions of a composite glyph completely refer to the | ||||
| 	// (already) hinted subglyphs. | ||||
| 	g.tmp = append(g.tmp[:0], points...) | ||||
| 	if err := g.hinter.run(program, points, g.tmp, g.tmp, ends); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if np0 != 0 { | ||||
| 		for i := range ends { | ||||
| 			ends[i] += np0 | ||||
| 		} | ||||
| 	} | ||||
| 	if !g.metricsSet { | ||||
| 		copy(g.phantomPoints[:], points[len(points)-4:]) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (g *GlyphBuf) addPhantomsAndScale(np0, np1 int, simple, adjust bool) { | ||||
| 	// Add the four phantom points. | ||||
| 	g.Points = append(g.Points, g.phantomPoints[:]...) | ||||
| 	// Scale the points. | ||||
| 	if simple && g.hinting != font.HintingNone { | ||||
| 		g.InFontUnits = append(g.InFontUnits, g.Points[np1:]...) | ||||
| 	} | ||||
| 	for i := np1; i < len(g.Points); i++ { | ||||
| 		p := &g.Points[i] | ||||
| 		p.X = g.font.scale(g.scale * p.X) | ||||
| 		p.Y = g.font.scale(g.scale * p.Y) | ||||
| 	} | ||||
| 	if g.hinting == font.HintingNone { | ||||
| 		return | ||||
| 	} | ||||
| 	// Round the 1st phantom point to the grid, shifting all other points equally. | ||||
| 	// Note that "all other points" starts from np0, not np1. | ||||
| 	// TODO: delete this adjustment and the np0/np1 distinction, when | ||||
| 	// we update the compatibility tests to C Freetype 2.5.3. | ||||
| 	// See http://git.savannah.gnu.org/cgit/freetype/freetype2.git/commit/?id=05c786d990390a7ca18e62962641dac740bacb06 | ||||
| 	if adjust { | ||||
| 		pp1x := g.Points[len(g.Points)-4].X | ||||
| 		if dx := ((pp1x + 32) &^ 63) - pp1x; dx != 0 { | ||||
| 			for i := np0; i < len(g.Points); i++ { | ||||
| 				g.Points[i].X += dx | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	if simple { | ||||
| 		g.Unhinted = append(g.Unhinted, g.Points[np1:]...) | ||||
| 	} | ||||
| 	// Round the 2nd and 4th phantom point to the grid. | ||||
| 	p := &g.Points[len(g.Points)-3] | ||||
| 	p.X = (p.X + 32) &^ 63 | ||||
| 	p = &g.Points[len(g.Points)-1] | ||||
| 	p.Y = (p.Y + 32) &^ 63 | ||||
| } | ||||
							
								
								
									
										1770
									
								
								vendor/github.com/golang/freetype/truetype/hint.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1770
									
								
								vendor/github.com/golang/freetype/truetype/hint.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										289
									
								
								vendor/github.com/golang/freetype/truetype/opcodes.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										289
									
								
								vendor/github.com/golang/freetype/truetype/opcodes.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,289 @@ | ||||
| // Copyright 2012 The Freetype-Go Authors. All rights reserved. | ||||
| // Use of this source code is governed by your choice of either the | ||||
| // FreeType License or the GNU General Public License version 2 (or | ||||
| // any later version), both of which can be found in the LICENSE file. | ||||
|  | ||||
| package truetype | ||||
|  | ||||
| // The Truetype opcodes are summarized at | ||||
| // https://developer.apple.com/fonts/TTRefMan/RM07/appendixA.html | ||||
|  | ||||
| const ( | ||||
| 	opSVTCA0    = 0x00 // Set freedom and projection Vectors To Coordinate Axis | ||||
| 	opSVTCA1    = 0x01 // . | ||||
| 	opSPVTCA0   = 0x02 // Set Projection Vector To Coordinate Axis | ||||
| 	opSPVTCA1   = 0x03 // . | ||||
| 	opSFVTCA0   = 0x04 // Set Freedom Vector to Coordinate Axis | ||||
| 	opSFVTCA1   = 0x05 // . | ||||
| 	opSPVTL0    = 0x06 // Set Projection Vector To Line | ||||
| 	opSPVTL1    = 0x07 // . | ||||
| 	opSFVTL0    = 0x08 // Set Freedom Vector To Line | ||||
| 	opSFVTL1    = 0x09 // . | ||||
| 	opSPVFS     = 0x0a // Set Projection Vector From Stack | ||||
| 	opSFVFS     = 0x0b // Set Freedom Vector From Stack | ||||
| 	opGPV       = 0x0c // Get Projection Vector | ||||
| 	opGFV       = 0x0d // Get Freedom Vector | ||||
| 	opSFVTPV    = 0x0e // Set Freedom Vector To Projection Vector | ||||
| 	opISECT     = 0x0f // moves point p to the InterSECTion of two lines | ||||
| 	opSRP0      = 0x10 // Set Reference Point 0 | ||||
| 	opSRP1      = 0x11 // Set Reference Point 1 | ||||
| 	opSRP2      = 0x12 // Set Reference Point 2 | ||||
| 	opSZP0      = 0x13 // Set Zone Pointer 0 | ||||
| 	opSZP1      = 0x14 // Set Zone Pointer 1 | ||||
| 	opSZP2      = 0x15 // Set Zone Pointer 2 | ||||
| 	opSZPS      = 0x16 // Set Zone PointerS | ||||
| 	opSLOOP     = 0x17 // Set LOOP variable | ||||
| 	opRTG       = 0x18 // Round To Grid | ||||
| 	opRTHG      = 0x19 // Round To Half Grid | ||||
| 	opSMD       = 0x1a // Set Minimum Distance | ||||
| 	opELSE      = 0x1b // ELSE clause | ||||
| 	opJMPR      = 0x1c // JuMP Relative | ||||
| 	opSCVTCI    = 0x1d // Set Control Value Table Cut-In | ||||
| 	opSSWCI     = 0x1e // Set Single Width Cut-In | ||||
| 	opSSW       = 0x1f // Set Single Width | ||||
| 	opDUP       = 0x20 // DUPlicate top stack element | ||||
| 	opPOP       = 0x21 // POP top stack element | ||||
| 	opCLEAR     = 0x22 // CLEAR the stack | ||||
| 	opSWAP      = 0x23 // SWAP the top two elements on the stack | ||||
| 	opDEPTH     = 0x24 // DEPTH of the stack | ||||
| 	opCINDEX    = 0x25 // Copy the INDEXed element to the top of the stack | ||||
| 	opMINDEX    = 0x26 // Move the INDEXed element to the top of the stack | ||||
| 	opALIGNPTS  = 0x27 // ALIGN PoinTS | ||||
| 	op_0x28     = 0x28 // deprecated | ||||
| 	opUTP       = 0x29 // UnTouch Point | ||||
| 	opLOOPCALL  = 0x2a // LOOP and CALL function | ||||
| 	opCALL      = 0x2b // CALL function | ||||
| 	opFDEF      = 0x2c // Function DEFinition | ||||
| 	opENDF      = 0x2d // END Function definition | ||||
| 	opMDAP0     = 0x2e // Move Direct Absolute Point | ||||
| 	opMDAP1     = 0x2f // . | ||||
| 	opIUP0      = 0x30 // Interpolate Untouched Points through the outline | ||||
| 	opIUP1      = 0x31 // . | ||||
| 	opSHP0      = 0x32 // SHift Point using reference point | ||||
| 	opSHP1      = 0x33 // . | ||||
| 	opSHC0      = 0x34 // SHift Contour using reference point | ||||
| 	opSHC1      = 0x35 // . | ||||
| 	opSHZ0      = 0x36 // SHift Zone using reference point | ||||
| 	opSHZ1      = 0x37 // . | ||||
| 	opSHPIX     = 0x38 // SHift point by a PIXel amount | ||||
| 	opIP        = 0x39 // Interpolate Point | ||||
| 	opMSIRP0    = 0x3a // Move Stack Indirect Relative Point | ||||
| 	opMSIRP1    = 0x3b // . | ||||
| 	opALIGNRP   = 0x3c // ALIGN to Reference Point | ||||
| 	opRTDG      = 0x3d // Round To Double Grid | ||||
| 	opMIAP0     = 0x3e // Move Indirect Absolute Point | ||||
| 	opMIAP1     = 0x3f // . | ||||
| 	opNPUSHB    = 0x40 // PUSH N Bytes | ||||
| 	opNPUSHW    = 0x41 // PUSH N Words | ||||
| 	opWS        = 0x42 // Write Store | ||||
| 	opRS        = 0x43 // Read Store | ||||
| 	opWCVTP     = 0x44 // Write Control Value Table in Pixel units | ||||
| 	opRCVT      = 0x45 // Read Control Value Table entry | ||||
| 	opGC0       = 0x46 // Get Coordinate projected onto the projection vector | ||||
| 	opGC1       = 0x47 // . | ||||
| 	opSCFS      = 0x48 // Sets Coordinate From the Stack using projection vector and freedom vector | ||||
| 	opMD0       = 0x49 // Measure Distance | ||||
| 	opMD1       = 0x4a // . | ||||
| 	opMPPEM     = 0x4b // Measure Pixels Per EM | ||||
| 	opMPS       = 0x4c // Measure Point Size | ||||
| 	opFLIPON    = 0x4d // set the auto FLIP Boolean to ON | ||||
| 	opFLIPOFF   = 0x4e // set the auto FLIP Boolean to OFF | ||||
| 	opDEBUG     = 0x4f // DEBUG call | ||||
| 	opLT        = 0x50 // Less Than | ||||
| 	opLTEQ      = 0x51 // Less Than or EQual | ||||
| 	opGT        = 0x52 // Greater Than | ||||
| 	opGTEQ      = 0x53 // Greater Than or EQual | ||||
| 	opEQ        = 0x54 // EQual | ||||
| 	opNEQ       = 0x55 // Not EQual | ||||
| 	opODD       = 0x56 // ODD | ||||
| 	opEVEN      = 0x57 // EVEN | ||||
| 	opIF        = 0x58 // IF test | ||||
| 	opEIF       = 0x59 // End IF | ||||
| 	opAND       = 0x5a // logical AND | ||||
| 	opOR        = 0x5b // logical OR | ||||
| 	opNOT       = 0x5c // logical NOT | ||||
| 	opDELTAP1   = 0x5d // DELTA exception P1 | ||||
| 	opSDB       = 0x5e // Set Delta Base in the graphics state | ||||
| 	opSDS       = 0x5f // Set Delta Shift in the graphics state | ||||
| 	opADD       = 0x60 // ADD | ||||
| 	opSUB       = 0x61 // SUBtract | ||||
| 	opDIV       = 0x62 // DIVide | ||||
| 	opMUL       = 0x63 // MULtiply | ||||
| 	opABS       = 0x64 // ABSolute value | ||||
| 	opNEG       = 0x65 // NEGate | ||||
| 	opFLOOR     = 0x66 // FLOOR | ||||
| 	opCEILING   = 0x67 // CEILING | ||||
| 	opROUND00   = 0x68 // ROUND value | ||||
| 	opROUND01   = 0x69 // . | ||||
| 	opROUND10   = 0x6a // . | ||||
| 	opROUND11   = 0x6b // . | ||||
| 	opNROUND00  = 0x6c // No ROUNDing of value | ||||
| 	opNROUND01  = 0x6d // . | ||||
| 	opNROUND10  = 0x6e // . | ||||
| 	opNROUND11  = 0x6f // . | ||||
| 	opWCVTF     = 0x70 // Write Control Value Table in Funits | ||||
| 	opDELTAP2   = 0x71 // DELTA exception P2 | ||||
| 	opDELTAP3   = 0x72 // DELTA exception P3 | ||||
| 	opDELTAC1   = 0x73 // DELTA exception C1 | ||||
| 	opDELTAC2   = 0x74 // DELTA exception C2 | ||||
| 	opDELTAC3   = 0x75 // DELTA exception C3 | ||||
| 	opSROUND    = 0x76 // Super ROUND | ||||
| 	opS45ROUND  = 0x77 // Super ROUND 45 degrees | ||||
| 	opJROT      = 0x78 // Jump Relative On True | ||||
| 	opJROF      = 0x79 // Jump Relative On False | ||||
| 	opROFF      = 0x7a // Round OFF | ||||
| 	op_0x7b     = 0x7b // deprecated | ||||
| 	opRUTG      = 0x7c // Round Up To Grid | ||||
| 	opRDTG      = 0x7d // Round Down To Grid | ||||
| 	opSANGW     = 0x7e // Set ANGle Weight | ||||
| 	opAA        = 0x7f // Adjust Angle | ||||
| 	opFLIPPT    = 0x80 // FLIP PoinT | ||||
| 	opFLIPRGON  = 0x81 // FLIP RanGe ON | ||||
| 	opFLIPRGOFF = 0x82 // FLIP RanGe OFF | ||||
| 	op_0x83     = 0x83 // deprecated | ||||
| 	op_0x84     = 0x84 // deprecated | ||||
| 	opSCANCTRL  = 0x85 // SCAN conversion ConTRoL | ||||
| 	opSDPVTL0   = 0x86 // Set Dual Projection Vector To Line | ||||
| 	opSDPVTL1   = 0x87 // . | ||||
| 	opGETINFO   = 0x88 // GET INFOrmation | ||||
| 	opIDEF      = 0x89 // Instruction DEFinition | ||||
| 	opROLL      = 0x8a // ROLL the top three stack elements | ||||
| 	opMAX       = 0x8b // MAXimum of top two stack elements | ||||
| 	opMIN       = 0x8c // MINimum of top two stack elements | ||||
| 	opSCANTYPE  = 0x8d // SCANTYPE | ||||
| 	opINSTCTRL  = 0x8e // INSTRuction execution ConTRoL | ||||
| 	op_0x8f     = 0x8f | ||||
| 	op_0x90     = 0x90 | ||||
| 	op_0x91     = 0x91 | ||||
| 	op_0x92     = 0x92 | ||||
| 	op_0x93     = 0x93 | ||||
| 	op_0x94     = 0x94 | ||||
| 	op_0x95     = 0x95 | ||||
| 	op_0x96     = 0x96 | ||||
| 	op_0x97     = 0x97 | ||||
| 	op_0x98     = 0x98 | ||||
| 	op_0x99     = 0x99 | ||||
| 	op_0x9a     = 0x9a | ||||
| 	op_0x9b     = 0x9b | ||||
| 	op_0x9c     = 0x9c | ||||
| 	op_0x9d     = 0x9d | ||||
| 	op_0x9e     = 0x9e | ||||
| 	op_0x9f     = 0x9f | ||||
| 	op_0xa0     = 0xa0 | ||||
| 	op_0xa1     = 0xa1 | ||||
| 	op_0xa2     = 0xa2 | ||||
| 	op_0xa3     = 0xa3 | ||||
| 	op_0xa4     = 0xa4 | ||||
| 	op_0xa5     = 0xa5 | ||||
| 	op_0xa6     = 0xa6 | ||||
| 	op_0xa7     = 0xa7 | ||||
| 	op_0xa8     = 0xa8 | ||||
| 	op_0xa9     = 0xa9 | ||||
| 	op_0xaa     = 0xaa | ||||
| 	op_0xab     = 0xab | ||||
| 	op_0xac     = 0xac | ||||
| 	op_0xad     = 0xad | ||||
| 	op_0xae     = 0xae | ||||
| 	op_0xaf     = 0xaf | ||||
| 	opPUSHB000  = 0xb0 // PUSH Bytes | ||||
| 	opPUSHB001  = 0xb1 // . | ||||
| 	opPUSHB010  = 0xb2 // . | ||||
| 	opPUSHB011  = 0xb3 // . | ||||
| 	opPUSHB100  = 0xb4 // . | ||||
| 	opPUSHB101  = 0xb5 // . | ||||
| 	opPUSHB110  = 0xb6 // . | ||||
| 	opPUSHB111  = 0xb7 // . | ||||
| 	opPUSHW000  = 0xb8 // PUSH Words | ||||
| 	opPUSHW001  = 0xb9 // . | ||||
| 	opPUSHW010  = 0xba // . | ||||
| 	opPUSHW011  = 0xbb // . | ||||
| 	opPUSHW100  = 0xbc // . | ||||
| 	opPUSHW101  = 0xbd // . | ||||
| 	opPUSHW110  = 0xbe // . | ||||
| 	opPUSHW111  = 0xbf // . | ||||
| 	opMDRP00000 = 0xc0 // Move Direct Relative Point | ||||
| 	opMDRP00001 = 0xc1 // . | ||||
| 	opMDRP00010 = 0xc2 // . | ||||
| 	opMDRP00011 = 0xc3 // . | ||||
| 	opMDRP00100 = 0xc4 // . | ||||
| 	opMDRP00101 = 0xc5 // . | ||||
| 	opMDRP00110 = 0xc6 // . | ||||
| 	opMDRP00111 = 0xc7 // . | ||||
| 	opMDRP01000 = 0xc8 // . | ||||
| 	opMDRP01001 = 0xc9 // . | ||||
| 	opMDRP01010 = 0xca // . | ||||
| 	opMDRP01011 = 0xcb // . | ||||
| 	opMDRP01100 = 0xcc // . | ||||
| 	opMDRP01101 = 0xcd // . | ||||
| 	opMDRP01110 = 0xce // . | ||||
| 	opMDRP01111 = 0xcf // . | ||||
| 	opMDRP10000 = 0xd0 // . | ||||
| 	opMDRP10001 = 0xd1 // . | ||||
| 	opMDRP10010 = 0xd2 // . | ||||
| 	opMDRP10011 = 0xd3 // . | ||||
| 	opMDRP10100 = 0xd4 // . | ||||
| 	opMDRP10101 = 0xd5 // . | ||||
| 	opMDRP10110 = 0xd6 // . | ||||
| 	opMDRP10111 = 0xd7 // . | ||||
| 	opMDRP11000 = 0xd8 // . | ||||
| 	opMDRP11001 = 0xd9 // . | ||||
| 	opMDRP11010 = 0xda // . | ||||
| 	opMDRP11011 = 0xdb // . | ||||
| 	opMDRP11100 = 0xdc // . | ||||
| 	opMDRP11101 = 0xdd // . | ||||
| 	opMDRP11110 = 0xde // . | ||||
| 	opMDRP11111 = 0xdf // . | ||||
| 	opMIRP00000 = 0xe0 // Move Indirect Relative Point | ||||
| 	opMIRP00001 = 0xe1 // . | ||||
| 	opMIRP00010 = 0xe2 // . | ||||
| 	opMIRP00011 = 0xe3 // . | ||||
| 	opMIRP00100 = 0xe4 // . | ||||
| 	opMIRP00101 = 0xe5 // . | ||||
| 	opMIRP00110 = 0xe6 // . | ||||
| 	opMIRP00111 = 0xe7 // . | ||||
| 	opMIRP01000 = 0xe8 // . | ||||
| 	opMIRP01001 = 0xe9 // . | ||||
| 	opMIRP01010 = 0xea // . | ||||
| 	opMIRP01011 = 0xeb // . | ||||
| 	opMIRP01100 = 0xec // . | ||||
| 	opMIRP01101 = 0xed // . | ||||
| 	opMIRP01110 = 0xee // . | ||||
| 	opMIRP01111 = 0xef // . | ||||
| 	opMIRP10000 = 0xf0 // . | ||||
| 	opMIRP10001 = 0xf1 // . | ||||
| 	opMIRP10010 = 0xf2 // . | ||||
| 	opMIRP10011 = 0xf3 // . | ||||
| 	opMIRP10100 = 0xf4 // . | ||||
| 	opMIRP10101 = 0xf5 // . | ||||
| 	opMIRP10110 = 0xf6 // . | ||||
| 	opMIRP10111 = 0xf7 // . | ||||
| 	opMIRP11000 = 0xf8 // . | ||||
| 	opMIRP11001 = 0xf9 // . | ||||
| 	opMIRP11010 = 0xfa // . | ||||
| 	opMIRP11011 = 0xfb // . | ||||
| 	opMIRP11100 = 0xfc // . | ||||
| 	opMIRP11101 = 0xfd // . | ||||
| 	opMIRP11110 = 0xfe // . | ||||
| 	opMIRP11111 = 0xff // . | ||||
| ) | ||||
|  | ||||
| // popCount is the number of stack elements that each opcode pops. | ||||
| var popCount = [256]uint8{ | ||||
| 	// 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f | ||||
| 	0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 5, // 0x00 - 0x0f | ||||
| 	1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, // 0x10 - 0x1f | ||||
| 	1, 1, 0, 2, 0, 1, 1, 2, 0, 1, 2, 1, 1, 0, 1, 1, // 0x20 - 0x2f | ||||
| 	0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 2, 2, 0, 0, 2, 2, // 0x30 - 0x3f | ||||
| 	0, 0, 2, 1, 2, 1, 1, 1, 2, 2, 2, 0, 0, 0, 0, 0, // 0x40 - 0x4f | ||||
| 	2, 2, 2, 2, 2, 2, 1, 1, 1, 0, 2, 2, 1, 1, 1, 1, // 0x50 - 0x5f | ||||
| 	2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6f | ||||
| 	2, 1, 1, 1, 1, 1, 1, 1, 2, 2, 0, 0, 0, 0, 1, 1, // 0x70 - 0x7f | ||||
| 	0, 2, 2, 0, 0, 1, 2, 2, 1, 1, 3, 2, 2, 1, 2, 0, // 0x80 - 0x8f | ||||
| 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x90 - 0x9f | ||||
| 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xa0 - 0xaf | ||||
| 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xb0 - 0xbf | ||||
| 	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xc0 - 0xcf | ||||
| 	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xd0 - 0xdf | ||||
| 	2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xe0 - 0xef | ||||
| 	2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xf0 - 0xff | ||||
| } | ||||
							
								
								
									
										653
									
								
								vendor/github.com/golang/freetype/truetype/truetype.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										653
									
								
								vendor/github.com/golang/freetype/truetype/truetype.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,653 @@ | ||||
| // Copyright 2010 The Freetype-Go Authors. All rights reserved. | ||||
| // Use of this source code is governed by your choice of either the | ||||
| // FreeType License or the GNU General Public License version 2 (or | ||||
| // any later version), both of which can be found in the LICENSE file. | ||||
|  | ||||
| // Package truetype provides a parser for the TTF and TTC file formats. | ||||
| // Those formats are documented at http://developer.apple.com/fonts/TTRefMan/ | ||||
| // and http://www.microsoft.com/typography/otspec/ | ||||
| // | ||||
| // Some of a font's methods provide lengths or co-ordinates, e.g. bounds, font | ||||
| // metrics and control points. All these methods take a scale parameter, which | ||||
| // is the number of pixels in 1 em, expressed as a 26.6 fixed point value. For | ||||
| // example, if 1 em is 10 pixels then scale is fixed.I(10), which is equal to | ||||
| // fixed.Int26_6(10 << 6). | ||||
| // | ||||
| // To measure a TrueType font in ideal FUnit space, use scale equal to | ||||
| // font.FUnitsPerEm(). | ||||
| package truetype // import "github.com/golang/freetype/truetype" | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	"golang.org/x/image/math/fixed" | ||||
| ) | ||||
|  | ||||
| // An Index is a Font's index of a rune. | ||||
| type Index uint16 | ||||
|  | ||||
| // A NameID identifies a name table entry. | ||||
| // | ||||
| // See https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6name.html | ||||
| type NameID uint16 | ||||
|  | ||||
| const ( | ||||
| 	NameIDCopyright          NameID = 0 | ||||
| 	NameIDFontFamily                = 1 | ||||
| 	NameIDFontSubfamily             = 2 | ||||
| 	NameIDUniqueSubfamilyID         = 3 | ||||
| 	NameIDFontFullName              = 4 | ||||
| 	NameIDNameTableVersion          = 5 | ||||
| 	NameIDPostscriptName            = 6 | ||||
| 	NameIDTrademarkNotice           = 7 | ||||
| 	NameIDManufacturerName          = 8 | ||||
| 	NameIDDesignerName              = 9 | ||||
| 	NameIDFontDescription           = 10 | ||||
| 	NameIDFontVendorURL             = 11 | ||||
| 	NameIDFontDesignerURL           = 12 | ||||
| 	NameIDFontLicense               = 13 | ||||
| 	NameIDFontLicenseURL            = 14 | ||||
| 	NameIDPreferredFamily           = 16 | ||||
| 	NameIDPreferredSubfamily        = 17 | ||||
| 	NameIDCompatibleName            = 18 | ||||
| 	NameIDSampleText                = 19 | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	// A 32-bit encoding consists of a most-significant 16-bit Platform ID and a | ||||
| 	// least-significant 16-bit Platform Specific ID. The magic numbers are | ||||
| 	// specified at https://www.microsoft.com/typography/otspec/name.htm | ||||
| 	unicodeEncodingBMPOnly  = 0x00000003 // PID = 0 (Unicode), PSID = 3 (Unicode 2.0 BMP Only) | ||||
| 	unicodeEncodingFull     = 0x00000004 // PID = 0 (Unicode), PSID = 4 (Unicode 2.0 Full Repertoire) | ||||
| 	microsoftSymbolEncoding = 0x00030000 // PID = 3 (Microsoft), PSID = 0 (Symbol) | ||||
| 	microsoftUCS2Encoding   = 0x00030001 // PID = 3 (Microsoft), PSID = 1 (UCS-2) | ||||
| 	microsoftUCS4Encoding   = 0x0003000a // PID = 3 (Microsoft), PSID = 10 (UCS-4) | ||||
| ) | ||||
|  | ||||
| // An HMetric holds the horizontal metrics of a single glyph. | ||||
| type HMetric struct { | ||||
| 	AdvanceWidth, LeftSideBearing fixed.Int26_6 | ||||
| } | ||||
|  | ||||
| // A VMetric holds the vertical metrics of a single glyph. | ||||
| type VMetric struct { | ||||
| 	AdvanceHeight, TopSideBearing fixed.Int26_6 | ||||
| } | ||||
|  | ||||
| // A FormatError reports that the input is not a valid TrueType font. | ||||
| type FormatError string | ||||
|  | ||||
| func (e FormatError) Error() string { | ||||
| 	return "freetype: invalid TrueType format: " + string(e) | ||||
| } | ||||
|  | ||||
| // An UnsupportedError reports that the input uses a valid but unimplemented | ||||
| // TrueType feature. | ||||
| type UnsupportedError string | ||||
|  | ||||
| func (e UnsupportedError) Error() string { | ||||
| 	return "freetype: unsupported TrueType feature: " + string(e) | ||||
| } | ||||
|  | ||||
| // u32 returns the big-endian uint32 at b[i:]. | ||||
| func u32(b []byte, i int) uint32 { | ||||
| 	return uint32(b[i])<<24 | uint32(b[i+1])<<16 | uint32(b[i+2])<<8 | uint32(b[i+3]) | ||||
| } | ||||
|  | ||||
| // u16 returns the big-endian uint16 at b[i:]. | ||||
| func u16(b []byte, i int) uint16 { | ||||
| 	return uint16(b[i])<<8 | uint16(b[i+1]) | ||||
| } | ||||
|  | ||||
| // readTable returns a slice of the TTF data given by a table's directory entry. | ||||
| func readTable(ttf []byte, offsetLength []byte) ([]byte, error) { | ||||
| 	offset := int(u32(offsetLength, 0)) | ||||
| 	if offset < 0 { | ||||
| 		return nil, FormatError(fmt.Sprintf("offset too large: %d", uint32(offset))) | ||||
| 	} | ||||
| 	length := int(u32(offsetLength, 4)) | ||||
| 	if length < 0 { | ||||
| 		return nil, FormatError(fmt.Sprintf("length too large: %d", uint32(length))) | ||||
| 	} | ||||
| 	end := offset + length | ||||
| 	if end < 0 || end > len(ttf) { | ||||
| 		return nil, FormatError(fmt.Sprintf("offset + length too large: %d", uint32(offset)+uint32(length))) | ||||
| 	} | ||||
| 	return ttf[offset:end], nil | ||||
| } | ||||
|  | ||||
| // parseSubtables returns the offset and platformID of the best subtable in | ||||
| // table, where best favors a Unicode cmap encoding, and failing that, a | ||||
| // Microsoft cmap encoding. offset is the offset of the first subtable in | ||||
| // table, and size is the size of each subtable. | ||||
| // | ||||
| // If pred is non-nil, then only subtables that satisfy that predicate will be | ||||
| // considered. | ||||
| func parseSubtables(table []byte, name string, offset, size int, pred func([]byte) bool) ( | ||||
| 	bestOffset int, bestPID uint32, retErr error) { | ||||
|  | ||||
| 	if len(table) < 4 { | ||||
| 		return 0, 0, FormatError(name + " too short") | ||||
| 	} | ||||
| 	nSubtables := int(u16(table, 2)) | ||||
| 	if len(table) < size*nSubtables+offset { | ||||
| 		return 0, 0, FormatError(name + " too short") | ||||
| 	} | ||||
| 	ok := false | ||||
| 	for i := 0; i < nSubtables; i, offset = i+1, offset+size { | ||||
| 		if pred != nil && !pred(table[offset:]) { | ||||
| 			continue | ||||
| 		} | ||||
| 		// We read the 16-bit Platform ID and 16-bit Platform Specific ID as a single uint32. | ||||
| 		// All values are big-endian. | ||||
| 		pidPsid := u32(table, offset) | ||||
| 		// We prefer the Unicode cmap encoding. Failing to find that, we fall | ||||
| 		// back onto the Microsoft cmap encoding. | ||||
| 		if pidPsid == unicodeEncodingBMPOnly || pidPsid == unicodeEncodingFull { | ||||
| 			bestOffset, bestPID, ok = offset, pidPsid>>16, true | ||||
| 			break | ||||
|  | ||||
| 		} else if pidPsid == microsoftSymbolEncoding || | ||||
| 			pidPsid == microsoftUCS2Encoding || | ||||
| 			pidPsid == microsoftUCS4Encoding { | ||||
|  | ||||
| 			bestOffset, bestPID, ok = offset, pidPsid>>16, true | ||||
| 			// We don't break out of the for loop, so that Unicode can override Microsoft. | ||||
| 		} | ||||
| 	} | ||||
| 	if !ok { | ||||
| 		return 0, 0, UnsupportedError(name + " encoding") | ||||
| 	} | ||||
| 	return bestOffset, bestPID, nil | ||||
| } | ||||
|  | ||||
| const ( | ||||
| 	locaOffsetFormatUnknown int = iota | ||||
| 	locaOffsetFormatShort | ||||
| 	locaOffsetFormatLong | ||||
| ) | ||||
|  | ||||
| // A cm holds a parsed cmap entry. | ||||
| type cm struct { | ||||
| 	start, end, delta, offset uint32 | ||||
| } | ||||
|  | ||||
| // A Font represents a Truetype font. | ||||
| type Font struct { | ||||
| 	// Tables sliced from the TTF data. The different tables are documented | ||||
| 	// at http://developer.apple.com/fonts/TTRefMan/RM06/Chap6.html | ||||
| 	cmap, cvt, fpgm, glyf, hdmx, head, hhea, hmtx, kern, loca, maxp, name, os2, prep, vmtx []byte | ||||
|  | ||||
| 	cmapIndexes []byte | ||||
|  | ||||
| 	// Cached values derived from the raw ttf data. | ||||
| 	cm                      []cm | ||||
| 	locaOffsetFormat        int | ||||
| 	nGlyph, nHMetric, nKern int | ||||
| 	fUnitsPerEm             int32 | ||||
| 	ascent                  int32               // In FUnits. | ||||
| 	descent                 int32               // In FUnits; typically negative. | ||||
| 	bounds                  fixed.Rectangle26_6 // In FUnits. | ||||
| 	// Values from the maxp section. | ||||
| 	maxTwilightPoints, maxStorage, maxFunctionDefs, maxStackElements uint16 | ||||
| } | ||||
|  | ||||
| func (f *Font) parseCmap() error { | ||||
| 	const ( | ||||
| 		cmapFormat4         = 4 | ||||
| 		cmapFormat12        = 12 | ||||
| 		languageIndependent = 0 | ||||
| 	) | ||||
|  | ||||
| 	offset, _, err := parseSubtables(f.cmap, "cmap", 4, 8, nil) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	offset = int(u32(f.cmap, offset+4)) | ||||
| 	if offset <= 0 || offset > len(f.cmap) { | ||||
| 		return FormatError("bad cmap offset") | ||||
| 	} | ||||
|  | ||||
| 	cmapFormat := u16(f.cmap, offset) | ||||
| 	switch cmapFormat { | ||||
| 	case cmapFormat4: | ||||
| 		language := u16(f.cmap, offset+4) | ||||
| 		if language != languageIndependent { | ||||
| 			return UnsupportedError(fmt.Sprintf("language: %d", language)) | ||||
| 		} | ||||
| 		segCountX2 := int(u16(f.cmap, offset+6)) | ||||
| 		if segCountX2%2 == 1 { | ||||
| 			return FormatError(fmt.Sprintf("bad segCountX2: %d", segCountX2)) | ||||
| 		} | ||||
| 		segCount := segCountX2 / 2 | ||||
| 		offset += 14 | ||||
| 		f.cm = make([]cm, segCount) | ||||
| 		for i := 0; i < segCount; i++ { | ||||
| 			f.cm[i].end = uint32(u16(f.cmap, offset)) | ||||
| 			offset += 2 | ||||
| 		} | ||||
| 		offset += 2 | ||||
| 		for i := 0; i < segCount; i++ { | ||||
| 			f.cm[i].start = uint32(u16(f.cmap, offset)) | ||||
| 			offset += 2 | ||||
| 		} | ||||
| 		for i := 0; i < segCount; i++ { | ||||
| 			f.cm[i].delta = uint32(u16(f.cmap, offset)) | ||||
| 			offset += 2 | ||||
| 		} | ||||
| 		for i := 0; i < segCount; i++ { | ||||
| 			f.cm[i].offset = uint32(u16(f.cmap, offset)) | ||||
| 			offset += 2 | ||||
| 		} | ||||
| 		f.cmapIndexes = f.cmap[offset:] | ||||
| 		return nil | ||||
|  | ||||
| 	case cmapFormat12: | ||||
| 		if u16(f.cmap, offset+2) != 0 { | ||||
| 			return FormatError(fmt.Sprintf("cmap format: % x", f.cmap[offset:offset+4])) | ||||
| 		} | ||||
| 		length := u32(f.cmap, offset+4) | ||||
| 		language := u32(f.cmap, offset+8) | ||||
| 		if language != languageIndependent { | ||||
| 			return UnsupportedError(fmt.Sprintf("language: %d", language)) | ||||
| 		} | ||||
| 		nGroups := u32(f.cmap, offset+12) | ||||
| 		if length != 12*nGroups+16 { | ||||
| 			return FormatError("inconsistent cmap length") | ||||
| 		} | ||||
| 		offset += 16 | ||||
| 		f.cm = make([]cm, nGroups) | ||||
| 		for i := uint32(0); i < nGroups; i++ { | ||||
| 			f.cm[i].start = u32(f.cmap, offset+0) | ||||
| 			f.cm[i].end = u32(f.cmap, offset+4) | ||||
| 			f.cm[i].delta = u32(f.cmap, offset+8) - f.cm[i].start | ||||
| 			offset += 12 | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| 	return UnsupportedError(fmt.Sprintf("cmap format: %d", cmapFormat)) | ||||
| } | ||||
|  | ||||
| func (f *Font) parseHead() error { | ||||
| 	if len(f.head) != 54 { | ||||
| 		return FormatError(fmt.Sprintf("bad head length: %d", len(f.head))) | ||||
| 	} | ||||
| 	f.fUnitsPerEm = int32(u16(f.head, 18)) | ||||
| 	f.bounds.Min.X = fixed.Int26_6(int16(u16(f.head, 36))) | ||||
| 	f.bounds.Min.Y = fixed.Int26_6(int16(u16(f.head, 38))) | ||||
| 	f.bounds.Max.X = fixed.Int26_6(int16(u16(f.head, 40))) | ||||
| 	f.bounds.Max.Y = fixed.Int26_6(int16(u16(f.head, 42))) | ||||
| 	switch i := u16(f.head, 50); i { | ||||
| 	case 0: | ||||
| 		f.locaOffsetFormat = locaOffsetFormatShort | ||||
| 	case 1: | ||||
| 		f.locaOffsetFormat = locaOffsetFormatLong | ||||
| 	default: | ||||
| 		return FormatError(fmt.Sprintf("bad indexToLocFormat: %d", i)) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (f *Font) parseHhea() error { | ||||
| 	if len(f.hhea) != 36 { | ||||
| 		return FormatError(fmt.Sprintf("bad hhea length: %d", len(f.hhea))) | ||||
| 	} | ||||
| 	f.ascent = int32(int16(u16(f.hhea, 4))) | ||||
| 	f.descent = int32(int16(u16(f.hhea, 6))) | ||||
| 	f.nHMetric = int(u16(f.hhea, 34)) | ||||
| 	if 4*f.nHMetric+2*(f.nGlyph-f.nHMetric) != len(f.hmtx) { | ||||
| 		return FormatError(fmt.Sprintf("bad hmtx length: %d", len(f.hmtx))) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (f *Font) parseKern() error { | ||||
| 	// Apple's TrueType documentation (http://developer.apple.com/fonts/TTRefMan/RM06/Chap6kern.html) says: | ||||
| 	// "Previous versions of the 'kern' table defined both the version and nTables fields in the header | ||||
| 	// as UInt16 values and not UInt32 values. Use of the older format on the Mac OS is discouraged | ||||
| 	// (although AAT can sense an old kerning table and still make correct use of it). Microsoft | ||||
| 	// Windows still uses the older format for the 'kern' table and will not recognize the newer one. | ||||
| 	// Fonts targeted for the Mac OS only should use the new format; fonts targeted for both the Mac OS | ||||
| 	// and Windows should use the old format." | ||||
| 	// Since we expect that almost all fonts aim to be Windows-compatible, we only parse the "older" format, | ||||
| 	// just like the C Freetype implementation. | ||||
| 	if len(f.kern) == 0 { | ||||
| 		if f.nKern != 0 { | ||||
| 			return FormatError("bad kern table length") | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| 	if len(f.kern) < 18 { | ||||
| 		return FormatError("kern data too short") | ||||
| 	} | ||||
| 	version, offset := u16(f.kern, 0), 2 | ||||
| 	if version != 0 { | ||||
| 		return UnsupportedError(fmt.Sprintf("kern version: %d", version)) | ||||
| 	} | ||||
|  | ||||
| 	n, offset := u16(f.kern, offset), offset+2 | ||||
| 	if n == 0 { | ||||
| 		return UnsupportedError("kern nTables: 0") | ||||
| 	} | ||||
| 	// TODO: support multiple subtables. In practice, almost all .ttf files | ||||
| 	// have only one subtable, if they have a kern table at all. But it's not | ||||
| 	// impossible. Xolonium Regular (https://fontlibrary.org/en/font/xolonium) | ||||
| 	// has 3 subtables. Those subtables appear to be disjoint, rather than | ||||
| 	// being the same kerning pairs encoded in three different ways. | ||||
| 	// | ||||
| 	// For now, we'll use only the first subtable. | ||||
|  | ||||
| 	offset += 2 // Skip the version. | ||||
| 	length, offset := int(u16(f.kern, offset)), offset+2 | ||||
| 	coverage, offset := u16(f.kern, offset), offset+2 | ||||
| 	if coverage != 0x0001 { | ||||
| 		// We only support horizontal kerning. | ||||
| 		return UnsupportedError(fmt.Sprintf("kern coverage: 0x%04x", coverage)) | ||||
| 	} | ||||
| 	f.nKern, offset = int(u16(f.kern, offset)), offset+2 | ||||
| 	if 6*f.nKern != length-14 { | ||||
| 		return FormatError("bad kern table length") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (f *Font) parseMaxp() error { | ||||
| 	if len(f.maxp) != 32 { | ||||
| 		return FormatError(fmt.Sprintf("bad maxp length: %d", len(f.maxp))) | ||||
| 	} | ||||
| 	f.nGlyph = int(u16(f.maxp, 4)) | ||||
| 	f.maxTwilightPoints = u16(f.maxp, 16) | ||||
| 	f.maxStorage = u16(f.maxp, 18) | ||||
| 	f.maxFunctionDefs = u16(f.maxp, 20) | ||||
| 	f.maxStackElements = u16(f.maxp, 24) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // scale returns x divided by f.fUnitsPerEm, rounded to the nearest integer. | ||||
| func (f *Font) scale(x fixed.Int26_6) fixed.Int26_6 { | ||||
| 	if x >= 0 { | ||||
| 		x += fixed.Int26_6(f.fUnitsPerEm) / 2 | ||||
| 	} else { | ||||
| 		x -= fixed.Int26_6(f.fUnitsPerEm) / 2 | ||||
| 	} | ||||
| 	return x / fixed.Int26_6(f.fUnitsPerEm) | ||||
| } | ||||
|  | ||||
| // Bounds returns the union of a Font's glyphs' bounds. | ||||
| func (f *Font) Bounds(scale fixed.Int26_6) fixed.Rectangle26_6 { | ||||
| 	b := f.bounds | ||||
| 	b.Min.X = f.scale(scale * b.Min.X) | ||||
| 	b.Min.Y = f.scale(scale * b.Min.Y) | ||||
| 	b.Max.X = f.scale(scale * b.Max.X) | ||||
| 	b.Max.Y = f.scale(scale * b.Max.Y) | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| // FUnitsPerEm returns the number of FUnits in a Font's em-square's side. | ||||
| func (f *Font) FUnitsPerEm() int32 { | ||||
| 	return f.fUnitsPerEm | ||||
| } | ||||
|  | ||||
| // Index returns a Font's index for the given rune. | ||||
| func (f *Font) Index(x rune) Index { | ||||
| 	c := uint32(x) | ||||
| 	for i, j := 0, len(f.cm); i < j; { | ||||
| 		h := i + (j-i)/2 | ||||
| 		cm := &f.cm[h] | ||||
| 		if c < cm.start { | ||||
| 			j = h | ||||
| 		} else if cm.end < c { | ||||
| 			i = h + 1 | ||||
| 		} else if cm.offset == 0 { | ||||
| 			return Index(c + cm.delta) | ||||
| 		} else { | ||||
| 			offset := int(cm.offset) + 2*(h-len(f.cm)+int(c-cm.start)) | ||||
| 			return Index(u16(f.cmapIndexes, offset)) | ||||
| 		} | ||||
| 	} | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| // Name returns the Font's name value for the given NameID. It returns "" if | ||||
| // there was an error, or if that name was not found. | ||||
| func (f *Font) Name(id NameID) string { | ||||
| 	x, platformID, err := parseSubtables(f.name, "name", 6, 12, func(b []byte) bool { | ||||
| 		return NameID(u16(b, 6)) == id | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} | ||||
| 	offset, length := u16(f.name, 4)+u16(f.name, x+10), u16(f.name, x+8) | ||||
| 	// Return the ASCII value of the encoded string. | ||||
| 	// The string is encoded as UTF-16 on non-Apple platformIDs; Apple is platformID 1. | ||||
| 	src := f.name[offset : offset+length] | ||||
| 	var dst []byte | ||||
| 	if platformID != 1 { // UTF-16. | ||||
| 		if len(src)&1 != 0 { | ||||
| 			return "" | ||||
| 		} | ||||
| 		dst = make([]byte, len(src)/2) | ||||
| 		for i := range dst { | ||||
| 			dst[i] = printable(u16(src, 2*i)) | ||||
| 		} | ||||
| 	} else { // ASCII. | ||||
| 		dst = make([]byte, len(src)) | ||||
| 		for i, c := range src { | ||||
| 			dst[i] = printable(uint16(c)) | ||||
| 		} | ||||
| 	} | ||||
| 	return string(dst) | ||||
| } | ||||
|  | ||||
| func printable(r uint16) byte { | ||||
| 	if 0x20 <= r && r < 0x7f { | ||||
| 		return byte(r) | ||||
| 	} | ||||
| 	return '?' | ||||
| } | ||||
|  | ||||
| // unscaledHMetric returns the unscaled horizontal metrics for the glyph with | ||||
| // the given index. | ||||
| func (f *Font) unscaledHMetric(i Index) (h HMetric) { | ||||
| 	j := int(i) | ||||
| 	if j < 0 || f.nGlyph <= j { | ||||
| 		return HMetric{} | ||||
| 	} | ||||
| 	if j >= f.nHMetric { | ||||
| 		p := 4 * (f.nHMetric - 1) | ||||
| 		return HMetric{ | ||||
| 			AdvanceWidth:    fixed.Int26_6(u16(f.hmtx, p)), | ||||
| 			LeftSideBearing: fixed.Int26_6(int16(u16(f.hmtx, p+2*(j-f.nHMetric)+4))), | ||||
| 		} | ||||
| 	} | ||||
| 	return HMetric{ | ||||
| 		AdvanceWidth:    fixed.Int26_6(u16(f.hmtx, 4*j)), | ||||
| 		LeftSideBearing: fixed.Int26_6(int16(u16(f.hmtx, 4*j+2))), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // HMetric returns the horizontal metrics for the glyph with the given index. | ||||
| func (f *Font) HMetric(scale fixed.Int26_6, i Index) HMetric { | ||||
| 	h := f.unscaledHMetric(i) | ||||
| 	h.AdvanceWidth = f.scale(scale * h.AdvanceWidth) | ||||
| 	h.LeftSideBearing = f.scale(scale * h.LeftSideBearing) | ||||
| 	return h | ||||
| } | ||||
|  | ||||
| // unscaledVMetric returns the unscaled vertical metrics for the glyph with | ||||
| // the given index. yMax is the top of the glyph's bounding box. | ||||
| func (f *Font) unscaledVMetric(i Index, yMax fixed.Int26_6) (v VMetric) { | ||||
| 	j := int(i) | ||||
| 	if j < 0 || f.nGlyph <= j { | ||||
| 		return VMetric{} | ||||
| 	} | ||||
| 	if 4*j+4 <= len(f.vmtx) { | ||||
| 		return VMetric{ | ||||
| 			AdvanceHeight:  fixed.Int26_6(u16(f.vmtx, 4*j)), | ||||
| 			TopSideBearing: fixed.Int26_6(int16(u16(f.vmtx, 4*j+2))), | ||||
| 		} | ||||
| 	} | ||||
| 	// The OS/2 table has grown over time. | ||||
| 	// https://developer.apple.com/fonts/TTRefMan/RM06/Chap6OS2.html | ||||
| 	// says that it was originally 68 bytes. Optional fields, including | ||||
| 	// the ascender and descender, are described at | ||||
| 	// http://www.microsoft.com/typography/otspec/os2.htm | ||||
| 	if len(f.os2) >= 72 { | ||||
| 		sTypoAscender := fixed.Int26_6(int16(u16(f.os2, 68))) | ||||
| 		sTypoDescender := fixed.Int26_6(int16(u16(f.os2, 70))) | ||||
| 		return VMetric{ | ||||
| 			AdvanceHeight:  sTypoAscender - sTypoDescender, | ||||
| 			TopSideBearing: sTypoAscender - yMax, | ||||
| 		} | ||||
| 	} | ||||
| 	return VMetric{ | ||||
| 		AdvanceHeight:  fixed.Int26_6(f.fUnitsPerEm), | ||||
| 		TopSideBearing: 0, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // VMetric returns the vertical metrics for the glyph with the given index. | ||||
| func (f *Font) VMetric(scale fixed.Int26_6, i Index) VMetric { | ||||
| 	// TODO: should 0 be bounds.YMax? | ||||
| 	v := f.unscaledVMetric(i, 0) | ||||
| 	v.AdvanceHeight = f.scale(scale * v.AdvanceHeight) | ||||
| 	v.TopSideBearing = f.scale(scale * v.TopSideBearing) | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| // Kern returns the horizontal adjustment for the given glyph pair. A positive | ||||
| // kern means to move the glyphs further apart. | ||||
| func (f *Font) Kern(scale fixed.Int26_6, i0, i1 Index) fixed.Int26_6 { | ||||
| 	if f.nKern == 0 { | ||||
| 		return 0 | ||||
| 	} | ||||
| 	g := uint32(i0)<<16 | uint32(i1) | ||||
| 	lo, hi := 0, f.nKern | ||||
| 	for lo < hi { | ||||
| 		i := (lo + hi) / 2 | ||||
| 		ig := u32(f.kern, 18+6*i) | ||||
| 		if ig < g { | ||||
| 			lo = i + 1 | ||||
| 		} else if ig > g { | ||||
| 			hi = i | ||||
| 		} else { | ||||
| 			return f.scale(scale * fixed.Int26_6(int16(u16(f.kern, 22+6*i)))) | ||||
| 		} | ||||
| 	} | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| // Parse returns a new Font for the given TTF or TTC data. | ||||
| // | ||||
| // For TrueType Collections, the first font in the collection is parsed. | ||||
| func Parse(ttf []byte) (font *Font, err error) { | ||||
| 	return parse(ttf, 0) | ||||
| } | ||||
|  | ||||
| func parse(ttf []byte, offset int) (font *Font, err error) { | ||||
| 	if len(ttf)-offset < 12 { | ||||
| 		err = FormatError("TTF data is too short") | ||||
| 		return | ||||
| 	} | ||||
| 	originalOffset := offset | ||||
| 	magic, offset := u32(ttf, offset), offset+4 | ||||
| 	switch magic { | ||||
| 	case 0x00010000: | ||||
| 		// No-op. | ||||
| 	case 0x74746366: // "ttcf" as a big-endian uint32. | ||||
| 		if originalOffset != 0 { | ||||
| 			err = FormatError("recursive TTC") | ||||
| 			return | ||||
| 		} | ||||
| 		ttcVersion, offset := u32(ttf, offset), offset+4 | ||||
| 		if ttcVersion != 0x00010000 && ttcVersion != 0x00020000 { | ||||
| 			err = FormatError("bad TTC version") | ||||
| 			return | ||||
| 		} | ||||
| 		numFonts, offset := int(u32(ttf, offset)), offset+4 | ||||
| 		if numFonts <= 0 { | ||||
| 			err = FormatError("bad number of TTC fonts") | ||||
| 			return | ||||
| 		} | ||||
| 		if len(ttf[offset:])/4 < numFonts { | ||||
| 			err = FormatError("TTC offset table is too short") | ||||
| 			return | ||||
| 		} | ||||
| 		// TODO: provide an API to select which font in a TrueType collection to return, | ||||
| 		// not just the first one. This may require an API to parse a TTC's name tables, | ||||
| 		// so users of this package can select the font in a TTC by name. | ||||
| 		offset = int(u32(ttf, offset)) | ||||
| 		if offset <= 0 || offset > len(ttf) { | ||||
| 			err = FormatError("bad TTC offset") | ||||
| 			return | ||||
| 		} | ||||
| 		return parse(ttf, offset) | ||||
| 	default: | ||||
| 		err = FormatError("bad TTF version") | ||||
| 		return | ||||
| 	} | ||||
| 	n, offset := int(u16(ttf, offset)), offset+2 | ||||
| 	offset += 6 // Skip the searchRange, entrySelector and rangeShift. | ||||
| 	if len(ttf) < 16*n+offset { | ||||
| 		err = FormatError("TTF data is too short") | ||||
| 		return | ||||
| 	} | ||||
| 	f := new(Font) | ||||
| 	// Assign the table slices. | ||||
| 	for i := 0; i < n; i++ { | ||||
| 		x := 16*i + offset | ||||
| 		switch string(ttf[x : x+4]) { | ||||
| 		case "cmap": | ||||
| 			f.cmap, err = readTable(ttf, ttf[x+8:x+16]) | ||||
| 		case "cvt ": | ||||
| 			f.cvt, err = readTable(ttf, ttf[x+8:x+16]) | ||||
| 		case "fpgm": | ||||
| 			f.fpgm, err = readTable(ttf, ttf[x+8:x+16]) | ||||
| 		case "glyf": | ||||
| 			f.glyf, err = readTable(ttf, ttf[x+8:x+16]) | ||||
| 		case "hdmx": | ||||
| 			f.hdmx, err = readTable(ttf, ttf[x+8:x+16]) | ||||
| 		case "head": | ||||
| 			f.head, err = readTable(ttf, ttf[x+8:x+16]) | ||||
| 		case "hhea": | ||||
| 			f.hhea, err = readTable(ttf, ttf[x+8:x+16]) | ||||
| 		case "hmtx": | ||||
| 			f.hmtx, err = readTable(ttf, ttf[x+8:x+16]) | ||||
| 		case "kern": | ||||
| 			f.kern, err = readTable(ttf, ttf[x+8:x+16]) | ||||
| 		case "loca": | ||||
| 			f.loca, err = readTable(ttf, ttf[x+8:x+16]) | ||||
| 		case "maxp": | ||||
| 			f.maxp, err = readTable(ttf, ttf[x+8:x+16]) | ||||
| 		case "name": | ||||
| 			f.name, err = readTable(ttf, ttf[x+8:x+16]) | ||||
| 		case "OS/2": | ||||
| 			f.os2, err = readTable(ttf, ttf[x+8:x+16]) | ||||
| 		case "prep": | ||||
| 			f.prep, err = readTable(ttf, ttf[x+8:x+16]) | ||||
| 		case "vmtx": | ||||
| 			f.vmtx, err = readTable(ttf, ttf[x+8:x+16]) | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	// Parse and sanity-check the TTF data. | ||||
| 	if err = f.parseHead(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if err = f.parseMaxp(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if err = f.parseCmap(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if err = f.parseKern(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if err = f.parseHhea(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	font = f | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										19
									
								
								vendor/github.com/wcharczuk/go-chart/v2/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								vendor/github.com/wcharczuk/go-chart/v2/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | ||||
| # Binaries for programs and plugins | ||||
| *.exe | ||||
| *.dll | ||||
| *.so | ||||
| *.dylib | ||||
|  | ||||
| # Test binary, build with `go test -c` | ||||
| *.test | ||||
|  | ||||
| # Output of the go coverage tool, specifically when used with LiteIDE | ||||
| *.out | ||||
|  | ||||
| # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 | ||||
| .glide/ | ||||
|  | ||||
| # Other | ||||
| .vscode | ||||
| .DS_Store | ||||
| coverage.html | ||||
							
								
								
									
										1
									
								
								vendor/github.com/wcharczuk/go-chart/v2/COVERAGE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								vendor/github.com/wcharczuk/go-chart/v2/COVERAGE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| 29.02 | ||||
							
								
								
									
										21
									
								
								vendor/github.com/wcharczuk/go-chart/v2/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/wcharczuk/go-chart/v2/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| MIT License | ||||
|  | ||||
| Copyright (c) 2016 William Charczuk. | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| SOFTWARE. | ||||
							
								
								
									
										10
									
								
								vendor/github.com/wcharczuk/go-chart/v2/Makefile
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								vendor/github.com/wcharczuk/go-chart/v2/Makefile
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| all: new-install test | ||||
|  | ||||
| new-install: | ||||
| 	@go get -v -u ./... | ||||
|  | ||||
| generate: | ||||
| 	@go generate ./... | ||||
|  | ||||
| test: | ||||
| 	@go test ./... | ||||
							
								
								
									
										4
									
								
								vendor/github.com/wcharczuk/go-chart/v2/PROFANITY_RULES.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								vendor/github.com/wcharczuk/go-chart/v2/PROFANITY_RULES.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| go-sdk: | ||||
|   excludeFiles: [ "*_test.go" ] | ||||
|   importsContain: [ github.com/blend/go-sdk/* ] | ||||
|   description: "please don't use go-sdk in this repo" | ||||
							
								
								
									
										95
									
								
								vendor/github.com/wcharczuk/go-chart/v2/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								vendor/github.com/wcharczuk/go-chart/v2/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,95 @@ | ||||
| go-chart | ||||
| ======== | ||||
| [](https://circleci.com/gh/wcharczuk/go-chart) [](https://goreportcard.com/report/github.com/wcharczuk/go-chart) | ||||
|  | ||||
| Package `chart` is a very simple golang native charting library that supports timeseries and continuous line charts. | ||||
|  | ||||
| Master should now be on the v3.x codebase, which overhauls the api significantly. Per usual, see `examples` for more information. | ||||
|  | ||||
| # Installation | ||||
|  | ||||
| To install `chart` run the following: | ||||
|  | ||||
| ```bash | ||||
| > go get -u github.com/wcharczuk/go-chart | ||||
| ``` | ||||
|  | ||||
| Most of the components are interchangeable so feel free to crib whatever you want. | ||||
|  | ||||
| # Output Examples | ||||
|  | ||||
| Spark Lines: | ||||
|  | ||||
|  | ||||
|  | ||||
| Single axis: | ||||
|  | ||||
|  | ||||
|  | ||||
| Two axis: | ||||
|  | ||||
|  | ||||
|  | ||||
| # Other Chart Types | ||||
|  | ||||
| Pie Chart: | ||||
|  | ||||
|  | ||||
|  | ||||
| The code for this chart can be found in `examples/pie_chart/main.go`. | ||||
|  | ||||
| Stacked Bar: | ||||
|  | ||||
|  | ||||
|  | ||||
| The code for this chart can be found in `examples/stacked_bar/main.go`. | ||||
|  | ||||
| # Code Examples | ||||
|  | ||||
| Actual chart configurations and examples can be found in the `./examples/` directory. They are simple CLI programs that write to `output.png` (they are also updated with `go generate`. | ||||
|  | ||||
| # Usage | ||||
|  | ||||
| Everything starts with the `chart.Chart` object. The bare minimum to draw a chart would be the following: | ||||
|  | ||||
| ```golang | ||||
|  | ||||
| import ( | ||||
|     ... | ||||
|     "bytes" | ||||
|     ... | ||||
|     "github.com/wcharczuk/go-chart" //exposes "chart" | ||||
| ) | ||||
|  | ||||
| graph := chart.Chart{ | ||||
|     Series: []chart.Series{ | ||||
|         chart.ContinuousSeries{ | ||||
|             XValues: []float64{1.0, 2.0, 3.0, 4.0}, | ||||
|             YValues: []float64{1.0, 2.0, 3.0, 4.0}, | ||||
|         }, | ||||
|     }, | ||||
| } | ||||
|  | ||||
| buffer := bytes.NewBuffer([]byte{}) | ||||
| err := graph.Render(chart.PNG, buffer) | ||||
| ``` | ||||
|  | ||||
| Explanation of the above: A `chart` can have many `Series`, a `Series` is a collection of things that need to be drawn according to the X range and the Y range(s). | ||||
|  | ||||
| Here, we have a single series with x range values as float64s, rendered to a PNG. Note; we can pass any type of `io.Writer` into `Render(...)`, meaning that we can render the chart to a file or a resonse or anything else that implements `io.Writer`. | ||||
|  | ||||
| # API Overview | ||||
|  | ||||
| Everything on the `chart.Chart` object has defaults that can be overriden. Whenever a developer sets a property on the chart object, it is to be assumed that value will be used instead of the default. | ||||
|  | ||||
| The best way to see the api in action is to look at the examples in the `./_examples/` directory. | ||||
|  | ||||
| # Design Philosophy | ||||
|  | ||||
| I wanted to make a charting library that used only native golang, that could be stood up on a server (i.e. it had built in fonts). | ||||
|  | ||||
| The goal with the API itself is to have the "zero value be useful", and to require the user to not code more than they absolutely needed. | ||||
|  | ||||
| # Contributions | ||||
|  | ||||
| Contributions are welcome though this library is in a holding pattern for the forseable future. | ||||
							
								
								
									
										91
									
								
								vendor/github.com/wcharczuk/go-chart/v2/annotation_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								vendor/github.com/wcharczuk/go-chart/v2/annotation_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,91 @@ | ||||
| package chart | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"math" | ||||
| ) | ||||
|  | ||||
| // Interface Assertions. | ||||
| var ( | ||||
| 	_ Series = (*AnnotationSeries)(nil) | ||||
| ) | ||||
|  | ||||
| // AnnotationSeries is a series of labels on the chart. | ||||
| type AnnotationSeries struct { | ||||
| 	Name        string | ||||
| 	Style       Style | ||||
| 	YAxis       YAxisType | ||||
| 	Annotations []Value2 | ||||
| } | ||||
|  | ||||
| // GetName returns the name of the time series. | ||||
| func (as AnnotationSeries) GetName() string { | ||||
| 	return as.Name | ||||
| } | ||||
|  | ||||
| // GetStyle returns the line style. | ||||
| func (as AnnotationSeries) GetStyle() Style { | ||||
| 	return as.Style | ||||
| } | ||||
|  | ||||
| // GetYAxis returns which YAxis the series draws on. | ||||
| func (as AnnotationSeries) GetYAxis() YAxisType { | ||||
| 	return as.YAxis | ||||
| } | ||||
|  | ||||
| func (as AnnotationSeries) annotationStyleDefaults(defaults Style) Style { | ||||
| 	return Style{ | ||||
| 		FontColor:   DefaultTextColor, | ||||
| 		Font:        defaults.Font, | ||||
| 		FillColor:   DefaultAnnotationFillColor, | ||||
| 		FontSize:    DefaultAnnotationFontSize, | ||||
| 		StrokeColor: defaults.StrokeColor, | ||||
| 		StrokeWidth: defaults.StrokeWidth, | ||||
| 		Padding:     DefaultAnnotationPadding, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Measure returns a bounds box of the series. | ||||
| func (as AnnotationSeries) Measure(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) Box { | ||||
| 	box := Box{ | ||||
| 		Top:    math.MaxInt32, | ||||
| 		Left:   math.MaxInt32, | ||||
| 		Right:  0, | ||||
| 		Bottom: 0, | ||||
| 	} | ||||
| 	if !as.Style.Hidden { | ||||
| 		seriesStyle := as.Style.InheritFrom(as.annotationStyleDefaults(defaults)) | ||||
| 		for _, a := range as.Annotations { | ||||
| 			style := a.Style.InheritFrom(seriesStyle) | ||||
| 			lx := canvasBox.Left + xrange.Translate(a.XValue) | ||||
| 			ly := canvasBox.Bottom - yrange.Translate(a.YValue) | ||||
| 			ab := Draw.MeasureAnnotation(r, canvasBox, style, lx, ly, a.Label) | ||||
| 			box.Top = MinInt(box.Top, ab.Top) | ||||
| 			box.Left = MinInt(box.Left, ab.Left) | ||||
| 			box.Right = MaxInt(box.Right, ab.Right) | ||||
| 			box.Bottom = MaxInt(box.Bottom, ab.Bottom) | ||||
| 		} | ||||
| 	} | ||||
| 	return box | ||||
| } | ||||
|  | ||||
| // Render draws the series. | ||||
| func (as AnnotationSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { | ||||
| 	if !as.Style.Hidden { | ||||
| 		seriesStyle := as.Style.InheritFrom(as.annotationStyleDefaults(defaults)) | ||||
| 		for _, a := range as.Annotations { | ||||
| 			style := a.Style.InheritFrom(seriesStyle) | ||||
| 			lx := canvasBox.Left + xrange.Translate(a.XValue) | ||||
| 			ly := canvasBox.Bottom - yrange.Translate(a.YValue) | ||||
| 			Draw.Annotation(r, canvasBox, style, lx, ly, a.Label) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Validate validates the series. | ||||
| func (as AnnotationSeries) Validate() error { | ||||
| 	if len(as.Annotations) == 0 { | ||||
| 		return fmt.Errorf("annotation series requires annotations to be set and not empty") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										24
									
								
								vendor/github.com/wcharczuk/go-chart/v2/array.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								vendor/github.com/wcharczuk/go-chart/v2/array.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | ||||
| package chart | ||||
|  | ||||
| var ( | ||||
| 	_ Sequence = (*Array)(nil) | ||||
| ) | ||||
|  | ||||
| // NewArray returns a new array from a given set of values. | ||||
| // Array implements Sequence, which allows it to be used with the sequence helpers. | ||||
| func NewArray(values ...float64) Array { | ||||
| 	return Array(values) | ||||
| } | ||||
|  | ||||
| // Array is a wrapper for an array of floats that implements `ValuesProvider`. | ||||
| type Array []float64 | ||||
|  | ||||
| // Len returns the value provider length. | ||||
| func (a Array) Len() int { | ||||
| 	return len(a) | ||||
| } | ||||
|  | ||||
| // GetValue returns the value at a given index. | ||||
| func (a Array) GetValue(index int) float64 { | ||||
| 	return a[index] | ||||
| } | ||||
							
								
								
									
										45
									
								
								vendor/github.com/wcharczuk/go-chart/v2/axis.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								vendor/github.com/wcharczuk/go-chart/v2/axis.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,45 @@ | ||||
| package chart | ||||
|  | ||||
| // TickPosition is an enumeration of possible tick drawing positions. | ||||
| type TickPosition int | ||||
|  | ||||
| const ( | ||||
| 	// TickPositionUnset means to use the default tick position. | ||||
| 	TickPositionUnset TickPosition = 0 | ||||
| 	// TickPositionBetweenTicks draws the labels for a tick between the previous and current tick. | ||||
| 	TickPositionBetweenTicks TickPosition = 1 | ||||
| 	// TickPositionUnderTick draws the tick below the tick. | ||||
| 	TickPositionUnderTick TickPosition = 2 | ||||
| ) | ||||
|  | ||||
| // YAxisType is a type of y-axis; it can either be primary or secondary. | ||||
| type YAxisType int | ||||
|  | ||||
| const ( | ||||
| 	// YAxisPrimary is the primary axis. | ||||
| 	YAxisPrimary YAxisType = 0 | ||||
| 	// YAxisSecondary is the secondary axis. | ||||
| 	YAxisSecondary YAxisType = 1 | ||||
| ) | ||||
|  | ||||
| // Axis is a chart feature detailing what values happen where. | ||||
| type Axis interface { | ||||
| 	GetName() string | ||||
| 	SetName(name string) | ||||
|  | ||||
| 	GetStyle() Style | ||||
| 	SetStyle(style Style) | ||||
|  | ||||
| 	GetTicks() []Tick | ||||
| 	GenerateTicks(r Renderer, ra Range, vf ValueFormatter) []Tick | ||||
|  | ||||
| 	// GenerateGridLines returns the gridlines for the axis. | ||||
| 	GetGridLines(ticks []Tick) []GridLine | ||||
|  | ||||
| 	// Measure should return an absolute box for the axis. | ||||
| 	// This is used when auto-fitting the canvas to the background. | ||||
| 	Measure(r Renderer, canvasBox Box, ra Range, style Style, ticks []Tick) Box | ||||
|  | ||||
| 	// Render renders the axis. | ||||
| 	Render(r Renderer, canvasBox Box, ra Range, style Style, ticks []Tick) | ||||
| } | ||||
							
								
								
									
										516
									
								
								vendor/github.com/wcharczuk/go-chart/v2/bar_chart.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										516
									
								
								vendor/github.com/wcharczuk/go-chart/v2/bar_chart.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,516 @@ | ||||
| package chart | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"math" | ||||
|  | ||||
| 	"github.com/golang/freetype/truetype" | ||||
| ) | ||||
|  | ||||
| // BarChart is a chart that draws bars on a range. | ||||
| type BarChart struct { | ||||
| 	Title      string | ||||
| 	TitleStyle Style | ||||
|  | ||||
| 	ColorPalette ColorPalette | ||||
|  | ||||
| 	Width  int | ||||
| 	Height int | ||||
| 	DPI    float64 | ||||
|  | ||||
| 	BarWidth int | ||||
|  | ||||
| 	Background Style | ||||
| 	Canvas     Style | ||||
|  | ||||
| 	XAxis Style | ||||
| 	YAxis YAxis | ||||
|  | ||||
| 	BarSpacing int | ||||
|  | ||||
| 	UseBaseValue bool | ||||
| 	BaseValue    float64 | ||||
|  | ||||
| 	Font        *truetype.Font | ||||
| 	defaultFont *truetype.Font | ||||
|  | ||||
| 	Bars     []Value | ||||
| 	Elements []Renderable | ||||
| } | ||||
|  | ||||
| // GetDPI returns the dpi for the chart. | ||||
| func (bc BarChart) GetDPI() float64 { | ||||
| 	if bc.DPI == 0 { | ||||
| 		return DefaultDPI | ||||
| 	} | ||||
| 	return bc.DPI | ||||
| } | ||||
|  | ||||
| // GetFont returns the text font. | ||||
| func (bc BarChart) GetFont() *truetype.Font { | ||||
| 	if bc.Font == nil { | ||||
| 		return bc.defaultFont | ||||
| 	} | ||||
| 	return bc.Font | ||||
| } | ||||
|  | ||||
| // GetWidth returns the chart width or the default value. | ||||
| func (bc BarChart) GetWidth() int { | ||||
| 	if bc.Width == 0 { | ||||
| 		return DefaultChartWidth | ||||
| 	} | ||||
| 	return bc.Width | ||||
| } | ||||
|  | ||||
| // GetHeight returns the chart height or the default value. | ||||
| func (bc BarChart) GetHeight() int { | ||||
| 	if bc.Height == 0 { | ||||
| 		return DefaultChartHeight | ||||
| 	} | ||||
| 	return bc.Height | ||||
| } | ||||
|  | ||||
| // GetBarSpacing returns the spacing between bars. | ||||
| func (bc BarChart) GetBarSpacing() int { | ||||
| 	if bc.BarSpacing == 0 { | ||||
| 		return DefaultBarSpacing | ||||
| 	} | ||||
| 	return bc.BarSpacing | ||||
| } | ||||
|  | ||||
| // GetBarWidth returns the default bar width. | ||||
| func (bc BarChart) GetBarWidth() int { | ||||
| 	if bc.BarWidth == 0 { | ||||
| 		return DefaultBarWidth | ||||
| 	} | ||||
| 	return bc.BarWidth | ||||
| } | ||||
|  | ||||
| // Render renders the chart with the given renderer to the given io.Writer. | ||||
| func (bc BarChart) Render(rp RendererProvider, w io.Writer) error { | ||||
| 	if len(bc.Bars) == 0 { | ||||
| 		return errors.New("please provide at least one bar") | ||||
| 	} | ||||
|  | ||||
| 	r, err := rp(bc.GetWidth(), bc.GetHeight()) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if bc.Font == nil { | ||||
| 		defaultFont, err := GetDefaultFont() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		bc.defaultFont = defaultFont | ||||
| 	} | ||||
| 	r.SetDPI(bc.GetDPI()) | ||||
|  | ||||
| 	bc.drawBackground(r) | ||||
|  | ||||
| 	var canvasBox Box | ||||
| 	var yt []Tick | ||||
| 	var yr Range | ||||
| 	var yf ValueFormatter | ||||
|  | ||||
| 	canvasBox = bc.getDefaultCanvasBox() | ||||
| 	yr = bc.getRanges() | ||||
| 	if yr.GetMax()-yr.GetMin() == 0 { | ||||
| 		return fmt.Errorf("invalid data range; cannot be zero") | ||||
| 	} | ||||
| 	yr = bc.setRangeDomains(canvasBox, yr) | ||||
| 	yf = bc.getValueFormatters() | ||||
|  | ||||
| 	if bc.hasAxes() { | ||||
| 		yt = bc.getAxesTicks(r, yr, yf) | ||||
| 		canvasBox = bc.getAdjustedCanvasBox(r, canvasBox, yr, yt) | ||||
| 		yr = bc.setRangeDomains(canvasBox, yr) | ||||
| 	} | ||||
| 	bc.drawCanvas(r, canvasBox) | ||||
| 	bc.drawBars(r, canvasBox, yr) | ||||
| 	bc.drawXAxis(r, canvasBox) | ||||
| 	bc.drawYAxis(r, canvasBox, yr, yt) | ||||
|  | ||||
| 	bc.drawTitle(r) | ||||
| 	for _, a := range bc.Elements { | ||||
| 		a(r, canvasBox, bc.styleDefaultsElements()) | ||||
| 	} | ||||
|  | ||||
| 	return r.Save(w) | ||||
| } | ||||
|  | ||||
| func (bc BarChart) drawCanvas(r Renderer, canvasBox Box) { | ||||
| 	Draw.Box(r, canvasBox, bc.getCanvasStyle()) | ||||
| } | ||||
|  | ||||
| func (bc BarChart) getRanges() Range { | ||||
| 	var yrange Range | ||||
| 	if bc.YAxis.Range != nil && !bc.YAxis.Range.IsZero() { | ||||
| 		yrange = bc.YAxis.Range | ||||
| 	} else { | ||||
| 		yrange = &ContinuousRange{} | ||||
| 	} | ||||
|  | ||||
| 	if !yrange.IsZero() { | ||||
| 		return yrange | ||||
| 	} | ||||
|  | ||||
| 	if len(bc.YAxis.Ticks) > 0 { | ||||
| 		tickMin, tickMax := math.MaxFloat64, -math.MaxFloat64 | ||||
| 		for _, t := range bc.YAxis.Ticks { | ||||
| 			tickMin = math.Min(tickMin, t.Value) | ||||
| 			tickMax = math.Max(tickMax, t.Value) | ||||
| 		} | ||||
| 		yrange.SetMin(tickMin) | ||||
| 		yrange.SetMax(tickMax) | ||||
| 		return yrange | ||||
| 	} | ||||
|  | ||||
| 	min, max := math.MaxFloat64, -math.MaxFloat64 | ||||
| 	for _, b := range bc.Bars { | ||||
| 		min = math.Min(b.Value, min) | ||||
| 		max = math.Max(b.Value, max) | ||||
| 	} | ||||
|  | ||||
| 	yrange.SetMin(min) | ||||
| 	yrange.SetMax(max) | ||||
|  | ||||
| 	return yrange | ||||
| } | ||||
|  | ||||
| func (bc BarChart) drawBackground(r Renderer) { | ||||
| 	Draw.Box(r, Box{ | ||||
| 		Right:  bc.GetWidth(), | ||||
| 		Bottom: bc.GetHeight(), | ||||
| 	}, bc.getBackgroundStyle()) | ||||
| } | ||||
|  | ||||
| func (bc BarChart) drawBars(r Renderer, canvasBox Box, yr Range) { | ||||
| 	xoffset := canvasBox.Left | ||||
|  | ||||
| 	width, spacing, _ := bc.calculateScaledTotalWidth(canvasBox) | ||||
| 	bs2 := spacing >> 1 | ||||
|  | ||||
| 	var barBox Box | ||||
| 	var bxl, bxr, by int | ||||
| 	for index, bar := range bc.Bars { | ||||
| 		bxl = xoffset + bs2 | ||||
| 		bxr = bxl + width | ||||
|  | ||||
| 		by = canvasBox.Bottom - yr.Translate(bar.Value) | ||||
|  | ||||
| 		if bc.UseBaseValue { | ||||
| 			barBox = Box{ | ||||
| 				Top:    by, | ||||
| 				Left:   bxl, | ||||
| 				Right:  bxr, | ||||
| 				Bottom: canvasBox.Bottom - yr.Translate(bc.BaseValue), | ||||
| 			} | ||||
| 		} else { | ||||
| 			barBox = Box{ | ||||
| 				Top:    by, | ||||
| 				Left:   bxl, | ||||
| 				Right:  bxr, | ||||
| 				Bottom: canvasBox.Bottom, | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		Draw.Box(r, barBox, bar.Style.InheritFrom(bc.styleDefaultsBar(index))) | ||||
|  | ||||
| 		xoffset += width + spacing | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (bc BarChart) drawXAxis(r Renderer, canvasBox Box) { | ||||
| 	if !bc.XAxis.Hidden { | ||||
| 		axisStyle := bc.XAxis.InheritFrom(bc.styleDefaultsAxes()) | ||||
| 		axisStyle.WriteToRenderer(r) | ||||
|  | ||||
| 		width, spacing, _ := bc.calculateScaledTotalWidth(canvasBox) | ||||
|  | ||||
| 		r.MoveTo(canvasBox.Left, canvasBox.Bottom) | ||||
| 		r.LineTo(canvasBox.Right, canvasBox.Bottom) | ||||
| 		r.Stroke() | ||||
|  | ||||
| 		r.MoveTo(canvasBox.Left, canvasBox.Bottom) | ||||
| 		r.LineTo(canvasBox.Left, canvasBox.Bottom+DefaultVerticalTickHeight) | ||||
| 		r.Stroke() | ||||
|  | ||||
| 		cursor := canvasBox.Left | ||||
| 		for index, bar := range bc.Bars { | ||||
| 			barLabelBox := Box{ | ||||
| 				Top:    canvasBox.Bottom + DefaultXAxisMargin, | ||||
| 				Left:   cursor, | ||||
| 				Right:  cursor + width + spacing, | ||||
| 				Bottom: bc.GetHeight(), | ||||
| 			} | ||||
|  | ||||
| 			if len(bar.Label) > 0 { | ||||
| 				Draw.TextWithin(r, bar.Label, barLabelBox, axisStyle) | ||||
| 			} | ||||
|  | ||||
| 			axisStyle.WriteToRenderer(r) | ||||
| 			if index < len(bc.Bars)-1 { | ||||
| 				r.MoveTo(barLabelBox.Right, canvasBox.Bottom) | ||||
| 				r.LineTo(barLabelBox.Right, canvasBox.Bottom+DefaultVerticalTickHeight) | ||||
| 				r.Stroke() | ||||
| 			} | ||||
| 			cursor += width + spacing | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (bc BarChart) drawYAxis(r Renderer, canvasBox Box, yr Range, ticks []Tick) { | ||||
| 	if !bc.YAxis.Style.Hidden { | ||||
| 		axisStyle := bc.YAxis.Style.InheritFrom(bc.styleDefaultsAxes()) | ||||
| 		axisStyle.WriteToRenderer(r) | ||||
|  | ||||
| 		r.MoveTo(canvasBox.Right, canvasBox.Top) | ||||
| 		r.LineTo(canvasBox.Right, canvasBox.Bottom) | ||||
| 		r.Stroke() | ||||
|  | ||||
| 		r.MoveTo(canvasBox.Right, canvasBox.Bottom) | ||||
| 		r.LineTo(canvasBox.Right+DefaultHorizontalTickWidth, canvasBox.Bottom) | ||||
| 		r.Stroke() | ||||
|  | ||||
| 		var ty int | ||||
| 		var tb Box | ||||
| 		for _, t := range ticks { | ||||
| 			ty = canvasBox.Bottom - yr.Translate(t.Value) | ||||
|  | ||||
| 			axisStyle.GetStrokeOptions().WriteToRenderer(r) | ||||
| 			r.MoveTo(canvasBox.Right, ty) | ||||
| 			r.LineTo(canvasBox.Right+DefaultHorizontalTickWidth, ty) | ||||
| 			r.Stroke() | ||||
|  | ||||
| 			axisStyle.GetTextOptions().WriteToRenderer(r) | ||||
| 			tb = r.MeasureText(t.Label) | ||||
| 			Draw.Text(r, t.Label, canvasBox.Right+DefaultYAxisMargin+5, ty+(tb.Height()>>1), axisStyle) | ||||
| 		} | ||||
|  | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (bc BarChart) drawTitle(r Renderer) { | ||||
| 	if len(bc.Title) > 0 && !bc.TitleStyle.Hidden { | ||||
| 		r.SetFont(bc.TitleStyle.GetFont(bc.GetFont())) | ||||
| 		r.SetFontColor(bc.TitleStyle.GetFontColor(bc.GetColorPalette().TextColor())) | ||||
| 		titleFontSize := bc.TitleStyle.GetFontSize(bc.getTitleFontSize()) | ||||
| 		r.SetFontSize(titleFontSize) | ||||
|  | ||||
| 		textBox := r.MeasureText(bc.Title) | ||||
|  | ||||
| 		textWidth := textBox.Width() | ||||
| 		textHeight := textBox.Height() | ||||
|  | ||||
| 		titleX := (bc.GetWidth() >> 1) - (textWidth >> 1) | ||||
| 		titleY := bc.TitleStyle.Padding.GetTop(DefaultTitleTop) + textHeight | ||||
|  | ||||
| 		r.Text(bc.Title, titleX, titleY) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (bc BarChart) getCanvasStyle() Style { | ||||
| 	return bc.Canvas.InheritFrom(bc.styleDefaultsCanvas()) | ||||
| } | ||||
|  | ||||
| func (bc BarChart) styleDefaultsCanvas() Style { | ||||
| 	return Style{ | ||||
| 		FillColor:   bc.GetColorPalette().CanvasColor(), | ||||
| 		StrokeColor: bc.GetColorPalette().CanvasStrokeColor(), | ||||
| 		StrokeWidth: DefaultCanvasStrokeWidth, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (bc BarChart) hasAxes() bool { | ||||
| 	return !bc.YAxis.Style.Hidden | ||||
| } | ||||
|  | ||||
| func (bc BarChart) setRangeDomains(canvasBox Box, yr Range) Range { | ||||
| 	yr.SetDomain(canvasBox.Height()) | ||||
| 	return yr | ||||
| } | ||||
|  | ||||
| func (bc BarChart) getDefaultCanvasBox() Box { | ||||
| 	return bc.box() | ||||
| } | ||||
|  | ||||
| func (bc BarChart) getValueFormatters() ValueFormatter { | ||||
| 	if bc.YAxis.ValueFormatter != nil { | ||||
| 		return bc.YAxis.ValueFormatter | ||||
| 	} | ||||
| 	return FloatValueFormatter | ||||
| } | ||||
|  | ||||
| func (bc BarChart) getAxesTicks(r Renderer, yr Range, yf ValueFormatter) (yticks []Tick) { | ||||
| 	if !bc.YAxis.Style.Hidden { | ||||
| 		yticks = bc.YAxis.GetTicks(r, yr, bc.styleDefaultsAxes(), yf) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (bc BarChart) calculateEffectiveBarSpacing(canvasBox Box) int { | ||||
| 	totalWithBaseSpacing := bc.calculateTotalBarWidth(bc.GetBarWidth(), bc.GetBarSpacing()) | ||||
| 	if totalWithBaseSpacing > canvasBox.Width() { | ||||
| 		lessBarWidths := canvasBox.Width() - (len(bc.Bars) * bc.GetBarWidth()) | ||||
| 		if lessBarWidths > 0 { | ||||
| 			return int(math.Ceil(float64(lessBarWidths) / float64(len(bc.Bars)))) | ||||
| 		} | ||||
| 		return 0 | ||||
| 	} | ||||
| 	return bc.GetBarSpacing() | ||||
| } | ||||
|  | ||||
| func (bc BarChart) calculateEffectiveBarWidth(canvasBox Box, spacing int) int { | ||||
| 	totalWithBaseWidth := bc.calculateTotalBarWidth(bc.GetBarWidth(), spacing) | ||||
| 	if totalWithBaseWidth > canvasBox.Width() { | ||||
| 		totalLessBarSpacings := canvasBox.Width() - (len(bc.Bars) * spacing) | ||||
| 		if totalLessBarSpacings > 0 { | ||||
| 			return int(math.Ceil(float64(totalLessBarSpacings) / float64(len(bc.Bars)))) | ||||
| 		} | ||||
| 		return 0 | ||||
| 	} | ||||
| 	return bc.GetBarWidth() | ||||
| } | ||||
|  | ||||
| func (bc BarChart) calculateTotalBarWidth(barWidth, spacing int) int { | ||||
| 	return len(bc.Bars) * (barWidth + spacing) | ||||
| } | ||||
|  | ||||
| func (bc BarChart) calculateScaledTotalWidth(canvasBox Box) (width, spacing, total int) { | ||||
| 	spacing = bc.calculateEffectiveBarSpacing(canvasBox) | ||||
| 	width = bc.calculateEffectiveBarWidth(canvasBox, spacing) | ||||
| 	total = bc.calculateTotalBarWidth(width, spacing) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (bc BarChart) getAdjustedCanvasBox(r Renderer, canvasBox Box, yrange Range, yticks []Tick) Box { | ||||
| 	axesOuterBox := canvasBox.Clone() | ||||
|  | ||||
| 	_, _, totalWidth := bc.calculateScaledTotalWidth(canvasBox) | ||||
|  | ||||
| 	if !bc.XAxis.Hidden { | ||||
| 		xaxisHeight := DefaultVerticalTickHeight | ||||
|  | ||||
| 		axisStyle := bc.XAxis.InheritFrom(bc.styleDefaultsAxes()) | ||||
| 		axisStyle.WriteToRenderer(r) | ||||
|  | ||||
| 		cursor := canvasBox.Left | ||||
| 		for _, bar := range bc.Bars { | ||||
| 			if len(bar.Label) > 0 { | ||||
| 				barLabelBox := Box{ | ||||
| 					Top:    canvasBox.Bottom + DefaultXAxisMargin, | ||||
| 					Left:   cursor, | ||||
| 					Right:  cursor + bc.GetBarWidth() + bc.GetBarSpacing(), | ||||
| 					Bottom: bc.GetHeight(), | ||||
| 				} | ||||
| 				lines := Text.WrapFit(r, bar.Label, barLabelBox.Width(), axisStyle) | ||||
| 				linesBox := Text.MeasureLines(r, lines, axisStyle) | ||||
|  | ||||
| 				xaxisHeight = MinInt(linesBox.Height()+(2*DefaultXAxisMargin), xaxisHeight) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		xbox := Box{ | ||||
| 			Top:    canvasBox.Top, | ||||
| 			Left:   canvasBox.Left, | ||||
| 			Right:  canvasBox.Left + totalWidth, | ||||
| 			Bottom: bc.GetHeight() - xaxisHeight, | ||||
| 		} | ||||
|  | ||||
| 		axesOuterBox = axesOuterBox.Grow(xbox) | ||||
| 	} | ||||
|  | ||||
| 	if !bc.YAxis.Style.Hidden { | ||||
| 		axesBounds := bc.YAxis.Measure(r, canvasBox, yrange, bc.styleDefaultsAxes(), yticks) | ||||
| 		axesOuterBox = axesOuterBox.Grow(axesBounds) | ||||
| 	} | ||||
|  | ||||
| 	return canvasBox.OuterConstrain(bc.box(), axesOuterBox) | ||||
| } | ||||
|  | ||||
| // box returns the chart bounds as a box. | ||||
| func (bc BarChart) box() Box { | ||||
| 	dpr := bc.Background.Padding.GetRight(10) | ||||
| 	dpb := bc.Background.Padding.GetBottom(50) | ||||
|  | ||||
| 	return Box{ | ||||
| 		Top:    bc.Background.Padding.GetTop(20), | ||||
| 		Left:   bc.Background.Padding.GetLeft(20), | ||||
| 		Right:  bc.GetWidth() - dpr, | ||||
| 		Bottom: bc.GetHeight() - dpb, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (bc BarChart) getBackgroundStyle() Style { | ||||
| 	return bc.Background.InheritFrom(bc.styleDefaultsBackground()) | ||||
| } | ||||
|  | ||||
| func (bc BarChart) styleDefaultsBackground() Style { | ||||
| 	return Style{ | ||||
| 		FillColor:   bc.GetColorPalette().BackgroundColor(), | ||||
| 		StrokeColor: bc.GetColorPalette().BackgroundStrokeColor(), | ||||
| 		StrokeWidth: DefaultStrokeWidth, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (bc BarChart) styleDefaultsBar(index int) Style { | ||||
| 	return Style{ | ||||
| 		StrokeColor: bc.GetColorPalette().GetSeriesColor(index), | ||||
| 		StrokeWidth: 3.0, | ||||
| 		FillColor:   bc.GetColorPalette().GetSeriesColor(index), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (bc BarChart) styleDefaultsTitle() Style { | ||||
| 	return bc.TitleStyle.InheritFrom(Style{ | ||||
| 		FontColor:           bc.GetColorPalette().TextColor(), | ||||
| 		Font:                bc.GetFont(), | ||||
| 		FontSize:            bc.getTitleFontSize(), | ||||
| 		TextHorizontalAlign: TextHorizontalAlignCenter, | ||||
| 		TextVerticalAlign:   TextVerticalAlignTop, | ||||
| 		TextWrap:            TextWrapWord, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func (bc BarChart) getTitleFontSize() float64 { | ||||
| 	effectiveDimension := MinInt(bc.GetWidth(), bc.GetHeight()) | ||||
| 	if effectiveDimension >= 2048 { | ||||
| 		return 48 | ||||
| 	} else if effectiveDimension >= 1024 { | ||||
| 		return 24 | ||||
| 	} else if effectiveDimension >= 512 { | ||||
| 		return 18 | ||||
| 	} else if effectiveDimension >= 256 { | ||||
| 		return 12 | ||||
| 	} | ||||
| 	return 10 | ||||
| } | ||||
|  | ||||
| func (bc BarChart) styleDefaultsAxes() Style { | ||||
| 	return Style{ | ||||
| 		StrokeColor:         bc.GetColorPalette().AxisStrokeColor(), | ||||
| 		Font:                bc.GetFont(), | ||||
| 		FontSize:            DefaultAxisFontSize, | ||||
| 		FontColor:           bc.GetColorPalette().TextColor(), | ||||
| 		TextHorizontalAlign: TextHorizontalAlignCenter, | ||||
| 		TextVerticalAlign:   TextVerticalAlignTop, | ||||
| 		TextWrap:            TextWrapWord, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (bc BarChart) styleDefaultsElements() Style { | ||||
| 	return Style{ | ||||
| 		Font: bc.GetFont(), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetColorPalette returns the color palette for the chart. | ||||
| func (bc BarChart) GetColorPalette() ColorPalette { | ||||
| 	if bc.ColorPalette != nil { | ||||
| 		return bc.ColorPalette | ||||
| 	} | ||||
| 	return AlternateColorPalette | ||||
| } | ||||
							
								
								
									
										135
									
								
								vendor/github.com/wcharczuk/go-chart/v2/bollinger_band_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								vendor/github.com/wcharczuk/go-chart/v2/bollinger_band_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,135 @@ | ||||
| package chart | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| ) | ||||
|  | ||||
| // Interface Assertions. | ||||
| var ( | ||||
| 	_ Series = (*BollingerBandsSeries)(nil) | ||||
| ) | ||||
|  | ||||
| // BollingerBandsSeries draws bollinger bands for an inner series. | ||||
| // Bollinger bands are defined by two lines, one at SMA+k*stddev, one at SMA-k*stdev. | ||||
| type BollingerBandsSeries struct { | ||||
| 	Name  string | ||||
| 	Style Style | ||||
| 	YAxis YAxisType | ||||
|  | ||||
| 	Period      int | ||||
| 	K           float64 | ||||
| 	InnerSeries ValuesProvider | ||||
|  | ||||
| 	valueBuffer *ValueBuffer | ||||
| } | ||||
|  | ||||
| // GetName returns the name of the time series. | ||||
| func (bbs BollingerBandsSeries) GetName() string { | ||||
| 	return bbs.Name | ||||
| } | ||||
|  | ||||
| // GetStyle returns the line style. | ||||
| func (bbs BollingerBandsSeries) GetStyle() Style { | ||||
| 	return bbs.Style | ||||
| } | ||||
|  | ||||
| // GetYAxis returns which YAxis the series draws on. | ||||
| func (bbs BollingerBandsSeries) GetYAxis() YAxisType { | ||||
| 	return bbs.YAxis | ||||
| } | ||||
|  | ||||
| // GetPeriod returns the window size. | ||||
| func (bbs BollingerBandsSeries) GetPeriod() int { | ||||
| 	if bbs.Period == 0 { | ||||
| 		return DefaultSimpleMovingAveragePeriod | ||||
| 	} | ||||
| 	return bbs.Period | ||||
| } | ||||
|  | ||||
| // GetK returns the K value, or the number of standard deviations above and below | ||||
| // to band the simple moving average with. | ||||
| // Typical K value is 2.0. | ||||
| func (bbs BollingerBandsSeries) GetK(defaults ...float64) float64 { | ||||
| 	if bbs.K == 0 { | ||||
| 		if len(defaults) > 0 { | ||||
| 			return defaults[0] | ||||
| 		} | ||||
| 		return 2.0 | ||||
| 	} | ||||
| 	return bbs.K | ||||
| } | ||||
|  | ||||
| // Len returns the number of elements in the series. | ||||
| func (bbs BollingerBandsSeries) Len() int { | ||||
| 	return bbs.InnerSeries.Len() | ||||
| } | ||||
|  | ||||
| // GetBoundedValues gets the bounded value for the series. | ||||
| func (bbs *BollingerBandsSeries) GetBoundedValues(index int) (x, y1, y2 float64) { | ||||
| 	if bbs.InnerSeries == nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if bbs.valueBuffer == nil || index == 0 { | ||||
| 		bbs.valueBuffer = NewValueBufferWithCapacity(bbs.GetPeriod()) | ||||
| 	} | ||||
| 	if bbs.valueBuffer.Len() >= bbs.GetPeriod() { | ||||
| 		bbs.valueBuffer.Dequeue() | ||||
| 	} | ||||
| 	px, py := bbs.InnerSeries.GetValues(index) | ||||
| 	bbs.valueBuffer.Enqueue(py) | ||||
| 	x = px | ||||
|  | ||||
| 	ay := Seq{bbs.valueBuffer}.Average() | ||||
| 	std := Seq{bbs.valueBuffer}.StdDev() | ||||
|  | ||||
| 	y1 = ay + (bbs.GetK() * std) | ||||
| 	y2 = ay - (bbs.GetK() * std) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // GetBoundedLastValues returns the last bounded value for the series. | ||||
| func (bbs *BollingerBandsSeries) GetBoundedLastValues() (x, y1, y2 float64) { | ||||
| 	if bbs.InnerSeries == nil { | ||||
| 		return | ||||
| 	} | ||||
| 	period := bbs.GetPeriod() | ||||
| 	seriesLength := bbs.InnerSeries.Len() | ||||
| 	startAt := seriesLength - period | ||||
| 	if startAt < 0 { | ||||
| 		startAt = 0 | ||||
| 	} | ||||
|  | ||||
| 	vb := NewValueBufferWithCapacity(period) | ||||
| 	for index := startAt; index < seriesLength; index++ { | ||||
| 		xn, yn := bbs.InnerSeries.GetValues(index) | ||||
| 		vb.Enqueue(yn) | ||||
| 		x = xn | ||||
| 	} | ||||
|  | ||||
| 	ay := Seq{vb}.Average() | ||||
| 	std := Seq{vb}.StdDev() | ||||
|  | ||||
| 	y1 = ay + (bbs.GetK() * std) | ||||
| 	y2 = ay - (bbs.GetK() * std) | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Render renders the series. | ||||
| func (bbs *BollingerBandsSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { | ||||
| 	s := bbs.Style.InheritFrom(defaults.InheritFrom(Style{ | ||||
| 		StrokeWidth: 1.0, | ||||
| 		StrokeColor: DefaultAxisColor.WithAlpha(64), | ||||
| 		FillColor:   DefaultAxisColor.WithAlpha(32), | ||||
| 	})) | ||||
|  | ||||
| 	Draw.BoundedSeries(r, canvasBox, xrange, yrange, s, bbs, bbs.GetPeriod()) | ||||
| } | ||||
|  | ||||
| // Validate validates the series. | ||||
| func (bbs BollingerBandsSeries) Validate() error { | ||||
| 	if bbs.InnerSeries == nil { | ||||
| 		return fmt.Errorf("bollinger bands series requires InnerSeries to be set") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										36
									
								
								vendor/github.com/wcharczuk/go-chart/v2/bounded_last_values_annotation_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								vendor/github.com/wcharczuk/go-chart/v2/bounded_last_values_annotation_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,36 @@ | ||||
| package chart | ||||
|  | ||||
| import "fmt" | ||||
|  | ||||
| // BoundedLastValuesAnnotationSeries returns a last value annotation series for a bounded values provider. | ||||
| func BoundedLastValuesAnnotationSeries(innerSeries FullBoundedValuesProvider, vfs ...ValueFormatter) AnnotationSeries { | ||||
| 	lvx, lvy1, lvy2 := innerSeries.GetBoundedLastValues() | ||||
|  | ||||
| 	var vf ValueFormatter | ||||
| 	if len(vfs) > 0 { | ||||
| 		vf = vfs[0] | ||||
| 	} else if typed, isTyped := innerSeries.(ValueFormatterProvider); isTyped { | ||||
| 		_, vf = typed.GetValueFormatters() | ||||
| 	} else { | ||||
| 		vf = FloatValueFormatter | ||||
| 	} | ||||
|  | ||||
| 	label1 := vf(lvy1) | ||||
| 	label2 := vf(lvy2) | ||||
|  | ||||
| 	var seriesName string | ||||
| 	var seriesStyle Style | ||||
| 	if typed, isTyped := innerSeries.(Series); isTyped { | ||||
| 		seriesName = fmt.Sprintf("%s - Last Values", typed.GetName()) | ||||
| 		seriesStyle = typed.GetStyle() | ||||
| 	} | ||||
|  | ||||
| 	return AnnotationSeries{ | ||||
| 		Name:  seriesName, | ||||
| 		Style: seriesStyle, | ||||
| 		Annotations: []Value2{ | ||||
| 			{XValue: lvx, YValue: lvy1, Label: label1}, | ||||
| 			{XValue: lvx, YValue: lvy2, Label: label2}, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										351
									
								
								vendor/github.com/wcharczuk/go-chart/v2/box.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										351
									
								
								vendor/github.com/wcharczuk/go-chart/v2/box.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,351 @@ | ||||
| package chart | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"math" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// BoxZero is a preset box that represents an intentional zero value. | ||||
| 	BoxZero = Box{IsSet: true} | ||||
| ) | ||||
|  | ||||
| // NewBox returns a new (set) box. | ||||
| func NewBox(top, left, right, bottom int) Box { | ||||
| 	return Box{ | ||||
| 		IsSet:  true, | ||||
| 		Top:    top, | ||||
| 		Left:   left, | ||||
| 		Right:  right, | ||||
| 		Bottom: bottom, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Box represents the main 4 dimensions of a box. | ||||
| type Box struct { | ||||
| 	Top    int | ||||
| 	Left   int | ||||
| 	Right  int | ||||
| 	Bottom int | ||||
| 	IsSet  bool | ||||
| } | ||||
|  | ||||
| // IsZero returns if the box is set or not. | ||||
| func (b Box) IsZero() bool { | ||||
| 	if b.IsSet { | ||||
| 		return false | ||||
| 	} | ||||
| 	return b.Top == 0 && b.Left == 0 && b.Right == 0 && b.Bottom == 0 | ||||
| } | ||||
|  | ||||
| // String returns a string representation of the box. | ||||
| func (b Box) String() string { | ||||
| 	return fmt.Sprintf("box(%d,%d,%d,%d)", b.Top, b.Left, b.Right, b.Bottom) | ||||
| } | ||||
|  | ||||
| // GetTop returns a coalesced value with a default. | ||||
| func (b Box) GetTop(defaults ...int) int { | ||||
| 	if !b.IsSet && b.Top == 0 { | ||||
| 		if len(defaults) > 0 { | ||||
| 			return defaults[0] | ||||
| 		} | ||||
| 		return 0 | ||||
| 	} | ||||
| 	return b.Top | ||||
| } | ||||
|  | ||||
| // GetLeft returns a coalesced value with a default. | ||||
| func (b Box) GetLeft(defaults ...int) int { | ||||
| 	if !b.IsSet && b.Left == 0 { | ||||
| 		if len(defaults) > 0 { | ||||
| 			return defaults[0] | ||||
| 		} | ||||
| 		return 0 | ||||
| 	} | ||||
| 	return b.Left | ||||
| } | ||||
|  | ||||
| // GetRight returns a coalesced value with a default. | ||||
| func (b Box) GetRight(defaults ...int) int { | ||||
| 	if !b.IsSet && b.Right == 0 { | ||||
| 		if len(defaults) > 0 { | ||||
| 			return defaults[0] | ||||
| 		} | ||||
| 		return 0 | ||||
| 	} | ||||
| 	return b.Right | ||||
| } | ||||
|  | ||||
| // GetBottom returns a coalesced value with a default. | ||||
| func (b Box) GetBottom(defaults ...int) int { | ||||
| 	if !b.IsSet && b.Bottom == 0 { | ||||
| 		if len(defaults) > 0 { | ||||
| 			return defaults[0] | ||||
| 		} | ||||
| 		return 0 | ||||
| 	} | ||||
| 	return b.Bottom | ||||
| } | ||||
|  | ||||
| // Width returns the width | ||||
| func (b Box) Width() int { | ||||
| 	return AbsInt(b.Right - b.Left) | ||||
| } | ||||
|  | ||||
| // Height returns the height | ||||
| func (b Box) Height() int { | ||||
| 	return AbsInt(b.Bottom - b.Top) | ||||
| } | ||||
|  | ||||
| // Center returns the center of the box | ||||
| func (b Box) Center() (x, y int) { | ||||
| 	w2, h2 := b.Width()>>1, b.Height()>>1 | ||||
| 	return b.Left + w2, b.Top + h2 | ||||
| } | ||||
|  | ||||
| // Aspect returns the aspect ratio of the box. | ||||
| func (b Box) Aspect() float64 { | ||||
| 	return float64(b.Width()) / float64(b.Height()) | ||||
| } | ||||
|  | ||||
| // Clone returns a new copy of the box. | ||||
| func (b Box) Clone() Box { | ||||
| 	return Box{ | ||||
| 		IsSet:  b.IsSet, | ||||
| 		Top:    b.Top, | ||||
| 		Left:   b.Left, | ||||
| 		Right:  b.Right, | ||||
| 		Bottom: b.Bottom, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // IsBiggerThan returns if a box is bigger than another box. | ||||
| func (b Box) IsBiggerThan(other Box) bool { | ||||
| 	return b.Top < other.Top || | ||||
| 		b.Bottom > other.Bottom || | ||||
| 		b.Left < other.Left || | ||||
| 		b.Right > other.Right | ||||
| } | ||||
|  | ||||
| // IsSmallerThan returns if a box is smaller than another box. | ||||
| func (b Box) IsSmallerThan(other Box) bool { | ||||
| 	return b.Top > other.Top && | ||||
| 		b.Bottom < other.Bottom && | ||||
| 		b.Left > other.Left && | ||||
| 		b.Right < other.Right | ||||
| } | ||||
|  | ||||
| // Equals returns if the box equals another box. | ||||
| func (b Box) Equals(other Box) bool { | ||||
| 	return b.Top == other.Top && | ||||
| 		b.Left == other.Left && | ||||
| 		b.Right == other.Right && | ||||
| 		b.Bottom == other.Bottom | ||||
| } | ||||
|  | ||||
| // Grow grows a box based on another box. | ||||
| func (b Box) Grow(other Box) Box { | ||||
| 	return Box{ | ||||
| 		Top:    MinInt(b.Top, other.Top), | ||||
| 		Left:   MinInt(b.Left, other.Left), | ||||
| 		Right:  MaxInt(b.Right, other.Right), | ||||
| 		Bottom: MaxInt(b.Bottom, other.Bottom), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Shift pushes a box by x,y. | ||||
| func (b Box) Shift(x, y int) Box { | ||||
| 	return Box{ | ||||
| 		Top:    b.Top + y, | ||||
| 		Left:   b.Left + x, | ||||
| 		Right:  b.Right + x, | ||||
| 		Bottom: b.Bottom + y, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Corners returns the box as a set of corners. | ||||
| func (b Box) Corners() BoxCorners { | ||||
| 	return BoxCorners{ | ||||
| 		TopLeft:     Point{b.Left, b.Top}, | ||||
| 		TopRight:    Point{b.Right, b.Top}, | ||||
| 		BottomRight: Point{b.Right, b.Bottom}, | ||||
| 		BottomLeft:  Point{b.Left, b.Bottom}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Fit is functionally the inverse of grow. | ||||
| // Fit maintains the original aspect ratio of the `other` box, | ||||
| // but constrains it to the bounds of the target box. | ||||
| func (b Box) Fit(other Box) Box { | ||||
| 	ba := b.Aspect() | ||||
| 	oa := other.Aspect() | ||||
|  | ||||
| 	if oa == ba { | ||||
| 		return b.Clone() | ||||
| 	} | ||||
|  | ||||
| 	bw, bh := float64(b.Width()), float64(b.Height()) | ||||
| 	bw2 := int(bw) >> 1 | ||||
| 	bh2 := int(bh) >> 1 | ||||
| 	if oa > ba { // ex. 16:9 vs. 4:3 | ||||
| 		var noh2 int | ||||
| 		if oa > 1.0 { | ||||
| 			noh2 = int(bw/oa) >> 1 | ||||
| 		} else { | ||||
| 			noh2 = int(bh*oa) >> 1 | ||||
| 		} | ||||
| 		return Box{ | ||||
| 			Top:    (b.Top + bh2) - noh2, | ||||
| 			Left:   b.Left, | ||||
| 			Right:  b.Right, | ||||
| 			Bottom: (b.Top + bh2) + noh2, | ||||
| 		} | ||||
| 	} | ||||
| 	var now2 int | ||||
| 	if oa > 1.0 { | ||||
| 		now2 = int(bh/oa) >> 1 | ||||
| 	} else { | ||||
| 		now2 = int(bw*oa) >> 1 | ||||
| 	} | ||||
| 	return Box{ | ||||
| 		Top:    b.Top, | ||||
| 		Left:   (b.Left + bw2) - now2, | ||||
| 		Right:  (b.Left + bw2) + now2, | ||||
| 		Bottom: b.Bottom, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Constrain is similar to `Fit` except that it will work | ||||
| // more literally like the opposite of grow. | ||||
| func (b Box) Constrain(other Box) Box { | ||||
| 	newBox := b.Clone() | ||||
|  | ||||
| 	newBox.Top = MaxInt(newBox.Top, other.Top) | ||||
| 	newBox.Left = MaxInt(newBox.Left, other.Left) | ||||
| 	newBox.Right = MinInt(newBox.Right, other.Right) | ||||
| 	newBox.Bottom = MinInt(newBox.Bottom, other.Bottom) | ||||
|  | ||||
| 	return newBox | ||||
| } | ||||
|  | ||||
| // OuterConstrain is similar to `Constraint` with the difference | ||||
| // that it applies corrections | ||||
| func (b Box) OuterConstrain(bounds, other Box) Box { | ||||
| 	newBox := b.Clone() | ||||
| 	if other.Top < bounds.Top { | ||||
| 		delta := bounds.Top - other.Top | ||||
| 		newBox.Top = b.Top + delta | ||||
| 	} | ||||
|  | ||||
| 	if other.Left < bounds.Left { | ||||
| 		delta := bounds.Left - other.Left | ||||
| 		newBox.Left = b.Left + delta | ||||
| 	} | ||||
|  | ||||
| 	if other.Right > bounds.Right { | ||||
| 		delta := other.Right - bounds.Right | ||||
| 		newBox.Right = b.Right - delta | ||||
| 	} | ||||
|  | ||||
| 	if other.Bottom > bounds.Bottom { | ||||
| 		delta := other.Bottom - bounds.Bottom | ||||
| 		newBox.Bottom = b.Bottom - delta | ||||
| 	} | ||||
| 	return newBox | ||||
| } | ||||
|  | ||||
| // BoxCorners is a box with independent corners. | ||||
| type BoxCorners struct { | ||||
| 	TopLeft, TopRight, BottomRight, BottomLeft Point | ||||
| } | ||||
|  | ||||
| // Box return the BoxCorners as a regular box. | ||||
| func (bc BoxCorners) Box() Box { | ||||
| 	return Box{ | ||||
| 		Top:    MinInt(bc.TopLeft.Y, bc.TopRight.Y), | ||||
| 		Left:   MinInt(bc.TopLeft.X, bc.BottomLeft.X), | ||||
| 		Right:  MaxInt(bc.TopRight.X, bc.BottomRight.X), | ||||
| 		Bottom: MaxInt(bc.BottomLeft.Y, bc.BottomRight.Y), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Width returns the width | ||||
| func (bc BoxCorners) Width() int { | ||||
| 	minLeft := MinInt(bc.TopLeft.X, bc.BottomLeft.X) | ||||
| 	maxRight := MaxInt(bc.TopRight.X, bc.BottomRight.X) | ||||
| 	return maxRight - minLeft | ||||
| } | ||||
|  | ||||
| // Height returns the height | ||||
| func (bc BoxCorners) Height() int { | ||||
| 	minTop := MinInt(bc.TopLeft.Y, bc.TopRight.Y) | ||||
| 	maxBottom := MaxInt(bc.BottomLeft.Y, bc.BottomRight.Y) | ||||
| 	return maxBottom - minTop | ||||
| } | ||||
|  | ||||
| // Center returns the center of the box | ||||
| func (bc BoxCorners) Center() (x, y int) { | ||||
|  | ||||
| 	left := MeanInt(bc.TopLeft.X, bc.BottomLeft.X) | ||||
| 	right := MeanInt(bc.TopRight.X, bc.BottomRight.X) | ||||
| 	x = ((right - left) >> 1) + left | ||||
|  | ||||
| 	top := MeanInt(bc.TopLeft.Y, bc.TopRight.Y) | ||||
| 	bottom := MeanInt(bc.BottomLeft.Y, bc.BottomRight.Y) | ||||
| 	y = ((bottom - top) >> 1) + top | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Rotate rotates the box. | ||||
| func (bc BoxCorners) Rotate(thetaDegrees float64) BoxCorners { | ||||
| 	cx, cy := bc.Center() | ||||
|  | ||||
| 	thetaRadians := DegreesToRadians(thetaDegrees) | ||||
|  | ||||
| 	tlx, tly := RotateCoordinate(cx, cy, bc.TopLeft.X, bc.TopLeft.Y, thetaRadians) | ||||
| 	trx, try := RotateCoordinate(cx, cy, bc.TopRight.X, bc.TopRight.Y, thetaRadians) | ||||
| 	brx, bry := RotateCoordinate(cx, cy, bc.BottomRight.X, bc.BottomRight.Y, thetaRadians) | ||||
| 	blx, bly := RotateCoordinate(cx, cy, bc.BottomLeft.X, bc.BottomLeft.Y, thetaRadians) | ||||
|  | ||||
| 	return BoxCorners{ | ||||
| 		TopLeft:     Point{tlx, tly}, | ||||
| 		TopRight:    Point{trx, try}, | ||||
| 		BottomRight: Point{brx, bry}, | ||||
| 		BottomLeft:  Point{blx, bly}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Equals returns if the box equals another box. | ||||
| func (bc BoxCorners) Equals(other BoxCorners) bool { | ||||
| 	return bc.TopLeft.Equals(other.TopLeft) && | ||||
| 		bc.TopRight.Equals(other.TopRight) && | ||||
| 		bc.BottomRight.Equals(other.BottomRight) && | ||||
| 		bc.BottomLeft.Equals(other.BottomLeft) | ||||
| } | ||||
|  | ||||
| func (bc BoxCorners) String() string { | ||||
| 	return fmt.Sprintf("BoxC{%s,%s,%s,%s}", bc.TopLeft.String(), bc.TopRight.String(), bc.BottomRight.String(), bc.BottomLeft.String()) | ||||
| } | ||||
|  | ||||
| // Point is an X,Y pair | ||||
| type Point struct { | ||||
| 	X, Y int | ||||
| } | ||||
|  | ||||
| // DistanceTo calculates the distance to another point. | ||||
| func (p Point) DistanceTo(other Point) float64 { | ||||
| 	dx := math.Pow(float64(p.X-other.X), 2) | ||||
| 	dy := math.Pow(float64(p.Y-other.Y), 2) | ||||
| 	return math.Pow(dx+dy, 0.5) | ||||
| } | ||||
|  | ||||
| // Equals returns if a point equals another point. | ||||
| func (p Point) Equals(other Point) bool { | ||||
| 	return p.X == other.X && p.Y == other.Y | ||||
| } | ||||
|  | ||||
| // String returns a string representation of the point. | ||||
| func (p Point) String() string { | ||||
| 	return fmt.Sprintf("P{%d,%d}", p.X, p.Y) | ||||
| } | ||||
							
								
								
									
										577
									
								
								vendor/github.com/wcharczuk/go-chart/v2/chart.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										577
									
								
								vendor/github.com/wcharczuk/go-chart/v2/chart.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,577 @@ | ||||
| package chart | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"math" | ||||
|  | ||||
| 	"github.com/golang/freetype/truetype" | ||||
| ) | ||||
|  | ||||
| // Chart is what we're drawing. | ||||
| type Chart struct { | ||||
| 	Title      string | ||||
| 	TitleStyle Style | ||||
|  | ||||
| 	ColorPalette ColorPalette | ||||
|  | ||||
| 	Width  int | ||||
| 	Height int | ||||
| 	DPI    float64 | ||||
|  | ||||
| 	Background Style | ||||
| 	Canvas     Style | ||||
|  | ||||
| 	XAxis          XAxis | ||||
| 	YAxis          YAxis | ||||
| 	YAxisSecondary YAxis | ||||
|  | ||||
| 	Font        *truetype.Font | ||||
| 	defaultFont *truetype.Font | ||||
|  | ||||
| 	Series   []Series | ||||
| 	Elements []Renderable | ||||
|  | ||||
| 	Log Logger | ||||
| } | ||||
|  | ||||
| // GetDPI returns the dpi for the chart. | ||||
| func (c Chart) GetDPI(defaults ...float64) float64 { | ||||
| 	if c.DPI == 0 { | ||||
| 		if len(defaults) > 0 { | ||||
| 			return defaults[0] | ||||
| 		} | ||||
| 		return DefaultDPI | ||||
| 	} | ||||
| 	return c.DPI | ||||
| } | ||||
|  | ||||
| // GetFont returns the text font. | ||||
| func (c Chart) GetFont() *truetype.Font { | ||||
| 	if c.Font == nil { | ||||
| 		return c.defaultFont | ||||
| 	} | ||||
| 	return c.Font | ||||
| } | ||||
|  | ||||
| // GetWidth returns the chart width or the default value. | ||||
| func (c Chart) GetWidth() int { | ||||
| 	if c.Width == 0 { | ||||
| 		return DefaultChartWidth | ||||
| 	} | ||||
| 	return c.Width | ||||
| } | ||||
|  | ||||
| // GetHeight returns the chart height or the default value. | ||||
| func (c Chart) GetHeight() int { | ||||
| 	if c.Height == 0 { | ||||
| 		return DefaultChartHeight | ||||
| 	} | ||||
| 	return c.Height | ||||
| } | ||||
|  | ||||
| // Render renders the chart with the given renderer to the given io.Writer. | ||||
| func (c Chart) Render(rp RendererProvider, w io.Writer) error { | ||||
| 	if len(c.Series) == 0 { | ||||
| 		return errors.New("please provide at least one series") | ||||
| 	} | ||||
| 	if err := c.checkHasVisibleSeries(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	c.YAxisSecondary.AxisType = YAxisSecondary | ||||
|  | ||||
| 	r, err := rp(c.GetWidth(), c.GetHeight()) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if c.Font == nil { | ||||
| 		defaultFont, err := GetDefaultFont() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		c.defaultFont = defaultFont | ||||
| 	} | ||||
| 	r.SetDPI(c.GetDPI(DefaultDPI)) | ||||
|  | ||||
| 	c.drawBackground(r) | ||||
|  | ||||
| 	var xt, yt, yta []Tick | ||||
| 	xr, yr, yra := c.getRanges() | ||||
| 	canvasBox := c.getDefaultCanvasBox() | ||||
| 	xf, yf, yfa := c.getValueFormatters() | ||||
|  | ||||
| 	Debugf(c.Log, "chart; canvas box: %v", canvasBox) | ||||
|  | ||||
| 	xr, yr, yra = c.setRangeDomains(canvasBox, xr, yr, yra) | ||||
|  | ||||
| 	err = c.checkRanges(xr, yr, yra) | ||||
| 	if err != nil { | ||||
| 		r.Save(w) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if c.hasAxes() { | ||||
| 		xt, yt, yta = c.getAxesTicks(r, xr, yr, yra, xf, yf, yfa) | ||||
| 		canvasBox = c.getAxesAdjustedCanvasBox(r, canvasBox, xr, yr, yra, xt, yt, yta) | ||||
| 		xr, yr, yra = c.setRangeDomains(canvasBox, xr, yr, yra) | ||||
|  | ||||
| 		Debugf(c.Log, "chart; axes adjusted canvas box: %v", canvasBox) | ||||
|  | ||||
| 		// do a second pass in case things haven't settled yet. | ||||
| 		xt, yt, yta = c.getAxesTicks(r, xr, yr, yra, xf, yf, yfa) | ||||
| 		canvasBox = c.getAxesAdjustedCanvasBox(r, canvasBox, xr, yr, yra, xt, yt, yta) | ||||
| 		xr, yr, yra = c.setRangeDomains(canvasBox, xr, yr, yra) | ||||
| 	} | ||||
|  | ||||
| 	if c.hasAnnotationSeries() { | ||||
| 		canvasBox = c.getAnnotationAdjustedCanvasBox(r, canvasBox, xr, yr, yra, xf, yf, yfa) | ||||
| 		xr, yr, yra = c.setRangeDomains(canvasBox, xr, yr, yra) | ||||
| 		xt, yt, yta = c.getAxesTicks(r, xr, yr, yra, xf, yf, yfa) | ||||
|  | ||||
| 		Debugf(c.Log, "chart; annotation adjusted canvas box: %v", canvasBox) | ||||
| 	} | ||||
|  | ||||
| 	c.drawCanvas(r, canvasBox) | ||||
| 	c.drawAxes(r, canvasBox, xr, yr, yra, xt, yt, yta) | ||||
| 	for index, series := range c.Series { | ||||
| 		c.drawSeries(r, canvasBox, xr, yr, yra, series, index) | ||||
| 	} | ||||
|  | ||||
| 	c.drawTitle(r) | ||||
|  | ||||
| 	for _, a := range c.Elements { | ||||
| 		a(r, canvasBox, c.styleDefaultsElements()) | ||||
| 	} | ||||
|  | ||||
| 	return r.Save(w) | ||||
| } | ||||
|  | ||||
| func (c Chart) checkHasVisibleSeries() error { | ||||
| 	var style Style | ||||
| 	for _, s := range c.Series { | ||||
| 		style = s.GetStyle() | ||||
| 		if !style.Hidden { | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
| 	return fmt.Errorf("chart render; must have (1) visible series") | ||||
| } | ||||
|  | ||||
| func (c Chart) validateSeries() error { | ||||
| 	var err error | ||||
| 	for _, s := range c.Series { | ||||
| 		err = s.Validate() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) { | ||||
| 	var minx, maxx float64 = math.MaxFloat64, -math.MaxFloat64 | ||||
| 	var miny, maxy float64 = math.MaxFloat64, -math.MaxFloat64 | ||||
| 	var minya, maxya float64 = math.MaxFloat64, -math.MaxFloat64 | ||||
|  | ||||
| 	seriesMappedToSecondaryAxis := false | ||||
|  | ||||
| 	// note: a possible future optimization is to not scan the series values if | ||||
| 	// all axis are represented by either custom ticks or custom ranges. | ||||
| 	for _, s := range c.Series { | ||||
| 		if !s.GetStyle().Hidden { | ||||
| 			seriesAxis := s.GetYAxis() | ||||
| 			if bvp, isBoundedValuesProvider := s.(BoundedValuesProvider); isBoundedValuesProvider { | ||||
| 				seriesLength := bvp.Len() | ||||
| 				for index := 0; index < seriesLength; index++ { | ||||
| 					vx, vy1, vy2 := bvp.GetBoundedValues(index) | ||||
|  | ||||
| 					minx = math.Min(minx, vx) | ||||
| 					maxx = math.Max(maxx, vx) | ||||
|  | ||||
| 					if seriesAxis == YAxisPrimary { | ||||
| 						miny = math.Min(miny, vy1) | ||||
| 						miny = math.Min(miny, vy2) | ||||
| 						maxy = math.Max(maxy, vy1) | ||||
| 						maxy = math.Max(maxy, vy2) | ||||
| 					} else if seriesAxis == YAxisSecondary { | ||||
| 						minya = math.Min(minya, vy1) | ||||
| 						minya = math.Min(minya, vy2) | ||||
| 						maxya = math.Max(maxya, vy1) | ||||
| 						maxya = math.Max(maxya, vy2) | ||||
| 						seriesMappedToSecondaryAxis = true | ||||
| 					} | ||||
| 				} | ||||
| 			} else if vp, isValuesProvider := s.(ValuesProvider); isValuesProvider { | ||||
| 				seriesLength := vp.Len() | ||||
| 				for index := 0; index < seriesLength; index++ { | ||||
| 					vx, vy := vp.GetValues(index) | ||||
|  | ||||
| 					minx = math.Min(minx, vx) | ||||
| 					maxx = math.Max(maxx, vx) | ||||
|  | ||||
| 					if seriesAxis == YAxisPrimary { | ||||
| 						miny = math.Min(miny, vy) | ||||
| 						maxy = math.Max(maxy, vy) | ||||
| 					} else if seriesAxis == YAxisSecondary { | ||||
| 						minya = math.Min(minya, vy) | ||||
| 						maxya = math.Max(maxya, vy) | ||||
| 						seriesMappedToSecondaryAxis = true | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if c.XAxis.Range == nil { | ||||
| 		xrange = &ContinuousRange{} | ||||
| 	} else { | ||||
| 		xrange = c.XAxis.Range | ||||
| 	} | ||||
|  | ||||
| 	if c.YAxis.Range == nil { | ||||
| 		yrange = &ContinuousRange{} | ||||
| 	} else { | ||||
| 		yrange = c.YAxis.Range | ||||
| 	} | ||||
|  | ||||
| 	if c.YAxisSecondary.Range == nil { | ||||
| 		yrangeAlt = &ContinuousRange{} | ||||
| 	} else { | ||||
| 		yrangeAlt = c.YAxisSecondary.Range | ||||
| 	} | ||||
|  | ||||
| 	if len(c.XAxis.Ticks) > 0 { | ||||
| 		tickMin, tickMax := math.MaxFloat64, -math.MaxFloat64 | ||||
| 		for _, t := range c.XAxis.Ticks { | ||||
| 			tickMin = math.Min(tickMin, t.Value) | ||||
| 			tickMax = math.Max(tickMax, t.Value) | ||||
| 		} | ||||
| 		xrange.SetMin(tickMin) | ||||
| 		xrange.SetMax(tickMax) | ||||
| 	} else if xrange.IsZero() { | ||||
| 		xrange.SetMin(minx) | ||||
| 		xrange.SetMax(maxx) | ||||
| 	} | ||||
|  | ||||
| 	if len(c.YAxis.Ticks) > 0 { | ||||
| 		tickMin, tickMax := math.MaxFloat64, -math.MaxFloat64 | ||||
| 		for _, t := range c.YAxis.Ticks { | ||||
| 			tickMin = math.Min(tickMin, t.Value) | ||||
| 			tickMax = math.Max(tickMax, t.Value) | ||||
| 		} | ||||
| 		yrange.SetMin(tickMin) | ||||
| 		yrange.SetMax(tickMax) | ||||
| 	} else if yrange.IsZero() { | ||||
| 		yrange.SetMin(miny) | ||||
| 		yrange.SetMax(maxy) | ||||
|  | ||||
| 		if !c.YAxis.Style.Hidden { | ||||
| 			delta := yrange.GetDelta() | ||||
| 			roundTo := GetRoundToForDelta(delta) | ||||
| 			rmin, rmax := RoundDown(yrange.GetMin(), roundTo), RoundUp(yrange.GetMax(), roundTo) | ||||
|  | ||||
| 			yrange.SetMin(rmin) | ||||
| 			yrange.SetMax(rmax) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if len(c.YAxisSecondary.Ticks) > 0 { | ||||
| 		tickMin, tickMax := math.MaxFloat64, -math.MaxFloat64 | ||||
| 		for _, t := range c.YAxis.Ticks { | ||||
| 			tickMin = math.Min(tickMin, t.Value) | ||||
| 			tickMax = math.Max(tickMax, t.Value) | ||||
| 		} | ||||
| 		yrangeAlt.SetMin(tickMin) | ||||
| 		yrangeAlt.SetMax(tickMax) | ||||
| 	} else if seriesMappedToSecondaryAxis && yrangeAlt.IsZero() { | ||||
| 		yrangeAlt.SetMin(minya) | ||||
| 		yrangeAlt.SetMax(maxya) | ||||
|  | ||||
| 		if !c.YAxisSecondary.Style.Hidden { | ||||
| 			delta := yrangeAlt.GetDelta() | ||||
| 			roundTo := GetRoundToForDelta(delta) | ||||
| 			rmin, rmax := RoundDown(yrangeAlt.GetMin(), roundTo), RoundUp(yrangeAlt.GetMax(), roundTo) | ||||
| 			yrangeAlt.SetMin(rmin) | ||||
| 			yrangeAlt.SetMax(rmax) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (c Chart) checkRanges(xr, yr, yra Range) error { | ||||
| 	Debugf(c.Log, "checking xrange: %v", xr) | ||||
| 	xDelta := xr.GetDelta() | ||||
| 	if math.IsInf(xDelta, 0) { | ||||
| 		return errors.New("infinite x-range delta") | ||||
| 	} | ||||
| 	if math.IsNaN(xDelta) { | ||||
| 		return errors.New("nan x-range delta") | ||||
| 	} | ||||
| 	if xDelta == 0 { | ||||
| 		return errors.New("zero x-range delta; there needs to be at least (2) values") | ||||
| 	} | ||||
|  | ||||
| 	Debugf(c.Log, "checking yrange: %v", yr) | ||||
| 	yDelta := yr.GetDelta() | ||||
| 	if math.IsInf(yDelta, 0) { | ||||
| 		return errors.New("infinite y-range delta") | ||||
| 	} | ||||
| 	if math.IsNaN(yDelta) { | ||||
| 		return errors.New("nan y-range delta") | ||||
| 	} | ||||
|  | ||||
| 	if c.hasSecondarySeries() { | ||||
| 		Debugf(c.Log, "checking secondary yrange: %v", yra) | ||||
| 		yraDelta := yra.GetDelta() | ||||
| 		if math.IsInf(yraDelta, 0) { | ||||
| 			return errors.New("infinite secondary y-range delta") | ||||
| 		} | ||||
| 		if math.IsNaN(yraDelta) { | ||||
| 			return errors.New("nan secondary y-range delta") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (c Chart) getDefaultCanvasBox() Box { | ||||
| 	return c.Box() | ||||
| } | ||||
|  | ||||
| func (c Chart) getValueFormatters() (x, y, ya ValueFormatter) { | ||||
| 	for _, s := range c.Series { | ||||
| 		if vfp, isVfp := s.(ValueFormatterProvider); isVfp { | ||||
| 			sx, sy := vfp.GetValueFormatters() | ||||
| 			if s.GetYAxis() == YAxisPrimary { | ||||
| 				x = sx | ||||
| 				y = sy | ||||
| 			} else if s.GetYAxis() == YAxisSecondary { | ||||
| 				x = sx | ||||
| 				ya = sy | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	if c.XAxis.ValueFormatter != nil { | ||||
| 		x = c.XAxis.GetValueFormatter() | ||||
| 	} | ||||
| 	if c.YAxis.ValueFormatter != nil { | ||||
| 		y = c.YAxis.GetValueFormatter() | ||||
| 	} | ||||
| 	if c.YAxisSecondary.ValueFormatter != nil { | ||||
| 		ya = c.YAxisSecondary.GetValueFormatter() | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (c Chart) hasAxes() bool { | ||||
| 	return !c.XAxis.Style.Hidden || !c.YAxis.Style.Hidden || !c.YAxisSecondary.Style.Hidden | ||||
| } | ||||
|  | ||||
| func (c Chart) getAxesTicks(r Renderer, xr, yr, yar Range, xf, yf, yfa ValueFormatter) (xticks, yticks, yticksAlt []Tick) { | ||||
| 	if !c.XAxis.Style.Hidden { | ||||
| 		xticks = c.XAxis.GetTicks(r, xr, c.styleDefaultsAxes(), xf) | ||||
| 	} | ||||
| 	if !c.YAxis.Style.Hidden { | ||||
| 		yticks = c.YAxis.GetTicks(r, yr, c.styleDefaultsAxes(), yf) | ||||
| 	} | ||||
| 	if !c.YAxisSecondary.Style.Hidden { | ||||
| 		yticksAlt = c.YAxisSecondary.GetTicks(r, yar, c.styleDefaultsAxes(), yfa) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (c Chart) getAxesAdjustedCanvasBox(r Renderer, canvasBox Box, xr, yr, yra Range, xticks, yticks, yticksAlt []Tick) Box { | ||||
| 	axesOuterBox := canvasBox.Clone() | ||||
| 	if !c.XAxis.Style.Hidden { | ||||
| 		axesBounds := c.XAxis.Measure(r, canvasBox, xr, c.styleDefaultsAxes(), xticks) | ||||
| 		Debugf(c.Log, "chart; x-axis measured %v", axesBounds) | ||||
| 		axesOuterBox = axesOuterBox.Grow(axesBounds) | ||||
| 	} | ||||
| 	if !c.YAxis.Style.Hidden { | ||||
| 		axesBounds := c.YAxis.Measure(r, canvasBox, yr, c.styleDefaultsAxes(), yticks) | ||||
| 		Debugf(c.Log, "chart; y-axis measured %v", axesBounds) | ||||
| 		axesOuterBox = axesOuterBox.Grow(axesBounds) | ||||
| 	} | ||||
| 	if !c.YAxisSecondary.Style.Hidden && c.hasSecondarySeries() { | ||||
| 		axesBounds := c.YAxisSecondary.Measure(r, canvasBox, yra, c.styleDefaultsAxes(), yticksAlt) | ||||
| 		Debugf(c.Log, "chart; y-axis secondary measured %v", axesBounds) | ||||
| 		axesOuterBox = axesOuterBox.Grow(axesBounds) | ||||
| 	} | ||||
|  | ||||
| 	return canvasBox.OuterConstrain(c.Box(), axesOuterBox) | ||||
| } | ||||
|  | ||||
| func (c Chart) setRangeDomains(canvasBox Box, xr, yr, yra Range) (Range, Range, Range) { | ||||
| 	xr.SetDomain(canvasBox.Width()) | ||||
| 	yr.SetDomain(canvasBox.Height()) | ||||
| 	yra.SetDomain(canvasBox.Height()) | ||||
| 	return xr, yr, yra | ||||
| } | ||||
|  | ||||
| func (c Chart) hasAnnotationSeries() bool { | ||||
| 	for _, s := range c.Series { | ||||
| 		if as, isAnnotationSeries := s.(AnnotationSeries); isAnnotationSeries { | ||||
| 			if !as.GetStyle().Hidden { | ||||
| 				return true | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (c Chart) hasSecondarySeries() bool { | ||||
| 	for _, s := range c.Series { | ||||
| 		if s.GetYAxis() == YAxisSecondary { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (c Chart) getAnnotationAdjustedCanvasBox(r Renderer, canvasBox Box, xr, yr, yra Range, xf, yf, yfa ValueFormatter) Box { | ||||
| 	annotationSeriesBox := canvasBox.Clone() | ||||
| 	for seriesIndex, s := range c.Series { | ||||
| 		if as, isAnnotationSeries := s.(AnnotationSeries); isAnnotationSeries { | ||||
| 			if !as.GetStyle().Hidden { | ||||
| 				style := c.styleDefaultsSeries(seriesIndex) | ||||
| 				var annotationBounds Box | ||||
| 				if as.YAxis == YAxisPrimary { | ||||
| 					annotationBounds = as.Measure(r, canvasBox, xr, yr, style) | ||||
| 				} else if as.YAxis == YAxisSecondary { | ||||
| 					annotationBounds = as.Measure(r, canvasBox, xr, yra, style) | ||||
| 				} | ||||
|  | ||||
| 				annotationSeriesBox = annotationSeriesBox.Grow(annotationBounds) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return canvasBox.OuterConstrain(c.Box(), annotationSeriesBox) | ||||
| } | ||||
|  | ||||
| func (c Chart) getBackgroundStyle() Style { | ||||
| 	return c.Background.InheritFrom(c.styleDefaultsBackground()) | ||||
| } | ||||
|  | ||||
| func (c Chart) drawBackground(r Renderer) { | ||||
| 	Draw.Box(r, Box{ | ||||
| 		Right:  c.GetWidth(), | ||||
| 		Bottom: c.GetHeight(), | ||||
| 	}, c.getBackgroundStyle()) | ||||
| } | ||||
|  | ||||
| func (c Chart) getCanvasStyle() Style { | ||||
| 	return c.Canvas.InheritFrom(c.styleDefaultsCanvas()) | ||||
| } | ||||
|  | ||||
| func (c Chart) drawCanvas(r Renderer, canvasBox Box) { | ||||
| 	Draw.Box(r, canvasBox, c.getCanvasStyle()) | ||||
| } | ||||
|  | ||||
| func (c Chart) drawAxes(r Renderer, canvasBox Box, xrange, yrange, yrangeAlt Range, xticks, yticks, yticksAlt []Tick) { | ||||
| 	if !c.XAxis.Style.Hidden { | ||||
| 		c.XAxis.Render(r, canvasBox, xrange, c.styleDefaultsAxes(), xticks) | ||||
| 	} | ||||
| 	if !c.YAxis.Style.Hidden { | ||||
| 		c.YAxis.Render(r, canvasBox, yrange, c.styleDefaultsAxes(), yticks) | ||||
| 	} | ||||
| 	if !c.YAxisSecondary.Style.Hidden { | ||||
| 		c.YAxisSecondary.Render(r, canvasBox, yrangeAlt, c.styleDefaultsAxes(), yticksAlt) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c Chart) drawSeries(r Renderer, canvasBox Box, xrange, yrange, yrangeAlt Range, s Series, seriesIndex int) { | ||||
| 	if !s.GetStyle().Hidden { | ||||
| 		if s.GetYAxis() == YAxisPrimary { | ||||
| 			s.Render(r, canvasBox, xrange, yrange, c.styleDefaultsSeries(seriesIndex)) | ||||
| 		} else if s.GetYAxis() == YAxisSecondary { | ||||
| 			s.Render(r, canvasBox, xrange, yrangeAlt, c.styleDefaultsSeries(seriesIndex)) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c Chart) drawTitle(r Renderer) { | ||||
| 	if len(c.Title) > 0 && !c.TitleStyle.Hidden { | ||||
| 		r.SetFont(c.TitleStyle.GetFont(c.GetFont())) | ||||
| 		r.SetFontColor(c.TitleStyle.GetFontColor(c.GetColorPalette().TextColor())) | ||||
| 		titleFontSize := c.TitleStyle.GetFontSize(DefaultTitleFontSize) | ||||
| 		r.SetFontSize(titleFontSize) | ||||
|  | ||||
| 		textBox := r.MeasureText(c.Title) | ||||
|  | ||||
| 		textWidth := textBox.Width() | ||||
| 		textHeight := textBox.Height() | ||||
|  | ||||
| 		titleX := (c.GetWidth() >> 1) - (textWidth >> 1) | ||||
| 		titleY := c.TitleStyle.Padding.GetTop(DefaultTitleTop) + textHeight | ||||
|  | ||||
| 		r.Text(c.Title, titleX, titleY) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c Chart) styleDefaultsBackground() Style { | ||||
| 	return Style{ | ||||
| 		FillColor:   c.GetColorPalette().BackgroundColor(), | ||||
| 		StrokeColor: c.GetColorPalette().BackgroundStrokeColor(), | ||||
| 		StrokeWidth: DefaultBackgroundStrokeWidth, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c Chart) styleDefaultsCanvas() Style { | ||||
| 	return Style{ | ||||
| 		FillColor:   c.GetColorPalette().CanvasColor(), | ||||
| 		StrokeColor: c.GetColorPalette().CanvasStrokeColor(), | ||||
| 		StrokeWidth: DefaultCanvasStrokeWidth, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c Chart) styleDefaultsSeries(seriesIndex int) Style { | ||||
| 	return Style{ | ||||
| 		DotColor:    c.GetColorPalette().GetSeriesColor(seriesIndex), | ||||
| 		StrokeColor: c.GetColorPalette().GetSeriesColor(seriesIndex), | ||||
| 		StrokeWidth: DefaultSeriesLineWidth, | ||||
| 		Font:        c.GetFont(), | ||||
| 		FontSize:    DefaultFontSize, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c Chart) styleDefaultsAxes() Style { | ||||
| 	return Style{ | ||||
| 		Font:        c.GetFont(), | ||||
| 		FontColor:   c.GetColorPalette().TextColor(), | ||||
| 		FontSize:    DefaultAxisFontSize, | ||||
| 		StrokeColor: c.GetColorPalette().AxisStrokeColor(), | ||||
| 		StrokeWidth: DefaultAxisLineWidth, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c Chart) styleDefaultsElements() Style { | ||||
| 	return Style{ | ||||
| 		Font: c.GetFont(), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetColorPalette returns the color palette for the chart. | ||||
| func (c Chart) GetColorPalette() ColorPalette { | ||||
| 	if c.ColorPalette != nil { | ||||
| 		return c.ColorPalette | ||||
| 	} | ||||
| 	return DefaultColorPalette | ||||
| } | ||||
|  | ||||
| // Box returns the chart bounds as a box. | ||||
| func (c Chart) Box() Box { | ||||
| 	dpr := c.Background.Padding.GetRight(DefaultBackgroundPadding.Right) | ||||
| 	dpb := c.Background.Padding.GetBottom(DefaultBackgroundPadding.Bottom) | ||||
|  | ||||
| 	return Box{ | ||||
| 		Top:    c.Background.Padding.GetTop(DefaultBackgroundPadding.Top), | ||||
| 		Left:   c.Background.Padding.GetLeft(DefaultBackgroundPadding.Left), | ||||
| 		Right:  c.GetWidth() - dpr, | ||||
| 		Bottom: c.GetHeight() - dpb, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										184
									
								
								vendor/github.com/wcharczuk/go-chart/v2/colors.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								vendor/github.com/wcharczuk/go-chart/v2/colors.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,184 @@ | ||||
| package chart | ||||
|  | ||||
| import "github.com/wcharczuk/go-chart/v2/drawing" | ||||
|  | ||||
| var ( | ||||
| 	// ColorWhite is white. | ||||
| 	ColorWhite = drawing.Color{R: 255, G: 255, B: 255, A: 255} | ||||
| 	// ColorBlue is the basic theme blue color. | ||||
| 	ColorBlue = drawing.Color{R: 0, G: 116, B: 217, A: 255} | ||||
| 	// ColorCyan is the basic theme cyan color. | ||||
| 	ColorCyan = drawing.Color{R: 0, G: 217, B: 210, A: 255} | ||||
| 	// ColorGreen is the basic theme green color. | ||||
| 	ColorGreen = drawing.Color{R: 0, G: 217, B: 101, A: 255} | ||||
| 	// ColorRed is the basic theme red color. | ||||
| 	ColorRed = drawing.Color{R: 217, G: 0, B: 116, A: 255} | ||||
| 	// ColorOrange is the basic theme orange color. | ||||
| 	ColorOrange = drawing.Color{R: 217, G: 101, B: 0, A: 255} | ||||
| 	// ColorYellow is the basic theme yellow color. | ||||
| 	ColorYellow = drawing.Color{R: 217, G: 210, B: 0, A: 255} | ||||
| 	// ColorBlack is the basic theme black color. | ||||
| 	ColorBlack = drawing.Color{R: 51, G: 51, B: 51, A: 255} | ||||
| 	// ColorLightGray is the basic theme light gray color. | ||||
| 	ColorLightGray = drawing.Color{R: 239, G: 239, B: 239, A: 255} | ||||
|  | ||||
| 	// ColorAlternateBlue is a alternate theme color. | ||||
| 	ColorAlternateBlue = drawing.Color{R: 106, G: 195, B: 203, A: 255} | ||||
| 	// ColorAlternateGreen is a alternate theme color. | ||||
| 	ColorAlternateGreen = drawing.Color{R: 42, G: 190, B: 137, A: 255} | ||||
| 	// ColorAlternateGray is a alternate theme color. | ||||
| 	ColorAlternateGray = drawing.Color{R: 110, G: 128, B: 139, A: 255} | ||||
| 	// ColorAlternateYellow is a alternate theme color. | ||||
| 	ColorAlternateYellow = drawing.Color{R: 240, G: 174, B: 90, A: 255} | ||||
| 	// ColorAlternateLightGray is a alternate theme color. | ||||
| 	ColorAlternateLightGray = drawing.Color{R: 187, G: 190, B: 191, A: 255} | ||||
|  | ||||
| 	// ColorTransparent is a transparent (alpha zero) color. | ||||
| 	ColorTransparent = drawing.Color{R: 1, G: 1, B: 1, A: 0} | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// DefaultBackgroundColor is the default chart background color. | ||||
| 	// It is equivalent to css color:white. | ||||
| 	DefaultBackgroundColor = ColorWhite | ||||
| 	// DefaultBackgroundStrokeColor is the default chart border color. | ||||
| 	// It is equivalent to color:white. | ||||
| 	DefaultBackgroundStrokeColor = ColorWhite | ||||
| 	// DefaultCanvasColor is the default chart canvas color. | ||||
| 	// It is equivalent to css color:white. | ||||
| 	DefaultCanvasColor = ColorWhite | ||||
| 	// DefaultCanvasStrokeColor is the default chart canvas stroke color. | ||||
| 	// It is equivalent to css color:white. | ||||
| 	DefaultCanvasStrokeColor = ColorWhite | ||||
| 	// DefaultTextColor is the default chart text color. | ||||
| 	// It is equivalent to #333333. | ||||
| 	DefaultTextColor = ColorBlack | ||||
| 	// DefaultAxisColor is the default chart axis line color. | ||||
| 	// It is equivalent to #333333. | ||||
| 	DefaultAxisColor = ColorBlack | ||||
| 	// DefaultStrokeColor is the default chart border color. | ||||
| 	// It is equivalent to #efefef. | ||||
| 	DefaultStrokeColor = ColorLightGray | ||||
| 	// DefaultFillColor is the default fill color. | ||||
| 	// It is equivalent to #0074d9. | ||||
| 	DefaultFillColor = ColorBlue | ||||
| 	// DefaultAnnotationFillColor is the default annotation background color. | ||||
| 	DefaultAnnotationFillColor = ColorWhite | ||||
| 	// DefaultGridLineColor is the default grid line color. | ||||
| 	DefaultGridLineColor = ColorLightGray | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// DefaultColors are a couple default series colors. | ||||
| 	DefaultColors = []drawing.Color{ | ||||
| 		ColorBlue, | ||||
| 		ColorGreen, | ||||
| 		ColorRed, | ||||
| 		ColorCyan, | ||||
| 		ColorOrange, | ||||
| 	} | ||||
|  | ||||
| 	// DefaultAlternateColors are a couple alternate colors. | ||||
| 	DefaultAlternateColors = []drawing.Color{ | ||||
| 		ColorAlternateBlue, | ||||
| 		ColorAlternateGreen, | ||||
| 		ColorAlternateGray, | ||||
| 		ColorAlternateYellow, | ||||
| 		ColorBlue, | ||||
| 		ColorGreen, | ||||
| 		ColorRed, | ||||
| 		ColorCyan, | ||||
| 		ColorOrange, | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| // GetDefaultColor returns a color from the default list by index. | ||||
| // NOTE: the index will wrap around (using a modulo). | ||||
| func GetDefaultColor(index int) drawing.Color { | ||||
| 	finalIndex := index % len(DefaultColors) | ||||
| 	return DefaultColors[finalIndex] | ||||
| } | ||||
|  | ||||
| // GetAlternateColor returns a color from the default list by index. | ||||
| // NOTE: the index will wrap around (using a modulo). | ||||
| func GetAlternateColor(index int) drawing.Color { | ||||
| 	finalIndex := index % len(DefaultAlternateColors) | ||||
| 	return DefaultAlternateColors[finalIndex] | ||||
| } | ||||
|  | ||||
| // ColorPalette is a set of colors that. | ||||
| type ColorPalette interface { | ||||
| 	BackgroundColor() drawing.Color | ||||
| 	BackgroundStrokeColor() drawing.Color | ||||
| 	CanvasColor() drawing.Color | ||||
| 	CanvasStrokeColor() drawing.Color | ||||
| 	AxisStrokeColor() drawing.Color | ||||
| 	TextColor() drawing.Color | ||||
| 	GetSeriesColor(index int) drawing.Color | ||||
| } | ||||
|  | ||||
| // DefaultColorPalette represents the default palatte. | ||||
| var DefaultColorPalette defaultColorPalette | ||||
|  | ||||
| type defaultColorPalette struct{} | ||||
|  | ||||
| func (dp defaultColorPalette) BackgroundColor() drawing.Color { | ||||
| 	return DefaultBackgroundColor | ||||
| } | ||||
|  | ||||
| func (dp defaultColorPalette) BackgroundStrokeColor() drawing.Color { | ||||
| 	return DefaultBackgroundStrokeColor | ||||
| } | ||||
|  | ||||
| func (dp defaultColorPalette) CanvasColor() drawing.Color { | ||||
| 	return DefaultCanvasColor | ||||
| } | ||||
|  | ||||
| func (dp defaultColorPalette) CanvasStrokeColor() drawing.Color { | ||||
| 	return DefaultCanvasStrokeColor | ||||
| } | ||||
|  | ||||
| func (dp defaultColorPalette) AxisStrokeColor() drawing.Color { | ||||
| 	return DefaultAxisColor | ||||
| } | ||||
|  | ||||
| func (dp defaultColorPalette) TextColor() drawing.Color { | ||||
| 	return DefaultTextColor | ||||
| } | ||||
|  | ||||
| func (dp defaultColorPalette) GetSeriesColor(index int) drawing.Color { | ||||
| 	return GetDefaultColor(index) | ||||
| } | ||||
|  | ||||
| // AlternateColorPalette represents the default palatte. | ||||
| var AlternateColorPalette alternateColorPalette | ||||
|  | ||||
| type alternateColorPalette struct{} | ||||
|  | ||||
| func (ap alternateColorPalette) BackgroundColor() drawing.Color { | ||||
| 	return DefaultBackgroundColor | ||||
| } | ||||
|  | ||||
| func (ap alternateColorPalette) BackgroundStrokeColor() drawing.Color { | ||||
| 	return DefaultBackgroundStrokeColor | ||||
| } | ||||
|  | ||||
| func (ap alternateColorPalette) CanvasColor() drawing.Color { | ||||
| 	return DefaultCanvasColor | ||||
| } | ||||
|  | ||||
| func (ap alternateColorPalette) CanvasStrokeColor() drawing.Color { | ||||
| 	return DefaultCanvasStrokeColor | ||||
| } | ||||
|  | ||||
| func (ap alternateColorPalette) AxisStrokeColor() drawing.Color { | ||||
| 	return DefaultAxisColor | ||||
| } | ||||
|  | ||||
| func (ap alternateColorPalette) TextColor() drawing.Color { | ||||
| 	return DefaultTextColor | ||||
| } | ||||
|  | ||||
| func (ap alternateColorPalette) GetSeriesColor(index int) drawing.Color { | ||||
| 	return GetAlternateColor(index) | ||||
| } | ||||
							
								
								
									
										44
									
								
								vendor/github.com/wcharczuk/go-chart/v2/concat_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								vendor/github.com/wcharczuk/go-chart/v2/concat_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,44 @@ | ||||
| package chart | ||||
|  | ||||
| // ConcatSeries is a special type of series that concatenates its `InnerSeries`. | ||||
| type ConcatSeries []Series | ||||
|  | ||||
| // Len returns the length of the concatenated set of series. | ||||
| func (cs ConcatSeries) Len() int { | ||||
| 	total := 0 | ||||
| 	for _, s := range cs { | ||||
| 		if typed, isValuesProvider := s.(ValuesProvider); isValuesProvider { | ||||
| 			total += typed.Len() | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return total | ||||
| } | ||||
|  | ||||
| // GetValue returns the value at the (meta) index (i.e 0 => totalLen-1) | ||||
| func (cs ConcatSeries) GetValue(index int) (x, y float64) { | ||||
| 	cursor := 0 | ||||
| 	for _, s := range cs { | ||||
| 		if typed, isValuesProvider := s.(ValuesProvider); isValuesProvider { | ||||
| 			len := typed.Len() | ||||
| 			if index < cursor+len { | ||||
| 				x, y = typed.GetValues(index - cursor) //FENCEPOSTS. | ||||
| 				return | ||||
| 			} | ||||
| 			cursor += typed.Len() | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Validate validates the series. | ||||
| func (cs ConcatSeries) Validate() error { | ||||
| 	var err error | ||||
| 	for _, s := range cs { | ||||
| 		err = s.Validate() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										81
									
								
								vendor/github.com/wcharczuk/go-chart/v2/continuous_range.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								vendor/github.com/wcharczuk/go-chart/v2/continuous_range.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,81 @@ | ||||
| package chart | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"math" | ||||
| ) | ||||
|  | ||||
| // ContinuousRange represents a boundary for a set of numbers. | ||||
| type ContinuousRange struct { | ||||
| 	Min        float64 | ||||
| 	Max        float64 | ||||
| 	Domain     int | ||||
| 	Descending bool | ||||
| } | ||||
|  | ||||
| // IsDescending returns if the range is descending. | ||||
| func (r ContinuousRange) IsDescending() bool { | ||||
| 	return r.Descending | ||||
| } | ||||
|  | ||||
| // IsZero returns if the ContinuousRange has been set or not. | ||||
| func (r ContinuousRange) IsZero() bool { | ||||
| 	return (r.Min == 0 || math.IsNaN(r.Min)) && | ||||
| 		(r.Max == 0 || math.IsNaN(r.Max)) && | ||||
| 		r.Domain == 0 | ||||
| } | ||||
|  | ||||
| // GetMin gets the min value for the continuous range. | ||||
| func (r ContinuousRange) GetMin() float64 { | ||||
| 	return r.Min | ||||
| } | ||||
|  | ||||
| // SetMin sets the min value for the continuous range. | ||||
| func (r *ContinuousRange) SetMin(min float64) { | ||||
| 	r.Min = min | ||||
| } | ||||
|  | ||||
| // GetMax returns the max value for the continuous range. | ||||
| func (r ContinuousRange) GetMax() float64 { | ||||
| 	return r.Max | ||||
| } | ||||
|  | ||||
| // SetMax sets the max value for the continuous range. | ||||
| func (r *ContinuousRange) SetMax(max float64) { | ||||
| 	r.Max = max | ||||
| } | ||||
|  | ||||
| // GetDelta returns the difference between the min and max value. | ||||
| func (r ContinuousRange) GetDelta() float64 { | ||||
| 	return r.Max - r.Min | ||||
| } | ||||
|  | ||||
| // GetDomain returns the range domain. | ||||
| func (r ContinuousRange) GetDomain() int { | ||||
| 	return r.Domain | ||||
| } | ||||
|  | ||||
| // SetDomain sets the range domain. | ||||
| func (r *ContinuousRange) SetDomain(domain int) { | ||||
| 	r.Domain = domain | ||||
| } | ||||
|  | ||||
| // String returns a simple string for the ContinuousRange. | ||||
| func (r ContinuousRange) String() string { | ||||
| 	if r.GetDelta() == 0 { | ||||
| 		return "ContinuousRange [empty]" | ||||
| 	} | ||||
| 	return fmt.Sprintf("ContinuousRange [%.2f,%.2f] => %d", r.Min, r.Max, r.Domain) | ||||
| } | ||||
|  | ||||
| // Translate maps a given value into the ContinuousRange space. | ||||
| func (r ContinuousRange) Translate(value float64) int { | ||||
| 	normalized := value - r.Min | ||||
| 	ratio := normalized / r.GetDelta() | ||||
|  | ||||
| 	if r.IsDescending() { | ||||
| 		return r.Domain - int(math.Ceil(ratio*float64(r.Domain))) | ||||
| 	} | ||||
|  | ||||
| 	return int(math.Ceil(ratio * float64(r.Domain))) | ||||
| } | ||||
							
								
								
									
										96
									
								
								vendor/github.com/wcharczuk/go-chart/v2/continuous_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								vendor/github.com/wcharczuk/go-chart/v2/continuous_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,96 @@ | ||||
| package chart | ||||
|  | ||||
| import "fmt" | ||||
|  | ||||
| // Interface Assertions. | ||||
| var ( | ||||
| 	_ Series              = (*ContinuousSeries)(nil) | ||||
| 	_ FirstValuesProvider = (*ContinuousSeries)(nil) | ||||
| 	_ LastValuesProvider  = (*ContinuousSeries)(nil) | ||||
| ) | ||||
|  | ||||
| // ContinuousSeries represents a line on a chart. | ||||
| type ContinuousSeries struct { | ||||
| 	Name  string | ||||
| 	Style Style | ||||
|  | ||||
| 	YAxis YAxisType | ||||
|  | ||||
| 	XValueFormatter ValueFormatter | ||||
| 	YValueFormatter ValueFormatter | ||||
|  | ||||
| 	XValues []float64 | ||||
| 	YValues []float64 | ||||
| } | ||||
|  | ||||
| // GetName returns the name of the time series. | ||||
| func (cs ContinuousSeries) GetName() string { | ||||
| 	return cs.Name | ||||
| } | ||||
|  | ||||
| // GetStyle returns the line style. | ||||
| func (cs ContinuousSeries) GetStyle() Style { | ||||
| 	return cs.Style | ||||
| } | ||||
|  | ||||
| // Len returns the number of elements in the series. | ||||
| func (cs ContinuousSeries) Len() int { | ||||
| 	return len(cs.XValues) | ||||
| } | ||||
|  | ||||
| // GetValues gets the x,y values at a given index. | ||||
| func (cs ContinuousSeries) GetValues(index int) (float64, float64) { | ||||
| 	return cs.XValues[index], cs.YValues[index] | ||||
| } | ||||
|  | ||||
| // GetFirstValues gets the first x,y values. | ||||
| func (cs ContinuousSeries) GetFirstValues() (float64, float64) { | ||||
| 	return cs.XValues[0], cs.YValues[0] | ||||
| } | ||||
|  | ||||
| // GetLastValues gets the last x,y values. | ||||
| func (cs ContinuousSeries) GetLastValues() (float64, float64) { | ||||
| 	return cs.XValues[len(cs.XValues)-1], cs.YValues[len(cs.YValues)-1] | ||||
| } | ||||
|  | ||||
| // GetValueFormatters returns value formatter defaults for the series. | ||||
| func (cs ContinuousSeries) GetValueFormatters() (x, y ValueFormatter) { | ||||
| 	if cs.XValueFormatter != nil { | ||||
| 		x = cs.XValueFormatter | ||||
| 	} else { | ||||
| 		x = FloatValueFormatter | ||||
| 	} | ||||
| 	if cs.YValueFormatter != nil { | ||||
| 		y = cs.YValueFormatter | ||||
| 	} else { | ||||
| 		y = FloatValueFormatter | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // GetYAxis returns which YAxis the series draws on. | ||||
| func (cs ContinuousSeries) GetYAxis() YAxisType { | ||||
| 	return cs.YAxis | ||||
| } | ||||
|  | ||||
| // Render renders the series. | ||||
| func (cs ContinuousSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { | ||||
| 	style := cs.Style.InheritFrom(defaults) | ||||
| 	Draw.LineSeries(r, canvasBox, xrange, yrange, style, cs) | ||||
| } | ||||
|  | ||||
| // Validate validates the series. | ||||
| func (cs ContinuousSeries) Validate() error { | ||||
| 	if len(cs.XValues) == 0 { | ||||
| 		return fmt.Errorf("continuous series; must have xvalues set") | ||||
| 	} | ||||
|  | ||||
| 	if len(cs.YValues) == 0 { | ||||
| 		return fmt.Errorf("continuous series; must have yvalues set") | ||||
| 	} | ||||
|  | ||||
| 	if len(cs.XValues) != len(cs.YValues) { | ||||
| 		return fmt.Errorf("continuous series; must have same length xvalues as yvalues") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										103
									
								
								vendor/github.com/wcharczuk/go-chart/v2/defaults.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								vendor/github.com/wcharczuk/go-chart/v2/defaults.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,103 @@ | ||||
| package chart | ||||
|  | ||||
| const ( | ||||
| 	// DefaultChartHeight is the default chart height. | ||||
| 	DefaultChartHeight = 400 | ||||
| 	// DefaultChartWidth is the default chart width. | ||||
| 	DefaultChartWidth = 1024 | ||||
| 	// DefaultStrokeWidth is the default chart stroke width. | ||||
| 	DefaultStrokeWidth = 0.0 | ||||
| 	// DefaultDotWidth is the default chart dot width. | ||||
| 	DefaultDotWidth = 0.0 | ||||
| 	// DefaultSeriesLineWidth is the default line width. | ||||
| 	DefaultSeriesLineWidth = 1.0 | ||||
| 	// DefaultAxisLineWidth is the line width of the axis lines. | ||||
| 	DefaultAxisLineWidth = 1.0 | ||||
| 	//DefaultDPI is the default dots per inch for the chart. | ||||
| 	DefaultDPI = 92.0 | ||||
| 	// DefaultMinimumFontSize is the default minimum font size. | ||||
| 	DefaultMinimumFontSize = 8.0 | ||||
| 	// DefaultFontSize is the default font size. | ||||
| 	DefaultFontSize = 10.0 | ||||
| 	// DefaultTitleFontSize is the default title font size. | ||||
| 	DefaultTitleFontSize = 18.0 | ||||
| 	// DefaultAnnotationDeltaWidth is the width of the left triangle out of annotations. | ||||
| 	DefaultAnnotationDeltaWidth = 10 | ||||
| 	// DefaultAnnotationFontSize is the font size of annotations. | ||||
| 	DefaultAnnotationFontSize = 10.0 | ||||
| 	// DefaultAxisFontSize is the font size of the axis labels. | ||||
| 	DefaultAxisFontSize = 10.0 | ||||
| 	// DefaultTitleTop is the default distance from the top of the chart to put the title. | ||||
| 	DefaultTitleTop = 10 | ||||
|  | ||||
| 	// DefaultBackgroundStrokeWidth is the default stroke on the chart background. | ||||
| 	DefaultBackgroundStrokeWidth = 0.0 | ||||
| 	// DefaultCanvasStrokeWidth is the default stroke on the chart canvas. | ||||
| 	DefaultCanvasStrokeWidth = 0.0 | ||||
|  | ||||
| 	// DefaultLineSpacing is the default vertical distance between lines of text. | ||||
| 	DefaultLineSpacing = 5 | ||||
|  | ||||
| 	// DefaultYAxisMargin is the default distance from the right of the canvas to the y axis labels. | ||||
| 	DefaultYAxisMargin = 10 | ||||
| 	// DefaultXAxisMargin is the default distance from bottom of the canvas to the x axis labels. | ||||
| 	DefaultXAxisMargin = 10 | ||||
|  | ||||
| 	//DefaultVerticalTickHeight is half the margin. | ||||
| 	DefaultVerticalTickHeight = DefaultXAxisMargin >> 1 | ||||
| 	//DefaultHorizontalTickWidth is half the margin. | ||||
| 	DefaultHorizontalTickWidth = DefaultYAxisMargin >> 1 | ||||
|  | ||||
| 	// DefaultTickCount is the default number of ticks to show | ||||
| 	DefaultTickCount = 10 | ||||
| 	// DefaultTickCountSanityCheck is a hard limit on number of ticks to prevent infinite loops. | ||||
| 	DefaultTickCountSanityCheck = 1 << 10 //1024 | ||||
|  | ||||
| 	// DefaultMinimumTickHorizontalSpacing is the minimum distance between horizontal ticks. | ||||
| 	DefaultMinimumTickHorizontalSpacing = 20 | ||||
| 	// DefaultMinimumTickVerticalSpacing is the minimum distance between vertical ticks. | ||||
| 	DefaultMinimumTickVerticalSpacing = 20 | ||||
|  | ||||
| 	// DefaultDateFormat is the default date format. | ||||
| 	DefaultDateFormat = "2006-01-02" | ||||
| 	// DefaultDateHourFormat is the date format for hour timestamp formats. | ||||
| 	DefaultDateHourFormat = "01-02 3PM" | ||||
| 	// DefaultDateMinuteFormat is the date format for minute range timestamp formats. | ||||
| 	DefaultDateMinuteFormat = "01-02 3:04PM" | ||||
| 	// DefaultFloatFormat is the default float format. | ||||
| 	DefaultFloatFormat = "%.2f" | ||||
| 	// DefaultPercentValueFormat is the default percent format. | ||||
| 	DefaultPercentValueFormat = "%0.2f%%" | ||||
|  | ||||
| 	// DefaultBarSpacing is the default pixel spacing between bars. | ||||
| 	DefaultBarSpacing = 100 | ||||
| 	// DefaultBarWidth is the default pixel width of bars in a bar chart. | ||||
| 	DefaultBarWidth = 50 | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// DashArrayDots is a dash array that represents '....' style stroke dashes. | ||||
| 	DashArrayDots = []int{1, 1} | ||||
| 	// DashArrayDashesSmall is a dash array that represents '- - -' style stroke dashes. | ||||
| 	DashArrayDashesSmall = []int{3, 3} | ||||
| 	// DashArrayDashesMedium is a dash array that represents '-- -- --' style stroke dashes. | ||||
| 	DashArrayDashesMedium = []int{5, 5} | ||||
| 	// DashArrayDashesLarge is a dash array that represents '----- ----- -----' style stroke dashes. | ||||
| 	DashArrayDashesLarge = []int{10, 10} | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// DefaultAnnotationPadding is the padding around an annotation. | ||||
| 	DefaultAnnotationPadding = Box{Top: 5, Left: 5, Right: 5, Bottom: 5} | ||||
|  | ||||
| 	// DefaultBackgroundPadding is the default canvas padding config. | ||||
| 	DefaultBackgroundPadding = Box{Top: 5, Left: 5, Right: 5, Bottom: 5} | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	// ContentTypePNG is the png mime type. | ||||
| 	ContentTypePNG = "image/png" | ||||
|  | ||||
| 	// ContentTypeSVG is the svg mime type. | ||||
| 	ContentTypeSVG = "image/svg+xml" | ||||
| ) | ||||
							
								
								
									
										315
									
								
								vendor/github.com/wcharczuk/go-chart/v2/donut_chart.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										315
									
								
								vendor/github.com/wcharczuk/go-chart/v2/donut_chart.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,315 @@ | ||||
| package chart | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
|  | ||||
| 	"github.com/golang/freetype/truetype" | ||||
| ) | ||||
|  | ||||
| // DonutChart is a chart that draws sections of a circle based on percentages with an hole. | ||||
| type DonutChart struct { | ||||
| 	Title      string | ||||
| 	TitleStyle Style | ||||
|  | ||||
| 	ColorPalette ColorPalette | ||||
|  | ||||
| 	Width  int | ||||
| 	Height int | ||||
| 	DPI    float64 | ||||
|  | ||||
| 	Background Style | ||||
| 	Canvas     Style | ||||
| 	SliceStyle Style | ||||
|  | ||||
| 	Font        *truetype.Font | ||||
| 	defaultFont *truetype.Font | ||||
|  | ||||
| 	Values   []Value | ||||
| 	Elements []Renderable | ||||
| } | ||||
|  | ||||
| // GetDPI returns the dpi for the chart. | ||||
| func (pc DonutChart) GetDPI(defaults ...float64) float64 { | ||||
| 	if pc.DPI == 0 { | ||||
| 		if len(defaults) > 0 { | ||||
| 			return defaults[0] | ||||
| 		} | ||||
| 		return DefaultDPI | ||||
| 	} | ||||
| 	return pc.DPI | ||||
| } | ||||
|  | ||||
| // GetFont returns the text font. | ||||
| func (pc DonutChart) GetFont() *truetype.Font { | ||||
| 	if pc.Font == nil { | ||||
| 		return pc.defaultFont | ||||
| 	} | ||||
| 	return pc.Font | ||||
| } | ||||
|  | ||||
| // GetWidth returns the chart width or the default value. | ||||
| func (pc DonutChart) GetWidth() int { | ||||
| 	if pc.Width == 0 { | ||||
| 		return DefaultChartWidth | ||||
| 	} | ||||
| 	return pc.Width | ||||
| } | ||||
|  | ||||
| // GetHeight returns the chart height or the default value. | ||||
| func (pc DonutChart) GetHeight() int { | ||||
| 	if pc.Height == 0 { | ||||
| 		return DefaultChartWidth | ||||
| 	} | ||||
| 	return pc.Height | ||||
| } | ||||
|  | ||||
| // Render renders the chart with the given renderer to the given io.Writer. | ||||
| func (pc DonutChart) Render(rp RendererProvider, w io.Writer) error { | ||||
| 	if len(pc.Values) == 0 { | ||||
| 		return errors.New("please provide at least one value") | ||||
| 	} | ||||
|  | ||||
| 	r, err := rp(pc.GetWidth(), pc.GetHeight()) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if pc.Font == nil { | ||||
| 		defaultFont, err := GetDefaultFont() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		pc.defaultFont = defaultFont | ||||
| 	} | ||||
| 	r.SetDPI(pc.GetDPI(DefaultDPI)) | ||||
|  | ||||
| 	canvasBox := pc.getDefaultCanvasBox() | ||||
| 	canvasBox = pc.getCircleAdjustedCanvasBox(canvasBox) | ||||
|  | ||||
| 	pc.drawBackground(r) | ||||
| 	pc.drawCanvas(r, canvasBox) | ||||
|  | ||||
| 	finalValues, err := pc.finalizeValues(pc.Values) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	pc.drawSlices(r, canvasBox, finalValues) | ||||
| 	pc.drawTitle(r) | ||||
| 	for _, a := range pc.Elements { | ||||
| 		a(r, canvasBox, pc.styleDefaultsElements()) | ||||
| 	} | ||||
|  | ||||
| 	return r.Save(w) | ||||
| } | ||||
|  | ||||
| func (pc DonutChart) drawBackground(r Renderer) { | ||||
| 	Draw.Box(r, Box{ | ||||
| 		Right:  pc.GetWidth(), | ||||
| 		Bottom: pc.GetHeight(), | ||||
| 	}, pc.getBackgroundStyle()) | ||||
| } | ||||
|  | ||||
| func (pc DonutChart) drawCanvas(r Renderer, canvasBox Box) { | ||||
| 	Draw.Box(r, canvasBox, pc.getCanvasStyle()) | ||||
| } | ||||
|  | ||||
| func (pc DonutChart) drawTitle(r Renderer) { | ||||
| 	if len(pc.Title) > 0 && !pc.TitleStyle.Hidden { | ||||
| 		Draw.TextWithin(r, pc.Title, pc.Box(), pc.styleDefaultsTitle()) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pc DonutChart) drawSlices(r Renderer, canvasBox Box, values []Value) { | ||||
| 	cx, cy := canvasBox.Center() | ||||
| 	diameter := MinInt(canvasBox.Width(), canvasBox.Height()) | ||||
| 	radius := float64(diameter>>1) / 1.1 | ||||
| 	labelRadius := (radius * 2.83) / 3.0 | ||||
|  | ||||
| 	// draw the donut slices | ||||
| 	var rads, delta, delta2, total float64 | ||||
| 	var lx, ly int | ||||
|  | ||||
| 	if len(values) == 1 { | ||||
| 		pc.styleDonutChartValue(0).WriteToRenderer(r) | ||||
| 		r.MoveTo(cx, cy) | ||||
| 		r.Circle(radius, cx, cy) | ||||
| 	} else { | ||||
| 		for index, v := range values { | ||||
| 			v.Style.InheritFrom(pc.styleDonutChartValue(index)).WriteToRenderer(r) | ||||
| 			r.MoveTo(cx, cy) | ||||
| 			rads = PercentToRadians(total) | ||||
| 			delta = PercentToRadians(v.Value) | ||||
|  | ||||
| 			r.ArcTo(cx, cy, (radius / 1.25), (radius / 1.25), rads, delta) | ||||
|  | ||||
| 			r.LineTo(cx, cy) | ||||
| 			r.Close() | ||||
| 			r.FillStroke() | ||||
| 			total = total + v.Value | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	//making the donut hole | ||||
| 	v := Value{Value: 100, Label: "center"} | ||||
| 	styletemp := pc.SliceStyle.InheritFrom(Style{ | ||||
| 		StrokeColor: ColorWhite, StrokeWidth: 4.0, FillColor: ColorWhite, FontColor: ColorWhite, //Font:        pc.GetFont(),//FontSize:    pc.getScaledFontSize(), | ||||
| 	}) | ||||
| 	v.Style.InheritFrom(styletemp).WriteToRenderer(r) | ||||
| 	r.MoveTo(cx, cy) | ||||
| 	r.ArcTo(cx, cy, (radius / 3.5), (radius / 3.5), DegreesToRadians(0), DegreesToRadians(359)) | ||||
| 	r.LineTo(cx, cy) | ||||
| 	r.Close() | ||||
| 	r.FillStroke() | ||||
|  | ||||
| 	// draw the labels | ||||
| 	total = 0 | ||||
| 	for index, v := range values { | ||||
| 		v.Style.InheritFrom(pc.styleDonutChartValue(index)).WriteToRenderer(r) | ||||
| 		if len(v.Label) > 0 { | ||||
| 			delta2 = PercentToRadians(total + (v.Value / 2.0)) | ||||
| 			delta2 = RadianAdd(delta2, _pi2) | ||||
| 			lx, ly = CirclePoint(cx, cy, labelRadius, delta2) | ||||
|  | ||||
| 			tb := r.MeasureText(v.Label) | ||||
| 			lx = lx - (tb.Width() >> 1) | ||||
| 			ly = ly + (tb.Height() >> 1) | ||||
|  | ||||
| 			r.Text(v.Label, lx, ly) | ||||
| 		} | ||||
| 		total = total + v.Value | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pc DonutChart) finalizeValues(values []Value) ([]Value, error) { | ||||
| 	finalValues := Values(values).Normalize() | ||||
| 	if len(finalValues) == 0 { | ||||
| 		return nil, fmt.Errorf("donut chart must contain at least (1) non-zero value") | ||||
| 	} | ||||
| 	return finalValues, nil | ||||
| } | ||||
|  | ||||
| func (pc DonutChart) getDefaultCanvasBox() Box { | ||||
| 	return pc.Box() | ||||
| } | ||||
|  | ||||
| func (pc DonutChart) getCircleAdjustedCanvasBox(canvasBox Box) Box { | ||||
| 	circleDiameter := MinInt(canvasBox.Width(), canvasBox.Height()) | ||||
|  | ||||
| 	square := Box{ | ||||
| 		Right:  circleDiameter, | ||||
| 		Bottom: circleDiameter, | ||||
| 	} | ||||
|  | ||||
| 	return canvasBox.Fit(square) | ||||
| } | ||||
|  | ||||
| func (pc DonutChart) getBackgroundStyle() Style { | ||||
| 	return pc.Background.InheritFrom(pc.styleDefaultsBackground()) | ||||
| } | ||||
|  | ||||
| func (pc DonutChart) getCanvasStyle() Style { | ||||
| 	return pc.Canvas.InheritFrom(pc.styleDefaultsCanvas()) | ||||
| } | ||||
|  | ||||
| func (pc DonutChart) styleDefaultsCanvas() Style { | ||||
| 	return Style{ | ||||
| 		FillColor:   pc.GetColorPalette().CanvasColor(), | ||||
| 		StrokeColor: pc.GetColorPalette().CanvasStrokeColor(), | ||||
| 		StrokeWidth: DefaultStrokeWidth, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pc DonutChart) styleDefaultsDonutChartValue() Style { | ||||
| 	return Style{ | ||||
| 		StrokeColor: pc.GetColorPalette().TextColor(), | ||||
| 		StrokeWidth: 4.0, | ||||
| 		FillColor:   pc.GetColorPalette().TextColor(), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pc DonutChart) styleDonutChartValue(index int) Style { | ||||
| 	return pc.SliceStyle.InheritFrom(Style{ | ||||
| 		StrokeColor: ColorWhite, | ||||
| 		StrokeWidth: 4.0, | ||||
| 		FillColor:   pc.GetColorPalette().GetSeriesColor(index), | ||||
| 		FontSize:    pc.getScaledFontSize(), | ||||
| 		FontColor:   pc.GetColorPalette().TextColor(), | ||||
| 		Font:        pc.GetFont(), | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func (pc DonutChart) getScaledFontSize() float64 { | ||||
| 	effectiveDimension := MinInt(pc.GetWidth(), pc.GetHeight()) | ||||
| 	if effectiveDimension >= 2048 { | ||||
| 		return 48.0 | ||||
| 	} else if effectiveDimension >= 1024 { | ||||
| 		return 24.0 | ||||
| 	} else if effectiveDimension > 512 { | ||||
| 		return 18.0 | ||||
| 	} else if effectiveDimension > 256 { | ||||
| 		return 12.0 | ||||
| 	} | ||||
| 	return 10.0 | ||||
| } | ||||
|  | ||||
| func (pc DonutChart) styleDefaultsBackground() Style { | ||||
| 	return Style{ | ||||
| 		FillColor:   pc.GetColorPalette().BackgroundColor(), | ||||
| 		StrokeColor: pc.GetColorPalette().BackgroundStrokeColor(), | ||||
| 		StrokeWidth: DefaultStrokeWidth, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pc DonutChart) styleDefaultsElements() Style { | ||||
| 	return Style{ | ||||
| 		Font: pc.GetFont(), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pc DonutChart) styleDefaultsTitle() Style { | ||||
| 	return pc.TitleStyle.InheritFrom(Style{ | ||||
| 		FontColor:           pc.GetColorPalette().TextColor(), | ||||
| 		Font:                pc.GetFont(), | ||||
| 		FontSize:            pc.getTitleFontSize(), | ||||
| 		TextHorizontalAlign: TextHorizontalAlignCenter, | ||||
| 		TextVerticalAlign:   TextVerticalAlignTop, | ||||
| 		TextWrap:            TextWrapWord, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func (pc DonutChart) getTitleFontSize() float64 { | ||||
| 	effectiveDimension := MinInt(pc.GetWidth(), pc.GetHeight()) | ||||
| 	if effectiveDimension >= 2048 { | ||||
| 		return 48 | ||||
| 	} else if effectiveDimension >= 1024 { | ||||
| 		return 24 | ||||
| 	} else if effectiveDimension >= 512 { | ||||
| 		return 18 | ||||
| 	} else if effectiveDimension >= 256 { | ||||
| 		return 12 | ||||
| 	} | ||||
| 	return 10 | ||||
| } | ||||
|  | ||||
| // GetColorPalette returns the color palette for the chart. | ||||
| func (pc DonutChart) GetColorPalette() ColorPalette { | ||||
| 	if pc.ColorPalette != nil { | ||||
| 		return pc.ColorPalette | ||||
| 	} | ||||
| 	return AlternateColorPalette | ||||
| } | ||||
|  | ||||
| // Box returns the chart bounds as a box. | ||||
| func (pc DonutChart) Box() Box { | ||||
| 	dpr := pc.Background.Padding.GetRight(DefaultBackgroundPadding.Right) | ||||
| 	dpb := pc.Background.Padding.GetBottom(DefaultBackgroundPadding.Bottom) | ||||
|  | ||||
| 	return Box{ | ||||
| 		Top:    pc.Background.Padding.GetTop(DefaultBackgroundPadding.Top), | ||||
| 		Left:   pc.Background.Padding.GetLeft(DefaultBackgroundPadding.Left), | ||||
| 		Right:  pc.GetWidth() - dpr, | ||||
| 		Bottom: pc.GetHeight() - dpb, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										325
									
								
								vendor/github.com/wcharczuk/go-chart/v2/draw.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										325
									
								
								vendor/github.com/wcharczuk/go-chart/v2/draw.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,325 @@ | ||||
| package chart | ||||
|  | ||||
| import ( | ||||
| 	"math" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// Draw contains helpers for drawing common objects. | ||||
| 	Draw = &draw{} | ||||
| ) | ||||
|  | ||||
| type draw struct{} | ||||
|  | ||||
| // LineSeries draws a line series with a renderer. | ||||
| func (d draw) LineSeries(r Renderer, canvasBox Box, xrange, yrange Range, style Style, vs ValuesProvider) { | ||||
| 	if vs.Len() == 0 { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	cb := canvasBox.Bottom | ||||
| 	cl := canvasBox.Left | ||||
|  | ||||
| 	v0x, v0y := vs.GetValues(0) | ||||
| 	x0 := cl + xrange.Translate(v0x) | ||||
| 	y0 := cb - yrange.Translate(v0y) | ||||
|  | ||||
| 	yv0 := yrange.Translate(0) | ||||
|  | ||||
| 	var vx, vy float64 | ||||
| 	var x, y int | ||||
|  | ||||
| 	if style.ShouldDrawStroke() && style.ShouldDrawFill() { | ||||
| 		style.GetFillOptions().WriteDrawingOptionsToRenderer(r) | ||||
| 		r.MoveTo(x0, y0) | ||||
| 		for i := 1; i < vs.Len(); i++ { | ||||
| 			vx, vy = vs.GetValues(i) | ||||
| 			x = cl + xrange.Translate(vx) | ||||
| 			y = cb - yrange.Translate(vy) | ||||
| 			r.LineTo(x, y) | ||||
| 		} | ||||
| 		r.LineTo(x, MinInt(cb, cb-yv0)) | ||||
| 		r.LineTo(x0, MinInt(cb, cb-yv0)) | ||||
| 		r.LineTo(x0, y0) | ||||
| 		r.Fill() | ||||
| 	} | ||||
|  | ||||
| 	if style.ShouldDrawStroke() { | ||||
| 		style.GetStrokeOptions().WriteDrawingOptionsToRenderer(r) | ||||
|  | ||||
| 		r.MoveTo(x0, y0) | ||||
| 		for i := 1; i < vs.Len(); i++ { | ||||
| 			vx, vy = vs.GetValues(i) | ||||
| 			x = cl + xrange.Translate(vx) | ||||
| 			y = cb - yrange.Translate(vy) | ||||
| 			r.LineTo(x, y) | ||||
| 		} | ||||
| 		r.Stroke() | ||||
| 	} | ||||
|  | ||||
| 	if style.ShouldDrawDot() { | ||||
| 		defaultDotWidth := style.GetDotWidth() | ||||
|  | ||||
| 		style.GetDotOptions().WriteDrawingOptionsToRenderer(r) | ||||
| 		for i := 0; i < vs.Len(); i++ { | ||||
| 			vx, vy = vs.GetValues(i) | ||||
| 			x = cl + xrange.Translate(vx) | ||||
| 			y = cb - yrange.Translate(vy) | ||||
|  | ||||
| 			dotWidth := defaultDotWidth | ||||
| 			if style.DotWidthProvider != nil { | ||||
| 				dotWidth = style.DotWidthProvider(xrange, yrange, i, vx, vy) | ||||
| 			} | ||||
|  | ||||
| 			if style.DotColorProvider != nil { | ||||
| 				dotColor := style.DotColorProvider(xrange, yrange, i, vx, vy) | ||||
|  | ||||
| 				r.SetFillColor(dotColor) | ||||
| 				r.SetStrokeColor(dotColor) | ||||
| 			} | ||||
|  | ||||
| 			r.Circle(dotWidth, x, y) | ||||
| 			r.FillStroke() | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // BoundedSeries draws a series that implements BoundedValuesProvider. | ||||
| func (d draw) BoundedSeries(r Renderer, canvasBox Box, xrange, yrange Range, style Style, bbs BoundedValuesProvider, drawOffsetIndexes ...int) { | ||||
| 	drawOffsetIndex := 0 | ||||
| 	if len(drawOffsetIndexes) > 0 { | ||||
| 		drawOffsetIndex = drawOffsetIndexes[0] | ||||
| 	} | ||||
|  | ||||
| 	cb := canvasBox.Bottom | ||||
| 	cl := canvasBox.Left | ||||
|  | ||||
| 	v0x, v0y1, v0y2 := bbs.GetBoundedValues(0) | ||||
| 	x0 := cl + xrange.Translate(v0x) | ||||
| 	y0 := cb - yrange.Translate(v0y1) | ||||
|  | ||||
| 	var vx, vy1, vy2 float64 | ||||
| 	var x, y int | ||||
|  | ||||
| 	xvalues := make([]float64, bbs.Len()) | ||||
| 	xvalues[0] = v0x | ||||
| 	y2values := make([]float64, bbs.Len()) | ||||
| 	y2values[0] = v0y2 | ||||
|  | ||||
| 	style.GetFillAndStrokeOptions().WriteToRenderer(r) | ||||
| 	r.MoveTo(x0, y0) | ||||
| 	for i := 1; i < bbs.Len(); i++ { | ||||
| 		vx, vy1, vy2 = bbs.GetBoundedValues(i) | ||||
|  | ||||
| 		xvalues[i] = vx | ||||
| 		y2values[i] = vy2 | ||||
|  | ||||
| 		x = cl + xrange.Translate(vx) | ||||
| 		y = cb - yrange.Translate(vy1) | ||||
| 		if i > drawOffsetIndex { | ||||
| 			r.LineTo(x, y) | ||||
| 		} else { | ||||
| 			r.MoveTo(x, y) | ||||
| 		} | ||||
| 	} | ||||
| 	y = cb - yrange.Translate(vy2) | ||||
| 	r.LineTo(x, y) | ||||
| 	for i := bbs.Len() - 1; i >= drawOffsetIndex; i-- { | ||||
| 		vx, vy2 = xvalues[i], y2values[i] | ||||
| 		x = cl + xrange.Translate(vx) | ||||
| 		y = cb - yrange.Translate(vy2) | ||||
| 		r.LineTo(x, y) | ||||
| 	} | ||||
| 	r.Close() | ||||
| 	r.FillStroke() | ||||
| } | ||||
|  | ||||
| // HistogramSeries draws a value provider as boxes from 0. | ||||
| func (d draw) HistogramSeries(r Renderer, canvasBox Box, xrange, yrange Range, style Style, vs ValuesProvider, barWidths ...int) { | ||||
| 	if vs.Len() == 0 { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	//calculate bar width? | ||||
| 	seriesLength := vs.Len() | ||||
| 	barWidth := int(math.Floor(float64(xrange.GetDomain()) / float64(seriesLength))) | ||||
| 	if len(barWidths) > 0 { | ||||
| 		barWidth = barWidths[0] | ||||
| 	} | ||||
|  | ||||
| 	cb := canvasBox.Bottom | ||||
| 	cl := canvasBox.Left | ||||
|  | ||||
| 	//foreach datapoint, draw a box. | ||||
| 	for index := 0; index < seriesLength; index++ { | ||||
| 		vx, vy := vs.GetValues(index) | ||||
| 		y0 := yrange.Translate(0) | ||||
| 		x := cl + xrange.Translate(vx) | ||||
| 		y := yrange.Translate(vy) | ||||
|  | ||||
| 		d.Box(r, Box{ | ||||
| 			Top:    cb - y0, | ||||
| 			Left:   x - (barWidth >> 1), | ||||
| 			Right:  x + (barWidth >> 1), | ||||
| 			Bottom: cb - y, | ||||
| 		}, style) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // MeasureAnnotation measures how big an annotation would be. | ||||
| func (d draw) MeasureAnnotation(r Renderer, canvasBox Box, style Style, lx, ly int, label string) Box { | ||||
| 	style.WriteToRenderer(r) | ||||
| 	defer r.ResetStyle() | ||||
|  | ||||
| 	textBox := r.MeasureText(label) | ||||
| 	textWidth := textBox.Width() | ||||
| 	textHeight := textBox.Height() | ||||
| 	halfTextHeight := textHeight >> 1 | ||||
|  | ||||
| 	pt := style.Padding.GetTop(DefaultAnnotationPadding.Top) | ||||
| 	pl := style.Padding.GetLeft(DefaultAnnotationPadding.Left) | ||||
| 	pr := style.Padding.GetRight(DefaultAnnotationPadding.Right) | ||||
| 	pb := style.Padding.GetBottom(DefaultAnnotationPadding.Bottom) | ||||
|  | ||||
| 	strokeWidth := style.GetStrokeWidth() | ||||
|  | ||||
| 	top := ly - (pt + halfTextHeight) | ||||
| 	right := lx + pl + pr + textWidth + DefaultAnnotationDeltaWidth + int(strokeWidth) | ||||
| 	bottom := ly + (pb + halfTextHeight) | ||||
|  | ||||
| 	return Box{ | ||||
| 		Top:    top, | ||||
| 		Left:   lx, | ||||
| 		Right:  right, | ||||
| 		Bottom: bottom, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Annotation draws an anotation with a renderer. | ||||
| func (d draw) Annotation(r Renderer, canvasBox Box, style Style, lx, ly int, label string) { | ||||
| 	style.GetTextOptions().WriteToRenderer(r) | ||||
| 	defer r.ResetStyle() | ||||
|  | ||||
| 	textBox := r.MeasureText(label) | ||||
| 	textWidth := textBox.Width() | ||||
| 	halfTextHeight := textBox.Height() >> 1 | ||||
|  | ||||
| 	style.GetFillAndStrokeOptions().WriteToRenderer(r) | ||||
|  | ||||
| 	pt := style.Padding.GetTop(DefaultAnnotationPadding.Top) | ||||
| 	pl := style.Padding.GetLeft(DefaultAnnotationPadding.Left) | ||||
| 	pr := style.Padding.GetRight(DefaultAnnotationPadding.Right) | ||||
| 	pb := style.Padding.GetBottom(DefaultAnnotationPadding.Bottom) | ||||
|  | ||||
| 	textX := lx + pl + DefaultAnnotationDeltaWidth | ||||
| 	textY := ly + halfTextHeight | ||||
|  | ||||
| 	ltx := lx + DefaultAnnotationDeltaWidth | ||||
| 	lty := ly - (pt + halfTextHeight) | ||||
|  | ||||
| 	rtx := lx + pl + pr + textWidth + DefaultAnnotationDeltaWidth | ||||
| 	rty := ly - (pt + halfTextHeight) | ||||
|  | ||||
| 	rbx := lx + pl + pr + textWidth + DefaultAnnotationDeltaWidth | ||||
| 	rby := ly + (pb + halfTextHeight) | ||||
|  | ||||
| 	lbx := lx + DefaultAnnotationDeltaWidth | ||||
| 	lby := ly + (pb + halfTextHeight) | ||||
|  | ||||
| 	r.MoveTo(lx, ly) | ||||
| 	r.LineTo(ltx, lty) | ||||
| 	r.LineTo(rtx, rty) | ||||
| 	r.LineTo(rbx, rby) | ||||
| 	r.LineTo(lbx, lby) | ||||
| 	r.LineTo(lx, ly) | ||||
| 	r.Close() | ||||
| 	r.FillStroke() | ||||
|  | ||||
| 	style.GetTextOptions().WriteToRenderer(r) | ||||
| 	r.Text(label, textX, textY) | ||||
| } | ||||
|  | ||||
| // Box draws a box with a given style. | ||||
| func (d draw) Box(r Renderer, b Box, s Style) { | ||||
| 	s.GetFillAndStrokeOptions().WriteToRenderer(r) | ||||
| 	defer r.ResetStyle() | ||||
|  | ||||
| 	r.MoveTo(b.Left, b.Top) | ||||
| 	r.LineTo(b.Right, b.Top) | ||||
| 	r.LineTo(b.Right, b.Bottom) | ||||
| 	r.LineTo(b.Left, b.Bottom) | ||||
| 	r.LineTo(b.Left, b.Top) | ||||
| 	r.FillStroke() | ||||
| } | ||||
|  | ||||
| func (d draw) BoxRotated(r Renderer, b Box, thetaDegrees float64, s Style) { | ||||
| 	d.BoxCorners(r, b.Corners().Rotate(thetaDegrees), s) | ||||
| } | ||||
|  | ||||
| func (d draw) BoxCorners(r Renderer, bc BoxCorners, s Style) { | ||||
| 	s.GetFillAndStrokeOptions().WriteToRenderer(r) | ||||
| 	defer r.ResetStyle() | ||||
|  | ||||
| 	r.MoveTo(bc.TopLeft.X, bc.TopLeft.Y) | ||||
| 	r.LineTo(bc.TopRight.X, bc.TopRight.Y) | ||||
| 	r.LineTo(bc.BottomRight.X, bc.BottomRight.Y) | ||||
| 	r.LineTo(bc.BottomLeft.X, bc.BottomLeft.Y) | ||||
| 	r.Close() | ||||
| 	r.FillStroke() | ||||
| } | ||||
|  | ||||
| // DrawText draws text with a given style. | ||||
| func (d draw) Text(r Renderer, text string, x, y int, style Style) { | ||||
| 	style.GetTextOptions().WriteToRenderer(r) | ||||
| 	defer r.ResetStyle() | ||||
|  | ||||
| 	r.Text(text, x, y) | ||||
| } | ||||
|  | ||||
| func (d draw) MeasureText(r Renderer, text string, style Style) Box { | ||||
| 	style.GetTextOptions().WriteToRenderer(r) | ||||
| 	defer r.ResetStyle() | ||||
|  | ||||
| 	return r.MeasureText(text) | ||||
| } | ||||
|  | ||||
| // TextWithin draws the text within a given box. | ||||
| func (d draw) TextWithin(r Renderer, text string, box Box, style Style) { | ||||
| 	style.GetTextOptions().WriteToRenderer(r) | ||||
| 	defer r.ResetStyle() | ||||
|  | ||||
| 	lines := Text.WrapFit(r, text, box.Width(), style) | ||||
| 	linesBox := Text.MeasureLines(r, lines, style) | ||||
|  | ||||
| 	y := box.Top | ||||
|  | ||||
| 	switch style.GetTextVerticalAlign() { | ||||
| 	case TextVerticalAlignBottom, TextVerticalAlignBaseline: // i have to build better baseline handling into measure text | ||||
| 		y = y - linesBox.Height() | ||||
| 	case TextVerticalAlignMiddle: | ||||
| 		y = y + (box.Height() >> 1) - (linesBox.Height() >> 1) | ||||
| 	case TextVerticalAlignMiddleBaseline: | ||||
| 		y = y + (box.Height() >> 1) - linesBox.Height() | ||||
| 	} | ||||
|  | ||||
| 	var tx, ty int | ||||
| 	for _, line := range lines { | ||||
| 		lineBox := r.MeasureText(line) | ||||
| 		switch style.GetTextHorizontalAlign() { | ||||
| 		case TextHorizontalAlignCenter: | ||||
| 			tx = box.Left + ((box.Width() - lineBox.Width()) >> 1) | ||||
| 		case TextHorizontalAlignRight: | ||||
| 			tx = box.Right - lineBox.Width() | ||||
| 		default: | ||||
| 			tx = box.Left | ||||
| 		} | ||||
| 		if style.TextRotationDegrees == 0 { | ||||
| 			ty = y + lineBox.Height() | ||||
| 		} else { | ||||
| 			ty = y | ||||
| 		} | ||||
|  | ||||
| 		r.Text(line, tx, ty) | ||||
| 		y += lineBox.Height() + style.GetTextLineSpacing() | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										5
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| go-chart > drawing | ||||
| ================== | ||||
|  | ||||
| The bulk of the code in this package is based on [draw2d](https://github.com/llgcode/draw2d), but | ||||
| with significant modifications to make the APIs more golang friendly and careful about units (points vs. pixels). | ||||
							
								
								
									
										126
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/color.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/color.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,126 @@ | ||||
| package drawing | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// ColorTransparent is a fully transparent color. | ||||
| 	ColorTransparent = Color{} | ||||
|  | ||||
| 	// ColorWhite is white. | ||||
| 	ColorWhite = Color{R: 255, G: 255, B: 255, A: 255} | ||||
|  | ||||
| 	// ColorBlack is black. | ||||
| 	ColorBlack = Color{R: 0, G: 0, B: 0, A: 255} | ||||
|  | ||||
| 	// ColorRed is red. | ||||
| 	ColorRed = Color{R: 255, G: 0, B: 0, A: 255} | ||||
|  | ||||
| 	// ColorGreen is green. | ||||
| 	ColorGreen = Color{R: 0, G: 255, B: 0, A: 255} | ||||
|  | ||||
| 	// ColorBlue is blue. | ||||
| 	ColorBlue = Color{R: 0, G: 0, B: 255, A: 255} | ||||
| ) | ||||
|  | ||||
| func parseHex(hex string) uint8 { | ||||
| 	v, _ := strconv.ParseInt(hex, 16, 16) | ||||
| 	return uint8(v) | ||||
| } | ||||
|  | ||||
| // ColorFromHex returns a color from a css hex code. | ||||
| func ColorFromHex(hex string) Color { | ||||
| 	var c Color | ||||
| 	if len(hex) == 3 { | ||||
| 		c.R = parseHex(string(hex[0])) * 0x11 | ||||
| 		c.G = parseHex(string(hex[1])) * 0x11 | ||||
| 		c.B = parseHex(string(hex[2])) * 0x11 | ||||
| 	} else { | ||||
| 		c.R = parseHex(string(hex[0:2])) | ||||
| 		c.G = parseHex(string(hex[2:4])) | ||||
| 		c.B = parseHex(string(hex[4:6])) | ||||
| 	} | ||||
| 	c.A = 255 | ||||
| 	return c | ||||
| } | ||||
|  | ||||
| // ColorFromAlphaMixedRGBA returns the system alpha mixed rgba values. | ||||
| func ColorFromAlphaMixedRGBA(r, g, b, a uint32) Color { | ||||
| 	fa := float64(a) / 255.0 | ||||
| 	var c Color | ||||
| 	c.R = uint8(float64(r) / fa) | ||||
| 	c.G = uint8(float64(g) / fa) | ||||
| 	c.B = uint8(float64(b) / fa) | ||||
| 	c.A = uint8(a | (a >> 8)) | ||||
| 	return c | ||||
| } | ||||
|  | ||||
| // ColorChannelFromFloat returns a normalized byte from a given float value. | ||||
| func ColorChannelFromFloat(v float64) uint8 { | ||||
| 	return uint8(v * 255) | ||||
| } | ||||
|  | ||||
| // Color is our internal color type because color.Color is bullshit. | ||||
| type Color struct { | ||||
| 	R, G, B, A uint8 | ||||
| } | ||||
|  | ||||
| // RGBA returns the color as a pre-alpha mixed color set. | ||||
| func (c Color) RGBA() (r, g, b, a uint32) { | ||||
| 	fa := float64(c.A) / 255.0 | ||||
| 	r = uint32(float64(uint32(c.R)) * fa) | ||||
| 	r |= r << 8 | ||||
| 	g = uint32(float64(uint32(c.G)) * fa) | ||||
| 	g |= g << 8 | ||||
| 	b = uint32(float64(uint32(c.B)) * fa) | ||||
| 	b |= b << 8 | ||||
| 	a = uint32(c.A) | ||||
| 	a |= a << 8 | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // IsZero returns if the color has been set or not. | ||||
| func (c Color) IsZero() bool { | ||||
| 	return c.R == 0 && c.G == 0 && c.B == 0 && c.A == 0 | ||||
| } | ||||
|  | ||||
| // IsTransparent returns if the colors alpha channel is zero. | ||||
| func (c Color) IsTransparent() bool { | ||||
| 	return c.A == 0 | ||||
| } | ||||
|  | ||||
| // WithAlpha returns a copy of the color with a given alpha. | ||||
| func (c Color) WithAlpha(a uint8) Color { | ||||
| 	return Color{ | ||||
| 		R: c.R, | ||||
| 		G: c.G, | ||||
| 		B: c.B, | ||||
| 		A: a, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Equals returns true if the color equals another. | ||||
| func (c Color) Equals(other Color) bool { | ||||
| 	return c.R == other.R && | ||||
| 		c.G == other.G && | ||||
| 		c.B == other.B && | ||||
| 		c.A == other.A | ||||
| } | ||||
|  | ||||
| // AverageWith averages two colors. | ||||
| func (c Color) AverageWith(other Color) Color { | ||||
| 	return Color{ | ||||
| 		R: (c.R + other.R) >> 1, | ||||
| 		G: (c.G + other.G) >> 1, | ||||
| 		B: (c.B + other.B) >> 1, | ||||
| 		A: c.A, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // String returns a css string representation of the color. | ||||
| func (c Color) String() string { | ||||
| 	fa := float64(c.A) / float64(255) | ||||
| 	return fmt.Sprintf("rgba(%v,%v,%v,%.1f)", c.R, c.G, c.B, fa) | ||||
| } | ||||
							
								
								
									
										6
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/constants.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/constants.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| package drawing | ||||
|  | ||||
| const ( | ||||
| 	// DefaultDPI is the default image DPI. | ||||
| 	DefaultDPI = 96.0 | ||||
| ) | ||||
							
								
								
									
										185
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/curve.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/curve.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,185 @@ | ||||
| package drawing | ||||
|  | ||||
| import "math" | ||||
|  | ||||
| const ( | ||||
| 	// CurveRecursionLimit represents the maximum recursion that is really necessary to subsivide a curve into straight lines | ||||
| 	CurveRecursionLimit = 32 | ||||
| ) | ||||
|  | ||||
| // Cubic | ||||
| //	x1, y1, cpx1, cpy1, cpx2, cpy2, x2, y2 float64 | ||||
|  | ||||
| // SubdivideCubic a Bezier cubic curve in 2 equivalents Bezier cubic curves. | ||||
| // c1 and c2 parameters are the resulting curves | ||||
| func SubdivideCubic(c, c1, c2 []float64) { | ||||
| 	// First point of c is the first point of c1 | ||||
| 	c1[0], c1[1] = c[0], c[1] | ||||
| 	// Last point of c is the last point of c2 | ||||
| 	c2[6], c2[7] = c[6], c[7] | ||||
|  | ||||
| 	// Subdivide segment using midpoints | ||||
| 	c1[2] = (c[0] + c[2]) / 2 | ||||
| 	c1[3] = (c[1] + c[3]) / 2 | ||||
|  | ||||
| 	midX := (c[2] + c[4]) / 2 | ||||
| 	midY := (c[3] + c[5]) / 2 | ||||
|  | ||||
| 	c2[4] = (c[4] + c[6]) / 2 | ||||
| 	c2[5] = (c[5] + c[7]) / 2 | ||||
|  | ||||
| 	c1[4] = (c1[2] + midX) / 2 | ||||
| 	c1[5] = (c1[3] + midY) / 2 | ||||
|  | ||||
| 	c2[2] = (midX + c2[4]) / 2 | ||||
| 	c2[3] = (midY + c2[5]) / 2 | ||||
|  | ||||
| 	c1[6] = (c1[4] + c2[2]) / 2 | ||||
| 	c1[7] = (c1[5] + c2[3]) / 2 | ||||
|  | ||||
| 	// Last Point of c1 is equal to the first point of c2 | ||||
| 	c2[0], c2[1] = c1[6], c1[7] | ||||
| } | ||||
|  | ||||
| // TraceCubic generate lines subdividing the cubic curve using a Liner | ||||
| // flattening_threshold helps determines the flattening expectation of the curve | ||||
| func TraceCubic(t Liner, cubic []float64, flatteningThreshold float64) { | ||||
| 	// Allocation curves | ||||
| 	var curves [CurveRecursionLimit * 8]float64 | ||||
| 	copy(curves[0:8], cubic[0:8]) | ||||
| 	i := 0 | ||||
|  | ||||
| 	// current curve | ||||
| 	var c []float64 | ||||
|  | ||||
| 	var dx, dy, d2, d3 float64 | ||||
|  | ||||
| 	for i >= 0 { | ||||
| 		c = curves[i*8:] | ||||
| 		dx = c[6] - c[0] | ||||
| 		dy = c[7] - c[1] | ||||
|  | ||||
| 		d2 = math.Abs((c[2]-c[6])*dy - (c[3]-c[7])*dx) | ||||
| 		d3 = math.Abs((c[4]-c[6])*dy - (c[5]-c[7])*dx) | ||||
|  | ||||
| 		// if it's flat then trace a line | ||||
| 		if (d2+d3)*(d2+d3) < flatteningThreshold*(dx*dx+dy*dy) || i == len(curves)-1 { | ||||
| 			t.LineTo(c[6], c[7]) | ||||
| 			i-- | ||||
| 		} else { | ||||
| 			// second half of bezier go lower onto the stack | ||||
| 			SubdivideCubic(c, curves[(i+1)*8:], curves[i*8:]) | ||||
| 			i++ | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Quad | ||||
| // x1, y1, cpx1, cpy2, x2, y2 float64 | ||||
|  | ||||
| // SubdivideQuad a Bezier quad curve in 2 equivalents Bezier quad curves. | ||||
| // c1 and c2 parameters are the resulting curves | ||||
| func SubdivideQuad(c, c1, c2 []float64) { | ||||
| 	// First point of c is the first point of c1 | ||||
| 	c1[0], c1[1] = c[0], c[1] | ||||
| 	// Last point of c is the last point of c2 | ||||
| 	c2[4], c2[5] = c[4], c[5] | ||||
|  | ||||
| 	// Subdivide segment using midpoints | ||||
| 	c1[2] = (c[0] + c[2]) / 2 | ||||
| 	c1[3] = (c[1] + c[3]) / 2 | ||||
| 	c2[2] = (c[2] + c[4]) / 2 | ||||
| 	c2[3] = (c[3] + c[5]) / 2 | ||||
| 	c1[4] = (c1[2] + c2[2]) / 2 | ||||
| 	c1[5] = (c1[3] + c2[3]) / 2 | ||||
| 	c2[0], c2[1] = c1[4], c1[5] | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func traceWindowIndices(i int) (startAt, endAt int) { | ||||
| 	startAt = i * 6 | ||||
| 	endAt = startAt + 6 | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func traceCalcDeltas(c []float64) (dx, dy, d float64) { | ||||
| 	dx = c[4] - c[0] | ||||
| 	dy = c[5] - c[1] | ||||
| 	d = math.Abs(((c[2]-c[4])*dy - (c[3]-c[5])*dx)) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func traceIsFlat(dx, dy, d, threshold float64) bool { | ||||
| 	return (d * d) < threshold*(dx*dx+dy*dy) | ||||
| } | ||||
|  | ||||
| func traceGetWindow(curves []float64, i int) []float64 { | ||||
| 	startAt, endAt := traceWindowIndices(i) | ||||
| 	return curves[startAt:endAt] | ||||
| } | ||||
|  | ||||
| // TraceQuad generate lines subdividing the curve using a Liner | ||||
| // flattening_threshold helps determines the flattening expectation of the curve | ||||
| func TraceQuad(t Liner, quad []float64, flatteningThreshold float64) { | ||||
| 	const curveLen = CurveRecursionLimit * 6 | ||||
| 	const curveEndIndex = curveLen - 1 | ||||
| 	const lastIteration = CurveRecursionLimit - 1 | ||||
|  | ||||
| 	// Allocates curves stack | ||||
| 	curves := make([]float64, curveLen) | ||||
|  | ||||
| 	// copy 6 elements from the quad path to the stack | ||||
| 	copy(curves[0:6], quad[0:6]) | ||||
|  | ||||
| 	var i int | ||||
| 	var c []float64 | ||||
| 	var dx, dy, d float64 | ||||
|  | ||||
| 	for i >= 0 { | ||||
| 		c = traceGetWindow(curves, i) | ||||
| 		dx, dy, d = traceCalcDeltas(c) | ||||
|  | ||||
| 		// bail early if the distance is 0 | ||||
| 		if d == 0 { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		// if it's flat then trace a line | ||||
| 		if traceIsFlat(dx, dy, d, flatteningThreshold) || i == lastIteration { | ||||
| 			t.LineTo(c[4], c[5]) | ||||
| 			i-- | ||||
| 		} else { | ||||
| 			SubdivideQuad(c, traceGetWindow(curves, i+1), traceGetWindow(curves, i)) | ||||
| 			i++ | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // TraceArc trace an arc using a Liner | ||||
| func TraceArc(t Liner, x, y, rx, ry, start, angle, scale float64) (lastX, lastY float64) { | ||||
| 	end := start + angle | ||||
| 	clockWise := true | ||||
| 	if angle < 0 { | ||||
| 		clockWise = false | ||||
| 	} | ||||
| 	ra := (math.Abs(rx) + math.Abs(ry)) / 2 | ||||
| 	da := math.Acos(ra/(ra+0.125/scale)) * 2 | ||||
| 	//normalize | ||||
| 	if !clockWise { | ||||
| 		da = -da | ||||
| 	} | ||||
| 	angle = start + da | ||||
| 	var curX, curY float64 | ||||
| 	for { | ||||
| 		if (angle < end-da/4) != clockWise { | ||||
| 			curX = x + math.Cos(end)*rx | ||||
| 			curY = y + math.Sin(end)*ry | ||||
| 			return curX, curY | ||||
| 		} | ||||
| 		curX = x + math.Cos(angle)*rx | ||||
| 		curY = y + math.Sin(angle)*ry | ||||
|  | ||||
| 		angle += da | ||||
| 		t.LineTo(curX, curY) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										89
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/dasher.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/dasher.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,89 @@ | ||||
| package drawing | ||||
|  | ||||
| // NewDashVertexConverter creates a new dash converter. | ||||
| func NewDashVertexConverter(dash []float64, dashOffset float64, flattener Flattener) *DashVertexConverter { | ||||
| 	var dasher DashVertexConverter | ||||
| 	dasher.dash = dash | ||||
| 	dasher.currentDash = 0 | ||||
| 	dasher.dashOffset = dashOffset | ||||
| 	dasher.next = flattener | ||||
| 	return &dasher | ||||
| } | ||||
|  | ||||
| // DashVertexConverter is a converter for dash vertexes. | ||||
| type DashVertexConverter struct { | ||||
| 	next           Flattener | ||||
| 	x, y, distance float64 | ||||
| 	dash           []float64 | ||||
| 	currentDash    int | ||||
| 	dashOffset     float64 | ||||
| } | ||||
|  | ||||
| // LineTo implements the pathbuilder interface. | ||||
| func (dasher *DashVertexConverter) LineTo(x, y float64) { | ||||
| 	dasher.lineTo(x, y) | ||||
| } | ||||
|  | ||||
| // MoveTo implements the pathbuilder interface. | ||||
| func (dasher *DashVertexConverter) MoveTo(x, y float64) { | ||||
| 	dasher.next.MoveTo(x, y) | ||||
| 	dasher.x, dasher.y = x, y | ||||
| 	dasher.distance = dasher.dashOffset | ||||
| 	dasher.currentDash = 0 | ||||
| } | ||||
|  | ||||
| // LineJoin implements the pathbuilder interface. | ||||
| func (dasher *DashVertexConverter) LineJoin() { | ||||
| 	dasher.next.LineJoin() | ||||
| } | ||||
|  | ||||
| // Close implements the pathbuilder interface. | ||||
| func (dasher *DashVertexConverter) Close() { | ||||
| 	dasher.next.Close() | ||||
| } | ||||
|  | ||||
| // End implements the pathbuilder interface. | ||||
| func (dasher *DashVertexConverter) End() { | ||||
| 	dasher.next.End() | ||||
| } | ||||
|  | ||||
| func (dasher *DashVertexConverter) lineTo(x, y float64) { | ||||
| 	rest := dasher.dash[dasher.currentDash] - dasher.distance | ||||
| 	for rest < 0 { | ||||
| 		dasher.distance = dasher.distance - dasher.dash[dasher.currentDash] | ||||
| 		dasher.currentDash = (dasher.currentDash + 1) % len(dasher.dash) | ||||
| 		rest = dasher.dash[dasher.currentDash] - dasher.distance | ||||
| 	} | ||||
| 	d := distance(dasher.x, dasher.y, x, y) | ||||
| 	for d >= rest { | ||||
| 		k := rest / d | ||||
| 		lx := dasher.x + k*(x-dasher.x) | ||||
| 		ly := dasher.y + k*(y-dasher.y) | ||||
| 		if dasher.currentDash%2 == 0 { | ||||
| 			// line | ||||
| 			dasher.next.LineTo(lx, ly) | ||||
| 		} else { | ||||
| 			// gap | ||||
| 			dasher.next.End() | ||||
| 			dasher.next.MoveTo(lx, ly) | ||||
| 		} | ||||
| 		d = d - rest | ||||
| 		dasher.x, dasher.y = lx, ly | ||||
| 		dasher.currentDash = (dasher.currentDash + 1) % len(dasher.dash) | ||||
| 		rest = dasher.dash[dasher.currentDash] | ||||
| 	} | ||||
| 	dasher.distance = d | ||||
| 	if dasher.currentDash%2 == 0 { | ||||
| 		// line | ||||
| 		dasher.next.LineTo(x, y) | ||||
| 	} else { | ||||
| 		// gap | ||||
| 		dasher.next.End() | ||||
| 		dasher.next.MoveTo(x, y) | ||||
| 	} | ||||
| 	if dasher.distance >= dasher.dash[dasher.currentDash] { | ||||
| 		dasher.distance = dasher.distance - dasher.dash[dasher.currentDash] | ||||
| 		dasher.currentDash = (dasher.currentDash + 1) % len(dasher.dash) | ||||
| 	} | ||||
| 	dasher.x, dasher.y = x, y | ||||
| } | ||||
							
								
								
									
										41
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/demux_flattener.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/demux_flattener.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,41 @@ | ||||
| package drawing | ||||
|  | ||||
| // DemuxFlattener is a flattener | ||||
| type DemuxFlattener struct { | ||||
| 	Flatteners []Flattener | ||||
| } | ||||
|  | ||||
| // MoveTo implements the path builder interface. | ||||
| func (dc DemuxFlattener) MoveTo(x, y float64) { | ||||
| 	for _, flattener := range dc.Flatteners { | ||||
| 		flattener.MoveTo(x, y) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // LineTo implements the path builder interface. | ||||
| func (dc DemuxFlattener) LineTo(x, y float64) { | ||||
| 	for _, flattener := range dc.Flatteners { | ||||
| 		flattener.LineTo(x, y) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // LineJoin implements the path builder interface. | ||||
| func (dc DemuxFlattener) LineJoin() { | ||||
| 	for _, flattener := range dc.Flatteners { | ||||
| 		flattener.LineJoin() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Close implements the path builder interface. | ||||
| func (dc DemuxFlattener) Close() { | ||||
| 	for _, flattener := range dc.Flatteners { | ||||
| 		flattener.Close() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // End implements the path builder interface. | ||||
| func (dc DemuxFlattener) End() { | ||||
| 	for _, flattener := range dc.Flatteners { | ||||
| 		flattener.End() | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										148
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/drawing.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/drawing.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,148 @@ | ||||
| package drawing | ||||
|  | ||||
| import ( | ||||
| 	"image/color" | ||||
|  | ||||
| 	"github.com/golang/freetype/truetype" | ||||
| ) | ||||
|  | ||||
| // FillRule defines the type for fill rules | ||||
| type FillRule int | ||||
|  | ||||
| const ( | ||||
| 	// FillRuleEvenOdd determines the "insideness" of a point in the shape | ||||
| 	// by drawing a ray from that point to infinity in any direction | ||||
| 	// and counting the number of path segments from the given shape that the ray crosses. | ||||
| 	// If this number is odd, the point is inside; if even, the point is outside. | ||||
| 	FillRuleEvenOdd FillRule = iota | ||||
| 	// FillRuleWinding determines the "insideness" of a point in the shape | ||||
| 	// by drawing a ray from that point to infinity in any direction | ||||
| 	// and then examining the places where a segment of the shape crosses the ray. | ||||
| 	// Starting with a count of zero, add one each time a path segment crosses | ||||
| 	// the ray from left to right and subtract one each time | ||||
| 	// a path segment crosses the ray from right to left. After counting the crossings, | ||||
| 	// if the result is zero then the point is outside the path. Otherwise, it is inside. | ||||
| 	FillRuleWinding | ||||
| ) | ||||
|  | ||||
| // LineCap is the style of line extremities | ||||
| type LineCap int | ||||
|  | ||||
| const ( | ||||
| 	// RoundCap defines a rounded shape at the end of the line | ||||
| 	RoundCap LineCap = iota | ||||
| 	// ButtCap defines a squared shape exactly at the end of the line | ||||
| 	ButtCap | ||||
| 	// SquareCap defines a squared shape at the end of the line | ||||
| 	SquareCap | ||||
| ) | ||||
|  | ||||
| // LineJoin is the style of segments joint | ||||
| type LineJoin int | ||||
|  | ||||
| const ( | ||||
| 	// BevelJoin represents cut segments joint | ||||
| 	BevelJoin LineJoin = iota | ||||
| 	// RoundJoin represents rounded segments joint | ||||
| 	RoundJoin | ||||
| 	// MiterJoin represents peaker segments joint | ||||
| 	MiterJoin | ||||
| ) | ||||
|  | ||||
| // StrokeStyle keeps stroke style attributes | ||||
| // that is used by the Stroke method of a Drawer | ||||
| type StrokeStyle struct { | ||||
| 	// Color defines the color of stroke | ||||
| 	Color color.Color | ||||
| 	// Line width | ||||
| 	Width float64 | ||||
| 	// Line cap style rounded, butt or square | ||||
| 	LineCap LineCap | ||||
| 	// Line join style bevel, round or miter | ||||
| 	LineJoin LineJoin | ||||
| 	// offset of the first dash | ||||
| 	DashOffset float64 | ||||
| 	// array represented dash length pair values are plain dash and impair are space between dash | ||||
| 	// if empty display plain line | ||||
| 	Dash []float64 | ||||
| } | ||||
|  | ||||
| // SolidFillStyle define style attributes for a solid fill style | ||||
| type SolidFillStyle struct { | ||||
| 	// Color defines the line color | ||||
| 	Color color.Color | ||||
| 	// FillRule defines the file rule to used | ||||
| 	FillRule FillRule | ||||
| } | ||||
|  | ||||
| // Valign Vertical Alignment of the text | ||||
| type Valign int | ||||
|  | ||||
| const ( | ||||
| 	// ValignTop top align text | ||||
| 	ValignTop Valign = iota | ||||
| 	// ValignCenter centered text | ||||
| 	ValignCenter | ||||
| 	// ValignBottom bottom aligned text | ||||
| 	ValignBottom | ||||
| 	// ValignBaseline align text with the baseline of the font | ||||
| 	ValignBaseline | ||||
| ) | ||||
|  | ||||
| // Halign Horizontal Alignment of the text | ||||
| type Halign int | ||||
|  | ||||
| const ( | ||||
| 	// HalignLeft Horizontally align to left | ||||
| 	HalignLeft = iota | ||||
| 	// HalignCenter Horizontally align to center | ||||
| 	HalignCenter | ||||
| 	// HalignRight Horizontally align to right | ||||
| 	HalignRight | ||||
| ) | ||||
|  | ||||
| // TextStyle describe text property | ||||
| type TextStyle struct { | ||||
| 	// Color defines the color of text | ||||
| 	Color color.Color | ||||
| 	// Size font size | ||||
| 	Size float64 | ||||
| 	// The font to use | ||||
| 	Font *truetype.Font | ||||
| 	// Horizontal Alignment of the text | ||||
| 	Halign Halign | ||||
| 	// Vertical Alignment of the text | ||||
| 	Valign Valign | ||||
| } | ||||
|  | ||||
| // ScalingPolicy is a constant to define how to scale an image | ||||
| type ScalingPolicy int | ||||
|  | ||||
| const ( | ||||
| 	// ScalingNone no scaling applied | ||||
| 	ScalingNone ScalingPolicy = iota | ||||
| 	// ScalingStretch the image is stretched so that its width and height are exactly the given width and height | ||||
| 	ScalingStretch | ||||
| 	// ScalingWidth the image is scaled so that its width is exactly the given width | ||||
| 	ScalingWidth | ||||
| 	// ScalingHeight the image is scaled so that its height is exactly the given height | ||||
| 	ScalingHeight | ||||
| 	// ScalingFit the image is scaled to the largest scale that allow the image to fit within a rectangle width x height | ||||
| 	ScalingFit | ||||
| 	// ScalingSameArea the image is scaled so that its area is exactly the area of the given rectangle width x height | ||||
| 	ScalingSameArea | ||||
| 	// ScalingFill the image is scaled to the smallest scale that allow the image to fully cover a rectangle width x height | ||||
| 	ScalingFill | ||||
| ) | ||||
|  | ||||
| // ImageScaling style attributes used to display the image | ||||
| type ImageScaling struct { | ||||
| 	// Horizontal Alignment of the image | ||||
| 	Halign Halign | ||||
| 	// Vertical Alignment of the image | ||||
| 	Valign Valign | ||||
| 	// Width Height used by scaling policy | ||||
| 	Width, Height float64 | ||||
| 	// ScalingPolicy defines the scaling policy to applied to the image | ||||
| 	ScalingPolicy ScalingPolicy | ||||
| } | ||||
							
								
								
									
										97
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/flattener.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/flattener.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,97 @@ | ||||
| package drawing | ||||
|  | ||||
| // Liner receive segment definition | ||||
| type Liner interface { | ||||
| 	// LineTo Draw a line from the current position to the point (x, y) | ||||
| 	LineTo(x, y float64) | ||||
| } | ||||
|  | ||||
| // Flattener receive segment definition | ||||
| type Flattener interface { | ||||
| 	// MoveTo Start a New line from the point (x, y) | ||||
| 	MoveTo(x, y float64) | ||||
| 	// LineTo Draw a line from the current position to the point (x, y) | ||||
| 	LineTo(x, y float64) | ||||
| 	// LineJoin add the most recent starting point to close the path to create a polygon | ||||
| 	LineJoin() | ||||
| 	// Close add the most recent starting point to close the path to create a polygon | ||||
| 	Close() | ||||
| 	// End mark the current line as finished so we can draw caps | ||||
| 	End() | ||||
| } | ||||
|  | ||||
| // Flatten convert curves into straight segments keeping join segments info | ||||
| func Flatten(path *Path, flattener Flattener, scale float64) { | ||||
| 	// First Point | ||||
| 	var startX, startY float64 | ||||
| 	// Current Point | ||||
| 	var x, y float64 | ||||
| 	var i int | ||||
| 	for _, cmp := range path.Components { | ||||
| 		switch cmp { | ||||
| 		case MoveToComponent: | ||||
| 			x, y = path.Points[i], path.Points[i+1] | ||||
| 			startX, startY = x, y | ||||
| 			if i != 0 { | ||||
| 				flattener.End() | ||||
| 			} | ||||
| 			flattener.MoveTo(x, y) | ||||
| 			i += 2 | ||||
| 		case LineToComponent: | ||||
| 			x, y = path.Points[i], path.Points[i+1] | ||||
| 			flattener.LineTo(x, y) | ||||
| 			flattener.LineJoin() | ||||
| 			i += 2 | ||||
| 		case QuadCurveToComponent: | ||||
| 			// we include the previous point for the start of the curve | ||||
| 			TraceQuad(flattener, path.Points[i-2:], 0.5) | ||||
| 			x, y = path.Points[i+2], path.Points[i+3] | ||||
| 			flattener.LineTo(x, y) | ||||
| 			i += 4 | ||||
| 		case CubicCurveToComponent: | ||||
| 			TraceCubic(flattener, path.Points[i-2:], 0.5) | ||||
| 			x, y = path.Points[i+4], path.Points[i+5] | ||||
| 			flattener.LineTo(x, y) | ||||
| 			i += 6 | ||||
| 		case ArcToComponent: | ||||
| 			x, y = TraceArc(flattener, path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3], path.Points[i+4], path.Points[i+5], scale) | ||||
| 			flattener.LineTo(x, y) | ||||
| 			i += 6 | ||||
| 		case CloseComponent: | ||||
| 			flattener.LineTo(startX, startY) | ||||
| 			flattener.Close() | ||||
| 		} | ||||
| 	} | ||||
| 	flattener.End() | ||||
| } | ||||
|  | ||||
| // SegmentedPath is a path of disparate point sectinos. | ||||
| type SegmentedPath struct { | ||||
| 	Points []float64 | ||||
| } | ||||
|  | ||||
| // MoveTo implements the path interface. | ||||
| func (p *SegmentedPath) MoveTo(x, y float64) { | ||||
| 	p.Points = append(p.Points, x, y) | ||||
| 	// TODO need to mark this point as moveto | ||||
| } | ||||
|  | ||||
| // LineTo implements the path interface. | ||||
| func (p *SegmentedPath) LineTo(x, y float64) { | ||||
| 	p.Points = append(p.Points, x, y) | ||||
| } | ||||
|  | ||||
| // LineJoin implements the path interface. | ||||
| func (p *SegmentedPath) LineJoin() { | ||||
| 	// TODO need to mark the current point as linejoin | ||||
| } | ||||
|  | ||||
| // Close implements the path interface. | ||||
| func (p *SegmentedPath) Close() { | ||||
| 	// TODO Close | ||||
| } | ||||
|  | ||||
| // End implements the path interface. | ||||
| func (p *SegmentedPath) End() { | ||||
| 	// Nothing to do | ||||
| } | ||||
							
								
								
									
										30
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/free_type_path.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/free_type_path.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| package drawing | ||||
|  | ||||
| import ( | ||||
| 	"github.com/golang/freetype/raster" | ||||
| 	"golang.org/x/image/math/fixed" | ||||
| ) | ||||
|  | ||||
| // FtLineBuilder is a builder for freetype raster glyphs. | ||||
| type FtLineBuilder struct { | ||||
| 	Adder raster.Adder | ||||
| } | ||||
|  | ||||
| // MoveTo implements the path builder interface. | ||||
| func (liner FtLineBuilder) MoveTo(x, y float64) { | ||||
| 	liner.Adder.Start(fixed.Point26_6{X: fixed.Int26_6(x * 64), Y: fixed.Int26_6(y * 64)}) | ||||
| } | ||||
|  | ||||
| // LineTo implements the path builder interface. | ||||
| func (liner FtLineBuilder) LineTo(x, y float64) { | ||||
| 	liner.Adder.Add1(fixed.Point26_6{X: fixed.Int26_6(x * 64), Y: fixed.Int26_6(y * 64)}) | ||||
| } | ||||
|  | ||||
| // LineJoin implements the path builder interface. | ||||
| func (liner FtLineBuilder) LineJoin() {} | ||||
|  | ||||
| // Close implements the path builder interface. | ||||
| func (liner FtLineBuilder) Close() {} | ||||
|  | ||||
| // End implements the path builder interface. | ||||
| func (liner FtLineBuilder) End() {} | ||||
							
								
								
									
										82
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/graphic_context.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/graphic_context.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,82 @@ | ||||
| package drawing | ||||
|  | ||||
| import ( | ||||
| 	"image" | ||||
| 	"image/color" | ||||
|  | ||||
| 	"github.com/golang/freetype/truetype" | ||||
| ) | ||||
|  | ||||
| // GraphicContext describes the interface for the various backends (images, pdf, opengl, ...) | ||||
| type GraphicContext interface { | ||||
| 	// PathBuilder describes the interface for path drawing | ||||
| 	PathBuilder | ||||
| 	// BeginPath creates a new path | ||||
| 	BeginPath() | ||||
| 	// GetMatrixTransform returns the current transformation matrix | ||||
| 	GetMatrixTransform() Matrix | ||||
| 	// SetMatrixTransform sets the current transformation matrix | ||||
| 	SetMatrixTransform(tr Matrix) | ||||
| 	// ComposeMatrixTransform composes the current transformation matrix with tr | ||||
| 	ComposeMatrixTransform(tr Matrix) | ||||
| 	// Rotate applies a rotation to the current transformation matrix. angle is in radian. | ||||
| 	Rotate(angle float64) | ||||
| 	// Translate applies a translation to the current transformation matrix. | ||||
| 	Translate(tx, ty float64) | ||||
| 	// Scale applies a scale to the current transformation matrix. | ||||
| 	Scale(sx, sy float64) | ||||
| 	// SetStrokeColor sets the current stroke color | ||||
| 	SetStrokeColor(c color.Color) | ||||
| 	// SetFillColor sets the current fill color | ||||
| 	SetFillColor(c color.Color) | ||||
| 	// SetFillRule sets the current fill rule | ||||
| 	SetFillRule(f FillRule) | ||||
| 	// SetLineWidth sets the current line width | ||||
| 	SetLineWidth(lineWidth float64) | ||||
| 	// SetLineCap sets the current line cap | ||||
| 	SetLineCap(cap LineCap) | ||||
| 	// SetLineJoin sets the current line join | ||||
| 	SetLineJoin(join LineJoin) | ||||
| 	// SetLineDash sets the current dash | ||||
| 	SetLineDash(dash []float64, dashOffset float64) | ||||
| 	// SetFontSize sets the current font size | ||||
| 	SetFontSize(fontSize float64) | ||||
| 	// GetFontSize gets the current font size | ||||
| 	GetFontSize() float64 | ||||
| 	// SetFont sets the font for the context | ||||
| 	SetFont(f *truetype.Font) | ||||
| 	// GetFont returns the current font | ||||
| 	GetFont() *truetype.Font | ||||
| 	// DrawImage draws the raster image in the current canvas | ||||
| 	DrawImage(image image.Image) | ||||
| 	// Save the context and push it to the context stack | ||||
| 	Save() | ||||
| 	// Restore remove the current context and restore the last one | ||||
| 	Restore() | ||||
| 	// Clear fills the current canvas with a default transparent color | ||||
| 	Clear() | ||||
| 	// ClearRect fills the specified rectangle with a default transparent color | ||||
| 	ClearRect(x1, y1, x2, y2 int) | ||||
| 	// SetDPI sets the current DPI | ||||
| 	SetDPI(dpi int) | ||||
| 	// GetDPI gets the current DPI | ||||
| 	GetDPI() int | ||||
| 	// GetStringBounds gets pixel bounds(dimensions) of given string | ||||
| 	GetStringBounds(s string) (left, top, right, bottom float64) | ||||
| 	// CreateStringPath creates a path from the string s at x, y | ||||
| 	CreateStringPath(text string, x, y float64) (cursor float64) | ||||
| 	// FillString draws the text at point (0, 0) | ||||
| 	FillString(text string) (cursor float64) | ||||
| 	// FillStringAt draws the text at the specified point (x, y) | ||||
| 	FillStringAt(text string, x, y float64) (cursor float64) | ||||
| 	// StrokeString draws the contour of the text at point (0, 0) | ||||
| 	StrokeString(text string) (cursor float64) | ||||
| 	// StrokeStringAt draws the contour of the text at point (x, y) | ||||
| 	StrokeStringAt(text string, x, y float64) (cursor float64) | ||||
| 	// Stroke strokes the paths with the color specified by SetStrokeColor | ||||
| 	Stroke(paths ...*Path) | ||||
| 	// Fill fills the paths with the color specified by SetFillColor | ||||
| 	Fill(paths ...*Path) | ||||
| 	// FillStroke first fills the paths and than strokes them | ||||
| 	FillStroke(paths ...*Path) | ||||
| } | ||||
							
								
								
									
										13
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/image_filter.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/image_filter.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| package drawing | ||||
|  | ||||
| // ImageFilter defines the type of filter to use | ||||
| type ImageFilter int | ||||
|  | ||||
| const ( | ||||
| 	// LinearFilter defines a linear filter | ||||
| 	LinearFilter ImageFilter = iota | ||||
| 	// BilinearFilter defines a bilinear filter | ||||
| 	BilinearFilter | ||||
| 	// BicubicFilter defines a bicubic filter | ||||
| 	BicubicFilter | ||||
| ) | ||||
							
								
								
									
										48
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/line.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/line.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,48 @@ | ||||
| package drawing | ||||
|  | ||||
| import ( | ||||
| 	"image/color" | ||||
| 	"image/draw" | ||||
| ) | ||||
|  | ||||
| // PolylineBresenham draws a polyline to an image | ||||
| func PolylineBresenham(img draw.Image, c color.Color, s ...float64) { | ||||
| 	for i := 2; i < len(s); i += 2 { | ||||
| 		Bresenham(img, c, int(s[i-2]+0.5), int(s[i-1]+0.5), int(s[i]+0.5), int(s[i+1]+0.5)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Bresenham draws a line between (x0, y0) and (x1, y1) | ||||
| func Bresenham(img draw.Image, color color.Color, x0, y0, x1, y1 int) { | ||||
| 	dx := abs(x1 - x0) | ||||
| 	dy := abs(y1 - y0) | ||||
| 	var sx, sy int | ||||
| 	if x0 < x1 { | ||||
| 		sx = 1 | ||||
| 	} else { | ||||
| 		sx = -1 | ||||
| 	} | ||||
| 	if y0 < y1 { | ||||
| 		sy = 1 | ||||
| 	} else { | ||||
| 		sy = -1 | ||||
| 	} | ||||
| 	err := dx - dy | ||||
|  | ||||
| 	var e2 int | ||||
| 	for { | ||||
| 		img.Set(x0, y0, color) | ||||
| 		if x0 == x1 && y0 == y1 { | ||||
| 			return | ||||
| 		} | ||||
| 		e2 = 2 * err | ||||
| 		if e2 > -dy { | ||||
| 			err = err - dy | ||||
| 			x0 = x0 + sx | ||||
| 		} | ||||
| 		if e2 < dx { | ||||
| 			err = err + dx | ||||
| 			y0 = y0 + sy | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										220
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/matrix.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										220
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/matrix.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,220 @@ | ||||
| package drawing | ||||
|  | ||||
| import ( | ||||
| 	"math" | ||||
| ) | ||||
|  | ||||
| // Matrix represents an affine transformation | ||||
| type Matrix [6]float64 | ||||
|  | ||||
| const ( | ||||
| 	epsilon = 1e-6 | ||||
| ) | ||||
|  | ||||
| // Determinant compute the determinant of the matrix | ||||
| func (tr Matrix) Determinant() float64 { | ||||
| 	return tr[0]*tr[3] - tr[1]*tr[2] | ||||
| } | ||||
|  | ||||
| // Transform applies the transformation matrix to points. It modify the points passed in parameter. | ||||
| func (tr Matrix) Transform(points []float64) { | ||||
| 	for i, j := 0, 1; j < len(points); i, j = i+2, j+2 { | ||||
| 		x := points[i] | ||||
| 		y := points[j] | ||||
| 		points[i] = x*tr[0] + y*tr[2] + tr[4] | ||||
| 		points[j] = x*tr[1] + y*tr[3] + tr[5] | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // TransformPoint applies the transformation matrix to point. It returns the point the transformed point. | ||||
| func (tr Matrix) TransformPoint(x, y float64) (xres, yres float64) { | ||||
| 	xres = x*tr[0] + y*tr[2] + tr[4] | ||||
| 	yres = x*tr[1] + y*tr[3] + tr[5] | ||||
| 	return xres, yres | ||||
| } | ||||
|  | ||||
| func minMax(x, y float64) (min, max float64) { | ||||
| 	if x > y { | ||||
| 		return y, x | ||||
| 	} | ||||
| 	return x, y | ||||
| } | ||||
|  | ||||
| // TransformRectangle applies the transformation matrix to the rectangle represented by the min and the max point of the rectangle | ||||
| func (tr Matrix) TransformRectangle(x0, y0, x2, y2 float64) (nx0, ny0, nx2, ny2 float64) { | ||||
| 	points := []float64{x0, y0, x2, y0, x2, y2, x0, y2} | ||||
| 	tr.Transform(points) | ||||
| 	points[0], points[2] = minMax(points[0], points[2]) | ||||
| 	points[4], points[6] = minMax(points[4], points[6]) | ||||
| 	points[1], points[3] = minMax(points[1], points[3]) | ||||
| 	points[5], points[7] = minMax(points[5], points[7]) | ||||
|  | ||||
| 	nx0 = math.Min(points[0], points[4]) | ||||
| 	ny0 = math.Min(points[1], points[5]) | ||||
| 	nx2 = math.Max(points[2], points[6]) | ||||
| 	ny2 = math.Max(points[3], points[7]) | ||||
| 	return nx0, ny0, nx2, ny2 | ||||
| } | ||||
|  | ||||
| // InverseTransform applies the transformation inverse matrix to the rectangle represented by the min and the max point of the rectangle | ||||
| func (tr Matrix) InverseTransform(points []float64) { | ||||
| 	d := tr.Determinant() // matrix determinant | ||||
| 	for i, j := 0, 1; j < len(points); i, j = i+2, j+2 { | ||||
| 		x := points[i] | ||||
| 		y := points[j] | ||||
| 		points[i] = ((x-tr[4])*tr[3] - (y-tr[5])*tr[2]) / d | ||||
| 		points[j] = ((y-tr[5])*tr[0] - (x-tr[4])*tr[1]) / d | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // InverseTransformPoint applies the transformation inverse matrix to point. It returns the point the transformed point. | ||||
| func (tr Matrix) InverseTransformPoint(x, y float64) (xres, yres float64) { | ||||
| 	d := tr.Determinant() // matrix determinant | ||||
| 	xres = ((x-tr[4])*tr[3] - (y-tr[5])*tr[2]) / d | ||||
| 	yres = ((y-tr[5])*tr[0] - (x-tr[4])*tr[1]) / d | ||||
| 	return xres, yres | ||||
| } | ||||
|  | ||||
| // VectorTransform applies the transformation matrix to points without using the translation parameter of the affine matrix. | ||||
| // It modify the points passed in parameter. | ||||
| func (tr Matrix) VectorTransform(points []float64) { | ||||
| 	for i, j := 0, 1; j < len(points); i, j = i+2, j+2 { | ||||
| 		x := points[i] | ||||
| 		y := points[j] | ||||
| 		points[i] = x*tr[0] + y*tr[2] | ||||
| 		points[j] = x*tr[1] + y*tr[3] | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewIdentityMatrix creates an identity transformation matrix. | ||||
| func NewIdentityMatrix() Matrix { | ||||
| 	return Matrix{1, 0, 0, 1, 0, 0} | ||||
| } | ||||
|  | ||||
| // NewTranslationMatrix creates a transformation matrix with a translation tx and ty translation parameter | ||||
| func NewTranslationMatrix(tx, ty float64) Matrix { | ||||
| 	return Matrix{1, 0, 0, 1, tx, ty} | ||||
| } | ||||
|  | ||||
| // NewScaleMatrix creates a transformation matrix with a sx, sy scale factor | ||||
| func NewScaleMatrix(sx, sy float64) Matrix { | ||||
| 	return Matrix{sx, 0, 0, sy, 0, 0} | ||||
| } | ||||
|  | ||||
| // NewRotationMatrix creates a rotation transformation matrix. angle is in radian | ||||
| func NewRotationMatrix(angle float64) Matrix { | ||||
| 	c := math.Cos(angle) | ||||
| 	s := math.Sin(angle) | ||||
| 	return Matrix{c, s, -s, c, 0, 0} | ||||
| } | ||||
|  | ||||
| // NewMatrixFromRects creates a transformation matrix, combining a scale and a translation, that transform rectangle1 into rectangle2. | ||||
| func NewMatrixFromRects(rectangle1, rectangle2 [4]float64) Matrix { | ||||
| 	xScale := (rectangle2[2] - rectangle2[0]) / (rectangle1[2] - rectangle1[0]) | ||||
| 	yScale := (rectangle2[3] - rectangle2[1]) / (rectangle1[3] - rectangle1[1]) | ||||
| 	xOffset := rectangle2[0] - (rectangle1[0] * xScale) | ||||
| 	yOffset := rectangle2[1] - (rectangle1[1] * yScale) | ||||
| 	return Matrix{xScale, 0, 0, yScale, xOffset, yOffset} | ||||
| } | ||||
|  | ||||
| // Inverse computes the inverse matrix | ||||
| func (tr *Matrix) Inverse() { | ||||
| 	d := tr.Determinant() // matrix determinant | ||||
| 	tr0, tr1, tr2, tr3, tr4, tr5 := tr[0], tr[1], tr[2], tr[3], tr[4], tr[5] | ||||
| 	tr[0] = tr3 / d | ||||
| 	tr[1] = -tr1 / d | ||||
| 	tr[2] = -tr2 / d | ||||
| 	tr[3] = tr0 / d | ||||
| 	tr[4] = (tr2*tr5 - tr3*tr4) / d | ||||
| 	tr[5] = (tr1*tr4 - tr0*tr5) / d | ||||
| } | ||||
|  | ||||
| // Copy copies the matrix. | ||||
| func (tr Matrix) Copy() Matrix { | ||||
| 	var result Matrix | ||||
| 	copy(result[:], tr[:]) | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| // Compose multiplies trToConcat x tr | ||||
| func (tr *Matrix) Compose(trToCompose Matrix) { | ||||
| 	tr0, tr1, tr2, tr3, tr4, tr5 := tr[0], tr[1], tr[2], tr[3], tr[4], tr[5] | ||||
| 	tr[0] = trToCompose[0]*tr0 + trToCompose[1]*tr2 | ||||
| 	tr[1] = trToCompose[1]*tr3 + trToCompose[0]*tr1 | ||||
| 	tr[2] = trToCompose[2]*tr0 + trToCompose[3]*tr2 | ||||
| 	tr[3] = trToCompose[3]*tr3 + trToCompose[2]*tr1 | ||||
| 	tr[4] = trToCompose[4]*tr0 + trToCompose[5]*tr2 + tr4 | ||||
| 	tr[5] = trToCompose[5]*tr3 + trToCompose[4]*tr1 + tr5 | ||||
| } | ||||
|  | ||||
| // Scale adds a scale to the matrix | ||||
| func (tr *Matrix) Scale(sx, sy float64) { | ||||
| 	tr[0] = sx * tr[0] | ||||
| 	tr[1] = sx * tr[1] | ||||
| 	tr[2] = sy * tr[2] | ||||
| 	tr[3] = sy * tr[3] | ||||
| } | ||||
|  | ||||
| // Translate adds a translation to the matrix | ||||
| func (tr *Matrix) Translate(tx, ty float64) { | ||||
| 	tr[4] = tx*tr[0] + ty*tr[2] + tr[4] | ||||
| 	tr[5] = ty*tr[3] + tx*tr[1] + tr[5] | ||||
| } | ||||
|  | ||||
| // Rotate adds a rotation to the matrix. | ||||
| func (tr *Matrix) Rotate(radians float64) { | ||||
| 	c := math.Cos(radians) | ||||
| 	s := math.Sin(radians) | ||||
| 	t0 := c*tr[0] + s*tr[2] | ||||
| 	t1 := s*tr[3] + c*tr[1] | ||||
| 	t2 := c*tr[2] - s*tr[0] | ||||
| 	t3 := c*tr[3] - s*tr[1] | ||||
| 	tr[0] = t0 | ||||
| 	tr[1] = t1 | ||||
| 	tr[2] = t2 | ||||
| 	tr[3] = t3 | ||||
| } | ||||
|  | ||||
| // GetTranslation gets the matrix traslation. | ||||
| func (tr Matrix) GetTranslation() (x, y float64) { | ||||
| 	return tr[4], tr[5] | ||||
| } | ||||
|  | ||||
| // GetScaling gets the matrix scaling. | ||||
| func (tr Matrix) GetScaling() (x, y float64) { | ||||
| 	return tr[0], tr[3] | ||||
| } | ||||
|  | ||||
| // GetScale computes a scale for the matrix | ||||
| func (tr Matrix) GetScale() float64 { | ||||
| 	x := 0.707106781*tr[0] + 0.707106781*tr[1] | ||||
| 	y := 0.707106781*tr[2] + 0.707106781*tr[3] | ||||
| 	return math.Sqrt(x*x + y*y) | ||||
| } | ||||
|  | ||||
| // ******************** Testing ******************** | ||||
|  | ||||
| // Equals tests if a two transformation are equal. A tolerance is applied when comparing matrix elements. | ||||
| func (tr Matrix) Equals(tr2 Matrix) bool { | ||||
| 	for i := 0; i < 6; i = i + 1 { | ||||
| 		if !fequals(tr[i], tr2[i]) { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // IsIdentity tests if a transformation is the identity transformation. A tolerance is applied when comparing matrix elements. | ||||
| func (tr Matrix) IsIdentity() bool { | ||||
| 	return fequals(tr[4], 0) && fequals(tr[5], 0) && tr.IsTranslation() | ||||
| } | ||||
|  | ||||
| // IsTranslation tests if a transformation is is a pure translation. A tolerance is applied when comparing matrix elements. | ||||
| func (tr Matrix) IsTranslation() bool { | ||||
| 	return fequals(tr[0], 1) && fequals(tr[1], 0) && fequals(tr[2], 0) && fequals(tr[3], 1) | ||||
| } | ||||
|  | ||||
| // fequals compares two floats. return true if the distance between the two floats is less than epsilon, false otherwise | ||||
| func fequals(float1, float2 float64) bool { | ||||
| 	return math.Abs(float1-float2) <= epsilon | ||||
| } | ||||
							
								
								
									
										31
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/painter.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/painter.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | ||||
| package drawing | ||||
|  | ||||
| import ( | ||||
| 	"image" | ||||
| 	"image/color" | ||||
|  | ||||
| 	"golang.org/x/image/draw" | ||||
| 	"golang.org/x/image/math/f64" | ||||
|  | ||||
| 	"github.com/golang/freetype/raster" | ||||
| ) | ||||
|  | ||||
| // Painter implements the freetype raster.Painter and has a SetColor method like the RGBAPainter | ||||
| type Painter interface { | ||||
| 	raster.Painter | ||||
| 	SetColor(color color.Color) | ||||
| } | ||||
|  | ||||
| // DrawImage draws an image into dest using an affine transformation matrix, an op and a filter | ||||
| func DrawImage(src image.Image, dest draw.Image, tr Matrix, op draw.Op, filter ImageFilter) { | ||||
| 	var transformer draw.Transformer | ||||
| 	switch filter { | ||||
| 	case LinearFilter: | ||||
| 		transformer = draw.NearestNeighbor | ||||
| 	case BilinearFilter: | ||||
| 		transformer = draw.BiLinear | ||||
| 	case BicubicFilter: | ||||
| 		transformer = draw.CatmullRom | ||||
| 	} | ||||
| 	transformer.Transform(dest, f64.Aff3{tr[0], tr[1], tr[4], tr[2], tr[3], tr[5]}, src, src.Bounds(), op, nil) | ||||
| } | ||||
							
								
								
									
										186
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/path.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/path.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,186 @@ | ||||
| package drawing | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"math" | ||||
| ) | ||||
|  | ||||
| // PathBuilder describes the interface for path drawing. | ||||
| type PathBuilder interface { | ||||
| 	// LastPoint returns the current point of the current sub path | ||||
| 	LastPoint() (x, y float64) | ||||
| 	// MoveTo creates a new subpath that start at the specified point | ||||
| 	MoveTo(x, y float64) | ||||
| 	// LineTo adds a line to the current subpath | ||||
| 	LineTo(x, y float64) | ||||
| 	// QuadCurveTo adds a quadratic Bézier curve to the current subpath | ||||
| 	QuadCurveTo(cx, cy, x, y float64) | ||||
| 	// CubicCurveTo adds a cubic Bézier curve to the current subpath | ||||
| 	CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) | ||||
| 	// ArcTo adds an arc to the current subpath | ||||
| 	ArcTo(cx, cy, rx, ry, startAngle, angle float64) | ||||
| 	// Close creates a line from the current point to the last MoveTo | ||||
| 	// point (if not the same) and mark the path as closed so the | ||||
| 	// first and last lines join nicely. | ||||
| 	Close() | ||||
| } | ||||
|  | ||||
| // PathComponent represents component of a path | ||||
| type PathComponent int | ||||
|  | ||||
| const ( | ||||
| 	// MoveToComponent is a MoveTo component in a Path | ||||
| 	MoveToComponent PathComponent = iota | ||||
| 	// LineToComponent is a LineTo component in a Path | ||||
| 	LineToComponent | ||||
| 	// QuadCurveToComponent is a QuadCurveTo component in a Path | ||||
| 	QuadCurveToComponent | ||||
| 	// CubicCurveToComponent is a CubicCurveTo component in a Path | ||||
| 	CubicCurveToComponent | ||||
| 	// ArcToComponent is a ArcTo component in a Path | ||||
| 	ArcToComponent | ||||
| 	// CloseComponent is a ArcTo component in a Path | ||||
| 	CloseComponent | ||||
| ) | ||||
|  | ||||
| // Path stores points | ||||
| type Path struct { | ||||
| 	// Components is a slice of PathComponent in a Path and mark the role of each points in the Path | ||||
| 	Components []PathComponent | ||||
| 	// Points are combined with Components to have a specific role in the path | ||||
| 	Points []float64 | ||||
| 	// Last Point of the Path | ||||
| 	x, y float64 | ||||
| } | ||||
|  | ||||
| func (p *Path) appendToPath(cmd PathComponent, points ...float64) { | ||||
| 	p.Components = append(p.Components, cmd) | ||||
| 	p.Points = append(p.Points, points...) | ||||
| } | ||||
|  | ||||
| // LastPoint returns the current point of the current path | ||||
| func (p *Path) LastPoint() (x, y float64) { | ||||
| 	return p.x, p.y | ||||
| } | ||||
|  | ||||
| // MoveTo starts a new path at (x, y) position | ||||
| func (p *Path) MoveTo(x, y float64) { | ||||
| 	p.appendToPath(MoveToComponent, x, y) | ||||
| 	p.x = x | ||||
| 	p.y = y | ||||
| } | ||||
|  | ||||
| // LineTo adds a line to the current path | ||||
| func (p *Path) LineTo(x, y float64) { | ||||
| 	if len(p.Components) == 0 { //special case when no move has been done | ||||
| 		p.MoveTo(0, 0) | ||||
| 	} | ||||
| 	p.appendToPath(LineToComponent, x, y) | ||||
| 	p.x = x | ||||
| 	p.y = y | ||||
| } | ||||
|  | ||||
| // QuadCurveTo adds a quadratic bezier curve to the current path | ||||
| func (p *Path) QuadCurveTo(cx, cy, x, y float64) { | ||||
| 	if len(p.Components) == 0 { //special case when no move has been done | ||||
| 		p.MoveTo(0, 0) | ||||
| 	} | ||||
| 	p.appendToPath(QuadCurveToComponent, cx, cy, x, y) | ||||
| 	p.x = x | ||||
| 	p.y = y | ||||
| } | ||||
|  | ||||
| // CubicCurveTo adds a cubic bezier curve to the current path | ||||
| func (p *Path) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) { | ||||
| 	if len(p.Components) == 0 { //special case when no move has been done | ||||
| 		p.MoveTo(0, 0) | ||||
| 	} | ||||
| 	p.appendToPath(CubicCurveToComponent, cx1, cy1, cx2, cy2, x, y) | ||||
| 	p.x = x | ||||
| 	p.y = y | ||||
| } | ||||
|  | ||||
| // ArcTo adds an arc to the path | ||||
| func (p *Path) ArcTo(cx, cy, rx, ry, startAngle, delta float64) { | ||||
| 	endAngle := startAngle + delta | ||||
| 	clockWise := true | ||||
| 	if delta < 0 { | ||||
| 		clockWise = false | ||||
| 	} | ||||
| 	// normalize | ||||
| 	if clockWise { | ||||
| 		for endAngle < startAngle { | ||||
| 			endAngle += math.Pi * 2.0 | ||||
| 		} | ||||
| 	} else { | ||||
| 		for startAngle < endAngle { | ||||
| 			startAngle += math.Pi * 2.0 | ||||
| 		} | ||||
| 	} | ||||
| 	startX := cx + math.Cos(startAngle)*rx | ||||
| 	startY := cy + math.Sin(startAngle)*ry | ||||
| 	if len(p.Components) > 0 { | ||||
| 		p.LineTo(startX, startY) | ||||
| 	} else { | ||||
| 		p.MoveTo(startX, startY) | ||||
| 	} | ||||
| 	p.appendToPath(ArcToComponent, cx, cy, rx, ry, startAngle, delta) | ||||
| 	p.x = cx + math.Cos(endAngle)*rx | ||||
| 	p.y = cy + math.Sin(endAngle)*ry | ||||
| } | ||||
|  | ||||
| // Close closes the current path | ||||
| func (p *Path) Close() { | ||||
| 	p.appendToPath(CloseComponent) | ||||
| } | ||||
|  | ||||
| // Copy make a clone of the current path and return it | ||||
| func (p *Path) Copy() (dest *Path) { | ||||
| 	dest = new(Path) | ||||
| 	dest.Components = make([]PathComponent, len(p.Components)) | ||||
| 	copy(dest.Components, p.Components) | ||||
| 	dest.Points = make([]float64, len(p.Points)) | ||||
| 	copy(dest.Points, p.Points) | ||||
| 	dest.x, dest.y = p.x, p.y | ||||
| 	return dest | ||||
| } | ||||
|  | ||||
| // Clear reset the path | ||||
| func (p *Path) Clear() { | ||||
| 	p.Components = p.Components[0:0] | ||||
| 	p.Points = p.Points[0:0] | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // IsEmpty returns true if the path is empty | ||||
| func (p *Path) IsEmpty() bool { | ||||
| 	return len(p.Components) == 0 | ||||
| } | ||||
|  | ||||
| // String returns a debug text view of the path | ||||
| func (p *Path) String() string { | ||||
| 	s := "" | ||||
| 	j := 0 | ||||
| 	for _, cmd := range p.Components { | ||||
| 		switch cmd { | ||||
| 		case MoveToComponent: | ||||
| 			s += fmt.Sprintf("MoveTo: %f, %f\n", p.Points[j], p.Points[j+1]) | ||||
| 			j = j + 2 | ||||
| 		case LineToComponent: | ||||
| 			s += fmt.Sprintf("LineTo: %f, %f\n", p.Points[j], p.Points[j+1]) | ||||
| 			j = j + 2 | ||||
| 		case QuadCurveToComponent: | ||||
| 			s += fmt.Sprintf("QuadCurveTo: %f, %f, %f, %f\n", p.Points[j], p.Points[j+1], p.Points[j+2], p.Points[j+3]) | ||||
| 			j = j + 4 | ||||
| 		case CubicCurveToComponent: | ||||
| 			s += fmt.Sprintf("CubicCurveTo: %f, %f, %f, %f, %f, %f\n", p.Points[j], p.Points[j+1], p.Points[j+2], p.Points[j+3], p.Points[j+4], p.Points[j+5]) | ||||
| 			j = j + 6 | ||||
| 		case ArcToComponent: | ||||
| 			s += fmt.Sprintf("ArcTo: %f, %f, %f, %f, %f, %f\n", p.Points[j], p.Points[j+1], p.Points[j+2], p.Points[j+3], p.Points[j+4], p.Points[j+5]) | ||||
| 			j = j + 6 | ||||
| 		case CloseComponent: | ||||
| 			s += "Close\n" | ||||
| 		} | ||||
| 	} | ||||
| 	return s | ||||
| } | ||||
							
								
								
									
										283
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/raster_graphic_context.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										283
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/raster_graphic_context.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,283 @@ | ||||
| package drawing | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"image" | ||||
| 	"image/color" | ||||
| 	"math" | ||||
|  | ||||
| 	"github.com/golang/freetype/raster" | ||||
| 	"github.com/golang/freetype/truetype" | ||||
| 	"golang.org/x/image/draw" | ||||
| 	"golang.org/x/image/font" | ||||
| 	"golang.org/x/image/math/fixed" | ||||
| ) | ||||
|  | ||||
| // NewRasterGraphicContext creates a new Graphic context from an image. | ||||
| func NewRasterGraphicContext(img draw.Image) (*RasterGraphicContext, error) { | ||||
| 	var painter Painter | ||||
| 	switch selectImage := img.(type) { | ||||
| 	case *image.RGBA: | ||||
| 		painter = raster.NewRGBAPainter(selectImage) | ||||
| 	default: | ||||
| 		return nil, errors.New("NewRasterGraphicContext() :: invalid image type") | ||||
| 	} | ||||
| 	return NewRasterGraphicContextWithPainter(img, painter), nil | ||||
| } | ||||
|  | ||||
| // NewRasterGraphicContextWithPainter creates a new Graphic context from an image and a Painter (see Freetype-go) | ||||
| func NewRasterGraphicContextWithPainter(img draw.Image, painter Painter) *RasterGraphicContext { | ||||
| 	width, height := img.Bounds().Dx(), img.Bounds().Dy() | ||||
| 	return &RasterGraphicContext{ | ||||
| 		NewStackGraphicContext(), | ||||
| 		img, | ||||
| 		painter, | ||||
| 		raster.NewRasterizer(width, height), | ||||
| 		raster.NewRasterizer(width, height), | ||||
| 		&truetype.GlyphBuf{}, | ||||
| 		DefaultDPI, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // RasterGraphicContext is the implementation of GraphicContext for a raster image | ||||
| type RasterGraphicContext struct { | ||||
| 	*StackGraphicContext | ||||
| 	img              draw.Image | ||||
| 	painter          Painter | ||||
| 	fillRasterizer   *raster.Rasterizer | ||||
| 	strokeRasterizer *raster.Rasterizer | ||||
| 	glyphBuf         *truetype.GlyphBuf | ||||
| 	DPI              float64 | ||||
| } | ||||
|  | ||||
| // SetDPI sets the screen resolution in dots per inch. | ||||
| func (rgc *RasterGraphicContext) SetDPI(dpi float64) { | ||||
| 	rgc.DPI = dpi | ||||
| 	rgc.recalc() | ||||
| } | ||||
|  | ||||
| // GetDPI returns the resolution of the Image GraphicContext | ||||
| func (rgc *RasterGraphicContext) GetDPI() float64 { | ||||
| 	return rgc.DPI | ||||
| } | ||||
|  | ||||
| // Clear fills the current canvas with a default transparent color | ||||
| func (rgc *RasterGraphicContext) Clear() { | ||||
| 	width, height := rgc.img.Bounds().Dx(), rgc.img.Bounds().Dy() | ||||
| 	rgc.ClearRect(0, 0, width, height) | ||||
| } | ||||
|  | ||||
| // ClearRect fills the current canvas with a default transparent color at the specified rectangle | ||||
| func (rgc *RasterGraphicContext) ClearRect(x1, y1, x2, y2 int) { | ||||
| 	imageColor := image.NewUniform(rgc.current.FillColor) | ||||
| 	draw.Draw(rgc.img, image.Rect(x1, y1, x2, y2), imageColor, image.ZP, draw.Over) | ||||
| } | ||||
|  | ||||
| // DrawImage draws the raster image in the current canvas | ||||
| func (rgc *RasterGraphicContext) DrawImage(img image.Image) { | ||||
| 	DrawImage(img, rgc.img, rgc.current.Tr, draw.Over, BilinearFilter) | ||||
| } | ||||
|  | ||||
| // FillString draws the text at point (0, 0) | ||||
| func (rgc *RasterGraphicContext) FillString(text string) (cursor float64, err error) { | ||||
| 	cursor, err = rgc.FillStringAt(text, 0, 0) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // FillStringAt draws the text at the specified point (x, y) | ||||
| func (rgc *RasterGraphicContext) FillStringAt(text string, x, y float64) (cursor float64, err error) { | ||||
| 	cursor, err = rgc.CreateStringPath(text, x, y) | ||||
| 	rgc.Fill() | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // StrokeString draws the contour of the text at point (0, 0) | ||||
| func (rgc *RasterGraphicContext) StrokeString(text string) (cursor float64, err error) { | ||||
| 	cursor, err = rgc.StrokeStringAt(text, 0, 0) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // StrokeStringAt draws the contour of the text at point (x, y) | ||||
| func (rgc *RasterGraphicContext) StrokeStringAt(text string, x, y float64) (cursor float64, err error) { | ||||
| 	cursor, err = rgc.CreateStringPath(text, x, y) | ||||
| 	rgc.Stroke() | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (rgc *RasterGraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error { | ||||
| 	if err := rgc.glyphBuf.Load(rgc.current.Font, fixed.Int26_6(rgc.current.Scale), glyph, font.HintingNone); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	e0 := 0 | ||||
| 	for _, e1 := range rgc.glyphBuf.Ends { | ||||
| 		DrawContour(rgc, rgc.glyphBuf.Points[e0:e1], dx, dy) | ||||
| 		e0 = e1 | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // CreateStringPath creates a path from the string s at x, y, and returns the string width. | ||||
| // The text is placed so that the left edge of the em square of the first character of s | ||||
| // and the baseline intersect at x, y. The majority of the affected pixels will be | ||||
| // above and to the right of the point, but some may be below or to the left. | ||||
| // For example, drawing a string that starts with a 'J' in an italic font may | ||||
| // affect pixels below and left of the point. | ||||
| func (rgc *RasterGraphicContext) CreateStringPath(s string, x, y float64) (cursor float64, err error) { | ||||
| 	f := rgc.GetFont() | ||||
| 	if f == nil { | ||||
| 		err = errors.New("No font loaded, cannot continue") | ||||
| 		return | ||||
| 	} | ||||
| 	rgc.recalc() | ||||
|  | ||||
| 	startx := x | ||||
| 	prev, hasPrev := truetype.Index(0), false | ||||
| 	for _, rc := range s { | ||||
| 		index := f.Index(rc) | ||||
| 		if hasPrev { | ||||
| 			x += fUnitsToFloat64(f.Kern(fixed.Int26_6(rgc.current.Scale), prev, index)) | ||||
| 		} | ||||
| 		err = rgc.drawGlyph(index, x, y) | ||||
| 		if err != nil { | ||||
| 			cursor = x - startx | ||||
| 			return | ||||
| 		} | ||||
| 		x += fUnitsToFloat64(f.HMetric(fixed.Int26_6(rgc.current.Scale), index).AdvanceWidth) | ||||
| 		prev, hasPrev = index, true | ||||
| 	} | ||||
| 	cursor = x - startx | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // GetStringBounds returns the approximate pixel bounds of a string. | ||||
| func (rgc *RasterGraphicContext) GetStringBounds(s string) (left, top, right, bottom float64, err error) { | ||||
| 	f := rgc.GetFont() | ||||
| 	if f == nil { | ||||
| 		err = errors.New("No font loaded, cannot continue") | ||||
| 		return | ||||
| 	} | ||||
| 	rgc.recalc() | ||||
|  | ||||
| 	left = math.MaxFloat64 | ||||
| 	top = math.MaxFloat64 | ||||
|  | ||||
| 	cursor := 0.0 | ||||
| 	prev, hasPrev := truetype.Index(0), false | ||||
| 	for _, rc := range s { | ||||
| 		index := f.Index(rc) | ||||
| 		if hasPrev { | ||||
| 			cursor += fUnitsToFloat64(f.Kern(fixed.Int26_6(rgc.current.Scale), prev, index)) | ||||
| 		} | ||||
|  | ||||
| 		if err = rgc.glyphBuf.Load(rgc.current.Font, fixed.Int26_6(rgc.current.Scale), index, font.HintingNone); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		e0 := 0 | ||||
| 		for _, e1 := range rgc.glyphBuf.Ends { | ||||
| 			ps := rgc.glyphBuf.Points[e0:e1] | ||||
| 			for _, p := range ps { | ||||
| 				x, y := pointToF64Point(p) | ||||
| 				top = math.Min(top, y) | ||||
| 				bottom = math.Max(bottom, y) | ||||
| 				left = math.Min(left, x+cursor) | ||||
| 				right = math.Max(right, x+cursor) | ||||
| 			} | ||||
| 			e0 = e1 | ||||
| 		} | ||||
| 		cursor += fUnitsToFloat64(f.HMetric(fixed.Int26_6(rgc.current.Scale), index).AdvanceWidth) | ||||
| 		prev, hasPrev = index, true | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // recalc recalculates scale and bounds values from the font size, screen | ||||
| // resolution and font metrics, and invalidates the glyph cache. | ||||
| func (rgc *RasterGraphicContext) recalc() { | ||||
| 	rgc.current.Scale = rgc.current.FontSizePoints * float64(rgc.DPI) | ||||
| } | ||||
|  | ||||
| // SetFont sets the font used to draw text. | ||||
| func (rgc *RasterGraphicContext) SetFont(font *truetype.Font) { | ||||
| 	rgc.current.Font = font | ||||
| } | ||||
|  | ||||
| // GetFont returns the font used to draw text. | ||||
| func (rgc *RasterGraphicContext) GetFont() *truetype.Font { | ||||
| 	return rgc.current.Font | ||||
| } | ||||
|  | ||||
| // SetFontSize sets the font size in points (as in ``a 12 point font''). | ||||
| func (rgc *RasterGraphicContext) SetFontSize(fontSizePoints float64) { | ||||
| 	rgc.current.FontSizePoints = fontSizePoints | ||||
| 	rgc.recalc() | ||||
| } | ||||
|  | ||||
| func (rgc *RasterGraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color) { | ||||
| 	rgc.painter.SetColor(color) | ||||
| 	rasterizer.Rasterize(rgc.painter) | ||||
| 	rasterizer.Clear() | ||||
| 	rgc.current.Path.Clear() | ||||
| } | ||||
|  | ||||
| // Stroke strokes the paths with the color specified by SetStrokeColor | ||||
| func (rgc *RasterGraphicContext) Stroke(paths ...*Path) { | ||||
| 	paths = append(paths, rgc.current.Path) | ||||
| 	rgc.strokeRasterizer.UseNonZeroWinding = true | ||||
|  | ||||
| 	stroker := NewLineStroker(rgc.current.Cap, rgc.current.Join, Transformer{Tr: rgc.current.Tr, Flattener: FtLineBuilder{Adder: rgc.strokeRasterizer}}) | ||||
| 	stroker.HalfLineWidth = rgc.current.LineWidth / 2 | ||||
|  | ||||
| 	var liner Flattener | ||||
| 	if rgc.current.Dash != nil && len(rgc.current.Dash) > 0 { | ||||
| 		liner = NewDashVertexConverter(rgc.current.Dash, rgc.current.DashOffset, stroker) | ||||
| 	} else { | ||||
| 		liner = stroker | ||||
| 	} | ||||
| 	for _, p := range paths { | ||||
| 		Flatten(p, liner, rgc.current.Tr.GetScale()) | ||||
| 	} | ||||
|  | ||||
| 	rgc.paint(rgc.strokeRasterizer, rgc.current.StrokeColor) | ||||
| } | ||||
|  | ||||
| // Fill fills the paths with the color specified by SetFillColor | ||||
| func (rgc *RasterGraphicContext) Fill(paths ...*Path) { | ||||
| 	paths = append(paths, rgc.current.Path) | ||||
| 	rgc.fillRasterizer.UseNonZeroWinding = rgc.current.FillRule == FillRuleWinding | ||||
|  | ||||
| 	flattener := Transformer{Tr: rgc.current.Tr, Flattener: FtLineBuilder{Adder: rgc.fillRasterizer}} | ||||
| 	for _, p := range paths { | ||||
| 		Flatten(p, flattener, rgc.current.Tr.GetScale()) | ||||
| 	} | ||||
|  | ||||
| 	rgc.paint(rgc.fillRasterizer, rgc.current.FillColor) | ||||
| } | ||||
|  | ||||
| // FillStroke first fills the paths and than strokes them | ||||
| func (rgc *RasterGraphicContext) FillStroke(paths ...*Path) { | ||||
| 	paths = append(paths, rgc.current.Path) | ||||
| 	rgc.fillRasterizer.UseNonZeroWinding = rgc.current.FillRule == FillRuleWinding | ||||
| 	rgc.strokeRasterizer.UseNonZeroWinding = true | ||||
|  | ||||
| 	flattener := Transformer{Tr: rgc.current.Tr, Flattener: FtLineBuilder{Adder: rgc.fillRasterizer}} | ||||
|  | ||||
| 	stroker := NewLineStroker(rgc.current.Cap, rgc.current.Join, Transformer{Tr: rgc.current.Tr, Flattener: FtLineBuilder{Adder: rgc.strokeRasterizer}}) | ||||
| 	stroker.HalfLineWidth = rgc.current.LineWidth / 2 | ||||
|  | ||||
| 	var liner Flattener | ||||
| 	if rgc.current.Dash != nil && len(rgc.current.Dash) > 0 { | ||||
| 		liner = NewDashVertexConverter(rgc.current.Dash, rgc.current.DashOffset, stroker) | ||||
| 	} else { | ||||
| 		liner = stroker | ||||
| 	} | ||||
|  | ||||
| 	demux := DemuxFlattener{Flatteners: []Flattener{flattener, liner}} | ||||
| 	for _, p := range paths { | ||||
| 		Flatten(p, demux, rgc.current.Tr.GetScale()) | ||||
| 	} | ||||
|  | ||||
| 	// Fill | ||||
| 	rgc.paint(rgc.fillRasterizer, rgc.current.FillColor) | ||||
| 	// Stroke | ||||
| 	rgc.paint(rgc.strokeRasterizer, rgc.current.StrokeColor) | ||||
| } | ||||
							
								
								
									
										211
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/stack_graphic_context.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										211
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/stack_graphic_context.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,211 @@ | ||||
| package drawing | ||||
|  | ||||
| import ( | ||||
| 	"image" | ||||
| 	"image/color" | ||||
|  | ||||
| 	"github.com/golang/freetype/truetype" | ||||
| ) | ||||
|  | ||||
| // StackGraphicContext is a context that does thngs. | ||||
| type StackGraphicContext struct { | ||||
| 	current *ContextStack | ||||
| } | ||||
|  | ||||
| // ContextStack is a graphic context implementation. | ||||
| type ContextStack struct { | ||||
| 	Tr          Matrix | ||||
| 	Path        *Path | ||||
| 	LineWidth   float64 | ||||
| 	Dash        []float64 | ||||
| 	DashOffset  float64 | ||||
| 	StrokeColor color.Color | ||||
| 	FillColor   color.Color | ||||
| 	FillRule    FillRule | ||||
| 	Cap         LineCap | ||||
| 	Join        LineJoin | ||||
|  | ||||
| 	FontSizePoints float64 | ||||
| 	Font           *truetype.Font | ||||
|  | ||||
| 	Scale float64 | ||||
|  | ||||
| 	Previous *ContextStack | ||||
| } | ||||
|  | ||||
| // NewStackGraphicContext Create a new Graphic context from an image | ||||
| func NewStackGraphicContext() *StackGraphicContext { | ||||
| 	gc := &StackGraphicContext{} | ||||
| 	gc.current = new(ContextStack) | ||||
| 	gc.current.Tr = NewIdentityMatrix() | ||||
| 	gc.current.Path = new(Path) | ||||
| 	gc.current.LineWidth = 1.0 | ||||
| 	gc.current.StrokeColor = image.Black | ||||
| 	gc.current.FillColor = image.White | ||||
| 	gc.current.Cap = RoundCap | ||||
| 	gc.current.FillRule = FillRuleEvenOdd | ||||
| 	gc.current.Join = RoundJoin | ||||
| 	gc.current.FontSizePoints = 10 | ||||
| 	return gc | ||||
| } | ||||
|  | ||||
| // GetMatrixTransform returns the matrix transform. | ||||
| func (gc *StackGraphicContext) GetMatrixTransform() Matrix { | ||||
| 	return gc.current.Tr | ||||
| } | ||||
|  | ||||
| // SetMatrixTransform sets the matrix transform. | ||||
| func (gc *StackGraphicContext) SetMatrixTransform(tr Matrix) { | ||||
| 	gc.current.Tr = tr | ||||
| } | ||||
|  | ||||
| // ComposeMatrixTransform composes a transform into the current transform. | ||||
| func (gc *StackGraphicContext) ComposeMatrixTransform(tr Matrix) { | ||||
| 	gc.current.Tr.Compose(tr) | ||||
| } | ||||
|  | ||||
| // Rotate rotates the matrix transform by an angle in degrees. | ||||
| func (gc *StackGraphicContext) Rotate(angle float64) { | ||||
| 	gc.current.Tr.Rotate(angle) | ||||
| } | ||||
|  | ||||
| // Translate translates a transform. | ||||
| func (gc *StackGraphicContext) Translate(tx, ty float64) { | ||||
| 	gc.current.Tr.Translate(tx, ty) | ||||
| } | ||||
|  | ||||
| // Scale scales a transform. | ||||
| func (gc *StackGraphicContext) Scale(sx, sy float64) { | ||||
| 	gc.current.Tr.Scale(sx, sy) | ||||
| } | ||||
|  | ||||
| // SetStrokeColor sets the stroke color. | ||||
| func (gc *StackGraphicContext) SetStrokeColor(c color.Color) { | ||||
| 	gc.current.StrokeColor = c | ||||
| } | ||||
|  | ||||
| // SetFillColor sets the fill color. | ||||
| func (gc *StackGraphicContext) SetFillColor(c color.Color) { | ||||
| 	gc.current.FillColor = c | ||||
| } | ||||
|  | ||||
| // SetFillRule sets the fill rule. | ||||
| func (gc *StackGraphicContext) SetFillRule(f FillRule) { | ||||
| 	gc.current.FillRule = f | ||||
| } | ||||
|  | ||||
| // SetLineWidth sets the line width. | ||||
| func (gc *StackGraphicContext) SetLineWidth(lineWidth float64) { | ||||
| 	gc.current.LineWidth = lineWidth | ||||
| } | ||||
|  | ||||
| // SetLineCap sets the line cap. | ||||
| func (gc *StackGraphicContext) SetLineCap(cap LineCap) { | ||||
| 	gc.current.Cap = cap | ||||
| } | ||||
|  | ||||
| // SetLineJoin sets the line join. | ||||
| func (gc *StackGraphicContext) SetLineJoin(join LineJoin) { | ||||
| 	gc.current.Join = join | ||||
| } | ||||
|  | ||||
| // SetLineDash sets the line dash. | ||||
| func (gc *StackGraphicContext) SetLineDash(dash []float64, dashOffset float64) { | ||||
| 	gc.current.Dash = dash | ||||
| 	gc.current.DashOffset = dashOffset | ||||
| } | ||||
|  | ||||
| // SetFontSize sets the font size. | ||||
| func (gc *StackGraphicContext) SetFontSize(fontSizePoints float64) { | ||||
| 	gc.current.FontSizePoints = fontSizePoints | ||||
| } | ||||
|  | ||||
| // GetFontSize gets the font size. | ||||
| func (gc *StackGraphicContext) GetFontSize() float64 { | ||||
| 	return gc.current.FontSizePoints | ||||
| } | ||||
|  | ||||
| // SetFont sets the current font. | ||||
| func (gc *StackGraphicContext) SetFont(f *truetype.Font) { | ||||
| 	gc.current.Font = f | ||||
| } | ||||
|  | ||||
| // GetFont returns the font. | ||||
| func (gc *StackGraphicContext) GetFont() *truetype.Font { | ||||
| 	return gc.current.Font | ||||
| } | ||||
|  | ||||
| // BeginPath starts a new path. | ||||
| func (gc *StackGraphicContext) BeginPath() { | ||||
| 	gc.current.Path.Clear() | ||||
| } | ||||
|  | ||||
| // IsEmpty returns if the path is empty. | ||||
| func (gc *StackGraphicContext) IsEmpty() bool { | ||||
| 	return gc.current.Path.IsEmpty() | ||||
| } | ||||
|  | ||||
| // LastPoint returns the last point on the path. | ||||
| func (gc *StackGraphicContext) LastPoint() (x float64, y float64) { | ||||
| 	return gc.current.Path.LastPoint() | ||||
| } | ||||
|  | ||||
| // MoveTo moves the cursor for a path. | ||||
| func (gc *StackGraphicContext) MoveTo(x, y float64) { | ||||
| 	gc.current.Path.MoveTo(x, y) | ||||
| } | ||||
|  | ||||
| // LineTo draws a line. | ||||
| func (gc *StackGraphicContext) LineTo(x, y float64) { | ||||
| 	gc.current.Path.LineTo(x, y) | ||||
| } | ||||
|  | ||||
| // QuadCurveTo draws a quad curve. | ||||
| func (gc *StackGraphicContext) QuadCurveTo(cx, cy, x, y float64) { | ||||
| 	gc.current.Path.QuadCurveTo(cx, cy, x, y) | ||||
| } | ||||
|  | ||||
| // CubicCurveTo draws a cubic curve. | ||||
| func (gc *StackGraphicContext) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) { | ||||
| 	gc.current.Path.CubicCurveTo(cx1, cy1, cx2, cy2, x, y) | ||||
| } | ||||
|  | ||||
| // ArcTo draws an arc. | ||||
| func (gc *StackGraphicContext) ArcTo(cx, cy, rx, ry, startAngle, delta float64) { | ||||
| 	gc.current.Path.ArcTo(cx, cy, rx, ry, startAngle, delta) | ||||
| } | ||||
|  | ||||
| // Close closes a path. | ||||
| func (gc *StackGraphicContext) Close() { | ||||
| 	gc.current.Path.Close() | ||||
| } | ||||
|  | ||||
| // Save pushes a context onto the stack. | ||||
| func (gc *StackGraphicContext) Save() { | ||||
| 	context := new(ContextStack) | ||||
| 	context.FontSizePoints = gc.current.FontSizePoints | ||||
| 	context.Font = gc.current.Font | ||||
| 	context.LineWidth = gc.current.LineWidth | ||||
| 	context.StrokeColor = gc.current.StrokeColor | ||||
| 	context.FillColor = gc.current.FillColor | ||||
| 	context.FillRule = gc.current.FillRule | ||||
| 	context.Dash = gc.current.Dash | ||||
| 	context.DashOffset = gc.current.DashOffset | ||||
| 	context.Cap = gc.current.Cap | ||||
| 	context.Join = gc.current.Join | ||||
| 	context.Path = gc.current.Path.Copy() | ||||
| 	context.Font = gc.current.Font | ||||
| 	context.Scale = gc.current.Scale | ||||
| 	copy(context.Tr[:], gc.current.Tr[:]) | ||||
| 	context.Previous = gc.current | ||||
| 	gc.current = context | ||||
| } | ||||
|  | ||||
| // Restore restores the previous context. | ||||
| func (gc *StackGraphicContext) Restore() { | ||||
| 	if gc.current.Previous != nil { | ||||
| 		oldContext := gc.current | ||||
| 		gc.current = gc.current.Previous | ||||
| 		oldContext.Previous = nil | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										85
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/stroker.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/stroker.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,85 @@ | ||||
| // Copyright 2010 The draw2d Authors. All rights reserved. | ||||
| // created: 13/12/2010 by Laurent Le Goff | ||||
|  | ||||
| package drawing | ||||
|  | ||||
| // NewLineStroker creates a new line stroker. | ||||
| func NewLineStroker(c LineCap, j LineJoin, flattener Flattener) *LineStroker { | ||||
| 	l := new(LineStroker) | ||||
| 	l.Flattener = flattener | ||||
| 	l.HalfLineWidth = 0.5 | ||||
| 	l.Cap = c | ||||
| 	l.Join = j | ||||
| 	return l | ||||
| } | ||||
|  | ||||
| // LineStroker draws the stroke portion of a line. | ||||
| type LineStroker struct { | ||||
| 	Flattener     Flattener | ||||
| 	HalfLineWidth float64 | ||||
| 	Cap           LineCap | ||||
| 	Join          LineJoin | ||||
| 	vertices      []float64 | ||||
| 	rewind        []float64 | ||||
| 	x, y, nx, ny  float64 | ||||
| } | ||||
|  | ||||
| // MoveTo implements the path builder interface. | ||||
| func (l *LineStroker) MoveTo(x, y float64) { | ||||
| 	l.x, l.y = x, y | ||||
| } | ||||
|  | ||||
| // LineTo implements the path builder interface. | ||||
| func (l *LineStroker) LineTo(x, y float64) { | ||||
| 	l.line(l.x, l.y, x, y) | ||||
| } | ||||
|  | ||||
| // LineJoin implements the path builder interface. | ||||
| func (l *LineStroker) LineJoin() {} | ||||
|  | ||||
| func (l *LineStroker) line(x1, y1, x2, y2 float64) { | ||||
| 	dx := (x2 - x1) | ||||
| 	dy := (y2 - y1) | ||||
| 	d := vectorDistance(dx, dy) | ||||
| 	if d != 0 { | ||||
| 		nx := dy * l.HalfLineWidth / d | ||||
| 		ny := -(dx * l.HalfLineWidth / d) | ||||
| 		l.appendVertex(x1+nx, y1+ny, x2+nx, y2+ny, x1-nx, y1-ny, x2-nx, y2-ny) | ||||
| 		l.x, l.y, l.nx, l.ny = x2, y2, nx, ny | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Close implements the path builder interface. | ||||
| func (l *LineStroker) Close() { | ||||
| 	if len(l.vertices) > 1 { | ||||
| 		l.appendVertex(l.vertices[0], l.vertices[1], l.rewind[0], l.rewind[1]) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // End implements the path builder interface. | ||||
| func (l *LineStroker) End() { | ||||
| 	if len(l.vertices) > 1 { | ||||
| 		l.Flattener.MoveTo(l.vertices[0], l.vertices[1]) | ||||
| 		for i, j := 2, 3; j < len(l.vertices); i, j = i+2, j+2 { | ||||
| 			l.Flattener.LineTo(l.vertices[i], l.vertices[j]) | ||||
| 		} | ||||
| 	} | ||||
| 	for i, j := len(l.rewind)-2, len(l.rewind)-1; j > 0; i, j = i-2, j-2 { | ||||
| 		l.Flattener.LineTo(l.rewind[i], l.rewind[j]) | ||||
| 	} | ||||
| 	if len(l.vertices) > 1 { | ||||
| 		l.Flattener.LineTo(l.vertices[0], l.vertices[1]) | ||||
| 	} | ||||
| 	l.Flattener.End() | ||||
| 	// reinit vertices | ||||
| 	l.vertices = l.vertices[0:0] | ||||
| 	l.rewind = l.rewind[0:0] | ||||
| 	l.x, l.y, l.nx, l.ny = 0, 0, 0, 0 | ||||
|  | ||||
| } | ||||
|  | ||||
| func (l *LineStroker) appendVertex(vertices ...float64) { | ||||
| 	s := len(vertices) / 2 | ||||
| 	l.vertices = append(l.vertices, vertices[:s]...) | ||||
| 	l.rewind = append(l.rewind, vertices[s:]...) | ||||
| } | ||||
							
								
								
									
										67
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/text.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/text.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,67 @@ | ||||
| package drawing | ||||
|  | ||||
| import ( | ||||
| 	"github.com/golang/freetype/truetype" | ||||
| 	"golang.org/x/image/math/fixed" | ||||
| ) | ||||
|  | ||||
| // DrawContour draws the given closed contour at the given sub-pixel offset. | ||||
| func DrawContour(path PathBuilder, ps []truetype.Point, dx, dy float64) { | ||||
| 	if len(ps) == 0 { | ||||
| 		return | ||||
| 	} | ||||
| 	startX, startY := pointToF64Point(ps[0]) | ||||
| 	path.MoveTo(startX+dx, startY+dy) | ||||
| 	q0X, q0Y, on0 := startX, startY, true | ||||
| 	for _, p := range ps[1:] { | ||||
| 		qX, qY := pointToF64Point(p) | ||||
| 		on := p.Flags&0x01 != 0 | ||||
| 		if on { | ||||
| 			if on0 { | ||||
| 				path.LineTo(qX+dx, qY+dy) | ||||
| 			} else { | ||||
| 				path.QuadCurveTo(q0X+dx, q0Y+dy, qX+dx, qY+dy) | ||||
| 			} | ||||
| 		} else if !on0 { | ||||
| 			midX := (q0X + qX) / 2 | ||||
| 			midY := (q0Y + qY) / 2 | ||||
| 			path.QuadCurveTo(q0X+dx, q0Y+dy, midX+dx, midY+dy) | ||||
| 		} | ||||
| 		q0X, q0Y, on0 = qX, qY, on | ||||
| 	} | ||||
| 	// Close the curve. | ||||
| 	if on0 { | ||||
| 		path.LineTo(startX+dx, startY+dy) | ||||
| 	} else { | ||||
| 		path.QuadCurveTo(q0X+dx, q0Y+dy, startX+dx, startY+dy) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // FontExtents contains font metric information. | ||||
| type FontExtents struct { | ||||
| 	// Ascent is the distance that the text | ||||
| 	// extends above the baseline. | ||||
| 	Ascent float64 | ||||
|  | ||||
| 	// Descent is the distance that the text | ||||
| 	// extends below the baseline.  The descent | ||||
| 	// is given as a negative value. | ||||
| 	Descent float64 | ||||
|  | ||||
| 	// Height is the distance from the lowest | ||||
| 	// descending point to the highest ascending | ||||
| 	// point. | ||||
| 	Height float64 | ||||
| } | ||||
|  | ||||
| // Extents returns the FontExtents for a font. | ||||
| // TODO needs to read this https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#intro | ||||
| func Extents(font *truetype.Font, size float64) FontExtents { | ||||
| 	bounds := font.Bounds(fixed.Int26_6(font.FUnitsPerEm())) | ||||
| 	scale := size / float64(font.FUnitsPerEm()) | ||||
| 	return FontExtents{ | ||||
| 		Ascent:  float64(bounds.Max.Y) * scale, | ||||
| 		Descent: float64(bounds.Min.Y) * scale, | ||||
| 		Height:  float64(bounds.Max.Y-bounds.Min.Y) * scale, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										39
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/transformer.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/transformer.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,39 @@ | ||||
| // Copyright 2010 The draw2d Authors. All rights reserved. | ||||
| // created: 13/12/2010 by Laurent Le Goff | ||||
|  | ||||
| package drawing | ||||
|  | ||||
| // Transformer apply the Matrix transformation tr | ||||
| type Transformer struct { | ||||
| 	Tr        Matrix | ||||
| 	Flattener Flattener | ||||
| } | ||||
|  | ||||
| // MoveTo implements the path builder interface. | ||||
| func (t Transformer) MoveTo(x, y float64) { | ||||
| 	u := x*t.Tr[0] + y*t.Tr[2] + t.Tr[4] | ||||
| 	v := x*t.Tr[1] + y*t.Tr[3] + t.Tr[5] | ||||
| 	t.Flattener.MoveTo(u, v) | ||||
| } | ||||
|  | ||||
| // LineTo implements the path builder interface. | ||||
| func (t Transformer) LineTo(x, y float64) { | ||||
| 	u := x*t.Tr[0] + y*t.Tr[2] + t.Tr[4] | ||||
| 	v := x*t.Tr[1] + y*t.Tr[3] + t.Tr[5] | ||||
| 	t.Flattener.LineTo(u, v) | ||||
| } | ||||
|  | ||||
| // LineJoin implements the path builder interface. | ||||
| func (t Transformer) LineJoin() { | ||||
| 	t.Flattener.LineJoin() | ||||
| } | ||||
|  | ||||
| // Close implements the path builder interface. | ||||
| func (t Transformer) Close() { | ||||
| 	t.Flattener.Close() | ||||
| } | ||||
|  | ||||
| // End implements the path builder interface. | ||||
| func (t Transformer) End() { | ||||
| 	t.Flattener.End() | ||||
| } | ||||
							
								
								
									
										68
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/util.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								vendor/github.com/wcharczuk/go-chart/v2/drawing/util.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,68 @@ | ||||
| package drawing | ||||
|  | ||||
| import ( | ||||
| 	"math" | ||||
|  | ||||
| 	"golang.org/x/image/math/fixed" | ||||
|  | ||||
| 	"github.com/golang/freetype/raster" | ||||
| 	"github.com/golang/freetype/truetype" | ||||
| ) | ||||
|  | ||||
| // PixelsToPoints returns the points for a given number of pixels at a DPI. | ||||
| func PixelsToPoints(dpi, pixels float64) (points float64) { | ||||
| 	points = (pixels * 72.0) / dpi | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // PointsToPixels returns the pixels for a given number of points at a DPI. | ||||
| func PointsToPixels(dpi, points float64) (pixels float64) { | ||||
| 	pixels = (points * dpi) / 72.0 | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func abs(i int) int { | ||||
| 	if i < 0 { | ||||
| 		return -i | ||||
| 	} | ||||
| 	return i | ||||
| } | ||||
|  | ||||
| func distance(x1, y1, x2, y2 float64) float64 { | ||||
| 	return vectorDistance(x2-x1, y2-y1) | ||||
| } | ||||
|  | ||||
| func vectorDistance(dx, dy float64) float64 { | ||||
| 	return float64(math.Sqrt(dx*dx + dy*dy)) | ||||
| } | ||||
|  | ||||
| func toFtCap(c LineCap) raster.Capper { | ||||
| 	switch c { | ||||
| 	case RoundCap: | ||||
| 		return raster.RoundCapper | ||||
| 	case ButtCap: | ||||
| 		return raster.ButtCapper | ||||
| 	case SquareCap: | ||||
| 		return raster.SquareCapper | ||||
| 	} | ||||
| 	return raster.RoundCapper | ||||
| } | ||||
|  | ||||
| func toFtJoin(j LineJoin) raster.Joiner { | ||||
| 	switch j { | ||||
| 	case RoundJoin: | ||||
| 		return raster.RoundJoiner | ||||
| 	case BevelJoin: | ||||
| 		return raster.BevelJoiner | ||||
| 	} | ||||
| 	return raster.RoundJoiner | ||||
| } | ||||
|  | ||||
| func pointToF64Point(p truetype.Point) (x, y float64) { | ||||
| 	return fUnitsToFloat64(p.X), -fUnitsToFloat64(p.Y) | ||||
| } | ||||
|  | ||||
| func fUnitsToFloat64(x fixed.Int26_6) float64 { | ||||
| 	scaled := x << 2 | ||||
| 	return float64(scaled/256) + float64(scaled%256)/256.0 | ||||
| } | ||||
							
								
								
									
										131
									
								
								vendor/github.com/wcharczuk/go-chart/v2/ema_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								vendor/github.com/wcharczuk/go-chart/v2/ema_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,131 @@ | ||||
| package chart | ||||
|  | ||||
| import "fmt" | ||||
|  | ||||
| const ( | ||||
| 	// DefaultEMAPeriod is the default EMA period used in the sigma calculation. | ||||
| 	DefaultEMAPeriod = 12 | ||||
| ) | ||||
|  | ||||
| // Interface Assertions. | ||||
| var ( | ||||
| 	_ Series              = (*EMASeries)(nil) | ||||
| 	_ FirstValuesProvider = (*EMASeries)(nil) | ||||
| 	_ LastValuesProvider  = (*EMASeries)(nil) | ||||
| ) | ||||
|  | ||||
| // EMASeries is a computed series. | ||||
| type EMASeries struct { | ||||
| 	Name  string | ||||
| 	Style Style | ||||
| 	YAxis YAxisType | ||||
|  | ||||
| 	Period      int | ||||
| 	InnerSeries ValuesProvider | ||||
|  | ||||
| 	cache []float64 | ||||
| } | ||||
|  | ||||
| // GetName returns the name of the time series. | ||||
| func (ema EMASeries) GetName() string { | ||||
| 	return ema.Name | ||||
| } | ||||
|  | ||||
| // GetStyle returns the line style. | ||||
| func (ema EMASeries) GetStyle() Style { | ||||
| 	return ema.Style | ||||
| } | ||||
|  | ||||
| // GetYAxis returns which YAxis the series draws on. | ||||
| func (ema EMASeries) GetYAxis() YAxisType { | ||||
| 	return ema.YAxis | ||||
| } | ||||
|  | ||||
| // GetPeriod returns the window size. | ||||
| func (ema EMASeries) GetPeriod() int { | ||||
| 	if ema.Period == 0 { | ||||
| 		return DefaultEMAPeriod | ||||
| 	} | ||||
| 	return ema.Period | ||||
| } | ||||
|  | ||||
| // Len returns the number of elements in the series. | ||||
| func (ema EMASeries) Len() int { | ||||
| 	return ema.InnerSeries.Len() | ||||
| } | ||||
|  | ||||
| // GetSigma returns the smoothing factor for the serise. | ||||
| func (ema EMASeries) GetSigma() float64 { | ||||
| 	return 2.0 / (float64(ema.GetPeriod()) + 1) | ||||
| } | ||||
|  | ||||
| // GetValues gets a value at a given index. | ||||
| func (ema *EMASeries) GetValues(index int) (x, y float64) { | ||||
| 	if ema.InnerSeries == nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if len(ema.cache) == 0 { | ||||
| 		ema.ensureCachedValues() | ||||
| 	} | ||||
| 	vx, _ := ema.InnerSeries.GetValues(index) | ||||
| 	x = vx | ||||
| 	y = ema.cache[index] | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // GetFirstValues computes the first moving average value. | ||||
| func (ema *EMASeries) GetFirstValues() (x, y float64) { | ||||
| 	if ema.InnerSeries == nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if len(ema.cache) == 0 { | ||||
| 		ema.ensureCachedValues() | ||||
| 	} | ||||
| 	x, _ = ema.InnerSeries.GetValues(0) | ||||
| 	y = ema.cache[0] | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // GetLastValues computes the last moving average value but walking back window size samples, | ||||
| // and recomputing the last moving average chunk. | ||||
| func (ema *EMASeries) GetLastValues() (x, y float64) { | ||||
| 	if ema.InnerSeries == nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if len(ema.cache) == 0 { | ||||
| 		ema.ensureCachedValues() | ||||
| 	} | ||||
| 	lastIndex := ema.InnerSeries.Len() - 1 | ||||
| 	x, _ = ema.InnerSeries.GetValues(lastIndex) | ||||
| 	y = ema.cache[lastIndex] | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (ema *EMASeries) ensureCachedValues() { | ||||
| 	seriesLength := ema.InnerSeries.Len() | ||||
| 	ema.cache = make([]float64, seriesLength) | ||||
| 	sigma := ema.GetSigma() | ||||
| 	for x := 0; x < seriesLength; x++ { | ||||
| 		_, y := ema.InnerSeries.GetValues(x) | ||||
| 		if x == 0 { | ||||
| 			ema.cache[x] = y | ||||
| 			continue | ||||
| 		} | ||||
| 		previousEMA := ema.cache[x-1] | ||||
| 		ema.cache[x] = ((y - previousEMA) * sigma) + previousEMA | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Render renders the series. | ||||
| func (ema *EMASeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { | ||||
| 	style := ema.Style.InheritFrom(defaults) | ||||
| 	Draw.LineSeries(r, canvasBox, xrange, yrange, style, ema) | ||||
| } | ||||
|  | ||||
| // Validate validates the series. | ||||
| func (ema *EMASeries) Validate() error { | ||||
| 	if ema.InnerSeries == nil { | ||||
| 		return fmt.Errorf("ema series requires InnerSeries to be set") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										49
									
								
								vendor/github.com/wcharczuk/go-chart/v2/fileutil.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								vendor/github.com/wcharczuk/go-chart/v2/fileutil.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,49 @@ | ||||
| package chart | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"io" | ||||
| 	"os" | ||||
| ) | ||||
|  | ||||
| // ReadLines reads a file and calls the handler for each line. | ||||
| func ReadLines(filePath string, handler func(string) error) error { | ||||
| 	f, err := os.Open(filePath) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer f.Close() | ||||
|  | ||||
| 	scanner := bufio.NewScanner(f) | ||||
| 	for scanner.Scan() { | ||||
| 		line := scanner.Text() | ||||
| 		err = handler(line) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // ReadChunks reads a file in `chunkSize` pieces, dispatched to the handler. | ||||
| func ReadChunks(filePath string, chunkSize int, handler func([]byte) error) error { | ||||
| 	f, err := os.Open(filePath) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer f.Close() | ||||
|  | ||||
| 	chunk := make([]byte, chunkSize) | ||||
| 	for { | ||||
| 		readBytes, err := f.Read(chunk) | ||||
| 		if err == io.EOF { | ||||
| 			break | ||||
| 		} | ||||
| 		readData := chunk[:readBytes] | ||||
| 		err = handler(readData) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										37
									
								
								vendor/github.com/wcharczuk/go-chart/v2/first_value_annotation.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								vendor/github.com/wcharczuk/go-chart/v2/first_value_annotation.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | ||||
| package chart | ||||
|  | ||||
| import "fmt" | ||||
|  | ||||
| // FirstValueAnnotation returns an annotation series of just the first value of a value provider as an annotation. | ||||
| func FirstValueAnnotation(innerSeries ValuesProvider, vfs ...ValueFormatter) AnnotationSeries { | ||||
| 	var vf ValueFormatter | ||||
| 	if len(vfs) > 0 { | ||||
| 		vf = vfs[0] | ||||
| 	} else if typed, isTyped := innerSeries.(ValueFormatterProvider); isTyped { | ||||
| 		_, vf = typed.GetValueFormatters() | ||||
| 	} else { | ||||
| 		vf = FloatValueFormatter | ||||
| 	} | ||||
|  | ||||
| 	var firstValue Value2 | ||||
| 	if typed, isTyped := innerSeries.(FirstValuesProvider); isTyped { | ||||
| 		firstValue.XValue, firstValue.YValue = typed.GetFirstValues() | ||||
| 		firstValue.Label = vf(firstValue.YValue) | ||||
| 	} else { | ||||
| 		firstValue.XValue, firstValue.YValue = innerSeries.GetValues(0) | ||||
| 		firstValue.Label = vf(firstValue.YValue) | ||||
| 	} | ||||
|  | ||||
| 	var seriesName string | ||||
| 	var seriesStyle Style | ||||
| 	if typed, isTyped := innerSeries.(Series); isTyped { | ||||
| 		seriesName = fmt.Sprintf("%s - First Value", typed.GetName()) | ||||
| 		seriesStyle = typed.GetStyle() | ||||
| 	} | ||||
|  | ||||
| 	return AnnotationSeries{ | ||||
| 		Name:        seriesName, | ||||
| 		Style:       seriesStyle, | ||||
| 		Annotations: []Value2{firstValue}, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										29
									
								
								vendor/github.com/wcharczuk/go-chart/v2/font.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								vendor/github.com/wcharczuk/go-chart/v2/font.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| package chart | ||||
|  | ||||
| import ( | ||||
| 	"sync" | ||||
|  | ||||
| 	"github.com/golang/freetype/truetype" | ||||
| 	"github.com/wcharczuk/go-chart/v2/roboto" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	_defaultFontLock sync.Mutex | ||||
| 	_defaultFont     *truetype.Font | ||||
| ) | ||||
|  | ||||
| // GetDefaultFont returns the default font (Roboto-Medium). | ||||
| func GetDefaultFont() (*truetype.Font, error) { | ||||
| 	if _defaultFont == nil { | ||||
| 		_defaultFontLock.Lock() | ||||
| 		defer _defaultFontLock.Unlock() | ||||
| 		if _defaultFont == nil { | ||||
| 			font, err := truetype.Parse(roboto.Roboto) | ||||
| 			if err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 			_defaultFont = font | ||||
| 		} | ||||
| 	} | ||||
| 	return _defaultFont, nil | ||||
| } | ||||
							
								
								
									
										8
									
								
								vendor/github.com/wcharczuk/go-chart/v2/go.mod
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								vendor/github.com/wcharczuk/go-chart/v2/go.mod
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| module github.com/wcharczuk/go-chart/v2 | ||||
|  | ||||
| go 1.15 | ||||
|  | ||||
| require ( | ||||
| 	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 | ||||
| 	golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 | ||||
| ) | ||||
							
								
								
									
										5
									
								
								vendor/github.com/wcharczuk/go-chart/v2/go.sum
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								vendor/github.com/wcharczuk/go-chart/v2/go.sum
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= | ||||
| github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= | ||||
| golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM= | ||||
| golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= | ||||
| golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
							
								
								
									
										72
									
								
								vendor/github.com/wcharczuk/go-chart/v2/grid_line.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								vendor/github.com/wcharczuk/go-chart/v2/grid_line.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,72 @@ | ||||
| package chart | ||||
|  | ||||
| // GridLineProvider is a type that provides grid lines. | ||||
| type GridLineProvider interface { | ||||
| 	GetGridLines(ticks []Tick, isVertical bool, majorStyle, minorStyle Style) []GridLine | ||||
| } | ||||
|  | ||||
| // GridLine is a line on a graph canvas. | ||||
| type GridLine struct { | ||||
| 	IsMinor bool | ||||
| 	Style   Style | ||||
| 	Value   float64 | ||||
| } | ||||
|  | ||||
| // Major returns if the gridline is a `major` line. | ||||
| func (gl GridLine) Major() bool { | ||||
| 	return !gl.IsMinor | ||||
| } | ||||
|  | ||||
| // Minor returns if the gridline is a `minor` line. | ||||
| func (gl GridLine) Minor() bool { | ||||
| 	return gl.IsMinor | ||||
| } | ||||
|  | ||||
| // Render renders the gridline | ||||
| func (gl GridLine) Render(r Renderer, canvasBox Box, ra Range, isVertical bool, defaults Style) { | ||||
| 	r.SetStrokeColor(gl.Style.GetStrokeColor(defaults.GetStrokeColor())) | ||||
| 	r.SetStrokeWidth(gl.Style.GetStrokeWidth(defaults.GetStrokeWidth())) | ||||
| 	r.SetStrokeDashArray(gl.Style.GetStrokeDashArray(defaults.GetStrokeDashArray())) | ||||
|  | ||||
| 	if isVertical { | ||||
| 		lineLeft := canvasBox.Left + ra.Translate(gl.Value) | ||||
| 		lineBottom := canvasBox.Bottom | ||||
| 		lineTop := canvasBox.Top | ||||
|  | ||||
| 		r.MoveTo(lineLeft, lineBottom) | ||||
| 		r.LineTo(lineLeft, lineTop) | ||||
| 		r.Stroke() | ||||
| 	} else { | ||||
| 		lineLeft := canvasBox.Left | ||||
| 		lineRight := canvasBox.Right | ||||
| 		lineHeight := canvasBox.Bottom - ra.Translate(gl.Value) | ||||
|  | ||||
| 		r.MoveTo(lineLeft, lineHeight) | ||||
| 		r.LineTo(lineRight, lineHeight) | ||||
| 		r.Stroke() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GenerateGridLines generates grid lines. | ||||
| func GenerateGridLines(ticks []Tick, majorStyle, minorStyle Style) []GridLine { | ||||
| 	var gl []GridLine | ||||
| 	isMinor := false | ||||
|  | ||||
| 	if len(ticks) < 3 { | ||||
| 		return gl | ||||
| 	} | ||||
|  | ||||
| 	for _, t := range ticks[1 : len(ticks)-1] { | ||||
| 		s := majorStyle | ||||
| 		if isMinor { | ||||
| 			s = minorStyle | ||||
| 		} | ||||
| 		gl = append(gl, GridLine{ | ||||
| 			Style:   s, | ||||
| 			IsMinor: isMinor, | ||||
| 			Value:   t.Value, | ||||
| 		}) | ||||
| 		isMinor = !isMinor | ||||
| 	} | ||||
| 	return gl | ||||
| } | ||||
							
								
								
									
										67
									
								
								vendor/github.com/wcharczuk/go-chart/v2/histogram_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								vendor/github.com/wcharczuk/go-chart/v2/histogram_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,67 @@ | ||||
| package chart | ||||
|  | ||||
| import "fmt" | ||||
|  | ||||
| // HistogramSeries is a special type of series that draws as a histogram. | ||||
| // Some peculiarities; it will always be lower bounded at 0 (at the very least). | ||||
| // This may alter ranges a bit and generally you want to put a histogram series on it's own y-axis. | ||||
| type HistogramSeries struct { | ||||
| 	Name        string | ||||
| 	Style       Style | ||||
| 	YAxis       YAxisType | ||||
| 	InnerSeries ValuesProvider | ||||
| } | ||||
|  | ||||
| // GetName implements Series.GetName. | ||||
| func (hs HistogramSeries) GetName() string { | ||||
| 	return hs.Name | ||||
| } | ||||
|  | ||||
| // GetStyle implements Series.GetStyle. | ||||
| func (hs HistogramSeries) GetStyle() Style { | ||||
| 	return hs.Style | ||||
| } | ||||
|  | ||||
| // GetYAxis returns which yaxis the series is mapped to. | ||||
| func (hs HistogramSeries) GetYAxis() YAxisType { | ||||
| 	return hs.YAxis | ||||
| } | ||||
|  | ||||
| // Len implements BoundedValuesProvider.Len. | ||||
| func (hs HistogramSeries) Len() int { | ||||
| 	return hs.InnerSeries.Len() | ||||
| } | ||||
|  | ||||
| // GetValues implements ValuesProvider.GetValues. | ||||
| func (hs HistogramSeries) GetValues(index int) (x, y float64) { | ||||
| 	return hs.InnerSeries.GetValues(index) | ||||
| } | ||||
|  | ||||
| // GetBoundedValues implements BoundedValuesProvider.GetBoundedValue | ||||
| func (hs HistogramSeries) GetBoundedValues(index int) (x, y1, y2 float64) { | ||||
| 	vx, vy := hs.InnerSeries.GetValues(index) | ||||
|  | ||||
| 	x = vx | ||||
|  | ||||
| 	if vy > 0 { | ||||
| 		y1 = vy | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	y2 = vy | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Render implements Series.Render. | ||||
| func (hs HistogramSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { | ||||
| 	style := hs.Style.InheritFrom(defaults) | ||||
| 	Draw.HistogramSeries(r, canvasBox, xrange, yrange, style, hs) | ||||
| } | ||||
|  | ||||
| // Validate validates the series. | ||||
| func (hs HistogramSeries) Validate() error { | ||||
| 	if hs.InnerSeries == nil { | ||||
| 		return fmt.Errorf("histogram series requires InnerSeries to be set") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										42
									
								
								vendor/github.com/wcharczuk/go-chart/v2/image_writer.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								vendor/github.com/wcharczuk/go-chart/v2/image_writer.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | ||||
| package chart | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"errors" | ||||
| 	"image" | ||||
| 	"image/png" | ||||
| ) | ||||
|  | ||||
| // RGBACollector is a render target for a chart. | ||||
| type RGBACollector interface { | ||||
| 	SetRGBA(i *image.RGBA) | ||||
| } | ||||
|  | ||||
| // ImageWriter is a special type of io.Writer that produces a final image. | ||||
| type ImageWriter struct { | ||||
| 	rgba     *image.RGBA | ||||
| 	contents *bytes.Buffer | ||||
| } | ||||
|  | ||||
| func (ir *ImageWriter) Write(buffer []byte) (int, error) { | ||||
| 	if ir.contents == nil { | ||||
| 		ir.contents = bytes.NewBuffer([]byte{}) | ||||
| 	} | ||||
| 	return ir.contents.Write(buffer) | ||||
| } | ||||
|  | ||||
| // SetRGBA sets a raw version of the image. | ||||
| func (ir *ImageWriter) SetRGBA(i *image.RGBA) { | ||||
| 	ir.rgba = i | ||||
| } | ||||
|  | ||||
| // Image returns an *image.Image for the result. | ||||
| func (ir *ImageWriter) Image() (image.Image, error) { | ||||
| 	if ir.rgba != nil { | ||||
| 		return ir.rgba, nil | ||||
| 	} | ||||
| 	if ir.contents != nil && ir.contents.Len() > 0 { | ||||
| 		return png.Decode(ir.contents) | ||||
| 	} | ||||
| 	return nil, errors.New("no valid sources for image data, cannot continue") | ||||
| } | ||||
							
								
								
									
										33
									
								
								vendor/github.com/wcharczuk/go-chart/v2/jet.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								vendor/github.com/wcharczuk/go-chart/v2/jet.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | ||||
| package chart | ||||
|  | ||||
| import "github.com/wcharczuk/go-chart/v2/drawing" | ||||
|  | ||||
| // Jet is a color map provider based on matlab's jet color map. | ||||
| func Jet(v, vmin, vmax float64) drawing.Color { | ||||
| 	c := drawing.Color{R: 0xff, G: 0xff, B: 0xff, A: 0xff} // white | ||||
| 	var dv float64 | ||||
|  | ||||
| 	if v < vmin { | ||||
| 		v = vmin | ||||
| 	} | ||||
| 	if v > vmax { | ||||
| 		v = vmax | ||||
| 	} | ||||
| 	dv = vmax - vmin | ||||
|  | ||||
| 	if v < (vmin + 0.25*dv) { | ||||
| 		c.R = 0 | ||||
| 		c.G = drawing.ColorChannelFromFloat(4 * (v - vmin) / dv) | ||||
| 	} else if v < (vmin + 0.5*dv) { | ||||
| 		c.R = 0 | ||||
| 		c.B = drawing.ColorChannelFromFloat(1 + 4*(vmin+0.25*dv-v)/dv) | ||||
| 	} else if v < (vmin + 0.75*dv) { | ||||
| 		c.R = drawing.ColorChannelFromFloat(4 * (v - vmin - 0.5*dv) / dv) | ||||
| 		c.B = 0 | ||||
| 	} else { | ||||
| 		c.G = drawing.ColorChannelFromFloat(1 + 4*(vmin+0.75*dv-v)/dv) | ||||
| 		c.B = 0 | ||||
| 	} | ||||
|  | ||||
| 	return c | ||||
| } | ||||
							
								
								
									
										37
									
								
								vendor/github.com/wcharczuk/go-chart/v2/last_value_annotation_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								vendor/github.com/wcharczuk/go-chart/v2/last_value_annotation_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | ||||
| package chart | ||||
|  | ||||
| import "fmt" | ||||
|  | ||||
| // LastValueAnnotationSeries returns an annotation series of just the last value of a value provider. | ||||
| func LastValueAnnotationSeries(innerSeries ValuesProvider, vfs ...ValueFormatter) AnnotationSeries { | ||||
| 	var vf ValueFormatter | ||||
| 	if len(vfs) > 0 { | ||||
| 		vf = vfs[0] | ||||
| 	} else if typed, isTyped := innerSeries.(ValueFormatterProvider); isTyped { | ||||
| 		_, vf = typed.GetValueFormatters() | ||||
| 	} else { | ||||
| 		vf = FloatValueFormatter | ||||
| 	} | ||||
|  | ||||
| 	var lastValue Value2 | ||||
| 	if typed, isTyped := innerSeries.(LastValuesProvider); isTyped { | ||||
| 		lastValue.XValue, lastValue.YValue = typed.GetLastValues() | ||||
| 		lastValue.Label = vf(lastValue.YValue) | ||||
| 	} else { | ||||
| 		lastValue.XValue, lastValue.YValue = innerSeries.GetValues(innerSeries.Len() - 1) | ||||
| 		lastValue.Label = vf(lastValue.YValue) | ||||
| 	} | ||||
|  | ||||
| 	var seriesName string | ||||
| 	var seriesStyle Style | ||||
| 	if typed, isTyped := innerSeries.(Series); isTyped { | ||||
| 		seriesName = fmt.Sprintf("%s - Last Value", typed.GetName()) | ||||
| 		seriesStyle = typed.GetStyle() | ||||
| 	} | ||||
|  | ||||
| 	return AnnotationSeries{ | ||||
| 		Name:        seriesName, | ||||
| 		Style:       seriesStyle, | ||||
| 		Annotations: []Value2{lastValue}, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										331
									
								
								vendor/github.com/wcharczuk/go-chart/v2/legend.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										331
									
								
								vendor/github.com/wcharczuk/go-chart/v2/legend.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,331 @@ | ||||
| package chart | ||||
|  | ||||
| import ( | ||||
| 	"github.com/wcharczuk/go-chart/v2/drawing" | ||||
| ) | ||||
|  | ||||
| // Legend returns a legend renderable function. | ||||
| func Legend(c *Chart, userDefaults ...Style) Renderable { | ||||
| 	return func(r Renderer, cb Box, chartDefaults Style) { | ||||
| 		legendDefaults := Style{ | ||||
| 			FillColor:   drawing.ColorWhite, | ||||
| 			FontColor:   DefaultTextColor, | ||||
| 			FontSize:    8.0, | ||||
| 			StrokeColor: DefaultAxisColor, | ||||
| 			StrokeWidth: DefaultAxisLineWidth, | ||||
| 		} | ||||
|  | ||||
| 		var legendStyle Style | ||||
| 		if len(userDefaults) > 0 { | ||||
| 			legendStyle = userDefaults[0].InheritFrom(chartDefaults.InheritFrom(legendDefaults)) | ||||
| 		} else { | ||||
| 			legendStyle = chartDefaults.InheritFrom(legendDefaults) | ||||
| 		} | ||||
|  | ||||
| 		// DEFAULTS | ||||
| 		legendPadding := Box{ | ||||
| 			Top:    5, | ||||
| 			Left:   5, | ||||
| 			Right:  5, | ||||
| 			Bottom: 5, | ||||
| 		} | ||||
| 		lineTextGap := 5 | ||||
| 		lineLengthMinimum := 25 | ||||
|  | ||||
| 		var labels []string | ||||
| 		var lines []Style | ||||
| 		for index, s := range c.Series { | ||||
| 			if !s.GetStyle().Hidden { | ||||
| 				if _, isAnnotationSeries := s.(AnnotationSeries); !isAnnotationSeries { | ||||
| 					labels = append(labels, s.GetName()) | ||||
| 					lines = append(lines, s.GetStyle().InheritFrom(c.styleDefaultsSeries(index))) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		legend := Box{ | ||||
| 			Top:  cb.Top, | ||||
| 			Left: cb.Left, | ||||
| 			// bottom and right will be sized by the legend content + relevant padding. | ||||
| 		} | ||||
|  | ||||
| 		legendContent := Box{ | ||||
| 			Top:    legend.Top + legendPadding.Top, | ||||
| 			Left:   legend.Left + legendPadding.Left, | ||||
| 			Right:  legend.Left + legendPadding.Left, | ||||
| 			Bottom: legend.Top + legendPadding.Top, | ||||
| 		} | ||||
|  | ||||
| 		legendStyle.GetTextOptions().WriteToRenderer(r) | ||||
|  | ||||
| 		// measure | ||||
| 		labelCount := 0 | ||||
| 		for x := 0; x < len(labels); x++ { | ||||
| 			if len(labels[x]) > 0 { | ||||
| 				tb := r.MeasureText(labels[x]) | ||||
| 				if labelCount > 0 { | ||||
| 					legendContent.Bottom += DefaultMinimumTickVerticalSpacing | ||||
| 				} | ||||
| 				legendContent.Bottom += tb.Height() | ||||
| 				right := legendContent.Left + tb.Width() + lineTextGap + lineLengthMinimum | ||||
| 				legendContent.Right = MaxInt(legendContent.Right, right) | ||||
| 				labelCount++ | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		legend = legend.Grow(legendContent) | ||||
| 		legend.Right = legendContent.Right + legendPadding.Right | ||||
| 		legend.Bottom = legendContent.Bottom + legendPadding.Bottom | ||||
|  | ||||
| 		Draw.Box(r, legend, legendStyle) | ||||
|  | ||||
| 		legendStyle.GetTextOptions().WriteToRenderer(r) | ||||
|  | ||||
| 		ycursor := legendContent.Top | ||||
| 		tx := legendContent.Left | ||||
| 		legendCount := 0 | ||||
| 		var label string | ||||
| 		for x := 0; x < len(labels); x++ { | ||||
| 			label = labels[x] | ||||
| 			if len(label) > 0 { | ||||
| 				if legendCount > 0 { | ||||
| 					ycursor += DefaultMinimumTickVerticalSpacing | ||||
| 				} | ||||
|  | ||||
| 				tb := r.MeasureText(label) | ||||
|  | ||||
| 				ty := ycursor + tb.Height() | ||||
| 				r.Text(label, tx, ty) | ||||
|  | ||||
| 				th2 := tb.Height() >> 1 | ||||
|  | ||||
| 				lx := tx + tb.Width() + lineTextGap | ||||
| 				ly := ty - th2 | ||||
| 				lx2 := legendContent.Right - legendPadding.Right | ||||
|  | ||||
| 				r.SetStrokeColor(lines[x].GetStrokeColor()) | ||||
| 				r.SetStrokeWidth(lines[x].GetStrokeWidth()) | ||||
| 				r.SetStrokeDashArray(lines[x].GetStrokeDashArray()) | ||||
|  | ||||
| 				r.MoveTo(lx, ly) | ||||
| 				r.LineTo(lx2, ly) | ||||
| 				r.Stroke() | ||||
|  | ||||
| 				ycursor += tb.Height() | ||||
| 				legendCount++ | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // LegendThin is a legend that doesn't obscure the chart area. | ||||
| func LegendThin(c *Chart, userDefaults ...Style) Renderable { | ||||
| 	return func(r Renderer, cb Box, chartDefaults Style) { | ||||
| 		legendDefaults := Style{ | ||||
| 			FillColor:   drawing.ColorWhite, | ||||
| 			FontColor:   DefaultTextColor, | ||||
| 			FontSize:    8.0, | ||||
| 			StrokeColor: DefaultAxisColor, | ||||
| 			StrokeWidth: DefaultAxisLineWidth, | ||||
| 			Padding: Box{ | ||||
| 				Top:    2, | ||||
| 				Left:   7, | ||||
| 				Right:  7, | ||||
| 				Bottom: 5, | ||||
| 			}, | ||||
| 		} | ||||
|  | ||||
| 		var legendStyle Style | ||||
| 		if len(userDefaults) > 0 { | ||||
| 			legendStyle = userDefaults[0].InheritFrom(chartDefaults.InheritFrom(legendDefaults)) | ||||
| 		} else { | ||||
| 			legendStyle = chartDefaults.InheritFrom(legendDefaults) | ||||
| 		} | ||||
|  | ||||
| 		r.SetFont(legendStyle.GetFont()) | ||||
| 		r.SetFontColor(legendStyle.GetFontColor()) | ||||
| 		r.SetFontSize(legendStyle.GetFontSize()) | ||||
|  | ||||
| 		var labels []string | ||||
| 		var lines []Style | ||||
| 		for index, s := range c.Series { | ||||
| 			if !s.GetStyle().Hidden { | ||||
| 				if _, isAnnotationSeries := s.(AnnotationSeries); !isAnnotationSeries { | ||||
| 					labels = append(labels, s.GetName()) | ||||
| 					lines = append(lines, s.GetStyle().InheritFrom(c.styleDefaultsSeries(index))) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		var textHeight int | ||||
| 		var textWidth int | ||||
| 		var textBox Box | ||||
| 		for x := 0; x < len(labels); x++ { | ||||
| 			if len(labels[x]) > 0 { | ||||
| 				textBox = r.MeasureText(labels[x]) | ||||
| 				textHeight = MaxInt(textBox.Height(), textHeight) | ||||
| 				textWidth = MaxInt(textBox.Width(), textWidth) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		legendBoxHeight := textHeight + legendStyle.Padding.Top + legendStyle.Padding.Bottom | ||||
| 		chartPadding := cb.Top | ||||
| 		legendYMargin := (chartPadding - legendBoxHeight) >> 1 | ||||
|  | ||||
| 		legendBox := Box{ | ||||
| 			Left:   cb.Left, | ||||
| 			Right:  cb.Right, | ||||
| 			Top:    legendYMargin, | ||||
| 			Bottom: legendYMargin + legendBoxHeight, | ||||
| 		} | ||||
|  | ||||
| 		Draw.Box(r, legendBox, legendDefaults) | ||||
|  | ||||
| 		r.SetFont(legendStyle.GetFont()) | ||||
| 		r.SetFontColor(legendStyle.GetFontColor()) | ||||
| 		r.SetFontSize(legendStyle.GetFontSize()) | ||||
|  | ||||
| 		lineTextGap := 5 | ||||
| 		lineLengthMinimum := 25 | ||||
|  | ||||
| 		tx := legendBox.Left + legendStyle.Padding.Left | ||||
| 		ty := legendYMargin + legendStyle.Padding.Top + textHeight | ||||
| 		var label string | ||||
| 		var lx, ly int | ||||
| 		th2 := textHeight >> 1 | ||||
| 		for index := range labels { | ||||
| 			label = labels[index] | ||||
| 			if len(label) > 0 { | ||||
| 				textBox = r.MeasureText(label) | ||||
| 				r.Text(label, tx, ty) | ||||
|  | ||||
| 				lx = tx + textBox.Width() + lineTextGap | ||||
| 				ly = ty - th2 | ||||
|  | ||||
| 				r.SetStrokeColor(lines[index].GetStrokeColor()) | ||||
| 				r.SetStrokeWidth(lines[index].GetStrokeWidth()) | ||||
| 				r.SetStrokeDashArray(lines[index].GetStrokeDashArray()) | ||||
|  | ||||
| 				r.MoveTo(lx, ly) | ||||
| 				r.LineTo(lx+lineLengthMinimum, ly) | ||||
| 				r.Stroke() | ||||
|  | ||||
| 				tx += textBox.Width() + DefaultMinimumTickHorizontalSpacing + lineTextGap + lineLengthMinimum | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // LegendLeft is a legend that is designed for longer series lists. | ||||
| func LegendLeft(c *Chart, userDefaults ...Style) Renderable { | ||||
| 	return func(r Renderer, cb Box, chartDefaults Style) { | ||||
| 		legendDefaults := Style{ | ||||
| 			FillColor:   drawing.ColorWhite, | ||||
| 			FontColor:   DefaultTextColor, | ||||
| 			FontSize:    8.0, | ||||
| 			StrokeColor: DefaultAxisColor, | ||||
| 			StrokeWidth: DefaultAxisLineWidth, | ||||
| 		} | ||||
|  | ||||
| 		var legendStyle Style | ||||
| 		if len(userDefaults) > 0 { | ||||
| 			legendStyle = userDefaults[0].InheritFrom(chartDefaults.InheritFrom(legendDefaults)) | ||||
| 		} else { | ||||
| 			legendStyle = chartDefaults.InheritFrom(legendDefaults) | ||||
| 		} | ||||
|  | ||||
| 		// DEFAULTS | ||||
| 		legendPadding := Box{ | ||||
| 			Top:    5, | ||||
| 			Left:   5, | ||||
| 			Right:  5, | ||||
| 			Bottom: 5, | ||||
| 		} | ||||
| 		lineTextGap := 5 | ||||
| 		lineLengthMinimum := 25 | ||||
|  | ||||
| 		var labels []string | ||||
| 		var lines []Style | ||||
| 		for index, s := range c.Series { | ||||
| 			if !s.GetStyle().Hidden { | ||||
| 				if _, isAnnotationSeries := s.(AnnotationSeries); !isAnnotationSeries { | ||||
| 					labels = append(labels, s.GetName()) | ||||
| 					lines = append(lines, s.GetStyle().InheritFrom(c.styleDefaultsSeries(index))) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		legend := Box{ | ||||
| 			Top:  5, | ||||
| 			Left: 5, | ||||
| 			// bottom and right will be sized by the legend content + relevant padding. | ||||
| 		} | ||||
|  | ||||
| 		legendContent := Box{ | ||||
| 			Top:    legend.Top + legendPadding.Top, | ||||
| 			Left:   legend.Left + legendPadding.Left, | ||||
| 			Right:  legend.Left + legendPadding.Left, | ||||
| 			Bottom: legend.Top + legendPadding.Top, | ||||
| 		} | ||||
|  | ||||
| 		legendStyle.GetTextOptions().WriteToRenderer(r) | ||||
|  | ||||
| 		// measure | ||||
| 		labelCount := 0 | ||||
| 		for x := 0; x < len(labels); x++ { | ||||
| 			if len(labels[x]) > 0 { | ||||
| 				tb := r.MeasureText(labels[x]) | ||||
| 				if labelCount > 0 { | ||||
| 					legendContent.Bottom += DefaultMinimumTickVerticalSpacing | ||||
| 				} | ||||
| 				legendContent.Bottom += tb.Height() | ||||
| 				right := legendContent.Left + tb.Width() + lineTextGap + lineLengthMinimum | ||||
| 				legendContent.Right = MaxInt(legendContent.Right, right) | ||||
| 				labelCount++ | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		legend = legend.Grow(legendContent) | ||||
| 		legend.Right = legendContent.Right + legendPadding.Right | ||||
| 		legend.Bottom = legendContent.Bottom + legendPadding.Bottom | ||||
|  | ||||
| 		Draw.Box(r, legend, legendStyle) | ||||
|  | ||||
| 		legendStyle.GetTextOptions().WriteToRenderer(r) | ||||
|  | ||||
| 		ycursor := legendContent.Top | ||||
| 		tx := legendContent.Left | ||||
| 		legendCount := 0 | ||||
| 		var label string | ||||
| 		for x := 0; x < len(labels); x++ { | ||||
| 			label = labels[x] | ||||
| 			if len(label) > 0 { | ||||
| 				if legendCount > 0 { | ||||
| 					ycursor += DefaultMinimumTickVerticalSpacing | ||||
| 				} | ||||
|  | ||||
| 				tb := r.MeasureText(label) | ||||
|  | ||||
| 				ty := ycursor + tb.Height() | ||||
| 				r.Text(label, tx, ty) | ||||
|  | ||||
| 				th2 := tb.Height() >> 1 | ||||
|  | ||||
| 				lx := tx + tb.Width() + lineTextGap | ||||
| 				ly := ty - th2 | ||||
| 				lx2 := legendContent.Right - legendPadding.Right | ||||
|  | ||||
| 				r.SetStrokeColor(lines[x].GetStrokeColor()) | ||||
| 				r.SetStrokeWidth(lines[x].GetStrokeWidth()) | ||||
| 				r.SetStrokeDashArray(lines[x].GetStrokeDashArray()) | ||||
|  | ||||
| 				r.MoveTo(lx, ly) | ||||
| 				r.LineTo(lx2, ly) | ||||
| 				r.Stroke() | ||||
|  | ||||
| 				ycursor += tb.Height() | ||||
| 				legendCount++ | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										42
									
								
								vendor/github.com/wcharczuk/go-chart/v2/linear_coefficient_provider.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								vendor/github.com/wcharczuk/go-chart/v2/linear_coefficient_provider.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | ||||
| package chart | ||||
|  | ||||
| // LinearCoefficientProvider is a type that returns linear cofficients. | ||||
| type LinearCoefficientProvider interface { | ||||
| 	Coefficients() (m, b, stdev, avg float64) | ||||
| } | ||||
|  | ||||
| // LinearCoefficients returns a fixed linear coefficient pair. | ||||
| func LinearCoefficients(m, b float64) LinearCoefficientSet { | ||||
| 	return LinearCoefficientSet{ | ||||
| 		M: m, | ||||
| 		B: b, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NormalizedLinearCoefficients returns a fixed linear coefficient pair. | ||||
| func NormalizedLinearCoefficients(m, b, stdev, avg float64) LinearCoefficientSet { | ||||
| 	return LinearCoefficientSet{ | ||||
| 		M:      m, | ||||
| 		B:      b, | ||||
| 		StdDev: stdev, | ||||
| 		Avg:    avg, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // LinearCoefficientSet is the m and b values for the linear equation in the form: | ||||
| // y = (m*x) + b | ||||
| type LinearCoefficientSet struct { | ||||
| 	M      float64 | ||||
| 	B      float64 | ||||
| 	StdDev float64 | ||||
| 	Avg    float64 | ||||
| } | ||||
|  | ||||
| // Coefficients returns the coefficients. | ||||
| func (lcs LinearCoefficientSet) Coefficients() (m, b, stdev, avg float64) { | ||||
| 	m = lcs.M | ||||
| 	b = lcs.B | ||||
| 	stdev = lcs.StdDev | ||||
| 	avg = lcs.Avg | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										187
									
								
								vendor/github.com/wcharczuk/go-chart/v2/linear_regression_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								vendor/github.com/wcharczuk/go-chart/v2/linear_regression_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,187 @@ | ||||
| package chart | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| ) | ||||
|  | ||||
| // Interface Assertions. | ||||
| var ( | ||||
| 	_ Series                    = (*LinearRegressionSeries)(nil) | ||||
| 	_ FirstValuesProvider       = (*LinearRegressionSeries)(nil) | ||||
| 	_ LastValuesProvider        = (*LinearRegressionSeries)(nil) | ||||
| 	_ LinearCoefficientProvider = (*LinearRegressionSeries)(nil) | ||||
| ) | ||||
|  | ||||
| // LinearRegressionSeries is a series that plots the n-nearest neighbors | ||||
| // linear regression for the values. | ||||
| type LinearRegressionSeries struct { | ||||
| 	Name  string | ||||
| 	Style Style | ||||
| 	YAxis YAxisType | ||||
|  | ||||
| 	Limit       int | ||||
| 	Offset      int | ||||
| 	InnerSeries ValuesProvider | ||||
|  | ||||
| 	m       float64 | ||||
| 	b       float64 | ||||
| 	avgx    float64 | ||||
| 	stddevx float64 | ||||
| } | ||||
|  | ||||
| // Coefficients returns the linear coefficients for the series. | ||||
| func (lrs LinearRegressionSeries) Coefficients() (m, b, stdev, avg float64) { | ||||
| 	if lrs.IsZero() { | ||||
| 		lrs.computeCoefficients() | ||||
| 	} | ||||
|  | ||||
| 	m = lrs.m | ||||
| 	b = lrs.b | ||||
| 	stdev = lrs.stddevx | ||||
| 	avg = lrs.avgx | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // GetName returns the name of the time series. | ||||
| func (lrs LinearRegressionSeries) GetName() string { | ||||
| 	return lrs.Name | ||||
| } | ||||
|  | ||||
| // GetStyle returns the line style. | ||||
| func (lrs LinearRegressionSeries) GetStyle() Style { | ||||
| 	return lrs.Style | ||||
| } | ||||
|  | ||||
| // GetYAxis returns which YAxis the series draws on. | ||||
| func (lrs LinearRegressionSeries) GetYAxis() YAxisType { | ||||
| 	return lrs.YAxis | ||||
| } | ||||
|  | ||||
| // Len returns the number of elements in the series. | ||||
| func (lrs LinearRegressionSeries) Len() int { | ||||
| 	return MinInt(lrs.GetLimit(), lrs.InnerSeries.Len()-lrs.GetOffset()) | ||||
| } | ||||
|  | ||||
| // GetLimit returns the window size. | ||||
| func (lrs LinearRegressionSeries) GetLimit() int { | ||||
| 	if lrs.Limit == 0 { | ||||
| 		return lrs.InnerSeries.Len() | ||||
| 	} | ||||
| 	return lrs.Limit | ||||
| } | ||||
|  | ||||
| // GetEndIndex returns the effective limit end. | ||||
| func (lrs LinearRegressionSeries) GetEndIndex() int { | ||||
| 	windowEnd := lrs.GetOffset() + lrs.GetLimit() | ||||
| 	innerSeriesLastIndex := lrs.InnerSeries.Len() - 1 | ||||
| 	return MinInt(windowEnd, innerSeriesLastIndex) | ||||
| } | ||||
|  | ||||
| // GetOffset returns the data offset. | ||||
| func (lrs LinearRegressionSeries) GetOffset() int { | ||||
| 	if lrs.Offset == 0 { | ||||
| 		return 0 | ||||
| 	} | ||||
| 	return lrs.Offset | ||||
| } | ||||
|  | ||||
| // GetValues gets a value at a given index. | ||||
| func (lrs *LinearRegressionSeries) GetValues(index int) (x, y float64) { | ||||
| 	if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 { | ||||
| 		return | ||||
| 	} | ||||
| 	if lrs.IsZero() { | ||||
| 		lrs.computeCoefficients() | ||||
| 	} | ||||
| 	offset := lrs.GetOffset() | ||||
| 	effectiveIndex := MinInt(index+offset, lrs.InnerSeries.Len()) | ||||
| 	x, y = lrs.InnerSeries.GetValues(effectiveIndex) | ||||
| 	y = (lrs.m * lrs.normalize(x)) + lrs.b | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // GetFirstValues computes the first linear regression value. | ||||
| func (lrs *LinearRegressionSeries) GetFirstValues() (x, y float64) { | ||||
| 	if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 { | ||||
| 		return | ||||
| 	} | ||||
| 	if lrs.IsZero() { | ||||
| 		lrs.computeCoefficients() | ||||
| 	} | ||||
| 	x, y = lrs.InnerSeries.GetValues(0) | ||||
| 	y = (lrs.m * lrs.normalize(x)) + lrs.b | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // GetLastValues computes the last linear regression value. | ||||
| func (lrs *LinearRegressionSeries) GetLastValues() (x, y float64) { | ||||
| 	if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 { | ||||
| 		return | ||||
| 	} | ||||
| 	if lrs.IsZero() { | ||||
| 		lrs.computeCoefficients() | ||||
| 	} | ||||
| 	endIndex := lrs.GetEndIndex() | ||||
| 	x, y = lrs.InnerSeries.GetValues(endIndex) | ||||
| 	y = (lrs.m * lrs.normalize(x)) + lrs.b | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Render renders the series. | ||||
| func (lrs *LinearRegressionSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { | ||||
| 	style := lrs.Style.InheritFrom(defaults) | ||||
| 	Draw.LineSeries(r, canvasBox, xrange, yrange, style, lrs) | ||||
| } | ||||
|  | ||||
| // Validate validates the series. | ||||
| func (lrs *LinearRegressionSeries) Validate() error { | ||||
| 	if lrs.InnerSeries == nil { | ||||
| 		return fmt.Errorf("linear regression series requires InnerSeries to be set") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // IsZero returns if we've computed the coefficients or not. | ||||
| func (lrs *LinearRegressionSeries) IsZero() bool { | ||||
| 	return lrs.m == 0 && lrs.b == 0 | ||||
| } | ||||
|  | ||||
| // | ||||
| // internal helpers | ||||
| // | ||||
|  | ||||
| func (lrs *LinearRegressionSeries) normalize(xvalue float64) float64 { | ||||
| 	return (xvalue - lrs.avgx) / lrs.stddevx | ||||
| } | ||||
|  | ||||
| // computeCoefficients computes the `m` and `b` terms in the linear formula given by `y = mx+b`. | ||||
| func (lrs *LinearRegressionSeries) computeCoefficients() { | ||||
| 	startIndex := lrs.GetOffset() | ||||
| 	endIndex := lrs.GetEndIndex() | ||||
|  | ||||
| 	p := float64(endIndex - startIndex) | ||||
|  | ||||
| 	xvalues := NewValueBufferWithCapacity(lrs.Len()) | ||||
| 	for index := startIndex; index < endIndex; index++ { | ||||
| 		x, _ := lrs.InnerSeries.GetValues(index) | ||||
| 		xvalues.Enqueue(x) | ||||
| 	} | ||||
|  | ||||
| 	lrs.avgx = Seq{xvalues}.Average() | ||||
| 	lrs.stddevx = Seq{xvalues}.StdDev() | ||||
|  | ||||
| 	var sumx, sumy, sumxx, sumxy float64 | ||||
| 	for index := startIndex; index < endIndex; index++ { | ||||
| 		x, y := lrs.InnerSeries.GetValues(index) | ||||
|  | ||||
| 		x = lrs.normalize(x) | ||||
|  | ||||
| 		sumx += x | ||||
| 		sumy += y | ||||
| 		sumxx += x * x | ||||
| 		sumxy += x * y | ||||
| 	} | ||||
|  | ||||
| 	lrs.m = (p*sumxy - sumx*sumy) / (p*sumxx - sumx*sumx) | ||||
| 	lrs.b = (sumy / p) - (lrs.m * sumx / p) | ||||
| } | ||||
							
								
								
									
										73
									
								
								vendor/github.com/wcharczuk/go-chart/v2/linear_sequence.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								vendor/github.com/wcharczuk/go-chart/v2/linear_sequence.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,73 @@ | ||||
| package chart | ||||
|  | ||||
| // LinearRange returns an array of values representing the range from start to end, incremented by 1.0. | ||||
| func LinearRange(start, end float64) []float64 { | ||||
| 	return Seq{NewLinearSequence().WithStart(start).WithEnd(end).WithStep(1.0)}.Values() | ||||
| } | ||||
|  | ||||
| // LinearRangeWithStep returns the array values of a linear seq with a given start, end and optional step. | ||||
| func LinearRangeWithStep(start, end, step float64) []float64 { | ||||
| 	return Seq{NewLinearSequence().WithStart(start).WithEnd(end).WithStep(step)}.Values() | ||||
| } | ||||
|  | ||||
| // NewLinearSequence returns a new linear generator. | ||||
| func NewLinearSequence() *LinearSeq { | ||||
| 	return &LinearSeq{step: 1.0} | ||||
| } | ||||
|  | ||||
| // LinearSeq is a stepwise generator. | ||||
| type LinearSeq struct { | ||||
| 	start float64 | ||||
| 	end   float64 | ||||
| 	step  float64 | ||||
| } | ||||
|  | ||||
| // Start returns the start value. | ||||
| func (lg LinearSeq) Start() float64 { | ||||
| 	return lg.start | ||||
| } | ||||
|  | ||||
| // End returns the end value. | ||||
| func (lg LinearSeq) End() float64 { | ||||
| 	return lg.end | ||||
| } | ||||
|  | ||||
| // Step returns the step value. | ||||
| func (lg LinearSeq) Step() float64 { | ||||
| 	return lg.step | ||||
| } | ||||
|  | ||||
| // Len returns the number of elements in the seq. | ||||
| func (lg LinearSeq) Len() int { | ||||
| 	if lg.start < lg.end { | ||||
| 		return int((lg.end-lg.start)/lg.step) + 1 | ||||
| 	} | ||||
| 	return int((lg.start-lg.end)/lg.step) + 1 | ||||
| } | ||||
|  | ||||
| // GetValue returns the value at a given index. | ||||
| func (lg LinearSeq) GetValue(index int) float64 { | ||||
| 	fi := float64(index) | ||||
| 	if lg.start < lg.end { | ||||
| 		return lg.start + (fi * lg.step) | ||||
| 	} | ||||
| 	return lg.start - (fi * lg.step) | ||||
| } | ||||
|  | ||||
| // WithStart sets the start and returns the linear generator. | ||||
| func (lg *LinearSeq) WithStart(start float64) *LinearSeq { | ||||
| 	lg.start = start | ||||
| 	return lg | ||||
| } | ||||
|  | ||||
| // WithEnd sets the end and returns the linear generator. | ||||
| func (lg *LinearSeq) WithEnd(end float64) *LinearSeq { | ||||
| 	lg.end = end | ||||
| 	return lg | ||||
| } | ||||
|  | ||||
| // WithStep sets the step and returns the linear generator. | ||||
| func (lg *LinearSeq) WithStep(step float64) *LinearSeq { | ||||
| 	lg.step = step | ||||
| 	return lg | ||||
| } | ||||
							
								
								
									
										119
									
								
								vendor/github.com/wcharczuk/go-chart/v2/linear_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								vendor/github.com/wcharczuk/go-chart/v2/linear_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,119 @@ | ||||
| package chart | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| ) | ||||
|  | ||||
| // Interface Assertions. | ||||
| var ( | ||||
| 	_ Series              = (*LinearSeries)(nil) | ||||
| 	_ FirstValuesProvider = (*LinearSeries)(nil) | ||||
| 	_ LastValuesProvider  = (*LinearSeries)(nil) | ||||
| ) | ||||
|  | ||||
| // LinearSeries is a series that plots a line in a given domain. | ||||
| type LinearSeries struct { | ||||
| 	Name  string | ||||
| 	Style Style | ||||
| 	YAxis YAxisType | ||||
|  | ||||
| 	XValues     []float64 | ||||
| 	InnerSeries LinearCoefficientProvider | ||||
|  | ||||
| 	m     float64 | ||||
| 	b     float64 | ||||
| 	stdev float64 | ||||
| 	avg   float64 | ||||
| } | ||||
|  | ||||
| // GetName returns the name of the time series. | ||||
| func (ls LinearSeries) GetName() string { | ||||
| 	return ls.Name | ||||
| } | ||||
|  | ||||
| // GetStyle returns the line style. | ||||
| func (ls LinearSeries) GetStyle() Style { | ||||
| 	return ls.Style | ||||
| } | ||||
|  | ||||
| // GetYAxis returns which YAxis the series draws on. | ||||
| func (ls LinearSeries) GetYAxis() YAxisType { | ||||
| 	return ls.YAxis | ||||
| } | ||||
|  | ||||
| // Len returns the number of elements in the series. | ||||
| func (ls LinearSeries) Len() int { | ||||
| 	return len(ls.XValues) | ||||
| } | ||||
|  | ||||
| // GetEndIndex returns the effective limit end. | ||||
| func (ls LinearSeries) GetEndIndex() int { | ||||
| 	return len(ls.XValues) - 1 | ||||
| } | ||||
|  | ||||
| // GetValues gets a value at a given index. | ||||
| func (ls *LinearSeries) GetValues(index int) (x, y float64) { | ||||
| 	if ls.InnerSeries == nil || len(ls.XValues) == 0 { | ||||
| 		return | ||||
| 	} | ||||
| 	if ls.IsZero() { | ||||
| 		ls.computeCoefficients() | ||||
| 	} | ||||
| 	x = ls.XValues[index] | ||||
| 	y = (ls.m * ls.normalize(x)) + ls.b | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // GetFirstValues computes the first linear regression value. | ||||
| func (ls *LinearSeries) GetFirstValues() (x, y float64) { | ||||
| 	if ls.InnerSeries == nil || len(ls.XValues) == 0 { | ||||
| 		return | ||||
| 	} | ||||
| 	if ls.IsZero() { | ||||
| 		ls.computeCoefficients() | ||||
| 	} | ||||
| 	x, y = ls.GetValues(0) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // GetLastValues computes the last linear regression value. | ||||
| func (ls *LinearSeries) GetLastValues() (x, y float64) { | ||||
| 	if ls.InnerSeries == nil || len(ls.XValues) == 0 { | ||||
| 		return | ||||
| 	} | ||||
| 	if ls.IsZero() { | ||||
| 		ls.computeCoefficients() | ||||
| 	} | ||||
| 	x, y = ls.GetValues(ls.GetEndIndex()) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Render renders the series. | ||||
| func (ls *LinearSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { | ||||
| 	Draw.LineSeries(r, canvasBox, xrange, yrange, ls.Style.InheritFrom(defaults), ls) | ||||
| } | ||||
|  | ||||
| // Validate validates the series. | ||||
| func (ls LinearSeries) Validate() error { | ||||
| 	if ls.InnerSeries == nil { | ||||
| 		return fmt.Errorf("linear regression series requires InnerSeries to be set") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // IsZero returns if the linear series has computed coefficients or not. | ||||
| func (ls LinearSeries) IsZero() bool { | ||||
| 	return ls.m == 0 && ls.b == 0 | ||||
| } | ||||
|  | ||||
| // computeCoefficients computes the `m` and `b` terms in the linear formula given by `y = mx+b`. | ||||
| func (ls *LinearSeries) computeCoefficients() { | ||||
| 	ls.m, ls.b, ls.stdev, ls.avg = ls.InnerSeries.Coefficients() | ||||
| } | ||||
|  | ||||
| func (ls *LinearSeries) normalize(xvalue float64) float64 { | ||||
| 	if ls.avg > 0 && ls.stdev > 0 { | ||||
| 		return (xvalue - ls.avg) / ls.stdev | ||||
| 	} | ||||
| 	return xvalue | ||||
| } | ||||
							
								
								
									
										148
									
								
								vendor/github.com/wcharczuk/go-chart/v2/logger.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								vendor/github.com/wcharczuk/go-chart/v2/logger.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,148 @@ | ||||
| package chart | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	_ Logger = (*StdoutLogger)(nil) | ||||
| ) | ||||
|  | ||||
| // NewLogger returns a new logger. | ||||
| func NewLogger(options ...LoggerOption) Logger { | ||||
| 	stl := &StdoutLogger{ | ||||
| 		TimeFormat: time.RFC3339Nano, | ||||
| 		Stdout:     os.Stdout, | ||||
| 		Stderr:     os.Stderr, | ||||
| 	} | ||||
| 	for _, option := range options { | ||||
| 		option(stl) | ||||
| 	} | ||||
| 	return stl | ||||
| } | ||||
|  | ||||
| // Logger is a type that implements the logging interface. | ||||
| type Logger interface { | ||||
| 	Info(...interface{}) | ||||
| 	Infof(string, ...interface{}) | ||||
| 	Debug(...interface{}) | ||||
| 	Debugf(string, ...interface{}) | ||||
| 	Err(error) | ||||
| 	FatalErr(error) | ||||
| 	Error(...interface{}) | ||||
| 	Errorf(string, ...interface{}) | ||||
| } | ||||
|  | ||||
| // Info logs an info message if the logger is set. | ||||
| func Info(log Logger, arguments ...interface{}) { | ||||
| 	if log == nil { | ||||
| 		return | ||||
| 	} | ||||
| 	log.Info(arguments...) | ||||
| } | ||||
|  | ||||
| // Infof logs an info message if the logger is set. | ||||
| func Infof(log Logger, format string, arguments ...interface{}) { | ||||
| 	if log == nil { | ||||
| 		return | ||||
| 	} | ||||
| 	log.Infof(format, arguments...) | ||||
| } | ||||
|  | ||||
| // Debug logs an debug message if the logger is set. | ||||
| func Debug(log Logger, arguments ...interface{}) { | ||||
| 	if log == nil { | ||||
| 		return | ||||
| 	} | ||||
| 	log.Debug(arguments...) | ||||
| } | ||||
|  | ||||
| // Debugf logs an debug message if the logger is set. | ||||
| func Debugf(log Logger, format string, arguments ...interface{}) { | ||||
| 	if log == nil { | ||||
| 		return | ||||
| 	} | ||||
| 	log.Debugf(format, arguments...) | ||||
| } | ||||
|  | ||||
| // LoggerOption mutates a stdout logger. | ||||
| type LoggerOption = func(*StdoutLogger) | ||||
|  | ||||
| //OptLoggerStdout sets the Stdout writer. | ||||
| func OptLoggerStdout(wr io.Writer) LoggerOption { | ||||
| 	return func(stl *StdoutLogger) { | ||||
| 		stl.Stdout = wr | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // OptLoggerStderr sets the Stdout writer. | ||||
| func OptLoggerStderr(wr io.Writer) LoggerOption { | ||||
| 	return func(stl *StdoutLogger) { | ||||
| 		stl.Stderr = wr | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // StdoutLogger is a basic logger. | ||||
| type StdoutLogger struct { | ||||
| 	TimeFormat string | ||||
| 	Stdout     io.Writer | ||||
| 	Stderr     io.Writer | ||||
| } | ||||
|  | ||||
| // Info writes an info message. | ||||
| func (l *StdoutLogger) Info(arguments ...interface{}) { | ||||
| 	l.Println(append([]interface{}{"[INFO]"}, arguments...)...) | ||||
| } | ||||
|  | ||||
| // Infof writes an info message. | ||||
| func (l *StdoutLogger) Infof(format string, arguments ...interface{}) { | ||||
| 	l.Println(append([]interface{}{"[INFO]"}, fmt.Sprintf(format, arguments...))...) | ||||
| } | ||||
|  | ||||
| // Debug writes an debug message. | ||||
| func (l *StdoutLogger) Debug(arguments ...interface{}) { | ||||
| 	l.Println(append([]interface{}{"[DEBUG]"}, arguments...)...) | ||||
| } | ||||
|  | ||||
| // Debugf writes an debug message. | ||||
| func (l *StdoutLogger) Debugf(format string, arguments ...interface{}) { | ||||
| 	l.Println(append([]interface{}{"[DEBUG]"}, fmt.Sprintf(format, arguments...))...) | ||||
| } | ||||
|  | ||||
| // Error writes an error message. | ||||
| func (l *StdoutLogger) Error(arguments ...interface{}) { | ||||
| 	l.Println(append([]interface{}{"[ERROR]"}, arguments...)...) | ||||
| } | ||||
|  | ||||
| // Errorf writes an error message. | ||||
| func (l *StdoutLogger) Errorf(format string, arguments ...interface{}) { | ||||
| 	l.Println(append([]interface{}{"[ERROR]"}, fmt.Sprintf(format, arguments...))...) | ||||
| } | ||||
|  | ||||
| // Err writes an error message. | ||||
| func (l *StdoutLogger) Err(err error) { | ||||
| 	if err != nil { | ||||
| 		l.Println(append([]interface{}{"[ERROR]"}, err.Error())...) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // FatalErr writes an error message and exits. | ||||
| func (l *StdoutLogger) FatalErr(err error) { | ||||
| 	if err != nil { | ||||
| 		l.Println(append([]interface{}{"[FATAL]"}, err.Error())...) | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Println prints a new message. | ||||
| func (l *StdoutLogger) Println(arguments ...interface{}) { | ||||
| 	fmt.Fprintln(l.Stdout, append([]interface{}{time.Now().UTC().Format(l.TimeFormat)}, arguments...)...) | ||||
| } | ||||
|  | ||||
| // Errorln prints a new message. | ||||
| func (l *StdoutLogger) Errorln(arguments ...interface{}) { | ||||
| 	fmt.Fprintln(l.Stderr, append([]interface{}{time.Now().UTC().Format(l.TimeFormat)}, arguments...)...) | ||||
| } | ||||
							
								
								
									
										338
									
								
								vendor/github.com/wcharczuk/go-chart/v2/macd_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										338
									
								
								vendor/github.com/wcharczuk/go-chart/v2/macd_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,338 @@ | ||||
| package chart | ||||
|  | ||||
| import "fmt" | ||||
|  | ||||
| const ( | ||||
| 	// DefaultMACDPeriodPrimary is the long window. | ||||
| 	DefaultMACDPeriodPrimary = 26 | ||||
| 	// DefaultMACDPeriodSecondary is the short window. | ||||
| 	DefaultMACDPeriodSecondary = 12 | ||||
| 	// DefaultMACDSignalPeriod is the signal period to compute for the MACD. | ||||
| 	DefaultMACDSignalPeriod = 9 | ||||
| ) | ||||
|  | ||||
| // MACDSeries computes the difference between the MACD line and the MACD Signal line. | ||||
| // It is used in technical analysis and gives a lagging indicator of momentum. | ||||
| type MACDSeries struct { | ||||
| 	Name        string | ||||
| 	Style       Style | ||||
| 	YAxis       YAxisType | ||||
| 	InnerSeries ValuesProvider | ||||
|  | ||||
| 	PrimaryPeriod   int | ||||
| 	SecondaryPeriod int | ||||
| 	SignalPeriod    int | ||||
|  | ||||
| 	signal *MACDSignalSeries | ||||
| 	macdl  *MACDLineSeries | ||||
| } | ||||
|  | ||||
| // Validate validates the series. | ||||
| func (macd MACDSeries) Validate() error { | ||||
| 	var err error | ||||
| 	if macd.signal != nil { | ||||
| 		err = macd.signal.Validate() | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if macd.macdl != nil { | ||||
| 		err = macd.macdl.Validate() | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // GetPeriods returns the primary and secondary periods. | ||||
| func (macd MACDSeries) GetPeriods() (w1, w2, sig int) { | ||||
| 	if macd.PrimaryPeriod == 0 { | ||||
| 		w1 = DefaultMACDPeriodPrimary | ||||
| 	} else { | ||||
| 		w1 = macd.PrimaryPeriod | ||||
| 	} | ||||
| 	if macd.SecondaryPeriod == 0 { | ||||
| 		w2 = DefaultMACDPeriodSecondary | ||||
| 	} else { | ||||
| 		w2 = macd.SecondaryPeriod | ||||
| 	} | ||||
| 	if macd.SignalPeriod == 0 { | ||||
| 		sig = DefaultMACDSignalPeriod | ||||
| 	} else { | ||||
| 		sig = macd.SignalPeriod | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // GetName returns the name of the time series. | ||||
| func (macd MACDSeries) GetName() string { | ||||
| 	return macd.Name | ||||
| } | ||||
|  | ||||
| // GetStyle returns the line style. | ||||
| func (macd MACDSeries) GetStyle() Style { | ||||
| 	return macd.Style | ||||
| } | ||||
|  | ||||
| // GetYAxis returns which YAxis the series draws on. | ||||
| func (macd MACDSeries) GetYAxis() YAxisType { | ||||
| 	return macd.YAxis | ||||
| } | ||||
|  | ||||
| // Len returns the number of elements in the series. | ||||
| func (macd MACDSeries) Len() int { | ||||
| 	if macd.InnerSeries == nil { | ||||
| 		return 0 | ||||
| 	} | ||||
|  | ||||
| 	return macd.InnerSeries.Len() | ||||
| } | ||||
|  | ||||
| // GetValues gets a value at a given index. For MACD it is the signal value. | ||||
| func (macd *MACDSeries) GetValues(index int) (x float64, y float64) { | ||||
| 	if macd.InnerSeries == nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if macd.signal == nil || macd.macdl == nil { | ||||
| 		macd.ensureChildSeries() | ||||
| 	} | ||||
|  | ||||
| 	_, lv := macd.macdl.GetValues(index) | ||||
| 	_, sv := macd.signal.GetValues(index) | ||||
|  | ||||
| 	x, _ = macd.InnerSeries.GetValues(index) | ||||
| 	y = lv - sv | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (macd *MACDSeries) ensureChildSeries() { | ||||
| 	w1, w2, sig := macd.GetPeriods() | ||||
|  | ||||
| 	macd.signal = &MACDSignalSeries{ | ||||
| 		InnerSeries:     macd.InnerSeries, | ||||
| 		PrimaryPeriod:   w1, | ||||
| 		SecondaryPeriod: w2, | ||||
| 		SignalPeriod:    sig, | ||||
| 	} | ||||
|  | ||||
| 	macd.macdl = &MACDLineSeries{ | ||||
| 		InnerSeries:     macd.InnerSeries, | ||||
| 		PrimaryPeriod:   w1, | ||||
| 		SecondaryPeriod: w2, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // MACDSignalSeries computes the EMA of the MACDLineSeries. | ||||
| type MACDSignalSeries struct { | ||||
| 	Name        string | ||||
| 	Style       Style | ||||
| 	YAxis       YAxisType | ||||
| 	InnerSeries ValuesProvider | ||||
|  | ||||
| 	PrimaryPeriod   int | ||||
| 	SecondaryPeriod int | ||||
| 	SignalPeriod    int | ||||
|  | ||||
| 	signal *EMASeries | ||||
| } | ||||
|  | ||||
| // Validate validates the series. | ||||
| func (macds MACDSignalSeries) Validate() error { | ||||
| 	if macds.signal != nil { | ||||
| 		return macds.signal.Validate() | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // GetPeriods returns the primary and secondary periods. | ||||
| func (macds MACDSignalSeries) GetPeriods() (w1, w2, sig int) { | ||||
| 	if macds.PrimaryPeriod == 0 { | ||||
| 		w1 = DefaultMACDPeriodPrimary | ||||
| 	} else { | ||||
| 		w1 = macds.PrimaryPeriod | ||||
| 	} | ||||
| 	if macds.SecondaryPeriod == 0 { | ||||
| 		w2 = DefaultMACDPeriodSecondary | ||||
| 	} else { | ||||
| 		w2 = macds.SecondaryPeriod | ||||
| 	} | ||||
| 	if macds.SignalPeriod == 0 { | ||||
| 		sig = DefaultMACDSignalPeriod | ||||
| 	} else { | ||||
| 		sig = macds.SignalPeriod | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // GetName returns the name of the time series. | ||||
| func (macds MACDSignalSeries) GetName() string { | ||||
| 	return macds.Name | ||||
| } | ||||
|  | ||||
| // GetStyle returns the line style. | ||||
| func (macds MACDSignalSeries) GetStyle() Style { | ||||
| 	return macds.Style | ||||
| } | ||||
|  | ||||
| // GetYAxis returns which YAxis the series draws on. | ||||
| func (macds MACDSignalSeries) GetYAxis() YAxisType { | ||||
| 	return macds.YAxis | ||||
| } | ||||
|  | ||||
| // Len returns the number of elements in the series. | ||||
| func (macds *MACDSignalSeries) Len() int { | ||||
| 	if macds.InnerSeries == nil { | ||||
| 		return 0 | ||||
| 	} | ||||
|  | ||||
| 	return macds.InnerSeries.Len() | ||||
| } | ||||
|  | ||||
| // GetValues gets a value at a given index. For MACD it is the signal value. | ||||
| func (macds *MACDSignalSeries) GetValues(index int) (x float64, y float64) { | ||||
| 	if macds.InnerSeries == nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if macds.signal == nil { | ||||
| 		macds.ensureSignal() | ||||
| 	} | ||||
| 	x, _ = macds.InnerSeries.GetValues(index) | ||||
| 	_, y = macds.signal.GetValues(index) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (macds *MACDSignalSeries) ensureSignal() { | ||||
| 	w1, w2, sig := macds.GetPeriods() | ||||
|  | ||||
| 	macds.signal = &EMASeries{ | ||||
| 		InnerSeries: &MACDLineSeries{ | ||||
| 			InnerSeries:     macds.InnerSeries, | ||||
| 			PrimaryPeriod:   w1, | ||||
| 			SecondaryPeriod: w2, | ||||
| 		}, | ||||
| 		Period: sig, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Render renders the series. | ||||
| func (macds *MACDSignalSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { | ||||
| 	style := macds.Style.InheritFrom(defaults) | ||||
| 	Draw.LineSeries(r, canvasBox, xrange, yrange, style, macds) | ||||
| } | ||||
|  | ||||
| // MACDLineSeries is a series that computes the inner ema1-ema2 value as a series. | ||||
| type MACDLineSeries struct { | ||||
| 	Name        string | ||||
| 	Style       Style | ||||
| 	YAxis       YAxisType | ||||
| 	InnerSeries ValuesProvider | ||||
|  | ||||
| 	PrimaryPeriod   int | ||||
| 	SecondaryPeriod int | ||||
|  | ||||
| 	ema1 *EMASeries | ||||
| 	ema2 *EMASeries | ||||
|  | ||||
| 	Sigma float64 | ||||
| } | ||||
|  | ||||
| // Validate validates the series. | ||||
| func (macdl MACDLineSeries) Validate() error { | ||||
| 	var err error | ||||
| 	if macdl.ema1 != nil { | ||||
| 		err = macdl.ema1.Validate() | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if macdl.ema2 != nil { | ||||
| 		err = macdl.ema2.Validate() | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if macdl.InnerSeries == nil { | ||||
| 		return fmt.Errorf("MACDLineSeries: must provide an inner series") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // GetName returns the name of the time series. | ||||
| func (macdl MACDLineSeries) GetName() string { | ||||
| 	return macdl.Name | ||||
| } | ||||
|  | ||||
| // GetStyle returns the line style. | ||||
| func (macdl MACDLineSeries) GetStyle() Style { | ||||
| 	return macdl.Style | ||||
| } | ||||
|  | ||||
| // GetYAxis returns which YAxis the series draws on. | ||||
| func (macdl MACDLineSeries) GetYAxis() YAxisType { | ||||
| 	return macdl.YAxis | ||||
| } | ||||
|  | ||||
| // GetPeriods returns the primary and secondary periods. | ||||
| func (macdl MACDLineSeries) GetPeriods() (w1, w2 int) { | ||||
| 	if macdl.PrimaryPeriod == 0 { | ||||
| 		w1 = DefaultMACDPeriodPrimary | ||||
| 	} else { | ||||
| 		w1 = macdl.PrimaryPeriod | ||||
| 	} | ||||
| 	if macdl.SecondaryPeriod == 0 { | ||||
| 		w2 = DefaultMACDPeriodSecondary | ||||
| 	} else { | ||||
| 		w2 = macdl.SecondaryPeriod | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Len returns the number of elements in the series. | ||||
| func (macdl *MACDLineSeries) Len() int { | ||||
| 	if macdl.InnerSeries == nil { | ||||
| 		return 0 | ||||
| 	} | ||||
|  | ||||
| 	return macdl.InnerSeries.Len() | ||||
| } | ||||
|  | ||||
| // GetValues gets a value at a given index. For MACD it is the signal value. | ||||
| func (macdl *MACDLineSeries) GetValues(index int) (x float64, y float64) { | ||||
| 	if macdl.InnerSeries == nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if macdl.ema1 == nil && macdl.ema2 == nil { | ||||
| 		macdl.ensureEMASeries() | ||||
| 	} | ||||
|  | ||||
| 	x, _ = macdl.InnerSeries.GetValues(index) | ||||
|  | ||||
| 	_, emav1 := macdl.ema1.GetValues(index) | ||||
| 	_, emav2 := macdl.ema2.GetValues(index) | ||||
|  | ||||
| 	y = emav2 - emav1 | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (macdl *MACDLineSeries) ensureEMASeries() { | ||||
| 	w1, w2 := macdl.GetPeriods() | ||||
|  | ||||
| 	macdl.ema1 = &EMASeries{ | ||||
| 		InnerSeries: macdl.InnerSeries, | ||||
| 		Period:      w1, | ||||
| 	} | ||||
| 	macdl.ema2 = &EMASeries{ | ||||
| 		InnerSeries: macdl.InnerSeries, | ||||
| 		Period:      w2, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Render renders the series. | ||||
| func (macdl *MACDLineSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { | ||||
| 	style := macdl.Style.InheritFrom(defaults) | ||||
| 	Draw.LineSeries(r, canvasBox, xrange, yrange, style, macdl) | ||||
| } | ||||
							
								
								
									
										252
									
								
								vendor/github.com/wcharczuk/go-chart/v2/mathutil.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										252
									
								
								vendor/github.com/wcharczuk/go-chart/v2/mathutil.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,252 @@ | ||||
| package chart | ||||
|  | ||||
| import "math" | ||||
|  | ||||
| const ( | ||||
| 	_pi   = math.Pi | ||||
| 	_2pi  = 2 * math.Pi | ||||
| 	_3pi4 = (3 * math.Pi) / 4.0 | ||||
| 	_4pi3 = (4 * math.Pi) / 3.0 | ||||
| 	_3pi2 = (3 * math.Pi) / 2.0 | ||||
| 	_5pi4 = (5 * math.Pi) / 4.0 | ||||
| 	_7pi4 = (7 * math.Pi) / 4.0 | ||||
| 	_pi2  = math.Pi / 2.0 | ||||
| 	_pi4  = math.Pi / 4.0 | ||||
| 	_d2r  = (math.Pi / 180.0) | ||||
| 	_r2d  = (180.0 / math.Pi) | ||||
| ) | ||||
|  | ||||
| // MinMax returns the minimum and maximum of a given set of values. | ||||
| func MinMax(values ...float64) (min, max float64) { | ||||
| 	if len(values) == 0 { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	max = values[0] | ||||
| 	min = values[0] | ||||
| 	var value float64 | ||||
| 	for index := 1; index < len(values); index++ { | ||||
| 		value = values[index] | ||||
| 		if value < min { | ||||
| 			min = value | ||||
| 		} | ||||
| 		if value > max { | ||||
| 			max = value | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // MinInt returns the minimum int. | ||||
| func MinInt(values ...int) (min int) { | ||||
| 	if len(values) == 0 { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	min = values[0] | ||||
| 	var value int | ||||
| 	for index := 1; index < len(values); index++ { | ||||
| 		value = values[index] | ||||
| 		if value < min { | ||||
| 			min = value | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // MaxInt returns the maximum int. | ||||
| func MaxInt(values ...int) (max int) { | ||||
| 	if len(values) == 0 { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	max = values[0] | ||||
| 	var value int | ||||
| 	for index := 1; index < len(values); index++ { | ||||
| 		value = values[index] | ||||
| 		if value > max { | ||||
| 			max = value | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // AbsInt returns the absolute value of an int. | ||||
| func AbsInt(value int) int { | ||||
| 	if value < 0 { | ||||
| 		return -value | ||||
| 	} | ||||
| 	return value | ||||
| } | ||||
|  | ||||
| // DegreesToRadians returns degrees as radians. | ||||
| func DegreesToRadians(degrees float64) float64 { | ||||
| 	return degrees * _d2r | ||||
| } | ||||
|  | ||||
| // RadiansToDegrees translates a radian value to a degree value. | ||||
| func RadiansToDegrees(value float64) float64 { | ||||
| 	return math.Mod(value, _2pi) * _r2d | ||||
| } | ||||
|  | ||||
| // PercentToRadians converts a normalized value (0,1) to radians. | ||||
| func PercentToRadians(pct float64) float64 { | ||||
| 	return DegreesToRadians(360.0 * pct) | ||||
| } | ||||
|  | ||||
| // RadianAdd adds a delta to a base in radians. | ||||
| func RadianAdd(base, delta float64) float64 { | ||||
| 	value := base + delta | ||||
| 	if value > _2pi { | ||||
| 		return math.Mod(value, _2pi) | ||||
| 	} else if value < 0 { | ||||
| 		return math.Mod(_2pi+value, _2pi) | ||||
| 	} | ||||
| 	return value | ||||
| } | ||||
|  | ||||
| // DegreesAdd adds a delta to a base in radians. | ||||
| func DegreesAdd(baseDegrees, deltaDegrees float64) float64 { | ||||
| 	value := baseDegrees + deltaDegrees | ||||
| 	if value > _2pi { | ||||
| 		return math.Mod(value, 360.0) | ||||
| 	} else if value < 0 { | ||||
| 		return math.Mod(360.0+value, 360.0) | ||||
| 	} | ||||
| 	return value | ||||
| } | ||||
|  | ||||
| // DegreesToCompass returns the degree value in compass / clock orientation. | ||||
| func DegreesToCompass(deg float64) float64 { | ||||
| 	return DegreesAdd(deg, -90.0) | ||||
| } | ||||
|  | ||||
| // CirclePoint returns the absolute position of a circle diameter point given | ||||
| // by the radius and the theta. | ||||
| func CirclePoint(cx, cy int, radius, thetaRadians float64) (x, y int) { | ||||
| 	x = cx + int(radius*math.Sin(thetaRadians)) | ||||
| 	y = cy - int(radius*math.Cos(thetaRadians)) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // RotateCoordinate rotates a coordinate around a given center by a theta in radians. | ||||
| func RotateCoordinate(cx, cy, x, y int, thetaRadians float64) (rx, ry int) { | ||||
| 	tempX, tempY := float64(x-cx), float64(y-cy) | ||||
| 	rotatedX := tempX*math.Cos(thetaRadians) - tempY*math.Sin(thetaRadians) | ||||
| 	rotatedY := tempX*math.Sin(thetaRadians) + tempY*math.Cos(thetaRadians) | ||||
| 	rx = int(rotatedX) + cx | ||||
| 	ry = int(rotatedY) + cy | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // RoundUp rounds up to a given roundTo value. | ||||
| func RoundUp(value, roundTo float64) float64 { | ||||
| 	if roundTo < 0.000000000000001 { | ||||
| 		return value | ||||
| 	} | ||||
| 	d1 := math.Ceil(value / roundTo) | ||||
| 	return d1 * roundTo | ||||
| } | ||||
|  | ||||
| // RoundDown rounds down to a given roundTo value. | ||||
| func RoundDown(value, roundTo float64) float64 { | ||||
| 	if roundTo < 0.000000000000001 { | ||||
| 		return value | ||||
| 	} | ||||
| 	d1 := math.Floor(value / roundTo) | ||||
| 	return d1 * roundTo | ||||
| } | ||||
|  | ||||
| // Normalize returns a set of numbers on the interval [0,1] for a given set of inputs. | ||||
| // An example: 4,3,2,1 => 0.4, 0.3, 0.2, 0.1 | ||||
| // Caveat; the total may be < 1.0; there are going to be issues with irrational numbers etc. | ||||
| func Normalize(values ...float64) []float64 { | ||||
| 	var total float64 | ||||
| 	for _, v := range values { | ||||
| 		total += v | ||||
| 	} | ||||
| 	output := make([]float64, len(values)) | ||||
| 	for x, v := range values { | ||||
| 		output[x] = RoundDown(v/total, 0.0001) | ||||
| 	} | ||||
| 	return output | ||||
| } | ||||
|  | ||||
| // Mean returns the mean of a set of values | ||||
| func Mean(values ...float64) float64 { | ||||
| 	return Sum(values...) / float64(len(values)) | ||||
| } | ||||
|  | ||||
| // MeanInt returns the mean of a set of integer values. | ||||
| func MeanInt(values ...int) int { | ||||
| 	return SumInt(values...) / len(values) | ||||
| } | ||||
|  | ||||
| // Sum sums a set of values. | ||||
| func Sum(values ...float64) float64 { | ||||
| 	var total float64 | ||||
| 	for _, v := range values { | ||||
| 		total += v | ||||
| 	} | ||||
| 	return total | ||||
| } | ||||
|  | ||||
| // SumInt sums a set of values. | ||||
| func SumInt(values ...int) int { | ||||
| 	var total int | ||||
| 	for _, v := range values { | ||||
| 		total += v | ||||
| 	} | ||||
| 	return total | ||||
| } | ||||
|  | ||||
| // PercentDifference computes the percentage difference between two values. | ||||
| // The formula is (v2-v1)/v1. | ||||
| func PercentDifference(v1, v2 float64) float64 { | ||||
| 	if v1 == 0 { | ||||
| 		return 0 | ||||
| 	} | ||||
| 	return (v2 - v1) / v1 | ||||
| } | ||||
|  | ||||
| // GetRoundToForDelta returns a `roundTo` value for a given delta. | ||||
| func GetRoundToForDelta(delta float64) float64 { | ||||
| 	startingDeltaBound := math.Pow(10.0, 10.0) | ||||
| 	for cursor := startingDeltaBound; cursor > 0; cursor /= 10.0 { | ||||
| 		if delta > cursor { | ||||
| 			return cursor / 10.0 | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return 0.0 | ||||
| } | ||||
|  | ||||
| // RoundPlaces rounds an input to a given places. | ||||
| func RoundPlaces(input float64, places int) (rounded float64) { | ||||
| 	if math.IsNaN(input) { | ||||
| 		return 0.0 | ||||
| 	} | ||||
|  | ||||
| 	sign := 1.0 | ||||
| 	if input < 0 { | ||||
| 		sign = -1 | ||||
| 		input *= -1 | ||||
| 	} | ||||
|  | ||||
| 	precision := math.Pow(10, float64(places)) | ||||
| 	digit := input * precision | ||||
| 	_, decimal := math.Modf(digit) | ||||
|  | ||||
| 	if decimal >= 0.5 { | ||||
| 		rounded = math.Ceil(digit) | ||||
| 	} else { | ||||
| 		rounded = math.Floor(digit) | ||||
| 	} | ||||
|  | ||||
| 	return rounded / precision * sign | ||||
| } | ||||
|  | ||||
| func f64i(value float64) int { | ||||
| 	r := RoundPlaces(value, 0) | ||||
| 	return int(r) | ||||
| } | ||||
							
								
								
									
										592
									
								
								vendor/github.com/wcharczuk/go-chart/v2/matrix/matrix.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										592
									
								
								vendor/github.com/wcharczuk/go-chart/v2/matrix/matrix.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,592 @@ | ||||
| package matrix | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"math" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	// DefaultEpsilon represents the minimum precision for matrix math operations. | ||||
| 	DefaultEpsilon = 0.000001 | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// ErrDimensionMismatch is a typical error. | ||||
| 	ErrDimensionMismatch = errors.New("dimension mismatch") | ||||
|  | ||||
| 	// ErrSingularValue is a typical error. | ||||
| 	ErrSingularValue = errors.New("singular value") | ||||
| ) | ||||
|  | ||||
| // New returns a new matrix. | ||||
| func New(rows, cols int, values ...float64) *Matrix { | ||||
| 	if len(values) == 0 { | ||||
| 		return &Matrix{ | ||||
| 			stride:   cols, | ||||
| 			epsilon:  DefaultEpsilon, | ||||
| 			elements: make([]float64, rows*cols), | ||||
| 		} | ||||
| 	} | ||||
| 	elems := make([]float64, rows*cols) | ||||
| 	copy(elems, values) | ||||
| 	return &Matrix{ | ||||
| 		stride:   cols, | ||||
| 		epsilon:  DefaultEpsilon, | ||||
| 		elements: elems, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Identity returns the identity matrix of a given order. | ||||
| func Identity(order int) *Matrix { | ||||
| 	m := New(order, order) | ||||
| 	for i := 0; i < order; i++ { | ||||
| 		m.Set(i, i, 1) | ||||
| 	} | ||||
| 	return m | ||||
| } | ||||
|  | ||||
| // Zero returns a matrix of a given size zeroed. | ||||
| func Zero(rows, cols int) *Matrix { | ||||
| 	return New(rows, cols) | ||||
| } | ||||
|  | ||||
| // Ones returns an matrix of ones. | ||||
| func Ones(rows, cols int) *Matrix { | ||||
| 	ones := make([]float64, rows*cols) | ||||
| 	for i := 0; i < (rows * cols); i++ { | ||||
| 		ones[i] = 1 | ||||
| 	} | ||||
|  | ||||
| 	return &Matrix{ | ||||
| 		stride:   cols, | ||||
| 		epsilon:  DefaultEpsilon, | ||||
| 		elements: ones, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Eye returns the eye matrix. | ||||
| func Eye(n int) *Matrix { | ||||
| 	m := Zero(n, n) | ||||
| 	for i := 0; i < len(m.elements); i += n + 1 { | ||||
| 		m.elements[i] = 1 | ||||
| 	} | ||||
| 	return m | ||||
| } | ||||
|  | ||||
| // NewFromArrays creates a matrix from a jagged array set. | ||||
| func NewFromArrays(a [][]float64) *Matrix { | ||||
| 	rows := len(a) | ||||
| 	if rows == 0 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	cols := len(a[0]) | ||||
| 	m := New(rows, cols) | ||||
| 	for row := 0; row < rows; row++ { | ||||
| 		for col := 0; col < cols; col++ { | ||||
| 			m.Set(row, col, a[row][col]) | ||||
| 		} | ||||
| 	} | ||||
| 	return m | ||||
| } | ||||
|  | ||||
| // Matrix represents a 2d dense array of floats. | ||||
| type Matrix struct { | ||||
| 	epsilon  float64 | ||||
| 	elements []float64 | ||||
| 	stride   int | ||||
| } | ||||
|  | ||||
| // String returns a string representation of the matrix. | ||||
| func (m *Matrix) String() string { | ||||
| 	buffer := bytes.NewBuffer(nil) | ||||
| 	rows, cols := m.Size() | ||||
|  | ||||
| 	for row := 0; row < rows; row++ { | ||||
| 		for col := 0; col < cols; col++ { | ||||
| 			buffer.WriteString(f64s(m.Get(row, col))) | ||||
| 			buffer.WriteRune(' ') | ||||
| 		} | ||||
| 		buffer.WriteRune('\n') | ||||
| 	} | ||||
| 	return buffer.String() | ||||
| } | ||||
|  | ||||
| // Epsilon returns the maximum precision for math operations. | ||||
| func (m *Matrix) Epsilon() float64 { | ||||
| 	return m.epsilon | ||||
| } | ||||
|  | ||||
| // WithEpsilon sets the epsilon on the matrix and returns a reference to the matrix. | ||||
| func (m *Matrix) WithEpsilon(epsilon float64) *Matrix { | ||||
| 	m.epsilon = epsilon | ||||
| 	return m | ||||
| } | ||||
|  | ||||
| // Each applies the action to each element of the matrix in | ||||
| // rows => cols order. | ||||
| func (m *Matrix) Each(action func(row, col int, value float64)) { | ||||
| 	rows, cols := m.Size() | ||||
| 	for row := 0; row < rows; row++ { | ||||
| 		for col := 0; col < cols; col++ { | ||||
| 			action(row, col, m.Get(row, col)) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Round rounds all the values in a matrix to it epsilon, | ||||
| // returning a reference to the original | ||||
| func (m *Matrix) Round() *Matrix { | ||||
| 	rows, cols := m.Size() | ||||
| 	for row := 0; row < rows; row++ { | ||||
| 		for col := 0; col < cols; col++ { | ||||
| 			m.Set(row, col, roundToEpsilon(m.Get(row, col), m.epsilon)) | ||||
| 		} | ||||
| 	} | ||||
| 	return m | ||||
| } | ||||
|  | ||||
| // Arrays returns the matrix as a two dimensional jagged array. | ||||
| func (m *Matrix) Arrays() [][]float64 { | ||||
| 	rows, cols := m.Size() | ||||
| 	a := make([][]float64, rows) | ||||
|  | ||||
| 	for row := 0; row < rows; row++ { | ||||
| 		a[row] = make([]float64, cols) | ||||
|  | ||||
| 		for col := 0; col < cols; col++ { | ||||
| 			a[row][col] = m.Get(row, col) | ||||
| 		} | ||||
| 	} | ||||
| 	return a | ||||
| } | ||||
|  | ||||
| // Size returns the dimensions of the matrix. | ||||
| func (m *Matrix) Size() (rows, cols int) { | ||||
| 	rows = len(m.elements) / m.stride | ||||
| 	cols = m.stride | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // IsSquare returns if the row count is equal to the column count. | ||||
| func (m *Matrix) IsSquare() bool { | ||||
| 	return m.stride == (len(m.elements) / m.stride) | ||||
| } | ||||
|  | ||||
| // IsSymmetric returns if the matrix is symmetric about its diagonal. | ||||
| func (m *Matrix) IsSymmetric() bool { | ||||
| 	rows, cols := m.Size() | ||||
|  | ||||
| 	if rows != cols { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	for i := 0; i < rows; i++ { | ||||
| 		for j := 0; j < i; j++ { | ||||
| 			if m.Get(i, j) != m.Get(j, i) { | ||||
| 				return false | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // Get returns the element at the given row, col. | ||||
| func (m *Matrix) Get(row, col int) float64 { | ||||
| 	index := (m.stride * row) + col | ||||
| 	return m.elements[index] | ||||
| } | ||||
|  | ||||
| // Set sets a value. | ||||
| func (m *Matrix) Set(row, col int, val float64) { | ||||
| 	index := (m.stride * row) + col | ||||
| 	m.elements[index] = val | ||||
| } | ||||
|  | ||||
| // Col returns a column of the matrix as a vector. | ||||
| func (m *Matrix) Col(col int) Vector { | ||||
| 	rows, _ := m.Size() | ||||
| 	values := make([]float64, rows) | ||||
| 	for row := 0; row < rows; row++ { | ||||
| 		values[row] = m.Get(row, col) | ||||
| 	} | ||||
| 	return Vector(values) | ||||
| } | ||||
|  | ||||
| // Row returns a row of the matrix as a vector. | ||||
| func (m *Matrix) Row(row int) Vector { | ||||
| 	_, cols := m.Size() | ||||
| 	values := make([]float64, cols) | ||||
| 	for col := 0; col < cols; col++ { | ||||
| 		values[col] = m.Get(row, col) | ||||
| 	} | ||||
| 	return Vector(values) | ||||
| } | ||||
|  | ||||
| // SubMatrix returns a sub matrix from a given outer matrix. | ||||
| func (m *Matrix) SubMatrix(i, j, rows, cols int) *Matrix { | ||||
| 	return &Matrix{ | ||||
| 		elements: m.elements[i*m.stride+j : i*m.stride+j+(rows-1)*m.stride+cols], | ||||
| 		stride:   m.stride, | ||||
| 		epsilon:  m.epsilon, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ScaleRow applies a scale to an entire row. | ||||
| func (m *Matrix) ScaleRow(row int, scale float64) { | ||||
| 	startIndex := row * m.stride | ||||
| 	for i := startIndex; i < m.stride; i++ { | ||||
| 		m.elements[i] = m.elements[i] * scale | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (m *Matrix) scaleAddRow(rd int, rs int, f float64) { | ||||
| 	indexd := rd * m.stride | ||||
| 	indexs := rs * m.stride | ||||
| 	for col := 0; col < m.stride; col++ { | ||||
| 		m.elements[indexd] += f * m.elements[indexs] | ||||
| 		indexd++ | ||||
| 		indexs++ | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // SwapRows swaps a row in the matrix in place. | ||||
| func (m *Matrix) SwapRows(i, j int) { | ||||
| 	var vi, vj float64 | ||||
| 	for col := 0; col < m.stride; col++ { | ||||
| 		vi = m.Get(i, col) | ||||
| 		vj = m.Get(j, col) | ||||
| 		m.Set(i, col, vj) | ||||
| 		m.Set(j, col, vi) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Augment concatenates two matrices about the horizontal. | ||||
| func (m *Matrix) Augment(m2 *Matrix) (*Matrix, error) { | ||||
| 	mr, mc := m.Size() | ||||
| 	m2r, m2c := m2.Size() | ||||
| 	if mr != m2r { | ||||
| 		return nil, ErrDimensionMismatch | ||||
| 	} | ||||
|  | ||||
| 	m3 := Zero(mr, mc+m2c) | ||||
| 	for row := 0; row < mr; row++ { | ||||
| 		for col := 0; col < mc; col++ { | ||||
| 			m3.Set(row, col, m.Get(row, col)) | ||||
| 		} | ||||
| 		for col := 0; col < m2c; col++ { | ||||
| 			m3.Set(row, mc+col, m2.Get(row, col)) | ||||
| 		} | ||||
| 	} | ||||
| 	return m3, nil | ||||
| } | ||||
|  | ||||
| // Copy returns a duplicate of a given matrix. | ||||
| func (m *Matrix) Copy() *Matrix { | ||||
| 	m2 := &Matrix{stride: m.stride, epsilon: m.epsilon, elements: make([]float64, len(m.elements))} | ||||
| 	copy(m2.elements, m.elements) | ||||
| 	return m2 | ||||
| } | ||||
|  | ||||
| // DiagonalVector returns a vector from the diagonal of a matrix. | ||||
| func (m *Matrix) DiagonalVector() Vector { | ||||
| 	rows, cols := m.Size() | ||||
| 	rank := minInt(rows, cols) | ||||
| 	values := make([]float64, rank) | ||||
|  | ||||
| 	for index := 0; index < rank; index++ { | ||||
| 		values[index] = m.Get(index, index) | ||||
| 	} | ||||
| 	return Vector(values) | ||||
| } | ||||
|  | ||||
| // Diagonal returns a matrix from the diagonal of a matrix. | ||||
| func (m *Matrix) Diagonal() *Matrix { | ||||
| 	rows, cols := m.Size() | ||||
| 	rank := minInt(rows, cols) | ||||
| 	m2 := New(rank, rank) | ||||
|  | ||||
| 	for index := 0; index < rank; index++ { | ||||
| 		m2.Set(index, index, m.Get(index, index)) | ||||
| 	} | ||||
| 	return m2 | ||||
| } | ||||
|  | ||||
| // Equals returns if a matrix equals another matrix. | ||||
| func (m *Matrix) Equals(other *Matrix) bool { | ||||
| 	if other == nil && m != nil { | ||||
| 		return false | ||||
| 	} else if other == nil { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	if m.stride != other.stride { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	msize := len(m.elements) | ||||
| 	m2size := len(other.elements) | ||||
|  | ||||
| 	if msize != m2size { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	for i := 0; i < msize; i++ { | ||||
| 		if m.elements[i] != other.elements[i] { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // L returns the matrix with zeros below the diagonal. | ||||
| func (m *Matrix) L() *Matrix { | ||||
| 	rows, cols := m.Size() | ||||
| 	m2 := New(rows, cols) | ||||
| 	for row := 0; row < rows; row++ { | ||||
| 		for col := row; col < cols; col++ { | ||||
| 			m2.Set(row, col, m.Get(row, col)) | ||||
| 		} | ||||
| 	} | ||||
| 	return m2 | ||||
| } | ||||
|  | ||||
| // U returns the matrix with zeros above the diagonal. | ||||
| // Does not include the diagonal. | ||||
| func (m *Matrix) U() *Matrix { | ||||
| 	rows, cols := m.Size() | ||||
| 	m2 := New(rows, cols) | ||||
| 	for row := 0; row < rows; row++ { | ||||
| 		for col := 0; col < row && col < cols; col++ { | ||||
| 			m2.Set(row, col, m.Get(row, col)) | ||||
| 		} | ||||
| 	} | ||||
| 	return m2 | ||||
| } | ||||
|  | ||||
| // math operations | ||||
|  | ||||
| // Multiply multiplies two matrices. | ||||
| func (m *Matrix) Multiply(m2 *Matrix) (m3 *Matrix, err error) { | ||||
| 	if m.stride*m2.stride != len(m2.elements) { | ||||
| 		return nil, ErrDimensionMismatch | ||||
| 	} | ||||
|  | ||||
| 	m3 = &Matrix{epsilon: m.epsilon, stride: m2.stride, elements: make([]float64, (len(m.elements)/m.stride)*m2.stride)} | ||||
| 	for m1c0, m3x := 0, 0; m1c0 < len(m.elements); m1c0 += m.stride { | ||||
| 		for m2r0 := 0; m2r0 < m2.stride; m2r0++ { | ||||
| 			for m1x, m2x := m1c0, m2r0; m2x < len(m2.elements); m2x += m2.stride { | ||||
| 				m3.elements[m3x] += m.elements[m1x] * m2.elements[m2x] | ||||
| 				m1x++ | ||||
| 			} | ||||
| 			m3x++ | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Pivotize does something i'm not sure what. | ||||
| func (m *Matrix) Pivotize() *Matrix { | ||||
| 	pv := make([]int, m.stride) | ||||
|  | ||||
| 	for i := range pv { | ||||
| 		pv[i] = i | ||||
| 	} | ||||
|  | ||||
| 	for j, dx := 0, 0; j < m.stride; j++ { | ||||
| 		row := j | ||||
| 		max := m.elements[dx] | ||||
| 		for i, ixcj := j, dx; i < m.stride; i++ { | ||||
| 			if m.elements[ixcj] > max { | ||||
| 				max = m.elements[ixcj] | ||||
| 				row = i | ||||
| 			} | ||||
| 			ixcj += m.stride | ||||
| 		} | ||||
| 		if j != row { | ||||
| 			pv[row], pv[j] = pv[j], pv[row] | ||||
| 		} | ||||
| 		dx += m.stride + 1 | ||||
| 	} | ||||
| 	p := Zero(m.stride, m.stride) | ||||
| 	for r, c := range pv { | ||||
| 		p.elements[r*m.stride+c] = 1 | ||||
| 	} | ||||
| 	return p | ||||
| } | ||||
|  | ||||
| // Times returns the product of a matrix and another. | ||||
| func (m *Matrix) Times(m2 *Matrix) (*Matrix, error) { | ||||
| 	mr, mc := m.Size() | ||||
| 	m2r, m2c := m2.Size() | ||||
|  | ||||
| 	if mc != m2r { | ||||
| 		return nil, fmt.Errorf("cannot multiply (%dx%d) and (%dx%d)", mr, mc, m2r, m2c) | ||||
| 		//return nil, ErrDimensionMismatch | ||||
| 	} | ||||
|  | ||||
| 	c := Zero(mr, m2c) | ||||
|  | ||||
| 	for i := 0; i < mr; i++ { | ||||
| 		sums := c.elements[i*c.stride : (i+1)*c.stride] | ||||
| 		for k, a := range m.elements[i*m.stride : i*m.stride+m.stride] { | ||||
| 			for j, b := range m2.elements[k*m2.stride : k*m2.stride+m2.stride] { | ||||
| 				sums[j] += a * b | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return c, nil | ||||
| } | ||||
|  | ||||
| // Decompositions | ||||
|  | ||||
| // LU performs the LU decomposition. | ||||
| func (m *Matrix) LU() (l, u, p *Matrix) { | ||||
| 	l = Zero(m.stride, m.stride) | ||||
| 	u = Zero(m.stride, m.stride) | ||||
| 	p = m.Pivotize() | ||||
| 	m, _ = p.Multiply(m) | ||||
| 	for j, jxc0 := 0, 0; j < m.stride; j++ { | ||||
| 		l.elements[jxc0+j] = 1 | ||||
| 		for i, ixc0 := 0, 0; ixc0 <= jxc0; i++ { | ||||
| 			sum := 0. | ||||
| 			for k, kxcj := 0, j; k < i; k++ { | ||||
| 				sum += u.elements[kxcj] * l.elements[ixc0+k] | ||||
| 				kxcj += m.stride | ||||
| 			} | ||||
| 			u.elements[ixc0+j] = m.elements[ixc0+j] - sum | ||||
| 			ixc0 += m.stride | ||||
| 		} | ||||
| 		for ixc0 := jxc0; ixc0 < len(m.elements); ixc0 += m.stride { | ||||
| 			sum := 0. | ||||
| 			for k, kxcj := 0, j; k < j; k++ { | ||||
| 				sum += u.elements[kxcj] * l.elements[ixc0+k] | ||||
| 				kxcj += m.stride | ||||
| 			} | ||||
| 			l.elements[ixc0+j] = (m.elements[ixc0+j] - sum) / u.elements[jxc0+j] | ||||
| 		} | ||||
| 		jxc0 += m.stride | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // QR performs the qr decomposition. | ||||
| func (m *Matrix) QR() (q, r *Matrix) { | ||||
| 	defer func() { | ||||
| 		q = q.Round() | ||||
| 		r = r.Round() | ||||
| 	}() | ||||
|  | ||||
| 	rows, cols := m.Size() | ||||
| 	qr := m.Copy() | ||||
| 	q = New(rows, cols) | ||||
| 	r = New(rows, cols) | ||||
|  | ||||
| 	var i, j, k int | ||||
| 	var norm, s float64 | ||||
|  | ||||
| 	for k = 0; k < cols; k++ { | ||||
| 		norm = 0 | ||||
| 		for i = k; i < rows; i++ { | ||||
| 			norm = math.Hypot(norm, qr.Get(i, k)) | ||||
| 		} | ||||
|  | ||||
| 		if norm != 0 { | ||||
| 			if qr.Get(k, k) < 0 { | ||||
| 				norm = -norm | ||||
| 			} | ||||
|  | ||||
| 			for i = k; i < rows; i++ { | ||||
| 				qr.Set(i, k, qr.Get(i, k)/norm) | ||||
| 			} | ||||
| 			qr.Set(k, k, qr.Get(k, k)+1.0) | ||||
|  | ||||
| 			for j = k + 1; j < cols; j++ { | ||||
| 				s = 0 | ||||
| 				for i = k; i < rows; i++ { | ||||
| 					s += qr.Get(i, k) * qr.Get(i, j) | ||||
| 				} | ||||
| 				s = -s / qr.Get(k, k) | ||||
| 				for i = k; i < rows; i++ { | ||||
| 					qr.Set(i, j, qr.Get(i, j)+s*qr.Get(i, k)) | ||||
|  | ||||
| 					if i < j { | ||||
| 						r.Set(i, j, qr.Get(i, j)) | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		r.Set(k, k, -norm) | ||||
|  | ||||
| 	} | ||||
|  | ||||
| 	//Q Matrix: | ||||
| 	i, j, k = 0, 0, 0 | ||||
|  | ||||
| 	for k = cols - 1; k >= 0; k-- { | ||||
| 		q.Set(k, k, 1.0) | ||||
| 		for j = k; j < cols; j++ { | ||||
| 			if qr.Get(k, k) != 0 { | ||||
| 				s = 0 | ||||
| 				for i = k; i < rows; i++ { | ||||
| 					s += qr.Get(i, k) * q.Get(i, j) | ||||
| 				} | ||||
| 				s = -s / qr.Get(k, k) | ||||
| 				for i = k; i < rows; i++ { | ||||
| 					q.Set(i, j, q.Get(i, j)+s*qr.Get(i, k)) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Transpose flips a matrix about its diagonal, returning a new copy. | ||||
| func (m *Matrix) Transpose() *Matrix { | ||||
| 	rows, cols := m.Size() | ||||
| 	m2 := Zero(cols, rows) | ||||
| 	for i := 0; i < rows; i++ { | ||||
| 		for j := 0; j < cols; j++ { | ||||
| 			m2.Set(j, i, m.Get(i, j)) | ||||
| 		} | ||||
| 	} | ||||
| 	return m2 | ||||
| } | ||||
|  | ||||
| // Inverse returns a matrix such that M*I==1. | ||||
| func (m *Matrix) Inverse() (*Matrix, error) { | ||||
| 	if !m.IsSymmetric() { | ||||
| 		return nil, ErrDimensionMismatch | ||||
| 	} | ||||
|  | ||||
| 	rows, cols := m.Size() | ||||
|  | ||||
| 	aug, _ := m.Augment(Eye(rows)) | ||||
| 	for i := 0; i < rows; i++ { | ||||
| 		j := i | ||||
| 		for k := i; k < rows; k++ { | ||||
| 			if math.Abs(aug.Get(k, i)) > math.Abs(aug.Get(j, i)) { | ||||
| 				j = k | ||||
| 			} | ||||
| 		} | ||||
| 		if j != i { | ||||
| 			aug.SwapRows(i, j) | ||||
| 		} | ||||
| 		if aug.Get(i, i) == 0 { | ||||
| 			return nil, ErrSingularValue | ||||
| 		} | ||||
| 		aug.ScaleRow(i, 1.0/aug.Get(i, i)) | ||||
| 		for k := 0; k < rows; k++ { | ||||
| 			if k == i { | ||||
| 				continue | ||||
| 			} | ||||
| 			aug.scaleAddRow(k, i, -aug.Get(k, i)) | ||||
| 		} | ||||
| 	} | ||||
| 	return aug.SubMatrix(0, cols, rows, cols), nil | ||||
| } | ||||
							
								
								
									
										45
									
								
								vendor/github.com/wcharczuk/go-chart/v2/matrix/regression.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								vendor/github.com/wcharczuk/go-chart/v2/matrix/regression.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,45 @@ | ||||
| package matrix | ||||
|  | ||||
| import "errors" | ||||
|  | ||||
| var ( | ||||
| 	// ErrPolyRegArraysSameLength is a common error. | ||||
| 	ErrPolyRegArraysSameLength = errors.New("polynomial array inputs must be the same length") | ||||
| ) | ||||
|  | ||||
| // Poly returns the polynomial regress of a given degree over the given values. | ||||
| func Poly(xvalues, yvalues []float64, degree int) ([]float64, error) { | ||||
| 	if len(xvalues) != len(yvalues) { | ||||
| 		return nil, ErrPolyRegArraysSameLength | ||||
| 	} | ||||
|  | ||||
| 	m := len(yvalues) | ||||
| 	n := degree + 1 | ||||
| 	y := New(m, 1, yvalues...) | ||||
| 	x := Zero(m, n) | ||||
|  | ||||
| 	for i := 0; i < m; i++ { | ||||
| 		ip := float64(1) | ||||
| 		for j := 0; j < n; j++ { | ||||
| 			x.Set(i, j, ip) | ||||
| 			ip *= xvalues[i] | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	q, r := x.QR() | ||||
| 	qty, err := q.Transpose().Times(y) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	c := make([]float64, n) | ||||
| 	for i := n - 1; i >= 0; i-- { | ||||
| 		c[i] = qty.Get(i, 0) | ||||
| 		for j := i + 1; j < n; j++ { | ||||
| 			c[i] -= c[j] * r.Get(i, j) | ||||
| 		} | ||||
| 		c[i] /= r.Get(i, i) | ||||
| 	} | ||||
|  | ||||
| 	return c, nil | ||||
| } | ||||
							
								
								
									
										36
									
								
								vendor/github.com/wcharczuk/go-chart/v2/matrix/util.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								vendor/github.com/wcharczuk/go-chart/v2/matrix/util.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,36 @@ | ||||
| package matrix | ||||
|  | ||||
| import ( | ||||
| 	"math" | ||||
| 	"strconv" | ||||
| ) | ||||
|  | ||||
| func minInt(values ...int) int { | ||||
| 	min := math.MaxInt32 | ||||
|  | ||||
| 	for x := 0; x < len(values); x++ { | ||||
| 		if values[x] < min { | ||||
| 			min = values[x] | ||||
| 		} | ||||
| 	} | ||||
| 	return min | ||||
| } | ||||
|  | ||||
| func maxInt(values ...int) int { | ||||
| 	max := math.MinInt32 | ||||
|  | ||||
| 	for x := 0; x < len(values); x++ { | ||||
| 		if values[x] > max { | ||||
| 			max = values[x] | ||||
| 		} | ||||
| 	} | ||||
| 	return max | ||||
| } | ||||
|  | ||||
| func f64s(v float64) string { | ||||
| 	return strconv.FormatFloat(v, 'f', -1, 64) | ||||
| } | ||||
|  | ||||
| func roundToEpsilon(value, epsilon float64) float64 { | ||||
| 	return math.Nextafter(value, value) | ||||
| } | ||||
							
								
								
									
										17
									
								
								vendor/github.com/wcharczuk/go-chart/v2/matrix/vector.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								vendor/github.com/wcharczuk/go-chart/v2/matrix/vector.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| package matrix | ||||
|  | ||||
| // Vector is just an array of values. | ||||
| type Vector []float64 | ||||
|  | ||||
| // DotProduct returns the dot product of two vectors. | ||||
| func (v Vector) DotProduct(v2 Vector) (result float64, err error) { | ||||
| 	if len(v) != len(v2) { | ||||
| 		err = ErrDimensionMismatch | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	for i := 0; i < len(v); i++ { | ||||
| 		result = result + (v[i] * v2[i]) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										138
									
								
								vendor/github.com/wcharczuk/go-chart/v2/min_max_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								vendor/github.com/wcharczuk/go-chart/v2/min_max_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,138 @@ | ||||
| package chart | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"math" | ||||
| ) | ||||
|  | ||||
| // MinSeries draws a horizontal line at the minimum value of the inner series. | ||||
| type MinSeries struct { | ||||
| 	Name        string | ||||
| 	Style       Style | ||||
| 	YAxis       YAxisType | ||||
| 	InnerSeries ValuesProvider | ||||
|  | ||||
| 	minValue *float64 | ||||
| } | ||||
|  | ||||
| // GetName returns the name of the time series. | ||||
| func (ms MinSeries) GetName() string { | ||||
| 	return ms.Name | ||||
| } | ||||
|  | ||||
| // GetStyle returns the line style. | ||||
| func (ms MinSeries) GetStyle() Style { | ||||
| 	return ms.Style | ||||
| } | ||||
|  | ||||
| // GetYAxis returns which YAxis the series draws on. | ||||
| func (ms MinSeries) GetYAxis() YAxisType { | ||||
| 	return ms.YAxis | ||||
| } | ||||
|  | ||||
| // Len returns the number of elements in the series. | ||||
| func (ms MinSeries) Len() int { | ||||
| 	return ms.InnerSeries.Len() | ||||
| } | ||||
|  | ||||
| // GetValues gets a value at a given index. | ||||
| func (ms *MinSeries) GetValues(index int) (x, y float64) { | ||||
| 	ms.ensureMinValue() | ||||
| 	x, _ = ms.InnerSeries.GetValues(index) | ||||
| 	y = *ms.minValue | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Render renders the series. | ||||
| func (ms *MinSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { | ||||
| 	style := ms.Style.InheritFrom(defaults) | ||||
| 	Draw.LineSeries(r, canvasBox, xrange, yrange, style, ms) | ||||
| } | ||||
|  | ||||
| func (ms *MinSeries) ensureMinValue() { | ||||
| 	if ms.minValue == nil { | ||||
| 		minValue := math.MaxFloat64 | ||||
| 		var y float64 | ||||
| 		for x := 0; x < ms.InnerSeries.Len(); x++ { | ||||
| 			_, y = ms.InnerSeries.GetValues(x) | ||||
| 			if y < minValue { | ||||
| 				minValue = y | ||||
| 			} | ||||
| 		} | ||||
| 		ms.minValue = &minValue | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Validate validates the series. | ||||
| func (ms *MinSeries) Validate() error { | ||||
| 	if ms.InnerSeries == nil { | ||||
| 		return fmt.Errorf("min series requires InnerSeries to be set") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // MaxSeries draws a horizontal line at the maximum value of the inner series. | ||||
| type MaxSeries struct { | ||||
| 	Name        string | ||||
| 	Style       Style | ||||
| 	YAxis       YAxisType | ||||
| 	InnerSeries ValuesProvider | ||||
|  | ||||
| 	maxValue *float64 | ||||
| } | ||||
|  | ||||
| // GetName returns the name of the time series. | ||||
| func (ms MaxSeries) GetName() string { | ||||
| 	return ms.Name | ||||
| } | ||||
|  | ||||
| // GetStyle returns the line style. | ||||
| func (ms MaxSeries) GetStyle() Style { | ||||
| 	return ms.Style | ||||
| } | ||||
|  | ||||
| // GetYAxis returns which YAxis the series draws on. | ||||
| func (ms MaxSeries) GetYAxis() YAxisType { | ||||
| 	return ms.YAxis | ||||
| } | ||||
|  | ||||
| // Len returns the number of elements in the series. | ||||
| func (ms MaxSeries) Len() int { | ||||
| 	return ms.InnerSeries.Len() | ||||
| } | ||||
|  | ||||
| // GetValues gets a value at a given index. | ||||
| func (ms *MaxSeries) GetValues(index int) (x, y float64) { | ||||
| 	ms.ensureMaxValue() | ||||
| 	x, _ = ms.InnerSeries.GetValues(index) | ||||
| 	y = *ms.maxValue | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Render renders the series. | ||||
| func (ms *MaxSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { | ||||
| 	style := ms.Style.InheritFrom(defaults) | ||||
| 	Draw.LineSeries(r, canvasBox, xrange, yrange, style, ms) | ||||
| } | ||||
|  | ||||
| func (ms *MaxSeries) ensureMaxValue() { | ||||
| 	if ms.maxValue == nil { | ||||
| 		maxValue := -math.MaxFloat64 | ||||
| 		var y float64 | ||||
| 		for x := 0; x < ms.InnerSeries.Len(); x++ { | ||||
| 			_, y = ms.InnerSeries.GetValues(x) | ||||
| 			if y > maxValue { | ||||
| 				maxValue = y | ||||
| 			} | ||||
| 		} | ||||
| 		ms.maxValue = &maxValue | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Validate validates the series. | ||||
| func (ms *MaxSeries) Validate() error { | ||||
| 	if ms.InnerSeries == nil { | ||||
| 		return fmt.Errorf("max series requires InnerSeries to be set") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										40
									
								
								vendor/github.com/wcharczuk/go-chart/v2/parse.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								vendor/github.com/wcharczuk/go-chart/v2/parse.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | ||||
| package chart | ||||
|  | ||||
| import ( | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // ParseFloats parses a list of floats. | ||||
| func ParseFloats(values ...string) ([]float64, error) { | ||||
| 	var output []float64 | ||||
| 	var parsedValue float64 | ||||
| 	var err error | ||||
| 	var cleaned string | ||||
| 	for _, value := range values { | ||||
| 		cleaned = strings.TrimSpace(strings.Replace(value, ",", "", -1)) | ||||
| 		if cleaned == "" { | ||||
| 			continue | ||||
| 		} | ||||
| 		if parsedValue, err = strconv.ParseFloat(cleaned, 64); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		output = append(output, parsedValue) | ||||
| 	} | ||||
| 	return output, nil | ||||
| } | ||||
|  | ||||
| // ParseTimes parses a list of times with a given format. | ||||
| func ParseTimes(layout string, values ...string) ([]time.Time, error) { | ||||
| 	var output []time.Time | ||||
| 	var parsedValue time.Time | ||||
| 	var err error | ||||
| 	for _, value := range values { | ||||
| 		if parsedValue, err = time.Parse(layout, value); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		output = append(output, parsedValue) | ||||
| 	} | ||||
| 	return output, nil | ||||
| } | ||||
							
								
								
									
										89
									
								
								vendor/github.com/wcharczuk/go-chart/v2/percent_change_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								vendor/github.com/wcharczuk/go-chart/v2/percent_change_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,89 @@ | ||||
| package chart | ||||
|  | ||||
| // Interface Assertions. | ||||
| var ( | ||||
| 	_ Series                 = (*PercentChangeSeries)(nil) | ||||
| 	_ FirstValuesProvider    = (*PercentChangeSeries)(nil) | ||||
| 	_ LastValuesProvider     = (*PercentChangeSeries)(nil) | ||||
| 	_ ValueFormatterProvider = (*PercentChangeSeries)(nil) | ||||
| ) | ||||
|  | ||||
| // PercentChangeSeriesSource is a series that | ||||
| // can be used with a PercentChangeSeries | ||||
| type PercentChangeSeriesSource interface { | ||||
| 	Series | ||||
| 	FirstValuesProvider | ||||
| 	LastValuesProvider | ||||
| 	ValuesProvider | ||||
| 	ValueFormatterProvider | ||||
| } | ||||
|  | ||||
| // PercentChangeSeries applies a | ||||
| // percentage difference function to a given continuous series. | ||||
| type PercentChangeSeries struct { | ||||
| 	Name        string | ||||
| 	Style       Style | ||||
| 	YAxis       YAxisType | ||||
| 	InnerSeries PercentChangeSeriesSource | ||||
| } | ||||
|  | ||||
| // GetName returns the name of the time series. | ||||
| func (pcs PercentChangeSeries) GetName() string { | ||||
| 	return pcs.Name | ||||
| } | ||||
|  | ||||
| // GetStyle returns the line style. | ||||
| func (pcs PercentChangeSeries) GetStyle() Style { | ||||
| 	return pcs.Style | ||||
| } | ||||
|  | ||||
| // Len implements part of Series. | ||||
| func (pcs PercentChangeSeries) Len() int { | ||||
| 	return pcs.InnerSeries.Len() | ||||
| } | ||||
|  | ||||
| // GetFirstValues implements FirstValuesProvider. | ||||
| func (pcs PercentChangeSeries) GetFirstValues() (x, y float64) { | ||||
| 	return pcs.InnerSeries.GetFirstValues() | ||||
| } | ||||
|  | ||||
| // GetValues gets x, y values at a given index. | ||||
| func (pcs PercentChangeSeries) GetValues(index int) (x, y float64) { | ||||
| 	_, fy := pcs.InnerSeries.GetFirstValues() | ||||
| 	x0, y0 := pcs.InnerSeries.GetValues(index) | ||||
| 	x = x0 | ||||
| 	y = PercentDifference(fy, y0) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // GetValueFormatters returns value formatter defaults for the series. | ||||
| func (pcs PercentChangeSeries) GetValueFormatters() (x, y ValueFormatter) { | ||||
| 	x, _ = pcs.InnerSeries.GetValueFormatters() | ||||
| 	y = PercentValueFormatter | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // GetYAxis returns which YAxis the series draws on. | ||||
| func (pcs PercentChangeSeries) GetYAxis() YAxisType { | ||||
| 	return pcs.YAxis | ||||
| } | ||||
|  | ||||
| // GetLastValues gets the last values. | ||||
| func (pcs PercentChangeSeries) GetLastValues() (x, y float64) { | ||||
| 	_, fy := pcs.InnerSeries.GetFirstValues() | ||||
| 	x0, y0 := pcs.InnerSeries.GetLastValues() | ||||
| 	x = x0 | ||||
| 	y = PercentDifference(fy, y0) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Render renders the series. | ||||
| func (pcs PercentChangeSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { | ||||
| 	style := pcs.Style.InheritFrom(defaults) | ||||
| 	Draw.LineSeries(r, canvasBox, xrange, yrange, style, pcs) | ||||
| } | ||||
|  | ||||
| // Validate validates the series. | ||||
| func (pcs PercentChangeSeries) Validate() error { | ||||
| 	return pcs.InnerSeries.Validate() | ||||
| } | ||||
							
								
								
									
										311
									
								
								vendor/github.com/wcharczuk/go-chart/v2/pie_chart.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										311
									
								
								vendor/github.com/wcharczuk/go-chart/v2/pie_chart.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,311 @@ | ||||
| package chart | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
|  | ||||
| 	"github.com/golang/freetype/truetype" | ||||
| ) | ||||
|  | ||||
| // PieChart is a chart that draws sections of a circle based on percentages. | ||||
| type PieChart struct { | ||||
| 	Title      string | ||||
| 	TitleStyle Style | ||||
|  | ||||
| 	ColorPalette ColorPalette | ||||
|  | ||||
| 	Width  int | ||||
| 	Height int | ||||
| 	DPI    float64 | ||||
|  | ||||
| 	Background Style | ||||
| 	Canvas     Style | ||||
| 	SliceStyle Style | ||||
|  | ||||
| 	Font        *truetype.Font | ||||
| 	defaultFont *truetype.Font | ||||
|  | ||||
| 	Values   []Value | ||||
| 	Elements []Renderable | ||||
| } | ||||
|  | ||||
| // GetDPI returns the dpi for the chart. | ||||
| func (pc PieChart) GetDPI(defaults ...float64) float64 { | ||||
| 	if pc.DPI == 0 { | ||||
| 		if len(defaults) > 0 { | ||||
| 			return defaults[0] | ||||
| 		} | ||||
| 		return DefaultDPI | ||||
| 	} | ||||
| 	return pc.DPI | ||||
| } | ||||
|  | ||||
| // GetFont returns the text font. | ||||
| func (pc PieChart) GetFont() *truetype.Font { | ||||
| 	if pc.Font == nil { | ||||
| 		return pc.defaultFont | ||||
| 	} | ||||
| 	return pc.Font | ||||
| } | ||||
|  | ||||
| // GetWidth returns the chart width or the default value. | ||||
| func (pc PieChart) GetWidth() int { | ||||
| 	if pc.Width == 0 { | ||||
| 		return DefaultChartWidth | ||||
| 	} | ||||
| 	return pc.Width | ||||
| } | ||||
|  | ||||
| // GetHeight returns the chart height or the default value. | ||||
| func (pc PieChart) GetHeight() int { | ||||
| 	if pc.Height == 0 { | ||||
| 		return DefaultChartWidth | ||||
| 	} | ||||
| 	return pc.Height | ||||
| } | ||||
|  | ||||
| // Render renders the chart with the given renderer to the given io.Writer. | ||||
| func (pc PieChart) Render(rp RendererProvider, w io.Writer) error { | ||||
| 	if len(pc.Values) == 0 { | ||||
| 		return errors.New("please provide at least one value") | ||||
| 	} | ||||
|  | ||||
| 	r, err := rp(pc.GetWidth(), pc.GetHeight()) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if pc.Font == nil { | ||||
| 		defaultFont, err := GetDefaultFont() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		pc.defaultFont = defaultFont | ||||
| 	} | ||||
| 	r.SetDPI(pc.GetDPI(DefaultDPI)) | ||||
|  | ||||
| 	canvasBox := pc.getDefaultCanvasBox() | ||||
| 	canvasBox = pc.getCircleAdjustedCanvasBox(canvasBox) | ||||
|  | ||||
| 	pc.drawBackground(r) | ||||
| 	pc.drawCanvas(r, canvasBox) | ||||
|  | ||||
| 	finalValues, err := pc.finalizeValues(pc.Values) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	pc.drawSlices(r, canvasBox, finalValues) | ||||
| 	pc.drawTitle(r) | ||||
| 	for _, a := range pc.Elements { | ||||
| 		a(r, canvasBox, pc.styleDefaultsElements()) | ||||
| 	} | ||||
|  | ||||
| 	return r.Save(w) | ||||
| } | ||||
|  | ||||
| func (pc PieChart) drawBackground(r Renderer) { | ||||
| 	Draw.Box(r, Box{ | ||||
| 		Right:  pc.GetWidth(), | ||||
| 		Bottom: pc.GetHeight(), | ||||
| 	}, pc.getBackgroundStyle()) | ||||
| } | ||||
|  | ||||
| func (pc PieChart) drawCanvas(r Renderer, canvasBox Box) { | ||||
| 	Draw.Box(r, canvasBox, pc.getCanvasStyle()) | ||||
| } | ||||
|  | ||||
| func (pc PieChart) drawTitle(r Renderer) { | ||||
| 	if len(pc.Title) > 0 && !pc.TitleStyle.Hidden { | ||||
| 		Draw.TextWithin(r, pc.Title, pc.Box(), pc.styleDefaultsTitle()) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pc PieChart) drawSlices(r Renderer, canvasBox Box, values []Value) { | ||||
| 	cx, cy := canvasBox.Center() | ||||
| 	diameter := MinInt(canvasBox.Width(), canvasBox.Height()) | ||||
| 	radius := float64(diameter >> 1) | ||||
| 	labelRadius := (radius * 2.0) / 3.0 | ||||
|  | ||||
| 	// draw the pie slices | ||||
| 	var rads, delta, delta2, total float64 | ||||
| 	var lx, ly int | ||||
|  | ||||
| 	if len(values) == 1 { | ||||
| 		pc.stylePieChartValue(0).WriteToRenderer(r) | ||||
| 		r.MoveTo(cx, cy) | ||||
| 		r.Circle(radius, cx, cy) | ||||
| 	} else { | ||||
| 		for index, v := range values { | ||||
| 			v.Style.InheritFrom(pc.stylePieChartValue(index)).WriteToRenderer(r) | ||||
|  | ||||
| 			r.MoveTo(cx, cy) | ||||
| 			rads = PercentToRadians(total) | ||||
| 			delta = PercentToRadians(v.Value) | ||||
|  | ||||
| 			r.ArcTo(cx, cy, radius, radius, rads, delta) | ||||
|  | ||||
| 			r.LineTo(cx, cy) | ||||
| 			r.Close() | ||||
| 			r.FillStroke() | ||||
| 			total = total + v.Value | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// draw the labels | ||||
| 	total = 0 | ||||
| 	for index, v := range values { | ||||
| 		v.Style.InheritFrom(pc.stylePieChartValue(index)).WriteToRenderer(r) | ||||
| 		if len(v.Label) > 0 { | ||||
| 			delta2 = PercentToRadians(total + (v.Value / 2.0)) | ||||
| 			delta2 = RadianAdd(delta2, _pi2) | ||||
| 			lx, ly = CirclePoint(cx, cy, labelRadius, delta2) | ||||
|  | ||||
| 			tb := r.MeasureText(v.Label) | ||||
| 			lx = lx - (tb.Width() >> 1) | ||||
| 			ly = ly + (tb.Height() >> 1) | ||||
|  | ||||
| 			if lx < 0 { | ||||
| 				lx = 0 | ||||
| 			} | ||||
| 			if ly < 0 { | ||||
| 				lx = 0 | ||||
| 			} | ||||
|  | ||||
| 			r.Text(v.Label, lx, ly) | ||||
| 		} | ||||
| 		total = total + v.Value | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pc PieChart) finalizeValues(values []Value) ([]Value, error) { | ||||
| 	finalValues := Values(values).Normalize() | ||||
| 	if len(finalValues) == 0 { | ||||
| 		return nil, fmt.Errorf("pie chart must contain at least (1) non-zero value") | ||||
| 	} | ||||
| 	return finalValues, nil | ||||
| } | ||||
|  | ||||
| func (pc PieChart) getDefaultCanvasBox() Box { | ||||
| 	return pc.Box() | ||||
| } | ||||
|  | ||||
| func (pc PieChart) getCircleAdjustedCanvasBox(canvasBox Box) Box { | ||||
| 	circleDiameter := MinInt(canvasBox.Width(), canvasBox.Height()) | ||||
|  | ||||
| 	square := Box{ | ||||
| 		Right:  circleDiameter, | ||||
| 		Bottom: circleDiameter, | ||||
| 	} | ||||
|  | ||||
| 	return canvasBox.Fit(square) | ||||
| } | ||||
|  | ||||
| func (pc PieChart) getBackgroundStyle() Style { | ||||
| 	return pc.Background.InheritFrom(pc.styleDefaultsBackground()) | ||||
| } | ||||
|  | ||||
| func (pc PieChart) getCanvasStyle() Style { | ||||
| 	return pc.Canvas.InheritFrom(pc.styleDefaultsCanvas()) | ||||
| } | ||||
|  | ||||
| func (pc PieChart) styleDefaultsCanvas() Style { | ||||
| 	return Style{ | ||||
| 		FillColor:   pc.GetColorPalette().CanvasColor(), | ||||
| 		StrokeColor: pc.GetColorPalette().CanvasStrokeColor(), | ||||
| 		StrokeWidth: DefaultStrokeWidth, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pc PieChart) styleDefaultsPieChartValue() Style { | ||||
| 	return Style{ | ||||
| 		StrokeColor: pc.GetColorPalette().TextColor(), | ||||
| 		StrokeWidth: 5.0, | ||||
| 		FillColor:   pc.GetColorPalette().TextColor(), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pc PieChart) stylePieChartValue(index int) Style { | ||||
| 	return pc.SliceStyle.InheritFrom(Style{ | ||||
| 		StrokeColor: ColorWhite, | ||||
| 		StrokeWidth: 5.0, | ||||
| 		FillColor:   pc.GetColorPalette().GetSeriesColor(index), | ||||
| 		FontSize:    pc.getScaledFontSize(), | ||||
| 		FontColor:   pc.GetColorPalette().TextColor(), | ||||
| 		Font:        pc.GetFont(), | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func (pc PieChart) getScaledFontSize() float64 { | ||||
| 	effectiveDimension := MinInt(pc.GetWidth(), pc.GetHeight()) | ||||
| 	if effectiveDimension >= 2048 { | ||||
| 		return 48.0 | ||||
| 	} else if effectiveDimension >= 1024 { | ||||
| 		return 24.0 | ||||
| 	} else if effectiveDimension > 512 { | ||||
| 		return 18.0 | ||||
| 	} else if effectiveDimension > 256 { | ||||
| 		return 12.0 | ||||
| 	} | ||||
| 	return 10.0 | ||||
| } | ||||
|  | ||||
| func (pc PieChart) styleDefaultsBackground() Style { | ||||
| 	return Style{ | ||||
| 		FillColor:   pc.GetColorPalette().BackgroundColor(), | ||||
| 		StrokeColor: pc.GetColorPalette().BackgroundStrokeColor(), | ||||
| 		StrokeWidth: DefaultStrokeWidth, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pc PieChart) styleDefaultsElements() Style { | ||||
| 	return Style{ | ||||
| 		Font: pc.GetFont(), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pc PieChart) styleDefaultsTitle() Style { | ||||
| 	return pc.TitleStyle.InheritFrom(Style{ | ||||
| 		FontColor:           pc.GetColorPalette().TextColor(), | ||||
| 		Font:                pc.GetFont(), | ||||
| 		FontSize:            pc.getTitleFontSize(), | ||||
| 		TextHorizontalAlign: TextHorizontalAlignCenter, | ||||
| 		TextVerticalAlign:   TextVerticalAlignTop, | ||||
| 		TextWrap:            TextWrapWord, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func (pc PieChart) getTitleFontSize() float64 { | ||||
| 	effectiveDimension := MinInt(pc.GetWidth(), pc.GetHeight()) | ||||
| 	if effectiveDimension >= 2048 { | ||||
| 		return 48 | ||||
| 	} else if effectiveDimension >= 1024 { | ||||
| 		return 24 | ||||
| 	} else if effectiveDimension >= 512 { | ||||
| 		return 18 | ||||
| 	} else if effectiveDimension >= 256 { | ||||
| 		return 12 | ||||
| 	} | ||||
| 	return 10 | ||||
| } | ||||
|  | ||||
| // GetColorPalette returns the color palette for the chart. | ||||
| func (pc PieChart) GetColorPalette() ColorPalette { | ||||
| 	if pc.ColorPalette != nil { | ||||
| 		return pc.ColorPalette | ||||
| 	} | ||||
| 	return AlternateColorPalette | ||||
| } | ||||
|  | ||||
| // Box returns the chart bounds as a box. | ||||
| func (pc PieChart) Box() Box { | ||||
| 	dpr := pc.Background.Padding.GetRight(DefaultBackgroundPadding.Right) | ||||
| 	dpb := pc.Background.Padding.GetBottom(DefaultBackgroundPadding.Bottom) | ||||
|  | ||||
| 	return Box{ | ||||
| 		Top:    pc.Background.Padding.GetTop(DefaultBackgroundPadding.Top), | ||||
| 		Left:   pc.Background.Padding.GetLeft(DefaultBackgroundPadding.Left), | ||||
| 		Right:  pc.GetWidth() - dpr, | ||||
| 		Bottom: pc.GetHeight() - dpb, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										177
									
								
								vendor/github.com/wcharczuk/go-chart/v2/polynomial_regression_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								vendor/github.com/wcharczuk/go-chart/v2/polynomial_regression_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,177 @@ | ||||
| package chart | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"math" | ||||
|  | ||||
| 	"github.com/wcharczuk/go-chart/v2/matrix" | ||||
| ) | ||||
|  | ||||
| // Interface Assertions. | ||||
| var ( | ||||
| 	_ Series              = (*PolynomialRegressionSeries)(nil) | ||||
| 	_ FirstValuesProvider = (*PolynomialRegressionSeries)(nil) | ||||
| 	_ LastValuesProvider  = (*PolynomialRegressionSeries)(nil) | ||||
| ) | ||||
|  | ||||
| // PolynomialRegressionSeries implements a polynomial regression over a given | ||||
| // inner series. | ||||
| type PolynomialRegressionSeries struct { | ||||
| 	Name  string | ||||
| 	Style Style | ||||
| 	YAxis YAxisType | ||||
|  | ||||
| 	Limit       int | ||||
| 	Offset      int | ||||
| 	Degree      int | ||||
| 	InnerSeries ValuesProvider | ||||
|  | ||||
| 	coeffs []float64 | ||||
| } | ||||
|  | ||||
| // GetName returns the name of the time series. | ||||
| func (prs PolynomialRegressionSeries) GetName() string { | ||||
| 	return prs.Name | ||||
| } | ||||
|  | ||||
| // GetStyle returns the line style. | ||||
| func (prs PolynomialRegressionSeries) GetStyle() Style { | ||||
| 	return prs.Style | ||||
| } | ||||
|  | ||||
| // GetYAxis returns which YAxis the series draws on. | ||||
| func (prs PolynomialRegressionSeries) GetYAxis() YAxisType { | ||||
| 	return prs.YAxis | ||||
| } | ||||
|  | ||||
| // Len returns the number of elements in the series. | ||||
| func (prs PolynomialRegressionSeries) Len() int { | ||||
| 	return MinInt(prs.GetLimit(), prs.InnerSeries.Len()-prs.GetOffset()) | ||||
| } | ||||
|  | ||||
| // GetLimit returns the window size. | ||||
| func (prs PolynomialRegressionSeries) GetLimit() int { | ||||
| 	if prs.Limit == 0 { | ||||
| 		return prs.InnerSeries.Len() | ||||
| 	} | ||||
| 	return prs.Limit | ||||
| } | ||||
|  | ||||
| // GetEndIndex returns the effective limit end. | ||||
| func (prs PolynomialRegressionSeries) GetEndIndex() int { | ||||
| 	windowEnd := prs.GetOffset() + prs.GetLimit() | ||||
| 	innerSeriesLastIndex := prs.InnerSeries.Len() - 1 | ||||
| 	return MinInt(windowEnd, innerSeriesLastIndex) | ||||
| } | ||||
|  | ||||
| // GetOffset returns the data offset. | ||||
| func (prs PolynomialRegressionSeries) GetOffset() int { | ||||
| 	if prs.Offset == 0 { | ||||
| 		return 0 | ||||
| 	} | ||||
| 	return prs.Offset | ||||
| } | ||||
|  | ||||
| // Validate validates the series. | ||||
| func (prs *PolynomialRegressionSeries) Validate() error { | ||||
| 	if prs.InnerSeries == nil { | ||||
| 		return fmt.Errorf("linear regression series requires InnerSeries to be set") | ||||
| 	} | ||||
|  | ||||
| 	endIndex := prs.GetEndIndex() | ||||
| 	if endIndex >= prs.InnerSeries.Len() { | ||||
| 		return fmt.Errorf("invalid window; inner series has length %d but end index is %d", prs.InnerSeries.Len(), endIndex) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // GetValues returns the series value for a given index. | ||||
| func (prs *PolynomialRegressionSeries) GetValues(index int) (x, y float64) { | ||||
| 	if prs.InnerSeries == nil || prs.InnerSeries.Len() == 0 { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if prs.coeffs == nil { | ||||
| 		coeffs, err := prs.computeCoefficients() | ||||
| 		if err != nil { | ||||
| 			panic(err) | ||||
| 		} | ||||
| 		prs.coeffs = coeffs | ||||
| 	} | ||||
|  | ||||
| 	offset := prs.GetOffset() | ||||
| 	effectiveIndex := MinInt(index+offset, prs.InnerSeries.Len()) | ||||
| 	x, y = prs.InnerSeries.GetValues(effectiveIndex) | ||||
| 	y = prs.apply(x) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // GetFirstValues computes the first poly regression value. | ||||
| func (prs *PolynomialRegressionSeries) GetFirstValues() (x, y float64) { | ||||
| 	if prs.InnerSeries == nil || prs.InnerSeries.Len() == 0 { | ||||
| 		return | ||||
| 	} | ||||
| 	if prs.coeffs == nil { | ||||
| 		coeffs, err := prs.computeCoefficients() | ||||
| 		if err != nil { | ||||
| 			panic(err) | ||||
| 		} | ||||
| 		prs.coeffs = coeffs | ||||
| 	} | ||||
| 	x, y = prs.InnerSeries.GetValues(0) | ||||
| 	y = prs.apply(x) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // GetLastValues computes the last poly regression value. | ||||
| func (prs *PolynomialRegressionSeries) GetLastValues() (x, y float64) { | ||||
| 	if prs.InnerSeries == nil || prs.InnerSeries.Len() == 0 { | ||||
| 		return | ||||
| 	} | ||||
| 	if prs.coeffs == nil { | ||||
| 		coeffs, err := prs.computeCoefficients() | ||||
| 		if err != nil { | ||||
| 			panic(err) | ||||
| 		} | ||||
| 		prs.coeffs = coeffs | ||||
| 	} | ||||
| 	endIndex := prs.GetEndIndex() | ||||
| 	x, y = prs.InnerSeries.GetValues(endIndex) | ||||
| 	y = prs.apply(x) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (prs *PolynomialRegressionSeries) apply(v float64) (out float64) { | ||||
| 	for index, coeff := range prs.coeffs { | ||||
| 		out = out + (coeff * math.Pow(v, float64(index))) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (prs *PolynomialRegressionSeries) computeCoefficients() ([]float64, error) { | ||||
| 	xvalues, yvalues := prs.values() | ||||
| 	return matrix.Poly(xvalues, yvalues, prs.Degree) | ||||
| } | ||||
|  | ||||
| func (prs *PolynomialRegressionSeries) values() (xvalues, yvalues []float64) { | ||||
| 	startIndex := prs.GetOffset() | ||||
| 	endIndex := prs.GetEndIndex() | ||||
|  | ||||
| 	xvalues = make([]float64, endIndex-startIndex) | ||||
| 	yvalues = make([]float64, endIndex-startIndex) | ||||
|  | ||||
| 	for index := startIndex; index < endIndex; index++ { | ||||
| 		x, y := prs.InnerSeries.GetValues(index) | ||||
| 		xvalues[index-startIndex] = x | ||||
| 		yvalues[index-startIndex] = y | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Render renders the series. | ||||
| func (prs *PolynomialRegressionSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { | ||||
| 	style := prs.Style.InheritFrom(defaults) | ||||
| 	Draw.LineSeries(r, canvasBox, xrange, yrange, style, prs) | ||||
| } | ||||
							
								
								
									
										92
									
								
								vendor/github.com/wcharczuk/go-chart/v2/random_sequence.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								vendor/github.com/wcharczuk/go-chart/v2/random_sequence.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,92 @@ | ||||
| package chart | ||||
|  | ||||
| import ( | ||||
| 	"math" | ||||
| 	"math/rand" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	_ Sequence = (*RandomSeq)(nil) | ||||
| ) | ||||
|  | ||||
| // RandomValues returns an array of random values. | ||||
| func RandomValues(count int) []float64 { | ||||
| 	return Seq{NewRandomSequence().WithLen(count)}.Values() | ||||
| } | ||||
|  | ||||
| // RandomValuesWithMax returns an array of random values with a given average. | ||||
| func RandomValuesWithMax(count int, max float64) []float64 { | ||||
| 	return Seq{NewRandomSequence().WithMax(max).WithLen(count)}.Values() | ||||
| } | ||||
|  | ||||
| // NewRandomSequence creates a new random seq. | ||||
| func NewRandomSequence() *RandomSeq { | ||||
| 	return &RandomSeq{ | ||||
| 		rnd: rand.New(rand.NewSource(time.Now().Unix())), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // RandomSeq is a random number seq generator. | ||||
| type RandomSeq struct { | ||||
| 	rnd *rand.Rand | ||||
| 	max *float64 | ||||
| 	min *float64 | ||||
| 	len *int | ||||
| } | ||||
|  | ||||
| // Len returns the number of elements that will be generated. | ||||
| func (r *RandomSeq) Len() int { | ||||
| 	if r.len != nil { | ||||
| 		return *r.len | ||||
| 	} | ||||
| 	return math.MaxInt32 | ||||
| } | ||||
|  | ||||
| // GetValue returns the value. | ||||
| func (r *RandomSeq) GetValue(_ int) float64 { | ||||
| 	if r.min != nil && r.max != nil { | ||||
| 		var delta float64 | ||||
|  | ||||
| 		if *r.max > *r.min { | ||||
| 			delta = *r.max - *r.min | ||||
| 		} else { | ||||
| 			delta = *r.min - *r.max | ||||
| 		} | ||||
|  | ||||
| 		return *r.min + (r.rnd.Float64() * delta) | ||||
| 	} else if r.max != nil { | ||||
| 		return r.rnd.Float64() * *r.max | ||||
| 	} else if r.min != nil { | ||||
| 		return *r.min + (r.rnd.Float64()) | ||||
| 	} | ||||
| 	return r.rnd.Float64() | ||||
| } | ||||
|  | ||||
| // WithLen sets a maximum len | ||||
| func (r *RandomSeq) WithLen(length int) *RandomSeq { | ||||
| 	r.len = &length | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| // Min returns the minimum value. | ||||
| func (r RandomSeq) Min() *float64 { | ||||
| 	return r.min | ||||
| } | ||||
|  | ||||
| // WithMin sets the scale and returns the Random. | ||||
| func (r *RandomSeq) WithMin(min float64) *RandomSeq { | ||||
| 	r.min = &min | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| // Max returns the maximum value. | ||||
| func (r RandomSeq) Max() *float64 { | ||||
| 	return r.max | ||||
| } | ||||
|  | ||||
| // WithMax sets the average and returns the Random. | ||||
| func (r *RandomSeq) WithMax(max float64) *RandomSeq { | ||||
| 	r.max = &max | ||||
| 	return r | ||||
| } | ||||
							
								
								
									
										43
									
								
								vendor/github.com/wcharczuk/go-chart/v2/range.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								vendor/github.com/wcharczuk/go-chart/v2/range.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | ||||
| package chart | ||||
|  | ||||
| // NameProvider is a type that returns a name. | ||||
| type NameProvider interface { | ||||
| 	GetName() string | ||||
| } | ||||
|  | ||||
| // StyleProvider is a type that returns a style. | ||||
| type StyleProvider interface { | ||||
| 	GetStyle() Style | ||||
| } | ||||
|  | ||||
| // IsZeroable is a type that returns if it's been set or not. | ||||
| type IsZeroable interface { | ||||
| 	IsZero() bool | ||||
| } | ||||
|  | ||||
| // Stringable is a type that has a string representation. | ||||
| type Stringable interface { | ||||
| 	String() string | ||||
| } | ||||
|  | ||||
| // Range is a common interface for a range of values. | ||||
| type Range interface { | ||||
| 	Stringable | ||||
| 	IsZeroable | ||||
|  | ||||
| 	GetMin() float64 | ||||
| 	SetMin(min float64) | ||||
|  | ||||
| 	GetMax() float64 | ||||
| 	SetMax(max float64) | ||||
|  | ||||
| 	GetDelta() float64 | ||||
|  | ||||
| 	GetDomain() int | ||||
| 	SetDomain(domain int) | ||||
|  | ||||
| 	IsDescending() bool | ||||
|  | ||||
| 	// Translate the range to the domain. | ||||
| 	Translate(value float64) int | ||||
| } | ||||
							
								
								
									
										230
									
								
								vendor/github.com/wcharczuk/go-chart/v2/raster_renderer.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								vendor/github.com/wcharczuk/go-chart/v2/raster_renderer.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,230 @@ | ||||
| package chart | ||||
|  | ||||
| import ( | ||||
| 	"image" | ||||
| 	"image/png" | ||||
| 	"io" | ||||
| 	"math" | ||||
|  | ||||
| 	"github.com/golang/freetype/truetype" | ||||
| 	"github.com/wcharczuk/go-chart/v2/drawing" | ||||
| ) | ||||
|  | ||||
| // PNG returns a new png/raster renderer. | ||||
| func PNG(width, height int) (Renderer, error) { | ||||
| 	i := image.NewRGBA(image.Rect(0, 0, width, height)) | ||||
| 	gc, err := drawing.NewRasterGraphicContext(i) | ||||
| 	if err == nil { | ||||
| 		return &rasterRenderer{ | ||||
| 			i:  i, | ||||
| 			gc: gc, | ||||
| 		}, nil | ||||
| 	} | ||||
| 	return nil, err | ||||
| } | ||||
|  | ||||
| // rasterRenderer renders chart commands to a bitmap. | ||||
| type rasterRenderer struct { | ||||
| 	i  *image.RGBA | ||||
| 	gc *drawing.RasterGraphicContext | ||||
|  | ||||
| 	rotateRadians *float64 | ||||
|  | ||||
| 	s Style | ||||
| } | ||||
|  | ||||
| func (rr *rasterRenderer) ResetStyle() { | ||||
| 	rr.s = Style{Font: rr.s.Font} | ||||
| 	rr.ClearTextRotation() | ||||
| } | ||||
|  | ||||
| // GetDPI returns the dpi. | ||||
| func (rr *rasterRenderer) GetDPI() float64 { | ||||
| 	return rr.gc.GetDPI() | ||||
| } | ||||
|  | ||||
| // SetDPI implements the interface method. | ||||
| func (rr *rasterRenderer) SetDPI(dpi float64) { | ||||
| 	rr.gc.SetDPI(dpi) | ||||
| } | ||||
|  | ||||
| // SetClassName implements the interface method. However, PNGs have no classes. | ||||
| func (rr *rasterRenderer) SetClassName(_ string) {} | ||||
|  | ||||
| // SetStrokeColor implements the interface method. | ||||
| func (rr *rasterRenderer) SetStrokeColor(c drawing.Color) { | ||||
| 	rr.s.StrokeColor = c | ||||
| } | ||||
|  | ||||
| // SetLineWidth implements the interface method. | ||||
| func (rr *rasterRenderer) SetStrokeWidth(width float64) { | ||||
| 	rr.s.StrokeWidth = width | ||||
| } | ||||
|  | ||||
| // StrokeDashArray sets the stroke dash array. | ||||
| func (rr *rasterRenderer) SetStrokeDashArray(dashArray []float64) { | ||||
| 	rr.s.StrokeDashArray = dashArray | ||||
| } | ||||
|  | ||||
| // SetFillColor implements the interface method. | ||||
| func (rr *rasterRenderer) SetFillColor(c drawing.Color) { | ||||
| 	rr.s.FillColor = c | ||||
| } | ||||
|  | ||||
| // MoveTo implements the interface method. | ||||
| func (rr *rasterRenderer) MoveTo(x, y int) { | ||||
| 	rr.gc.MoveTo(float64(x), float64(y)) | ||||
| } | ||||
|  | ||||
| // LineTo implements the interface method. | ||||
| func (rr *rasterRenderer) LineTo(x, y int) { | ||||
| 	rr.gc.LineTo(float64(x), float64(y)) | ||||
| } | ||||
|  | ||||
| // QuadCurveTo implements the interface method. | ||||
| func (rr *rasterRenderer) QuadCurveTo(cx, cy, x, y int) { | ||||
| 	rr.gc.QuadCurveTo(float64(cx), float64(cy), float64(x), float64(y)) | ||||
| } | ||||
|  | ||||
| // ArcTo implements the interface method. | ||||
| func (rr *rasterRenderer) ArcTo(cx, cy int, rx, ry, startAngle, delta float64) { | ||||
| 	rr.gc.ArcTo(float64(cx), float64(cy), rx, ry, startAngle, delta) | ||||
| } | ||||
|  | ||||
| // Close implements the interface method. | ||||
| func (rr *rasterRenderer) Close() { | ||||
| 	rr.gc.Close() | ||||
| } | ||||
|  | ||||
| // Stroke implements the interface method. | ||||
| func (rr *rasterRenderer) Stroke() { | ||||
| 	rr.gc.SetStrokeColor(rr.s.StrokeColor) | ||||
| 	rr.gc.SetLineWidth(rr.s.StrokeWidth) | ||||
| 	rr.gc.SetLineDash(rr.s.StrokeDashArray, 0) | ||||
| 	rr.gc.Stroke() | ||||
| } | ||||
|  | ||||
| // Fill implements the interface method. | ||||
| func (rr *rasterRenderer) Fill() { | ||||
| 	rr.gc.SetFillColor(rr.s.FillColor) | ||||
| 	rr.gc.Fill() | ||||
| } | ||||
|  | ||||
| // FillStroke implements the interface method. | ||||
| func (rr *rasterRenderer) FillStroke() { | ||||
| 	rr.gc.SetFillColor(rr.s.FillColor) | ||||
| 	rr.gc.SetStrokeColor(rr.s.StrokeColor) | ||||
| 	rr.gc.SetLineWidth(rr.s.StrokeWidth) | ||||
| 	rr.gc.SetLineDash(rr.s.StrokeDashArray, 0) | ||||
| 	rr.gc.FillStroke() | ||||
| } | ||||
|  | ||||
| // Circle fully draws a circle at a given point but does not apply the fill or stroke. | ||||
| func (rr *rasterRenderer) Circle(radius float64, x, y int) { | ||||
| 	xf := float64(x) | ||||
| 	yf := float64(y) | ||||
|  | ||||
| 	rr.gc.MoveTo(xf-radius, yf)                            //9 | ||||
| 	rr.gc.QuadCurveTo(xf-radius, yf-radius, xf, yf-radius) //12 | ||||
| 	rr.gc.QuadCurveTo(xf+radius, yf-radius, xf+radius, yf) //3 | ||||
| 	rr.gc.QuadCurveTo(xf+radius, yf+radius, xf, yf+radius) //6 | ||||
| 	rr.gc.QuadCurveTo(xf-radius, yf+radius, xf-radius, yf) //9 | ||||
| } | ||||
|  | ||||
| // SetFont implements the interface method. | ||||
| func (rr *rasterRenderer) SetFont(f *truetype.Font) { | ||||
| 	rr.s.Font = f | ||||
| } | ||||
|  | ||||
| // SetFontSize implements the interface method. | ||||
| func (rr *rasterRenderer) SetFontSize(size float64) { | ||||
| 	rr.s.FontSize = size | ||||
| } | ||||
|  | ||||
| // SetFontColor implements the interface method. | ||||
| func (rr *rasterRenderer) SetFontColor(c drawing.Color) { | ||||
| 	rr.s.FontColor = c | ||||
| } | ||||
|  | ||||
| // Text implements the interface method. | ||||
| func (rr *rasterRenderer) Text(body string, x, y int) { | ||||
| 	xf, yf := rr.getCoords(x, y) | ||||
| 	rr.gc.SetFont(rr.s.Font) | ||||
| 	rr.gc.SetFontSize(rr.s.FontSize) | ||||
| 	rr.gc.SetFillColor(rr.s.FontColor) | ||||
| 	rr.gc.CreateStringPath(body, float64(xf), float64(yf)) | ||||
| 	rr.gc.Fill() | ||||
| } | ||||
|  | ||||
| // MeasureText returns the height and width in pixels of a string. | ||||
| func (rr *rasterRenderer) MeasureText(body string) Box { | ||||
| 	rr.gc.SetFont(rr.s.Font) | ||||
| 	rr.gc.SetFontSize(rr.s.FontSize) | ||||
| 	rr.gc.SetFillColor(rr.s.FontColor) | ||||
| 	l, t, r, b, err := rr.gc.GetStringBounds(body) | ||||
| 	if err != nil { | ||||
| 		return Box{} | ||||
| 	} | ||||
| 	if l < 0 { | ||||
| 		r = r - l // equivalent to r+(-1*l) | ||||
| 		l = 0 | ||||
| 	} | ||||
| 	if t < 0 { | ||||
| 		b = b - t | ||||
| 		t = 0 | ||||
| 	} | ||||
|  | ||||
| 	if l > 0 { | ||||
| 		r = r + l | ||||
| 		l = 0 | ||||
| 	} | ||||
|  | ||||
| 	if t > 0 { | ||||
| 		b = b + t | ||||
| 		t = 0 | ||||
| 	} | ||||
|  | ||||
| 	textBox := Box{ | ||||
| 		Top:    int(math.Ceil(t)), | ||||
| 		Left:   int(math.Ceil(l)), | ||||
| 		Right:  int(math.Ceil(r)), | ||||
| 		Bottom: int(math.Ceil(b)), | ||||
| 	} | ||||
| 	if rr.rotateRadians == nil { | ||||
| 		return textBox | ||||
| 	} | ||||
|  | ||||
| 	return textBox.Corners().Rotate(RadiansToDegrees(*rr.rotateRadians)).Box() | ||||
| } | ||||
|  | ||||
| // SetTextRotation sets a text rotation. | ||||
| func (rr *rasterRenderer) SetTextRotation(radians float64) { | ||||
| 	rr.rotateRadians = &radians | ||||
| } | ||||
|  | ||||
| func (rr *rasterRenderer) getCoords(x, y int) (xf, yf int) { | ||||
| 	if rr.rotateRadians == nil { | ||||
| 		xf = x | ||||
| 		yf = y | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	rr.gc.Translate(float64(x), float64(y)) | ||||
| 	rr.gc.Rotate(*rr.rotateRadians) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // ClearTextRotation clears text rotation. | ||||
| func (rr *rasterRenderer) ClearTextRotation() { | ||||
| 	rr.gc.SetMatrixTransform(drawing.NewIdentityMatrix()) | ||||
| 	rr.rotateRadians = nil | ||||
| } | ||||
|  | ||||
| // Save implements the interface method. | ||||
| func (rr *rasterRenderer) Save(w io.Writer) error { | ||||
| 	if typed, isTyped := w.(RGBACollector); isTyped { | ||||
| 		typed.SetRGBA(rr.i) | ||||
| 		return nil | ||||
| 	} | ||||
| 	return png.Encode(w, rr.i) | ||||
| } | ||||
							
								
								
									
										4
									
								
								vendor/github.com/wcharczuk/go-chart/v2/renderable.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								vendor/github.com/wcharczuk/go-chart/v2/renderable.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| package chart | ||||
|  | ||||
| // Renderable is a function that can be called to render custom elements on the chart. | ||||
| type Renderable func(r Renderer, canvasBox Box, defaults Style) | ||||
							
								
								
									
										89
									
								
								vendor/github.com/wcharczuk/go-chart/v2/renderer.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								vendor/github.com/wcharczuk/go-chart/v2/renderer.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,89 @@ | ||||
| package chart | ||||
|  | ||||
| import ( | ||||
| 	"io" | ||||
|  | ||||
| 	"github.com/golang/freetype/truetype" | ||||
| 	"github.com/wcharczuk/go-chart/v2/drawing" | ||||
| ) | ||||
|  | ||||
| // Renderer represents the basic methods required to draw a chart. | ||||
| type Renderer interface { | ||||
| 	// ResetStyle should reset any style related settings on the renderer. | ||||
| 	ResetStyle() | ||||
|  | ||||
| 	// GetDPI gets the DPI for the renderer. | ||||
| 	GetDPI() float64 | ||||
|  | ||||
| 	// SetDPI sets the DPI for the renderer. | ||||
| 	SetDPI(dpi float64) | ||||
|  | ||||
| 	// SetClassName sets the current class name. | ||||
| 	SetClassName(string) | ||||
|  | ||||
| 	// SetStrokeColor sets the current stroke color. | ||||
| 	SetStrokeColor(drawing.Color) | ||||
|  | ||||
| 	// SetFillColor sets the current fill color. | ||||
| 	SetFillColor(drawing.Color) | ||||
|  | ||||
| 	// SetStrokeWidth sets the stroke width. | ||||
| 	SetStrokeWidth(width float64) | ||||
|  | ||||
| 	// SetStrokeDashArray sets the stroke dash array. | ||||
| 	SetStrokeDashArray(dashArray []float64) | ||||
|  | ||||
| 	// MoveTo moves the cursor to a given point. | ||||
| 	MoveTo(x, y int) | ||||
|  | ||||
| 	// LineTo both starts a shape and draws a line to a given point | ||||
| 	// from the previous point. | ||||
| 	LineTo(x, y int) | ||||
|  | ||||
| 	// QuadCurveTo draws a quad curve. | ||||
| 	// cx and cy represent the bezier "control points". | ||||
| 	QuadCurveTo(cx, cy, x, y int) | ||||
|  | ||||
| 	// ArcTo draws an arc with a given center (cx,cy) | ||||
| 	// a given set of radii (rx,ry), a startAngle and delta (in radians). | ||||
| 	ArcTo(cx, cy int, rx, ry, startAngle, delta float64) | ||||
|  | ||||
| 	// Close finalizes a shape as drawn by LineTo. | ||||
| 	Close() | ||||
|  | ||||
| 	// Stroke strokes the path. | ||||
| 	Stroke() | ||||
|  | ||||
| 	// Fill fills the path, but does not stroke. | ||||
| 	Fill() | ||||
|  | ||||
| 	// FillStroke fills and strokes a path. | ||||
| 	FillStroke() | ||||
|  | ||||
| 	// Circle draws a circle at the given coords with a given radius. | ||||
| 	Circle(radius float64, x, y int) | ||||
|  | ||||
| 	// SetFont sets a font for a text field. | ||||
| 	SetFont(*truetype.Font) | ||||
|  | ||||
| 	// SetFontColor sets a font's color | ||||
| 	SetFontColor(drawing.Color) | ||||
|  | ||||
| 	// SetFontSize sets the font size for a text field. | ||||
| 	SetFontSize(size float64) | ||||
|  | ||||
| 	// Text draws a text blob. | ||||
| 	Text(body string, x, y int) | ||||
|  | ||||
| 	// MeasureText measures text. | ||||
| 	MeasureText(body string) Box | ||||
|  | ||||
| 	// SetTextRotatation sets a rotation for drawing elements. | ||||
| 	SetTextRotation(radians float64) | ||||
|  | ||||
| 	// ClearTextRotation clears rotation. | ||||
| 	ClearTextRotation() | ||||
|  | ||||
| 	// Save writes the image to the given writer. | ||||
| 	Save(w io.Writer) error | ||||
| } | ||||
							
								
								
									
										4
									
								
								vendor/github.com/wcharczuk/go-chart/v2/renderer_provider.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								vendor/github.com/wcharczuk/go-chart/v2/renderer_provider.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| package chart | ||||
|  | ||||
| // RendererProvider is a function that returns a renderer. | ||||
| type RendererProvider func(int, int) (Renderer, error) | ||||
							
								
								
									
										5
									
								
								vendor/github.com/wcharczuk/go-chart/v2/roboto/roboto.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								vendor/github.com/wcharczuk/go-chart/v2/roboto/roboto.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										275
									
								
								vendor/github.com/wcharczuk/go-chart/v2/seq.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										275
									
								
								vendor/github.com/wcharczuk/go-chart/v2/seq.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,275 @@ | ||||
| package chart | ||||
|  | ||||
| import ( | ||||
| 	"math" | ||||
| 	"sort" | ||||
| ) | ||||
|  | ||||
| // ValueSequence returns a sequence for a given values set. | ||||
| func ValueSequence(values ...float64) Seq { | ||||
| 	return Seq{NewArray(values...)} | ||||
| } | ||||
|  | ||||
| // Sequence is a provider for values for a seq. | ||||
| type Sequence interface { | ||||
| 	Len() int | ||||
| 	GetValue(int) float64 | ||||
| } | ||||
|  | ||||
| // Seq is a utility wrapper for seq providers. | ||||
| type Seq struct { | ||||
| 	Sequence | ||||
| } | ||||
|  | ||||
| // Values enumerates the seq into a slice. | ||||
| func (s Seq) Values() (output []float64) { | ||||
| 	if s.Len() == 0 { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	output = make([]float64, s.Len()) | ||||
| 	for i := 0; i < s.Len(); i++ { | ||||
| 		output[i] = s.GetValue(i) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Each applies the `mapfn` to all values in the value provider. | ||||
| func (s Seq) Each(mapfn func(int, float64)) { | ||||
| 	for i := 0; i < s.Len(); i++ { | ||||
| 		mapfn(i, s.GetValue(i)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Map applies the `mapfn` to all values in the value provider, | ||||
| // returning a new seq. | ||||
| func (s Seq) Map(mapfn func(i int, v float64) float64) Seq { | ||||
| 	output := make([]float64, s.Len()) | ||||
| 	for i := 0; i < s.Len(); i++ { | ||||
| 		mapfn(i, s.GetValue(i)) | ||||
| 	} | ||||
| 	return Seq{Array(output)} | ||||
| } | ||||
|  | ||||
| // FoldLeft collapses a seq from left to right. | ||||
| func (s Seq) FoldLeft(mapfn func(i int, v0, v float64) float64) (v0 float64) { | ||||
| 	if s.Len() == 0 { | ||||
| 		return 0 | ||||
| 	} | ||||
|  | ||||
| 	if s.Len() == 1 { | ||||
| 		return s.GetValue(0) | ||||
| 	} | ||||
|  | ||||
| 	v0 = s.GetValue(0) | ||||
| 	for i := 1; i < s.Len(); i++ { | ||||
| 		v0 = mapfn(i, v0, s.GetValue(i)) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // FoldRight collapses a seq from right to left. | ||||
| func (s Seq) FoldRight(mapfn func(i int, v0, v float64) float64) (v0 float64) { | ||||
| 	if s.Len() == 0 { | ||||
| 		return 0 | ||||
| 	} | ||||
|  | ||||
| 	if s.Len() == 1 { | ||||
| 		return s.GetValue(0) | ||||
| 	} | ||||
|  | ||||
| 	v0 = s.GetValue(s.Len() - 1) | ||||
| 	for i := s.Len() - 2; i >= 0; i-- { | ||||
| 		v0 = mapfn(i, v0, s.GetValue(i)) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Min returns the minimum value in the seq. | ||||
| func (s Seq) Min() float64 { | ||||
| 	if s.Len() == 0 { | ||||
| 		return 0 | ||||
| 	} | ||||
| 	min := s.GetValue(0) | ||||
| 	var value float64 | ||||
| 	for i := 1; i < s.Len(); i++ { | ||||
| 		value = s.GetValue(i) | ||||
| 		if value < min { | ||||
| 			min = value | ||||
| 		} | ||||
| 	} | ||||
| 	return min | ||||
| } | ||||
|  | ||||
| // Max returns the maximum value in the seq. | ||||
| func (s Seq) Max() float64 { | ||||
| 	if s.Len() == 0 { | ||||
| 		return 0 | ||||
| 	} | ||||
| 	max := s.GetValue(0) | ||||
| 	var value float64 | ||||
| 	for i := 1; i < s.Len(); i++ { | ||||
| 		value = s.GetValue(i) | ||||
| 		if value > max { | ||||
| 			max = value | ||||
| 		} | ||||
| 	} | ||||
| 	return max | ||||
| } | ||||
|  | ||||
| // MinMax returns the minimum and the maximum in one pass. | ||||
| func (s Seq) MinMax() (min, max float64) { | ||||
| 	if s.Len() == 0 { | ||||
| 		return | ||||
| 	} | ||||
| 	min = s.GetValue(0) | ||||
| 	max = min | ||||
| 	var value float64 | ||||
| 	for i := 1; i < s.Len(); i++ { | ||||
| 		value = s.GetValue(i) | ||||
| 		if value < min { | ||||
| 			min = value | ||||
| 		} | ||||
| 		if value > max { | ||||
| 			max = value | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Sort returns the seq sorted in ascending order. | ||||
| // This fully enumerates the seq. | ||||
| func (s Seq) Sort() Seq { | ||||
| 	if s.Len() == 0 { | ||||
| 		return s | ||||
| 	} | ||||
| 	values := s.Values() | ||||
| 	sort.Float64s(values) | ||||
| 	return Seq{Array(values)} | ||||
| } | ||||
|  | ||||
| // Reverse reverses the sequence | ||||
| func (s Seq) Reverse() Seq { | ||||
| 	if s.Len() == 0 { | ||||
| 		return s | ||||
| 	} | ||||
|  | ||||
| 	values := s.Values() | ||||
| 	valuesLen := len(values) | ||||
| 	valuesLen1 := len(values) - 1 | ||||
| 	valuesLen2 := valuesLen >> 1 | ||||
| 	var i, j float64 | ||||
| 	for index := 0; index < valuesLen2; index++ { | ||||
| 		i = values[index] | ||||
| 		j = values[valuesLen1-index] | ||||
| 		values[index] = j | ||||
| 		values[valuesLen1-index] = i | ||||
| 	} | ||||
|  | ||||
| 	return Seq{Array(values)} | ||||
| } | ||||
|  | ||||
| // Median returns the median or middle value in the sorted seq. | ||||
| func (s Seq) Median() (median float64) { | ||||
| 	l := s.Len() | ||||
| 	if l == 0 { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	sorted := s.Sort() | ||||
| 	if l%2 == 0 { | ||||
| 		v0 := sorted.GetValue(l/2 - 1) | ||||
| 		v1 := sorted.GetValue(l/2 + 1) | ||||
| 		median = (v0 + v1) / 2 | ||||
| 	} else { | ||||
| 		median = float64(sorted.GetValue(l << 1)) | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Sum adds all the elements of a series together. | ||||
| func (s Seq) Sum() (accum float64) { | ||||
| 	if s.Len() == 0 { | ||||
| 		return 0 | ||||
| 	} | ||||
|  | ||||
| 	for i := 0; i < s.Len(); i++ { | ||||
| 		accum += s.GetValue(i) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Average returns the float average of the values in the buffer. | ||||
| func (s Seq) Average() float64 { | ||||
| 	if s.Len() == 0 { | ||||
| 		return 0 | ||||
| 	} | ||||
|  | ||||
| 	return s.Sum() / float64(s.Len()) | ||||
| } | ||||
|  | ||||
| // Variance computes the variance of the buffer. | ||||
| func (s Seq) Variance() float64 { | ||||
| 	if s.Len() == 0 { | ||||
| 		return 0 | ||||
| 	} | ||||
|  | ||||
| 	m := s.Average() | ||||
| 	var variance, v float64 | ||||
| 	for i := 0; i < s.Len(); i++ { | ||||
| 		v = s.GetValue(i) | ||||
| 		variance += (v - m) * (v - m) | ||||
| 	} | ||||
|  | ||||
| 	return variance / float64(s.Len()) | ||||
| } | ||||
|  | ||||
| // StdDev returns the standard deviation. | ||||
| func (s Seq) StdDev() float64 { | ||||
| 	if s.Len() == 0 { | ||||
| 		return 0 | ||||
| 	} | ||||
|  | ||||
| 	return math.Pow(s.Variance(), 0.5) | ||||
| } | ||||
|  | ||||
| //Percentile finds the relative standing in a slice of floats. | ||||
| // `percent` should be given on the interval [0,1.0). | ||||
| func (s Seq) Percentile(percent float64) (percentile float64) { | ||||
| 	l := s.Len() | ||||
| 	if l == 0 { | ||||
| 		return 0 | ||||
| 	} | ||||
|  | ||||
| 	if percent < 0 || percent > 1.0 { | ||||
| 		panic("percent out of range [0.0, 1.0)") | ||||
| 	} | ||||
|  | ||||
| 	sorted := s.Sort() | ||||
| 	index := percent * float64(l) | ||||
| 	if index == float64(int64(index)) { | ||||
| 		i := f64i(index) | ||||
| 		ci := sorted.GetValue(i - 1) | ||||
| 		c := sorted.GetValue(i) | ||||
| 		percentile = (ci + c) / 2.0 | ||||
| 	} else { | ||||
| 		i := f64i(index) | ||||
| 		percentile = sorted.GetValue(i) | ||||
| 	} | ||||
|  | ||||
| 	return percentile | ||||
| } | ||||
|  | ||||
| // Normalize maps every value to the interval [0, 1.0]. | ||||
| func (s Seq) Normalize() Seq { | ||||
| 	min, max := s.MinMax() | ||||
|  | ||||
| 	delta := max - min | ||||
| 	output := make([]float64, s.Len()) | ||||
| 	for i := 0; i < s.Len(); i++ { | ||||
| 		output[i] = (s.GetValue(i) - min) / delta | ||||
| 	} | ||||
|  | ||||
| 	return Seq{Array(output)} | ||||
| } | ||||
							
								
								
									
										10
									
								
								vendor/github.com/wcharczuk/go-chart/v2/series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								vendor/github.com/wcharczuk/go-chart/v2/series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| package chart | ||||
|  | ||||
| // Series is an alias to Renderable. | ||||
| type Series interface { | ||||
| 	GetName() string | ||||
| 	GetYAxis() YAxisType | ||||
| 	GetStyle() Style | ||||
| 	Validate() error | ||||
| 	Render(r Renderer, canvasBox Box, xrange, yrange Range, s Style) | ||||
| } | ||||
							
								
								
									
										120
									
								
								vendor/github.com/wcharczuk/go-chart/v2/sma_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								vendor/github.com/wcharczuk/go-chart/v2/sma_series.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,120 @@ | ||||
| package chart | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	// DefaultSimpleMovingAveragePeriod is the default number of values to average. | ||||
| 	DefaultSimpleMovingAveragePeriod = 16 | ||||
| ) | ||||
|  | ||||
| // Interface Assertions. | ||||
| var ( | ||||
| 	_ Series              = (*SMASeries)(nil) | ||||
| 	_ FirstValuesProvider = (*SMASeries)(nil) | ||||
| 	_ LastValuesProvider  = (*SMASeries)(nil) | ||||
| ) | ||||
|  | ||||
| // SMASeries is a computed series. | ||||
| type SMASeries struct { | ||||
| 	Name  string | ||||
| 	Style Style | ||||
| 	YAxis YAxisType | ||||
|  | ||||
| 	Period      int | ||||
| 	InnerSeries ValuesProvider | ||||
| } | ||||
|  | ||||
| // GetName returns the name of the time series. | ||||
| func (sma SMASeries) GetName() string { | ||||
| 	return sma.Name | ||||
| } | ||||
|  | ||||
| // GetStyle returns the line style. | ||||
| func (sma SMASeries) GetStyle() Style { | ||||
| 	return sma.Style | ||||
| } | ||||
|  | ||||
| // GetYAxis returns which YAxis the series draws on. | ||||
| func (sma SMASeries) GetYAxis() YAxisType { | ||||
| 	return sma.YAxis | ||||
| } | ||||
|  | ||||
| // Len returns the number of elements in the series. | ||||
| func (sma SMASeries) Len() int { | ||||
| 	return sma.InnerSeries.Len() | ||||
| } | ||||
|  | ||||
| // GetPeriod returns the window size. | ||||
| func (sma SMASeries) GetPeriod(defaults ...int) int { | ||||
| 	if sma.Period == 0 { | ||||
| 		if len(defaults) > 0 { | ||||
| 			return defaults[0] | ||||
| 		} | ||||
| 		return DefaultSimpleMovingAveragePeriod | ||||
| 	} | ||||
| 	return sma.Period | ||||
| } | ||||
|  | ||||
| // GetValues gets a value at a given index. | ||||
| func (sma SMASeries) GetValues(index int) (x, y float64) { | ||||
| 	if sma.InnerSeries == nil || sma.InnerSeries.Len() == 0 { | ||||
| 		return | ||||
| 	} | ||||
| 	px, _ := sma.InnerSeries.GetValues(index) | ||||
| 	x = px | ||||
| 	y = sma.getAverage(index) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // GetFirstValues computes the first moving average value. | ||||
| func (sma SMASeries) GetFirstValues() (x, y float64) { | ||||
| 	if sma.InnerSeries == nil || sma.InnerSeries.Len() == 0 { | ||||
| 		return | ||||
| 	} | ||||
| 	px, _ := sma.InnerSeries.GetValues(0) | ||||
| 	x = px | ||||
| 	y = sma.getAverage(0) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // GetLastValues computes the last moving average value but walking back window size samples, | ||||
| // and recomputing the last moving average chunk. | ||||
| func (sma SMASeries) GetLastValues() (x, y float64) { | ||||
| 	if sma.InnerSeries == nil || sma.InnerSeries.Len() == 0 { | ||||
| 		return | ||||
| 	} | ||||
| 	seriesLen := sma.InnerSeries.Len() | ||||
| 	px, _ := sma.InnerSeries.GetValues(seriesLen - 1) | ||||
| 	x = px | ||||
| 	y = sma.getAverage(seriesLen - 1) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (sma SMASeries) getAverage(index int) float64 { | ||||
| 	period := sma.GetPeriod() | ||||
| 	floor := MaxInt(0, index-period) | ||||
| 	var accum float64 | ||||
| 	var count float64 | ||||
| 	for x := index; x >= floor; x-- { | ||||
| 		_, vy := sma.InnerSeries.GetValues(x) | ||||
| 		accum += vy | ||||
| 		count += 1.0 | ||||
| 	} | ||||
| 	return accum / count | ||||
| } | ||||
|  | ||||
| // Render renders the series. | ||||
| func (sma SMASeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { | ||||
| 	style := sma.Style.InheritFrom(defaults) | ||||
| 	Draw.LineSeries(r, canvasBox, xrange, yrange, style, sma) | ||||
| } | ||||
|  | ||||
| // Validate validates the series. | ||||
| func (sma SMASeries) Validate() error { | ||||
| 	if sma.InnerSeries == nil { | ||||
| 		return fmt.Errorf("sma series requires InnerSeries to be set") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										610
									
								
								vendor/github.com/wcharczuk/go-chart/v2/stacked_bar_chart.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										610
									
								
								vendor/github.com/wcharczuk/go-chart/v2/stacked_bar_chart.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,610 @@ | ||||
| package chart | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"math" | ||||
|  | ||||
| 	"github.com/golang/freetype/truetype" | ||||
| ) | ||||
|  | ||||
| // StackedBar is a bar within a StackedBarChart. | ||||
| type StackedBar struct { | ||||
| 	Name   string | ||||
| 	Width  int | ||||
| 	Values []Value | ||||
| } | ||||
|  | ||||
| // GetWidth returns the width of the bar. | ||||
| func (sb StackedBar) GetWidth() int { | ||||
| 	if sb.Width == 0 { | ||||
| 		return 50 | ||||
| 	} | ||||
| 	return sb.Width | ||||
| } | ||||
|  | ||||
| // StackedBarChart is a chart that draws sections of a bar based on percentages. | ||||
| type StackedBarChart struct { | ||||
| 	Title      string | ||||
| 	TitleStyle Style | ||||
|  | ||||
| 	ColorPalette ColorPalette | ||||
|  | ||||
| 	Width  int | ||||
| 	Height int | ||||
| 	DPI    float64 | ||||
|  | ||||
| 	Background Style | ||||
| 	Canvas     Style | ||||
|  | ||||
| 	XAxis Style | ||||
| 	YAxis Style | ||||
|  | ||||
| 	BarSpacing int | ||||
|  | ||||
| 	Font        *truetype.Font | ||||
| 	defaultFont *truetype.Font | ||||
|  | ||||
| 	IsHorizontal bool | ||||
|  | ||||
| 	Bars     []StackedBar | ||||
| 	Elements []Renderable | ||||
| } | ||||
|  | ||||
| // GetDPI returns the dpi for the chart. | ||||
| func (sbc StackedBarChart) GetDPI(defaults ...float64) float64 { | ||||
| 	if sbc.DPI == 0 { | ||||
| 		if len(defaults) > 0 { | ||||
| 			return defaults[0] | ||||
| 		} | ||||
| 		return DefaultDPI | ||||
| 	} | ||||
| 	return sbc.DPI | ||||
| } | ||||
|  | ||||
| // GetFont returns the text font. | ||||
| func (sbc StackedBarChart) GetFont() *truetype.Font { | ||||
| 	if sbc.Font == nil { | ||||
| 		return sbc.defaultFont | ||||
| 	} | ||||
| 	return sbc.Font | ||||
| } | ||||
|  | ||||
| // GetWidth returns the chart width or the default value. | ||||
| func (sbc StackedBarChart) GetWidth() int { | ||||
| 	if sbc.Width == 0 { | ||||
| 		return DefaultChartWidth | ||||
| 	} | ||||
| 	return sbc.Width | ||||
| } | ||||
|  | ||||
| // GetHeight returns the chart height or the default value. | ||||
| func (sbc StackedBarChart) GetHeight() int { | ||||
| 	if sbc.Height == 0 { | ||||
| 		return DefaultChartWidth | ||||
| 	} | ||||
| 	return sbc.Height | ||||
| } | ||||
|  | ||||
| // GetBarSpacing returns the spacing between bars. | ||||
| func (sbc StackedBarChart) GetBarSpacing() int { | ||||
| 	if sbc.BarSpacing == 0 { | ||||
| 		return 100 | ||||
| 	} | ||||
| 	return sbc.BarSpacing | ||||
| } | ||||
|  | ||||
| // Render renders the chart with the given renderer to the given io.Writer. | ||||
| func (sbc StackedBarChart) Render(rp RendererProvider, w io.Writer) error { | ||||
| 	if len(sbc.Bars) == 0 { | ||||
| 		return errors.New("please provide at least one bar") | ||||
| 	} | ||||
|  | ||||
| 	r, err := rp(sbc.GetWidth(), sbc.GetHeight()) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if sbc.Font == nil { | ||||
| 		defaultFont, err := GetDefaultFont() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		sbc.defaultFont = defaultFont | ||||
| 	} | ||||
| 	r.SetDPI(sbc.GetDPI(DefaultDPI)) | ||||
|  | ||||
| 	var canvasBox Box | ||||
| 	if sbc.IsHorizontal { | ||||
| 		canvasBox = sbc.getHorizontalAdjustedCanvasBox(r, sbc.getDefaultCanvasBox()) | ||||
| 		sbc.drawCanvas(r, canvasBox) | ||||
| 		sbc.drawHorizontalBars(r, canvasBox) | ||||
| 		sbc.drawHorizontalXAxis(r, canvasBox) | ||||
| 		sbc.drawHorizontalYAxis(r, canvasBox) | ||||
| 	} else { | ||||
| 		canvasBox = sbc.getAdjustedCanvasBox(r, sbc.getDefaultCanvasBox()) | ||||
| 		sbc.drawCanvas(r, canvasBox) | ||||
| 		sbc.drawBars(r, canvasBox) | ||||
| 		sbc.drawXAxis(r, canvasBox) | ||||
| 		sbc.drawYAxis(r, canvasBox) | ||||
| 	} | ||||
|  | ||||
| 	sbc.drawTitle(r) | ||||
| 	for _, a := range sbc.Elements { | ||||
| 		a(r, canvasBox, sbc.styleDefaultsElements()) | ||||
| 	} | ||||
|  | ||||
| 	return r.Save(w) | ||||
| } | ||||
|  | ||||
| func (sbc StackedBarChart) drawCanvas(r Renderer, canvasBox Box) { | ||||
| 	Draw.Box(r, canvasBox, sbc.getCanvasStyle()) | ||||
| } | ||||
|  | ||||
| func (sbc StackedBarChart) drawBars(r Renderer, canvasBox Box) { | ||||
| 	xoffset := canvasBox.Left | ||||
| 	for _, bar := range sbc.Bars { | ||||
| 		sbc.drawBar(r, canvasBox, xoffset, bar) | ||||
| 		xoffset += (sbc.GetBarSpacing() + bar.GetWidth()) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (sbc StackedBarChart) drawHorizontalBars(r Renderer, canvasBox Box) { | ||||
| 	yOffset := canvasBox.Top | ||||
| 	for _, bar := range sbc.Bars { | ||||
| 		sbc.drawHorizontalBar(r, canvasBox, yOffset, bar) | ||||
| 		yOffset += sbc.GetBarSpacing() + bar.GetWidth() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (sbc StackedBarChart) drawBar(r Renderer, canvasBox Box, xoffset int, bar StackedBar) int { | ||||
| 	barSpacing2 := sbc.GetBarSpacing() >> 1 | ||||
| 	bxl := xoffset + barSpacing2 | ||||
| 	bxr := bxl + bar.GetWidth() | ||||
|  | ||||
| 	normalizedBarComponents := Values(bar.Values).Normalize() | ||||
| 	yoffset := canvasBox.Top | ||||
| 	for index, bv := range normalizedBarComponents { | ||||
| 		barHeight := int(math.Ceil(bv.Value * float64(canvasBox.Height()))) | ||||
| 		barBox := Box{ | ||||
| 			Top:    yoffset, | ||||
| 			Left:   bxl, | ||||
| 			Right:  bxr, | ||||
| 			Bottom: MinInt(yoffset+barHeight, canvasBox.Bottom-DefaultStrokeWidth), | ||||
| 		} | ||||
| 		Draw.Box(r, barBox, bv.Style.InheritFrom(sbc.styleDefaultsStackedBarValue(index))) | ||||
| 		yoffset += barHeight | ||||
| 	} | ||||
|  | ||||
| 	// draw the labels | ||||
| 	yoffset = canvasBox.Top | ||||
| 	var lx, ly int | ||||
| 	for index, bv := range normalizedBarComponents { | ||||
| 		barHeight := int(math.Ceil(bv.Value * float64(canvasBox.Height()))) | ||||
|  | ||||
| 		if len(bv.Label) > 0 { | ||||
| 			lx = bxl + ((bxr - bxl) / 2) | ||||
| 			ly = yoffset + (barHeight / 2) | ||||
|  | ||||
| 			bv.Style.InheritFrom(sbc.styleDefaultsStackedBarValue(index)).WriteToRenderer(r) | ||||
| 			tb := r.MeasureText(bv.Label) | ||||
| 			lx = lx - (tb.Width() >> 1) | ||||
| 			ly = ly + (tb.Height() >> 1) | ||||
|  | ||||
| 			if lx < 0 { | ||||
| 				lx = 0 | ||||
| 			} | ||||
| 			if ly < 0 { | ||||
| 				lx = 0 | ||||
| 			} | ||||
|  | ||||
| 			r.Text(bv.Label, lx, ly) | ||||
| 		} | ||||
| 		yoffset += barHeight | ||||
| 	} | ||||
|  | ||||
| 	return bxr | ||||
| } | ||||
|  | ||||
| func (sbc StackedBarChart) drawHorizontalBar(r Renderer, canvasBox Box, yoffset int, bar StackedBar) { | ||||
| 	halfBarSpacing := sbc.GetBarSpacing() >> 1 | ||||
|  | ||||
| 	boxTop := yoffset + halfBarSpacing | ||||
| 	boxBottom := boxTop + bar.GetWidth() | ||||
|  | ||||
| 	normalizedBarComponents := Values(bar.Values).Normalize() | ||||
|  | ||||
| 	xOffset := canvasBox.Right | ||||
| 	for index, bv := range normalizedBarComponents { | ||||
| 		barHeight := int(math.Ceil(bv.Value * float64(canvasBox.Width()))) | ||||
| 		barBox := Box{ | ||||
| 			Top:    boxTop, | ||||
| 			Left:   MinInt(xOffset-barHeight, canvasBox.Left+DefaultStrokeWidth), | ||||
| 			Right:  xOffset, | ||||
| 			Bottom: boxBottom, | ||||
| 		} | ||||
| 		Draw.Box(r, barBox, bv.Style.InheritFrom(sbc.styleDefaultsStackedBarValue(index))) | ||||
| 		xOffset -= barHeight | ||||
| 	} | ||||
|  | ||||
| 	// draw the labels | ||||
| 	xOffset = canvasBox.Right | ||||
| 	var lx, ly int | ||||
| 	for index, bv := range normalizedBarComponents { | ||||
| 		barHeight := int(math.Ceil(bv.Value * float64(canvasBox.Width()))) | ||||
|  | ||||
| 		if len(bv.Label) > 0 { | ||||
| 			lx = xOffset - (barHeight / 2) | ||||
| 			ly = boxTop + ((boxBottom - boxTop) / 2) | ||||
|  | ||||
| 			bv.Style.InheritFrom(sbc.styleDefaultsStackedBarValue(index)).WriteToRenderer(r) | ||||
| 			tb := r.MeasureText(bv.Label) | ||||
| 			lx = lx - (tb.Width() >> 1) | ||||
| 			ly = ly + (tb.Height() >> 1) | ||||
|  | ||||
| 			if lx < 0 { | ||||
| 				lx = 0 | ||||
| 			} | ||||
| 			if ly < 0 { | ||||
| 				lx = 0 | ||||
| 			} | ||||
|  | ||||
| 			r.Text(bv.Label, lx, ly) | ||||
| 		} | ||||
| 		xOffset -= barHeight | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (sbc StackedBarChart) drawXAxis(r Renderer, canvasBox Box) { | ||||
| 	if !sbc.XAxis.Hidden { | ||||
| 		axisStyle := sbc.XAxis.InheritFrom(sbc.styleDefaultsAxes()) | ||||
| 		axisStyle.WriteToRenderer(r) | ||||
|  | ||||
| 		r.MoveTo(canvasBox.Left, canvasBox.Bottom) | ||||
| 		r.LineTo(canvasBox.Right, canvasBox.Bottom) | ||||
| 		r.Stroke() | ||||
|  | ||||
| 		r.MoveTo(canvasBox.Left, canvasBox.Bottom) | ||||
| 		r.LineTo(canvasBox.Left, canvasBox.Bottom+DefaultVerticalTickHeight) | ||||
| 		r.Stroke() | ||||
|  | ||||
| 		cursor := canvasBox.Left | ||||
| 		for _, bar := range sbc.Bars { | ||||
|  | ||||
| 			barLabelBox := Box{ | ||||
| 				Top:    canvasBox.Bottom + DefaultXAxisMargin, | ||||
| 				Left:   cursor, | ||||
| 				Right:  cursor + bar.GetWidth() + sbc.GetBarSpacing(), | ||||
| 				Bottom: sbc.GetHeight(), | ||||
| 			} | ||||
| 			if len(bar.Name) > 0 { | ||||
| 				Draw.TextWithin(r, bar.Name, barLabelBox, axisStyle) | ||||
| 			} | ||||
| 			axisStyle.WriteToRenderer(r) | ||||
| 			r.MoveTo(barLabelBox.Right, canvasBox.Bottom) | ||||
| 			r.LineTo(barLabelBox.Right, canvasBox.Bottom+DefaultVerticalTickHeight) | ||||
| 			r.Stroke() | ||||
| 			cursor += bar.GetWidth() + sbc.GetBarSpacing() | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (sbc StackedBarChart) drawHorizontalXAxis(r Renderer, canvasBox Box) { | ||||
| 	if !sbc.XAxis.Hidden { | ||||
| 		axisStyle := sbc.XAxis.InheritFrom(sbc.styleDefaultsAxes()) | ||||
| 		axisStyle.WriteToRenderer(r) | ||||
| 		r.MoveTo(canvasBox.Left, canvasBox.Bottom) | ||||
| 		r.LineTo(canvasBox.Right, canvasBox.Bottom) | ||||
| 		r.Stroke() | ||||
|  | ||||
| 		r.MoveTo(canvasBox.Left, canvasBox.Bottom) | ||||
| 		r.LineTo(canvasBox.Left, canvasBox.Bottom+DefaultVerticalTickHeight) | ||||
| 		r.Stroke() | ||||
|  | ||||
| 		ticks := LinearRangeWithStep(0.0, 1.0, 0.2) | ||||
| 		for _, t := range ticks { | ||||
| 			axisStyle.GetStrokeOptions().WriteToRenderer(r) | ||||
| 			tx := canvasBox.Left + int(t*float64(canvasBox.Width())) | ||||
| 			r.MoveTo(tx, canvasBox.Bottom) | ||||
| 			r.LineTo(tx, canvasBox.Bottom+DefaultVerticalTickHeight) | ||||
| 			r.Stroke() | ||||
|  | ||||
| 			axisStyle.GetTextOptions().WriteToRenderer(r) | ||||
| 			text := fmt.Sprintf("%0.0f%%", t*100) | ||||
|  | ||||
| 			textBox := r.MeasureText(text) | ||||
| 			textX := tx - (textBox.Width() >> 1) | ||||
| 			textY := canvasBox.Bottom + DefaultXAxisMargin + 10 | ||||
|  | ||||
| 			if t == 1 { | ||||
| 				textX = canvasBox.Right - textBox.Width() | ||||
| 			} | ||||
|  | ||||
| 			Draw.Text(r, text, textX, textY, axisStyle) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (sbc StackedBarChart) drawYAxis(r Renderer, canvasBox Box) { | ||||
| 	if !sbc.YAxis.Hidden { | ||||
| 		axisStyle := sbc.YAxis.InheritFrom(sbc.styleDefaultsAxes()) | ||||
| 		axisStyle.WriteToRenderer(r) | ||||
| 		r.MoveTo(canvasBox.Right, canvasBox.Top) | ||||
| 		r.LineTo(canvasBox.Right, canvasBox.Bottom) | ||||
| 		r.Stroke() | ||||
|  | ||||
| 		r.MoveTo(canvasBox.Right, canvasBox.Bottom) | ||||
| 		r.LineTo(canvasBox.Right+DefaultHorizontalTickWidth, canvasBox.Bottom) | ||||
| 		r.Stroke() | ||||
|  | ||||
| 		ticks := LinearRangeWithStep(0.0, 1.0, 0.2) | ||||
| 		for _, t := range ticks { | ||||
| 			axisStyle.GetStrokeOptions().WriteToRenderer(r) | ||||
| 			ty := canvasBox.Bottom - int(t*float64(canvasBox.Height())) | ||||
| 			r.MoveTo(canvasBox.Right, ty) | ||||
| 			r.LineTo(canvasBox.Right+DefaultHorizontalTickWidth, ty) | ||||
| 			r.Stroke() | ||||
|  | ||||
| 			axisStyle.GetTextOptions().WriteToRenderer(r) | ||||
| 			text := fmt.Sprintf("%0.0f%%", t*100) | ||||
|  | ||||
| 			tb := r.MeasureText(text) | ||||
| 			Draw.Text(r, text, canvasBox.Right+DefaultYAxisMargin+5, ty+(tb.Height()>>1), axisStyle) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (sbc StackedBarChart) drawHorizontalYAxis(r Renderer, canvasBox Box) { | ||||
| 	if !sbc.YAxis.Hidden { | ||||
| 		axisStyle := sbc.YAxis.InheritFrom(sbc.styleDefaultsHorizontalAxes()) | ||||
| 		axisStyle.WriteToRenderer(r) | ||||
|  | ||||
| 		r.MoveTo(canvasBox.Left, canvasBox.Bottom) | ||||
| 		r.LineTo(canvasBox.Left, canvasBox.Top) | ||||
| 		r.Stroke() | ||||
|  | ||||
| 		r.MoveTo(canvasBox.Left, canvasBox.Bottom) | ||||
| 		r.LineTo(canvasBox.Left-DefaultHorizontalTickWidth, canvasBox.Bottom) | ||||
| 		r.Stroke() | ||||
|  | ||||
| 		cursor := canvasBox.Top | ||||
| 		for _, bar := range sbc.Bars { | ||||
| 			barLabelBox := Box{ | ||||
| 				Top:    cursor, | ||||
| 				Left:   0, | ||||
| 				Right:  canvasBox.Left - DefaultYAxisMargin, | ||||
| 				Bottom: cursor + bar.GetWidth() + sbc.GetBarSpacing(), | ||||
| 			} | ||||
| 			if len(bar.Name) > 0 { | ||||
| 				Draw.TextWithin(r, bar.Name, barLabelBox, axisStyle) | ||||
| 			} | ||||
| 			axisStyle.WriteToRenderer(r) | ||||
| 			r.MoveTo(canvasBox.Left, barLabelBox.Bottom) | ||||
| 			r.LineTo(canvasBox.Left-DefaultHorizontalTickWidth, barLabelBox.Bottom) | ||||
| 			r.Stroke() | ||||
| 			cursor += bar.GetWidth() + sbc.GetBarSpacing() | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (sbc StackedBarChart) drawTitle(r Renderer) { | ||||
| 	if len(sbc.Title) > 0 && !sbc.TitleStyle.Hidden { | ||||
| 		r.SetFont(sbc.TitleStyle.GetFont(sbc.GetFont())) | ||||
| 		r.SetFontColor(sbc.TitleStyle.GetFontColor(sbc.GetColorPalette().TextColor())) | ||||
| 		titleFontSize := sbc.TitleStyle.GetFontSize(DefaultTitleFontSize) | ||||
| 		r.SetFontSize(titleFontSize) | ||||
|  | ||||
| 		textBox := r.MeasureText(sbc.Title) | ||||
|  | ||||
| 		textWidth := textBox.Width() | ||||
| 		textHeight := textBox.Height() | ||||
|  | ||||
| 		titleX := (sbc.GetWidth() >> 1) - (textWidth >> 1) | ||||
| 		titleY := sbc.TitleStyle.Padding.GetTop(DefaultTitleTop) + textHeight | ||||
|  | ||||
| 		r.Text(sbc.Title, titleX, titleY) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (sbc StackedBarChart) getCanvasStyle() Style { | ||||
| 	return sbc.Canvas.InheritFrom(sbc.styleDefaultsCanvas()) | ||||
| } | ||||
|  | ||||
| func (sbc StackedBarChart) styleDefaultsCanvas() Style { | ||||
| 	return Style{ | ||||
| 		FillColor:   sbc.GetColorPalette().CanvasColor(), | ||||
| 		StrokeColor: sbc.GetColorPalette().CanvasStrokeColor(), | ||||
| 		StrokeWidth: DefaultCanvasStrokeWidth, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetColorPalette returns the color palette for the chart. | ||||
| func (sbc StackedBarChart) GetColorPalette() ColorPalette { | ||||
| 	if sbc.ColorPalette != nil { | ||||
| 		return sbc.ColorPalette | ||||
| 	} | ||||
| 	return AlternateColorPalette | ||||
| } | ||||
|  | ||||
| func (sbc StackedBarChart) getDefaultCanvasBox() Box { | ||||
| 	return sbc.Box() | ||||
| } | ||||
|  | ||||
| func (sbc StackedBarChart) getAdjustedCanvasBox(r Renderer, canvasBox Box) Box { | ||||
| 	var totalWidth int | ||||
| 	for _, bar := range sbc.Bars { | ||||
| 		totalWidth += bar.GetWidth() + sbc.GetBarSpacing() | ||||
| 	} | ||||
|  | ||||
| 	if !sbc.XAxis.Hidden { | ||||
| 		xaxisHeight := DefaultVerticalTickHeight | ||||
|  | ||||
| 		axisStyle := sbc.XAxis.InheritFrom(sbc.styleDefaultsAxes()) | ||||
| 		axisStyle.WriteToRenderer(r) | ||||
|  | ||||
| 		cursor := canvasBox.Left | ||||
| 		for _, bar := range sbc.Bars { | ||||
| 			if len(bar.Name) > 0 { | ||||
| 				barLabelBox := Box{ | ||||
| 					Top:    canvasBox.Bottom + DefaultXAxisMargin, | ||||
| 					Left:   cursor, | ||||
| 					Right:  cursor + bar.GetWidth() + sbc.GetBarSpacing(), | ||||
| 					Bottom: sbc.GetHeight(), | ||||
| 				} | ||||
| 				lines := Text.WrapFit(r, bar.Name, barLabelBox.Width(), axisStyle) | ||||
| 				linesBox := Text.MeasureLines(r, lines, axisStyle) | ||||
|  | ||||
| 				xaxisHeight = MaxInt(linesBox.Height()+(2*DefaultXAxisMargin), xaxisHeight) | ||||
| 			} | ||||
| 		} | ||||
| 		return Box{ | ||||
| 			Top:    canvasBox.Top, | ||||
| 			Left:   canvasBox.Left, | ||||
| 			Right:  canvasBox.Left + totalWidth, | ||||
| 			Bottom: sbc.GetHeight() - xaxisHeight, | ||||
| 		} | ||||
| 	} | ||||
| 	return Box{ | ||||
| 		Top:    canvasBox.Top, | ||||
| 		Left:   canvasBox.Left, | ||||
| 		Right:  canvasBox.Left + totalWidth, | ||||
| 		Bottom: canvasBox.Bottom, | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| func (sbc StackedBarChart) getHorizontalAdjustedCanvasBox(r Renderer, canvasBox Box) Box { | ||||
| 	var totalHeight int | ||||
| 	for _, bar := range sbc.Bars { | ||||
| 		totalHeight += bar.GetWidth() + sbc.GetBarSpacing() | ||||
| 	} | ||||
|  | ||||
| 	if !sbc.YAxis.Hidden { | ||||
| 		yAxisWidth := DefaultHorizontalTickWidth | ||||
|  | ||||
| 		axisStyle := sbc.YAxis.InheritFrom(sbc.styleDefaultsHorizontalAxes()) | ||||
| 		axisStyle.WriteToRenderer(r) | ||||
|  | ||||
| 		cursor := canvasBox.Top | ||||
| 		for _, bar := range sbc.Bars { | ||||
| 			if len(bar.Name) > 0 { | ||||
| 				barLabelBox := Box{ | ||||
| 					Top:    cursor, | ||||
| 					Left:   0, | ||||
| 					Right:  canvasBox.Left + DefaultYAxisMargin, | ||||
| 					Bottom: cursor + bar.GetWidth() + sbc.GetBarSpacing(), | ||||
| 				} | ||||
| 				lines := Text.WrapFit(r, bar.Name, barLabelBox.Width(), axisStyle) | ||||
| 				linesBox := Text.MeasureLines(r, lines, axisStyle) | ||||
|  | ||||
| 				yAxisWidth = MaxInt(linesBox.Height()+(2*DefaultXAxisMargin), yAxisWidth) | ||||
| 			} | ||||
| 		} | ||||
| 		return Box{ | ||||
| 			Top:    canvasBox.Top, | ||||
| 			Left:   canvasBox.Left + yAxisWidth, | ||||
| 			Right:  canvasBox.Right, | ||||
| 			Bottom: canvasBox.Top + totalHeight, | ||||
| 		} | ||||
| 	} | ||||
| 	return Box{ | ||||
| 		Top:    canvasBox.Top, | ||||
| 		Left:   canvasBox.Left, | ||||
| 		Right:  canvasBox.Right, | ||||
| 		Bottom: canvasBox.Top + totalHeight, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Box returns the chart bounds as a box. | ||||
| func (sbc StackedBarChart) Box() Box { | ||||
| 	dpr := sbc.Background.Padding.GetRight(10) | ||||
| 	dpb := sbc.Background.Padding.GetBottom(50) | ||||
|  | ||||
| 	return Box{ | ||||
| 		Top:    sbc.Background.Padding.GetTop(20), | ||||
| 		Left:   sbc.Background.Padding.GetLeft(20), | ||||
| 		Right:  sbc.GetWidth() - dpr, | ||||
| 		Bottom: sbc.GetHeight() - dpb, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (sbc StackedBarChart) styleDefaultsStackedBarValue(index int) Style { | ||||
| 	return Style{ | ||||
| 		StrokeColor: sbc.GetColorPalette().GetSeriesColor(index), | ||||
| 		StrokeWidth: 3.0, | ||||
| 		FillColor:   sbc.GetColorPalette().GetSeriesColor(index), | ||||
| 		FontSize:    sbc.getScaledFontSize(), | ||||
| 		FontColor:   sbc.GetColorPalette().TextColor(), | ||||
| 		Font:        sbc.GetFont(), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (sbc StackedBarChart) styleDefaultsTitle() Style { | ||||
| 	return sbc.TitleStyle.InheritFrom(Style{ | ||||
| 		FontColor:           DefaultTextColor, | ||||
| 		Font:                sbc.GetFont(), | ||||
| 		FontSize:            sbc.getTitleFontSize(), | ||||
| 		TextHorizontalAlign: TextHorizontalAlignCenter, | ||||
| 		TextVerticalAlign:   TextVerticalAlignTop, | ||||
| 		TextWrap:            TextWrapWord, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func (sbc StackedBarChart) getScaledFontSize() float64 { | ||||
| 	effectiveDimension := MinInt(sbc.GetWidth(), sbc.GetHeight()) | ||||
| 	if effectiveDimension >= 2048 { | ||||
| 		return 48.0 | ||||
| 	} else if effectiveDimension >= 1024 { | ||||
| 		return 24.0 | ||||
| 	} else if effectiveDimension > 512 { | ||||
| 		return 18.0 | ||||
| 	} else if effectiveDimension > 256 { | ||||
| 		return 12.0 | ||||
| 	} | ||||
| 	return 10.0 | ||||
| } | ||||
|  | ||||
| func (sbc StackedBarChart) getTitleFontSize() float64 { | ||||
| 	effectiveDimension := MinInt(sbc.GetWidth(), sbc.GetHeight()) | ||||
| 	if effectiveDimension >= 2048 { | ||||
| 		return 48 | ||||
| 	} else if effectiveDimension >= 1024 { | ||||
| 		return 24 | ||||
| 	} else if effectiveDimension >= 512 { | ||||
| 		return 18 | ||||
| 	} else if effectiveDimension >= 256 { | ||||
| 		return 12 | ||||
| 	} | ||||
| 	return 10 | ||||
| } | ||||
|  | ||||
| func (sbc StackedBarChart) styleDefaultsAxes() Style { | ||||
| 	return Style{ | ||||
| 		StrokeColor:         DefaultAxisColor, | ||||
| 		Font:                sbc.GetFont(), | ||||
| 		FontSize:            DefaultAxisFontSize, | ||||
| 		FontColor:           DefaultAxisColor, | ||||
| 		TextHorizontalAlign: TextHorizontalAlignCenter, | ||||
| 		TextVerticalAlign:   TextVerticalAlignTop, | ||||
| 		TextWrap:            TextWrapWord, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (sbc StackedBarChart) styleDefaultsHorizontalAxes() Style { | ||||
| 	return Style{ | ||||
| 		StrokeColor:         DefaultAxisColor, | ||||
| 		Font:                sbc.GetFont(), | ||||
| 		FontSize:            DefaultAxisFontSize, | ||||
| 		FontColor:           DefaultAxisColor, | ||||
| 		TextHorizontalAlign: TextHorizontalAlignCenter, | ||||
| 		TextVerticalAlign:   TextVerticalAlignMiddle, | ||||
| 		TextWrap:            TextWrapWord, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (sbc StackedBarChart) styleDefaultsElements() Style { | ||||
| 	return Style{ | ||||
| 		Font: sbc.GetFont(), | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										57
									
								
								vendor/github.com/wcharczuk/go-chart/v2/stringutil.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								vendor/github.com/wcharczuk/go-chart/v2/stringutil.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,57 @@ | ||||
| package chart | ||||
|  | ||||
| import "strings" | ||||
|  | ||||
| // SplitCSV splits a corpus by the `,`, dropping leading or trailing whitespace unless quoted. | ||||
| func SplitCSV(text string) (output []string) { | ||||
| 	if len(text) == 0 { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	var state int | ||||
| 	var word []rune | ||||
| 	var opened rune | ||||
| 	for _, r := range text { | ||||
| 		switch state { | ||||
| 		case 0: // word | ||||
| 			if isQuote(r) { | ||||
| 				opened = r | ||||
| 				state = 1 | ||||
| 			} else if isCSVDelim(r) { | ||||
| 				output = append(output, strings.TrimSpace(string(word))) | ||||
| 				word = nil | ||||
| 			} else { | ||||
| 				word = append(word, r) | ||||
| 			} | ||||
| 		case 1: // we're in a quoted section | ||||
| 			if matchesQuote(opened, r) { | ||||
| 				state = 0 | ||||
| 				continue | ||||
| 			} | ||||
| 			word = append(word, r) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if len(word) > 0 { | ||||
| 		output = append(output, strings.TrimSpace(string(word))) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func isCSVDelim(r rune) bool { | ||||
| 	return r == rune(',') | ||||
| } | ||||
|  | ||||
| func isQuote(r rune) bool { | ||||
| 	return r == '"' || r == '\'' || r == '“' || r == '”' || r == '`' | ||||
| } | ||||
|  | ||||
| func matchesQuote(a, b rune) bool { | ||||
| 	if a == '“' && b == '”' { | ||||
| 		return true | ||||
| 	} | ||||
| 	if a == '”' && b == '“' { | ||||
| 		return true | ||||
| 	} | ||||
| 	return a == b | ||||
| } | ||||
							
								
								
									
										480
									
								
								vendor/github.com/wcharczuk/go-chart/v2/style.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										480
									
								
								vendor/github.com/wcharczuk/go-chart/v2/style.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,480 @@ | ||||
| package chart | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/golang/freetype/truetype" | ||||
| 	"github.com/wcharczuk/go-chart/v2/drawing" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	// Disabled indicates if the value should be interpreted as set intentionally to zero. | ||||
| 	// this is because golang optionals aren't here yet. | ||||
| 	Disabled = -1 | ||||
| ) | ||||
|  | ||||
| // Hidden is a prebuilt style with the `Hidden` property set to true. | ||||
| func Hidden() Style { | ||||
| 	return Style{ | ||||
| 		Hidden: true, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Shown is a prebuilt style with the `Hidden` property set to false. | ||||
| // You can also think of this as the default. | ||||
| func Shown() Style { | ||||
| 	return Style{ | ||||
| 		Hidden: false, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // StyleTextDefaults returns a style for drawing outside a | ||||
| // chart context. | ||||
| func StyleTextDefaults() Style { | ||||
| 	font, _ := GetDefaultFont() | ||||
| 	return Style{ | ||||
| 		Hidden:    false, | ||||
| 		Font:      font, | ||||
| 		FontColor: DefaultTextColor, | ||||
| 		FontSize:  DefaultTitleFontSize, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Style is a simple style set. | ||||
| type Style struct { | ||||
| 	Hidden  bool | ||||
| 	Padding Box | ||||
|  | ||||
| 	ClassName string | ||||
|  | ||||
| 	StrokeWidth     float64 | ||||
| 	StrokeColor     drawing.Color | ||||
| 	StrokeDashArray []float64 | ||||
|  | ||||
| 	DotColor drawing.Color | ||||
| 	DotWidth float64 | ||||
|  | ||||
| 	DotWidthProvider SizeProvider | ||||
| 	DotColorProvider DotColorProvider | ||||
|  | ||||
| 	FillColor drawing.Color | ||||
|  | ||||
| 	FontSize  float64 | ||||
| 	FontColor drawing.Color | ||||
| 	Font      *truetype.Font | ||||
|  | ||||
| 	TextHorizontalAlign TextHorizontalAlign | ||||
| 	TextVerticalAlign   TextVerticalAlign | ||||
| 	TextWrap            TextWrap | ||||
| 	TextLineSpacing     int | ||||
| 	TextRotationDegrees float64 //0 is unset or normal | ||||
| } | ||||
|  | ||||
| // IsZero returns if the object is set or not. | ||||
| func (s Style) IsZero() bool { | ||||
| 	return !s.Hidden && | ||||
| 		s.StrokeColor.IsZero() && | ||||
| 		s.StrokeWidth == 0 && | ||||
| 		s.DotColor.IsZero() && | ||||
| 		s.DotWidth == 0 && | ||||
| 		s.FillColor.IsZero() && | ||||
| 		s.FontColor.IsZero() && | ||||
| 		s.FontSize == 0 && | ||||
| 		s.Font == nil && | ||||
| 		s.ClassName == "" | ||||
| } | ||||
|  | ||||
| // String returns a text representation of the style. | ||||
| func (s Style) String() string { | ||||
| 	if s.IsZero() { | ||||
| 		return "{}" | ||||
| 	} | ||||
|  | ||||
| 	var output []string | ||||
| 	if s.Hidden { | ||||
| 		output = []string{"\"hidden\": true"} | ||||
| 	} else { | ||||
| 		output = []string{"\"hidden\": false"} | ||||
| 	} | ||||
|  | ||||
| 	if s.ClassName != "" { | ||||
| 		output = append(output, fmt.Sprintf("\"class_name\": %s", s.ClassName)) | ||||
| 	} else { | ||||
| 		output = append(output, "\"class_name\": null") | ||||
| 	} | ||||
|  | ||||
| 	if !s.Padding.IsZero() { | ||||
| 		output = append(output, fmt.Sprintf("\"padding\": %s", s.Padding.String())) | ||||
| 	} else { | ||||
| 		output = append(output, "\"padding\": null") | ||||
| 	} | ||||
|  | ||||
| 	if s.StrokeWidth >= 0 { | ||||
| 		output = append(output, fmt.Sprintf("\"stroke_width\": %0.2f", s.StrokeWidth)) | ||||
| 	} else { | ||||
| 		output = append(output, "\"stroke_width\": null") | ||||
| 	} | ||||
|  | ||||
| 	if !s.StrokeColor.IsZero() { | ||||
| 		output = append(output, fmt.Sprintf("\"stroke_color\": %s", s.StrokeColor.String())) | ||||
| 	} else { | ||||
| 		output = append(output, "\"stroke_color\": null") | ||||
| 	} | ||||
|  | ||||
| 	if len(s.StrokeDashArray) > 0 { | ||||
| 		var elements []string | ||||
| 		for _, v := range s.StrokeDashArray { | ||||
| 			elements = append(elements, fmt.Sprintf("%.2f", v)) | ||||
| 		} | ||||
| 		dashArray := strings.Join(elements, ", ") | ||||
| 		output = append(output, fmt.Sprintf("\"stroke_dash_array\": [%s]", dashArray)) | ||||
| 	} else { | ||||
| 		output = append(output, "\"stroke_dash_array\": null") | ||||
| 	} | ||||
|  | ||||
| 	if s.DotWidth >= 0 { | ||||
| 		output = append(output, fmt.Sprintf("\"dot_width\": %0.2f", s.DotWidth)) | ||||
| 	} else { | ||||
| 		output = append(output, "\"dot_width\": null") | ||||
| 	} | ||||
|  | ||||
| 	if !s.DotColor.IsZero() { | ||||
| 		output = append(output, fmt.Sprintf("\"dot_color\": %s", s.DotColor.String())) | ||||
| 	} else { | ||||
| 		output = append(output, "\"dot_color\": null") | ||||
| 	} | ||||
|  | ||||
| 	if !s.FillColor.IsZero() { | ||||
| 		output = append(output, fmt.Sprintf("\"fill_color\": %s", s.FillColor.String())) | ||||
| 	} else { | ||||
| 		output = append(output, "\"fill_color\": null") | ||||
| 	} | ||||
|  | ||||
| 	if s.FontSize != 0 { | ||||
| 		output = append(output, fmt.Sprintf("\"font_size\": \"%0.2fpt\"", s.FontSize)) | ||||
| 	} else { | ||||
| 		output = append(output, "\"font_size\": null") | ||||
| 	} | ||||
|  | ||||
| 	if !s.FontColor.IsZero() { | ||||
| 		output = append(output, fmt.Sprintf("\"font_color\": %s", s.FontColor.String())) | ||||
| 	} else { | ||||
| 		output = append(output, "\"font_color\": null") | ||||
| 	} | ||||
|  | ||||
| 	if s.Font != nil { | ||||
| 		output = append(output, fmt.Sprintf("\"font\": \"%s\"", s.Font.Name(truetype.NameIDFontFamily))) | ||||
| 	} else { | ||||
| 		output = append(output, "\"font_color\": null") | ||||
| 	} | ||||
|  | ||||
| 	return "{" + strings.Join(output, ", ") + "}" | ||||
| } | ||||
|  | ||||
| // GetClassName returns the class name or a default. | ||||
| func (s Style) GetClassName(defaults ...string) string { | ||||
| 	if s.ClassName == "" { | ||||
| 		if len(defaults) > 0 { | ||||
| 			return defaults[0] | ||||
| 		} | ||||
| 		return "" | ||||
| 	} | ||||
| 	return s.ClassName | ||||
| } | ||||
|  | ||||
| // GetStrokeColor returns the stroke color. | ||||
| func (s Style) GetStrokeColor(defaults ...drawing.Color) drawing.Color { | ||||
| 	if s.StrokeColor.IsZero() { | ||||
| 		if len(defaults) > 0 { | ||||
| 			return defaults[0] | ||||
| 		} | ||||
| 		return drawing.ColorTransparent | ||||
| 	} | ||||
| 	return s.StrokeColor | ||||
| } | ||||
|  | ||||
| // GetFillColor returns the fill color. | ||||
| func (s Style) GetFillColor(defaults ...drawing.Color) drawing.Color { | ||||
| 	if s.FillColor.IsZero() { | ||||
| 		if len(defaults) > 0 { | ||||
| 			return defaults[0] | ||||
| 		} | ||||
| 		return drawing.ColorTransparent | ||||
| 	} | ||||
| 	return s.FillColor | ||||
| } | ||||
|  | ||||
| // GetDotColor returns the stroke color. | ||||
| func (s Style) GetDotColor(defaults ...drawing.Color) drawing.Color { | ||||
| 	if s.DotColor.IsZero() { | ||||
| 		if len(defaults) > 0 { | ||||
| 			return defaults[0] | ||||
| 		} | ||||
| 		return drawing.ColorTransparent | ||||
| 	} | ||||
| 	return s.DotColor | ||||
| } | ||||
|  | ||||
| // GetStrokeWidth returns the stroke width. | ||||
| func (s Style) GetStrokeWidth(defaults ...float64) float64 { | ||||
| 	if s.StrokeWidth == 0 { | ||||
| 		if len(defaults) > 0 { | ||||
| 			return defaults[0] | ||||
| 		} | ||||
| 		return DefaultStrokeWidth | ||||
| 	} | ||||
| 	return s.StrokeWidth | ||||
| } | ||||
|  | ||||
| // GetDotWidth returns the dot width for scatter plots. | ||||
| func (s Style) GetDotWidth(defaults ...float64) float64 { | ||||
| 	if s.DotWidth == 0 { | ||||
| 		if len(defaults) > 0 { | ||||
| 			return defaults[0] | ||||
| 		} | ||||
| 		return DefaultDotWidth | ||||
| 	} | ||||
| 	return s.DotWidth | ||||
| } | ||||
|  | ||||
| // GetStrokeDashArray returns the stroke dash array. | ||||
| func (s Style) GetStrokeDashArray(defaults ...[]float64) []float64 { | ||||
| 	if len(s.StrokeDashArray) == 0 { | ||||
| 		if len(defaults) > 0 { | ||||
| 			return defaults[0] | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| 	return s.StrokeDashArray | ||||
| } | ||||
|  | ||||
| // GetFontSize gets the font size. | ||||
| func (s Style) GetFontSize(defaults ...float64) float64 { | ||||
| 	if s.FontSize == 0 { | ||||
| 		if len(defaults) > 0 { | ||||
| 			return defaults[0] | ||||
| 		} | ||||
| 		return DefaultFontSize | ||||
| 	} | ||||
| 	return s.FontSize | ||||
| } | ||||
|  | ||||
| // GetFontColor gets the font size. | ||||
| func (s Style) GetFontColor(defaults ...drawing.Color) drawing.Color { | ||||
| 	if s.FontColor.IsZero() { | ||||
| 		if len(defaults) > 0 { | ||||
| 			return defaults[0] | ||||
| 		} | ||||
| 		return drawing.ColorTransparent | ||||
| 	} | ||||
| 	return s.FontColor | ||||
| } | ||||
|  | ||||
| // GetFont returns the font face. | ||||
| func (s Style) GetFont(defaults ...*truetype.Font) *truetype.Font { | ||||
| 	if s.Font == nil { | ||||
| 		if len(defaults) > 0 { | ||||
| 			return defaults[0] | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| 	return s.Font | ||||
| } | ||||
|  | ||||
| // GetPadding returns the padding. | ||||
| func (s Style) GetPadding(defaults ...Box) Box { | ||||
| 	if s.Padding.IsZero() { | ||||
| 		if len(defaults) > 0 { | ||||
| 			return defaults[0] | ||||
| 		} | ||||
| 		return Box{} | ||||
| 	} | ||||
| 	return s.Padding | ||||
| } | ||||
|  | ||||
| // GetTextHorizontalAlign returns the horizontal alignment. | ||||
| func (s Style) GetTextHorizontalAlign(defaults ...TextHorizontalAlign) TextHorizontalAlign { | ||||
| 	if s.TextHorizontalAlign == TextHorizontalAlignUnset { | ||||
| 		if len(defaults) > 0 { | ||||
| 			return defaults[0] | ||||
| 		} | ||||
| 		return TextHorizontalAlignUnset | ||||
| 	} | ||||
| 	return s.TextHorizontalAlign | ||||
| } | ||||
|  | ||||
| // GetTextVerticalAlign returns the vertical alignment. | ||||
| func (s Style) GetTextVerticalAlign(defaults ...TextVerticalAlign) TextVerticalAlign { | ||||
| 	if s.TextVerticalAlign == TextVerticalAlignUnset { | ||||
| 		if len(defaults) > 0 { | ||||
| 			return defaults[0] | ||||
| 		} | ||||
| 		return TextVerticalAlignUnset | ||||
| 	} | ||||
| 	return s.TextVerticalAlign | ||||
| } | ||||
|  | ||||
| // GetTextWrap returns the word wrap. | ||||
| func (s Style) GetTextWrap(defaults ...TextWrap) TextWrap { | ||||
| 	if s.TextWrap == TextWrapUnset { | ||||
| 		if len(defaults) > 0 { | ||||
| 			return defaults[0] | ||||
| 		} | ||||
| 		return TextWrapUnset | ||||
| 	} | ||||
| 	return s.TextWrap | ||||
| } | ||||
|  | ||||
| // GetTextLineSpacing returns the spacing in pixels between lines of text (vertically). | ||||
| func (s Style) GetTextLineSpacing(defaults ...int) int { | ||||
| 	if s.TextLineSpacing == 0 { | ||||
| 		if len(defaults) > 0 { | ||||
| 			return defaults[0] | ||||
| 		} | ||||
| 		return DefaultLineSpacing | ||||
| 	} | ||||
| 	return s.TextLineSpacing | ||||
| } | ||||
|  | ||||
| // GetTextRotationDegrees returns the text rotation in degrees. | ||||
| func (s Style) GetTextRotationDegrees(defaults ...float64) float64 { | ||||
| 	if s.TextRotationDegrees == 0 { | ||||
| 		if len(defaults) > 0 { | ||||
| 			return defaults[0] | ||||
| 		} | ||||
| 	} | ||||
| 	return s.TextRotationDegrees | ||||
| } | ||||
|  | ||||
| // WriteToRenderer passes the style's options to a renderer. | ||||
| func (s Style) WriteToRenderer(r Renderer) { | ||||
| 	r.SetClassName(s.GetClassName()) | ||||
| 	r.SetStrokeColor(s.GetStrokeColor()) | ||||
| 	r.SetStrokeWidth(s.GetStrokeWidth()) | ||||
| 	r.SetStrokeDashArray(s.GetStrokeDashArray()) | ||||
| 	r.SetFillColor(s.GetFillColor()) | ||||
| 	r.SetFont(s.GetFont()) | ||||
| 	r.SetFontColor(s.GetFontColor()) | ||||
| 	r.SetFontSize(s.GetFontSize()) | ||||
|  | ||||
| 	r.ClearTextRotation() | ||||
| 	if s.GetTextRotationDegrees() != 0 { | ||||
| 		r.SetTextRotation(DegreesToRadians(s.GetTextRotationDegrees())) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // WriteDrawingOptionsToRenderer passes just the drawing style options to a renderer. | ||||
| func (s Style) WriteDrawingOptionsToRenderer(r Renderer) { | ||||
| 	r.SetClassName(s.GetClassName()) | ||||
| 	r.SetStrokeColor(s.GetStrokeColor()) | ||||
| 	r.SetStrokeWidth(s.GetStrokeWidth()) | ||||
| 	r.SetStrokeDashArray(s.GetStrokeDashArray()) | ||||
| 	r.SetFillColor(s.GetFillColor()) | ||||
| } | ||||
|  | ||||
| // WriteTextOptionsToRenderer passes just the text style options to a renderer. | ||||
| func (s Style) WriteTextOptionsToRenderer(r Renderer) { | ||||
| 	r.SetClassName(s.GetClassName()) | ||||
| 	r.SetFont(s.GetFont()) | ||||
| 	r.SetFontColor(s.GetFontColor()) | ||||
| 	r.SetFontSize(s.GetFontSize()) | ||||
| } | ||||
|  | ||||
| // InheritFrom coalesces two styles into a new style. | ||||
| func (s Style) InheritFrom(defaults Style) (final Style) { | ||||
| 	final.ClassName = s.GetClassName(defaults.ClassName) | ||||
|  | ||||
| 	final.StrokeColor = s.GetStrokeColor(defaults.StrokeColor) | ||||
| 	final.StrokeWidth = s.GetStrokeWidth(defaults.StrokeWidth) | ||||
| 	final.StrokeDashArray = s.GetStrokeDashArray(defaults.StrokeDashArray) | ||||
|  | ||||
| 	final.DotColor = s.GetDotColor(defaults.DotColor) | ||||
| 	final.DotWidth = s.GetDotWidth(defaults.DotWidth) | ||||
|  | ||||
| 	final.DotWidthProvider = s.DotWidthProvider | ||||
| 	final.DotColorProvider = s.DotColorProvider | ||||
|  | ||||
| 	final.FillColor = s.GetFillColor(defaults.FillColor) | ||||
| 	final.FontColor = s.GetFontColor(defaults.FontColor) | ||||
| 	final.FontSize = s.GetFontSize(defaults.FontSize) | ||||
| 	final.Font = s.GetFont(defaults.Font) | ||||
| 	final.Padding = s.GetPadding(defaults.Padding) | ||||
| 	final.TextHorizontalAlign = s.GetTextHorizontalAlign(defaults.TextHorizontalAlign) | ||||
| 	final.TextVerticalAlign = s.GetTextVerticalAlign(defaults.TextVerticalAlign) | ||||
| 	final.TextWrap = s.GetTextWrap(defaults.TextWrap) | ||||
| 	final.TextLineSpacing = s.GetTextLineSpacing(defaults.TextLineSpacing) | ||||
| 	final.TextRotationDegrees = s.GetTextRotationDegrees(defaults.TextRotationDegrees) | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // GetStrokeOptions returns the stroke components. | ||||
| func (s Style) GetStrokeOptions() Style { | ||||
| 	return Style{ | ||||
| 		ClassName:       s.ClassName, | ||||
| 		StrokeDashArray: s.StrokeDashArray, | ||||
| 		StrokeColor:     s.StrokeColor, | ||||
| 		StrokeWidth:     s.StrokeWidth, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetFillOptions returns the fill components. | ||||
| func (s Style) GetFillOptions() Style { | ||||
| 	return Style{ | ||||
| 		ClassName: s.ClassName, | ||||
| 		FillColor: s.FillColor, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetDotOptions returns the dot components. | ||||
| func (s Style) GetDotOptions() Style { | ||||
| 	return Style{ | ||||
| 		ClassName:       s.ClassName, | ||||
| 		StrokeDashArray: nil, | ||||
| 		FillColor:       s.DotColor, | ||||
| 		StrokeColor:     s.DotColor, | ||||
| 		StrokeWidth:     1.0, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetFillAndStrokeOptions returns the fill and stroke components. | ||||
| func (s Style) GetFillAndStrokeOptions() Style { | ||||
| 	return Style{ | ||||
| 		ClassName:       s.ClassName, | ||||
| 		StrokeDashArray: s.StrokeDashArray, | ||||
| 		FillColor:       s.FillColor, | ||||
| 		StrokeColor:     s.StrokeColor, | ||||
| 		StrokeWidth:     s.StrokeWidth, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetTextOptions returns just the text components of the style. | ||||
| func (s Style) GetTextOptions() Style { | ||||
| 	return Style{ | ||||
| 		ClassName:           s.ClassName, | ||||
| 		FontColor:           s.FontColor, | ||||
| 		FontSize:            s.FontSize, | ||||
| 		Font:                s.Font, | ||||
| 		TextHorizontalAlign: s.TextHorizontalAlign, | ||||
| 		TextVerticalAlign:   s.TextVerticalAlign, | ||||
| 		TextWrap:            s.TextWrap, | ||||
| 		TextLineSpacing:     s.TextLineSpacing, | ||||
| 		TextRotationDegrees: s.TextRotationDegrees, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ShouldDrawStroke tells drawing functions if they should draw the stroke. | ||||
| func (s Style) ShouldDrawStroke() bool { | ||||
| 	return !s.StrokeColor.IsZero() && s.StrokeWidth > 0 | ||||
| } | ||||
|  | ||||
| // ShouldDrawDot tells drawing functions if they should draw the dot. | ||||
| func (s Style) ShouldDrawDot() bool { | ||||
| 	return (!s.DotColor.IsZero() && s.DotWidth > 0) || s.DotColorProvider != nil || s.DotWidthProvider != nil | ||||
| } | ||||
|  | ||||
| // ShouldDrawFill tells drawing functions if they should draw the stroke. | ||||
| func (s Style) ShouldDrawFill() bool { | ||||
| 	return !s.FillColor.IsZero() | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user