I created an heatmap and some sparklines following this example.
In the example the user could click on the labels of the rows and those of the columns, in my case I kept only the possibility to click on the columns labels.
That example was perfect for my data, only I need to update the heatmap based on the radio buttons selection.
The first radio buttons allow you to choose the type of area (A or B).
The second group of radio buttons allow you to choose the daily data to be displayed.
However, the data are "incomplete": not all the months have daily data but only April and December.
So if you select the April or December radio button, the daily data on the heatmap are shown, otherwise the monthly ones are shown.
The example works but it is very primitive because the heatmap is deleted and recreated every time.
// Return to the initial order when the user clicks on the button
d3.select('#initialOrder').on('click', function() {
var trans = heat.transition().duration(1000);
var sortedYear = Array.from(Array(numYLabels).keys());
trans.selectAll('.cell')
.attr('y', function(d) {
var row = parseInt(d3.select(this).attr('data-r'));
return sortedYear.indexOf(row)*cellSize;
});
trans.selectAll('.rowLabel')
.attr('y', function(d, k) {
return sortedYear.indexOf(k) * cellSize;
});
sortedYear.forEach(function(d) {
d3.select('#data-svg-' + d).raise();
});
});
// Area radio button change selection
d3.selectAll('input[name=area-rb]').on('change', function() {
areaSelected = d3.select('input[name="area-rb"]:checked').property("value");
console.log('areaSelected:', areaSelected);
d3.select('#heatmapSvg').remove();
d3.selectAll('.data-svg').remove();
createHeatmap();
});
// Month radio button change selection
d3.selectAll('input[name=month-rb]').on('change', function() {
monthSelected = d3.select('input[name="month-rb"]:checked').property("value");
console.log('monthSelected:', monthSelected);
if(avaibleDayData.includes(monthSelected)) {
d3.select('#heatmapSvg').remove();
d3.selectAll('.data-svg').remove();
createHeatmap();
}
else {
monthSelected = 'nothing';
d3.select('#heatmapSvg').remove();
d3.selectAll('.data-svg').remove();
createHeatmap();
}
});
I found this example that allow to update the heatmap but I can't able to adapt the code.
In the example, data changes only value and not "shape". That is, the number of labels remains the same. In my case, the situation is a bit more complicated.
I create a Plunker with the code.
This is a good example where a LOT of d3's enter/update/exit selection can be implemented.
Yeah, I agree: recreating a chart on every change isn't a good solution.
Okay, so here's a fork of your Plunkr using enter/update/merge/exit methods.
http://plnkr.co/edit/2v8YQoZSClhKpW2U1pwi?p=preview
A preview of how I got the merge selection done: Let's take rowLabels for example:
// make region labels
var rowLabels = rowLabelGroup
.selectAll('text.rowLabel')
.data(yLabelsNames);
// ENTER SELECTION FOR COL LABELS
// ENTER + UPDATE
// After merging the entered elements with the update selection,
// apply operations to both.
rowLabels
.enter().append('text').attr('class', 'rowLabel mono')
.attr('font-weight', 'normal')
.style('text-anchor', 'end')
.on('mouseover', function(d) {
d3.select(this).attr('font-weight', 'bold').style('fill', 'red');
})
.on('mouseout', function(d) {
d3.select(this).attr('font-weight', 'normal').style('fill', 'black');
})
.attr('x', 0)
.attr('y', function(d, i) {
return i * cellSize;
})
.attr('transform', function(d, i) {
return 'translate(-3, 11)';
})
.merge(rowLabels)
.attr('name', function(d) {
return d;
})
.text(function(d) {
return d;
})
.attr('id', function(d) {
return 'rowLabel_' + yLabelsNames.indexOf(d);
})
.attr('label-r', function(d) {
return yLabelsNames.indexOf(d);
});
// exit/remove elements which are no longer in use
rowLabels.exit().remove();
Similarly, these methods have been applied to colLabels, cells and sparkLineSvgs as you can notice in the code.
And regarding appending of the SVGs, I've moved that code to outside the updateHeatmap function. And yes, btw, I've changed the name of the function from createHeatmap to updateHeatmap.
And I did encounter an issue while hovering over for the tooltip i.e. the tooltip flickered a lot. To counter that, I've added pointer-events:none to .d3-tip tooltip.
Go through the code and let me know if I've missed on anything or if you face issue understanding any part.
Hope it helps.
Related
I've created a dropdown menu using d3.js v4, but instead of showing the selected option on the HTML page I want it to show a (fixed) name I set myself. This is what I have so far:
d3.select("body" )
.append("select")
.attr("id", 'dropdownMenu')
.attr("class", "button")
.selectAll("dropdownOption")
.data(['a', 'b', 'c', 'd'])
.enter().append("option")
.text(function(d) { return d;})
.attr("value", function(d, i) { return i; });
d3.select('#dropdownMenu').property("dropdownOption" , 0);
I've already tried setting a label attribute, but that did not work.
Please note: I'm quite new to d3 so if anything can be improved on my code I would appreciate the feedback.
To clarify: if the dropdown would select the colors of graph, the options would be ['red, 'blue', 'green']. Currently my code would show the button on the front-end with 'red' written in the button. Instead, I would like it to show 'Graph color'.
I have set up a table using d3 js that successfully displays a saved csv file, called data.csv, in my html like so:
<script type="text/javascript"charset="utf-8">
d3.text("/Users/cool_stuff/data.csv", function(data) {
var parsedCSV = d3.csv.parseRows(data);
var container = d3.select("#meowwoof")
.selectAll("tr")
.data(parsedCSV).enter()
.append("tr")
.selectAll("td")
.data(function(d) { return d; }).enter()
.append("td")
.text(function(d) {
return d;
}
);
});
</script>
<tbody id="meowwoof">
</tbody>
This works great but I would like to add so if the user clicks on any row, it links so some URL that is, for example:
https://www.google.com/ + *value in first column of the given row thye clicked on*
I have found a few examples talking about how to do this, but they are for different initial set ups than what I am doing and thus they do not translate, and I am JS noob which makes it even harder to fit the explanations into my own setup
This is an example answer to a similar but different question I have not been able to figure out how to port:
d3 adding html links to a column of data in a table
As pointed out, you can attach a click event handler and redirect the user to the corresponding link
var container = d3.select("#meowwoof")
.selectAll("tr")
.data(parsedCSV).enter()
.append("tr")
.selectAll("td")
.data(function(d) { return d; })
.enter()
.append("td")
.text(function(d) {
return d;
}
)
.on('click',d=>{
// Assuming parsedCsv contains and d is a link
window.location.href = d;
})
I'm really struggling linking the checked/unchecked to lines.
I managed to have lines selected when options checked from checkbox.
But when the options are unchecekd then they are removed completely.
Instead of removing them, I'd to like to have them chinging attributes, such as colours.
I took the relevant part only since original code is unnessesarily long.
The data is nested by key - country and the checkbox options contain the country as id/value.
Initially lines (same path but grey colour) are rendered and the part below is colouring the lines that are selected in the checkbox.
Lines are coloured successfully but they disappear when the respective options are unchecekd. Instead, I'd like to have them remain with initial colour which is grey.
But it seems .exit() doesn't work here.
I'm really struggling... Could anyone help me?
d3.select(#checkbox).on("change", function() {
var country = this.value
display = this.checked ? "inline" : "none";
var filtered = lineWrapper
.selectAll(".line")
.filter(function(d) { return d.key === country; });
filtered
.transition()
.duration(2000)
.ease(d3.easeLinear)
.style("stroke", function(d) {return colScale(d.key); })
.style("stroke-width", 3)
.style("fill", "none");
filtered
.exit()
.transition()
.style("stroke", "#C0C0C0")
.style("stroke-width", 1)
.style("fill", "none");
I have a force layout with a legend, and I’ve appended checkboxes to each role in the legend.
I’m trying to follow this example(https://jsfiddle.net/zhanghuancs/cuYu8/) and add interactivity to the legend, but when I uncheck any of the checkboxes, all the links disappear instead of the links and nodes related to the role.
I’m creating the legend using this loop,
var col = color(d.group);
// adding code to display legend
// as nodes are filled
if(!(d.role in roles)){
legend.append("li")
.html(d.role)
.style("color", col)
//add checkbox
.append("input")
.attr("type", "checkbox")
.attr("id", function (d){
return "chk_" + d;
})
//check box
.property("checked", true)
.on("click", function (d){
//click event
var visibility = this.checked? "visible" : "hidden";
filterLegend(d,visibility);
})
roles[d.role] = true;
}
return col; })
Here’s a fiddle of my graph (https://jsfiddle.net/gbhrea/077mb7o1/1/), using a lot of data so just used a small sample for the fiddle. (graph won’t show on fiddle but will keep the link anyways so that full code can be seen)
Here is the code for changing visibility
function filterLegend(aType, aVisibility){
//change vis of link
link.style("visibility", function (o) {
var lOriginalVisibility = $(this).css("visibility");
return o.type === aType ? aVisibility : lOriginalVisibility;
});
//change vis of node
//if links of node invisible, node should be too
node.style("visibility", function (o, i) {
var lHideNode = true;
link.each(function (d, i) {
if (d.source === o || d.target === o) {
if ($(this).css("visibility") === "visible") {
lHideNode = false;
}
}
})
});
} //end filter
To be clear, what I want to achieve is - When I uncheck a role, e.g Actor, all nodes and links disappear. Any pointers would be greatly appreciated :)
There are a few mistakes in your code. First, you're not binding any data to the legend elements so .on("click", function (d) { }) won't work because d isn't defined there. You do have d.role there though, so that's what you should use to pass on to functions.
Similarly, your filterLegend() function references undefined things (.type) -- this is why everything disappears at the moment. You're passing in something undefined and comparing it to something undefined, which gives true. Furthermore, links don't have the node information directly, but under .source and .target, so you need to compare to .source.role and .target.role.
For the nodes on the other hand, it's much easier than what your current code is trying to do -- there's no need to iterate over the links. You can use the code you have for the links here, except for comparing to the existing .role attribute instead of the non-existing .type.
Complete demo here.
I want to remove a curve when a checkbox is unchecked. I tried this without success:
function drawdata(fileno,draw){
var name="circle"+fileno;
if (draw) {
console.log("Drawing file "+fileno+ " ("+files[fileno]+")");
svg.selectAll(name) .data(datasets[fileno]) .enter() .append("circle")
.attr('cx',function(a){ return xscales[xval](a[xval]); })
.attr('cy',function(a){ return yscales[yval](a[yval]); })
.attr('r',3)
.style("opacity", 1)
;
} else {
console.log("removing file "+fileno);
svg.selectAll(name).data(datasets[fileno]).exit().remove();
}
}
The first part work well (ie if I check a checkbox the file is plotted) but when I uncheck it, it is not removed from the svg. I guess I do not understand the exit well. Can someone point out what is wrong? Thanks.
Sample jsfiddle code:
http://jsfiddle.net/datavis/530h1qLw/11/#&togetherjs=Rkz5QyDgJm
Update:
With the addition of the fiddle, it's pretty easy to see what is going on. When you do svg.selectAll(name) it's returning an empty selection. The reason for that is because there are no elements of circleN. circleN is not a valid DOM element, and none of them exist anyway, so the selection will fail.
If you make the following changes to your cds function you will see that your code now works:
function cds(el) {
name = "circle" + el.id;
if (el.checked) {
svg.selectAll(name)
.data(dataset[el.id])
.enter().append("circle")
.attr('cx', function (a) { return xscale(a[0]); })
.attr('cy', function (a) { return yscale(a[1]); })
.attr('r', 3)
.attr('class', name)
.style("opacity", 1);
} else {
svg.selectAll("circle." + name).remove();
}
}
There are two changes of note in this function:
.attr('class', name) being added to the code creating your new DOM elements. This will add a class to the circle, making the selection easier when it comes time to remove them.
Updated the line that removes them to svg.selectAll("circle." + name).remove(); which will remove the circles when you uncheck the radio boxes.