chart.go 13 KB

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