|
@@ -1,5 +1,7 @@
|
|
|
-import { Nullable, Scene, Mesh, Observable, StandardMaterial, Material, Color3, Animation, Animatable } from "babylonjs";
|
|
|
+import { Nullable, Scene, Mesh, StandardMaterial, Material, Animation, Observer, Vector3, GlowLayer, Engine, AbstractMesh } from "babylonjs";
|
|
|
import { Chart } from ".";
|
|
|
+import { AdvancedDynamicTexture, DisplayGrid } from "../../2D";
|
|
|
+import { FluentMaterial } from "../materials";
|
|
|
|
|
|
/** Class used to render bar graphs */
|
|
|
export class BarGraph extends Chart {
|
|
@@ -8,8 +10,18 @@ export class BarGraph extends Chart {
|
|
|
private _maxBarHeight = 10;
|
|
|
private _defaultMaterial: Nullable<Material>;
|
|
|
protected _ownDefaultMaterial = false;
|
|
|
+ private _barMeshes: Nullable<Array<Mesh>>;
|
|
|
+ private _backgroundMesh: Nullable<Mesh>;
|
|
|
+ private _backgroundADT : Nullable<AdvancedDynamicTexture>;
|
|
|
|
|
|
- public onElementCreated = new Observable<Mesh>();
|
|
|
+ private _pickedPointObserver: Nullable<Observer<Vector3>>;
|
|
|
+
|
|
|
+ private _glowLayer: GlowLayer;
|
|
|
+
|
|
|
+ private _onElementEnterObserver: Nullable<Observer<AbstractMesh>>;
|
|
|
+ private _onElementOutObserver: Nullable<Observer<AbstractMesh>>;
|
|
|
+
|
|
|
+ private _labelDimension: string;
|
|
|
|
|
|
/** Gets or sets the margin between bars */
|
|
|
public get margin(): number {
|
|
@@ -56,6 +68,21 @@ export class BarGraph extends Chart {
|
|
|
this.refresh();
|
|
|
}
|
|
|
|
|
|
+ /** Gets or sets the dimension used for the labels */
|
|
|
+ public get labelDimension(): string {
|
|
|
+ return this._labelDimension;
|
|
|
+ }
|
|
|
+
|
|
|
+ public set labelDimension(value: string) {
|
|
|
+ if (this._labelDimension === value) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this._labelDimension = value;
|
|
|
+
|
|
|
+ this.refresh();
|
|
|
+ }
|
|
|
+
|
|
|
/** Gets or sets the material used by bar meshes */
|
|
|
public get defaultMaterial(): Nullable<Material> {
|
|
|
return this._defaultMaterial;
|
|
@@ -74,16 +101,47 @@ export class BarGraph extends Chart {
|
|
|
/**
|
|
|
* Creates a new BarGraph
|
|
|
* @param name defines the name of the graph
|
|
|
+ * @param scene defines the hosting scene
|
|
|
*/
|
|
|
- constructor(name: string, scene?: Scene) {
|
|
|
+ constructor(name: string, scene: Nullable<Scene> = Engine.LastCreatedScene) {
|
|
|
super(name, scene);
|
|
|
+
|
|
|
+ this._glowLayer = new GlowLayer("glow", scene!);
|
|
|
+
|
|
|
+ let activeBar: Nullable<Mesh>;
|
|
|
+ this._onElementEnterObserver = this.onElementEnterObservable.add(mesh => {
|
|
|
+ activeBar = <Mesh>mesh;
|
|
|
+ });
|
|
|
+
|
|
|
+ this._onElementOutObserver = this.onElementOutObservable.add(mesh => {
|
|
|
+ activeBar = null;
|
|
|
+ });
|
|
|
+
|
|
|
+ this._glowLayer.customEmissiveColorSelector = (mesh, subMesh, material, result) => {
|
|
|
+ if (mesh === activeBar) {
|
|
|
+ let chartColor = this._dataSource!.color.scale(0.75);
|
|
|
+ result.set(chartColor.r, chartColor.g, chartColor.b, 1.0);
|
|
|
+ } else {
|
|
|
+ result.set(0, 0, 0, 0);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
protected _createDefaultMaterial(scene: Scene): Material {
|
|
|
- var result = new StandardMaterial("Plastic", scene);
|
|
|
-
|
|
|
- result.diffuseColor = this._dataSource!.color;
|
|
|
- result.specularColor = Color3.Black();
|
|
|
+ var result = new FluentMaterial("fluent", scene);
|
|
|
+ result.albedoColor = this._dataSource!.color.scale(0.5);
|
|
|
+ result.innerGlowColorIntensity = 0.6;
|
|
|
+ result.renderHoverLight = true;
|
|
|
+ result.hoverRadius = 5;
|
|
|
+
|
|
|
+ this._pickedPointObserver = this.onPickedPointChangedObservable.add(pickedPoint => {
|
|
|
+ if (pickedPoint) {
|
|
|
+ result.hoverPosition = pickedPoint;
|
|
|
+ result.hoverColor.a = 1.0;
|
|
|
+ } else {
|
|
|
+ result.hoverColor.a = 0;
|
|
|
+ }
|
|
|
+ });
|
|
|
|
|
|
return result;
|
|
|
}
|
|
@@ -98,16 +156,22 @@ export class BarGraph extends Chart {
|
|
|
var box = Mesh.CreateBox(name, 1, scene);
|
|
|
box.setPivotPoint(new BABYLON.Vector3(0, -0.5, 0));
|
|
|
|
|
|
+ box.metadata = "chart";
|
|
|
+
|
|
|
return box;
|
|
|
}
|
|
|
|
|
|
- /** Force the graph to redraw itself */
|
|
|
+ /**
|
|
|
+ * Force the graph to redraw itself
|
|
|
+ * @returns the current BarGraph
|
|
|
+ */
|
|
|
public refresh(): BarGraph {
|
|
|
- // Cleanup
|
|
|
- var descendants = this._rootNode.getDescendants();
|
|
|
- descendants.forEach(n => n.dispose());
|
|
|
+ if (this._blockRefresh) {
|
|
|
+ return this;
|
|
|
+ }
|
|
|
|
|
|
if (!this._dataSource) {
|
|
|
+ this._clean();
|
|
|
return this;
|
|
|
}
|
|
|
|
|
@@ -119,12 +183,13 @@ export class BarGraph extends Chart {
|
|
|
}
|
|
|
|
|
|
// Scan data
|
|
|
- let min = Number.MAX_VALUE;
|
|
|
+ let min = 0;
|
|
|
let max = Number.MIN_VALUE;
|
|
|
|
|
|
const data = this._dataFilters ? this._dataSource.getFilteredData(this._dataFilters) : this._dataSource.data;
|
|
|
|
|
|
- data.forEach(entry => {
|
|
|
+ // Check the limit of the entire series
|
|
|
+ this._dataSource.data.forEach(entry => {
|
|
|
if (min > entry.value) {
|
|
|
min = entry.value;
|
|
|
}
|
|
@@ -136,36 +201,119 @@ export class BarGraph extends Chart {
|
|
|
|
|
|
let ratio = this.maxBarHeight / (max - min);
|
|
|
|
|
|
+ let createMesh = false;
|
|
|
+
|
|
|
+ // Do we need to create new graph or animate the current one
|
|
|
+ if (!this._barMeshes || this._barMeshes.length !== data.length) {
|
|
|
+ this._clean();
|
|
|
+ createMesh = true;
|
|
|
+ this._barMeshes = [];
|
|
|
+ }
|
|
|
+
|
|
|
+ this.removeLabels();
|
|
|
+
|
|
|
+ // Axis
|
|
|
+ if (!this._backgroundMesh) {
|
|
|
+ this._backgroundMesh = BABYLON.Mesh.CreatePlane("background", 1, scene);
|
|
|
+ this._backgroundMesh.parent = this._rootNode;
|
|
|
+ this._backgroundMesh.setPivotPoint(new BABYLON.Vector3(0, -0.5, 0));
|
|
|
+
|
|
|
+ this._backgroundADT = AdvancedDynamicTexture.CreateForMesh(this._backgroundMesh, 512, 512, false);
|
|
|
+
|
|
|
+ let displayGrid = new DisplayGrid();
|
|
|
+ displayGrid.displayMajorLines = false;
|
|
|
+ displayGrid.minorLineColor = "White";
|
|
|
+ displayGrid.minorLineTickness = 2;
|
|
|
+ displayGrid.cellWidth = 512 / data.length;
|
|
|
+ displayGrid.cellHeight = 512 / 5;
|
|
|
+
|
|
|
+ this._backgroundADT.addControl(displayGrid);
|
|
|
+
|
|
|
+ (<StandardMaterial>this._backgroundMesh.material!).opacityTexture = null;
|
|
|
+ }
|
|
|
+ this._backgroundMesh.position.z = this.barWidth;
|
|
|
+ this._backgroundMesh.scaling.x = (this.barWidth + this.margin) * data.length;
|
|
|
+ this._backgroundMesh.scaling.y = this._maxBarHeight;
|
|
|
+
|
|
|
// We will generate one bar per entry
|
|
|
let left = -(data.length / 2) * (this.barWidth + this.margin) + 1.5 * this._margin;
|
|
|
let index = 0;
|
|
|
data.forEach(entry => {
|
|
|
|
|
|
- var barMesh = this._createBarMesh(this.name + "_box_" + index++, scene);
|
|
|
+ var barMesh: Mesh;
|
|
|
+ if (createMesh) {
|
|
|
+ barMesh = this._createBarMesh(this.name + "_box_" + index++, scene);
|
|
|
+ barMesh.enablePointerMoveEvents = true;
|
|
|
+ this._barMeshes!.push(barMesh);
|
|
|
+ } else {
|
|
|
+ barMesh = this._barMeshes![index++];
|
|
|
+ }
|
|
|
|
|
|
barMesh.parent = this._rootNode;
|
|
|
- barMesh.position.x += left;
|
|
|
+ barMesh.position.x = left;
|
|
|
+ let currentScalingYState = barMesh.scaling.y;
|
|
|
barMesh.scaling.set(this.barWidth, 0, this._barWidth);
|
|
|
|
|
|
- Animation.CreateAndStartAnimation("entryScale", barMesh, "scaling.y", 30, 30, 0, entry.value * ratio, 0);
|
|
|
+ var easing = new BABYLON.CircleEase();
|
|
|
+ Animation.CreateAndStartAnimation("entryScale", barMesh, "scaling.y", 30, 30, currentScalingYState, entry.value * ratio, 0, easing);
|
|
|
|
|
|
barMesh.material = this._defaultMaterial;
|
|
|
|
|
|
this.onElementCreated.notifyObservers(barMesh);
|
|
|
|
|
|
left += this.barWidth + this.margin;
|
|
|
- });
|
|
|
|
|
|
+ // Label
|
|
|
+ if (!this._labelDimension) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ let label = this.addLabel(entry[this._labelDimension]);
|
|
|
+ label.position = barMesh.position.clone();
|
|
|
+ label.position.z -= this.barWidth;
|
|
|
+ label.scaling.x = this.barWidth;
|
|
|
+ });
|
|
|
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
+ /** Clean associated resources */
|
|
|
public dispose() {
|
|
|
+ super.dispose();
|
|
|
if (this._ownDefaultMaterial && this._defaultMaterial) {
|
|
|
this._defaultMaterial.dispose();
|
|
|
this._defaultMaterial = null;
|
|
|
}
|
|
|
|
|
|
- this._rootNode.dispose();
|
|
|
+ if (this._backgroundADT) {
|
|
|
+ this._backgroundADT.dispose();
|
|
|
+ this._backgroundADT = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this._pickedPointObserver) {
|
|
|
+ this.onPickedPointChangedObservable.remove(this._pickedPointObserver);
|
|
|
+ this._pickedPointObserver = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this._onElementEnterObserver) {
|
|
|
+ this.onElementEnterObservable.remove(this._onElementEnterObserver);
|
|
|
+ this._onElementEnterObserver = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this._onElementOutObserver) {
|
|
|
+ this.onElementOutObservable.remove(this._onElementOutObserver);
|
|
|
+ this._onElementOutObserver = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ protected _clean(): void {
|
|
|
+ super._clean();
|
|
|
+ this._barMeshes = null;
|
|
|
+ this._backgroundMesh = null;
|
|
|
+
|
|
|
+ if (this._backgroundADT) {
|
|
|
+ this._backgroundADT.dispose();
|
|
|
+ this._backgroundADT = null;
|
|
|
+ }
|
|
|
}
|
|
|
}
|