I have a requirement where I have to create a custom widget using Amcharts. But I am facing problem that before the libraries are loaded am4core function is called.
HTML Code
<com-sap-sample-helloworld5></com-sap-sample-helloworld5>
Js code
(function () {
const amchartscorejs = "https://www.amcharts.com/lib/4/core.js";
const amchartschartsjs = "https://www.amcharts.com/lib/4/charts.js";
const amchartsanimatedjs = "https://www.amcharts.com/lib/4/themes/animated.js";
const vennchartjs = "https://cdn.amcharts.com/lib/4/plugins/venn.js";
async function LoadLibs() {
console.log("LoadLibs");
try {
await loadScript(amchartscorejs);
await loadScript(amchartschartsjs);
await loadScript(amchartsanimatedjs);
await loadScript(vennchartjs);
} catch (e) {
alert(e);
} finally {
that._firstConnection = 1;
}
}
LoadLibs();
function loadScript(src) {
console.log("LoadScript");
return new Promise(function (resolve, reject) {
let script = document.createElement("script");
script.src = src;
script.onload = () => {
console.log("Load: " + src);
resolve(script);
};
script.onerror = () => reject(new Error(`Script load error for ${src}`));
document.head.appendChild(script);
});
}
let template = document.createElement("template");
template.innerHTML = `<div id="chartdiv" width="100%" height="500px"></div>`;
customElements.define(
"com-sap-sample-helloworld5",
class HelloWorld extends HTMLElement {
constructor() {
super();
let shadowRoot = this.attachShadow({
mode: "open",
});
shadowRoot.appendChild(template.content.cloneNode(true));
this._firstConnection = false;
this.addEventListener("click", (event) => {
var event = new Event("onClick");
this.dispatchEvent(event);
});
}
//Fired when the widget is added to the html DOM of the page
connectedCallback() {
this._firstConnection = true;
this.redraw();
}
//Fired when the widget is removed from the html DOM of the page (e.g. by hide)
disconnectedCallback() {}
//When the custom widget is updated, the Custom Widget SDK framework executes this function first
onCustomWidgetBeforeUpdate(oChangedProperties) {}
//When the custom widget is updated, the Custom Widget SDK framework executes this function after the update
onCustomWidgetAfterUpdate(oChangedProperties) {
if (this._firstConnection) {
this.redraw();
}
}
//When the custom widget is removed from the canvas or the analytic application is closed
onCustomWidgetDestroy() {}
//When the custom widget is resized on the canvas, the Custom Widget SDK framework executes the following JavaScript function call on the custom widget
// Commented out by default
/*
onCustomWidgetResize(width, height){
}
*/
get chartType() {
return this.chartTypeValue;
}
set chartType(value) {
this.chartTypeValue = value;
}
redraw() {
console.log("redraw function");
// Themes begin
am4core.useTheme(am4themes_animated);
// Themes end
var data = [
{ name: "A", value: 10 },
{
name: "B",
value: 10,
},
{
name: "C",
value: 10,
},
{
name: "X",
value: 2,
sets: ["A", "B"],
},
{
name: "Y",
value: 2,
sets: ["A", "C"],
},
{
name: "Z",
value: 2,
sets: ["B", "C"],
},
{
name: "Q",
value: 1,
sets: ["A", "B", "C"],
},
];
var chart = am4core.create("chartdiv", am4plugins_venn.VennDiagram);
var series = chart.series.push(new am4plugins_venn.VennSeries());
series.dataFields.category = "name";
series.dataFields.value = "value";
series.dataFields.intersections = "sets";
series.data = data;
chart.legend = new am4charts.Legend();
chart.legend.marginTop = 40;
}
}
);
})();
Please tell what changes should I do so that first amCharts libraries are loaded and then redraw() function is called.
You can also check the logs in jsfiddle to understand what problem I am facing.
Thanks in advance.
I started with Promise.all, but ended up on an Amcharts Issue that Amcharts needs to be loaded in sequence.
ES7 awaits will do the job
You were halfway there, but stopped the async part and continued with sync loadLibs
, it looks like you then struggled trying to flag the element when loading was done.
Here libraries load in sequence, with a generic loadScripts function,
ready for multiple custom elements depending on AmCharts, script will only load once:
(function() {
function log() {
let args = [...arguments];
//console.log(`%c ${args.shift()} `, "background:lightgreen", ...args); //Chrome!
document.body.append(args.join` `,document.createElement('BR'));
}
log("start IIFE script");
async function loadScripts() {
const load = (src) => new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = `https://www.amcharts.com/lib/4/${src}.js`;
if (document.querySelector(`[src="${script.src}"]`)) resolve();
log("load", script.src)
script.onload = resolve;
//script.onerror = () => reject(new Error(`Script load error for ${src}`));
document.head.append(script)
});
await load("core"); // must be loaded first
await load("charts");
await load("themes/animated");
await load("plugins/venn");
return " / return (not even required)";
}
customElements.define('my-element', class extends HTMLElement {
connectedCallback() {
this.attachShadow({mode: "open"}).innerHTML = `<div>Executing:</div>`;
log('connectedCallback');
loadScripts().then(result => {
log('done loading', result);
});
}
});
})();
<my-element></my-element>
You could move everything inside the (now async
marked) connectedCallback
async connectedCallback() {
const load = (src) => new Promise((resolve, reject) => {
let script = document.createElement('script');
script.src = `https://www.amcharts.com/lib/4/${src}.js`;
//if (document.querySelector(`[src="${script.src}"]`)) resolve();
script.onload = resolve;
this.append(script); // fine, doesn't really matter where SCRIPT is injected
});
await load("core");
await load("charts");
await load("themes/animated");
await load("plugins/venn");
// Loaded all
}
If the connectedCallback runs multiple times because you move DOM nodes around you need that check for already loaded script