chart.go 12 KB

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