Unable to render using Ivisual interface

I am currently trying to use Three.js to visualise meshes in microsoft Power BI. The PowerBI template uses an interface called IVisual, I have as yet been unable to make three.js objects appear within the PowerBI window though. Code is below, any help would be much appreciated
code:

"use strict";

import "core-js/stable";

import "./../style/visual.less";

import powerbi from "powerbi-visuals-api";

import VisualConstructorOptions = powerbi.extensibility.visual.VisualConstructorOptions;

import VisualUpdateOptions = powerbi.extensibility.visual.VisualUpdateOptions;

import IVisual = powerbi.extensibility.visual.IVisual;

import EnumerateVisualObjectInstancesOptions = powerbi.EnumerateVisualObjectInstancesOptions;

import VisualObjectInstance = powerbi.VisualObjectInstance;

import DataView = powerbi.DataView;

import VisualObjectInstanceEnumerationObject = powerbi.VisualObjectInstanceEnumerationObject;

import * as THREE from 'three'

import { VisualSettings } from "./settings";

export class Visual implements IVisual {

    private target: HTMLElement;

    private updateCount: number;

    private settings: VisualSettings;

    private textNode: Text;

    private camera: THREE.PerspectiveCamera;

    private renderer: THREE.WebGLRenderer;

    private geometry: THREE.PolyhedronGeometry;

    private materials: THREE.MeshStandardMaterial;

    private mesh: THREE.Mesh;

    private light: THREE.PointLight;

    constructor(options: VisualConstructorOptions) {

        let scene = new THREE.Scene();

        console.log('Visual constructor', options);

        this.target = options.element;

        this.updateCount = 0;

        if (document) {

            const new_p: HTMLElement = document.createElement("p");

            new_p.appendChild(document.createTextNode("Updates: "));

            const new_em: HTMLElement = document.createElement("em");

            this.textNode = document.createTextNode(this.updateCount.toString());

            const new_m: HTMLElement = document.createElement("M");

            

            new_em.appendChild(this.textNode);

            new_p.appendChild(new_em);

            this.target.appendChild(new_p);

            this.camera = new THREE.PerspectiveCamera(100,window.innerWidth/window.innerHeight,0.1,1000);

            this.camera.position.z=5;

            

            this.renderer = new THREE.WebGLRenderer();

            this.renderer.setSize(window.innerWidth,window.innerHeight);

            this.renderer.setClearColor('#D13D1D'); 

            

            window.addEventListener('resize',()=>{

              this.renderer.setSize(window.innerWidth,window.innerHeight);

              this.camera.aspect = window.innerWidth/window.innerHeight;

              this.camera.updateProjectMatrix();

            })

            this.geometry = new THREE.SphereGeometry(1.5,10,10);         

         

            this.materials = new THREE.MeshStandardMaterial({color: 0xFFFFFF})

                        

            this.mesh = new THREE.Mesh(this.geometry,this.materials);

            this.mesh.material.color.set("green");

         

            this.light = new THREE.PointLight(0xFFFFFF,1,500);

            this.light.position.set(10,0,40);

         

            scene.add(this.light);

            

            scene.add(this.mesh);

            document.appendChild(new_m);

            this.renderer.render(scene,this.camera);

            this.target.append(this.renderer);

         

        }

    }

    public update(options: VisualUpdateOptions) {

       this.settings = Visual.parseSettings(options && options.dataViews && options.dataViews[0]);

        console.log('Visual update', options);

        if (this.textNode) {

       this.textNode.textContent = (this.updateCount++).toString();

                        }

        }

    private static parseSettings(dataView: DataView): VisualSettings {

        return <VisualSettings>VisualSettings.parse(dataView);

    }

    /**

     * This function gets called for each of the objects defined in the capabilities files and allows you to select which of the

     * objects and properties you want to expose to the users in the property pane.

     *

     */

    public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstance[] | VisualObjectInstanceEnumerationObject {

        return VisualSettings.enumerateObjectInstances(this.settings || VisualSettings.getDefault(), options);

    }

}

This line looks wrong. It should be:

this.target.append(this.renderer.domElement);

Also using appendChild() seems more robust to me.

Ok have changed the method and cleaned up the code. Still don’t get the three mesh coming through but if it does seem to come up with a blank page as opposed to the textnode elements. Not sure if this means that it is creating a blank canvas over the top of the textnodes or just not rendering the canvas at all.
code:

“use strict”;

import “core-js/stable”;

import “./…/style/visual.less”;

import powerbi from “powerbi-visuals-api”;

import VisualConstructorOptions = powerbi.extensibility.visual.VisualConstructorOptions;

import VisualUpdateOptions = powerbi.extensibility.visual.VisualUpdateOptions;

import IVisual = powerbi.extensibility.visual.IVisual;

import EnumerateVisualObjectInstancesOptions = powerbi.EnumerateVisualObjectInstancesOptions;

import VisualObjectInstance = powerbi.VisualObjectInstance;

import DataView = powerbi.DataView;

import VisualObjectInstanceEnumerationObject = powerbi.VisualObjectInstanceEnumerationObject;

import * as THREE from ‘three’

import { VisualSettings } from “./settings”;

export class Visual implements IVisual {

private target: HTMLElement;

private updateCount: number;

private settings: VisualSettings;

private textNode: Text;

private scene: THREE.Scene;

private camera: THREE.PerspectiveCamera;

private renderer: THREE.WebGLRenderer;

private geometry: THREE.BoxGeometry;

private materials: THREE.MeshBasicMaterial;

private mesh: THREE.Mesh;

constructor(options: VisualConstructorOptions) {

    console.log('Visual constructor', options);

    this.target = options.element;

    this.updateCount = 0;

    this.scene = new THREE.Scene();

    this.camera = new THREE.PerspectiveCamera(100,200,0.1,1000);

    this.renderer = new THREE.WebGLRenderer();

    this.renderer.setSize(window.innerWidth,window.innerHeight);

       

    if (document) {

        this.target.appendChild(this.renderer.domElement)

        const new_p: HTMLElement = document.createElement("p");

        new_p.appendChild(document.createTextNode("Update counting:"));

        const new_em: HTMLElement = document.createElement("em");

        this.textNode = document.createTextNode(this.updateCount.toString());

        new_em.appendChild(this.textNode);

        new_p.appendChild(new_em);

        this.target.appendChild(new_p); 

        

        this.geometry = new THREE.BoxGeometry(1,1,1);

        this.materials = new THREE.MeshBasicMaterial({color: 0x00ff00});

        this.mesh = new THREE.Mesh(this.geometry, this.materials);

        this.scene.add(this.mesh);

        this.camera.position.z=5;

    }

}

public update(options: VisualUpdateOptions) {

    this.settings = Visual.parseSettings(options && options.dataViews && options.dataViews[0]);

    console.log('Visual update', options);

    if (this.textNode) {

        this.textNode.textContent = (this.updateCount++).toString();

    }

}

private static parseSettings(dataView: DataView): VisualSettings {

    return <VisualSettings>VisualSettings.parse(dataView);

}

/**

 * This function gets called for each of the objects defined in the capabilities files and allows you to select which of the

 * objects and properties you want to expose to the users in the property pane.

 *

 */

public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstance[] | VisualObjectInstanceEnumerationObject {

    return VisualSettings.enumerateObjectInstances(this.settings || VisualSettings.getDefault(), options);

}

}

Turned out to be that the latest version of Three.JS doesn’t run with a powerBI visual, I had to rollback to an earlier version and it appeared.

Hi @Julian_Venczel - I’m also trying to get Three.JS working within the Power BI ecosystem.

You mentioned in your last message that the latest version of Three.JS doesn’t run with a Power BI visual - can you please advise which version you used?

Unfortunately I don’t recall which version I had to revert to, it was at another job over 2 years ago. I would suggest just uninstalling and reinstalling until it works. I think it was a version released around 2018 if that helps.

Hi @Julian_Venczel - thank you very much for your quick reply!

Not a problem - in the interim I’ve actually managed to do a bit of an end-run and use the Microsoft Power BI custom React visual tutorial to implement a react-three-fiber custom Visual (albeit so far just getting a cube to display in the viewport).

This feels a bit like a hacky house of cards built on sand and held together with stickytape, but it may allow me to move forward with the actual visual development.

If I need to revert to pure Three.JS your suggestion of starting around 2018 versions will be invaluable, thanks very much!