chart.go 19 KB

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