chart.go 13 KB

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