D3js Get the name of the data when it hovers - javascript

I'm working on an interactive graph project in django using d3js.
I'm trying to create a scatter graph in d3js that displays a tooltip when the cursor hovers over a data node.
However, I don't know how to get the name that corresponds to the pre-defined data when the cursor hovers over it.
The scatter data I'm using is
x : scatter x-coordinate
y : scatter y-coordinate
name : a unique name for the data
I want to get the value of this name key during this hover.
How can I reference the value of the original data corresponding to the data node with the key etc.?
Thank you.
My code is as follows.
<div id="dc-graph"></div>
// data from python (django)
var graph_data = JSON.parse('{{ graph|safe }}');
var num = graph_data.num;
var width = graph_data.width;
var height = graph_data.height;
var margin = graph_data.margin;
var svgWidth = width + margin.left + margin.right;
var svgHeight = height + margin.top + margin.bottom;
var data = [];
for (let i = 0; i < num; i++){
const adata = {x: graph_data.x[i], y: graph_data.y[i], name: graph_data.name[i]};
data.push(adata);
}
// svg
var svg = d3.select("#dc-graph")
.append("svg")
.attr("class", "dc-graph-svg")
.attr('width', svgWidth)
.attr('height', svgHeight);
// tooptip
var tooltip = d3.select("#dc-graph")
.append("div")
.attr("id", "dc-tooltip")
// axis
var xScale = d3.scaleLinear()
.domain([0, d3.max(data, function(d){return d.x;})])
.range([0, width]);
var yScale = d3.scaleLinear()
.domain([0, d3.max(data, function(d){return d.y;})])
.range([height, 0]);
var axisx = d3.axisBottom(xScale);
var axisy = d3.axisLeft(yScale);
svg.append("g")
.attr("class", "x_axis")
.attr("transform", "translate(" + margin.left + "," + (height + margin.top) + ")")
.call(axisx);
svg.append("g")
.attr("class", "y_axis")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(axisy);
// plot
svg.append("g")
.selectAll("circle")
.attr("class", "sc-scatter")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
console.log(d.x);
return xScale(d.x);
})
.attr("cy", function(d) {
console.log(d.y);
return yScale(d.y);
})
.attr("fill", "SkyBlue")
.attr("r", 4)
.on("mouseover", function(d) {
tooltip
.style("visibility", "visible")
.html("name : " + d.name + "<br>x: " + d.x + "y: " + d.y);
})
.on("mousemove", function(evnet, d) {
const[x, y] = d3.pointer(evnet);
tooltip
.style("top", (y - 20) + "px")
.style("left", (x + 10) + "px");
})
.on("mouseout", function(d) {
tooltip.style("visibility", "hidden");
});

Related

Can not show labels in group bar chart d3js v6

I try to follow as following link to put labels in the groups bar chart, but it does not show up.
Anyone know what's going on my text label?
http://plnkr.co/edit/9lAiAXwet1bCOYL58lWN?p=preview&preview
Append text to Grouped Bar Chart in d3js v4
// set the dimensions and margins of the graph
var margin = { top: 10, right: 30, bottom: 40, left: 50 },
width = 700 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
const dataUrl = "https://raw.githubusercontent.com/yushinglui/IV/main/time_distance_status_v2.csv"
//fetch the data
d3.csv(dataUrl)
.then((data) => {
// append the svg object to the body of the page
var svg = d3.select("#graph-2")
.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 + ")")
// List of subgroups = header of the csv files = soil condition here
var subgroups = data.columns.slice(1)
// List of groups = species here = value of the first column called group -> I show them on the X axis
var groups = d3.map(data, function (d) { return (d.startTime) })
// Add X axis
var x = d3.scaleBand()
.domain(groups)
.range([0, width])
.padding([0.2])
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickSize(0));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 20])
.range([height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// Another scale for subgroup position?
var xSubgroup = d3.scaleBand()
.domain(subgroups)
.range([0, x.bandwidth()])
.padding([0.05])
// color palette = one color per subgroup
var color = d3.scaleOrdinal()
.domain(subgroups)
.range(['#98abc5', '#8a89a6'])
// Show the bars
svg.append("g")
.selectAll("g")
// Enter in data = loop group per group
.data(data)
.enter()
.append("g")
.attr("transform", function (d) { return "translate(" + x(d.startTime) + ",0)"; })
.selectAll("rect")
.data(function (d) { return subgroups.map(function (key) { return { key: key, value: d[key] }; }); })
.enter()
.append("rect")
.attr("x", function (d) { return xSubgroup(d.key); })
.attr("y", function (d) { return y(d.value); })
.attr("width", xSubgroup.bandwidth())
.attr("height", function (d) { return height - y(d.value); })
.attr("fill", function (d) { return color(d.key); })
//axis labels
svg.append('text')
.attr('x', - (height / 2))
.attr('y', width - 650)
.attr('transform', 'rotate(-90)')
.attr('text-anchor', 'middle')
.style("font-size", "17px")
.text('Average Distance');
svg.append('text')
.attr('x', 300)
.attr('y', width - 240)
.attr('transform', 'rotate()')
.attr('text-anchor', 'middle')
.style("font-size", "17px")
.text('Start Time');
// legend
svg.append("circle").attr("cx", 200).attr("cy", 20).attr("r", 6).style("fill", "#98abc5")
svg.append("circle").attr("cx", 300).attr("cy", 20).attr("r", 6).style("fill", "#8a89a6")
svg.append("text").attr("x", 220).attr("y", 20).text("Present").style("font-size", "15px").attr("alignment-baseline", "middle")
svg.append("text").attr("x", 320).attr("y", 20).text("Absent").style("font-size", "15px").attr("alignment-baseline", "middle")
//text labels on bars
svg.append("g")
.selectAll("g")
// Enter in data = loop group per group
.data(data)
.enter()
.append("g")
.attr("transform", function (d) { return "translate(" + x(d.startTime) + ",0)"; })
.selectAll("text")
.data(function (d) {
return [d['P'], d['ABS']];
})
.enter()
.append("text")
.attr("fill", "black")
.text(function (d) {
return formatCount(d)
})
.attr("transform", function (d, i) {
var x0 = xSubgroup.bandwidth() * i + 11,
y0 = y(d) + 8;
return "translate(" + x0 + "," + y0 + ") rotate(90)";
})
});
try this...and if possible please provide code snippet....
svg.append("text")
.attr("fill", "black")
.text(function (d) {
console.log( formatCount(d) )
return formatCount(d)
})
.attr("transform", function (d, i) {
var x0 = xSubgroup.bandwidth() * i + 11,
y0 = y(d) + 8;
return "translate(" + x0 + "," + y0 + ") rotate(90)";
})

D3 JS: How to add transition to refresh a plot when the dataset x-scale changes?

Looking at this Histogram chart using d3 example I plugged in my data but it had some strange side effects e.g. after refreshing to a new dataset, some information from the previous dataset i.e. x-axis scale was retained. I tried deleting and appending a new x-axis etc but nothing worked.
This happened due to the fact that my datasets had completely different x-axis ranges and scales. The only way I found to make it work was to select the whole svg element, remove it and re-append everything anew. However, this doesn't make a pleasant transition for the user so I was wondering how can this be improved to make it refreshable using transitions as in the original example even when having datasets with different x-scales and ranges.
This was my last approach which is a bit harsh to the eye:
// delete old
d3.select("#" + divId).select("svg").remove();
// then recreate all new
And this was my refresh attempt (integrated with AngularJS). Note how it has some common initialization and then if the SVG doesn't exist appends everything new otherwise tries to update it. I went bit by bit but can't see why the refresh doesn't remove all the previous dataset information of the x-axis scale:
var divId = $scope.histogramData.divId;
var color = $scope.histogramData.color;
var values = $scope.histogramData.data[$scope.histogramData.selected];
var svg = $scope.histogramData.svg;
// plot common initialization
var margin = {top: 40, right: 20, bottom: 20, left: 20},
width = 450 - margin.left - margin.right,
height = 370 - margin.top - margin.bottom;
var max = d3.max(values);
var min = d3.min(values);
var x = d3.scale.linear()
.domain([min, max])
.range([0, width]);
// generate a histogram using twenty uniformly-spaced bins.
var data = d3.layout.histogram()
.bins(x.ticks(10))
(values);
var yMax = d3.max(data, function(d){ return d.length });
var yMin = d3.min(data, function(d){ return d.length });
var colorScale = d3.scale.linear()
.domain([yMin, yMax])
.range([d3.rgb(color).brighter(), d3.rgb(color).darker()]);
var y = d3.scale.linear()
.domain([0, yMax])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
// ===================================================================
// If the SVG doesn't exist then adds everything new
// ===================================================================
if (svg === undefined) {
var svg = d3.select("#" + divId)
.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 + ")");
$scope.histogramData.svg = svg;
var bar = svg.selectAll(".bar")
.data(data)
.enter()
.append("g")
.attr("class", "bar")
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; });
bar.append("rect")
.attr("x", 1)
.attr("width", (x(data[0].dx) - x(0)) - 1)
.attr("height", function(d) { return height - y(d.y); })
.attr("fill", function(d) { return colorScale(d.y) });
bar.append("text")
.attr("dy", ".75em")
.attr("y", -12)
.attr("x", (x(data[0].dx) - x(0)) / 2)
.attr("text-anchor", "middle")
.text(function(d) { return formatCount(d.y); });
var gTitle = svg.append("text")
.attr("x", 0)
.attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "left")
.classed("label", true)
.text($scope.histogramData.spec[selected]);
$scope.histogramData.gTitle = gTitle;
var gAxis = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
$scope.histogramData.gAxis = gAxis;
} else {
// ===================================================================
// If the SVG does exist then tries refreshing
// ===================================================================
var bar = svg.selectAll(".bar").data(data);
// remove object with data
bar.exit().remove();
bar.transition()
.duration(1000)
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; });
bar.select("rect")
.transition()
.duration(1000)
.attr("height", function(d) { return height - y(d.y); })
.attr("fill", function(d) { return colorScale(d.y) });
bar.select("text")
.transition()
.duration(1000)
.text(function(d) { return formatCount(d.y); });
var gTitle = $scope.histogramData.gTitle;
gTitle.transition()
.duration(1000)
.text($scope.histogramData.spec[selected]);
var gAxis = $scope.histogramData.gAxis;
gAxis.transition()
.duration(1000)
.call(xAxis);
}
I would suggest to keep this d3 code inside one angularJS directive and keep a watch on the json which you are using to plot that graph. As soon as values are changing the directive will be called again and the graph will be plotted. Hope it helps.

Adding a legend to D3 graph V4

I am currently trying to create a legend for my graph in D3 version 4. I've seen other examples where you can preset the legend with static text but I want for the legend labels to be loaded dynamically from the csv data which I am using. Here is my code:
HTML:
<div class ="graphics"> </div>
Calling the graph with JS:
var margin = {top:20, right: 20, bottom: 30, left:50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var parseTime = d3.timeParse("%d/%m/%Y %H:%M");
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var valueline = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.channelA); });
var valuelineTwo = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.channelB);});
//apend the svg object o the body
var svg = d3.select(".graphics").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 + ")");
//GETtheDATA
d3.csv("data.csv", function(error, data){
if (error) throw error;
data.forEach(function(d){
d.date = parseTime(d.date);
d.channelA = + d.channelA;
d.channelB = + d.channelB;
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) {return Math.max(d.channelA, d.channelB);})]);
svg.append("path")
.data([data])
.attr("class", "line")
.style("stroke", "FECB00")
.attr("d", valueline);
svg.append("path")
.data([data])
.attr("class", "line")
.attr("d", valuelineTwo);
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add the Y Axis
svg.append("g")
.call(d3.axisLeft(y));
});
Trying to create the Legend:
var legendRectSize = 18;
var legendSpacing = 4;
var legend = svg.selectAll('.legend')
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
var height = legendRectSize + legendSpacing;
var offset = height * color.domain().length / 2;
var horz = -2 * legendRectSize;
var vert = i * height - offset;
return 'translate(' + horz + ',' + vert + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color);
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function(d) { return d; });
I am also trying to draw a rectangle around the legend which I am not sure how to do and I cannot find any relevant examples:
Previous questions which I've checked: Example 1, Example 2, Example 3
Here is an image of how much graph looks:

d3 transform scale date to x position?

I have a linear y scale with a time series x scale. I want to put an overlay that follows the x/y value (similar to http://bl.ocks.org/mbostock/3902569).
The issue is that I'm not able to transform to the proper x scale value; for example when I mouseover my chart it outputs (this data is correct):
{ y: 0.05, x: "2015-07-26 15:08:47" }
{ y: 0.05, x: "2015-07-26 15:08:47" }
{ y: 0.05, x: "2015-07-26 15:08:47" }
Now I want to use this data to draw a point at that location; the issue is that I cannot replicate the above bl.locks.org example, and the transform isn't able to use the x position as a date; so how can I transform that x date to the point on the chart?
My mousemove is below:
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height,0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var varea = d3.svg.area()
.defined(function(d) { return d.y != null; })
.x(function(d) { return x(parseDate(d.x)); })
.y0(height)
.y1(function(d) { return y(d.y); });
var svg = d3.select(".swatch").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 + ")");
x.domain(d3.extent(data, function(d) { return parseDate(d.x); }));
y.domain([0, d3.max(data, function(d) {
if (d.y >= 1) {
return d.y
}
return 1;
})]);
svg.append("path")
.attr("class", "area")
.attr("d", varea(data));
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
var focus = svg.append("g")
.attr("class", "focus")
.attr("display", "none");
focus.append("circle")
.attr("r", 4.5);
focus.append("text")
.attr("x", 9)
.attr("dy", ".35em");
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", function() {
focus.style("display", null);
})
.on("mouseout", function() {
focus.style("display", "none");
})
.on("mousemove", function() {
var x0 = x.invert(d3.mouse(this)[0]);
var bisect = d3.bisector(function(d) { return parseDate(d.x); }).right;
var item = data[bisect(data, x0)];
focus.attr("transform", "translate(" + x(parseDate(item.x)) + "," + y(item.y) + ")");
focus.select("text").text(item.y);
console.log(x(parseDate(item.x)));
console.log(y(item.y));
});
This code produces errors like the following in the console:
Unexpected value translate(NaN,120) parsing transform attribute.
So, the question is how do I convert the date to a proper coordinate?
Alright, so there were a couple of problems with my code.
I wasn't parsing the x value as a date; so I started parsing it with parseDate (see sample code) and then passed it to the x scale; this allowed me to get proper location on the chart.
The second issue was that display wasn't be set properly (setting it to null in Firefox wasn't allowing it to appear). So I changed that to display: inline; and it started showing up on the chart.

How to get the x position of a bar on d3 paired bar graph for tooltip update

I'm currently trying to create tooltips for an interactive paired bar chart in d3 but I'm having trouble getting the x-position in order to locate the tooltip on the svg.
Here is a JSFiddle showing the problem: notice the yPosition updates but I'm not sure how to get the proper xPosition for each bar on mouseover
My code is as follows:
$(document).ready(function(){
//Graph
var e = $('#graph');
//Get width of graph div
var w = document.getElementById('graph').clientWidth;
//Set margins, width and height of graph
var margin = {top: 40, right: 20, bottom: 30, left: 40}, width = w - margin.left - margin.right -10, height = 500 - margin.top - margin.bottom;
//Sca;e amd ramge
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var x1 = d3.scale.ordinal();
var y = d3.scale.linear()
.range([height, 0]);
//Define x and y axis
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
//Generate svg element in graph div
var svg = d3.select("#graph").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 + ")");
//Redraw graph on window resize
function updateWindow(){
x = w.innerWidth || e.clientWidth || g.clientWidth;
y = w.innerHeight|| e.clientHeight|| g.clientHeight;
svg.attr("width", x).attr("height", y);
}
window.onresize = updateWindow;
//Access output data to populate graph
d3.csv("inputs/outputs/Milk_Month2.csv",type, function(error, data) {
//Graph colors
var color = d3.scale.ordinal().range(["#98abc5", "#8a89a6"]);
//data attributes for the two different graphs
var categoryNamesMilk = d3.keys(data[0]).filter(function(key) { return key !== "month" && key !== "water_current" && key !== "water_future" ; });
var categoryNamesWater = d3.keys(data[0]).filter(function(key) { return key !== "month" && key !== "milk_current" && key !== "milk_future" ; });
data.forEach(function(d) {
d.categoriesMilk = categoryNamesMilk.map(function(name) { return {name: name, value: +d[name]}; });
d.categoriesWater = categoryNamesWater.map(function(name) { return {name: name, value: +d[name]}; });
});
x.domain(data.map(function(d) { return d.month; }));
x1.domain(categoryNamesMilk).rangeRoundBands([0, x.rangeBand()]);
y.domain([0, d3.max(data, function(d) { return d3.max(d.categoriesMilk, function(d) { return d.value; }); })]);
//Generate X Axis
var axisX = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
//Generate Y Axis
var axisY = svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Milk Production (KG/Day)").attr("class","yaxis-text");
var infoBars = svg.selectAll(".bar")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d,i) {console.log(x(d.month),i); return "translate(" + x(d.month) + ",0)"; });
infoBars.selectAll("rect")
.data(function(d) { return d.categoriesMilk; })
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function(d) { return x1(d.name); })
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.style("fill", function(d) { return color(d.name); })
.on("mouseover", function(d,i) {
//Where I'm having problems - getting the X attribute!
var xPosition = parseFloat(x(i) + x1.rangeBand());
var yPosition = parseFloat(d3.select(this).attr("y")) / 2 + height / 2;
//Update the tooltip position and value
d3.select("#tooltip")
.style("left", xPosition + "px")
.style("top", yPosition + "px")
.select("#value")
.text(d.value);
//Show the tooltip
d3.select("#tooltip").classed("hidden", false);
}).on("mouseout", function() {
//Hide the tooltip
d3.select("#tooltip").classed("hidden", true);
});
});
//Data type conversion
function type(d) {
d.milk_current = +d.milk_current;
d.water_current = +d.water_current;
d.milk_future = +d.milk_future;
d.water_future = +d.water_future;
return d;
}
});
It pulls from a csv as follows (I also have a click function that transitions to a second paired graph, hence the additional data columns
month,water_current,milk_current,water_future,milk_future
1,2.259,1.955,2.4849,100
2,0.006,0,0.0066,0
3,14.443,11.795,15.8873,10.6155
4,2.87,21.538,3.157,19.3842
5,0,20.216,1,18.1944
6,0.973,18.37,1.0703,16.533
7,1.492,15.686,1.6412,14.1174
8,0,13.146,0.5,11.8314
9,0,12.087,0.4,10.8783
10,0,9.626,0.3,8.6634
11,7.434,8.19,8.1774,7.371
12,6.049,7.207,6.6539,6.4863
I'm pretty sure I need to access the parent element of each "rect" on mouseover and retrieve the x position, but I haven't had any luck doing this...Any advice?
You can access the parent g of each rect like:
.on("mouseover", function(d,i) {
var parentG = d3.select(this.parentNode);
To get the X value:
var barPos = parseFloat(parentG.attr('transform').split("(")[1]);
And the x position becomes:
var xPosition = barPos + x1(d.name);
Fiddle here.
Another way is to use d3.event.pageX/Y property
d3.select("#tooltip")
.style("left", (d3.event.pageX + 20) + "px")
.style("top", (d3.event.pageY - 50 )+ "px")**strong text**

Categories

Resources