I have a loop that goes through data and creates several google charts. I have added a selectHandler that does something when a bar of a chart is clicked. I have no problem getting the bar selected once I have the chart, but I do not know how to tell the handler which chart was clicked.
Here is the code:
inside the drawChart() which is in a loop:
chart[chart_index] = new google.visualization.BarChart(document.getElementById('chart_div<%= qcount %>'));
chart[chart_index].draw(data, {width: 450, height: 300, title: 'title'});
google.visualization.events.addListener(chart[chart_index], 'select', selectHandler);
chart_index = chart_index+1;
and the selectHandler works like this:
function selectHandler(e) {
var bar_index = chart[HERE_GOES_THE_CHART_INDEX].getSelection()[0].row;
}
Thanks
There's no way to get the specific chart from the event handler, so you have to use another method of passing the chart to the handler. Here's one way you can do it:
function selectHandler(myChart) {
// test to see if anything was selected before you get the index
// otherwise you will get errors when the selection contains 0 elements
var selection = myChart.getSelection();
if (selection.length) {
var bar_index = selection[0].row;
// do something with bar_index
// you should also test bar_index, as the user could have clicked a legend item, which would give a null value for row
}
}
chart[chart_index] = new google.visualization.BarChart(document.getElementById('chart_div<%= qcount %>'));
// generally speaking, you should add event handlers before drawing the chart
google.visualization.events.addListener(chart[chart_index], 'select', (function (x) {
return function () {
selectHandler(chart[x]);
}
})(chart_index));
chart[chart_index].draw(data, {width: 450, height: 300, title: 'title'});
chart_index = chart_index+1;
This closure passes chart_index to the inside of the closure, and assigns it to x:
(function (x) {
return function () {
selectHandler(chart[x]);
}
})(chart_index)
so the value of x is locked inside the closure, even when you increment chart_index later. The closure returns a function which becomes the event handler. This function calls selectHandler, passing in chart[x] when someone clicks on a chart element. If you are iterating over this in a loop, the value of x will be unique inside each closure, giving you the ability to reference specific charts in your selectHandler function.
After reading google visualization event handling...
SELECT Event:
The select event does not pass any properties or objects to the
handler (your function handler should not expect any parameters to be
passed to it).
So although you can use getSelection(), you need another function to determine which chart is has been acted upon. Enters another event handler:
// google.visualization.table exposes a 'page' event.
google.visualization.events.addListener(table, 'page', myPageEventHandler);
...
function myPageEventHandler(e) {
alert('The user is navigating to page ' + e['page']);
}
You need an event handler that has event object passed in param so you can determine which chart is being evented. Once you have the current chart, you can use getSelection() to see the current selection in that chart.
Function binding to the rescue.
google.visualization.events.addListener(chart[chart_index], 'select', selectHandler.bind(chart[chart_index]));
Your handler will receive the chart always as the first argument.
If you're targeting older browsers, here's a great binding polyfill by Mozilla crew:
MDN Function.prototype.bind()
Related
I have a simple line graph made with chartjs with a legend. By default, when the user clicks the legend then it shows/hides the dataset that the user clicked. I have a callback function that I would like to call, but when I assign the callback function to the legend, then the original functionality of showing/hiding the datasets is not present anymore. Is there a way for me to add my callback function without getting rid of the default functionality? Below is how my current legend onClick looks, "vm.legendOnclickCallback()" simply calls a console.log statement for now, for testing. My filter function is used to get rid of a bar (named dayBar) from the legend.
legend: {
labels: {
filter: function (item, chart) {
return !item.text.includes('dayBar');
}
},
onClick: vm.legendOnclickCallback
}
The docs show how to do this. You can call the default handler in your custom function:
var defaultLegendClickHandler = Chart.defaults.global.legend.onClick;
defaultLegendClickHandler(e, legendItem);
Is there a way to trigger a function from within a rowFormatter? I'm using the responsiveLayout: "collapse"-option, and I really like it.
However, I would like to trigger the toggleList function (or what's it's called.... 1 from '19)
I would like to not go the .click() way, so I created my own (rip-off) solution within the rowClick:
let isOpen = row._row.modules.responsiveLayout.open;
var collapseEl = row._row.element.querySelector('div.tabulator-responsive-collapse');
if (!(isOpen)) {
collapseEl.classList.add("open");
if (collapseEl) {
collapseEl.style.display = '';
}
} else {
collapseEl.classList.remove("open");
if (collapseEl) {
collapseEl.style.display = 'none';
}
}
row._row.modules.responsiveLayout.open = !(isOpen);
But... There must be a good way to trigger toggleList(), instead of writing a rip-off function, which doing the same thing...
I've tried to look through the values and functions in row._row, with no luck. I'm 99.7% sure that I missed this part in the documentation........ But I've really tried to search the best I could.
TL;DR: I would like to trigger the toggleList() function defined within formatter, in my rowClick() event-function. Is that possible?
There is no toggleList function built into Tabulator.
In the example you reference there it is simply a function called toggleList that is defined inside the row formatter and triggered when an element added by the row formatted is clicked.
Because the toggleClick function is defined inside the row formatter its scope is limited to that formatter function so it cannot be accessed from outside it.
one way to get around this would be to assign the function to a property on the row data object then you could access it from else where in the table.
So if we take the example you provided a link to and at the top of the customResponsiveCollapseFormatter function add the following:
var data = cell.getData(); //retrieve the row data object
Yhen where we define the toggleList function, instead of the simple function definition we can assign it to a property on the data object, lets call it collapseToggle, we will also tweak it so it dosnt need the isOpen property passed in and insted flips the state of the open variable itself, that way it can be called from anywhere outside the formatter without knowledge of the current state:
data.collapseToggle = function toggleList(){
open = !open;
Then in our cellClick function we can check to see if the collapseToggle property is defined on the row data and then call it:
cellClick:function(e, cell){
var data = cell.getData();
if(data.collapseToggle){
data.collapseToggle();
}
}
I am trying to catch an onlick event on a bubble in bubble chart. I want to log the label of clicked bubble into the console. I have written a function to do the job which actually looks like this
$("#myChart").click(function(evt) {
var activePoints = myBubbleChart.getElementAtEvent(evt);
console.log(activePoints.label);
});
Every time I click on a bubble this function logs "undefined" into console. I have even tried getBarsAtEvent and getSegmentsAtEvent none of them worked. What's wrong in my code? And can anybody please tell me how can I get the label value of bubble which I clicked?
Chart.js options have a built-in onClick property (see documentation).
It works like this :
options: {
onClick: function(e) {
var element = this.getElementAtEvent(e);
// If you click on at least 1 element ...
if (element.length > 0) {
// Logs it
console.log(element[0]);
// Here we get the data linked to the clicked bubble ...
var datasetLabel = this.config.data.datasets[element[0]._datasetIndex].label;
// data gives you `x`, `y` and `r` values
var data = this.config.data.datasets[element[0]._datasetIndex].data[element[0]._index];
}
}
}
Check this jsFiddle for a full example.
Getting the name of the clicked item, the current dataset, or the item in the current dataset is straightforward:
onClick: (e, i) => {
const bubbleSelected = i[0];
console.log(this.widget.data.bubblesData[bubbleSelected.datasetIndex].tooltipData[bubbleSelected.index]);
}
Parameter i contains an array of clicked elements which contains the following:
With these image data, knowing which dataset you have selected and which element of the dataset you have clicked, you can, from an array of data, obtain the data, names or whatever you want from the current dataset of the current position.
In my case I had in bubblesData a list of datasets (datasetIndex) and in each dataset an object called tooltipData with a list of data for each element of the dataset (index).
If you want to see how to also modify the tooltip to show it like this visit this link: Chart JS Show HTML in Tooltip
Documentation:
https://www.chartjs.org/docs/latest/configuration/interactions.html
https://stackoverflow.com/a/44160605/11770490
Here is my scenario. The page loads and the map loads with an empty vector layer. So its there, but has no features. Then the user clicks a button and a CQL filter loads features according to the CQL settings.
My methodology to implement this. I set an empty vector layer, no loader or strategy. The button the user clicks for the first time calls a "initialization " function (=firstTimeOnly()) that:
sets a loader and a strategy to the vector layer
now that a loader exists, calls another "filtering" function (=changeFilter()) that
resets the loader's cql filter and loads features
now the "filtering" function gets attached to the button and called
with every click. The "initialization" function served its purpose
and detaches itself from the button.
Here is my code
<button id= "magicButton", onclick="firstTimeOnly()">Click me</button>
//set globals to use them
var cqlFilter = "name='testpoint9'";
var urlTemplate = 'http://localhost:5550/geoserver/mymap/wfs?service=WFS&version=1.0.0&request=GetFeature&typeName=mymap:layerName&CQL_FILTER={{CQLFILTER}}&outputFormat=application/json';
var loader = function (extent) {
var url = urlTemplate.replace('{{CQLFILTER}}', cqlFilter);
$.ajax(url, {
type: 'GET',
success: function(response) {
var res = response;
var geojsonFormat = new ol.format.GeoJSON();
sourceVector.addFeatures(geojsonFormat.readFeatures(response));
}
})
};
var strategy = new ol.loadingstrategy.tile(ol.tilegrid.createXYZ({maxZoom: 20}));
//empty vector source
var sourceVector = new ol.source.Vector({});
function changeFilter() {
//remove all, set cql and reload
var featsToRemove = layerVector.getSource().getFeatures();
for (var f=0;f<featsToRemove.length;f++)
{
layerVector.getSource().removeFeature(featsToRemove[f]);
}
cqlFilter = "name LIKE 'p'";
sourceVector.clear(true);
}
layerVector = new ol.layer.Vector({
source: sourceVector,
style:styleFunction
});
function firstTimeOnly() {
sourceVector.set('loader', loader);
sourceVector.set('strategy', strategy);
changeFilter();
document.getElementById("magicButton").removeEventListener("click", firstTimeOnly, false);
document.getElementById("magicButton").addEventListener("click", changeFilter, false);
}
This is based to erilem's code for cql filter resetting and if I use just his code works fine. But if I want to start with an empty layer and edit it as the above, I get nothing. My code gives no errors. But, if I click the button I get nothing.
Please advide me how to fix this. Or maybe its an overkill and you suggest something smarter.
Thanks
UPDATE
If I put
console.log("loader "+sourceVector.get('loader'));
at the end of the changeFilter I get the loader function. So, I guess the first time I click the button, the firstTimeOnly actually sets a loader and calls changeFilter. So, the loader is there, but does not work? Any help?
Without getting into potential issues with Openlayers the issue I see is that removeEventListener only works for removing listeners that were set with addEventListener. Since you bound the onclick declaratively in the HTML the way to unbind it would be by doing document.getElementById("magicButton").onclick = null.
That said I would change your example so that somewhere in your code you set the event listener using addEventListener.
Example:
function firstTimeOnly() {
sourceVector.set('loader', loader);
sourceVector.set('strategy', strategy);
changeFilter();
document.getElementById("magicButton").removeEventListener("click", firstTimeOnly, false);
document.getElementById("magicButton").addEventListener("click", changeFilter, false);
}
document.getElementById("magicButton").addEventListener("click", firstTimeOnly, false);
And get rid of the onclick in the HTML.
Also consider caching the reference to magicButton so that you don't have to be constantly calling getElementById.
var magicButton = document.getElementById("magicButton");
I'm setting up a way to be able to enlarge small-ish charts on my webpage by un-hiding a page 'overlay' div and creating a secondary, larger chart inside it with the same data as the chart which was clicked on.
function DrawSmallChart() {
chart_data = [1, 2, 3, 4, 5];
LargeChartOptions = {
series: [{
name: 'Data',
data: chart_data
}],
};
SmallChartOptions = {
series: [{
name: 'Data',
data: chart_data
}],
events: {
click = function (e) { DrawLargeChart(LargeChartOptions); }
}
};
$('#smallchart-div-container').highcharts(SmallChartOptions);
}
function DrawLargeChart(options) {
chart_container_div = document.getElementById("graph-div-id");
chart_container_div.style.display = ""
$('#graph-div-id').off();
$("#graph-overlay-graph").highcharts('StockChart', options);
}
I have another function that hides this div when I click a button.
The first time I click the small graph when the page loads, the big graph shows up fine with all the data. The second time I click it, the graph shows but with no data.
I've used the debugger to flick through what is happening and I've found exactly what the problem is, but I can't figure out how to solve it.
The first time I click the graph, the DrawLargeChart function is called with options.series = <Array containing my series object with chart_data>. The second time, DrawLargeChart is called with options.series = null.
When I refresh the page it is the same - first click works, subsequent clicks don't. I suspect it has something to do with the chart_data variable...
Any help would be greatly appreciated
EDIT 1:
After some more debugging, it is clear that the options object which is passed to DrawLargeChart() is not the same in the first click versus subsequent clicks. There is nothing in my code which is changing the LargeChartOptions structure
EDIT 2:
I figured out that this is a pass by value / pass by reference error. LargeChartOptions is being passed in by reference which no longer exists after the first click. Is there a way to pass it by value? I'd rather not have to type out the LargeChartOptions (much bigger than I've typed up here) into the function parameter in case I change anything in future
EDIT 3 // I've figured it out:
I figured out what the problem is. The $(target).highcharts(options) function actually modifies the options object and sets options.series = null
The solution
I modified the DrawLargeChart function to create a local copy of options by using options_buffer = $.extend(true,{},options);
function DrawLargeChart(options) {
chart_container_div = document.getElementById("graph-div-id");
options_buffer = $(target).highcharts(options);
chart_container_div.style.display = ""
$('#graph-div-id').off();
$("#graph-overlay-graph").highcharts('StockChart', options_buffer);
}
By creating options_buffer, the highcharts function cannot modify LargeChartOptions (because options is just a reference to that variable - yay Javascript)
I found the answer (in original post but copied here):
I modified the DrawLargeChart function to create a local copy of options by using options_buffer = $.extend(true,{},options);
function DrawLargeChart(options) {
chart_container_div = document.getElementById("graph-div-id");
options_buffer = $(target).highcharts(options);
chart_container_div.style.display = ""
$('#graph-div-id').off();
$("#graph-overlay-graph").highcharts('StockChart', options_buffer);
}
By creating options_buffer, the highcharts function cannot modify LargeChartOptions (because options is just a reference to that variable - yay Javascript)