Babylon.js - Charting demo source

Back to demo

main.ts

import { runDemo } from "../shared/demoRunner";
import { createChartingTestScene } from "./scene";

runDemo({ createScene: createChartingTestScene });

scene.ts

import type { Engine } from "@babylonjs/core/Engines/engine";
import { Scene } from "@babylonjs/core/scene";
import { ArcRotateCamera } from "@babylonjs/core/Cameras/arcRotateCamera";
import { DirectionalLight } from "@babylonjs/core/Lights/directionalLight";
import { ShadowGenerator } from "@babylonjs/core/Lights/Shadows/shadowGenerator";
import { StandardMaterial } from "@babylonjs/core/Materials/standardMaterial";
import { DynamicTexture } from "@babylonjs/core/Materials/Textures/dynamicTexture";
import { Color3 } from "@babylonjs/core/Maths/math.color";
import { Vector3 } from "@babylonjs/core/Maths/math.vector";
import { Animation } from "@babylonjs/core/Animations/animation";
import { CreatePlane } from "@babylonjs/core/Meshes/Builders/planeBuilder";
import { CreateGround } from "@babylonjs/core/Meshes/Builders/groundBuilder";
import { CreateBox } from "@babylonjs/core/Meshes/Builders/boxBuilder";
import "@babylonjs/core/Lights/Shadows/shadowGeneratorSceneComponent";
import "@babylonjs/core/Animations/animatable";

interface IChartSeries {
    label: string;
    value: number;
    color: Color3;
}

export function createChartingTestScene(engine: Engine, canvas: HTMLCanvasElement): Scene {
    const scene = new Scene(engine);
    const light = new DirectionalLight("dir01", new Vector3(0, -0.5, 1.0), scene);
    const camera = new ArcRotateCamera("Camera", 0, 0, 10, Vector3.Zero(), scene);
    camera.setPosition(new Vector3(20, 70, -100));
    camera.attachControl(canvas, true);
    light.position = new Vector3(0, 25, -50);

    const scale = 0.6;

    const browsersSeries: IChartSeries[] = [
        { label: "IE", value: 32, color: new Color3(0, 0, 1) },
        { label: "Chrome", value: 28, color: new Color3(1, 0, 0) },
        { label: "Firefox", value: 16, color: new Color3(1, 0, 1) },
        { label: "Opera", value: 14, color: new Color3(1, 1, 0) },
        { label: "Safari", value: 10, color: new Color3(0, 1, 1) },
    ];

    const playgroundSize = 100;

    // Background
    const background = CreatePlane("background", { size: playgroundSize }, scene);
    const backgroundMaterial = new StandardMaterial("background", scene);
    background.material = backgroundMaterial;
    background.scaling.y = 0.5;
    background.position.z = playgroundSize / 2 - 0.5;
    background.position.y = playgroundSize / 4;
    background.receiveShadows = true;
    const backgroundTexture = new DynamicTexture("dynamic texture", 512, scene, true);
    backgroundMaterial.diffuseTexture = backgroundTexture;
    backgroundMaterial.specularColor = new Color3(0, 0, 0);
    backgroundMaterial.backFaceCulling = false;

    backgroundTexture.drawText("Eternalcoding", null, 80, "bold 70px Segoe UI", "white", "#555555");
    backgroundTexture.drawText("- browsers statistics -", null, 250, "35px Segoe UI", "white", null);

    // Ground
    const ground = CreateGround("ground", { width: playgroundSize, height: playgroundSize, subdivisions: 1 }, scene);
    const groundMaterial = new StandardMaterial("ground", scene);
    groundMaterial.diffuseColor = new Color3(0.5, 0.5, 0.5);
    groundMaterial.specularColor = new Color3(0, 0, 0);
    ground.material = groundMaterial;
    ground.receiveShadows = true;
    ground.position.y = -0.1;

    const shadowGenerator = new ShadowGenerator(1024, light);
    shadowGenerator.usePoissonSampling = true;

    const createSeries = function (series: IChartSeries[]) {
        const margin = 2;
        const offset = playgroundSize / series.length - margin;
        let x = -playgroundSize / 2 + offset / 2;

        for (let index = 0; index < series.length; index++) {
            const data = series[index];

            const bar = CreateBox(data.label, { size: 1.0 }, scene);
            bar.scaling = new Vector3(offset / 2.0, 0, offset / 2.0);
            bar.position.x = x;
            bar.position.y = 0;

            let animation = new Animation("anim", "scaling", 30, Animation.ANIMATIONTYPE_VECTOR3);
            animation.setKeys([
                { frame: 0, value: new Vector3(offset / 2.0, 0, offset / 2.0) },
                { frame: 100, value: new Vector3(offset / 2.0, data.value * scale, offset / 2.0) },
            ]);
            bar.animations.push(animation);

            animation = new Animation("anim2", "position.y", 30, Animation.ANIMATIONTYPE_FLOAT);
            animation.setKeys([
                { frame: 0, value: 0 },
                { frame: 100, value: (data.value * scale) / 2 },
            ]);
            bar.animations.push(animation);
            scene.beginAnimation(bar, 0, 100, false, 2.0);

            const barMaterial = new StandardMaterial(data.label + "mat", scene);
            barMaterial.diffuseColor = data.color;
            barMaterial.emissiveColor = data.color.scale(0.3);
            barMaterial.specularColor = new Color3(0, 0, 0);
            bar.material = barMaterial;

            shadowGenerator.getShadowMap()!.renderList!.push(bar);

            const barLegend = CreateGround(
                data.label + "Legend",
                { width: playgroundSize / 2, height: offset * 2, subdivisions: 1 },
                scene
            );
            barLegend.position.x = x;
            barLegend.position.z = -playgroundSize / 4;
            barLegend.rotation.y = Math.PI / 2;

            const barLegendMaterial = new StandardMaterial(data.label + "LegendMat", scene);
            const barLegendTexture = new DynamicTexture("dynamic texture", 512, scene, true);
            barLegendTexture.hasAlpha = true;
            barLegendMaterial.diffuseTexture = barLegendTexture;
            barLegendMaterial.emissiveColor = new Color3(0.4, 0.4, 0.4);
            barLegend.material = barLegendMaterial;

            const size = barLegendTexture.getSize();
            barLegendTexture.drawText(
                data.label + " (" + data.value + "%)",
                80,
                size.height / 2 + 30,
                "bold 50px Segoe UI",
                "white",
                "transparent"
            );

            x += offset + margin;
        }
    };

    createSeries(browsersSeries);

    camera.lowerAlphaLimit = Math.PI;
    camera.upperAlphaLimit = 2 * Math.PI;
    camera.lowerBetaLimit = 0.1;
    camera.upperBetaLimit = (Math.PI / 2) * 0.99;
    camera.lowerRadiusLimit = 5;
    camera.upperRadiusLimit = 150;

    return scene;
}