chart.ts 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. import { Nullable, TransformNode, Scene, Vector3, Engine, Observer, PointerInfo, Observable, Mesh, AbstractMesh } from "babylonjs";
  2. import { DataSeries } from ".";
  3. import { AdvancedDynamicTexture, TextBlock, Rectangle, TextWrapping } from "../../2D";
  4. /**
  5. * Base class for all chart controls
  6. * @see http://doc.babylonjs.com/how_to/chart3d#charts
  7. */
  8. export abstract class Chart {
  9. protected _dataSource: Nullable<DataSeries>;
  10. protected _rootNode: TransformNode;
  11. protected _dataFilters: {[key: string]: string};
  12. private _pointerObserver: Nullable<Observer<PointerInfo>>;
  13. protected _scene: Scene;
  14. private _lastElementOver: Nullable<AbstractMesh>;
  15. private _labelMeshes = new Array<Mesh>();
  16. protected _blockRefresh = false;
  17. /** Observable raised when a refresh was done */
  18. public onRefreshObservable = new Observable<Chart>();
  19. /** Observable raised when a new element is created */
  20. public onElementCreatedObservable = new Observable<Mesh>();
  21. /**
  22. * Observable raised when the point picked by the pointer events changed
  23. */
  24. public onPickedPointChangedObservable = new Observable<Nullable<Vector3>>();
  25. /**
  26. * Observable raised when the pointer enters an element of the chart
  27. */
  28. public onElementEnterObservable = new Observable<AbstractMesh>();
  29. /**
  30. * Observable raised when the pointer leaves an element of the chart
  31. */
  32. public onElementOutObservable = new Observable<AbstractMesh>();
  33. /** User defined callback used to create labels */
  34. public labelCreationFunction: Nullable<(label: string, width: number, includeBackground: boolean) => Mesh>;
  35. /** Gets or sets the rotation of the entire chart */
  36. public set rotation(value: Vector3) {
  37. this._rootNode.rotation = value;
  38. }
  39. public get rotation(): Vector3 {
  40. return this._rootNode.rotation;
  41. }
  42. /** Gets or sets the position of the entire chart */
  43. public set position(value: Vector3) {
  44. this._rootNode.position = value;
  45. }
  46. public get position(): Vector3 {
  47. return this._rootNode.position;
  48. }
  49. /** Gets or sets the scaling of the entire chart */
  50. public set scaling(value: Vector3) {
  51. this._rootNode.scaling = value;
  52. }
  53. public get scaling(): Vector3 {
  54. return this._rootNode.scaling;
  55. }
  56. /** Gets or sets the data source used by the graph */
  57. public get dataSource(): Nullable<DataSeries> {
  58. return this._dataSource;
  59. }
  60. public set dataSource(value: Nullable<DataSeries>) {
  61. if (this._dataSource === value) {
  62. return;
  63. }
  64. this._dataSource = value;
  65. this.refresh();
  66. }
  67. /** Gets or sets the filters applied to data source */
  68. public get dataFilters(): {[key: string]: string} {
  69. return this._dataFilters;
  70. }
  71. public set dataFilters(filters: {[key: string]: string}) {
  72. this._dataFilters = filters;
  73. this.refresh();
  74. }
  75. /** Gets the root node associated with this graph */
  76. public get rootNode(): TransformNode {
  77. return this._rootNode;
  78. }
  79. /** Gets or sets a value indicating if refresh function should be executed (useful when multiple changes will happen and you want to run refresh only at the end) */
  80. public get blockRefresh(): boolean {
  81. return this._blockRefresh;
  82. }
  83. public set blockRefresh(value: boolean) {
  84. if (this._blockRefresh === value) {
  85. return;
  86. }
  87. this._blockRefresh = value;
  88. if (value) {
  89. this.refresh();
  90. }
  91. }
  92. /** Gets or sets the name of the graph */
  93. public name: string;
  94. /**
  95. * Creates a new Chart
  96. * @param name defines the name of the graph
  97. * @param scene defines the hosting scene
  98. */
  99. constructor(name: string, scene: Nullable<Scene> = Engine.LastCreatedScene) {
  100. this.name = name;
  101. this._rootNode = new TransformNode(name, scene);
  102. this._scene = scene!;
  103. this._pointerObserver = this._scene.onPointerObservable.add((pi, state) => {
  104. if (!pi.pickInfo || !pi.pickInfo.hit) {
  105. if (this._lastElementOver) {
  106. this.onElementOutObservable.notifyObservers(this._lastElementOver);
  107. this._lastElementOver = null;
  108. }
  109. this.onPickedPointChangedObservable.notifyObservers(null);
  110. return;
  111. }
  112. let metadata = pi.pickInfo.pickedMesh!.metadata;
  113. if (metadata && metadata.value) {
  114. if (this._lastElementOver !== pi.pickInfo.pickedMesh) {
  115. if (this._lastElementOver) {
  116. this.onElementOutObservable.notifyObservers(this._lastElementOver);
  117. this._lastElementOver = null;
  118. }
  119. this._lastElementOver = pi.pickInfo.pickedMesh;
  120. this.onElementEnterObservable.notifyObservers(this._lastElementOver!);
  121. }
  122. } else {
  123. if (this._lastElementOver) {
  124. this.onElementOutObservable.notifyObservers(this._lastElementOver);
  125. this._lastElementOver = null;
  126. }
  127. }
  128. this.onPickedPointChangedObservable.notifyObservers(pi.pickInfo.pickedPoint);
  129. });
  130. }
  131. /**
  132. * Function called by the chart objects when they need a label. Could be user defined if you set this.labelCreationFunction to a custom callback
  133. * @param label defines the text of the label
  134. * @param width defines the expected width (height is supposed to be 1)
  135. * @param includeBackground defines if a background rectangle must be added (default is true)
  136. * @returns a mesh used to host the label
  137. */
  138. protected _addLabel(label: string, width: number, includeBackground = true): Mesh {
  139. if (this.labelCreationFunction) {
  140. let labelMesh = this.labelCreationFunction(label, width, includeBackground);
  141. labelMesh.parent = this._rootNode;
  142. this._labelMeshes.push(labelMesh);
  143. return labelMesh;
  144. }
  145. let plane = Mesh.CreatePlane(label, 1, this._scene);
  146. this._labelMeshes.push(plane);
  147. plane.parent = this._rootNode;
  148. plane.billboardMode = Mesh.BILLBOARDMODE_ALL;
  149. plane.scaling.x = width;
  150. let resolution = 256;
  151. let adt = AdvancedDynamicTexture.CreateForMesh(plane, resolution, resolution / width, false, true);
  152. let textBlock = new TextBlock(label, label);
  153. textBlock.color = "White";
  154. textBlock.textWrapping = TextWrapping.Ellipsis;
  155. textBlock.fontWeight = "Bold";
  156. textBlock.fontSize = 50;
  157. if (includeBackground) {
  158. let rectangle = new Rectangle(label + "Border");
  159. rectangle.thickness = 4;
  160. rectangle.color = "White";
  161. rectangle.background = "Black";
  162. rectangle.addControl(textBlock);
  163. adt.addControl(rectangle);
  164. } else {
  165. adt.addControl(textBlock);
  166. }
  167. return plane;
  168. }
  169. /**
  170. * Remove specific label mesh
  171. * @param label defines the label mesh to remove
  172. */
  173. protected _removeLabel(label: Mesh): void {
  174. let index = this._labelMeshes.indexOf(label);
  175. if (index === -1) {
  176. return;
  177. }
  178. this._labelMeshes.splice(index, 1);
  179. label.dispose(false, true);
  180. }
  181. /** Remove all created labels */
  182. protected _removeLabels(): void {
  183. this._labelMeshes.forEach(label => {
  184. label.dispose(false, true);
  185. });
  186. this._labelMeshes = [];
  187. }
  188. /**
  189. * Force the chart to redraw itself
  190. * @returns the current chart
  191. */
  192. public abstract refresh(): Chart;
  193. /** Release all associated resources */
  194. public dispose() {
  195. this.onElementCreatedObservable.clear();
  196. this.onPickedPointChangedObservable.clear();
  197. this.onElementEnterObservable.clear();
  198. this.onElementOutObservable.clear();
  199. this.labelCreationFunction = null;
  200. if (this._pointerObserver) {
  201. this._scene.onPointerObservable.remove(this._pointerObserver);
  202. this._pointerObserver = null;
  203. }
  204. this._rootNode.dispose();
  205. }
  206. protected _clean(): void {
  207. // Cleanup
  208. var descendants = this._rootNode.getDescendants();
  209. descendants.forEach(n => n.dispose());
  210. }
  211. }