I am destroying the chart but when it's not rendered I get error.
Is there a way to check if chart is rendered, then destroy it?
if(chart)
chart.destroy()
Each time i destroy an object that does not exist i get TypeError: Failed to execute 'removeChild' on 'Node': parameter 1 is not of type 'Node'.
Also i need to render it again if it's not rendered, i won't render it again and again. I need that check
The linked documentation states that render() returns a promise once the chart is drawn to the page.
The code however seems to return that promise immediately (which makes sense) and resolves that promise, when the chart was drawn.
As far as I can see, it should be sufficient to set and keep a state-flag after the promise is resolved like so:
let chart = new ApexCharts(el, options);
chart.render().then(() => chart.ohYeahThisChartHasBeenRendered = true);
/* ... */
if (chart.ohYeahThisChartHasBeenRendered) {
chart.destroy();
}
Update after comment
Yes this works! I made this runnable example for you (typically this is the duty of the person asking the question ;) ) Press the button and inspect the log):
<html>
<head>
<title>chart test</title>
<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
<head>
<body>
<div id="chart"></div>
<script>
let options = {
chart: { type: 'line' },
series: [{ name: 'sales', data: [30,40,35,50,49,60,70,91,125] }],
xaxis: { categories: [1991,1992,1993,1994,1995,1996,1997, 1998,1999]}
},
chart = new ApexCharts(document.querySelector("#chart"), options),
logChart = () => console.log(chart),
destroyChart = () => {
if (chart.ohYeahThisChartHasBeenRendered) {
chart.destroy();
chart.ohYeahThisChartHasBeenRendered = false;
}
};
chart.render().then(() => chart.ohYeahThisChartHasBeenRendered = true);
</script>
<button onclick="logChart()">Log chart</button>
<button onclick="destroyChart()">Destroy chart</button>
</body>
</html>
I suspect that you tried something like this to check for the flag:
chart.render().then(() => chart.ohYeahThisChartHasBeenRendered = true);
console.log(chart.ohYeahThisChartHasBeenRendered);
It will not do what you expect because the promise is not resolved yet.
Update after another comment
As pointed out by a comment there is a related known issue with apexcharts:
https://github.com/apexcharts/apexcharts.js/pull/415
Even though this question asks to "check if the chart is rendered", the code suggests that they actually want to "check if the chart exists". I would also like to check if a chart exists before rendering it, and I suspect this is the more common issue.
I'm not sure about the accepted answer here. It seems that this answer always creates a new chart, hence there is no need to check if the chart exists.
I worked on this for some time - got no help from documentations- and finally discovered the Apex object. Check out Apex._chartInstances: this field is undefined before any charts render, and as they render, they store references here. After at least one rendering, the length of this field is equal to the number of existing charts.
Check if any charts have ever existed: (Apex._chartInstances === undefined)
Check if any charts currently exist: (Apex._chartInstances.length > 0)
Access the id's of existing charts: (Apex._chartInstances[0].id)
These bits were enough to make it work for my case. Hope this helps somebody else.
I was able to use the beforeMount and mounted events to check if the chart was rendered or not. For some reason, I am not able to catch the error that ApexChart throws.
My logic:
in beforeMount, make a delayed call to error handler and set error flag to true.
in mounted, set error flag to false. When the error handler runs, if the error flag is false, you skip othwe
{
...
chart: {
type: "scatter",
height: height,
events: {
beforeMount: function (chartContext, config) {
setTimeout(() => {
PAGE_DATA.ShowChartError = true;
showChartErrorMessage($(chartContext.el));
}, 1000);
},
mounted: function (chartContext, config) {
PAGE_DATA.ShowChartError = false;
},
},
},
...
}
Error handler,
function showChartErrorMessage($el) {
if (PAGE_DATA.ShowChartError) {
// show error msg
$el.siblings(".error-help-container").removeClass("hidden");
// hide chart div
$el.hide();
}
PAGE_DATA.ShowChartError = false;
}
I tried all sorts of suggestions on destroying a rendered chart and nothing seemed to work. Finally I tried this and it worked.
Before you render it, put a destroy in a try catch with no error, basically an on error resume next and then render it.
var chart22 = new ApexCharts(document.querySelector("#row2-2"), options1);
try{
chart22.destroy();
}
catch{
}
chart22 = new ApexCharts(document.querySelector("#row2-2"), options1);
chart22.render();
Related
I want to read out the current color of my tsParticles upon a button press and process it further. With particlesJS I was able to achiev this and I wanted to switch to tsParticles. Sadly now I only get an error message stating "Uncaught TypeError: Cannot read properties of undefined (reading 'particles')".
The (simplified) code I'm using is:
tsParticles.load("tsparticles", {
particles: {
color: {
value: "#ff0000",
animation: {
enable: false,
speed: 20,
sync: true
}
}}})
thirdButton.addEventListener( "click", function() {
console.log("Button clicked");
let currentColor= tsParticles.options.particles.color.value;
console.log(currentColor);
particle_colorchange(currentColor)
});
Both are initialized and load correctly and work. The error comes from "tsParticles.options.particles.color.value;"
I was expecting the color (hex value) to be displayed / further processed.
I tried various ways to access the color.
ALso I tried to narrow down where exactly the error occurs by using
if (typeof tsparticles !== 'undefined') {
let currentColor = tsparticles.options.particles.color.value;
console.log(currentColor);
particle_colorchange(currentColor)
} else {
console.error("tsparticles object is not defined");
}
and going step by step for the typeof (menaing next is: typeof tsparticles.options. Sadly I could find an answer with that.
Any help is much appreciated.
You can access the particle options via the container object and then get the particle color from the options, like so:
tsParticles.load('tsparticles', { /* particle options */ }).then(function (container) {
// you can now get the particle color via accessing the options of the container
let particle_color = container.options.particles.color;
console.log(particle_color);
});
You can read more about this in the "Particles Manager Object" section within this article about TsParticles v2.
Hope that helps!
You can read out the current color with for example a button press like this. Inside the button eventlistener function you add
let currentColor= tsParticles.domItem(0).particles.container.options.particles.color.value ;
to read out the particle color and:
let currentColor= tsParticles.domItem(0).particles.container.options.particles.links.color.value ;
to read out the link color.
and assign a new color like this:
tsParticles.domItem(0).particles.container.options.particles.color.value = new_color; tsParticles.domItem(0).particles.container.options.particles.links.color.value=new_color; tsParticles.domItem(0).refresh();
I have not figured out how to update the assignment for onhover links & particles but there should be a way.
I have this structure inside Vue DIV:
<div class="row">
<div class="well chart-container">
<div id="chart" v-if="chartShown"></div>
<h1 v-if="textShown">{{staticPropertyValue}}</h1>
</div>
</div>
In my application I'd like to be able to display chart div OR h1 tag. Here's a part of my javaScript:
app.textShown = false;
app.chartShown = true;
if (data.length == 0) {
MG.data_graphic({
title: "Missing Data",
description: "",
error: 'No data',
chart_type: 'missing-data',
missing_text: 'There is no data',
target: '#chart',
full_width: true,
height: 600
});
return;
};
if (data.length == 1) {
app.staticPropertyValue = data[0].value;
app.chartShown = false;
app.textShown = true;
return;
}
console.log('DRAWING GRAPH');
console.log(document.getElementById('chart'));
MG.data_graphic({
title: "Fetched data",
description: "",
data: data,
full_width: true,
height: 600,
target: '#chart',
x_accessor: 'time',
y_accessor: 'value'
});
So, depending on data.length property, the chart is shown or h1 tag is shown. The problem appears, when I first time call above code and display h1 tag (because data.length == 1) and then next time I call it with date.length > 1 (chart should appear). I get error:
The specified target element "#chart" could not be found in the page.
The chart will not be rendered.
It is from the library that I'm using for drawing charts - metricsgraphics.js.
So I console logged the result of
document.getElementById('chart')
and it was null. So it means that although i switch chartShown to true, it's not done fast enough. How can I fix this?
I also tried using v-show instead of v-if - didn't work well - I had some errors about the width of some elements being NaN.
You should run this piece of code in the mounted() callback of your component.
To me, it looks like you're running it when the script has finished loading, which is not necessarily when the DOM is fully built and definitely not when Vue has finished rendering its components.
Working with v-if while using an external library is not a good idea anyway, you're much better off initializing your view in the mounted() callback and then watching your chartShown variable like so:
{
...,
watch: {
chartShown(nv) {
if (nv) {
// setup chart
} else {
// remove chart
}
}
},
...
}
I want to make a function which send dynamically all visible series name in a Highchart instance to a PHP function.
For example, in this chart, I want to get this array : [Salle, PR].
If I click on Internet, the serie become visible and I want to get [Salle, Internet, PR].
To do this, I tried to use legendItemClick event and make a function that check if each serie is visible to add it to an array but I can't figure out how to use the visible option to do this.
Do you have an idea ?
As of now, I don't have much code to share :
plotOptions: {
series: {
events: {
legendItemClick: function(){
}
}
}
}
If you retain the pointer to your chart like this:
var ch = Highcharts.chart(_chart_data_);
Then later you can access the whole chart structure. What you will be interested in is the series array.
ch.series[]
It contains array of all your series. Series with visible attribute set to true are the ones that currently displayed. So,it might be something like this:
var ch = Highcharts.chart(...
plotOptions: {
series: {
events: {
legendItemClick: function(){
ch.series.forEach(function(sr){
if(sr.visible){
console.log(sr.name, "visible!");
}
});
}
}
}
}
...);
However, there is a catch with your approach, that on actual legend click your current action for the legend is not yet processed.so the output you will see is the output for the previous state, before current click.
So for that reason you may try to use setTimeout to get your listing after the event is applied:
events: {
legendItemClick: function(){
setTimeout(
function(){
ch.series.forEach(
function(sr){
if(sr.visible){
console.log(sr.name, "visible!");
}
}
)
},20);
}
}
Try this and check the console log: http://jsfiddle.net/op8142z0/
I'm trying to create my own lightbox script where I can pass the variables (title, description, itemtype, itemid, etc.) in clean formatting like this (inspired by fancybox):
myFunction({
title: "My title",
description: "My description"
});
Clicking on a certain element prepends some HTML to a div with jQuery.
I have adapted a piece of code I found on Stackoverflow and "kind of" understand the code. The top function has not been changed and worked before I edited the bottom code, to that I added click(function() { } because in the example the code was executed on pageload.
However, when I click my H1 element the firebug console tells me ReferenceError: popup is not defined
This is my Javascript:
$(document).ready(function() {
(function ($) {
$.fn.popup = function (options) {
var settings = $.extend({
title: function (someData) {
return someData;
},
description: function (someData) {
return someData;
},
}, options);
$("#content").prepend(
"<div style=\"position:fixed;top:0;left:0;width:100%;height:100%;background:#FFFFFF;\">\
<h1>"+ settings.title +"</h1>\
<p>" + settings.description +"</p>\
</div>"
);
};
}(jQuery));
$(".openbox1").click(function() {
popup({
title: "Title 1",
description: "Description 1"
});
}));
$(".openbox2").click(function() {
popup({
title: "Title 2",
description: "Description 2"
});
}));
});
This is my HTML
<div id="content">
<h1 class="openbox1">open box 1</h1>
<h1 class="openbox2">open box 2</h1>
</div>
A. Wolff commented that I need to execute the function like this:
$(".openbox1").click(function() {
$(this).popup({
...
});
});
This fixed it, thanks!
First off, what you did, and I hope this helps:
// This, of course is same as "document.onload"
// Don't confuse it with "window.onload"
// wich will wait till WHOLE dom is loaded to run any script
$(document).ready(function() {
(function ($) {
// This is, in essence, the start of a jQuery plugin
// This is often referred to as the "quick and dirty setup"
// as it's a direct call to add a method to jQuery's
// element object. Meaning it can be recalled as
// $(element).popup().
// This should not be confused with $.popup = function
// which would just add a method to jQuery's core object
$.fn.popup = function (options) {
var settings = $.extend({
...
}(jQuery));
$(".openbox1").click(function() {
// here is where your issue comes in
// as previously noted, you did not create a
// method named "popup".
// you added a method to jQuery's Element Object
// called "popup".
// This is why `$(this).popup` works and
// plain `popup` does not.
// You're inside an "event" asigned to any element
// having class name `openbox1`. Thus, any call
// in here to `this`, will reference that element
popup({
Secondly, a different example of how to write it. I won't say better because, even if I say my way is better, it wouldn't make your "corrected" way wrong. In Javascript, as the old saying goes, There's more than one way to skin a cat.
My Example:
// Notice I'm adding this plugin BEFORE the document load.
// This means, you could easily add this to a file and load it
// in script tags like any other Javascript,
// as long as it's loaded AFTER jquery.
(function($) {
// this ensures that your plugin name is available and not previously added to jQuery library
if (!$.popup) {
// this also provides us "variable scope" within to work in
// here begin adding the plugin to jQuery
// I started with $.extend, so it can be added to the jQuery library and used in traditional format
// $.popup('element selector', { options })
// as well as the element.action format we'll add later
// $.(element selector).popup({ options })
// This should help give you a good idea of the whole of what all is going on
$.extend({
popup: function() {
var ele = arguments[0], // this is our jQuery element
args = Array.prototype.slice.call(arguments, 1); // this gets the rest of the arguments
// this next step is useful if you make the traditional call `$.popup(this, { options })`
if (!(ele instanceof jQuery)) ele = $(ele);
// now we have total control! Bwahahha!
// Fun aside, here is where it's good to check if you've already asigned this plugin
// if not, then make some "marker", so you can recall the element plugin and comment an
// action instead of reinitializing it
if (!ele.data('popup')) $.popup.init(ele, args);
else {
// at this point, you would know the element already has this plugin initialized
// so here you could change an initial options
// like how with jQueryUI, you might would call:
// $(element).popup('option', 'optionName', value)
}
return ele;
}
});
// here is where we add the $(element selector).popup method
// this simply adds the method to the element object
// If you don't fully understand what's going on inside (as I explain below),
// just know that it's some "fancy footwork" to pass the method onto our initial
// method creation, $.popup
$.fn.extend({
popup: function(/*no need for parameter names here as arguments are evaluated inside and passed on to initial method*/) {
// set this element as first argument to fit with initial plugin method
var args = [$(this)];
// if there are arguments/params/options/commands too be set, add them
if (arguments.length) for (x in arguments) args.push(arguments[x]);
// pass through jquery and our arguments, end result provides same arguments as if the call was:
// $.popup($(element), options)
return $.popup.apply($, args);
}
});
// This next part is not seen in many plugins but useful depending on what you're creating
$.popup.init = function(ele, opt) {
// here is where we'll handle the "heavy work" of establishing a plugin on this element
// Start with setting the options for this plugin.
// This means extending the default options to use any passed in options
// In the most simple of cases, options are passed in as an Oject.
// However, that's not always the case, thus the reason for this being
// a continued array of our arguments from earlier.
// We'll stick with the simplest case for now, your case, that the only options are an
// Object that was passed in.
// using the extend method, with true, with a blank object,
// allows us to added the new options "on top" of the default ones, without changing the default ones
// oh and the "true" part just tells extend to "dig deep" basically (multideminsional)
if (opt && typeof opt[0] == 'object') opt = $.extend(true, {}, $.popup.defaults, opt[0]);
var par = opt.parent instanceof jQuery ? opt.parent : $('body'),
tit = opt.title,
des = opt.description,
// this last one will be the wrapper element we put everything in
// you have this in yours, but it's written in a very long way
// this is jQuery simplified
wrap = $('<div />', { style: 'position:fixed;top:0;left:0;width:100%;height:100%;background:#FFFFFF;' }),
// much like the previous element, cept this is where our title goes
head = $('<h1 />', { text: tit }).appendTo(wrap),
content = $('<p />', { text: des }).appendTo(wrap);
$(par).append(wrap);
// finally, add our marker i mentioned earlier
ele.data('popup', opt);
// just adding the following cause i noticed there is no close
// fyi, i would change this plugin a little and make an actial "open" command, but that's another tutorial
var closer = $('<span />', { text: '[x]', style: 'cursor:pointer;position:absolute;bottom:1em;right:1em;' });
wrap.append(closer);
closer.click(function(e) { ele.data('popup', false); wrap.remove(); });
};
$.popup.defaults = { // establish base properties here that can be over-written via .props, but their values should never truly change
'parent': undefined, // added this to keep it dynamic, instead of always looking for an element ID'd as content
title: '',
description: ''
};
}
})(jQuery);
// the following is basically jQuery shorthand for document.ready
$(function() {
// i think you get the rest
$(".openbox1").on('click', function(e) {
$(this).popup({
title: "Title 1",
description: "Description 1",
parent: $("#content")
});
})
$(".openbox2").on('click', function(e) {
$(this).popup({
title: "Title 2",
description: "Description 2",
parent: $("#content")
});
})
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div id="content">
<h1 class="openbox1">open box 1</h1>
<h1 class="openbox2">open box 2</h1>
</div>
on a tab panel I create a tab for each year I have in a database (in this case the database contains at the moment only 3 years: 2012, 2013 ans 2014) and finally I set as active tab the current year (2013). In the controller I do the following:
var tp= this.getTpOverview();
this.getPlannedYearsStore().load({
callback: function(records) {
for (i=0; i< records.length; i++){
var year = records[i].data.year;
var tab = tp.add({
title: year,
year: year,
layout:'fit',
listeners: {
activate: function() {
var tbOverview = Ext.getCmp('tabOverview-'+ this.year);
if (!tbOverview) {
var gridOverview = Ext.create('WLPT.view.CPAssMonthActHours', {
id: 'tabOverview-' + this.year,
year: this.year,
xtype: 'cpassmonthacthoursview',
autoScroll: true
});
this.add(gridOverview);
} else {
selectedYear = this.year;
tbOverview.getStore().load({
params : {
wrk_year: selectedYear
}
});
}
}
}
});
if (currentYear == parseInt(records[i].data.year)) {
tab2Activate = tab;
}
}
tp.setActiveTab(tab2Activate);
}
});
When I run the application this seams to work fine.
I forgot to say that each tab contains a grid panel with a check column (Checkbox model) and for each item (row) a cell editor is setted on selected cells.
The active tab (2013) works fine. I can check the checkboxes to perfom a sum of the selected items. Indeed, the cell editor works fine.
The problem appears when I change the tab. The corresponding grid comes with the checkbox column. But on the javascript console appears the following error message:
Uncaught TypeError: Cannot call method 'setWidth' of undefined ext-all-debug.js:95689
Ext.define.onColumnResize ext-all-debug.js:95689
Ext.define.onColumnResize ext-all-debug.js:101362
Ext.util.Event.Ext.extend.fire ext-all-debug.js:8896
Ext.define.continueFireEvent ext-all-debug.js:9102
Ext.define.fireEvent ext-all-debug.js:9080
Ext.override.fireEvent ext-all-debug.js:51104
Ext.define.onHeaderResize ext-all-debug.js:97344
Ext.define.afterComponentLayout ext-all-debug.js:98063
Ext.define.notifyOwner ext-all-debug.js:28381
Ext.define.callLayout ext-all-debug.js:103511
Ext.define.flushLayouts ext-all-debug.js:103680
Ext.define.runComplete ext-all-debug.js:104194
callOverrideParent ext-all-debug.js:54
Base.implement.callParent ext-all-debug.js:3813
Ext.override.runComplete ext-all-debug.js:21234
Ext.define.run ext-all-debug.js:104175
Ext.define.statics.flushLayouts ext-all-debug.js:21238
Ext.define.statics.resumeLayouts ext-all-debug.js:21246
Ext.resumeLayouts ext-all-debug.js:23343
Ext.define.setActiveTab ext-all-debug.js:111589
Ext.define.onClick ext-all-debug.js:111357
(anonymous function)
Ext.apply.createListenerWrap.wrap
Despite that, the grid is shown correctly. But, when I select a item the javascript console shows the following error message:
Uncaught TypeError: Cannot call method 'up' of null ext-all-debug.js:99591
Ext.define.onRowFocus ext-all-debug.js:99591
Ext.util.Event.Ext.extend.fire ext-all-debug.js:8896
Ext.define.continueFireEvent ext-all-debug.js:9102
Ext.define.fireEvent ext-all-debug.js:9080
Ext.override.fireEvent ext-all-debug.js:51104
Ext.define.focusRow ext-all-debug.js:92462
Ext.define.onRowFocus ext-all-debug.js:92423
Ext.define.onLastFocusChanged ext-all-debug.js:109495
Ext.define.setLastFocused ext-all-debug.js:83855
Ext.define.doMultiSelect ext-all-debug.js:83761
Ext.define.doSelect ext-all-debug.js:83721
Ext.define.selectWithEvent ext-all-debug.js:83623
Ext.define.onRowMouseDown ext-all-debug.js:109750
Ext.util.Event.Ext.extend.fire ext-all-debug.js:8896
Ext.define.continueFireEvent ext-all-debug.js:9102
Ext.define.fireEvent ext-all-debug.js:9080
Ext.override.fireEvent ext-all-debug.js:51104
Ext.define.processUIEvent ext-all-debug.js:85315
Ext.define.handleEvent ext-all-debug.js:85227
(anonymous function)
Ext.apply.createListenerWrap.wrap
The selection on the item fires the event 'select' and 'deselect' when I click a second time. But the check symbol on the checkbox doesn't work any time.
I have thougth to put this symbol manually on the events 'select' and 'deselect' as a workaround, but I don't know how to put this style and which one is.
Do you have any ideas? Look forward for your suggestions. Thank you in advance.
Manuel
I think, the errors are not related to the code you posted. In fact, your code does not set the width, nor does it call up.
I find your code convoluted: a callback with a listener inside, that creates a view inside. And I don't understand if your code is inside a controller or another class.
Here is a problem:
var tab = tp.add({
//xtype is missing
title: year,
For debugging, I can giv you the following recommendation:
Use ext-dev.js instead of ext-all-debug.js. This will load all required classes one after the other, and the errors in the backtrace are not all inside ext-all-debug.js, but each line shows the line in the source class with all comments in it.
To get a cleaner programming style, try to follow the MVC pattern strictly:
Folder structure as recommended
Define events in the controller, like
init: function(){
this.listen({
store: {
'#plannedYearsStore': {load: this.onPlannedYearsStoreLoad}
}
})
this.control({
'tab': {activate: this.onTabActivate}
})
},
onPlannedYearsStoreLoad: function (store, records){
for (i=0; i< records.length; i++){
var year = records[i].data.year;
var tab = tp.add({
...
},
onTabActivate: function (){
var tbOverview = Ext.getCmp('tabOverview-'+ this.year);
...
},
If possible, define your tab in a view class in a separate file.
When you adhere striclty to this MVC structure, you will get a much easier maintainable code.