chart.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  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. left float32 // Left margin in pixels
  23. bottom float32 // Bottom margin in pixels
  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.left = 30
  41. cl.bottom = 20
  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. // Appends bottom horizontal line
  181. bx, by := chart.Pix2NDC(chart.left, chart.ContentHeight()-chart.bottom)
  182. positions := math32.NewArrayF32(0, 0)
  183. positions.Append(
  184. bx, by, 0, color.R, color.G, color.B,
  185. 1, by, 0, color.R, color.G, color.B,
  186. )
  187. // Appends vertical lines
  188. step := (1 - bx) / float32(lines)
  189. for i := 0; i < lines; i++ {
  190. nx := bx + float32(i)*step
  191. positions.Append(
  192. nx, 0, 0, color.R, color.G, color.B,
  193. nx, by, 0, color.R, color.G, color.B,
  194. )
  195. }
  196. // Creates geometry using one interlaced VBO
  197. geom := geometry.NewGeometry()
  198. geom.AddVBO(gls.NewVBO().
  199. AddAttrib("VertexPosition", 3).
  200. AddAttrib("VertexColor", 3).
  201. SetBuffer(positions),
  202. )
  203. // Creates material
  204. mat := material.NewMaterial()
  205. mat.SetLineWidth(1.0)
  206. mat.SetShader("shaderChart")
  207. // Initializes the panel with this graphic
  208. gr := graphic.NewGraphic(geom, gls.LINES)
  209. gr.AddMaterial(sx, mat, 0, 0)
  210. sx.Panel.InitializeGraphic(chart.ContentWidth(), chart.ContentHeight(), gr)
  211. // Add labels after the panel is initialized
  212. for i := 0; i < lines; i++ {
  213. nx := bx + float32(i)*step
  214. l := NewLabel(fmt.Sprintf(sx.format, float32(i)))
  215. px, py := sx.NDC2Pix(nx, by)
  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 := (sx.chart.ContentWidth() - sx.chart.left) / float32(sx.lines)
  225. for i := 0; i < len(sx.labels); i++ {
  226. px := sx.chart.left + float32(i)*step
  227. py := sx.Height() - sx.chart.bottom
  228. //log.Error("label x:%v y:%v", px, py)
  229. l := sx.labels[i]
  230. l.SetPosition(px, py)
  231. }
  232. // bx, by := sx.chart.Pix2NDC(sx.chart.left, sx.chart.ContentHeight()-sx.chart.bottom)
  233. // step := 1 / (float32(sx.lines) + 1)
  234. // for i := 0; i < len(sx.labels); i++ {
  235. // nx := bx + float32(i)*step
  236. // px, py := sx.NDC2Pix(nx, by)
  237. // log.Error("label x:%v y:%v", px, py)
  238. // l := sx.labels[i]
  239. // l.SetPosition(px, py)
  240. // }
  241. }
  242. func (sx *ChartScaleX) setLabelsText(x []float32) {
  243. }
  244. func (sx *ChartScaleX) recalc() {
  245. if sx.chart.title != nil {
  246. th := sx.chart.title.Height()
  247. sx.SetPosition(0, th)
  248. sx.SetHeight(sx.chart.ContentHeight() - th)
  249. sx.updateLabels()
  250. } else {
  251. sx.SetPosition(0, 0)
  252. sx.SetHeight(sx.chart.ContentHeight())
  253. sx.updateLabels()
  254. }
  255. }
  256. // RenderSetup is called by the renderer before drawing this graphic
  257. // Calculates the model matrix and transfer to OpenGL.
  258. func (sx *ChartScaleX) RenderSetup(gs *gls.GLS, rinfo *core.RenderInfo) {
  259. //log.Error("ChartScaleX RenderSetup:%p", sx)
  260. // Sets model matrix and transfer to shader
  261. var mm math32.Matrix4
  262. sx.SetModelMatrix(gs, &mm)
  263. sx.modelMatrixUni.SetMatrix4(&mm)
  264. sx.modelMatrixUni.Transfer(gs)
  265. }
  266. // ChartScaleY is a panel with LINE geometry which draws the chart Y vertical scale axis,
  267. // horizontal and labels.
  268. type ChartScaleY struct {
  269. Panel // Embedded panel
  270. chart *ChartLine // Container chart
  271. lines int // Number of horizontal lines
  272. format string // Labels format string
  273. model *Label // Model label to calculate height/width
  274. labels []*Label // Array of scale labels
  275. }
  276. // newChartScaleY creates and returns a pointer to a new ChartScaleY for the specified
  277. // chart, number of lines and color
  278. func newChartScaleY(chart *ChartLine, lines int, color *math32.Color) *ChartScaleY {
  279. sy := new(ChartScaleY)
  280. sy.chart = chart
  281. sy.lines = lines
  282. sy.format = "%v"
  283. sy.model = NewLabel(" ")
  284. // Appends scaleY left vertical line
  285. positions := math32.NewArrayF32(0, 0)
  286. bx, by := chart.Pix2NDC(chart.left, chart.ContentHeight()-chart.bottom)
  287. positions.Append(
  288. bx, by, 0, color.R, color.G, color.B,
  289. bx, 0, 0, color.R, color.G, color.B,
  290. )
  291. // Appends horizontal lines
  292. step := -by / float32(lines)
  293. for i := 0; i < lines; i++ {
  294. ny := by + float32(i)*step
  295. positions.Append(
  296. bx, ny, 0, color.R, color.G, color.B,
  297. 1, ny, 0, color.R, color.G, color.B,
  298. )
  299. }
  300. // Creates geometry using one interlaced VBO
  301. geom := geometry.NewGeometry()
  302. geom.AddVBO(gls.NewVBO().
  303. AddAttrib("VertexPosition", 3).
  304. AddAttrib("VertexColor", 3).
  305. SetBuffer(positions),
  306. )
  307. // Creates material
  308. mat := material.NewMaterial()
  309. mat.SetLineWidth(1.0)
  310. mat.SetShader("shaderChart")
  311. // Initializes the panel with this graphic
  312. gr := graphic.NewGraphic(geom, gls.LINES)
  313. gr.AddMaterial(sy, mat, 0, 0)
  314. sy.Panel.InitializeGraphic(chart.ContentWidth(), chart.ContentHeight(), gr)
  315. // Add labels after the panel is initialized
  316. for i := 0; i < lines; i++ {
  317. ny := by + float32(i)*step
  318. l := NewLabel(fmt.Sprintf(sy.format, float32(i)))
  319. px, py := sy.NDC2Pix(0, ny)
  320. py -= sy.model.Height() / 2
  321. //log.Error("label x:%v y:%v", px, py)
  322. l.SetPosition(px, py)
  323. sy.Add(l)
  324. sy.labels = append(sy.labels, l)
  325. }
  326. sy.recalc()
  327. return sy
  328. }
  329. func (sy *ChartScaleY) updateLabels() {
  330. _, by := sy.chart.Pix2NDC(sy.chart.left, sy.chart.ContentHeight()-sy.chart.bottom)
  331. step := 1 / (float32(sy.lines) + 1)
  332. for i := 0; i < len(sy.labels); i++ {
  333. ny := by + float32(i)*step
  334. px, py := sy.NDC2Pix(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. //
  354. // LineGraph
  355. //
  356. type LineGraph struct {
  357. Panel // Embedded panel
  358. chart *ChartLine // Container chart
  359. color math32.Color // Line color
  360. y []float32 // Data y
  361. }
  362. func newLineGraph(chart *ChartLine, color *math32.Color, y []float32) *LineGraph {
  363. log.Error("newLineGraph")
  364. lg := new(LineGraph)
  365. lg.chart = chart
  366. lg.color = *color
  367. lg.y = y
  368. lg.setGeometry()
  369. return lg
  370. }
  371. func (lg *LineGraph) SetColor(color *math32.Color) {
  372. }
  373. func (lg *LineGraph) SetData(x, y []float32) {
  374. lg.y = y
  375. }
  376. func (lg *LineGraph) setGeometry() {
  377. lg.chart.calcRangeY()
  378. log.Error("minY:%v maxY:%v", lg.chart.minY, lg.chart.maxY)
  379. // Creates array for vertices and colors
  380. positions := math32.NewArrayF32(0, 0)
  381. origin := false
  382. step := 1.0 / float32(lg.chart.countX-1)
  383. rangeY := lg.chart.maxY - lg.chart.minY
  384. for i := 0; i < lg.chart.countX; i++ {
  385. x := i + lg.chart.offsetX
  386. if x >= len(lg.y) {
  387. break
  388. }
  389. px := float32(i) * step
  390. if !origin {
  391. positions.Append(px, -1, 0, lg.color.R, lg.color.G, lg.color.B)
  392. origin = true
  393. }
  394. vy := lg.y[x]
  395. py := -1 + (vy / rangeY)
  396. positions.Append(px, py, 0, lg.color.R, lg.color.G, lg.color.B)
  397. }
  398. // Creates geometry using one interlaced VBO
  399. geom := geometry.NewGeometry()
  400. geom.AddVBO(gls.NewVBO().
  401. AddAttrib("VertexPosition", 3).
  402. AddAttrib("VertexColor", 3).
  403. SetBuffer(positions),
  404. )
  405. // Creates material
  406. mat := material.NewMaterial()
  407. mat.SetLineWidth(1.0)
  408. mat.SetShader("shaderChart")
  409. // Initializes the panel with this graphic
  410. gr := graphic.NewGraphic(geom, gls.LINE_STRIP)
  411. gr.AddMaterial(lg, mat, 0, 0)
  412. lg.Panel.InitializeGraphic(lg.chart.ContentWidth(), lg.chart.ContentHeight(), gr)
  413. }
  414. func (lg *LineGraph) recalc() {
  415. px := lg.chart.left
  416. py := float32(0)
  417. w := lg.chart.ContentWidth() - lg.chart.left
  418. h := lg.chart.ContentHeight() - lg.chart.bottom
  419. if lg.chart.title != nil {
  420. py += lg.chart.title.Height()
  421. h -= lg.chart.title.Height()
  422. }
  423. lg.SetPosition(px, py)
  424. lg.SetSize(w, h)
  425. }
  426. //
  427. // Vertex Shader template
  428. //
  429. const shaderChartVertex = `
  430. #version {{.Version}}
  431. // Vertex attributes
  432. {{template "attributes" .}}
  433. // Input uniforms
  434. uniform mat4 ModelMatrix;
  435. // Outputs for fragment shader
  436. out vec3 Color;
  437. void main() {
  438. Color = VertexColor;
  439. // Set position
  440. vec4 pos = vec4(VertexPosition.xyz, 1);
  441. gl_Position = ModelMatrix * pos;
  442. }
  443. `
  444. //
  445. // Fragment Shader template
  446. //
  447. const shaderChartFrag = `
  448. #version {{.Version}}
  449. in vec3 Color;
  450. out vec4 FragColor;
  451. void main() {
  452. FragColor = vec4(Color, 1.0);
  453. }
  454. `