Link lines across small multiple charts d3js - javascript

I am trying to get lines to change style on mouseover across multiple charts. In this example available here, I have two charts that both have five groups A,B,C,D,E. Each however is in a different csv (I am open to bringing the data in one csv or as one json array, but this is just how I have it set up right now).
I can get two charts each with five lines corresponding to the group. Using the below code, I get the hovered over line to change style whilst fading out the other lines in that chart.
// Fading and Selecting Lines
d3.selectAll('path.line.mainline')
.on("mouseover", function(d) {
var HoveredLine = this;
d3.selectAll('path.line.mainline').transition().duration(0)
.style('opacity',function () {
return (this === HoveredLine) ? 1.0 : 0.1;
})
.style('stroke-width',function () {
return (this === HoveredLine) ? 4 : 2;
})
;
})
This is achieved by giving the lines an id using classed. Using a different id, the lines in the other chart are selected similarly.
What I want to achieve is a way that if the line of e.g. group A is highlighted in one chart, it is also highlighted in the other chart also (and all other non-selected lines are faded in all charts). I thought maybe this could be done by getting the index of the selected line and somehow using that in the other chart.

We can solve it by having a single place where we handle mouseover and mouseout for both lines.
Primarily to avoid code repeat (DRY principle)
We will write mouse over and mouse out in a single place from where we can handle events in both svg.
So instead of attaching listeners individually like this
d3.selectAll('path.line.mainline')
.on("mouseover", function(d) {
and
d3.selectAll('path.line.mainlinel')
.on("mouseover", function(d) {
Do it like this:
d3.selectAll('path.line')//register this to all paths
.on("mouseover", function(d,i) {
Make use of filter to get the lines on which it is hovered.
d3.selectAll('path.line').filter(function(d1) {
return d.name == d1.name; all which have same name get it via filter
})
.style("opacity", 1)//show filtered links
.style("stroke-width", 4);
Full method will be like this:
function doHover() {
d3.selectAll('path.line')//register this to all paths
.on("mouseover", function(d,i) {
//first make all lines vanish
d3.selectAll('path.line')
.style("opacity", 0.1)
.style("stroke-width", 2)
//only show lines which have same name.
d3.selectAll('path.line').filter(function(d1) {
return d.name == d1.name
})
.style("opacity", 1)
.style("stroke-width", 4);
d3.select("div#chartw.container svg")
.append("text")
.attr("id", "cohorttext")
.html("Cohort " + d.name)
.attr("x", (width) / 1.2)
.attr("y", margin.top * 1.5)
.style("fill", color(d.name))
.style("font-weight", "bold")
.style("font-size", "18px");
d3.select("div#chartw.container svg")
.append("text")
.attr("id", "cohorttextx")
.html("Gini = " + giniw[i%giniw.length])//so that its always within the max length
.attr("x", (width) / 1.2)
.attr("y", 20 + margin.top * 1.5)
.style("fill", color(d.name))
.style("font-size", "14px");
d3.select("div#chartl.container svg")
.append("text")
.attr("id", "cohorttext")
.text("Cohort " + d.name)
.attr("x", (width) / 1.2)
.attr("y", margin.top * 1.5)
.style("fill", color(d.name))
.style("font-weight", "bold")
.style("font-size", "18px");
d3.select("div#chartl.container svg")
.append("text")
.attr("id", "cohorttextx")
.html("Gini = " + ginil[i%ginil.length])//so that its always within the max length
.attr("x", (width) / 1.2)
.attr("y", 20 + margin.top * 1.5)
.style("fill", color(d.name))
.style("font-size", "14px");
})
.on("mouseout", function() {
d3.selectAll('path.line')
.style("opacity", 1)
.style("stroke-width", 2);
//selectALL because we are giving same id to both text in 2 svgs
d3.selectAll("#cohorttext").remove()
d3.selectAll("#cohorttextx").remove()
})
}
Working code here
Please let me know if you have any queries on this.

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.

Replacing text instead of appending in d3 & Jquery

I am using a combination of Javascript+jQuery & d3.js to create custom Chart elements in SAP UI5 dashboard.
In my render function in gauge.ds, I have below code:
this.render = function()
{
this.body = d3.select("#" + this.placeholderName)
.append("svg:svg")
.attr("class", "gauge")
.attr("width", this.config.size + 10)
.attr("height", this.config.size + 10);
this.body.append("svg:circle")
.attr("cx", this.config.cx)
.attr("cy", this.config.cy)
.attr("r", this.config.raduis)
.style("fill", "#ccc")
.style("stroke", "#000")
.style("stroke-width", "0.5px");
this.body.append("svg:circle")
.attr("cx", this.config.cx)
.attr("cy", this.config.cy)
.attr("r", 0.9 * this.config.raduis)
.style("fill", "#fff")
.style("stroke", "#e0e0e0")
.style("stroke-width", "2px");
if (undefined != this.config.label)
{
var fontSize = Math.round(this.config.size / 12);
this.body.append("svg:text")
.attr("x", this.config.cx)
.attr("y", this.config.cy * 2 + fontSize / 2)
.attr("dy", fontSize / 2)
.attr("text-anchor", "middle")
.text(this.config.label)
.style("font-size", fontSize + "px")
.style("fill", "#333")
.style("stroke-width", "0px");
}
This creates a gauge and there is a label beside that gauge as shown in the image.
Now in my redraw function, I want to replace this label with a new text. I have written following code but it does not work as it writes over previous label and both are visible now on one another.
this.redraw = function(value)
{
var fontSize = Math.round(this.config.size / 12);
this.body.append("svg:text")
.attr("x", this.config.cx)
.attr("y", this.config.cy * 2 + fontSize / 2)
.attr("dy", fontSize / 2)
.attr("text-anchor", "middle")
.text(value)
.style("font-size", fontSize + "px")
.style("fill", "#333")
.style("stroke-width", "0px");
}
What code should I change to replace the text in the label instead of writing over it?
Thanks.
Give an ID to your text in the render function:
this.body.append("svg:text")
.attr("id", "textLabel")
//etc...
And select by class in the redraw function:
this.body.select("#textLabel")
.text(value)
If that is the only <text> element in the SVG selection, you could simply do...
this.body.select("text")
.text(value)
... without any ID or class.
A third solution is naming a selection outside both function, which you could change inside them.
Finally, two advices:
First, you said "I am using a combination of jQuery & d3.js". That's almost always a terrible idea. Don't do that.
Second, I'd advise you to mind the names of your variables and objects. You are referring to a SVG selection as body. Normally, we would expect that this.body refers to the <body>. Thus, change it to this.svg, it's clearer for whoever is reading your code.

D3.js Trying to use histogram layout to draw rectangles

So I'm sort of new to Javascript and I am trying to create a histogram using d3.js. I've been following tutorials and examples of previously created histograms in d3 but cannot figure out how to make my rectangles appear.
My histogram currently contains 4 bins with the numbers 1, 2, 3 and 4 in each bin symbolizing a color attribute of each data point in my dataset. When I do console.log(d) in the .attr "x" function it will appear as an a kind of array with 4 different indices, each with the total number of data points in my dataset with that specific color. Now I'm trying to make that "array" into rectangles but my width and height functions aren't correct. If someone could explain what d.dx and d.y do any why they're wrong that would be helpful. I'm using d3.v3.min.js as my script src value
d3.csv("data.csv", function(data) {
var map = data.map(function (i) { return parseInt(i.color); })
var histogram = d3.layout.histogram()
.bins(4)(map)
var canvas = d3.select("body").append("svg")
.attr("width", 500)
.attr("height", 500);
var bars = canvas.selectAll(".bar")
.data(histogram)
.enter()
.append("g")
bars.append("rect")
.attr("x", function (d)
{
//console.log(d)
return d.x * 5; })
.attr("y", 0)
.attr("width",function(d) { return d.dx; })
.attr("height", function(d) { d.y; })
.attr("fill", "steelblue");
});
I updated your plunk as follows.
bars.append("rect")
.attr("x", function(d) { return d.x*100; })
.attr("y", 50)
.attr("height", function(d) { return d.y * 10;})
.attr("width", function(d) { return d.dx*50;})
.attr("fill", "steelblue")
.on("mouseout", function()
{
d3.select(this)
.attr("fill", "steelblue");
})
.on("mouseover", function()
{
d3.select(this)
.attr("fill", "orange");
});
Your code seems to work fine, only your elements are overlapping (also, d3 v4 was referenced instead of v3). What I did is:
multiply d.x by 50 to space the elements
multiplied d.dx by 50 to reduce the overlapping
As to your former questions:
d.x corresponds to the extent of a bin, in your case 0.75 (4 ranges make between 1 and 4 make 0.75: 1+(0.75*4)=4)
*d.y corresponds to the 'height' of a bin, i.e. the number of elements.

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

Looking for a way to display labels on sunburst chart (could not find a working example)

Thanks to someone's help (Brandon), I've been able to add tooltips to the sunburst charts.
I am still looking for a way to display the label of a path on the sunburst chart (and then have the dual mode tooltip + text).
The example that I'd like to improve is provided on jsfiddle.net/trakkasure/UPqX5/
I am looking for the code to add to the following code section:
path = svg.data([getData()]).selectAll("path")
.data(partition.nodes)
.enter().append("svg:path")
.attr("d", arc)
.style("fill", function(d) { return color((d.children ? d : d.parent).name); })
.on("click", magnify)
.on("mouseover", function(d) {
tooltip.show([d3.event.clientX,d3.event.clientY],'<div>'+d.name+'</div> <div>'+d.value+'</div>')
})
.on('mouseout',function(){
tooltip.cleanup()
})
.each(stash);
And I'd like to see the labels as shown on the example is provided on http://bl.ocks.org/910126. I can not get that example to work for me (I'm still new to D3)
I do recognize that there might be too much text on that chart, but in my scenario it is not a problem.
Can someone help me understand how to display all these labels on the chart?
Simply append svg:text elements to the canvas:
path.append("svg:text")
.attr("transform", function(d) { return "rotate(" + (d.x + d.dx / 2 - Math.PI / 2) / Math.PI * 180 + ")"; })
.attr("x", function(d) { return d.y; })
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.text(function(d) { return d.name; });
However, in my edit, this will break your magnify function, so i create an svg group to hold together each pair of path and text. In my opinion, elements are better organized this way, easier to query in the future.
Note that you need to modify your magnify function to also animate the text, as for now it only animate the path and leave the text at their original position.
group = svg.data([getData()]).selectAll("path")
.data(partition.nodes)
.enter().append('svg:g');
//path variable is required by magnify function
path = group.append("svg:path")
.attr("d", arc)
.style("fill", function(d) { return color((d.children ? d : d.parent).name); })
.on("click", magnify)
.on("mouseover", function(d) {
tooltip.show([d3.event.clientX,d3.event.clientY],'<div>'+d.name+'</div><div>'+d.value+'</div>')
})
.on('mouseout',function(){
tooltip.cleanup()
})
.each(stash);
// you may need to assign the result to a variable,
// for example to animate the text in your magnify function,
// as shown in the path variable above
group.append("svg:text")
.attr("transform", function(d) { return "rotate(" + (d.x + d.dx / 2 - Math.PI / 2) / Math.PI * 180 + ")"; })
.attr("x", function(d) { return d.y; })
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.text(function(d) { return d.name; });
Code was taken from your given example, however
I edited the x attribute into .attr("x", function(d) { return d.y; }) to properly position the text based on your data structure (the example uses Math.sqrt(d.y)). I also modify the text function to return d.name
here is the jsFiddle

Categories