Sorting function issue with grouped bar chart - javascript

This question is similar to my previous question here, the difference is that the answer to that question doesn't work for my new chart.
Code here: Plunker
The issue is that the sorting toggle checkbox stops sorting the chart when you try to uncheck the box again.
If I remove the function from d3.select("#myCheckbox") and replace it with d3.select("#myCheckbox").on('change', update) it works again, but rather than the bars shifting x position it only shifts the ticks position on the x-axis & the bars update and change position simultaniously.

The problem here is that you're reassigning sortIndex when clicking on the checkbox:
d3.select("#myCheckbox").on('change', function() {
sortIndex = data.map( function(d) { return d.State} );
update();
});
However, the very data it uses has been sorted!
The solution is simple, just remove that:
d3.select("#myCheckbox").on('change', update);
Besides that, make sure you're using the proper key function:
var barGroups = g.selectAll(".layer")
.data(data, function(d){return d.State});
Here is the updated Plunker: https://plnkr.co/edit/goU5K7LB4Gj1jhynLqGt?p=preview

Related

How to return x y coordinates of the selection box in Plotly JavaScript?

I would like to retrieve the coordinates generated by Box Select from a chart made with Plotly JavaScript, but looking in the documentation it seems there is no attribute for the layout.selections.path, how can I retrieve the x.min, x.max, y.min and y.max coordinates of a selection box?
Thanks!
I finally came to a solution, using a callback function of the event plotly_selected:
graphDiv.on("plotly_selected", function (d) {
console.log(d.range);
});
The full code on Codepen

ChartJS showing wrong dataset following visibility property toggle

In my project I have I have ten graphs in a table with each graph showing two datasets.
I want to toggle the visibility of each dataset by clicking it which is default behaviour in ChartJS.
All the charts are held in an array and following the built in toggle method of ChartJS, every chart is updated to the dataset of the first chart. So to be clear, when the page loads, each chart is shown correctly, but if I made the dataset invisible and visible again, it becomes visible but with the wrong data.
I tried writing my own update function to toggle the visibility.
var option = {
legend: {
onClick: ToggleDatasetVisibility,
}}
and then
function ToggleDatasetVisibility() {
this.chart.getDatasetMeta(1).hidden = !this.chart.getDatasetMeta(1).hidden;
// this.chart.update();
}
However exactly the same thing happens. After the update,
*Note, I'm only toggling the second dataset of each DatasetMeta so that's why the 1 is hardcoded in this example. That's not the chart in question.
In case it's useful, we is the code when the charts are primed with data when the page loads and here the update shows the correct data.
for (var i = 0; i < 10; i++) {
var MyArray= await fetch("http://localhost:1234/Demo/FetchResults?model_id=" + i);
//Add the recorded tunnel data to dataset 0
LineChartArray[i].data.datasets[0].data = ResultsArray;
LineChartArray[i].data.datasets[0].label = '';
LineChartArray[i].data.datasets[1].data = MyArray;
LineChartArray[i].data.datasets[1].label = 'LabelXYZ';
LineChartArray[i].update(); //This update updates the graph with the correct data.
}
}
So, I don't know if there's a bug with chart JS or a correct determine which chart to update but I discovered that the render method works. This updates the chart following the visibility toggle loads the correct data back in when made visible. I thought it might be my use of the 'this' command but it works fine with the render method.
I just updated the toggle function like so,
function ToggleDatasetVisibility() {
this.chart.getDatasetMeta(1).hidden = !this.chart.getDatasetMeta(1).hidden;
this.chart.render();
}

Highcharts no drilldown on bar / column click

I have the following use case: On a page there is a Highcharts column with drilldown as in this example.
When the labels below the bars are clicked, I would like to drilldown, as default. However, when the bar / column itself is clicked, I do not want to drilldown but rather get information on the clicked bar (e. g. the name of the category). How is this possible?
What I've tried so far is to evaluate the click event with jQuery and then stop drilling down by using "return false;". The problem with that is that I have seen no way to retrieve the column data as the JavaScript object itself is not accessible.
What I did then is to use the "drilldown" event. I can now easily access the objects data / properties but have have two difficulties:
How to identify whether a bar or the label below the bar was clicked?
How can I prevent the drilldown in case a bar was clicked?
Thank you in advance for hints and tips!
Andy
You can wrap doDrilldown method, and there decide what should happen and when, snippet:
(function (H) {
H.wrap(H.Point.prototype, 'doDrilldown', function (p, hold, x) {
var UNDEFINED;
if (x !== UNDEFINED) {
p.call(this, hold, x); // x is defined when clicked on a category
} else {
myFun(this); // call your method after click on a bar
}
});
})(Highcharts)
And live demo: http://jsfiddle.net/L51c8fa4/

D3 elements being removed although I'm only adding new ones

It's been a while since I picked up d3.js and it seems I'm a bit rusty. I'm trying to do a form where I can add more input fields by clicking + and removing the existing ones by clicking -.
To try and discover what I had wrong, I started colouring the enter(), update and exit() with green, yellow and red respectively.
The original data array has two elements, so they show up as green:
Then I click on the plus sign which pushes a new element to the array, and I expected to see two yellows and one green, but instead I see all the elements deleted besides the last one, and this repeats if I click + again:
And plus again:
I've compared my code with the classic General Update Pattern and I can't see anything significant apart from the way I set the keys, in which I use the e-mail. This is code I've added to fix another underlying issue where not all the boxes were being added, just one out of each 3.
My commented code is as follows:
var renderFriends = function () {
console.log("Rendering friends:" + friendsList)
var friends = d3.select('.friends-container')
.selectAll('div')
.data(friendsList, function(d,i) {
// this was something I added when I thought the problem were the keys
return d
})
// updates will be yellow
friends.classed("update", true)
var enter = friends.enter()
// Friend box
// all the divs are because I'm using foundation css
// the new class is what marks the font green
var friendBox = enter.append('div').classed('large-12 columns new', true)
friendBox.append('div').classed('large-8 columns', true)
.append("input")
.attr("type", "text")
.attr("value", String)
// Icon box
var iconBox = friendBox.append('div').classed('large-2 left columns', true)
.append("i")
.classed('fi-minus', true)
.on("click", function(d) {
// out of scope for this question
console.log("Removing:" + d)
friendsList.remove(friendsList.indexOf(d))
renderFriends()
})
// exit state should colour the fonts red
friends.exit().classed('remove', true)
}
I did a small test with custom styles and this is what got (when I clicked minus button):
All elements have green background since they all have "new" class, the "update" elements have yellow border, and the "remove" red background.
So what I've noticed is that you have a various Divs nested, and the problem is that when you do a selectAll('div') is going to select all divs and d3 is expecting for each div element being selected to be a data element corresponding to it.
So if you want to add another element and your friendsList is:
friendsList = ['a#test.com','b#test.com','c#test.com'];
d3.selectAll('div') is going to take 6 divs (when you had 2 friends and added one), and its going to bind only 3 elements because your dataset contains only 3 friends, and its going to target the rest of elements as "exiting".
To solve this, simply change your select using a class like '.friend' and also add it to each element being inserted (only the main container div);
Like this:
http://jsfiddle.net/2codv59e/

Persisting flot legend label checkbox on un-check

I have a scenario where I want to toggle series based on checkbox selection. I have added the checkbox to the legend using labelFormatting in legend option like:
var otherOptions = {
legend: {
container: legend,
labelFormatter: function (label, series) {
var cb = '<input type="checkbox" name="' + label + '" checked="checked" id="id' + label + '"> ' + label;
return cb;
}
},
};
And I have added the click event for the legend so that I can manipulate the series based on checked items. It all works fine except when I uncheck a label in legend, on re-draw it removes that series line from the legend as well. So for ex., below is the before and after image:
Before
After
Notice that in after image "USA" checkbox is missing.
Is there any way I can still show the unchecked "USA" checkbox in legend?
I looked at the suggestion here: flot graph, use legend to turn on/off series
But the only problem is that I don't want to have legend AND checkbox legend separate. The question on the given link was answered 1+ year ago so I thought I am gonna take a chance and ask the question again in case someone knows a way to do this.
Please let me know.
Thanks!
Instead of removing the series all together if the checkbox is unchecked, add it with empty data.
Something like this:
function plotByChoice(doAll)
{
$('#legend .legendCB').each(
function(){
if (this.checked)
{
data.push(datasets[someLabel]);
}
else
{
data.push({label: someLabel, data: []})
}
}
);
// plot call etc...
}
Working fiddle is here.
For anyone looking for a clean solution AND for Chart.js v3 :
This is unfortunately not possible for Chart.js v3 as legends are generated with text (no HTML can be appended to the legend itself, unfortunately).
We have to use generateLabels function to render the legend labels.
I suggest using the pointStyle option to render an image (checkbox, actually), instead of a rectangle by default, so we can rerender automatically everytime the user checks or not the legend (it's dynamic, ofc, and we don't have to do something complicated programmatically).
So, first, generate the 2 images, corresponding to the checkboxes (I'm using FontAwesome icons that I've generated in PNG from SVG files) :
let checkboxYes = new Image(14, 14);
checkboxYes.src = '/static/img/check_yes.png';
let checkboxNo = new Image(14, 14);
checkboxNo.src = '/static/img/check_no.png';
Then, we can use it in the Chart.js options:
legend: {
labels: {
usePointStyle: true,
generateLabels: function(chart) {
labels = Chart.defaults.plugins.legend.labels.generateLabels(chart);
for (var key in labels) {
labels[key].pointStyle = labels[key].hidden ? checkboxNo : checkboxYes
}
return labels;
}
}
},
Explanations
I'm setting usePointStyle to true so that I can customize the point style
later
Chart.defaults.plugins.legend.labels.generateLabels(chart) kind of pre generate the labels for me (and store it into labels), so it's like a function override!
Then for each label, I'm changing its pointStyle to the checkbox image depending on the hidden parameter (if the user checked the box)
Finally, return the labels
It's perhaps not the best solution, but works amazingly well for me:
Click here to see the animated result in GIF :)

Categories