I need to draw horizontal lines in a d3.js chart.
Using the below code I was able to draw lines.
g.append("line")
.attr("x1", x(0.5))
.attr("y1", y(1))
.attr("x2", x(3.5))
.attr("y2", y(1))
.style("stroke-width", 5)
.style("stroke", "blue")
.style("fill", "none");
But I need to draw around 8 lines in the chart.
Currently I have copy pasted the above code 8 times and entered the values manually.
Is there a way to pass the coordinates from an array, instead of copy pasting it 8?
I tried to do something like this but it did not work.
method 1:
var dataTest = [{x1:4,y1:3,x2:6,y2:3}]
g.append("g")
.selectAll('line')
.data(dataTest)
.enter()
.append('g').attr('class', 'line').append('g')
.attr({'x1':function(d,i){ console.log(d); return d.x1; },
'y1':function(d){ return d.y1; },
'x2':function(d,i){ return d.x2; },
'y2':function(d){ return d.y2; },
})
.style("stroke-width", 2)
.style("stroke", "blue")
.style("fill", "none");
method 2:
g.append("line")
.attr("d", line(dataTest))
.style("stroke-width", 2)
.style("stroke", "green")
.style("fill", "none");
where the line function returned the x and y values.
var dataTest = [{x:4,y:3},{x:6,y:3}]
var line = d3.svg.line()
.x(function (d) {
console.log(d.x);
return (d.x);
})
.y(function (d) {
return (d.y);
});
Related
For a project I am attempting to have three visualizations for data based on car stats, where if you hover over one, the others will show the affects of that hovering as well.
The first is a bar graph, the second is a scatterplot, and the third is a line graph. For the line graph I wanted to group by manufacturer so that I don't have a couple hundred lines on my line graph, as the plot coordinates on the x and y are acceleration and model year. The other two don't need to be grouped in this way because one of their axes is the manufacturer.
I have the interactions from the line graph to the other two working since there is no nesting on the bar or scatterplot, and both the scatterplot and the bar graph can affect each other perfectly fine, but since the data is nested for the line graph, I can't seem to figure out how to access it, as the way I was doing it for the other two (using filtering) does not seem to work.
Below I am first showing where I am trying to create interactions when the mouse hovers (this is for the bar graph), and below that I include how my line graph is set up to show how it works. All I want is to make the corresponding line stand out more from the others by thickening the stroke when I hover over the bar or plot (in the scatterplot), and then go back to the normal size upon moving my cursor.
I followed the tutorial on the D3 website for line graphs, so there shouldn't be anything particularly wrong with that code.
Creating the bars for the bar graph, the mouseover and mouseout are the important parts:
var path1 = svg1.selectAll("myRect")
.data(data)
.enter()
.append("rect")
.attr("x", x1(0.1) )
.attr("y", function(d) { return y1(d.Manufacturer); })
.attr("height", y1.bandwidth() )
.attr("width", function(d) { return x1(d.Cylinders); })
.attr("fill", function (d) {
return color1(d.Cylinders);
})
.on('mouseover', function (d, i) {
svg1.selectAll('rect')
.filter(function(f) {
return f.Manufacturer === d.Manufacturer;
})
.attr("fill", function (d) {
return color4(d.Cylinders);
})
svg2.selectAll('circle')
.filter(function(f) {
return f.Manufacturer === d.Manufacturer;
})
.attr('r', 9)
.attr("fill", function (d) {
return color5(d.Horsepower);
});
svg3.selectAll('path') //THIS IS THE LINE GRAPH
.filter(function(f) {
console.log(this)
return ; // <-------This is where I don't know what to return to just get one line
})
.attr("stroke-width", 7)
})
.on('mouseout', function (d, i) {
svg1.selectAll('rect')
.filter(function(f) {
return f.Manufacturer === d.Manufacturer;
})
.attr("fill", function (d) {
return color1(d.Cylinders);
});
svg2.selectAll('circle')
.filter(function(f) {
return f.Manufacturer === d.Manufacturer;
})
.attr('r', 5)
.attr("fill", function (d) {
return color2(d.Acceleration);
});
d3.selectAll('path') //DELESLECTING LINE GRAPH
.filter(function(f) {
return f.key === d.Manufacturer; //this is what I tried before but it doesn't work
})
.attr("stroke-width", 1.5)
});
Creating the line graph:
var sumstat = d3.nest()
.key(function(d) { return d.Manufacturer;})
.entries(data);
// Add X axis
var x3 = d3.scaleLinear()
.domain([69, 84])
.range([ 0, width3 ]);
svg3.append("g")
.attr("transform", "translate(0," + height3 + ")")
.call(d3.axisBottom(x3).ticks(5));
// Add Y axis
var y3 = d3.scaleLinear()
.domain([8, d3.max(data, function(d) { return +d.Acceleration; })])
.range([ height3, 0 ]);
svg3.append("g")
.call(d3.axisLeft(y3));
var div3 = d3.select("#my_div").append("div")
.attr("class", "#tool_tip")
.style("opacity", 0)
.style("font-size", "xx-large");
// color palette
var res = sumstat.map(function(d){ return d.key }) // list of group names
var color = d3.scaleOrdinal()
.domain(res)
.range(['darkolivegreen','darkred','palevioletred','indianred', 'hotpink'])
// Draw the line
svg3.selectAll(".line")
.data(sumstat)
.enter()
.append("path")
.attr("fill", "none")
.attr("stroke", function(d){ return color(d.key) })
.attr("stroke-width", 1.5)
.attr("d", function(d){
return d3.line()
.x(function(d) { return x3(d.ModelYear); })
.y(function(d) { return y3(+d.Acceleration); })
(d.values)
})
.on('mouseover', function (d, i) {
//highlight;
svg3.selectAll("path")
.attr("stroke-width", 0.9)
d3.select(this)
.attr("stroke", function(d){ return color(d.key)})
.attr("stroke-width", 6)
svg1.selectAll('rect')
.filter(function(f) {
return f.Manufacturer === d.key;
})
.attr("fill", function (d) {
return color4(d.Cylinders);
})
svg2.selectAll('circle')
.filter(function(f) {
return f.Manufacturer === d.key;
})
.attr('r', 9)
.attr("fill", function (d) {
return color5(d.Horsepower);
});
})
.on('mouseout', function (d, i) {
svg3.selectAll("path")
.attr("stroke-width", 1.5)
d3.select(this)
.attr("stroke", function(d){ return color(d.key)})
.attr("stroke-width", 1.5)
svg1.selectAll('rect')
.filter(function(f) {
return f.Manufacturer === d.key;
})
.attr("fill", function (d) {
return color1(d.Cylinders);
})
svg2.selectAll('circle')
.filter(function(f) {
return f.Manufacturer === d.key;
})
.attr('r', 5)
.attr("fill", function (d) {
return color2(d.Horsepower);
});
});
Any assistance I can get would be greatly appreciated!!
I think I may have figured out the problem. It would seem that trying to filter the paths causes an issue because the x and y axes are also technically lines, and thus have paths that are null. I tried
svg3.selectAll('path')
.filter(function(f) {
console.log(f)
if(f!=null)
return f.key === d.Manufacturer;
})
.attr("stroke-width",7)
In the .on('mouseover') function, and it seems to be working. The issue was the nulls, not the actual accessing of the keys.
Still taking suggestions if there is a better way to do this!
I have a bubble chart in which I make bubbles in the following way:
var circles = svg.selectAll(null)
.data(data)
.enter()
.append("circle")
.attr("cx", width / 2)
.attr("cy", height / 2)
.attr("opacity", 0.3)
.attr("r", 20)
.style("fill", function(d){
if(+d.student_percentile <= 40){
return "red";
}
else if(+d.student_percentile > 40 && +d.student_percentile <= 70){
return "yellow";
}
else{
return "green";
}
})
.attr("cx", function(d) {
return xscale(+d.student_percentile);
})
.attr("cy", function(d) {
return yscale(+d.rank);
})
.on('mouseover', function(d, i) {
d3.select(this)
.transition()
.duration(1000)
.ease(d3.easeBounce)
.attr("r", 32)
.style("fill", "orange")
.style("cursor", "pointer")
.attr("text-anchor", "middle");
texts.filter(function(e) {
return +e.rank === +d.rank;
})
.attr("font-size", "20px");
}
)
.on('mouseout', function(d, i) {
d3.select(this).transition()
.style("opacity", 0.3)
.attr("r", 20)
.style("fill", "blue")
.style("cursor", "default");
texts.filter(function(e) {
return e.rank === d.rank;
})
.transition()
.duration(1000)
.ease(d3.easeBounce)
.attr("font-size", "10px")
});
I have given colors red, yellow, green to the bubbles based on the student percentile. On mouseover, I change the color of bubble to 'orange'. Now the issue is, on mouseout, currently I am making colors of bubbles as 'blue' but I want to assign the same color to them as they had before mouseover, i.e., red/green/yellow. How do I find out what color, the bubbles had?
One way is to obviously check the percentile of student and then give color based on that(like I have initially assigned green/yellow/red colors), but is there any direct way of finding the actual color of bubble?
Thanks in advance!
There are several ways for doing this.
Solution 1:
The most obvious one is declaring a variable...
var previous;
... to which you assign to the colour of the element on the mouseover...
previous = d3.select(this).style("fill");
... and reuse in the mouseout:
d3.select(this).style("fill", previous)
Here is a demo:
var svg = d3.select("svg");
var colors = d3.scaleOrdinal(d3.schemeCategory10);
var previous;
var circles = svg.selectAll(null)
.data(d3.range(5))
.enter()
.append("circle")
.attr("cy", 75)
.attr("cx", function(d, i) {
return 50 + 50 * i
})
.attr("r", 20)
.style("fill", function(d, i) {
return colors(i)
})
.on("mouseover", function() {
previous = d3.select(this).style("fill");
d3.select(this).style("fill", "#222");
}).on("mouseout", function() {
d3.select(this).style("fill", previous)
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
Solution 2:
However, D3 has a nice feature, called local variables. You simply have to define the local...
var local = d3.local();
..., set it on the mouseover:
local.set(this, d3.select(this).style("fill"));
And then, get its value on the mouseout:
d3.select(this).style("fill", local.get(this));
Here is the demo:
var svg = d3.select("svg");
var colors = d3.scaleOrdinal(d3.schemeCategory10);
var local = d3.local();
var circles = svg.selectAll(null)
.data(d3.range(5))
.enter()
.append("circle")
.attr("cy", 75)
.attr("cx", function(d, i) {
return 50 + 50 * i
})
.attr("r", 20)
.style("fill", function(d, i) {
return colors(i)
})
.on("mouseover", function() {
local.set(this, d3.select(this).style("fill"));
d3.select(this).style("fill", "#222");
}).on("mouseout", function() {
d3.select(this).style("fill", local.get(this));
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
Solution 3:
Since DDD (also known as D3) means data-driven documents, you can use the bound datum to get the previous colour.
First, you set it (in my demo, using the colors scale):
.style("fill", function(d, i) {
return d.fill = colors(i);
})
And then you use it in the mouseout. Check the demo:
var svg = d3.select("svg");
var colors = d3.scaleOrdinal(d3.schemeCategory10);
var circles = svg.selectAll(null)
.data(d3.range(5).map(function(d) {
return {
x: d
}
}))
.enter()
.append("circle")
.attr("cy", 75)
.attr("cx", function(d) {
return 50 + 50 * d.x
})
.attr("r", 20)
.style("fill", function(d, i) {
return d.fill = colors(i);
})
.on("mouseover", function() {
d3.select(this).style("fill", "#222");
}).on("mouseout", function(d) {
d3.select(this).style("fill", d.fill);
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
For using this solution #3, the element's datum has to be an object.
PS: drop that bunch of if...else for setting the style of the bubbles. Use a scale instead.
I have 2 lines in the form of waves plotted in x-y axis based on randomly generated data and i am showing circles on the waves denoting the data points on it.
Based on setInterval of 200 ms, I am updating the original data and the lines(waves) are moving to the left, but the issue is that the only circles which are there in the initial interval are moving and for 2nd interval onward the circles are not showing up on the waves.
see the jsfiddle for the running code : https://jsfiddle.net/rajatmehta/tm5166e1/10/
here is the code :
chartBody.append("path") // Add the valueline path
.datum(globalData)
.attr("id", "path1")
.attr("class", "line")
.attr("d", valueline);
chartBody.selectAll(null)
.data(globalData)
.enter()
.append("circle")
.attr("class", "dot1")
.attr("r", 3)
.attr("cx", function(d) {
console.log(d);
return x(d.timestamp);
})
.attr("cy", function(d) {
return y(d.value);
});
chartBody.selectAll(null)
.data(globalDataNew)
.enter()
.append("circle")
.attr("class", "dot2")
.attr("r", 3)
.attr("cx", function(d) {
return x(d.timestamp);
})
.attr("cy", function(d) {
return y(d.value);
});
chartBody.append("path") // Add the valueline path
.datum(globalDataNew)
.attr("id", "path2")
.attr("class", "line")
.attr("d", valueline2);
any idea how to do that ?
You need to create new circles based on the updated data. Currently, you are only updating the data to selection, but not appending circles, and then moving existing circles to the left.
For example, you could to this:
chartBody.selectAll(".dot1")
.data(globalData, function(d){ return d.timestamp; })
.enter()
.append("circle")
.attr("class", "dot1")
.attr("r", 3)
.attr("cx", function(d) {
return x(d.timestamp);
})
.attr("cy", function(d) {
return y(d.value);
});
chartBody.selectAll(".dot2")
.data(globalDataNew, function(d){ return d.timestamp; })
.enter()
.append("circle")
.attr("class", "dot2")
.attr("r", 3)
.attr("cx", function(d) {
return x(d.timestamp);
})
.attr("cy", function(d) {
return y(d.value);
});
d3.selectAll(".dot1")
//.data(globalData)
.transition()
.duration(duration)
.ease("linear")
.attr("transform", "translate(" + String(dx) + ")");
d3.selectAll(".dot2")
//.data(globalDataNew)
.transition()
.ease("linear")
.duration(duration)
.attr("transform", "translate(" + String(dx) + ")");
See here: https://jsfiddle.net/tm5166e1/11/
This appends the data, using the timestamp as a key so you only create new circles for newly added datums.
(There is an issue when they are first added which is beyond the scope of this question, but it will be worth checking out these examples: https://bl.ocks.org/tomshanley/15a2b09a95ccaf338650e50fd207fcbf and https://bl.ocks.org/mbostock/1642874)
I am trying to render a gantt chart, where I am binding my data in d3 and rendering circle in both end. My data is somewhat similar to this structure:
function Event(start, end) {
this.startTime = start;
this.endTime = end;
}
I bind my data as usual:
myplot.selectAll(".EventStart")
.data(EventList).enter()
.append("circle")
.attr("class", "EventStart")
.attr("cx", function (d) { return scaleX(d.startTime)})
.attr("cy", function (d) { return eventRenderingHeight })
.attr("r", 5)
.style("fill", "white");
myplot.selectAll(".EventEnd")
.data(EventList).enter()
.append("circle")
.attr("class", "EventEnd")
.attr("cx", function (d) { return scaleX(d.endTime)})
.attr("cy", function (d) { return eventRenderingHeight })
.attr("r", 5)
.style("fill", "white");
Now, this will render two white circle at teh begining and end of my events.
But I want to omit rendering the 2nd circle if startTime and EndTime is same.
How can I do it?
Thanks.
You can either filter the dataList before binding
myplot.selectAll(".EventEnd")
.data(EventList.filter(function(d){ return d.startTime!=d.endTime }))
.enter()
.append("circle")
.attr("class", "EventEnd")
.attr("cx", function (d) { return scaleX(d.endTime)})
.attr("cy", function (d) { return eventRenderingHeight })
.attr("r", 5)
.style("fill", "white");
OR
Filter as shown below.
myplot.selectAll(".EventEnd")
.data(EventList)
.enter()
.append("circle")
.filter(function(d) { return d.startTime!=d.endTime })
.attr("class", "EventEnd")
.attr("cx", function (d) { return scaleX(d.endTime)})
.attr("cy", function (d) { return eventRenderingHeight })
.attr("r", 5)
.style("fill", "white");
I have a multiline graph that displays 10 series of data, I am trying to get the lines to update with new data but for some reason I can't get that happening.
The transition with the new data is working for the points on the lines so I assume I am not selecting the right elements but for the life of me I can't figure out where my mistake is.
At one point I had one line changing which indicated it was only updating from the first index of the data array.
Any insight would be appreciated:
Initial Series creation-
var series = svg.selectAll(".series")
.data(seriesData)
.enter().append("g")
.attr("class", "series");
series.append("path")
.attr("id", function (d) {
return d.name;
})
.attr("stay", "false")
.attr("class", "line")
.attr("d", function (d) {
d.line = this;
return line(d.values);
})
.attr("opacity", ".2")
.on("click", function () {
fadeOuts(this);
})
.style("stroke", function (d) {
return strokeCol;
})
.style("stroke-width", "4px")
.style("fill", "none");
Update function:
This is where I am stuck, the points respond to the new data but the paths do not.
series.data(newseriesData);
series.selectAll("path")
.attr("id", function (d) {
return d.name;
})
.attr("d", function (d) {
d.line = this;
return line(d.values);
})
.attr("opacity", ".2")
.on("click", function () {
fadeOuts(this);
})
.style("stroke", function (d) {
return strokeCol;
})
.style("stroke-width", "4px")
.style("fill", "none");
series.selectAll(".point")
.data(function (d) {
return d.values;
})
.transition()
.attr("cx", function (d) {
return x(d.label) + x.rangeBand() / 2;
})
.attr("cy", function (d) {
return y(d.value);
})
.style("fill", function (d) {
return color(d.name);
})
.style("stroke", "grey")
.style("stroke-width", "2px")
.on("mouseover", function (d) {
showPopover.call(this, d);
})
.on("mouseout", function (d) {
removePopovers();
})
Yes this is a university project, this is the last piece of work in a solid 50+ hour effort on this and I'd just like to get it knocked out.
The short answer is that instead of series.selectAll("path") you should use series.select("path"). Remember that series is already a selection, and the subselection is done for each element in it. You've appended exactly one element to each of the selection, so .select() is fine and no .selectAll() is required.
The main difference this makes is that .select() inherits the data from the parent selection, while .selectAll() doesn't -- when doing .selectAll() the data is simply not updated and therefore no change occurs.