| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740 |
- package gui
- import (
- "fmt"
- "github.com/g3n/engine/core"
- "github.com/g3n/engine/geometry"
- "github.com/g3n/engine/gls"
- "github.com/g3n/engine/graphic"
- "github.com/g3n/engine/material"
- "github.com/g3n/engine/math32"
- "github.com/g3n/engine/renderer/shader"
- "math"
- )
- func init() {
- shader.AddShader("shaderChartVertex", shaderChartVertex)
- shader.AddShader("shaderChartFrag", shaderChartFrag)
- shader.AddProgram("shaderChart", "shaderChartVertex", "shaderChartFrag")
- }
- //
- //
- // ChartLine implements a panel which can contain several line charts
- //
- //
- type ChartLine struct {
- Panel // Embedded panel
- title *Label // Optional title label
- left float32 // Left margin in pixels
- bottom float32 // Bottom margin in pixels
- top float32 // Top margin in pixels
- scaleX *ChartScaleX // X scale panel
- scaleY *ChartScaleY // Y scale panel
- startX int // Initial x offset in data buffers
- countX int // Count of data buffer points starting from startX
- firstX float32 // Label of first data point to show
- stepX float32 // Step to add to firstX for next data point
- minY float32 // Minimum Y value
- maxY float32 // Maximum Y value
- autoY bool // Auto range flag for Y values
- formatX string // String format for scale X labels
- formatY string // String format for scale Y labels
- labelsX []*Label // Array of scale X labels
- labelsY []*Label // Array of scale Y labels
- graphs []*LineGraph // Array of line graphs
- }
- const (
- deltaLine = 0.001 // Delta in NDC for lines over the boundary
- )
- // NewChartLine creates and returns a new line chart panel with
- // the specified dimensions in pixels.
- func NewChartLine(width, height float32) *ChartLine {
- cl := new(ChartLine)
- cl.Panel.Initialize(width, height)
- cl.left = 34
- cl.bottom = 20
- cl.top = 10
- cl.startX = 0
- cl.countX = 10
- cl.firstX = 0.0
- cl.stepX = 1.0
- cl.minY = 0.0
- cl.maxY = 10.0
- cl.autoY = false
- cl.formatX = "%v"
- cl.formatY = "%v"
- return cl
- }
- //func (cl *ChartLine) SetMargins(left, bottom float32) {
- //
- // cl.baseX, cl.baseY = cl.Pix2NDC(left, bottom)
- // cl.recalc()
- //}
- // SetTitle sets the chart title
- func (cl *ChartLine) SetTitle(title *Label) {
- if cl.title != nil {
- cl.Remove(cl.title)
- cl.title = nil
- }
- if title != nil {
- cl.Add(title)
- cl.title = title
- }
- cl.recalc()
- }
- // SetFormatX sets the string format of the X scale labels
- func (cl *ChartLine) SetFormatX(format string) {
- cl.formatX = format
- }
- // SetFormatY sets the string format of the Y scale labels
- func (cl *ChartLine) SetFormatY(format string) {
- cl.formatY = format
- }
- // SetScaleX sets the X scale number of lines and color
- func (cl *ChartLine) SetScaleX(lines int, color *math32.Color) {
- if cl.scaleX != nil {
- cl.ClearScaleX()
- }
- // Add scale lines
- cl.scaleX = newChartScaleX(cl, lines, color)
- cl.Add(cl.scaleX)
- // Add scale labels
- // The positions of the labels will be set by 'recalc()'
- value := cl.firstX + float32(cl.startX)*cl.stepX
- for i := 0; i < lines; i++ {
- l := NewLabel(fmt.Sprintf(cl.formatX, value))
- cl.Add(l)
- cl.labelsX = append(cl.labelsX, l)
- value += cl.stepX
- }
- cl.recalc()
- }
- // ClearScaleX removes the X scale if it was previously set
- func (cl *ChartLine) ClearScaleX() {
- if cl.scaleX == nil {
- return
- }
- // Remove and dispose scale lines
- cl.Remove(cl.scaleX)
- cl.scaleX.Dispose()
- // Remove and dispose scale labels
- for i := 0; i < len(cl.labelsX); i++ {
- label := cl.labelsX[i]
- cl.Remove(label)
- label.Dispose()
- }
- cl.labelsX = cl.labelsX[0:0]
- cl.scaleX = nil
- }
- // SetScaleY sets the Y scale number of lines and color
- func (cl *ChartLine) SetScaleY(lines int, color *math32.Color) {
- if cl.scaleY != nil {
- cl.ClearScaleY()
- }
- if lines < 2 {
- lines = 2
- }
- // Add scale lines
- cl.scaleY = newChartScaleY(cl, lines, color)
- cl.Add(cl.scaleY)
- // Add scale labels
- // The position of the labels will be set by 'recalc()'
- value := cl.minY
- step := (cl.maxY - cl.minY) / float32(lines-1)
- for i := 0; i < lines; i++ {
- l := NewLabel(fmt.Sprintf(cl.formatY, value))
- cl.Add(l)
- cl.labelsY = append(cl.labelsY, l)
- value += step
- }
- cl.recalc()
- }
- // ClearScaleY removes the Y scale if it was previously set
- func (cl *ChartLine) ClearScaleY() {
- if cl.scaleY == nil {
- return
- }
- // Remove and dispose scale lines
- cl.Remove(cl.scaleY)
- cl.scaleY.Dispose()
- // Remove and dispose scale labels
- for i := 0; i < len(cl.labelsY); i++ {
- label := cl.labelsY[i]
- cl.Remove(label)
- label.Dispose()
- }
- cl.labelsY = cl.labelsY[0:0]
- cl.scaleY = nil
- }
- // SetRangeX sets the interval of the data to be shown
- // start is the start position in the Y data array.
- // count is the number of data points to show, starting from the specified offset.
- func (cl *ChartLine) SetRangeX(start int, count int) {
- cl.startX = start
- cl.countX = count
- cl.updateLabelsX()
- // Update graphs
- for i := 0; i < len(cl.graphs); i++ {
- g := cl.graphs[i]
- g.updateData()
- }
- }
- // SetLabelX sets the value for the labels of the x scale
- // first is the value for the first data point
- // step is the value to be added for the next data point
- func (cl *ChartLine) SetLabelX(first float32, step float32) {
- cl.firstX = first
- cl.stepX = step
- cl.updateLabelsX()
- // Update graphs
- for i := 0; i < len(cl.graphs); i++ {
- g := cl.graphs[i]
- g.updateData()
- }
- }
- func (cl *ChartLine) SetRangeY(min float32, max float32) {
- cl.minY = min
- cl.maxY = max
- cl.updateLabelsY()
- // Update graphs
- for i := 0; i < len(cl.graphs); i++ {
- g := cl.graphs[i]
- g.updateData()
- }
- }
- func (cl *ChartLine) SetRangeYauto(auto bool) {
- cl.autoY = auto
- cl.updateLabelsY()
- // Update graphs
- for i := 0; i < len(cl.graphs); i++ {
- g := cl.graphs[i]
- g.updateData()
- }
- }
- // AddLine adds a line graph to the chart
- func (cl *ChartLine) AddGraph(color *math32.Color, data []float32) *LineGraph {
- graph := newLineGraph(cl, color, data)
- cl.graphs = append(cl.graphs, graph)
- cl.Add(graph)
- cl.recalc()
- return graph
- }
- func (cl *ChartLine) RemoveGraph(g *LineGraph) {
- cl.Remove(g)
- g.Dispose()
- for pos, current := range cl.graphs {
- if current == g {
- copy(cl.graphs[pos:], cl.graphs[pos+1:])
- cl.graphs[len(cl.graphs)-1] = nil
- cl.graphs = cl.graphs[:len(cl.graphs)-1]
- break
- }
- }
- }
- // updateLabelsX updates the X scale labels text
- func (cl *ChartLine) updateLabelsX() {
- if cl.scaleX == nil {
- return
- }
- pstep := (cl.ContentWidth() - cl.left) / float32(len(cl.labelsX))
- value := cl.firstX + float32(cl.startX)*cl.stepX
- for i := 0; i < len(cl.labelsX); i++ {
- label := cl.labelsX[i]
- label.SetText(fmt.Sprintf(cl.formatX, value))
- px := cl.left + float32(i)*pstep
- label.SetPosition(px, cl.ContentHeight()-cl.bottom)
- value += cl.stepX
- }
- }
- // updateLabelsY updates the Y scale labels text and positions
- func (cl *ChartLine) updateLabelsY() {
- if cl.scaleY == nil {
- return
- }
- th := float32(0)
- if cl.title != nil {
- th = cl.title.height
- }
- nlines := cl.scaleY.lines
- vstep := (cl.maxY - cl.minY) / float32(nlines-1)
- pstep := (cl.ContentHeight() - th - cl.top - cl.bottom) / float32(nlines-1)
- value := cl.minY
- for i := 0; i < nlines; i++ {
- label := cl.labelsY[i]
- label.SetText(fmt.Sprintf(cl.formatY, value))
- px := cl.left - 2 - label.Width()
- if px < 0 {
- px = 0
- }
- py := cl.ContentHeight() - cl.bottom - float32(i)*pstep
- label.SetPosition(px, py-label.Height()/2)
- value += vstep
- }
- }
- func (cl *ChartLine) calcRangeY() {
- if !cl.autoY {
- return
- }
- minY := float32(math.MaxFloat32)
- maxY := -float32(math.MaxFloat32)
- for g := 0; g < len(cl.graphs); g++ {
- graph := cl.graphs[g]
- for x := 0; x < cl.countX; x++ {
- if x+cl.startX >= len(graph.data) {
- break
- }
- vy := graph.data[x+cl.startX]
- if vy < minY {
- minY = vy
- }
- if vy > maxY {
- maxY = vy
- }
- }
- }
- cl.minY = minY
- cl.maxY = maxY
- }
- // recalc recalculates the positions of the inner panels
- func (cl *ChartLine) recalc() {
- // Center title position
- if cl.title != nil {
- xpos := (cl.ContentWidth() - cl.title.width) / 2
- cl.title.SetPositionX(xpos)
- }
- // Recalc scale X and its labels
- if cl.scaleX != nil {
- cl.scaleX.recalc()
- cl.updateLabelsX()
- }
- // Recalc scale Y and its labels
- if cl.scaleY != nil {
- cl.scaleY.recalc()
- cl.updateLabelsY()
- }
- // Recalc graphs
- for i := 0; i < len(cl.graphs); i++ {
- g := cl.graphs[i]
- g.recalc()
- cl.SetTopChild(g)
- }
- }
- //
- //
- // ChartScaleX is a panel with GL_LINES geometry which draws the chart X horizontal scale axis,
- // vertical lines and line labels.
- //
- //
- type ChartScaleX struct {
- Panel // Embedded panel
- chart *ChartLine // Container chart
- lines int // Number of vertical lines
- bounds gls.Uniform4f // Bound uniform in OpenGL window coordinates
- mat chartMaterial // Chart material
- }
- // newChartScaleX creates and returns a pointer to a new ChartScaleX for the specified
- // chart, number of lines and color
- func newChartScaleX(chart *ChartLine, lines int, color *math32.Color) *ChartScaleX {
- sx := new(ChartScaleX)
- sx.chart = chart
- sx.lines = lines
- sx.bounds.Init("Bounds")
- // Appends bottom horizontal line
- positions := math32.NewArrayF32(0, 0)
- positions.Append(0, -1+deltaLine, 0, 1, -1+deltaLine, 0)
- // Appends vertical lines
- step := 1 / float32(lines)
- for i := 0; i < lines; i++ {
- nx := float32(i) * step
- if i == 0 {
- nx += deltaLine
- }
- positions.Append(nx, 0, 0, nx, -1, 0)
- }
- // Creates geometry and adds VBO
- geom := geometry.NewGeometry()
- geom.AddVBO(gls.NewVBO().AddAttrib("VertexPosition", 3).SetBuffer(positions))
- // Initializes the panel graphic
- gr := graphic.NewGraphic(geom, gls.LINES)
- sx.mat.Init(color)
- gr.AddMaterial(sx, &sx.mat, 0, 0)
- sx.Panel.InitializeGraphic(chart.ContentWidth(), chart.ContentHeight(), gr)
- sx.recalc()
- return sx
- }
- func (sx *ChartScaleX) setLabelsText(x []float32) {
- }
- // recalc recalculates the position and size of this scale inside its parent
- func (sx *ChartScaleX) recalc() {
- py := sx.chart.top
- if sx.chart.title != nil {
- py += sx.chart.title.Height()
- }
- sx.SetPosition(sx.chart.left, py)
- sx.SetSize(sx.chart.ContentWidth()-sx.chart.left, sx.chart.ContentHeight()-py-sx.chart.bottom)
- }
- // RenderSetup is called by the renderer before drawing this graphic
- // It overrides the original panel RenderSetup
- // Calculates the model matrix and transfer to OpenGL.
- func (sx *ChartScaleX) RenderSetup(gs *gls.GLS, rinfo *core.RenderInfo) {
- //log.Error("ChartScaleX RenderSetup:%v", sx.pospix)
- // Sets model matrix and transfer to shader
- var mm math32.Matrix4
- sx.SetModelMatrix(gs, &mm)
- sx.modelMatrixUni.SetMatrix4(&mm)
- sx.modelMatrixUni.Transfer(gs)
- // Sets bounds in OpenGL window coordinates and transfer to shader
- _, _, _, height := gs.GetViewport()
- sx.bounds.Set(sx.pospix.X, float32(height)-sx.pospix.Y, sx.width, sx.height)
- sx.bounds.Transfer(gs)
- }
- //
- //
- // ChartScaleY is a panel with LINE geometry which draws the chart Y vertical scale axis,
- // horizontal and labels.
- //
- //
- type ChartScaleY struct {
- Panel // Embedded panel
- chart *ChartLine // Container chart
- lines int // Number of horizontal lines
- bounds gls.Uniform4f // Bound uniform in OpenGL window coordinates
- mat chartMaterial // Chart material
- }
- // newChartScaleY creates and returns a pointer to a new ChartScaleY for the specified
- // chart, number of lines and color
- func newChartScaleY(chart *ChartLine, lines int, color *math32.Color) *ChartScaleY {
- if lines < 2 {
- lines = 2
- }
- sy := new(ChartScaleY)
- sy.chart = chart
- sy.lines = lines
- sy.bounds.Init("Bounds")
- // Appends left vertical line
- positions := math32.NewArrayF32(0, 0)
- positions.Append(0+deltaLine, 0, 0, 0+deltaLine, -1, 0)
- // Appends horizontal lines
- step := 1 / float32(lines-1)
- for i := 0; i < lines; i++ {
- ny := -1 + float32(i)*step
- if i == 0 {
- ny += deltaLine
- }
- if i == lines-1 {
- ny -= deltaLine
- }
- positions.Append(0, ny, 0, 1, ny, 0)
- }
- // Creates geometry and adds VBO
- geom := geometry.NewGeometry()
- geom.AddVBO(gls.NewVBO().AddAttrib("VertexPosition", 3).SetBuffer(positions))
- // Initializes the panel with this graphic
- gr := graphic.NewGraphic(geom, gls.LINES)
- sy.mat.Init(color)
- gr.AddMaterial(sy, &sy.mat, 0, 0)
- sy.Panel.InitializeGraphic(chart.ContentWidth(), chart.ContentHeight(), gr)
- sy.recalc()
- return sy
- }
- // recalc recalculates the position and size of this scale inside its parent
- func (sy *ChartScaleY) recalc() {
- py := sy.chart.top
- if sy.chart.title != nil {
- py += sy.chart.title.Height()
- }
- sy.SetPosition(sy.chart.left, py)
- sy.SetSize(sy.chart.ContentWidth()-sy.chart.left, sy.chart.ContentHeight()-py-sy.chart.bottom)
- }
- // RenderSetup is called by the renderer before drawing this graphic
- // It overrides the original panel RenderSetup
- // Calculates the model matrix and transfer to OpenGL.
- func (sy *ChartScaleY) RenderSetup(gs *gls.GLS, rinfo *core.RenderInfo) {
- //log.Error("ChartScaleY RenderSetup:%v", sy.pospix)
- // Sets model matrix and transfer to shader
- var mm math32.Matrix4
- sy.SetModelMatrix(gs, &mm)
- sy.modelMatrixUni.SetMatrix4(&mm)
- sy.modelMatrixUni.Transfer(gs)
- // Sets bounds in OpenGL window coordinates and transfer to shader
- _, _, _, height := gs.GetViewport()
- sy.bounds.Set(sy.pospix.X, float32(height)-sy.pospix.Y, sy.width, sy.height)
- sy.bounds.Transfer(gs)
- }
- //
- //
- // LineGraph
- //
- //
- type LineGraph struct {
- Panel // Embedded panel
- chart *ChartLine // Container chart
- color math32.Color // Line color
- data []float32 // Data y
- bounds gls.Uniform4f // Bound uniform in OpenGL window coordinates
- mat chartMaterial // Chart material
- vbo *gls.VBO
- positions math32.ArrayF32
- }
- func newLineGraph(chart *ChartLine, color *math32.Color, y []float32) *LineGraph {
- lg := new(LineGraph)
- lg.bounds.Init("Bounds")
- lg.chart = chart
- lg.color = *color
- lg.data = y
- // Creates geometry and adds VBO with positions
- geom := geometry.NewGeometry()
- lg.vbo = gls.NewVBO().AddAttrib("VertexPosition", 3)
- lg.positions = math32.NewArrayF32(0, 0)
- lg.vbo.SetBuffer(lg.positions)
- geom.AddVBO(lg.vbo)
- // Initializes the panel with this graphic
- gr := graphic.NewGraphic(geom, gls.LINE_STRIP)
- lg.mat.Init(&lg.color)
- gr.AddMaterial(lg, &lg.mat, 0, 0)
- lg.Panel.InitializeGraphic(lg.chart.ContentWidth(), lg.chart.ContentHeight(), gr)
- lg.SetData(y)
- return lg
- }
- func (lg *LineGraph) SetColor(color *math32.Color) {
- }
- func (lg *LineGraph) SetData(data []float32) {
- lg.data = data
- lg.updateData()
- }
- func (lg *LineGraph) SetLineWidth(width float32) {
- lg.mat.SetLineWidth(width)
- }
- func (lg *LineGraph) updateData() {
- lg.chart.calcRangeY()
- positions := math32.NewArrayF32(0, 0)
- step := 1.0 / float32(lg.chart.countX-1)
- rangeY := lg.chart.maxY - lg.chart.minY
- for i := 0; i < lg.chart.countX; i++ {
- x := i + lg.chart.startX
- if x >= len(lg.data) {
- break
- }
- px := float32(i) * step
- vy := lg.data[x]
- py := -1 + ((vy - lg.chart.minY) / rangeY)
- positions.Append(px, py, 0)
- }
- lg.vbo.SetBuffer(positions)
- }
- func (lg *LineGraph) recalc() {
- py := lg.chart.top
- if lg.chart.title != nil {
- py += lg.chart.title.Height()
- }
- px := lg.chart.left
- w := lg.chart.ContentWidth() - lg.chart.left
- h := lg.chart.ContentHeight() - py - lg.chart.bottom
- lg.SetPosition(px, py)
- lg.SetSize(w, h)
- }
- // RenderSetup is called by the renderer before drawing this graphic
- // It overrides the original panel RenderSetup
- // Calculates the model matrix and transfer to OpenGL.
- func (lg *LineGraph) RenderSetup(gs *gls.GLS, rinfo *core.RenderInfo) {
- //log.Error("LineGraph RenderSetup:%v with/height: %v/%v", lg.posclip, lg.wclip, lg.hclip)
- // Sets model matrix and transfer to shader
- var mm math32.Matrix4
- lg.SetModelMatrix(gs, &mm)
- lg.modelMatrixUni.SetMatrix4(&mm)
- lg.modelMatrixUni.Transfer(gs)
- // Sets bounds in OpenGL window coordinates and transfer to shader
- _, _, _, height := gs.GetViewport()
- lg.bounds.Set(lg.pospix.X, float32(height)-lg.pospix.Y, lg.width, lg.height)
- lg.bounds.Transfer(gs)
- }
- //
- //
- // Chart material (for lines)
- //
- //
- type chartMaterial struct {
- material.Material // Embedded material
- color *gls.Uniform3f // Emissive color uniform
- }
- func (cm *chartMaterial) Init(color *math32.Color) {
- cm.Material.Init()
- cm.SetShader("shaderChart")
- // Creates uniforms and adds to material
- cm.color = gls.NewUniform3f("MatColor")
- // Set initial values
- cm.color.SetColor(color)
- }
- func (cm *chartMaterial) RenderSetup(gs *gls.GLS) {
- cm.Material.RenderSetup(gs)
- cm.color.Transfer(gs)
- }
- //
- // Vertex Shader template
- //
- const shaderChartVertex = `
- #version {{.Version}}
- // Vertex attributes
- {{template "attributes" .}}
- // Input uniforms
- uniform mat4 ModelMatrix;
- uniform vec3 MatColor;
- // Outputs for fragment shader
- out vec3 Color;
- void main() {
- Color = MatColor;
- // Set position
- vec4 pos = vec4(VertexPosition.xyz, 1);
- vec4 posclip = ModelMatrix * pos;
- gl_Position = posclip;
- }
- `
- //
- // Fragment Shader template
- //
- const shaderChartFrag = `
- #version {{.Version}}
- // Input uniforms from vertex shader
- in vec3 Color;
- // Input uniforms
- uniform vec4 Bounds;
- // Output
- out vec4 FragColor;
- void main() {
- // Discard fragment outside of the received bounds in OpenGL window pixel coordinates
- // Bounds[0] - x
- // Bounds[1] - y
- // Bounds[2] - width
- // Bounds[3] - height
- if (gl_FragCoord.x < Bounds[0] || gl_FragCoord.x > Bounds[0] + Bounds[2]) {
- discard;
- }
- if (gl_FragCoord.y > Bounds[1] || gl_FragCoord.y < Bounds[1] - Bounds[3]) {
- discard;
- }
- FragColor = vec4(Color, 1.0);
- }
- `
|