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/
Related
I am using the Chartist.js graph api to create a graph and I need each bar to click to a location. This is easy enough, but I also need to pass the corresponding label value if possible.
var graph = new Chartist.Bar('.ct-chart', {
labels : ['L1','L2','L3'],
series : [1,2,3]
});
So, I have added an onclick method to each bar, but need it to get the corresponding label value to pass to the location page. Example:
graph.on('created', function() {
$('.ct-bar').click(function () {
var val = $(this).attr('ct:value');
if (val > 0) {
window.location = 'location/?label=BAR LABEL HERE (eg: L1)';
}
});
});
Would anyone know if this is possible?
Many thanks.
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();
I have a Chart.js doughnut chart (v2.7.1) and I am looking to set the class of an external element when the user hovers over the relevant chart element
I can use the onHover event within the options.hover of the chart
hover: {
onHover: function (evt, item) {
if (item.length) {
var index = item[0]._index;
var legendElement = $(#get element based on index#);
legendElement.addClass('active');
}
}
}
and this sets the class on the element (legendElement) perfectly but I need to be able to remove the class I set from the element when the user is no longer hovering over the element
Am I using the correct approach? Is there a way to detect that the onHover is complete or that the segment is no longer in focus?
Are you defining an "events" property apart from the "onHover" property? If you add "mouseout" to the list, the "onHover" function will be called in both cases. Check out the documentation for this https://www.chartjs.org/docs/latest/general/interactions/events.html
An example code that might work:
options: {
events: ["mousemove", "mouseout"],
onHover: function (evt, item) {
if (item.length) {
var index = item[0]._index;
var legendElement = $(#get element based on index#);
if(evt.type == "mousemove"){
legendElement.addClass('active');
}else{
legendElement.removeClass('active');
}
}
}
}
I'm using a chart with drilldown and I've allowed to select a point (=click). On the event click, I create an HTML table with help of AJAX to list the entities related to the count (if I see 5 items, by clicking on it I'll see who are the 5 items and list them). This becomes a 2nd/3rd level of drilldown (depending I've clicked on the 1st level of 2nd level in the chart)
However, I'd like to remove the selection on first level on the drill up event.
Here is my code (EDITED) :
Edit
I'm adding series like this (sample found here) :
$(function() {
var myChart = new Highcharts.Chart({
chart: {
type: 'column',
renderTo: 'drillDownContainer',
// Deactivate drilldown by selection.
// See reason below in the drilldown method.
selection: function(event) {
return false;
},
drilldown: function(e) {
// Deactivate click on serie. To drilldown, the user should click on the category
if (e.category === undefined)
{
return;
}
// Set subTitle (2nd param) by point name without modify Title
// Giving 1st param as null tells the text will be for the subTitle,.
// For Title, remove the null 1st param and let text.
this.setTitle(null, { text: e.point.name });
if (!e.seriesOptions) {
var chart = this,
drilldowns = {
'Social': [
{"name":"Serie 1","data": [{"id":113,"name":"my data","y":14}
]}
]
};
var categorySeries = drilldowns[e.point.name];
var series;
for (var i = 0; i < categorySeries.length; i++) {
if (categorySeries[i].name === e.point.series.name) {
series = categorySeries[i];
break;
}
}
chart.addSingleSeriesAsDrilldown(e.point, series);
drilldownsAdded++;
// Buffers the number of drilldown added and once the number of series has been reached, the drill down is applied
// So, can't navigate by clicking to a single item.
// However, simply click on the category (xAxis) and then unselect the other series by clicking on legend
// The click has been disabled.
if (drilldownsAdded === 3) {
drilldownsAdded = 0;
chart.applyDrilldown();
}
}
},
drillup: function(e) {
this.setTitle(null, { text: '' }); // Erase subTitle when back to original view
$('#ajaxContainer').html(''); // Remove the table drilldown level3
},
drillupall: function(e) {
debugger;
console.log(this.series);
console.log(this.series.length);
for(i = 0; i < this.series.length; i++)
{
console.log("i = " + i);
console.log(this.series[i].data.length);
for (d = 0; i < this.series[i].data.length; d++)
{
console.log("d = " + d);
console.log(this.series[i].data[d].selected);
}
}
}
}
}); -- End of myChartdeclaration
myChart.addSeries({
color: colorsArray['impact101'],
name: '1-Modéré',
data: [
{
id: '101',
name: 'Type 1',
y: 64,
drilldown: true
},
{
id: '102',
name: 'Type 2',
y: 41,
drilldown: true
}]
}, true);
});
Demo of the point selection : http://jsfiddle.net/8truG/12/
What do I'd like to do? (EDIT)
If I select a point on the 2nd level, then return to 1st level and then back to same drilldown data, the point selected before is not selected anymore.
However, for the 1st level, the selection remains.
On the drillup event, the this.series[x].data[y] corresponds to the data of the 2nd level. Kind of obvious as the drilldown is not finished for all series but event raised as many as there is series.
On the drillupall event, I'm getting the right serie. I can see my 3 series on debug but they are all without any data. So I can't apply this.series[i].data[d].selected as suggested in comment below.
I'm using Highcharts 5.0.9.
Any idea to help me ?
Thanks for your help.
I got helped on the Highcharts forum. See here.
Here is the answer (in case the link above doesn't work in the future):
In this case you can change the state of a specific point with
Point.select() function. Take a look at the example posted below. It
works like that: in drillup() event there is a call of
getSelectedPoints() which returns all selected points from the base
series. Next, there is a call of select() function with false as an
argument on a selected point to unselect it. In addition, the call of
the getSelectedPoints() is located in a timeout, otherwise it would
return an empty array (see
https://github.com/highcharts/highcharts/issues/5583).
CODE:
setTimeout(function() { selectedPoints =
chart.getSelectedPoints()[0];
chart.getSelectedPoints()[0].select(false); }, 0);
API Reference: http://api.highcharts.com/highcharts/Ch ... ctedPoints
http://api.highcharts.com/highcharts/Point.select
Examples: http://jsfiddle.net/d_paul/smshk0b2/
Consequently, here now the code fixing the issue :
drillupall: function(e) {
this.setTitle(null, { text: '' }); // Erase subTitle when back to original view
$('#ajaxContainer').html(''); // Remove the table drilldown level3
var chart = this,
selectedPoints;
setTimeout(function() {
selectedPoints = chart.getSelectedPoints()[0];
// Ensure there is any selectedPoints to unselect
if (selectedPoints != null)
{
selectedPoints.select(false);
}
}, 0);
Regards.
I'm teaching myself EXTjs 4 by building a very simple application.
In EXTjs 4 I've got 4 grids that each have the Grid to Grid drag/drop plugin. (Example functionality here: http://dev.sencha.com/deploy/ext-4.0.2a/examples/dd/dnd_grid_to_grid.html )
In my view I have the plugin defined as such:
viewConfig: {
plugins: {
ptype: 'gridviewdragdrop',
dragGroup: 'ddzone',
dropGroup: 'ddzone'
}
},
Now in the example, they have different dragGroups and dropGroups, but because I want the items to drag/dropped between each other fluidly, I gave the groups the same name.
The way the information gets originally populated into the 4 different lists is by looking at an the state_id in the db. All state_ids 1 go into the backlog store, 2 In Progress store, etc, etc.
So what I need to do when the item is drag/dropped, is remove it from its old store and put it into the new store (updating the state_id at the same time, so I can sync it with the db afterwards).
My only problem is figuring out the origin grid and destination grid of the row that was moved over.
Thank you!
PS. If you're curious this is what my drop event handler looks like at the moment:
dropit: function (node, data, dropRec, dropPosition) {
console.log('this');
console.log(this);
console.log('node');
console.log(node);
console.log('data');
console.log(data);
console.log('droprec');
console.log(dropRec);
console.log('dropPosition');
console.log(dropPosition);
},
As you can see, I haven't gotten very far ^_^
Alright, I figured out a way of doing it that seems to be less then ideal... but it works so until someone provides a better solution I'll be stuck doing it like this:
dropit: function (node, data, dropRec, dropPosition) {
if (node.dragData.records[0].store.$className == "AM.store.BacklogCards")
{
data.records[0].set('state_id', 1);
this.getBacklogCardsStore().sync();
}
else if (node.dragData.records[0].store.$className == "AM.store.InprogressCards")
{
data.records[0].set('state_id', 2);
this.getInprogressCardsStore().sync();
}
else if (node.dragData.records[0].store.$className == "AM.store.ReviewCards")
{
data.records[0].set('state_id', 3);
this.getReviewCardsStore().sync();
}
else
{
data.records[0].set('state_id', 4);
this.getDoneCardsStore().sync();
}
I noticed that node.dragData.records[0].store.$className points to defined store that is what the grid bases itself on.
Using the data.records[0].set('state_id', 1); sets the state_id for the row that was moved over and then finally, I call the sync function to update the db with the new row information.