소스 검색

Merge pull request #4855 from BabylonJS/chart3d

Chart3d
David Catuhe 7 년 전
부모
커밋
be726bd632

+ 4 - 4
dist/preview release/babylon.max.js

@@ -33398,7 +33398,7 @@ var BABYLON;
         /**
          * Creates a ribbon mesh.
          * Please consider using the same method from the MeshBuilder class instead.
-         * The ribbon is a parametric shape :  http://doc.babylonjs.com/tutorials/Parametric_Shapes.  It has no predefined shape. Its final shape will depend on the input parameters.
+         * The ribbon is a parametric shape :  http://doc.babylonjs.com/how_to/parametric_shapes.  It has no predefined shape. Its final shape will depend on the input parameters.
          *
          * Please read this full tutorial to understand how to design a ribbon : http://doc.babylonjs.com/tutorials/Ribbon_Tutorial
          * The parameter `pathArray` is a required array of paths, what are each an array of successive Vector3. The pathArray parameter depicts the ribbon geometry.
@@ -33637,7 +33637,7 @@ var BABYLON;
         };
         /**
          * Creates an extruded shape mesh.
-         * The extrusion is a parametric shape :  http://doc.babylonjs.com/tutorials/Parametric_Shapes.  It has no predefined shape. Its final shape will depend on the input parameters.
+         * The extrusion is a parametric shape :  http://doc.babylonjs.com/how_to/parametric_shapes.  It has no predefined shape. Its final shape will depend on the input parameters.
          * Please consider using the same method from the MeshBuilder class instead.
          *
          * Please read this full tutorial to understand how to design an extruded shape : http://doc.babylonjs.com/how_to/parametric_shapes#extruded-shapes
@@ -33669,7 +33669,7 @@ var BABYLON;
         };
         /**
          * Creates an custom extruded shape mesh.
-         * The custom extrusion is a parametric shape :  http://doc.babylonjs.com/tutorials/Parametric_Shapes.  It has no predefined shape. Its final shape will depend on the input parameters.
+         * The custom extrusion is a parametric shape :  http://doc.babylonjs.com/how_to/parametric_shapes.  It has no predefined shape. Its final shape will depend on the input parameters.
          * Please consider using the same method from the MeshBuilder class instead.
          *
          * Please read this full tutorial to understand how to design a custom extruded shape : http://doc.babylonjs.com/how_to/parametric_shapes#extruded-shapes
@@ -33826,7 +33826,7 @@ var BABYLON;
         };
         /**
          * Creates a tube mesh.
-         * The tube is a parametric shape :  http://doc.babylonjs.com/tutorials/Parametric_Shapes.  It has no predefined shape. Its final shape will depend on the input parameters.
+         * The tube is a parametric shape :  http://doc.babylonjs.com/how_to/parametric_shapes.  It has no predefined shape. Its final shape will depend on the input parameters.
          * Please consider using the same method from the MeshBuilder class instead.
          * The parameter `path` is a required array of successive Vector3. It is the curve used as the axis of the tube.
          * The parameter `radius` (positive float, default 1) sets the tube radius size.

+ 4 - 4
dist/preview release/babylon.no-module.max.js

@@ -33365,7 +33365,7 @@ var BABYLON;
         /**
          * Creates a ribbon mesh.
          * Please consider using the same method from the MeshBuilder class instead.
-         * The ribbon is a parametric shape :  http://doc.babylonjs.com/tutorials/Parametric_Shapes.  It has no predefined shape. Its final shape will depend on the input parameters.
+         * The ribbon is a parametric shape :  http://doc.babylonjs.com/how_to/parametric_shapes.  It has no predefined shape. Its final shape will depend on the input parameters.
          *
          * Please read this full tutorial to understand how to design a ribbon : http://doc.babylonjs.com/tutorials/Ribbon_Tutorial
          * The parameter `pathArray` is a required array of paths, what are each an array of successive Vector3. The pathArray parameter depicts the ribbon geometry.
@@ -33604,7 +33604,7 @@ var BABYLON;
         };
         /**
          * Creates an extruded shape mesh.
-         * The extrusion is a parametric shape :  http://doc.babylonjs.com/tutorials/Parametric_Shapes.  It has no predefined shape. Its final shape will depend on the input parameters.
+         * The extrusion is a parametric shape :  http://doc.babylonjs.com/how_to/parametric_shapes.  It has no predefined shape. Its final shape will depend on the input parameters.
          * Please consider using the same method from the MeshBuilder class instead.
          *
          * Please read this full tutorial to understand how to design an extruded shape : http://doc.babylonjs.com/how_to/parametric_shapes#extruded-shapes
@@ -33636,7 +33636,7 @@ var BABYLON;
         };
         /**
          * Creates an custom extruded shape mesh.
-         * The custom extrusion is a parametric shape :  http://doc.babylonjs.com/tutorials/Parametric_Shapes.  It has no predefined shape. Its final shape will depend on the input parameters.
+         * The custom extrusion is a parametric shape :  http://doc.babylonjs.com/how_to/parametric_shapes.  It has no predefined shape. Its final shape will depend on the input parameters.
          * Please consider using the same method from the MeshBuilder class instead.
          *
          * Please read this full tutorial to understand how to design a custom extruded shape : http://doc.babylonjs.com/how_to/parametric_shapes#extruded-shapes
@@ -33793,7 +33793,7 @@ var BABYLON;
         };
         /**
          * Creates a tube mesh.
-         * The tube is a parametric shape :  http://doc.babylonjs.com/tutorials/Parametric_Shapes.  It has no predefined shape. Its final shape will depend on the input parameters.
+         * The tube is a parametric shape :  http://doc.babylonjs.com/how_to/parametric_shapes.  It has no predefined shape. Its final shape will depend on the input parameters.
          * Please consider using the same method from the MeshBuilder class instead.
          * The parameter `path` is a required array of successive Vector3. It is the curve used as the axis of the tube.
          * The parameter `radius` (positive float, default 1) sets the tube radius size.

+ 4 - 4
dist/preview release/es6.js

@@ -33365,7 +33365,7 @@ var BABYLON;
         /**
          * Creates a ribbon mesh.
          * Please consider using the same method from the MeshBuilder class instead.
-         * The ribbon is a parametric shape :  http://doc.babylonjs.com/tutorials/Parametric_Shapes.  It has no predefined shape. Its final shape will depend on the input parameters.
+         * The ribbon is a parametric shape :  http://doc.babylonjs.com/how_to/parametric_shapes.  It has no predefined shape. Its final shape will depend on the input parameters.
          *
          * Please read this full tutorial to understand how to design a ribbon : http://doc.babylonjs.com/tutorials/Ribbon_Tutorial
          * The parameter `pathArray` is a required array of paths, what are each an array of successive Vector3. The pathArray parameter depicts the ribbon geometry.
@@ -33604,7 +33604,7 @@ var BABYLON;
         };
         /**
          * Creates an extruded shape mesh.
-         * The extrusion is a parametric shape :  http://doc.babylonjs.com/tutorials/Parametric_Shapes.  It has no predefined shape. Its final shape will depend on the input parameters.
+         * The extrusion is a parametric shape :  http://doc.babylonjs.com/how_to/parametric_shapes.  It has no predefined shape. Its final shape will depend on the input parameters.
          * Please consider using the same method from the MeshBuilder class instead.
          *
          * Please read this full tutorial to understand how to design an extruded shape : http://doc.babylonjs.com/how_to/parametric_shapes#extruded-shapes
@@ -33636,7 +33636,7 @@ var BABYLON;
         };
         /**
          * Creates an custom extruded shape mesh.
-         * The custom extrusion is a parametric shape :  http://doc.babylonjs.com/tutorials/Parametric_Shapes.  It has no predefined shape. Its final shape will depend on the input parameters.
+         * The custom extrusion is a parametric shape :  http://doc.babylonjs.com/how_to/parametric_shapes.  It has no predefined shape. Its final shape will depend on the input parameters.
          * Please consider using the same method from the MeshBuilder class instead.
          *
          * Please read this full tutorial to understand how to design a custom extruded shape : http://doc.babylonjs.com/how_to/parametric_shapes#extruded-shapes
@@ -33793,7 +33793,7 @@ var BABYLON;
         };
         /**
          * Creates a tube mesh.
-         * The tube is a parametric shape :  http://doc.babylonjs.com/tutorials/Parametric_Shapes.  It has no predefined shape. Its final shape will depend on the input parameters.
+         * The tube is a parametric shape :  http://doc.babylonjs.com/how_to/parametric_shapes.  It has no predefined shape. Its final shape will depend on the input parameters.
          * Please consider using the same method from the MeshBuilder class instead.
          * The parameter `path` is a required array of successive Vector3. It is the curve used as the axis of the tube.
          * The parameter `radius` (positive float, default 1) sets the tube radius size.

+ 14 - 0
gui/src/2D/controls/container.ts

@@ -154,6 +154,20 @@ export class Container extends Control {
     }
 
     /**
+     * Removes all controls from the current container
+     * @returns the current container
+     */
+    public clearControls(): Container {
+        let children = this._children.slice();
+
+        for (var child of children) {
+            this.removeControl(child);
+        }
+
+        return this;
+    }
+
+    /**
      * Removes a control from the current container
      * @param control defines the control to remove
      * @returns the current container

+ 71 - 37
gui/src/2D/controls/displayGrid.ts

@@ -17,6 +17,37 @@ export class DisplayGrid extends Control {
 
     private _background = "Black";
 
+    private _displayMajorLines = true;
+    private _displayMinorLines = true;
+
+    /** Gets or sets a boolean indicating if minor lines must be rendered (true by default)) */
+    public get displayMinorLines(): boolean {
+        return this._displayMinorLines;
+    }
+
+    public set displayMinorLines(value: boolean) {
+        if (this._displayMinorLines === value) {
+            return;
+        }
+
+        this._displayMinorLines = value;
+        this._markAsDirty();
+    }  
+
+    /** Gets or sets a boolean indicating if major lines must be rendered (true by default)) */
+    public get displayMajorLines(): boolean {
+        return this._displayMajorLines;
+    }
+
+    public set displayMajorLines(value: boolean) {
+        if (this._displayMajorLines === value) {
+            return;
+        }
+
+        this._displayMajorLines = value;
+        this._markAsDirty();
+    }  
+
     /** Gets or sets background color (Black by default) */
     public get background(): string {
         return this._background;
@@ -131,52 +162,55 @@ export class DisplayGrid extends Control {
             let cellCountX = this._currentMeasure.width / this._cellWidth;
             let cellCountY = this._currentMeasure.height / this._cellHeight;
 
-            // Minor lines
-            context.strokeStyle = this._minorLineColor;
-            context.lineWidth = this._minorLineTickness;        
-
+            // Minor lines    
             const left = this._currentMeasure.left + this._currentMeasure.width / 2;
-
-            for (var x = -cellCountX / 2; x < cellCountX / 2; x++) {
-                const cellX = left + x * this.cellWidth;
-
-                context.beginPath();
-                context.moveTo(cellX, this._currentMeasure.top);
-                context.lineTo(cellX, this._currentMeasure.top + this._currentMeasure.height);
-                
-                context.stroke();                
-            }
-
             const top = this._currentMeasure.top + this._currentMeasure.height / 2;
 
-            for (var y = -cellCountY / 2; y < cellCountY / 2; y++) {
-                const cellY = top + y * this.cellHeight;
+            if (this._displayMinorLines) {
+                context.strokeStyle = this._minorLineColor;
+                context.lineWidth = this._minorLineTickness;    
 
-                context.beginPath();
-                context.moveTo(this._currentMeasure.left, cellY);
-                context.lineTo(this._currentMeasure.left + this._currentMeasure.width, cellY);
-                context.stroke();
-            }
+                for (var x = -cellCountX / 2; x < cellCountX / 2; x++) {
+                    const cellX = left + x * this.cellWidth;
 
-            // Major lines
-            context.strokeStyle = this._majorLineColor;
-            context.lineWidth = this._majorLineTickness;        
+                    context.beginPath();
+                    context.moveTo(cellX, this._currentMeasure.top);
+                    context.lineTo(cellX, this._currentMeasure.top + this._currentMeasure.height);
+                    
+                    context.stroke();                
+                }
 
-            for (var x = -cellCountX / 2 + this._majorLineFrequency; x < cellCountX / 2; x += this._majorLineFrequency) {
-                let cellX = left + x * this.cellWidth;
+                for (var y = -cellCountY / 2; y < cellCountY / 2; y++) {
+                    const cellY = top + y * this.cellHeight;
 
-                context.beginPath();    
-                context.moveTo(cellX, this._currentMeasure.top);
-                context.lineTo(cellX, this._currentMeasure.top + this._currentMeasure.height);
-                context.stroke();
+                    context.beginPath();
+                    context.moveTo(this._currentMeasure.left, cellY);
+                    context.lineTo(this._currentMeasure.left + this._currentMeasure.width, cellY);
+                    context.stroke();
+                }
             }
 
-            for (var y = -cellCountY / 2 + this._majorLineFrequency; y < cellCountY / 2; y += this._majorLineFrequency) {
-                let cellY = top + y * this.cellHeight;
-                context.moveTo(this._currentMeasure.left, cellY);
-                context.lineTo(this._currentMeasure.left + this._currentMeasure.width, cellY);
-                context.closePath();
-                context.stroke();
+            // Major lines
+            if (this._displayMajorLines) {
+                context.strokeStyle = this._majorLineColor;
+                context.lineWidth = this._majorLineTickness;        
+
+                for (var x = -cellCountX / 2 + this._majorLineFrequency; x < cellCountX / 2; x += this._majorLineFrequency) {
+                    let cellX = left + x * this.cellWidth;
+
+                    context.beginPath();    
+                    context.moveTo(cellX, this._currentMeasure.top);
+                    context.lineTo(cellX, this._currentMeasure.top + this._currentMeasure.height);
+                    context.stroke();
+                }
+
+                for (var y = -cellCountY / 2 + this._majorLineFrequency; y < cellCountY / 2; y += this._majorLineFrequency) {
+                    let cellY = top + y * this.cellHeight;
+                    context.moveTo(this._currentMeasure.left, cellY);
+                    context.lineTo(this._currentMeasure.left + this._currentMeasure.width, cellY);
+                    context.closePath();
+                    context.stroke();
+                }
             }
         }
 

+ 319 - 0
gui/src/3D/charting/barGraph.ts

@@ -0,0 +1,319 @@
+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 {
+    private _margin = 1;
+    private _barWidth = 2
+    private _maxBarHeight = 10;
+    private _defaultMaterial: Nullable<Material>;
+    protected _ownDefaultMaterial = false;
+    private _barMeshes: Nullable<Array<Mesh>>;
+    private _backgroundMesh: Nullable<Mesh>;
+    private _backgroundADT : Nullable<AdvancedDynamicTexture>;
+
+    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 {
+        return this._margin;
+    }
+
+    public set margin(value: number) {
+        if (this._margin === value) {
+            return;
+        }
+
+        this._margin = value;
+
+        this.refresh();
+    }
+
+    /** Gets or sets the with of each bar */
+    public get barWidth(): number {
+        return this._barWidth;
+    }
+
+    public set barWidth(value: number) {
+        if (this._barWidth === value) {
+            return;
+        }
+
+        this._barWidth = value;
+
+        this.refresh();
+    }
+
+    /** Gets or sets the maximum height of a bar */
+    public get maxBarHeight(): number {
+        return this._maxBarHeight;
+    }
+
+    public set maxBarHeight(value: number) {
+        if (this._maxBarHeight === value) {
+            return;
+        }
+
+        this._maxBarHeight = value;
+
+        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;
+    }
+
+    public set defaultMaterial(value: Nullable<Material>) {
+        if (this._defaultMaterial === value) {
+            return;
+        }
+
+        this._defaultMaterial = value;
+
+        this.refresh();
+    }
+
+    /**
+     * Creates a new BarGraph
+     * @param name defines the name of the graph
+     * @param scene defines the hosting 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 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;
+    }
+
+    /**
+     * Children class can override this function to provide a new mesh (as long as it stays inside a 1x1x1 box)
+     * @param name defines the mesh name
+     * @param scene defines the hosting scene
+     * @returns a new mesh used to represent the current bar
+     */
+    protected _createBarMesh(name: string, scene: Scene): Mesh {
+        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 
+     * @returns the current BarGraph
+    */
+    public refresh(): BarGraph {
+        if (this._blockRefresh) {
+            return this;
+        }
+
+        if (!this._dataSource) {
+            this._clean();
+            return this;
+        }
+
+        let scene = this._rootNode.getScene();
+
+        // Default material
+        if (!this._defaultMaterial) {
+            this._defaultMaterial = this._createDefaultMaterial(scene);
+        }
+
+        // Scan data
+        let min = 0;
+        let max = Number.MIN_VALUE;
+
+        const data = this._dataFilters ? this._dataSource.getFilteredData(this._dataFilters) : this._dataSource.data;
+
+        // Check the limit of the entire series
+        this._dataSource.data.forEach(entry => {
+            if (min > entry.value) {
+                min = entry.value;
+            }
+
+            if (max < entry.value) {
+                max = entry.value;
+            }
+        });
+
+        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: 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;
+            let currentScalingYState = barMesh.scaling.y;
+            barMesh.scaling.set(this.barWidth, 0, this._barWidth);
+
+            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;
+        }
+
+        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;
+        }
+    }
+}

+ 193 - 0
gui/src/3D/charting/chart.ts

@@ -0,0 +1,193 @@
+import { Nullable, TransformNode, Scene, Vector3, Engine, Observer, PointerInfo, Observable, Mesh, AbstractMesh } from "babylonjs";
+import { DataSeries } from ".";
+import { AdvancedDynamicTexture, TextBlock } from "../../2D";
+
+/** base class for all chart controls*/
+export abstract class Chart {
+    protected _dataSource: Nullable<DataSeries>;
+    protected _rootNode: TransformNode;
+    protected _dataFilters: {[key: string]: string};
+    private _pointerObserver: Nullable<Observer<PointerInfo>>;
+    protected _scene: Scene;
+    private _lastElementOver: Nullable<AbstractMesh>;
+    private _labelMeshes = new Array<Mesh>();
+    protected _blockRefresh = false;
+
+    /** Observable raised when a new element is created */
+    public onElementCreated = new Observable<Mesh>();
+
+    /**
+     * Observable raised when the point picked by the pointer events changed
+     */
+    public onPickedPointChangedObservable = new Observable<Nullable<Vector3>>();
+
+    /**
+     * Observable raised when the pointer enters an element of the chart
+    */
+    public onElementEnterObservable = new Observable<AbstractMesh>();
+
+    /**
+     * Observable raised when the pointer leaves an element of the chart
+     */
+    public onElementOutObservable = new Observable<AbstractMesh>();
+
+    /** Gets or sets the rotation of the entire chart */
+    public set rotation(value: Vector3) {
+        this._rootNode.rotation = value;
+    }
+
+    public get rotation(): Vector3 {
+        return this._rootNode.rotation;
+    }
+
+    /** Gets or sets the position of the entire chart */
+    public set position(value: Vector3) {
+        this._rootNode.position = value;
+    }
+
+    public get position(): Vector3 {
+        return this._rootNode.position;
+    }
+
+    /** Gets or sets the scaling of the entire chart */
+    public set scaling(value: Vector3) {
+        this._rootNode.scaling = value;
+    }
+
+    public get scaling(): Vector3 {
+        return this._rootNode.scaling;
+    }
+
+    /** Gets or sets the data source used by the graph */
+    public get dataSource(): Nullable<DataSeries> {
+        return this._dataSource;
+    }
+
+    public set dataSource(value: Nullable<DataSeries>) {
+        if (this._dataSource === value) {
+            return;
+        }
+
+        this._dataSource = value;
+
+        this.refresh();
+    }
+
+    /** Gets the filters applied to data source */
+    public get dataFilters(): {[key: string]: string} {
+        return this._dataFilters;
+    }
+
+    public set dataFilters(filters: {[key: string]: string}) {
+        this._dataFilters = filters;
+
+        this.refresh();
+    }
+
+    /** Gets the root node associated with this graph */
+    public get rootNode(): TransformNode {
+        return this._rootNode;
+    }
+
+    /** 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) */
+    public get blockRefresh(): boolean {
+        return this._blockRefresh;
+    }
+
+    public set blockRefresh(value: boolean) {
+        if (this._blockRefresh === value) {
+            return;
+        }
+
+        this._blockRefresh = value;
+
+        if (value) {
+            this.refresh();
+        }
+    }
+
+    /** Gets or sets the name of the graph */
+    public name: string; 
+
+    /**
+     * Creates a new Chart
+     * @param name defines the name of the graph
+     * @param scene defines the hosting scene
+     */
+    constructor(name: string, scene: Nullable<Scene> = Engine.LastCreatedScene) {
+        this.name = name;
+        this._rootNode = new TransformNode(name, scene);
+
+        this._scene = scene!;
+
+        this._pointerObserver = this._scene.onPointerObservable.add((pi, state) => {
+            if (!pi.pickInfo || !pi.pickInfo.hit) {
+                if (this._lastElementOver) {
+                    this.onElementOutObservable.notifyObservers(this._lastElementOver);
+                    this._lastElementOver = null;
+                }
+
+                this.onPickedPointChangedObservable.notifyObservers(null);
+                return;
+            }
+
+            if (pi.pickInfo.pickedMesh!.metadata === "chart") {
+                if (this._lastElementOver !== pi.pickInfo.pickedMesh) {
+                    this._lastElementOver = pi.pickInfo.pickedMesh;
+                    this.onElementEnterObservable.notifyObservers(this._lastElementOver!);
+                }
+            }
+
+            this.onPickedPointChangedObservable.notifyObservers(pi.pickInfo.pickedPoint);
+        });
+    }
+
+    public addLabel(label: string): Mesh {
+        let plane = Mesh.CreatePlane(label, 1, this._scene);
+
+        this._labelMeshes.push(plane);
+
+        plane.parent = this._rootNode;
+        plane.billboardMode = Mesh.BILLBOARDMODE_ALL;
+        plane.renderingGroupId = 1;
+
+        let adt = AdvancedDynamicTexture.CreateForMesh(plane, 512, 128, false);
+        let textBlock = new TextBlock(label, label);
+        textBlock.color = "White";
+        textBlock.fontWeight = "Bold";
+        textBlock.fontSize = 80;
+
+        adt.addControl(textBlock);
+
+        return plane;
+    }
+
+    public removeLabels() {
+        this._labelMeshes.forEach(label => {
+            label.dispose(false, true);
+        });
+
+        this._labelMeshes = [];
+    }
+
+    /** 
+     * Force the graph to redraw itself 
+     * @returns the current BarGraph
+    */
+    public abstract refresh(): Chart;
+
+    public dispose() {
+        if (this._pointerObserver) {
+            this._scene.onPointerObservable.remove(this._pointerObserver);
+            this._pointerObserver = null;
+        }
+
+        this._rootNode.dispose();
+    }
+
+    protected _clean(): void {
+        // Cleanup
+        var descendants = this._rootNode.getDescendants();
+        descendants.forEach(n => n.dispose());
+    }
+}

+ 162 - 0
gui/src/3D/charting/dataSeries.ts

@@ -0,0 +1,162 @@
+import { Color3 } from "babylonjs";
+
+/** Class used to store data to display */
+export class DataSeries {
+    /** Gets or sets the label of the series */
+    public label: string;
+
+    /** Gets or sets the color associated with the series */
+    public color: Color3;
+
+    /** Gets or sets the list of dimensions (used to filter data) */
+    public dimensions: Array<string>;
+
+    /** Gets or sets the list of values (data to display) */
+    public data: Array<any>;  
+
+    /**
+     * Apply a list of filters to the data and return a list
+     * @param filters defines the filters to apply
+     * @returns an array containing the filtered data
+     */
+    public getFilteredData(filters: {[key: string]: string}): Array<any> {
+        let filteredData = new Array<any>();
+
+        this.data.forEach(element => {
+            let isValid = false;
+            for (var filter in filters) {
+                if (!filters.hasOwnProperty(filter)) {
+                    continue;
+                }
+
+                var filterValue = filters[filter];
+                isValid = (element[filter] === filterValue);
+
+                if (!isValid) {
+                    break;
+                }
+            }
+
+            if (isValid) {
+                filteredData.push(element);
+            }
+        });
+
+        return filteredData;
+    }
+
+    /**
+     * Get the different values of a dimension
+     * @param key defines the dimension name
+     * @returns An array of values
+     */
+    public getDimensionValues(key: string): Array<any> {
+        var result = new Array<any>();
+
+        this.data.forEach((entry) => {
+            var value = entry[key];
+            if (result.indexOf(value) === -1) {
+                result.push(value);
+            }
+        });
+
+        return result;
+    }
+
+    /**
+     * Create a new DataSeries containing testing values
+     * @returns the new DataSeries
+     */
+    public static CreateFakeData(): DataSeries {
+        var series = new DataSeries();
+        series.label = "Product #1";
+        series.color = new Color3(1.0, 0, 0);
+
+        series.dimensions = ["Year", "Country"];
+
+        series.data = [
+            {
+                "Year": 2014,
+                "Country": "France",
+                "value": 10
+            }, 
+            {
+                "Year": 2014,
+                "Country": "USA",
+                "value": 200
+            }, 
+            {
+                "Year": 2014,
+                "Country": "India",
+                "value": 400
+            },
+            {
+                "Year": 2014,
+                "Country": "UK",
+                "value": 180
+            },
+            {
+                "Year": 2014,
+                "Country": "Germany",
+                "value": 400
+            }, 
+            {
+                "Year": 2014,
+                "Country": "Australia",
+                "value": 24
+            }, 
+            {
+                "Year": 2014,
+                "Country": "China",
+                "value": 540
+            }, 
+            {
+                "Year": 2014,
+                "Country": "Japan",
+                "value": 150
+            },
+            {
+                "Year": 2015,
+                "Country": "France",
+                "value": 12
+            }, 
+            {
+                "Year": 2015,
+                "Country": "USA",
+                "value": 120
+            }, 
+            {
+                "Year": 2015,
+                "Country": "India",
+                "value": 480
+            }, 
+            {
+                "Year": 2015,
+                "Country": "UK",
+                "value": 10
+            },
+            {
+                "Year": 2015,
+                "Country": "Germany",
+                "value": 80
+            }, 
+            {
+                "Year": 2015,
+                "Country": "Australia",
+                "value": 230
+            }, 
+            {
+                "Year": 2015,
+                "Country": "China",
+                "value": 490
+            }, 
+            {
+                "Year": 2015,
+                "Country": "Japan",
+                "value": 120
+            }
+        ];
+        
+        return series;
+    }
+}

+ 3 - 0
gui/src/3D/charting/index.ts

@@ -0,0 +1,3 @@
+export * from "./dataSeries";
+export * from "./chart";
+export * from "./barGraph";

+ 1 - 0
gui/src/3D/index.ts

@@ -1,5 +1,6 @@
 export * from "./controls";
 export * from "./materials";
+export * from "./charting";
 
 export * from "./gui3DManager";
 export * from "./vector3WithInfo";

+ 1 - 1
gui/src/3D/materials/fluentMaterial.ts

@@ -80,7 +80,7 @@ export class FluentMaterial extends PushMaterial {
     public renderHoverLight = false;
 
     /**
-     * Gets or sets the radius used to render the hover light (default is 0.15)
+     * Gets or sets the radius used to render the hover light (default is 1.0)
      */
     @serialize()
     public hoverRadius = 1.0;

+ 3 - 3
src/Collisions/babylon.pickingInfo.ts

@@ -25,7 +25,7 @@
          */
         public pickedPoint: Nullable<Vector3> = null;
         /**
-         * The mesh corrisponding the the pick collision
+         * The mesh corresponding the the pick collision
          */
         public pickedMesh: Nullable<AbstractMesh> = null;
         /** (See getTextureCoordinates) The barycentric U coordinate that is used when calulating the texture coordinates of the collision.*/
@@ -48,10 +48,10 @@
         public ray: Nullable<Ray> = null;
 
         /**
-         * Gets the normal corrispodning to the face the pick collided with
+         * Gets the normal correspodning to the face the pick collided with
          * @param useWorldCoordinates If the resulting normal should be relative to the world (default: false)
          * @param useVerticesNormals If the vertices normals should be used to calculate the normal instead of the normal map
-         * @returns The normal corrispodning to the face the pick collided with
+         * @returns The normal correspodning to the face the pick collided with
          */
         public getNormal(useWorldCoordinates = false, useVerticesNormals = true): Nullable<Vector3> {
             if (!this.pickedMesh || !this.pickedMesh.isVerticesDataPresent(VertexBuffer.NormalKind)) {

+ 4 - 4
src/Mesh/babylon.mesh.ts

@@ -2671,7 +2671,7 @@
         /**
          * Creates a ribbon mesh.   
          * Please consider using the same method from the MeshBuilder class instead.   
-         * The ribbon is a parametric shape :  http://doc.babylonjs.com/tutorials/Parametric_Shapes.  It has no predefined shape. Its final shape will depend on the input parameters.    
+         * The ribbon is a parametric shape :  http://doc.babylonjs.com/how_to/parametric_shapes.  It has no predefined shape. Its final shape will depend on the input parameters.    
          *
          * Please read this full tutorial to understand how to design a ribbon : http://doc.babylonjs.com/tutorials/Ribbon_Tutorial    
          * The parameter `pathArray` is a required array of paths, what are each an array of successive Vector3. The pathArray parameter depicts the ribbon geometry.    
@@ -2916,7 +2916,7 @@
 
         /**
          * Creates an extruded shape mesh.    
-         * The extrusion is a parametric shape :  http://doc.babylonjs.com/tutorials/Parametric_Shapes.  It has no predefined shape. Its final shape will depend on the input parameters.  
+         * The extrusion is a parametric shape :  http://doc.babylonjs.com/how_to/parametric_shapes.  It has no predefined shape. Its final shape will depend on the input parameters.  
          * Please consider using the same method from the MeshBuilder class instead.    
          *
          * Please read this full tutorial to understand how to design an extruded shape : http://doc.babylonjs.com/how_to/parametric_shapes#extruded-shapes     
@@ -2948,7 +2948,7 @@
         }
         /**
          * Creates an custom extruded shape mesh.    
-         * The custom extrusion is a parametric shape :  http://doc.babylonjs.com/tutorials/Parametric_Shapes.  It has no predefined shape. Its final shape will depend on the input parameters.  
+         * The custom extrusion is a parametric shape :  http://doc.babylonjs.com/how_to/parametric_shapes.  It has no predefined shape. Its final shape will depend on the input parameters.  
          * Please consider using the same method from the MeshBuilder class instead.    
          *
          * Please read this full tutorial to understand how to design a custom extruded shape : http://doc.babylonjs.com/how_to/parametric_shapes#extruded-shapes     
@@ -3113,7 +3113,7 @@
         }
         /**
          * Creates a tube mesh.    
-         * The tube is a parametric shape :  http://doc.babylonjs.com/tutorials/Parametric_Shapes.  It has no predefined shape. Its final shape will depend on the input parameters.    
+         * The tube is a parametric shape :  http://doc.babylonjs.com/how_to/parametric_shapes.  It has no predefined shape. Its final shape will depend on the input parameters.    
          * Please consider using the same method from the MeshBuilder class instead.    
          * The parameter `path` is a required array of successive Vector3. It is the curve used as the axis of the tube.        
          * The parameter `radius` (positive float, default 1) sets the tube radius size.