首页 > 其他分享 >使用 github.com/wcharczuk/go-chart 绘图

使用 github.com/wcharczuk/go-chart 绘图

时间:2022-11-07 16:33:51浏览次数:72  
标签:github curr log nameArr chart go append cad

公共绘图函数


package charts

import (
	"bytes"
	"log"
	"os"

	chart "github.com/wcharczuk/go-chart/v2"
	drawing "github.com/wcharczuk/go-chart/v2/drawing"

	"golang.org/x/text/message"
)

// DrawChart3AndByteArr 即生成文件,也返回文件流, 邮件附件用
func DrawChart3AndByteArr(infoArr []string, seriesArr []chart.Series, fileName string) []byte {
	ba := DrawChart2ByteArr3(infoArr, seriesArr)
	log.Println(fileName)

	fo, err := os.Create(fileName)
	if err != nil {
		panic(err)
	}

	if _, err := fo.Write(ba); err != nil {
		panic(err)
	}

	return ba
}

// DrawChart2ByteArr3 多条曲线的绘制, 把这个走势图转换成 byte 数据
// X 轴 是同一个时间的多个曲线对比
func DrawChart2ByteArr3(infoArr []string, seriesArr []chart.Series) []byte {
	// log.Println(infoArr)
	pp := message.NewPrinter(message.MatchLanguage("en")) // https://godoc.org/golang.org/x/text/message

	graph := chart.Chart{
		Background: chart.Style{
			Padding: chart.Box{
				Top:  20,
				Left: 20,
			},
		},
		Font: GetZWFont(),
		XAxis: chart.XAxis{
			Name: "时间:分钟",
			NameStyle: chart.Style{
				//		Show:      true,
				FontColor: drawing.Color{R: 255, G: 0, B: 0, A: 255},
			},
			//			Style:          chart.StyleShow(),
			// ValueFormatter: chart.TimeValueFormatterWithFormat("2006-01-02 15:04"),
			// 2020-10-22 原先太占空间,只显示时间,不显示日期
			ValueFormatter: chart.TimeValueFormatterWithFormat("15:04"),
		},
		YAxis: chart.YAxis{
			// Name:      "QPS",
			// NameStyle: chart.StyleShow(),
			//			Style: chart.StyleShow(),
			ValueFormatter: func(v interface{}) string {
				if vf, isFloat := v.(float64); isFloat {
					// log.Println(vf)
					return pp.Sprintf("%0.f", vf)
				}
				// log.Printf("不是 float")
				// log.Println(v)
				return ""
			},
		},
		Series: seriesArr,
	}

	if len(infoArr) > 0 {
		//note we have to do this as a separate step because we need a reference to graph
		graph.Elements = []chart.Renderable{
			TextAndLineInfo(infoArr, &graph),
			// TextInfo(infoArr),
			// chart.LegendLeft(&graph),
		}
	}

	// log.Println("334")
	// log.Println(seriesArr)

	buffer := bytes.NewBuffer([]byte{})
	err := graph.Render(chart.PNG, buffer)
	if err != nil {
		log.Fatal(err)
	}

	return buffer.Bytes()

}

// TextAndLineInfo 图中绘制文字和线条说明
func TextAndLineInfo(txtArr []string, c *chart.Chart) chart.Renderable {

	var labels []string
	var lines []chart.Style
	for index, s := range c.Series {
		style := s.GetStyle()
		if !style.Hidden {
			if _, isAnnotationSeries := s.(chart.AnnotationSeries); !isAnnotationSeries {
				labels = append(labels, s.GetName())
				lines = append(lines, s.GetStyle().InheritFrom(getStyleDefaultsSeries(c, index)))
			}
		}
	}

	return func(r chart.Renderer, cb chart.Box, chartDefaults chart.Style) {
		// log.Println(fmt.Sprintf("h:%d; w:%d", cb.Height(), cb.Width()))
		// log.Println(chartDefaults)
		// log.Println(cb)

		r.SetFont(GetZWFont())
		r.SetFontColor(drawing.ColorBlue)
		r.SetFontSize(14)
		i := 0
		for _, txt := range txtArr {
			if len(txt) > 0 {
				r.Text(txt, 30+i*10, 40+i*30)
				i++
			}
		}

		tx := 30 + i*10
		for x := 0; x < len(labels); x++ {
			label := labels[x]
			if len(label) > 0 {
				// 写文字
				ty := 40 + i*30
				r.Text(label, tx, ty)

				// 绘线条
				tb := r.MeasureText(label)
				lx := tx + tb.Width() + 5
				ly := ty - 6

				r.SetStrokeColor(lines[x].GetStrokeColor())
				r.SetStrokeWidth(lines[x].GetStrokeWidth())
				r.SetStrokeDashArray(lines[x].GetStrokeDashArray())
				r.MoveTo(lx, ly)
				r.LineTo(lx+30, ly)
				r.Stroke()

				i++
			}
		}
		// log.Println("12444")
	}
}

// getStyleDefaultsSeries 获得默认信息
func getStyleDefaultsSeries(c *chart.Chart, seriesIndex int) chart.Style {
	return chart.Style{
		DotColor:    c.GetColorPalette().GetSeriesColor(seriesIndex),
		StrokeColor: c.GetColorPalette().GetSeriesColor(seriesIndex),
		StrokeWidth: chart.DefaultSeriesLineWidth,
		Font:        c.GetFont(),
		FontSize:    chart.DefaultFontSize,
	}
}

加载字体


package charts

import (
	"io/ioutil"
	"log"

	"github.com/golang/freetype/truetype"
)

const (
	fontFile = "/Library/Fonts/Microsoft-YaHei.ttf" // 需要使用的字体文件
)

// GetZWFont 画图跟字体有关的峰值
// 参考 https://www.cnblogs.com/ghj1976/p/3445568.html
// https://github.com/wcharczuk/go-chart/tree/master/_examples/text_rotation
func GetZWFont() *truetype.Font {
	// 读字体数据
	fontBytes, err := ioutil.ReadFile(fontFile)
	if err != nil {
		log.Println(err)
		return nil
	}
	font, err := truetype.Parse(fontBytes)
	if err != nil {
		log.Println(err)
		return nil
	}
	return font
}

绘图


package report

import (
	"fmt"
	"log"
	"os"
	"analysis"
	"charts"
	"tools"
	"time"

	"github.com/wcharczuk/go-chart/v2"
	"github.com/wcharczuk/go-chart/v2/drawing"
)

// 生成趋势图
func BuildTrendChart(cad *analysis.CacheAppkeyData) {
	// QPM
	buildTrendChartTransaction("", cad.Appkey, "qpm", cad.BeginTime, cad.EndTime, cad.TransactionQPMMap, cad.TransactionQPMpTestMap)
	log.Println("qpm")
	// AVG
	buildTrendChartTransaction("", cad.Appkey, "avg", cad.BeginTime, cad.EndTime, cad.TransactionAvgMap, cad.TransactionAvgpTestMap)
	log.Println("avg")
	// Error
	buildTrendChartTransaction("", cad.Appkey, "error", cad.BeginTime, cad.EndTime, cad.TransactionErrorMap, cad.TransactionErrorpTestMap)
	log.Println("error")

	buildTrendChartThroughput(cad.Appkey, cad.BeginTime, cad.EndTime, cad.TransactionQPMMap, cad.TransactionQPMpTestMap, cad.TransactionAvgMap, cad.TransactionAvgpTestMap)
	log.Println("throughput")

	buildTrendChartHosts("", cad.Appkey, "cpu.busy", cad.BeginTime, cad.EndTime, cad.HostsCPUBusyMap)
	log.Println("cpu.busy")
	buildTrendChartHosts("", cad.Appkey, "jvm.fullgc.count", cad.BeginTime, cad.EndTime, cad.HostsJvmFullgcCountMap)
	log.Println("jvm.fullgc.count")
	buildTrendChartHosts("", cad.Appkey, "jvm.thread.blocked.count", cad.BeginTime, cad.EndTime, cad.HostsJvmThreadBlockedCountMap)
	log.Println("jvm.thread.blocked.count")
	buildTrendChartHosts("", cad.Appkey, "mem.memused.percent", cad.BeginTime, cad.EndTime, cad.HostsMemMemusedPercentMap)
	log.Println("mem.memused.percent")
	buildTrendChartHosts("", cad.Appkey, "mem.swapused.percent", cad.BeginTime, cad.EndTime, cad.HostsMemSwapusedPercentMap)
	log.Println("mem.swapused.percent")

	// 容量 散点图
	BuildCapacityScatterPlot1(cad)
}

// 生成一个服务的多台机器的某个容器指标走势图
func buildTrendChartHosts(showtype, octokey, datatype string, begin, end time.Time, tMap map[string]map[time.Time]float64) {
	xvArr := [][]time.Time{}
	yvArr := [][]float64{}
	nameArr := []string{}
	hasData := false
	for hulkName, dmap := range tMap {
		nameArr = append(nameArr, hulkName)
		xv := []time.Time{}
		yv := []float64{}
		for curr := begin; !curr.After(end); curr = curr.Add(time.Minute) {
			v1, ex1 := dmap[curr]
			if ex1 {
				xv = append(xv, curr)
				yv = append(yv, v1)
				hasData = true
			}
		}
		xvArr = append(xvArr, xv)
		yvArr = append(yvArr, yv)
	}

	if hasData {
		centerTime, err := tools.GetCenterTime(begin, end)
		if err != nil {
			log.Println(err)
		}
		path := pathCheck(centerTime)

		fileName := fmt.Sprintf("%s/%s-%s-%s.png", path, octokey, datatype, centerTime.Format("20060102"))
		// 生成图片

		BuildIMGFile2(showtype, fileName, true, false, []string{fmt.Sprintf("%s-%s", octokey, datatype)}, nameArr, xvArr, yvArr...)

	}
}

// 生成 昨天、昨天压测、前天、上周同期,上月同期 这样几条走势线图
func buildTrendChartTransaction(showtype, octokey, datatype string, begin, end time.Time, tMap, tpTestMap map[time.Time]float64) {

	centerTime, err := tools.GetCenterTime(begin, end)
	if err != nil {
		log.Println(err)
	}

	hasData := false

	xv1 := []time.Time{}
	yv1 := []float64{}
	xvpTest := []time.Time{}
	yvpTest := []float64{}
	xvYesterday := []time.Time{}
	yvYesterday := []float64{}
	xvWeek := []time.Time{}
	yvWeek := []float64{}
	xvMonth := []time.Time{}
	yvMonth := []float64{}

	for curr := begin; !curr.After(end); curr = curr.Add(time.Minute) {
		v1, ex1 := tMap[curr]
		if ex1 {
			xv1 = append(xv1, curr)
			yv1 = append(yv1, v1)
			hasData = true
		} else {
			// 20211101 为了避免只有一个点问题, 如果没有值,开始和结束点的值设置为0
			if curr == begin {
				xv1 = append(xv1, curr)
				yv1 = append(yv1, 0.0)
			} else if curr == end {
				xv1 = append(xv1, curr)
				yv1 = append(yv1, 0.0)
			}
		}

		v2, ex2 := tpTestMap[curr]
		if ex2 {
			xvpTest = append(xvpTest, curr)
			yvpTest = append(yvpTest, v2)
			hasData = true
		}

		v3, ex3 := tMap[curr.AddDate(0, 0, -1)]
		if ex3 {
			xvYesterday = append(xvYesterday, curr)
			yvYesterday = append(yvYesterday, v3)
			hasData = true
		}

		v4, ex4 := tMap[curr.AddDate(0, 0, -7)]
		if ex4 {
			xvWeek = append(xvWeek, curr)
			yvWeek = append(yvWeek, v4)
			hasData = true
		}
		v5, ex5 := tMap[curr.AddDate(0, -1, 0)]
		if ex5 {
			xvMonth = append(xvMonth, curr)
			yvMonth = append(yvMonth, v5)
			hasData = true
		}
	}

	nameArr := []string{}
	xvArr := [][]time.Time{}
	yvArr := [][]float64{}
	if len(xv1) > 0 {
		nameArr = append(nameArr, fmt.Sprintf("昨日%s", centerTime.Format("20060102")))
		xvArr = append(xvArr, xv1)
		yvArr = append(yvArr, yv1)
	}
	if len(xvpTest) > 0 {
		nameArr = append(nameArr, fmt.Sprintf("昨日压测%s", centerTime.Format("20060102")))
		xvArr = append(xvArr, xvpTest)
		yvArr = append(yvArr, yvpTest)
	}
	if len(xvYesterday) > 0 {
		nameArr = append(nameArr, fmt.Sprintf("前日%s", centerTime.AddDate(0, 0, -1).Format("20060102")))
		xvArr = append(xvArr, xvYesterday)
		yvArr = append(yvArr, yvYesterday)
	}
	if len(xvWeek) > 0 {
		nameArr = append(nameArr, fmt.Sprintf("上周同期%s", centerTime.AddDate(0, 0, -7).Format("20060102")))
		xvArr = append(xvArr, xvWeek)
		yvArr = append(yvArr, yvWeek)
	}
	if len(xvMonth) > 0 {
		nameArr = append(nameArr, fmt.Sprintf("上月同期%s", centerTime.AddDate(0, -1, 0).Format("20060102")))
		xvArr = append(xvArr, xvMonth)
		yvArr = append(yvArr, yvMonth)
	}

	if hasData {

		path := pathCheck(centerTime)

		fileName := fmt.Sprintf("%s/%s-%s-%s.png", path, octokey, datatype, centerTime.Format("20060102"))
		// 生成图片
		BuildIMGFile2(showtype, fileName, true, true, []string{fmt.Sprintf("%s-%s", octokey, datatype)}, nameArr, xvArr, yvArr...)

	}
}

// 生成吞吐量走势图
func buildTrendChartThroughput(octokey string, begin, end time.Time, qpmMap, qpmpTestMap, avgMap, avgpTestMap map[time.Time]float64) {

	centerTime, err := tools.GetCenterTime(begin, end)
	if err != nil {
		log.Println(err)
	}

	hasData := false

	xv1 := []time.Time{}
	yv1 := []float64{}
	xvYesterday := []time.Time{}
	yvYesterday := []float64{}
	xvWeek := []time.Time{}
	yvWeek := []float64{}
	xvMonth := []time.Time{}
	yvMonth := []float64{}

	for curr := begin; !curr.After(end); curr = curr.Add(time.Minute) {

		// 昨日
		v1, ex1 := qpmMap[curr]
		vt1, ext1 := avgMap[curr]
		if ex1 && ext1 {
			xv1 = append(xv1, curr)
			vp1, exp1 := qpmpTestMap[curr]
			vtp1, extp1 := avgpTestMap[curr]
			if exp1 && extp1 {
				yv1 = append(yv1, v1*vt1+vp1*vtp1)
			} else {
				yv1 = append(yv1, v1*vt1)
			}
			hasData = true
		}

		// 前日
		v3, ex3 := qpmMap[curr.AddDate(0, 0, -1)]
		vt3, ext3 := avgMap[curr.AddDate(0, 0, -1)]
		if ex3 && ext3 {
			xvYesterday = append(xvYesterday, curr)
			yvYesterday = append(yvYesterday, v3*vt3)
			hasData = true
		}

		// 上周同期
		v4, ex4 := qpmMap[curr.AddDate(0, 0, -7)]
		vt4, ext4 := avgMap[curr.AddDate(0, 0, -7)]
		if ex4 && ext4 {
			xvWeek = append(xvWeek, curr)
			yvWeek = append(yvWeek, v4*vt4)
			hasData = true
		}

		// 上月同期
		v5, ex5 := qpmMap[curr.AddDate(0, -1, 0)]
		vt5, ext5 := avgMap[curr.AddDate(0, -1, 0)]
		if ex5 && ext5 {
			xvMonth = append(xvMonth, curr)
			yvMonth = append(yvMonth, v5*vt5)
			hasData = true
		}
	}

	nameArr := []string{}
	xvArr := [][]time.Time{}
	yvArr := [][]float64{}
	if len(xv1) > 0 {
		nameArr = append(nameArr, fmt.Sprintf("昨日%s吞吐量", centerTime.Format("20060102")))
		xvArr = append(xvArr, xv1)
		yvArr = append(yvArr, yv1)
	}

	if len(xvYesterday) > 0 {
		nameArr = append(nameArr, fmt.Sprintf("前日%s吞吐量", centerTime.AddDate(0, 0, -1).Format("20060102")))
		xvArr = append(xvArr, xvYesterday)
		yvArr = append(yvArr, yvYesterday)
	}
	if len(xvWeek) > 0 {
		nameArr = append(nameArr, fmt.Sprintf("上周同期%s吞吐量", centerTime.AddDate(0, 0, -7).Format("20060102")))
		xvArr = append(xvArr, xvWeek)
		yvArr = append(yvArr, yvWeek)
	}
	if len(xvMonth) > 0 {
		nameArr = append(nameArr, fmt.Sprintf("上月同期%s吞吐量", centerTime.AddDate(0, -1, 0).Format("20060102")))
		xvArr = append(xvArr, xvMonth)
		yvArr = append(yvArr, yvMonth)
	}

	if hasData {

		path := pathCheck(centerTime)

		fileName := fmt.Sprintf("%s/%s-%s-%s.png", path, octokey, "throughput", centerTime.Format("20060102"))
		// 生成图片
		BuildIMGFile2("dot", fileName, true, true, []string{fmt.Sprintf("%s-%s", octokey, "throughput")}, nameArr, xvArr, yvArr...)

	}
}

// 生成包含多条走势线的图
func BuildIMGFile2(showtype, filename string, enforce, showName bool, lbInfoArr, nameArr []string, xv [][]time.Time, yv ...[]float64) {
	_, err1 := os.ReadFile(filename)

	if !enforce && err1 == nil {
		// 非强制, 且有文件, 直接返回,不用再生成一次了
		log.Printf("不需要修改走势图%s\r", filename)
		log.Println(err1)
		return
	}

	// 流量走势图
	sArr := []chart.Series{}
	for i, name := range nameArr {
		if showtype == "dot" { // 20211031 全部用点来表示
			if showName {
				sArr = append(sArr, chart.TimeSeries{
					Name: name,
					Style: chart.Style{
						StrokeColor: getColor(i),
						StrokeWidth: chart.Disabled,
						DotWidth:    1,
					},
					XValues: xv[i], // 20211026 每一组x坐标都不一样,用于丢数据的场景
					YValues: yv[i], // i 是从 0 开始的
				})
			} else {
				sArr = append(sArr, chart.TimeSeries{
					Style: chart.Style{
						StrokeColor: getColor(i),
						StrokeWidth: chart.Disabled,
						DotWidth:    1,
					},
					XValues: xv[i], // 20211026 每一组x坐标都不一样,用于丢数据的场景
					YValues: yv[i], // i 是从 0 开始的
				})
			}

		} else { // 默认用线来表示
			if showName {

				sArr = append(sArr, chart.TimeSeries{
					Name: name,
					Style: chart.Style{
						StrokeColor: getColor(i),
					},
					XValues: xv[i], // 20211026 每一组x坐标都不一样,用于丢数据的场景
					YValues: yv[i], // i 是从 0 开始的
				})
			} else {
				sArr = append(sArr, chart.TimeSeries{
					Style: chart.Style{
						StrokeColor: getColor(i),
					},
					XValues: xv[i], // 20211026 每一组x坐标都不一样,用于丢数据的场景
					YValues: yv[i], // i 是从 0 开始的
				})
			}
		}
	}
	if len(nameArr) > 0 {
		log.Printf("开始绘图 %s\r\n", filename)
		charts.DrawChart3AndByteArr(lbInfoArr, sArr, filename)
	}
}

// 检查,并生成某天目录
func pathCheck(ct time.Time) string {
	// 产生保存目录 按照月产生对应目录,便于定期清理
	tools.PathCreate(fmt.Sprintf("./data/%s", ct.Format("200601")))
	tools.PathCreate(fmt.Sprintf("./data/%s/imgs", ct.Format("200601")))
	tools.PathCreate(fmt.Sprintf("./data/%s/imgs/%s", ct.Format("200601"), ct.Format("20060102")))
	tools.PathCreate(fmt.Sprintf("./data/%s/imgs/%s/day", ct.Format("200601"), ct.Format("20060102")))
	return fmt.Sprintf("./data/%s/imgs/%s/day", ct.Format("200601"), ct.Format("20060102"))
}

func getColor(i int) drawing.Color {
	switch i % 5 {
	case 0:
		return drawing.Color{R: 0, G: 0, B: 255, A: 255}
	case 1:
		return drawing.Color{R: 0, G: 255, B: 0, A: 255}
	case 2:
		return drawing.Color{R: 148, G: 0, B: 211, A: 255}
	case 3:
		return drawing.Color{R: 220, G: 20, B: 60, A: 255}
	case 4:
		return drawing.Color{R: 124, G: 205, B: 124, A: 255}
	default:
		return drawing.Color{R: 220, G: 160, B: 122, A: 255}
	}
}


标签:github,curr,log,nameArr,chart,go,append,cad
From: https://www.cnblogs.com/ghj1976/p/shi-yong-githubcomwcharczukgochart-hui-tu.html

相关文章

  • mongodb基本操作合集
    创建管理员账号useadmindb.createUser({user:"root",pwd:"xxxxxx",roles:[{role:"root",db:"admin"}]})其他库创建账号......
  • Window环境下,安装MongoDB
    一、下载MongoDB官网下载地址:https://www.mongodb.com/try/download/community,选择MongoDB版本,平台为Windows,本文选择的安装包格式为msi:二、安装下载完成后,双击下载的m......
  • CSRF和token以及用django实现
    csrfCSRF(Cross-SiteRequestForgery,跨站点伪造请求)是一种网络攻击方式,该攻击可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击站点,从而在未授权的情况下执......
  • git推送错误:error: failed to push some refs to 'https://github.com/xxxxxxxxxx.git
    git推送到github仓库时,报错,如图报错原因:出现错误的主要原因是github中的README.md文件不在本地代码目录中 解决方案:1、输入以下命令,将远程库中的更新合并到(pull=fetc......
  • Google翻译 失效 idea TKK 问题
    参考:https://bookfere.com/post/1020.html原因:谷歌翻译退出中国,可以更改host文件来实现正常使用直接使用版本142.250.0.90translate.googleapis.com142.250.0.90tr......
  • django-environ学习
    官方说明:https://django-environ.readthedocs.io/en/latest/index.htmlinstallpipinstalldjango-environquickstartimportenvironimportosenv=environ.Env(......
  • dmitryikh/leaves 的go版本例子
    packagemlimport( "github.com/dmitryikh/leaves")varmodelML*leaves.Ensemble//初始化加载模型funcinitML(){ ifmodelML!=nil{ return } useT......
  • mongodb踩坑
    mongo中的日期,在显示上,会比我们正常的时间少8h。如果向mongo中插入数据,会少8h如果从mongo中查出数据,那么在idea中会是正常的;而如果是在datagrip/navicat中查,那么显示的时......
  • 使用 Django 发送邮件, 以及遇到的一些问题
    尝试了下使用Django发送邮件在setting.py中的配置#mailconfigEMAIL_BACKEND='django.core.mail.backends.smtp.EmailBackend'EMAIL_HOST='smtp.qq.com'EM......
  • The Google File System 翻译和理解
    TheGoogleFileSystem摘要GFS是一个可扩展的分布式文件系统,用于大型分布式数据密集型应用上。它可以运行在便宜的普通硬件上,提供了高性能和一定的容错性。1.分布式......