chart.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. package gui
  2. import (
  3. "fmt"
  4. "github.com/g3n/engine/core"
  5. "github.com/g3n/engine/geometry"
  6. "github.com/g3n/engine/gls"
  7. "github.com/g3n/engine/graphic"
  8. "github.com/g3n/engine/material"
  9. "github.com/g3n/engine/math32"
  10. "github.com/g3n/engine/renderer/shader"
  11. "math"
  12. )
  13. func init() {
  14. shader.AddShader("shaderChartVertex", shaderChartVertex)
  15. shader.AddShader("shaderChartFrag", shaderChartFrag)
  16. shader.AddProgram("shaderChart", "shaderChartVertex", "shaderChartFrag")
  17. }
  18. //
  19. //
  20. // ChartLine implements a panel which can contain several line charts
  21. //
  22. //
  23. type ChartLine struct {
  24. Panel // Embedded panel
  25. title *Label // Optional title label
  26. left float32 // Left margin in pixels
  27. bottom float32 // Bottom margin in pixels
  28. scaleX *ChartScaleX // X scale panel
  29. scaleY *ChartScaleY // Y scale panel
  30. offsetX int // Initial offset in data buffers
  31. countX int // Count of data buffer points starting from offsetX
  32. firstX float32 // Value of first data point to show
  33. stepX float32 // Step to add to firstX for next data point
  34. minY float32 // Minimum Y value
  35. maxY float32 //
  36. autoY bool // Auto range flag for Y values
  37. formatX string // String format for scale X labels
  38. formatY string // String format for scale Y labels
  39. labelsX []*Label // Array of scale X labels
  40. labelsY []*Label // Array of scale Y labels
  41. graphs []*LineGraph // Array of line graphs
  42. }
  43. // NewChartLine creates and returns a new line chart panel with
  44. // the specified dimensions in pixels.
  45. func NewChartLine(width, height float32) *ChartLine {
  46. cl := new(ChartLine)
  47. cl.Panel.Initialize(width, height)
  48. cl.left = 30
  49. cl.bottom = 20
  50. cl.offsetX = 0
  51. cl.countX = 10
  52. cl.firstX = 0.0
  53. cl.stepX = 1.0
  54. cl.autoY = false
  55. cl.formatX = "%v"
  56. cl.formatY = "%v"
  57. return cl
  58. }
  59. //func (cl *ChartLine) SetMargins(left, bottom float32) {
  60. //
  61. // cl.baseX, cl.baseY = cl.Pix2NDC(left, bottom)
  62. // cl.recalc()
  63. //}
  64. // SetTitle sets the chart title
  65. func (cl *ChartLine) SetTitle(title *Label) {
  66. if cl.title != nil {
  67. cl.Remove(cl.title)
  68. cl.title = nil
  69. }
  70. if title != nil {
  71. cl.Add(title)
  72. cl.title = title
  73. }
  74. cl.recalc()
  75. }
  76. // SetScaleX sets the line chart x scale number of lines and color
  77. func (cl *ChartLine) SetScaleX(lines int, color *math32.Color) {
  78. if cl.scaleX != nil {
  79. cl.ClearScaleX()
  80. }
  81. // Add scale lines
  82. cl.scaleX = newChartScaleX(cl, lines, color)
  83. cl.Add(cl.scaleX)
  84. // Add scale labels
  85. // The positions of the labels will be set by 'recalc()'
  86. for i := 0; i < lines; i++ {
  87. l := NewLabel(fmt.Sprintf(cl.formatX, float32(i)))
  88. cl.Add(l)
  89. cl.labelsX = append(cl.labelsX, l)
  90. }
  91. cl.recalc()
  92. }
  93. // ClearScaleX removes the chart x scale if it was previously set
  94. func (cl *ChartLine) ClearScaleX() {
  95. if cl.scaleX == nil {
  96. return
  97. }
  98. // Remove and dispose scale lines
  99. cl.Remove(cl.scaleX)
  100. cl.scaleX.Dispose()
  101. // Remove and dispose scale labels
  102. for i := 0; i < len(cl.labelsX); i++ {
  103. label := cl.labelsX[i]
  104. cl.Remove(label)
  105. label.Dispose()
  106. }
  107. cl.labelsX = cl.labelsX[0:0]
  108. cl.scaleX = nil
  109. }
  110. // SetScaleY sets the line chart y scale number of lines and color
  111. func (cl *ChartLine) SetScaleY(lines int, color *math32.Color) {
  112. if cl.scaleY != nil {
  113. cl.ClearScaleY()
  114. }
  115. // Add scale lines
  116. cl.scaleY = newChartScaleY(cl, lines, color)
  117. cl.Add(cl.scaleY)
  118. // Add scale labels
  119. // The position of the labels will be set by 'recalc()'
  120. for i := 0; i < lines; i++ {
  121. l := NewLabel(fmt.Sprintf(cl.formatY, float32(i)))
  122. cl.Add(l)
  123. cl.labelsY = append(cl.labelsY, l)
  124. }
  125. cl.recalc()
  126. }
  127. // ClearScaleY removes the chart x scale if it was previously set
  128. func (cl *ChartLine) ClearScaleY() {
  129. if cl.scaleY == nil {
  130. return
  131. }
  132. // Remove and dispose scale lines
  133. cl.Remove(cl.scaleY)
  134. cl.scaleY.Dispose()
  135. // Remove and dispose scale labels
  136. for i := 0; i < len(cl.labelsY); i++ {
  137. label := cl.labelsY[i]
  138. cl.Remove(label)
  139. label.Dispose()
  140. }
  141. cl.labelsY = cl.labelsY[0:0]
  142. cl.scaleY = nil
  143. }
  144. // SetRangeX sets the interval of the data to be shown:
  145. // offset is the start position in the Y data array.
  146. // count is the number of data points to show, starting from the specified offset.
  147. // first is the label for the first X point
  148. // step is the value added to first for the next data point
  149. func (cl *ChartLine) SetRangeX(offset int, count int, first float32, step float32) {
  150. cl.offsetX = offset
  151. cl.countX = count
  152. cl.firstX = first
  153. cl.stepX = step
  154. }
  155. func (cl *ChartLine) SetRangeY(min float32, max float32) {
  156. cl.minY = min
  157. cl.maxY = max
  158. }
  159. // AddLine adds a line graph to the chart
  160. func (cl *ChartLine) AddGraph(color *math32.Color, data []float32) *LineGraph {
  161. graph := newLineGraph(cl, color, data)
  162. cl.graphs = append(cl.graphs, graph)
  163. cl.Add(graph)
  164. cl.recalc()
  165. return graph
  166. }
  167. func (cl *ChartLine) RemoveGraph(g *LineGraph) {
  168. cl.Remove(g)
  169. g.Dispose()
  170. for pos, current := range cl.graphs {
  171. if current == g {
  172. copy(cl.graphs[pos:], cl.graphs[pos+1:])
  173. cl.graphs[len(cl.graphs)-1] = nil
  174. cl.graphs = cl.graphs[:len(cl.graphs)-1]
  175. break
  176. }
  177. }
  178. }
  179. func (cl *ChartLine) calcRangeY() {
  180. if !cl.autoY {
  181. return
  182. }
  183. minY := float32(math.MaxFloat32)
  184. maxY := -float32(math.MaxFloat32)
  185. for g := 0; g < len(cl.graphs); g++ {
  186. graph := cl.graphs[g]
  187. for x := 0; x < cl.countX; x++ {
  188. if x+cl.offsetX >= len(graph.y) {
  189. break
  190. }
  191. vy := graph.y[x+cl.offsetX]
  192. if vy < minY {
  193. minY = vy
  194. }
  195. if vy > maxY {
  196. maxY = vy
  197. }
  198. }
  199. }
  200. cl.minY = minY
  201. cl.maxY = maxY
  202. }
  203. // recalc recalculates the positions of the inner panels
  204. func (cl *ChartLine) recalc() {
  205. // Center title position
  206. th := float32(0)
  207. if cl.title != nil {
  208. xpos := (cl.ContentWidth() - cl.title.width) / 2
  209. cl.title.SetPositionX(xpos)
  210. th = cl.title.height
  211. }
  212. // Recalc scale X and its labels
  213. if cl.scaleX != nil {
  214. cl.scaleX.recalc()
  215. step := (cl.ContentWidth() - cl.left) / float32(len(cl.labelsX))
  216. for i := 0; i < len(cl.labelsX); i++ {
  217. label := cl.labelsX[i]
  218. px := cl.left + float32(i)*step
  219. label.SetPosition(px, cl.ContentHeight()-cl.bottom)
  220. }
  221. }
  222. // Recalc scale Y and its labels
  223. if cl.scaleY != nil {
  224. cl.scaleY.recalc()
  225. step := (cl.ContentHeight() - cl.bottom - th) / float32(len(cl.labelsY))
  226. for i := 0; i < len(cl.labelsY); i++ {
  227. label := cl.labelsY[i]
  228. py := cl.ContentHeight() - cl.bottom - float32(i)*step
  229. label.SetPosition(0, py-label.Height()/2)
  230. }
  231. }
  232. // Recalc graphs
  233. for i := 0; i < len(cl.graphs); i++ {
  234. g := cl.graphs[i]
  235. g.recalc()
  236. cl.SetTopChild(g)
  237. }
  238. // Sets the title at the top
  239. if cl.title != nil {
  240. log.Error("TITLE TOP")
  241. cl.SetTopChild(cl.title)
  242. }
  243. }
  244. //
  245. //
  246. // ChartScaleX is a panel with GL_LINES geometry which draws the chart X horizontal scale axis,
  247. // vertical lines and line labels.
  248. //
  249. //
  250. type ChartScaleX struct {
  251. Panel // Embedded panel
  252. chart *ChartLine // Container chart
  253. lines int // Number of vertical lines
  254. bounds gls.Uniform4f // Bound uniform in OpenGL window coordinates
  255. mat chartMaterial // Chart material
  256. }
  257. // newChartScaleX creates and returns a pointer to a new ChartScaleX for the specified
  258. // chart, number of lines and color
  259. func newChartScaleX(chart *ChartLine, lines int, color *math32.Color) *ChartScaleX {
  260. sx := new(ChartScaleX)
  261. sx.chart = chart
  262. sx.lines = lines
  263. sx.bounds.Init("Bounds")
  264. // Appends bottom horizontal line
  265. positions := math32.NewArrayF32(0, 0)
  266. positions.Append(0, -1, 0, 1, -1, 0)
  267. // Appends vertical lines
  268. step := 1 / float32(lines)
  269. for i := 0; i < lines; i++ {
  270. nx := float32(i) * step
  271. positions.Append(nx, 0, 0, nx, -1, 0)
  272. }
  273. // Creates geometry and adds VBO
  274. geom := geometry.NewGeometry()
  275. geom.AddVBO(gls.NewVBO().AddAttrib("VertexPosition", 3).SetBuffer(positions))
  276. // Initializes the panel graphic
  277. gr := graphic.NewGraphic(geom, gls.LINES)
  278. sx.mat.Init(color)
  279. gr.AddMaterial(sx, &sx.mat, 0, 0)
  280. sx.Panel.InitializeGraphic(chart.ContentWidth(), chart.ContentHeight(), gr)
  281. sx.recalc()
  282. return sx
  283. }
  284. func (sx *ChartScaleX) setLabelsText(x []float32) {
  285. }
  286. // recalc recalculates the position and size of this scale inside its parent
  287. func (sx *ChartScaleX) recalc() {
  288. py := float32(0)
  289. if sx.chart.title != nil {
  290. py = sx.chart.title.Height()
  291. }
  292. sx.SetPosition(sx.chart.left, py)
  293. sx.SetSize(sx.chart.ContentWidth()-sx.chart.left, sx.chart.ContentHeight()-py-sx.chart.bottom)
  294. }
  295. // RenderSetup is called by the renderer before drawing this graphic
  296. // It overrides the original panel RenderSetup
  297. // Calculates the model matrix and transfer to OpenGL.
  298. func (sx *ChartScaleX) RenderSetup(gs *gls.GLS, rinfo *core.RenderInfo) {
  299. //log.Error("ChartScaleX RenderSetup:%v", sx.pospix)
  300. // Sets model matrix and transfer to shader
  301. var mm math32.Matrix4
  302. sx.SetModelMatrix(gs, &mm)
  303. sx.modelMatrixUni.SetMatrix4(&mm)
  304. sx.modelMatrixUni.Transfer(gs)
  305. // Sets bounds in OpenGL window coordinates and transfer to shader
  306. _, _, _, height := gs.GetViewport()
  307. sx.bounds.Set(sx.pospix.X, float32(height)-sx.pospix.Y, sx.width, sx.height)
  308. sx.bounds.Transfer(gs)
  309. }
  310. //
  311. // ChartScaleY is a panel with LINE geometry which draws the chart Y vertical scale axis,
  312. // horizontal and labels.
  313. //
  314. type ChartScaleY struct {
  315. Panel // Embedded panel
  316. chart *ChartLine // Container chart
  317. lines int // Number of horizontal lines
  318. bounds gls.Uniform4f // Bound uniform in OpenGL window coordinates
  319. mat chartMaterial // Chart material
  320. }
  321. // newChartScaleY creates and returns a pointer to a new ChartScaleY for the specified
  322. // chart, number of lines and color
  323. func newChartScaleY(chart *ChartLine, lines int, color *math32.Color) *ChartScaleY {
  324. sy := new(ChartScaleY)
  325. sy.chart = chart
  326. sy.lines = lines
  327. sy.bounds.Init("Bounds")
  328. // Appends left vertical line
  329. positions := math32.NewArrayF32(0, 0)
  330. positions.Append(0, 0, 0, 0, -1, 0)
  331. // Appends horizontal lines
  332. step := 1 / float32(lines)
  333. for i := 0; i < lines; i++ {
  334. ny := -1 + float32(i)*step
  335. positions.Append(0, ny, 0, 1, ny, 0)
  336. }
  337. // Creates geometry and adds VBO
  338. geom := geometry.NewGeometry()
  339. geom.AddVBO(gls.NewVBO().AddAttrib("VertexPosition", 3).SetBuffer(positions))
  340. // Initializes the panel with this graphic
  341. gr := graphic.NewGraphic(geom, gls.LINES)
  342. sy.mat.Init(color)
  343. gr.AddMaterial(sy, &sy.mat, 0, 0)
  344. sy.Panel.InitializeGraphic(chart.ContentWidth(), chart.ContentHeight(), gr)
  345. sy.recalc()
  346. return sy
  347. }
  348. // recalc recalculates the position and size of this scale inside its parent
  349. func (sy *ChartScaleY) recalc() {
  350. py := float32(0)
  351. if sy.chart.title != nil {
  352. py = sy.chart.title.Height()
  353. }
  354. sy.SetPosition(sy.chart.left, py)
  355. sy.SetSize(sy.chart.ContentWidth()-sy.chart.left, sy.chart.ContentHeight()-py-sy.chart.bottom)
  356. }
  357. // RenderSetup is called by the renderer before drawing this graphic
  358. // It overrides the original panel RenderSetup
  359. // Calculates the model matrix and transfer to OpenGL.
  360. func (sy *ChartScaleY) RenderSetup(gs *gls.GLS, rinfo *core.RenderInfo) {
  361. //log.Error("ChartScaleY RenderSetup:%v", sy.pospix)
  362. // Sets model matrix and transfer to shader
  363. var mm math32.Matrix4
  364. sy.SetModelMatrix(gs, &mm)
  365. sy.modelMatrixUni.SetMatrix4(&mm)
  366. sy.modelMatrixUni.Transfer(gs)
  367. // Sets bounds in OpenGL window coordinates and transfer to shader
  368. _, _, _, height := gs.GetViewport()
  369. sy.bounds.Set(sy.pospix.X, float32(height)-sy.pospix.Y, sy.width, sy.height)
  370. sy.bounds.Transfer(gs)
  371. }
  372. //
  373. //
  374. // LineGraph
  375. //
  376. //
  377. type LineGraph struct {
  378. Panel // Embedded panel
  379. chart *ChartLine // Container chart
  380. color math32.Color // Line color
  381. y []float32 // Data y
  382. bounds gls.Uniform4f // Bound uniform in OpenGL window coordinates
  383. mat chartMaterial // Chart material
  384. }
  385. func newLineGraph(chart *ChartLine, color *math32.Color, y []float32) *LineGraph {
  386. log.Error("newLineGraph")
  387. lg := new(LineGraph)
  388. lg.bounds.Init("Bounds")
  389. lg.chart = chart
  390. lg.color = *color
  391. lg.y = y
  392. lg.setGeometry()
  393. return lg
  394. }
  395. func (lg *LineGraph) SetColor(color *math32.Color) {
  396. }
  397. func (lg *LineGraph) SetData(x, y []float32) {
  398. lg.y = y
  399. }
  400. func (lg *LineGraph) SetLineWidth(width float32) {
  401. lg.mat.SetLineWidth(width)
  402. }
  403. func (lg *LineGraph) setGeometry() {
  404. lg.chart.calcRangeY()
  405. log.Error("minY:%v maxY:%v", lg.chart.minY, lg.chart.maxY)
  406. // Creates array for vertices and colors
  407. positions := math32.NewArrayF32(0, 0)
  408. origin := false
  409. step := 1.0 / float32(lg.chart.countX-1)
  410. rangeY := lg.chart.maxY - lg.chart.minY
  411. for i := 0; i < lg.chart.countX; i++ {
  412. x := i + lg.chart.offsetX
  413. if x >= len(lg.y) {
  414. break
  415. }
  416. px := float32(i) * step
  417. if !origin {
  418. positions.Append(px, -1, 0)
  419. origin = true
  420. }
  421. vy := lg.y[x]
  422. py := -1 + (vy / rangeY)
  423. if py > 0 {
  424. log.Error("PY:%v", py)
  425. }
  426. positions.Append(px, py, 0)
  427. }
  428. // Creates geometry using one interlaced VBO
  429. geom := geometry.NewGeometry()
  430. geom.AddVBO(gls.NewVBO().AddAttrib("VertexPosition", 3).SetBuffer(positions))
  431. // Initializes the panel with this graphic
  432. gr := graphic.NewGraphic(geom, gls.LINE_STRIP)
  433. lg.mat.Init(&lg.color)
  434. gr.AddMaterial(lg, &lg.mat, 0, 0)
  435. lg.Panel.InitializeGraphic(lg.chart.ContentWidth(), lg.chart.ContentHeight(), gr)
  436. }
  437. func (lg *LineGraph) recalc() {
  438. px := lg.chart.left
  439. py := float32(0)
  440. w := lg.chart.ContentWidth() - lg.chart.left
  441. h := lg.chart.ContentHeight() - lg.chart.bottom
  442. if lg.chart.title != nil {
  443. py += lg.chart.title.Height()
  444. h -= lg.chart.title.Height()
  445. }
  446. lg.SetPosition(px, py)
  447. lg.SetSize(w, h)
  448. }
  449. // RenderSetup is called by the renderer before drawing this graphic
  450. // It overrides the original panel RenderSetup
  451. // Calculates the model matrix and transfer to OpenGL.
  452. func (lg *LineGraph) RenderSetup(gs *gls.GLS, rinfo *core.RenderInfo) {
  453. //log.Error("LineGraph RenderSetup:%v with/height: %v/%v", lg.posclip, lg.wclip, lg.hclip)
  454. // Sets model matrix and transfer to shader
  455. var mm math32.Matrix4
  456. lg.SetModelMatrix(gs, &mm)
  457. lg.modelMatrixUni.SetMatrix4(&mm)
  458. lg.modelMatrixUni.Transfer(gs)
  459. // Sets bounds in OpenGL window coordinates and transfer to shader
  460. _, _, _, height := gs.GetViewport()
  461. lg.bounds.Set(lg.pospix.X, float32(height)-lg.pospix.Y, lg.width, lg.height)
  462. lg.bounds.Transfer(gs)
  463. }
  464. //
  465. //
  466. // Chart material (for lines)
  467. //
  468. //
  469. type chartMaterial struct {
  470. material.Material // Embedded material
  471. color *gls.Uniform3f // Emissive color uniform
  472. }
  473. func (cm *chartMaterial) Init(color *math32.Color) {
  474. cm.Material.Init()
  475. cm.SetShader("shaderChart")
  476. // Creates uniforms and adds to material
  477. cm.color = gls.NewUniform3f("MatColor")
  478. // Set initial values
  479. cm.color.SetColor(color)
  480. }
  481. func (cm *chartMaterial) RenderSetup(gs *gls.GLS) {
  482. cm.Material.RenderSetup(gs)
  483. cm.color.Transfer(gs)
  484. }
  485. //
  486. // Vertex Shader template
  487. //
  488. const shaderChartVertex = `
  489. #version {{.Version}}
  490. // Vertex attributes
  491. {{template "attributes" .}}
  492. // Input uniforms
  493. uniform mat4 ModelMatrix;
  494. uniform vec3 MatColor;
  495. // Outputs for fragment shader
  496. out vec3 Color;
  497. void main() {
  498. Color = MatColor;
  499. // Set position
  500. vec4 pos = vec4(VertexPosition.xyz, 1);
  501. vec4 posclip = ModelMatrix * pos;
  502. gl_Position = posclip;
  503. }
  504. `
  505. //
  506. // Fragment Shader template
  507. //
  508. const shaderChartFrag = `
  509. #version {{.Version}}
  510. // Input uniforms from vertex shader
  511. in vec3 Color;
  512. // Input uniforms
  513. uniform vec4 Bounds;
  514. // Output
  515. out vec4 FragColor;
  516. void main() {
  517. // Discard fragment outside of the received bounds in OpenGL window pixel coordinates
  518. // Bounds[0] - x
  519. // Bounds[1] - y
  520. // Bounds[2] - width
  521. // Bounds[3] - height
  522. if (gl_FragCoord.x < Bounds[0] || gl_FragCoord.x > Bounds[0] + Bounds[2]) {
  523. discard;
  524. }
  525. if (gl_FragCoord.y > Bounds[1] || gl_FragCoord.y < Bounds[1] - Bounds[3]) {
  526. discard;
  527. }
  528. FragColor = vec4(Color, 1.0);
  529. }
  530. `