chart.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797
  1. // Copyright 2016 The G3N Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package gui
  5. import (
  6. "fmt"
  7. "github.com/g3n/engine/core"
  8. "github.com/g3n/engine/geometry"
  9. "github.com/g3n/engine/gls"
  10. "github.com/g3n/engine/graphic"
  11. "github.com/g3n/engine/material"
  12. "github.com/g3n/engine/math32"
  13. "github.com/g3n/engine/renderer/shaders"
  14. "math"
  15. )
  16. func init() {
  17. shaders.AddShader("shaderChartVertex", shaderChartVertex)
  18. shaders.AddShader("shaderChartFrag", shaderChartFrag)
  19. shaders.AddProgram("shaderChart", "shaderChartVertex", "shaderChartFrag")
  20. }
  21. //
  22. // Chart implements a panel which can contain a title, an x scale,
  23. // an y scale and several graphs
  24. //
  25. type Chart struct {
  26. Panel // Embedded panel
  27. left float32 // Left margin in pixels
  28. bottom float32 // Bottom margin in pixels
  29. top float32 // Top margin in pixels
  30. firstX float32 // Value for the first x label
  31. stepX float32 // Step for the next x label
  32. countStepX float32 // Number of values per x step
  33. minY float32 // Minimum Y value
  34. maxY float32 // Maximum Y value
  35. autoY bool // Auto range flag for Y values
  36. formatX string // String format for scale X labels
  37. formatY string // String format for scale Y labels
  38. fontSizeX float64 // X scale label font size
  39. fontSizeY float64 // Y scale label font size
  40. title *Label // Optional title label
  41. scaleX *chartScaleX // X scale panel
  42. scaleY *chartScaleY // Y scale panel
  43. labelsX []*Label // Array of scale X labels
  44. labelsY []*Label // Array of scale Y labels
  45. graphs []*Graph // Array of line graphs
  46. }
  47. const (
  48. deltaLine = 0.001 // Delta in NDC for lines over the boundary
  49. )
  50. // NewChart creates and returns a new chart panel with
  51. // the specified dimensions in pixels.
  52. func NewChart(width, height float32) *Chart {
  53. ch := new(Chart)
  54. ch.Init(width, height)
  55. return ch
  56. }
  57. // Init initializes a new chart with the specified width and height
  58. // It is normally used to initialize a Chart embedded in a struct
  59. func (ch *Chart) Init(width float32, height float32) {
  60. ch.Panel.Initialize(width, height)
  61. ch.left = 40
  62. ch.bottom = 20
  63. ch.top = 10
  64. ch.firstX = 0
  65. ch.stepX = 1
  66. ch.countStepX = 1
  67. ch.minY = -10.0
  68. ch.maxY = 10.0
  69. ch.autoY = false
  70. ch.formatX = "%v"
  71. ch.formatY = "%v"
  72. ch.fontSizeX = 14
  73. ch.fontSizeY = 14
  74. ch.Subscribe(OnResize, ch.onResize)
  75. }
  76. // SetTitle sets the chart title text and font size.
  77. // To remove the title pass an empty string
  78. func (ch *Chart) SetTitle(title string, size float64) {
  79. // Remove title
  80. if title == "" {
  81. if ch.title != nil {
  82. ch.Remove(ch.title)
  83. ch.title.Dispose()
  84. ch.title = nil
  85. ch.recalc()
  86. }
  87. return
  88. }
  89. // Sets title
  90. if ch.title == nil {
  91. ch.title = NewLabel(title)
  92. ch.title.SetColor4(math32.NewColor4("black"))
  93. ch.Add(ch.title)
  94. }
  95. ch.title.SetText(title)
  96. ch.title.SetFontSize(size)
  97. ch.recalc()
  98. }
  99. // SetMarginY sets the y scale margin
  100. func (ch *Chart) SetMarginY(left float32) {
  101. ch.left = left
  102. ch.recalc()
  103. }
  104. // SetMarginX sets the x scale margin
  105. func (ch *Chart) SetMarginX(bottom float32) {
  106. ch.bottom = bottom
  107. ch.recalc()
  108. }
  109. // SetFormatX sets the string format of the X scale labels
  110. func (ch *Chart) SetFormatX(format string) {
  111. ch.formatX = format
  112. ch.updateLabelsX()
  113. }
  114. // SetFormatY sets the string format of the Y scale labels
  115. func (ch *Chart) SetFormatY(format string) {
  116. ch.formatY = format
  117. ch.updateLabelsY()
  118. }
  119. // SetFontSizeX sets the font size for the x scale labels
  120. func (ch *Chart) SetFontSizeX(size float64) {
  121. ch.fontSizeX = size
  122. for i := 0; i < len(ch.labelsX); i++ {
  123. ch.labelsX[i].SetFontSize(ch.fontSizeX)
  124. }
  125. }
  126. // SetFontSizeY sets the font size for the y scale labels
  127. func (ch *Chart) SetFontSizeY(size float64) {
  128. ch.fontSizeY = size
  129. for i := 0; i < len(ch.labelsY); i++ {
  130. ch.labelsY[i].SetFontSize(ch.fontSizeY)
  131. }
  132. }
  133. // SetScaleX sets the X scale number of lines, lines color and label font size
  134. func (ch *Chart) SetScaleX(lines int, color *math32.Color) {
  135. if ch.scaleX != nil {
  136. ch.ClearScaleX()
  137. }
  138. // Add scale lines
  139. ch.scaleX = newChartScaleX(ch, lines, color)
  140. ch.Add(ch.scaleX)
  141. // Add scale labels
  142. // The positions of the labels will be set by 'recalc()'
  143. value := ch.firstX
  144. for i := 0; i < lines; i++ {
  145. l := NewLabel(fmt.Sprintf(ch.formatX, value))
  146. l.SetColor4(math32.NewColor4("black"))
  147. l.SetFontSize(ch.fontSizeX)
  148. ch.Add(l)
  149. ch.labelsX = append(ch.labelsX, l)
  150. value += ch.stepX
  151. }
  152. ch.recalc()
  153. }
  154. // ClearScaleX removes the X scale if it was previously set
  155. func (ch *Chart) ClearScaleX() {
  156. if ch.scaleX == nil {
  157. return
  158. }
  159. // Remove and dispose scale lines
  160. ch.Remove(ch.scaleX)
  161. ch.scaleX.Dispose()
  162. // Remove and dispose scale labels
  163. for i := 0; i < len(ch.labelsX); i++ {
  164. label := ch.labelsX[i]
  165. ch.Remove(label)
  166. label.Dispose()
  167. }
  168. ch.labelsX = ch.labelsX[0:0]
  169. ch.scaleX = nil
  170. }
  171. // SetScaleY sets the Y scale number of lines and color
  172. func (ch *Chart) SetScaleY(lines int, color *math32.Color) {
  173. if ch.scaleY != nil {
  174. ch.ClearScaleY()
  175. }
  176. if lines < 2 {
  177. lines = 2
  178. }
  179. // Add scale lines
  180. ch.scaleY = newChartScaleY(ch, lines, color)
  181. ch.Add(ch.scaleY)
  182. // Add scale labels
  183. // The position of the labels will be set by 'recalc()'
  184. value := ch.minY
  185. step := (ch.maxY - ch.minY) / float32(lines-1)
  186. for i := 0; i < lines; i++ {
  187. l := NewLabel(fmt.Sprintf(ch.formatY, value))
  188. l.SetColor4(math32.NewColor4("black"))
  189. l.SetFontSize(ch.fontSizeY)
  190. ch.Add(l)
  191. ch.labelsY = append(ch.labelsY, l)
  192. value += step
  193. }
  194. ch.recalc()
  195. }
  196. // ClearScaleY removes the Y scale if it was previously set
  197. func (ch *Chart) ClearScaleY() {
  198. if ch.scaleY == nil {
  199. return
  200. }
  201. // Remove and dispose scale lines
  202. ch.Remove(ch.scaleY)
  203. ch.scaleY.Dispose()
  204. // Remove and dispose scale labels
  205. for i := 0; i < len(ch.labelsY); i++ {
  206. label := ch.labelsY[i]
  207. ch.Remove(label)
  208. label.Dispose()
  209. }
  210. ch.labelsY = ch.labelsY[0:0]
  211. ch.scaleY = nil
  212. }
  213. // SetRangeX sets the X scale labels and range per step
  214. // firstX is the value of first label of the x scale
  215. // stepX is the step to be added to get the next x scale label
  216. // countStepX is the number of elements of the data buffer for each line step
  217. func (ch *Chart) SetRangeX(firstX float32, stepX float32, countStepX float32) {
  218. ch.firstX = firstX
  219. ch.stepX = stepX
  220. ch.countStepX = countStepX
  221. ch.updateGraphs()
  222. }
  223. // SetRangeY sets the minimum and maximum values of the y scale
  224. func (ch *Chart) SetRangeY(min float32, max float32) {
  225. if ch.autoY {
  226. return
  227. }
  228. ch.minY = min
  229. ch.maxY = max
  230. ch.updateGraphs()
  231. }
  232. // SetRangeYauto sets the state of the auto
  233. func (ch *Chart) SetRangeYauto(auto bool) {
  234. ch.autoY = auto
  235. if !auto {
  236. return
  237. }
  238. ch.updateGraphs()
  239. }
  240. // RangeY returns the current y range
  241. func (ch *Chart) RangeY() (minY, maxY float32) {
  242. return ch.minY, ch.maxY
  243. }
  244. // AddLineGraph adds a line graph to the chart
  245. func (ch *Chart) AddLineGraph(color *math32.Color, data []float32) *Graph {
  246. graph := newGraph(ch, color, data)
  247. ch.graphs = append(ch.graphs, graph)
  248. ch.Add(graph)
  249. ch.recalc()
  250. ch.updateGraphs()
  251. return graph
  252. }
  253. // RemoveGraph removes and disposes of the specified graph from the chart
  254. func (ch *Chart) RemoveGraph(g *Graph) {
  255. ch.Remove(g)
  256. g.Dispose()
  257. for pos, current := range ch.graphs {
  258. if current == g {
  259. copy(ch.graphs[pos:], ch.graphs[pos+1:])
  260. ch.graphs[len(ch.graphs)-1] = nil
  261. ch.graphs = ch.graphs[:len(ch.graphs)-1]
  262. break
  263. }
  264. }
  265. if !ch.autoY {
  266. return
  267. }
  268. ch.updateGraphs()
  269. }
  270. // updateLabelsX updates the X scale labels text
  271. func (ch *Chart) updateLabelsX() {
  272. if ch.scaleX == nil {
  273. return
  274. }
  275. pstep := (ch.ContentWidth() - ch.left) / float32(len(ch.labelsX))
  276. value := ch.firstX
  277. for i := 0; i < len(ch.labelsX); i++ {
  278. label := ch.labelsX[i]
  279. label.SetText(fmt.Sprintf(ch.formatX, value))
  280. px := ch.left + float32(i)*pstep
  281. label.SetPosition(px, ch.ContentHeight()-ch.bottom)
  282. value += ch.stepX
  283. }
  284. }
  285. // updateLabelsY updates the Y scale labels text and positions
  286. func (ch *Chart) updateLabelsY() {
  287. if ch.scaleY == nil {
  288. return
  289. }
  290. th := float32(0)
  291. if ch.title != nil {
  292. th = ch.title.height
  293. }
  294. nlines := ch.scaleY.lines
  295. vstep := (ch.maxY - ch.minY) / float32(nlines-1)
  296. pstep := (ch.ContentHeight() - th - ch.top - ch.bottom) / float32(nlines-1)
  297. value := ch.minY
  298. for i := 0; i < nlines; i++ {
  299. label := ch.labelsY[i]
  300. label.SetText(fmt.Sprintf(ch.formatY, value))
  301. px := ch.left - 4 - label.Width()
  302. if px < 0 {
  303. px = 0
  304. }
  305. py := ch.ContentHeight() - ch.bottom - float32(i)*pstep
  306. label.SetPosition(px, py-label.Height()/2)
  307. value += vstep
  308. }
  309. }
  310. // calcRangeY calculates the minimum and maximum y values for all graphs
  311. func (ch *Chart) calcRangeY() {
  312. if !ch.autoY || len(ch.graphs) == 0 {
  313. return
  314. }
  315. minY := float32(math.MaxFloat32)
  316. maxY := -float32(math.MaxFloat32)
  317. for g := 0; g < len(ch.graphs); g++ {
  318. graph := ch.graphs[g]
  319. for x := 0; x < len(graph.data); x++ {
  320. vy := graph.data[x]
  321. if vy < minY {
  322. minY = vy
  323. }
  324. if vy > maxY {
  325. maxY = vy
  326. }
  327. }
  328. }
  329. ch.minY = minY
  330. ch.maxY = maxY
  331. }
  332. // updateGraphs should be called when the range the scales change or
  333. // any graph data changes
  334. func (ch *Chart) updateGraphs() {
  335. ch.calcRangeY()
  336. ch.updateLabelsX()
  337. ch.updateLabelsY()
  338. for i := 0; i < len(ch.graphs); i++ {
  339. g := ch.graphs[i]
  340. g.updateData()
  341. }
  342. }
  343. // onResize process OnResize events for this chart
  344. func (ch *Chart) onResize(evname string, ev interface{}) {
  345. ch.recalc()
  346. }
  347. // recalc recalculates the positions of the inner panels
  348. func (ch *Chart) recalc() {
  349. // Center title position
  350. if ch.title != nil {
  351. xpos := (ch.ContentWidth() - ch.title.width) / 2
  352. ch.title.SetPositionX(xpos)
  353. }
  354. // Recalc scale X and its labels
  355. if ch.scaleX != nil {
  356. ch.scaleX.recalc()
  357. ch.updateLabelsX()
  358. }
  359. // Recalc scale Y and its labels
  360. if ch.scaleY != nil {
  361. ch.scaleY.recalc()
  362. ch.updateLabelsY()
  363. }
  364. // Recalc graphs
  365. for i := 0; i < len(ch.graphs); i++ {
  366. g := ch.graphs[i]
  367. g.recalc()
  368. ch.SetTopChild(g)
  369. }
  370. }
  371. //
  372. // chartScaleX is a panel with GL_LINES geometry which draws the chart X horizontal scale axis,
  373. // vertical lines and line labels.
  374. //
  375. type chartScaleX struct {
  376. Panel // Embedded panel
  377. chart *Chart // Container chart
  378. lines int // Number of vertical lines
  379. mat chartMaterial // Chart material
  380. uniBounds gls.Uniform // Bounds uniform location cache
  381. }
  382. // newChartScaleX creates and returns a pointer to a new chartScaleX for the specified
  383. // chart, number of lines and color
  384. func newChartScaleX(chart *Chart, lines int, color *math32.Color) *chartScaleX {
  385. sx := new(chartScaleX)
  386. sx.chart = chart
  387. sx.lines = lines
  388. sx.uniBounds.Init("Bounds")
  389. // Appends bottom horizontal line
  390. positions := math32.NewArrayF32(0, 0)
  391. positions.Append(0, -1+deltaLine, 0, 1, -1+deltaLine, 0)
  392. // Appends vertical lines
  393. step := 1 / float32(lines)
  394. for i := 0; i < lines; i++ {
  395. nx := float32(i) * step
  396. if i == 0 {
  397. nx += deltaLine
  398. }
  399. positions.Append(nx, 0, 0, nx, -1, 0)
  400. }
  401. // Creates geometry and adds VBO
  402. geom := geometry.NewGeometry()
  403. geom.AddVBO(gls.NewVBO(positions).AddAttrib(gls.VertexPosition))
  404. // Initializes the panel graphic
  405. gr := graphic.NewGraphic(geom, gls.LINES)
  406. sx.mat.Init(color)
  407. gr.AddMaterial(sx, &sx.mat, 0, 0)
  408. sx.Panel.InitializeGraphic(chart.ContentWidth(), chart.ContentHeight(), gr)
  409. sx.recalc()
  410. return sx
  411. }
  412. // recalc recalculates the position and size of this scale inside its parent
  413. func (sx *chartScaleX) recalc() {
  414. py := sx.chart.top
  415. if sx.chart.title != nil {
  416. py += sx.chart.title.Height()
  417. }
  418. sx.SetPosition(sx.chart.left, py)
  419. sx.SetSize(sx.chart.ContentWidth()-sx.chart.left, sx.chart.ContentHeight()-py-sx.chart.bottom)
  420. }
  421. // RenderSetup is called by the renderer before drawing this graphic
  422. // It overrides the original panel RenderSetup
  423. // Calculates the model matrix and transfer to OpenGL.
  424. func (sx *chartScaleX) RenderSetup(gs *gls.GLS, rinfo *core.RenderInfo) {
  425. // Sets model matrix
  426. var mm math32.Matrix4
  427. sx.SetModelMatrix(gs, &mm)
  428. // Transfer model matrix uniform
  429. location := sx.uniMatrix.Location(gs)
  430. gs.UniformMatrix4fv(location, 1, false, &mm[0])
  431. // Sets bounds in OpenGL window coordinates and transfer to shader
  432. _, _, _, height := gs.GetViewport()
  433. location = sx.uniBounds.Location(gs)
  434. gs.Uniform4f(location, sx.pospix.X, float32(height)-sx.pospix.Y, sx.width, sx.height)
  435. }
  436. //
  437. // ChartScaleY is a panel with LINE geometry which draws the chart Y vertical scale axis,
  438. // horizontal and labels.
  439. //
  440. type chartScaleY struct {
  441. Panel // Embedded panel
  442. chart *Chart // Container chart
  443. lines int // Number of horizontal lines
  444. mat chartMaterial // Chart material
  445. uniBounds gls.Uniform // Bounds uniform location cache
  446. }
  447. // newChartScaleY creates and returns a pointer to a new chartScaleY for the specified
  448. // chart, number of lines and color
  449. func newChartScaleY(chart *Chart, lines int, color *math32.Color) *chartScaleY {
  450. if lines < 2 {
  451. lines = 2
  452. }
  453. sy := new(chartScaleY)
  454. sy.chart = chart
  455. sy.lines = lines
  456. sy.uniBounds.Init("Bounds")
  457. // Appends left vertical line
  458. positions := math32.NewArrayF32(0, 0)
  459. positions.Append(0+deltaLine, 0, 0, 0+deltaLine, -1, 0)
  460. // Appends horizontal lines
  461. step := 1 / float32(lines-1)
  462. for i := 0; i < lines; i++ {
  463. ny := -1 + float32(i)*step
  464. if i == 0 {
  465. ny += deltaLine
  466. }
  467. if i == lines-1 {
  468. ny -= deltaLine
  469. }
  470. positions.Append(0, ny, 0, 1, ny, 0)
  471. }
  472. // Creates geometry and adds VBO
  473. geom := geometry.NewGeometry()
  474. geom.AddVBO(gls.NewVBO(positions).AddAttrib(gls.VertexPosition))
  475. // Initializes the panel with this graphic
  476. gr := graphic.NewGraphic(geom, gls.LINES)
  477. sy.mat.Init(color)
  478. gr.AddMaterial(sy, &sy.mat, 0, 0)
  479. sy.Panel.InitializeGraphic(chart.ContentWidth(), chart.ContentHeight(), gr)
  480. sy.recalc()
  481. return sy
  482. }
  483. // recalc recalculates the position and size of this scale inside its parent
  484. func (sy *chartScaleY) recalc() {
  485. py := sy.chart.top
  486. if sy.chart.title != nil {
  487. py += sy.chart.title.Height()
  488. }
  489. sy.SetPosition(sy.chart.left, py)
  490. sy.SetSize(sy.chart.ContentWidth()-sy.chart.left, sy.chart.ContentHeight()-py-sy.chart.bottom)
  491. }
  492. // RenderSetup is called by the renderer before drawing this graphic
  493. // It overrides the original panel RenderSetup
  494. // Calculates the model matrix and transfer to OpenGL.
  495. func (sy *chartScaleY) RenderSetup(gs *gls.GLS, rinfo *core.RenderInfo) {
  496. // Sets model matrix
  497. var mm math32.Matrix4
  498. sy.SetModelMatrix(gs, &mm)
  499. // Transfer model matrix uniform
  500. location := sy.uniMatrix.Location(gs)
  501. gs.UniformMatrix4fv(location, 1, false, &mm[0])
  502. // Sets bounds in OpenGL window coordinates and transfer to shader
  503. _, _, _, height := gs.GetViewport()
  504. location = sy.uniBounds.Location(gs)
  505. gs.Uniform4f(location, sy.pospix.X, float32(height)-sy.pospix.Y, sy.width, sy.height)
  506. }
  507. //
  508. // Graph is the GUI element that represents a single plotted function.
  509. // A Chart has an array of Graph objects.
  510. //
  511. type Graph struct {
  512. Panel // Embedded panel
  513. chart *Chart // Container chart
  514. color math32.Color // Line color
  515. data []float32 // Data y
  516. mat chartMaterial // Chart material
  517. vbo *gls.VBO
  518. positions math32.ArrayF32
  519. uniBounds gls.Uniform // Bounds uniform location cache
  520. }
  521. // newGraph creates and returns a pointer to a new graph for the specified chart
  522. func newGraph(chart *Chart, color *math32.Color, data []float32) *Graph {
  523. lg := new(Graph)
  524. lg.uniBounds.Init("Bounds")
  525. lg.chart = chart
  526. lg.color = *color
  527. lg.data = data
  528. // Creates geometry and adds VBO with positions
  529. geom := geometry.NewGeometry()
  530. lg.positions = math32.NewArrayF32(0, 0)
  531. lg.vbo = gls.NewVBO(lg.positions).AddAttrib(gls.VertexPosition)
  532. geom.AddVBO(lg.vbo)
  533. // Initializes the panel with this graphic
  534. gr := graphic.NewGraphic(geom, gls.LINE_STRIP)
  535. lg.mat.Init(&lg.color)
  536. gr.AddMaterial(lg, &lg.mat, 0, 0)
  537. lg.Panel.InitializeGraphic(lg.chart.ContentWidth(), lg.chart.ContentHeight(), gr)
  538. lg.SetData(data)
  539. return lg
  540. }
  541. // SetColor sets the color of the graph
  542. func (lg *Graph) SetColor(color *math32.Color) {
  543. lg.color = *color
  544. }
  545. // SetData sets the graph data
  546. func (lg *Graph) SetData(data []float32) {
  547. lg.data = data
  548. lg.updateData()
  549. }
  550. // SetLineWidth sets the graph line width
  551. func (lg *Graph) SetLineWidth(width float32) {
  552. lg.mat.SetLineWidth(width)
  553. }
  554. // updateData regenerates the lines for the current data
  555. func (lg *Graph) updateData() {
  556. lines := 1
  557. if lg.chart.scaleX != nil {
  558. lines = lg.chart.scaleX.lines
  559. }
  560. step := 1.0 / (float32(lines) * lg.chart.countStepX)
  561. positions := math32.NewArrayF32(0, 0)
  562. rangeY := lg.chart.maxY - lg.chart.minY
  563. for i := 0; i < len(lg.data); i++ {
  564. px := float32(i) * step
  565. vy := lg.data[i]
  566. py := -1 + ((vy - lg.chart.minY) / rangeY)
  567. positions.Append(px, py, 0)
  568. }
  569. lg.vbo.SetBuffer(positions)
  570. lg.SetChanged(true)
  571. }
  572. // recalc recalculates the position and width of the this panel
  573. func (lg *Graph) recalc() {
  574. py := lg.chart.top
  575. if lg.chart.title != nil {
  576. py += lg.chart.title.Height()
  577. }
  578. px := lg.chart.left
  579. w := lg.chart.ContentWidth() - lg.chart.left
  580. h := lg.chart.ContentHeight() - py - lg.chart.bottom
  581. lg.SetPosition(px, py)
  582. lg.SetSize(w, h)
  583. }
  584. // RenderSetup is called by the renderer before drawing this graphic
  585. // It overrides the original panel RenderSetup
  586. // Calculates the model matrix and transfer to OpenGL.
  587. func (lg *Graph) RenderSetup(gs *gls.GLS, rinfo *core.RenderInfo) {
  588. // Sets model matrix
  589. var mm math32.Matrix4
  590. lg.SetModelMatrix(gs, &mm)
  591. // Transfer model matrix uniform
  592. location := lg.uniMatrix.Location(gs)
  593. gs.UniformMatrix4fv(location, 1, false, &mm[0])
  594. // Sets bounds in OpenGL window coordinates and transfer to shader
  595. _, _, _, height := gs.GetViewport()
  596. location = lg.uniBounds.Location(gs)
  597. gs.Uniform4f(location, lg.pospix.X, float32(height)-lg.pospix.Y, lg.width, lg.height)
  598. }
  599. //
  600. // Chart material
  601. //
  602. type chartMaterial struct {
  603. material.Material // Embedded material
  604. color math32.Color // emissive color
  605. uniColor gls.Uniform // color uniform location cache
  606. }
  607. func (cm *chartMaterial) Init(color *math32.Color) {
  608. cm.Material.Init()
  609. cm.SetShader("shaderChart")
  610. cm.SetShaderUnique(true)
  611. cm.uniColor.Init("MatColor")
  612. cm.color = *color
  613. }
  614. func (cm *chartMaterial) RenderSetup(gs *gls.GLS) {
  615. cm.Material.RenderSetup(gs)
  616. gs.Uniform3f(cm.uniColor.Location(gs), cm.color.R, cm.color.G, cm.color.B)
  617. }
  618. //
  619. // Vertex Shader template
  620. //
  621. const shaderChartVertex = `
  622. // Vertex attributes
  623. #include <attributes>
  624. // Input uniforms
  625. uniform mat4 ModelMatrix;
  626. uniform vec3 MatColor;
  627. // Outputs for fragment shader
  628. out vec3 Color;
  629. void main() {
  630. Color = MatColor;
  631. // Set position
  632. vec4 pos = vec4(VertexPosition.xyz, 1);
  633. vec4 posclip = ModelMatrix * pos;
  634. gl_Position = posclip;
  635. }
  636. `
  637. //
  638. // Fragment Shader template
  639. //
  640. const shaderChartFrag = `
  641. // Input uniforms from vertex shader
  642. in vec3 Color;
  643. // Input uniforms
  644. uniform vec4 Bounds;
  645. // Output
  646. out vec4 FragColor;
  647. void main() {
  648. // Discard fragment outside of the received bounds in OpenGL window pixel coordinates
  649. // Bounds[0] - x
  650. // Bounds[1] - y
  651. // Bounds[2] - width
  652. // Bounds[3] - height
  653. if (gl_FragCoord.x < Bounds[0] || gl_FragCoord.x > Bounds[0] + Bounds[2]) {
  654. discard;
  655. }
  656. if (gl_FragCoord.y > Bounds[1] || gl_FragCoord.y < Bounds[1] - Bounds[3]) {
  657. discard;
  658. }
  659. FragColor = vec4(Color, 1.0);
  660. }
  661. `