Перейти ежедневный сюжет библиотеки

Go

Введение

В этой статье представлена ​​очень мощная и простая в использовании библиотека рисования для языка Go —plot.plotВстроено множество часто используемых компонентов, которые в основном удовлетворяют повседневные потребности. В то же время он также предоставляет настраиваемый интерфейс, который может реализовать наши индивидуальные потребности.plotОн в основном используется для визуализации данных, чтобы мы могли наблюдать и сравнивать.

быстрый в использовании

Сначала установите:

$ go get gonum.org/v1/plot/...

После использования:

package main

import (
  "log"
  "math/rand"

  "gonum.org/v1/plot"
  "gonum.org/v1/plot/plotter"
  "gonum.org/v1/plot/plotutil"
  "gonum.org/v1/plot/vg"
)

func main() {
  rand.Seed(int64(0))

  p, err := plot.New()
  if err != nil {
    log.Fatal(err)
  }

  p.Title.Text = "Get Started"
  p.X.Label.Text = "X"
  p.Y.Label.Text = "Y"

  err = plotutil.AddLinePoints(p,
    "First", randomPoints(15),
    "Second", randomPoints(15),
    "Third", randomPoints(15))
  if err != nil {
    log.Fatal(err)
  }

  if err = p.Save(4*vg.Inch, 4*vg.Inch, "points.png"); err != nil {
    log.Fatal(err)
  }
}

func randomPoints(n int) plotter.XYs {
  points := make(plotter.XYs, n)
  for i := range points {
    if i == 0 {
      points[i].X = rand.Float64()
    } else {
      points[i].X = points[i-1].X + rand.Float64()
    }
    points[i].Y = points[i].X + 10 * rand.Float64()
  }

  return points
}

вывод запуска программыpoints.pngФайл изображения:

plotиспользование относительно простое. Во-первых, позвонитеplot.New()Создайте «холст» со следующей структурой:

// Plot is the basic type representing a plot.
type Plot struct {
  Title struct {
    Text string
    Padding vg.Length
    draw.TextStyle
  }
  BackgroundColor color.Color
  X, Y Axis
  Legend Legend
  plotters []Plotter
}

Затем установите свойства изображения, напрямую назначив значения полям структуры холста. Напримерp.Title.Text = "Get Startedустановить содержание заголовка изображения;p.X.Label.Text = "X",p.Y.Label.Text = "Y"Задает имена меток для осей X и Y изображения.

Затем используйтеplotutilИли другие методы подпакета для рисования на холсте, которые вызываются в приведенном выше коде.AddLinePoints()Рисуется 3 полилинии.

Наконец, сохраните изображение, которое вызывается в приведенном выше коде.p.Save()метод сохранения изображения в файл.

больше графики

gonum/plotИнкапсулируйте интерфейсы на разных уровнях в определенные подпакеты:

  • plot: обеспечивает простой интерфейс для компоновки и рисования;
  • plotter:использоватьplotВ предоставленном интерфейсе реализован стандартный набор графопостроителей, таких как скаттер, бар, прямоугольник и т.д. можно использоватьplotterПредоставляемый интерфейс реализует собственный плоттер;
  • plotutil: Предоставляет удобный способ рисования обычной графики;
  • vg: инкапсулирует различные серверные части и предоставляет общий API векторной графики.

гистограмма

Гистограмма той же шириныбарВысота или длина данных указывает на отношение размера данных. Сравнение данных одного и того же типа делает разницу очень интуитивной, и мы часто используем гистограммы при сравнении производительности нескольких библиотек. Ниже мы используемjson-iter/goРепозиторий GitHub для сравненияjsoniter,easyjson,stdТри библиотеки JSON обрабатывают данные для рисования гистограмм:

package main

import (
  "log"

  "gonum.org/v1/plot"
  "gonum.org/v1/plot/plotter"
  "gonum.org/v1/plot/plotutil"
  "gonum.org/v1/plot/vg"
)

func main() {
  std := plotter.Values{35510, 1960, 99}
  easyjson := plotter.Values{8499, 160, 4}
  jsoniter := plotter.Values{5623, 160, 3}

  p, err := plot.New()
  if err != nil {
    log.Fatal(err)
  }

  p.Title.Text = "jsoniter vs easyjson vs std"
  p.Y.Label.Text = ""

  w := vg.Points(20)
  stdBar, err := plotter.NewBarChart(std, w)
  if err != nil {
    log.Fatal(err)
  }
  stdBar.LineStyle.Width = vg.Length(0)
  stdBar.Color = plotutil.Color(0)
  stdBar.Offset = -w

  easyjsonBar, err := plotter.NewBarChart(easyjson, w)
  if err != nil {
    log.Fatal(err)
  }
  easyjsonBar.LineStyle.Width = vg.Length(0)
  easyjsonBar.Color = plotutil.Color(1)

  jsoniterBar, err := plotter.NewBarChart(jsoniter, w)
  if err != nil {
    log.Fatal(err)
  }
  jsoniterBar.LineStyle.Width = vg.Length(0)
  jsoniterBar.Color = plotutil.Color(2)
  jsoniterBar.Offset = w

  p.Add(stdBar, easyjsonBar, jsoniterBar)
  p.Legend.Add("std", stdBar)
  p.Legend.Add("easyjson", easyjsonBar)
  p.Legend.Add("jsoniter", jsoniterBar)
  p.Legend.Top = true
  p.NominalX("ns/op", "allocation bytes", "allocation times")

  if err = p.Save(5*vg.Inch, 5*vg.Inch, "barchart.png"); err != nil {
    log.Fatal(err)
  }
}

Сначала создайте список значений, мы создали список 2D-координат в первом примере.plotter.XYs, а на самом деле список 3D координатplotter.XYZs.

Затем позвонитеplotter.NewBarChart()Гистограммы создаются для каждого из трех наборов данных.w = vg.Points(20)Используется для установки ширины полосы.LineStyle.WidthУстановите ширину линии, которая на самом деле является шириной границы.ColorУстановите цвет.OffsetУстановите смещение, так как каждый набор баров в соответствующем месте отображается для лучшего сравнения, установитеstdBar.OffsetУстановить как-wсместит его на одну ширину бара влево;easyjsonСмещение не установлено, по умолчанию 0, без смещения;jsoniterсмещение установлено наw, смещение на одну ширину полосы вправо. В конце концов они показаны рядом друг с другом.

Затем добавьте 3 полосы на холст. Далее установите ихлегенда, и отобразите его вверху.

последний звонокp.Save()сохранить изображение.

Запуск программы дает следующее изображение:

можно ясно увидетьjsoniterПроизводительность, использование памяти и время выделения памяти — все на высшем уровне.Возможно, удастся использовать данные той же размерности, порядок величины не сильно отличается, и изображение будет выглядеть лучше (┬_┬).

Уведомлениеplotter.Color(2)такое использование.plotНабор значений цвета предопределен, если мы хотим их использовать, мы можем напрямую передать индекс, чтобы получить соответствующий цвет, чтобы различать разные графики (например, 3 гистограммы выше используют 3 разных индекса):

// src/gonum.org/v1/plot/plotutil/plotutil.go
var DefaultColors = SoftColors
var SoftColors = []color.Color{
  rgb(241, 90, 96),
  rgb(122, 195, 106),
  rgb(90, 155, 212),
  rgb(250, 167, 91),
  rgb(158, 103, 171),
  rgb(206, 112, 88),
  rgb(215, 127, 180),
}

func Color(i int) color.Color {
  n := len(DefaultColors)
  if i < 0 {
    return DefaultColors[i%n+n]
  }
  return DefaultColors[i%n]
}

Кроме цвета есть формаplotter.Shape(i)и режим тиреplotter.Dashes(i).

vg.Length(0)отличается, этот просто преобразует 0 вvg.Lengthтип!

Изображение функции

plotГрафики функций можно рисовать!

func main() {
  p, err := plot.New()
  if err != nil {
    log.Fatal(err)
  }
  p.Title.Text = "Functions"
  p.X.Label.Text = "X"
  p.Y.Label.Text = "Y"

  square := plotter.NewFunction(func(x float64) float64 { return x * x })
  square.Color = plotutil.Color(0)

  sqrt := plotter.NewFunction(func(x float64) float64 { return 10 * math.Sqrt(x) })
  sqrt.Dashes = []vg.Length{vg.Points(1), vg.Points(2)}
  sqrt.Width = vg.Points(1)
  sqrt.Color = plotutil.Color(1)

  exp := plotter.NewFunction(func(x float64) float64 { return math.Pow(2, x) })
  exp.Dashes = []vg.Length{vg.Points(2), vg.Points(3)}
  exp.Width = vg.Points(2)
  exp.Color = plotutil.Color(2)

  sin := plotter.NewFunction(func(x float64) float64 { return 10*math.Sin(x) + 50 })
  sin.Dashes = []vg.Length{vg.Points(3), vg.Points(4)}
  sin.Width = vg.Points(3)
  sin.Color = plotutil.Color(3)

  p.Add(square, sqrt, exp, sin)
  p.Legend.Add("x^2", square)
  p.Legend.Add("10*sqrt(x)", sqrt)
  p.Legend.Add("2^x", exp)
  p.Legend.Add("10*sin(x)+50", sin)
  p.Legend.ThumbnailWidth = 0.5 * vg.Inch

  p.X.Min = 0
  p.X.Max = 10
  p.Y.Min = 0
  p.Y.Max = 100

  if err = p.Save(4*vg.Inch, 4*vg.Inch, "functions.png"); err != nil {
    log.Fatal(err)
  }
}

первый звонокplotter.NewFunction()Создайте образ функции. Он принимает функцию с одним входным параметромfloat64, единственный выходной параметрfloat64, поэтому он может рисовать изображение функции только одной независимой переменной. Затем установите три свойства для изображения функцииDashes(подчеркнуто),Width(ширина линии) иColor(цвет). По умолчанию непрерывные линии используются для рисования функций, таких как квадратная функция на рисунке. может быть установленDashesпозволятьplotрисовать прерывистые линии,DashesПринимает два значения длины, первая длина представляет собой разделительное расстояние, а вторая длина представляет собой длину непрерывной линии. также используется здесьplotutil.Color(i)Первые 4 предустановленных цвета используются последовательно.

Создание холста и настройка легенды такие же, как и раньше. тоже проходил здесьp.Xиp.YизMin/MaxСвойство ограничивает диапазон координат, для которого рисуется изображение.

Запустите программу для создания образа:

пузырьковая диаграмма

использоватьplotВы можете нарисовать очень красивую пузырьковую диаграмму:

func main() {
  n := 10
  bubbleData := randomTriples(n)

  minZ, maxZ := math.Inf(1), math.Inf(-1)
  for _, xyz := range bubbleData {
    if xyz.Z > maxZ {
      maxZ = xyz.Z
    }
    if xyz.Z < minZ {
      minZ = xyz.Z
    }
  }

  p, err := plot.New()
  if err != nil {
    log.Fatal(err)
  }
  p.Title.Text = "Bubbles"
  p.X.Label.Text = "X"
  p.Y.Label.Text = "Y"

  bs, err := plotter.NewScatter(bubbleData)
  if err != nil {
    log.Fatal(err)
  }
  bs.GlyphStyleFunc = func(i int) draw.GlyphStyle {
    c := color.RGBA{R: 196, B: 128, A: 255}
    var minRadius, maxRadius = vg.Points(1), vg.Points(20)
    rng := maxRadius - minRadius
    _, _, z := bubbleData.XYZ(i)
    d := (z - minZ) / (maxZ - minZ)
    r := vg.Length(d)*rng + minRadius
    return draw.GlyphStyle{Color: c, Radius: r, Shape: draw.CircleGlyph{}}
  }
  p.Add(bs)

  if err = p.Save(4*vg.Inch, 4*vg.Inch, "bubble.png"); err != nil {
    log.Fatal(err)
  }
}

func randomTriples(n int) plotter.XYZs {
  data := make(plotter.XYZs, n)
  for i := range data {
    if i == 0 {
      data[i].X = rand.Float64()
    } else {
      data[i].X = data[i-1].X + 2*rand.Float64()
    }
    data[i].Y = data[i].X + 10*rand.Float64()
    data[i].Z = data[i].X
  }

  return data
}

Мы генерируем набор трехмерных координатных точек, вызываяplotter.NewScatter()Сгенерируйте точечную диаграмму. мы установилиGlyphStyleFuncФункция ловушки, которая вызывается перед отрисовкой каждой точки, возвращаетdraw.GlyphStyleтип,plotБудет отрисован в соответствии с возвращенным объектом. В нашем примере каждый раз, когда мы возвращаемdraw.GlyphStyleобъект, черезZОтношение координат к наибольшей и наименьшей координатам отображается в [vg.Points(1),vg.Points(20)] интервал, чтобы получить радиус.

Сгенерированное изображение:

Точно так же мы можем вернуть квадратdraw.GlyphStyleобъект для рисования «квадратного графика», просто поместите функцию ловушкиGlyphStyleFuncвнесите некоторые изменения в оператор return:

return draw.GlyphStyle{Color: c, Radius: r, Shape: draw.SquareGlyph{}}

Можно нарисовать "квадратную схему" 😄:

практическое применение

Ниже мы применяем то, что было введено в предыдущей статьеgopsutilи в этой статьеplotСоздайте веб-страницу для наблюдения за использованием ЦП и памяти машины в режиме реального времени:

func index(w http.ResponseWriter, r *http.Request) {
  t, err := template.ParseFiles("index.html")
  if err != nil {
    log.Fatal(err)
  }

  t.Execute(w, nil)
}

func image(w http.ResponseWriter, r *http.Request) {
  monitor.WriteTo(w)
}

func main() {
  mux := http.NewServeMux()
  mux.HandleFunc("/", index)
  mux.HandleFunc("/image", image)

  go monitor.Run()

  s := &http.Server{
    Addr:    ":8080",
    Handler: mux,
  }
  if err := s.ListenAndServe(); err != nil {
    log.Fatal(err)
  }
}

Сначала мы написали HTTP-сервер, прослушивающий порт 8080. Настроить два маршрута,/показать главную страницу,/imageперечислитьMonitorВозвращаются методы построения графиков использования ЦП и памяти.MonitorСтруктура будет представлена ​​позже.index.htmlСодержание следующее:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Monitor</title>
</head>
<body>
  <img src="/image" alt="" id="img">
  <script>
    let img = document.querySelector("#img")
    setInterval(function () {
      img.src = "/image?s=" + Math.random()
    }, 500)
  </script>
</body>
</html>

Страница относительно проста, просто показывает изображение. Затем запустите таймер на 500 мс в JS и повторно запрашивайте изображение для замены существующего изображения каждые 500 мс. я устанавливаюimg.srcК обратной стороне атрибута добавляется случайное число, что предотвращает попадание в кеш самой последней картинки.

Посмотрите нижеMonitorСтруктура:

type Monitor struct {
  Mem       []float64
  CPU       []float64
  MaxRecord int
  Lock      sync.Mutex
}

func NewMonitor(max int) *Monitor {
  return &Monitor{
    MaxRecord: max,
  }
}

var monitor = NewMonitor(50)

В этой структуре записываются последние 50 записей. Каждые 500 мс данные об использовании ЦП и памяти будут собираться и записываться вCPUиMemВ поле:

func (m *Monitor) Collect() {
  mem, err := mem.VirtualMemory()
  if err != nil {
    log.Fatal(err)
  }

  cpu, err := cpu.Percent(500*time.Millisecond, false)
  if err != nil {
    log.Fatal(err)
  }

  m.Lock.Lock()
  defer m.Lock.Unlock()

  m.Mem = append(m.Mem, mem.UsedPercent)
  m.CPU = append(m.CPU, cpu[0])
}

func (m *Monitor) Run() {
  for {
    m.Collect()
    time.Sleep(500 * time.Millisecond)
  }
}

когда HTTP-запрос/imageПри роутинге, согласно собранному на данный моментCPUиMemОбраз генерации данных возвращает:

func (m *Monitor) WriteTo(w io.Writer) {
  m.Lock.Lock()
  defer m.Lock.Unlock()

  cpuData := make(plotter.XYs, len(m.CPU))
  for i, p := range m.CPU {
    cpuData[i].X = float64(i + 1)
    cpuData[i].Y = p
  }

  memData := make(plotter.XYs, len(m.Mem))
  for i, p := range m.Mem {
    memData[i].X = float64(i + 1)
    memData[i].Y = p
  }

  p, err := plot.New()
  if err != nil {
    log.Fatal(err)
  }

  cpuLine, err := plotter.NewLine(cpuData)
  if err != nil {
    log.Fatal(err)
  }
  cpuLine.Color = plotutil.Color(1)

  memLine, err := plotter.NewLine(memData)
  if err != nil {
    log.Fatal(err)
  }
  memLine.Color = plotutil.Color(2)

  p.Add(cpuLine, memLine)

  p.Legend.Add("cpu", cpuLine)
  p.Legend.Add("mem", memLine)

  p.X.Min = 0
  p.X.Max = float64(m.MaxRecord)
  p.Y.Min = 0
  p.Y.Max = 100

  wc, err := p.WriterTo(4*vg.Inch, 4*vg.Inch, "png")
  if err != nil {
    log.Fatal(err)
  }
  wc.WriteTo(w)
}

Запустите сервер:

$ go run main.go

Откройте браузер и введитеlocalhost:8080, наблюдайте за изменениями изображения:

Суммировать

В этой статье представлена ​​мощная библиотека для рисованияplot, и, наконец, заканчивается программой мониторинга. Из-за ограничений по площади,plotРазличные предоставленные типы чертежей не могут быть описаны по отдельности.plotтакже поддерживаетsvg/pdfи другие форматы для сохранения. Заинтересованную детскую обувь можно изучить самостоятельно.

Если вы найдете забавную и простую в использовании языковую библиотеку Go, вы можете отправить сообщение о проблеме в ежедневной библиотеке Go GitHub😄

Ссылаться на

  1. Сюжет Гитхаб:github.com/gonum/plot
  2. Example Plots: GitHub.com/go num/сюжет/…
  3. Перейти на ежедневный репозиторий GitHub:GitHub.com/Darenjun/go-of…

я

мой блог:darjun.github.io

Добро пожаловать, чтобы обратить внимание на мою общедоступную учетную запись WeChat [GoUpUp], учитесь вместе и добивайтесь прогресса вместе ~