amCharts 4 chart not updating with switchbutton, slider - javascript

For a radar chart, I'm trying to toggle (with a switch button) or slide (with a slider) between different sets of data to display. (I'll include the button here first, but I'm eventually try to extend that to a slider later.). 1. Initialize button and keep track of user toggle. 2. Pick which data set to use. both generateRadarData and generateRadarData2 work well on their own if I use chart.data = either one.
The below is the edited attempt:
var chartSwitchButton = chart.chartContainer.createChild(am4core.SwitchButton);
chartSwitchButton.events.on("toggle", function () {chart.data = whichData();})
function whichData() {
var dataToUse = chart.data;
if (chartSwitchButton.isActive) {dataToUse = generateRadarData();
} else {
dataToUse = generateRadarData2();}
return dataToUse};
chart.data = whichData();
I have tried commenting out the last line (since ideally it would have been updated via the event listener), but then no data displays.
Here is a more recent attempt to update the data using invalidateRawData:
chartSwitchButton.events.on("toggle", function (event) {
chart.data = whichData();
chart.invalidateRawData();
});
function whichData() {
var data = [];
if (chartSwitchButton.isActive) {
chart.data = generateRadarData();
} else {
chart.data = generateRadarData2();
}
chart.invalidateRawData(); //also tried invalidateData. tried this command in event listener as well as here.
data.push(chart.data); //attempt to replace/update raw data
//console.log(chart.data);
return chart.data; //this return line is necessary to output data but theoretically shouldn't be.
}
and have tried implementing the if-else w/in the event listener without having to use whichData as a separate function like so:
chartSwitchButton.events.on("toggle", function () {if (chartSwitchButton.isActive) {
chart.data = generateRadarData();
} else {
chart.data = generateRadarData2();
}
chart.invalidateRawData();})
I'm still unable to switch between the two sets of data with user interaction. In fact, if I don't return something for chart.data or declare what chart.data is supposed to be outside of the events.on or whichData(), then none of my data prints at all.
If anybody has suggestions on how to do this with a button (or a slider would be even better) that would be awesome.
Basically, after setting up the button, I tried to (a) keep track of the state of the button (as determined by user), (b) determine which state the button is in, and (c) pick a data set to use based off of that info. This version is edited from a previous attempt as per initial comments below. Thanks for your help.

Documentation is "toggled" not "toggle" in the events listener. The event does not recognize "toggle" but needed "toggled". https://www.amcharts.com/docs/v4/reference/switchbutton/#toggled_event

Related

how can i update the items at html page

let bricks = document.querySelectorAll(".brick");
bricks.forEach((brick) => {
brick.addEventListener("click", function () {
let bricktype = brick.getAttribute("type");
console.log(bricktype);
});
});
from the above code ...
bricks variable get updated while the page first load. then i have form to add extra items to the bricks variable.. but i can't get update it .. if i try to reload page all the value eraes from memory
i want add values to that brocks array while i dynamicall add values in the html page
Yes, that code will only be executed at page load. This is a design problem, try refactoring your system some like this:
First, a function to add a listener for a single brick
function setupBrick(brick) {
brick.addEventListener("click", function () {
let bricktype = brick.getAttribute("type");
console.log(bricktype);
});
}
Now, in first page load:
let bricks = document.querySelectorAll(".brick");
bricks.forEach((brick) => {
setupBrick(brick)
});
And when user dynamically push a brick, do the following:
// get brick from form and store it in the "brickToPush" variable
setupBrick(brickToPush) // before push the brick
bricks.push(brickToPush)
That should solve your problem, you need to manually add that listener somehow, and encapsulating that logic in a function is a good practice

How to catch an onClick event on bubble chart?

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

Cannot set WFS filter the second time

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");

Use jQuery to determine when Django's filter_horizontal changes and then get the new data

I have a filter_horizontal selector in my Django admin that has a list of categories for products (this is on a product page in the admin). I want to change how the product change form looks based on the category or categories that are chosen in the filter_horizontal box.
I want to call a function every time a category is moved from the from or to section of the filter_horizontal.
What I have now is:
(function($){
$(document).ready(function(){
function toggleAttributeSection(choices) {
$.getJSON('/ajax/category-type/', { id: choices}, function (data, jqXHR) {
// check the data and make changes according to the choices
});
}
// The id in the assignment below is correct, but maybe I need to add option[]??
var $category = $('#id_category_to');
$category.change(function(){
toggleAttributeSection($(this).val());
});
});
})(django.jQuery);
The function never gets called when I move categories from the left side to the right side, or vice versa, of the filter_horizontal.
I assume that $category.change() is not correct, but I don't know what other events might be triggered when the filter_horizontal is changed. Also, I know there are multiple options inside of the select box. I haven't gotten that far yet, but how do I ensure all of them are passed to the function?
If anyone can point me in the right direction I would be very grateful. Thank!
You need to extend the SelectBox.redisplay function in a scope like so:
(function() {
var oldRedisplay = SelectBox.redisplay;
SelectBox.redisplay = function(id) {
oldRedisplay.call(this, id);
// do something
};
})();
Make sure to apply this after SelectBox has been initialized on the page and every time a select box refreshes (option moves, filter is added, etc.) your new function will be called.
(Code courtesy of Cork on #jquery)
I finally figured this out. Here is how it is done if anyone stumbles on this question. You need to listen for change events on both the _from and _to fields in the Django filter_horizontal and use a timeout to allow the Django javascript to finish running before you pull the contents of the _from or _to fields. Here is the code that worked for me:
var $category = $('#id_category_to');
$category.change(function(){
setTimeout(function () { toggleAttributeSection(getFilterCategoryIds()) }, 500);
});
var $avail_category = $('#id_category_from');
$avail_category.change(function(){
setTimeout(function () { toggleAttributeSection(getFilterCategoryIds()) }, 500);
});
And this is how I get the contents of the _to field:
function getFilterCategoryIds() {
var x = document.getElementById("id_category_to");
var counti;
var ids = [];
for (counti = 0; counti < x.length; counti++) {
ids.push(x.options[counti].value);
}
return ids;
}
I know it was a convoluted question and answer and people won't come across this often but hopefully it helps someone out.

Updating data points that have changed on invisble HighCharts series issue

I am using HighCharts to show a graph containing a line and bar chart. By default the bar series is not visible until you click the series header. I have code that updates the bars on the fly but I have noticed that I cannot get existing information whilst the chart is invisible.
When I get data from the server I attempt to update any existing points, and add new ones if we have any. This code works great if the series is visible, but if the series is not visible then chart.series[1].data[idx] returns undefined. Clicking the series will show the chart so I know the data is there somewhere. What am I doing wrong?
if (chart.series[1].data[idx] && chart.series[1].data[idx].y != inRate)
{
chart.series[1].data[idx].update(inRate);
redraw = true;
}
else if (!chart.series[1].data[idx])
{
var time = IntervalToTime(i, doorInterval);
chart.series[1].addPoint([time, inRate], false, false);
redraw = true;
}
After some investigation I made some progress. Apparently the data array isnt filled until the series has been visible at least once, so you cannot check for the data existence there. I found a processed data queue where data is held which seems to work but doing this method causes an error when attempting to call chart.series[1].data[idx].update(inRate)
if (chart.series[1].processedXData[idx] && chart.series[1].processedYData[idx] != inRate)
{
chart.series[1].data[idx].update(inRate);
redraw = true;
}
else if (!chart.series[1].processedXData[idx])
{
var time = IntervalToTime(i, doorInterval);
chart.series[1].addPoint([time, inRate], false, false);
redraw = true;
}
After some investigation I found the issue. Apparently the data array isnt filled until the series has been visible at least once, so you cannot check for the data existence there. I found a processed data queue where data is held which seems to work.
if (chart.series[1].processedXData[idx] && chart.series[1].processedYData[idx] != inRate)
{
chart.series[1].data[idx].update(inRate);
redraw = true;
}
else if (!chart.series[1].processedXData[idx])
{
var time = IntervalToTime(i, doorInterval);
chart.series[1].addPoint([time, inRate], false, false);
redraw = true;
}

Categories