D3v4 responsive legend - javascript

I’m building a cartographic responsive visualization with d3 v4 and I'm having some trouble to build a legend which fully adapts to the size of the window.
Each element of the legend is now adapted when the window is resized .attr("width", "1em").attr("height", "1em") as well as the size of the text describing each element .attr("x", "1.8em").
But I'm having some trouble to modify the following line .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; }) in order to lower the space between each line when the window gets smaller.
Do you have any suggestion to solve this problem? Thanks for your help!
Small part of my code:
var legend = d3.select("#legendecontainer").append("svg")
.attr("class", "legend")
.selectAll("g")
.data(color.domain())
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; })
;
legend.append("rect").attr("width", "1em")
.attr("height", "1em").style("fill", color);
legend.append("text")
.data(legendText).attr("x", "1.8em").attr("y", ".5em")
.attr("dy", ".35em").text(function(d) { return d; });

Related

Plot nodes in d3.js Heatmap - Need Guidance

I'm new to d3.js implementation. Need some help d3.js heatmap
I have a Requirement :
A heat map which shows the difference between each record. based on records Severity and Probability types:
Desired Image :
Data:
In the Above output picture , You can see the circles :
Assume those as records being displayed on a graph .
Code for that starts after Comment " //Add dots or circles " .
Record data example :
{
"group":"Likely",
"variable":"Significant",
"value":79,
"record":"Retention of environmental safety records",
"color":"red"
}
Data for those records are in variable "dots" You can find in the code below. In that I have 3 records . But 2 circles are overlapping .
I have worked on a Heatmap Design .
Combining :
Heatmap : https://www.d3-graph-gallery.com/graph/heatmap_style.html
Connected scatter plot : https://www.d3-graph-gallery.com/graph/connectedscatter_tooltip.html
For now , I have just updated the data :
I have 2 Issues :
Overlapping dots issue
Tooltip not showing after Updating to svg
Detail:
1. Overlapping dots issue
Data for those records are in variable "dots" You can find in the code below. In that I have 3 records . But 2 circles are overlapping .
The desired Output is something like this : Circles should not be Overlapped .
If two records with same data , It should display 2 records. I need help in implenting that . Any suggestion is appreciated .
** 2. ToolTip Issue :**
I had an Issue with Tooltip (It was working with div tag ) previously it was as shown below :
Due to Some requirement i had to go with svg tag in the Html rather than Div tag . since This has to be implemented in Lwc in Salesforce.
Html Updated from div to Svg as shown below :
After Updating ,
Entire Heatmap is working except the Tooltip part .
Updated the Tooltip part to Svg as shown below :
Now the Tooltip is not working .
code :
<!-- Code from d3-graph-gallery.com -->
<!DOCTYPE html>
<meta charset="utf-8">
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v5.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz">
<svg
class="d3"
width={svgWidth}
height={svgHeight}
lwc:dom="manual"
></svg></div>
<!-- Load color palettes -->
<script>
// set the dimensions and margins of the graph
var margin = {top: 80, right: 25, bottom: 30, left: 100},
width = 550 - margin.left - margin.right,
height = 450 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select(this.template.querySelector('svg.d3'))
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
//Read the data
var data = [{"group":"Rare","variable":"Insignificant","value":45,"color":"purple"},{"group":"Rare","variable":"Minimal","value":95,"color":"purple"},{"group":"Rare","variable":"Moderate","value":22,"color":"green"},{"group":"Rare","variable":"Significant","value":14,"color":"green"},{"group":"Rare","variable":"Catastrophic","value":59,"color":"yellow"},{"group":"Unlikely","variable":"Minimal","value":37,"color":"purple"},{"group":"Unlikely","variable":"Insignificant","value":37,"color":"purple"},{"group":"Unlikely","variable":"Moderate","value":81,"color":"green"},{"group":"Unlikely","variable":"Significant","value":79,"color":"yellow"},{"group":"Unlikely","variable":"Catastrophic","value":84,"color":"orange"},{"group":"Probable","variable":"Insignificant","value":96,"color":"purple"},{"group":"Probable","variable":"Minimal","value":37,"color":"green"},{"group":"Probable","variable":"Moderate","value":98,"color":"yellow"},{"group":"Probable","variable":"Significant","value":10,"color":"orange"},{"group":"Probable","variable":"Catastrophic","value":86,"color":"red"},{"group":"Likely","variable":"Insignificant","value":75,"color":"green"},{"group":"Likely","variable":"Minimal","value":18,"color":"yellow"},{"group":"Likely","variable":"Moderate","value":92,"color":"orange"},{"group":"Likely","variable":"Significant","value":43,"color":"red"},{"group":"Likely","variable":"Catastrophic","value":16,"color":"red"},{"group":"Almost Certain","variable":"Insignificant","value":44,"color":"green"},{"group":"Almost Certain","variable":"Minimal","value":29,"color":"yellow"},{"group":"Almost Certain","variable":"Moderate","value":58,"color":"orange"},{"group":"Almost Certain","variable":"Significant","value":55,"color":"red"},{"group":"Almost Certain","variable":"Catastrophic","value":65,"color":"red"}]; // Labels of row and columns -> unique identifier of the column called 'group' and 'variable'
var myGroups = d3.map(data, function(d){return d.group;}).keys()
var myVars = d3.map(data, function(d){return d.variable;}).keys()
// Build X scales and axis:
var x = d3.scaleBand()
.range([ 0, width ])
.domain(myGroups)
.padding(0.05);
svg.append("g")
.style("font-size", 15)
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickSize(0))
.select(".domain").remove()
// Build Y scales and axis:
var y = d3.scaleBand()
.range([ height, 0 ])
.domain(myVars)
.padding(0.05);
svg.append("g")
.style("font-size", 15)
.call(d3.axisLeft(y).tickSize(0))
.select(".domain").remove()
// Build color scale
var myColor = d3.scaleSequential()
.interpolator(d3.interpolateInferno)
.domain([1,100])
// create a tooltip
var tooltip = d3.select("#my_dataviz")
.append("div")
.style("opacity", 0)
.attr("class", "tooltip")
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "2px")
.style("border-radius", "5px")
.style("padding", "5px")
.style("position","fixed")
// Three function that change the tooltip when user hover / move / leave a cell
var mouseover = function(d) {
tooltip
.style("opacity", 1)
d3.select(this)
.style("stroke", "black")
.style("opacity", 1)
}
var mousemove = function(d) {
tooltip
.html("The exact value of<br>this cell is: " + d.value)
.style("left", (d3.mouse(this)[0]+70) + "px")
.style("top", (d3.mouse(this)[1]) + "px")
}
var mouseleave = function(d) {
tooltip
.style("opacity", 0)
d3.select(this)
.style("stroke", "none")
.style("opacity", 0.8)
}
// add the squares
svg.selectAll()
.data(data, function(d) {return d.group+':'+d.variable;})
.enter()
.append("rect")
.attr("x", function(d) { return x(d.group) })
.attr("y", function(d) { return y(d.variable) })
.attr("rx", 4)
.attr("ry", 4)
.attr("width", x.bandwidth() )
.attr("height", y.bandwidth() )
.style("fill", function(d) { return d.color } )
.style("stroke-width", 4)
.style("stroke", "none")
.style("opacity", 0.8)
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseleave", mouseleave)
// Three function that change the tooltip when user hover / move / leave a cell
var mouseover1 = function(d) {
tooltip
.style("opacity", 1)
d3.select(this)
.style("stroke", "black")
.style("opacity", 1)
}
var mousemove1 = function(d) {
tooltip
.html("4. " + d.record)
.style("left", (d3.mouse(this)[0]+90) + "px")
.style("top", (d3.mouse(this)[1]) + "px")
}
var mouseleave1 = function(d) {
tooltip
.style("opacity", 0)
d3.select(this)
.style("stroke", "none")
.style("opacity", 0.8)
}
//Add dots or circles
var dots =
[{"group":"Likely","variable":"Significant","value":79,"record":"Retention of environmental safety records","color":"red"},
{"group":"Unlikely","variable":"Minimal","value":84,"record":"Risk of Fines from European Union GDPR due to data breach","color":"orange"},
{"group":"Unlikely","variable":"Minimal","value":84,"record":"Risk Management Case record","color":"green"}];
// Add the points
svg
.append("g")
.selectAll("dot")
.data(dots)
.enter()
.append("circle")
.attr("class", "myCircle")
.attr("cx", function(d) { return x(d.group) + x.bandwidth()/2 } )
.attr("cy", function(d) { return y(d.variable)+ y.bandwidth()/2 } )
.attr("r", 8)
.attr("stroke", "#69b3a2")
.attr("stroke-width", 3)
.attr("fill", function(d) { return d.color })
.on("mouseover", mouseover1)
.on("mousemove", mousemove1)
.on("mouseleave", mouseleave1)
//})
// Add title to graph
svg.append("text")
.attr("x", 0)
.attr("y", -50)
.attr("text-anchor", "left")
.style("font-size", "22px")
.text("A heatmap");
// Add subtitle to graph
svg.append("text")
.attr("x", 0)
.attr("y", -20)
.attr("text-anchor", "left")
.style("font-size", "14px")
.style("fill", "grey")
.style("max-width", 400)
.text("A short description of the take-away message of this chart.");
</script>
Output (Updated) :
Can someone help me in resolving these issues.
I need to display the multiple dots inside the same squares, each dot as a seperate element . when you hover over it It should display the record it represents.
Any suggestion is appreciated . Thankyou
Give your tooltip a position of fixed:
.style("position","fixed")
This will allow the top and left attributes to impact its positioning.
To make the dots appear within the boxes, change your cx attribute to:
.attr("cx", function(d) { return x(d.group) + x.bandwidth()/2 } )
That should center the dots within each box.
To give the dots their own positioning, the dots need to have their own y and x scales. Just make sure it has the same width and height.
Do you have another dataset for this? If not, I wonder if a different scale would give you the outcome you are looking for.

D3 legend for line chart axes

I have line chart made with the help of d3.js which has axes showing numbers with units such as 0m, 1m 2m, 3m..etc for varios units. I want to show legend for these units such m = mili, n=nano, B = billion etc near the line chart.
Here is the an example of line chart with legends. I am not sure if you are following the same process to draw your line charts as most examples in d3 use the same. But as your question do not have any code so here goes an example anyways.Below is the snippet of how to do so:
var legend = svg.selectAll(".legend")
.data(cities)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(0," + i * 20 + ")";
});
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 4)
.style("fill", function(d) {
return color(d);
});
legend.append("text")
.attr("x", width - 24)
.attr("y", 6)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) {
return d;
});

How to get second bars to show on grouped bar chart?

I'm trying to get a second bar into my graph. The elements are correctly getting appended but not in the correct location and not the correct height. What I want from the data to be at the 1 position in the x-axis to have 2 bars one with a height of 2 and the other height of 3 and so on.
http://jsfiddle.net/626uesbh/4/
var svg2 = d3.select("#histogram").append("svg2")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg2.selectAll(".rect")
.data(data)
.enter().append("g")
.attr("transform", "translate(0, 100)")
.append("rect")
.attr("class", "rect")
.attr("width", 10)
.attr("height", function(d) { return height - y2Map(d); })
.attr("x", xMap)
.attr("y", yMap)
.style("fill", "blue");
I suspect svg2 transform is the problem but after trying fiddling with it for an hour I can seem to get what I want. I looked at this question and tried to implement it into my problem. D3.js grouped bar chart
Since each element in your data contains the values for both bars, you have to add them as a group. That is, add a 'g' element to the chart for each element in the array, then add a bar for inner_array[1] and inner_array[2].
Hopefully this gets you on the right path, essentially all I changed was the stuff after your //bar comment.
http://jsfiddle.net/626uesbh/6/
// bar
var bar_groups = svg.selectAll('.bar-group')
.data(data)
.enter().append('g')
.attr('class', 'bar-group');
bar_groups.append('rect')
.attr("class", "rect")
.attr("width", 10)
.attr("height", function(d) { return height - yScale(d[1]); })
.attr("x", function(d) {
return xScale(d[0]) - 5;
})
.attr("y", function(d) {
return yScale(d[1]);
})
.style("fill", "green");
bar_groups.append('rect')
.attr("class", "rect")
.attr("width", 10)
.attr("height", function(d) { return height - yScale(d[2]); })
.attr("x", function(d) {
return xScale(d[0]) + 5;
})
.attr("y", function(d) {
return yScale(d[2]);
})
.style("fill", "blue");
Note: there are much more elegant ways to do this. I am only showing you how to add the bars to your existing code. Please take a look at http://bl.ocks.org/mbostock/3887051 for further guidance.

D3 change visibilty of nodes on click based on class

I have a bubble chart, where nodes are declared as below and I append for each circle a class which is decided by a array ("category") which decides its category, the variable color is d3.scale.category10() .domain(d3.range(number of elements in "category" array));.
var node = svg.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("class", function(d) {return category[d.cluster];})
.text(function(d) { return d.text; })
.filter(function(d){ return d.count >= 1; })
.style("fill", function(d) { return color(d.cluster); })
.call(force.drag);
Next, I make a legend which depends on the categories of each of the circles with their color (as shown above). For this, I do the following
var legend = svg.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color)
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return category[d]; })
Now, what I want is that when the user clicks the legend text, then the bubbles corresponding to the category of the legend be hidden.
So I add the following to the legend, text object.
.on("click", function(d){
node.selectAll('.'+category[d]).style("visibility", "hidden");
});
But, this does not hide the nodes. Please help.
When you call node.selectAll() it will select all childs of this node that fit the selector. In your case you want to call it on document. So you have to do something like d3.selectAll('.'+category[d])

Fitting data for D3 graph to create legend

I have a data variable which contains the following:
[Object { score="2.8", word="Blue"}, Object { score="2.8", word="Red"}, Object { score="3.9", word="Green"}]
I'm interested in modifying a piece of a D3 graph http://bl.ocks.org/3887051 to display the legend, which would be the list of the "word", for my data set.
The legend script looks like this (from link above):
var ageNames = d3.keys(data[0]).filter(function(key) { return key !== "State"; });
var legend = svg.selectAll(".legend")
.data(ageNames.slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
How do I modify their ageNames function to display the "word" set from my data? I'm not sure how they're utilizing the d3.keys. Is there another way to do it?
This should work more or less, but you may need to reverse() (as the original example does) or otherwise rearrange the elements of words, in order to correctly map a word to the right color. Depends on how you've implemented your graph.
var words = yourDataArray.map(function(entry) { return entry.word; });
var legend = svg.selectAll(".legend")
.data(words)
// The rest stays the same

Categories