chart.go 19 KB

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