Individual symbols are clipped in d3js - javascript

I am basically trying to clip a chart containing "path" elements representing data points.
It seems like, path elements are not initially "transformed" and remain at (0,0) and are thus clipped by rectangle surrounding entire chart.
Basically, I have replaced "circle" elements as drawn in D3 Brush Example with "path" elements of symbol type "circle".
.append("circle")
.attr("class", "point")
.attr("clip-path", "url(#clip)")
.attr("r", function(d){return Math.floor(Math.random() * (20 - 5 + 1) + 5);})
.attr("cx", function(d) { return x(d.index); })
.attr("cy", function(d) { return y(d.value); })
replaced with
.append("path")
.attr("class", "point")
.attr("clip-path","url(#clip)")
.attr("d", d3.svg.symbol().type("circle"))
Here is jsfiddle. As you can see, only lower right quarter of the circle is visible and rest have been clipped out. What is going on? What is the solution?
Edit:
Also, the actual "clipping" is not working for those circles(paths). Why is that? Why does it work for "circle" but not for "path"?

Related

Tooltip for Line Chart with Clip Path in D3

I have put together a D3 line chart and added threshold encoding using clip path / clipping. The only problem I am facing is I am not able to add tooltips to this chart. I want a tooltip when I hover anywhere in the chart and the corresponding y axis value on the chart shows up in the tooltip.
I have added threshold encoding using this example by Mike Bostock.
var line = d3.svg.line()
.interpolate("basis")
.x(function(d) { return _config.xScale(d.vtc); })
.y(function(d) { return _config.yScale(d.values); });
svg.append("clipPath")
.attr("id", "clip-above")
.append("rect")
.attr("width", _config.width)
.attr("height", _config.yScale(55));
svg.append("clipPath")
.attr("id", "clip-below")
.append("rect")
.attr("y", _config.yScale(55))
.attr("width", _config.width)
.attr("height", _config.height - _config.yScale(55));
svg.selectAll(".line")
.data(["above", "below"])
.enter().append("path")
.attr("class", function(d) { return "line " + d; })
.attr("clip-path", function(d) { return "url(#clip-" + d + ")"; })
.datum(data)
.attr("d", line);
I didn't know how to go about adding a tooltip for this particular chart as there is clip rectangle over the path and the path is broken down into above and below segment to give the colour effects.
Do we have a unified way to add a tooltip to normal path and this one? If yes I would like to know some sources/links I can look at.
Something like this, but not that complicated (without any indicator on the line, just the tooltip)
My CODEPEN LINK
You can add mouseOver handler for the line and translate back the mouse y position to yAxis value using the .invert function of d3 linear scale. Now, you can dynamically add a tooltip text element and set the position, value to it
Here is the updated Codepen link
NOTE: You still need to increase the capture area of the line. This can be done by adding a transparent stroke to the line.
svg.selectAll(".line")
.data(["above", "below"])
.enter().append("path")
.attr("class", function(d) { return "line " + d; })
.attr("clip-path", function(d) { return "url(#clip-" + d + ")"; })
.datum(data)
.attr("d", line)
.on("mouseover", function() {
var mousePos = d3.mouse(this);
var yAxisValue = _config.yScale.invert(mousePos[1]);
svg.selectAll(".tooltip").data([mousePos])
.enter().append("text")
.classed("tooltip", true)
.attr("x", function(d) { return d[0]})
.attr("y", function(d) { return d[1]})
.text(yAxisValue);
})
.on("mouseout", function() {
svg.selectAll(".tooltip").data([]).exit().remove();
});

How to show text on a SVG path

So I have points on my SVG map and now I would like to show text next to them. This is a jsfiddle with 2 points and showing their ID text. But as you can see there is no text somehow.
var featureCollection = topojson.feature(topology, topology.objects.testtest);
lines.append("g")
.attr("id", "lines")
.selectAll("path")
.data(featureCollection.features)
.enter().append("path")
.attr("d", path)
.append("text")
.attr("class", "nodetext")
.attr("x", 22)
.attr("y", 4)
.text(function (d) {
return d.properties.id;
});
And I checked it with some other text beside example I already have here. It's working in the same way.
So does it not work with pathes? Could that be?
A 'text' element can't be a child of a 'path' element, it should be a sibling. Group them if they are related and need to be positioned accordingly.
As #liamness says, your text can't be a child of path but needs to be a sibling. Your problem goes a little further, though, since you are using a path and you can't group and position the element conventionally. There is where path.centroid comes in handy. It allows you to find the center of you path and position your text there:
var e = lines.append("g")
.attr("id", "lines")
.selectAll("path")
.data(featureCollection.features)
.enter(); // save enter selection
e.append("path") // add path as child of lines g
.attr("d", path);
e.append("text") // add text as child of lines g, sibling of path
.attr("class", "nodetext")
.attr('x', function(d,i){
return path.centroid(d)[0]; // horizontal center of path
})
.attr('y', function(d,i){
return path.centroid(d)[1] + 13; // vertical center of path
})
.attr('text-anchor','middle')
.text(function (d) {
return d.properties.id;
});
Updated fiddle.

d3 lines and circles data

I am trying to draw a set of lines and circle points, but I cant figure out how to get the circles to work.
The line function needs an array of points, but for the circle it needs just the x/y of each point.
How do I append a circle (to the same group as the line), for each x/y point?
// Data join
var join = svg.selectAll("g")
.data(lineData)
// Enter
var group = join.enter()
.append("g");
group.append("path")
.attr('stroke', 'blue')
.attr('stroke-width', 2)
.attr('fill', 'none');
group.append('circle')
.attr("r", 10)
.attr('fill', 'blue');
// Update
join.select("path")
.attr('d', line);
join.select("circle")
.attr("cx", function(d) { return x(d.x); })
.attr("cy", function(d) { return y(d.y); });
Full code is here: http://jsfiddle.net/dxxddvL4/1/
The basic pattern you need to use are nested selections -- for each line, there are multiple circles. It's easier to do the lines and circles separately, lines and g elements first:
var join = svg.selectAll("g")
.data(lineData);
// Enter
join.enter()
.append("g")
.append("path")
.attr('stroke', 'blue')
.attr('stroke-width', 2)
.attr('fill', 'none');
// Update
join.select("path")
.attr('d', line);
join.exit().remove();
The code is basically the same as yours, except that the appended g elements aren't saved in a separate selection and the exit selection is handled by removing the elements. Now the circles, along the same lines:
var circles = join.selectAll("circle")
.data(function(d) { return d; });
circles.enter()
.append('circle')
.attr("r", 10)
.attr('fill', 'blue');
circles.attr("cx", function(d) { return x(d.x); })
.attr("cy", function(d) { return y(d.y); });
circles.exit().remove();
The first line here is the nested selection -- for each element in the array that denotes the line, we want a circle. Note that this is operating on the update selection of the g elements. This is ok because the elements in the enter selection are merged into the update selection when the g elements are appended. That is, even though we only handle the update selection, any newly-appended elements are included in this.
After that, we handle the selections as usual. The enter selection has elements appended, the update selection sets the coordinates, the exit selection removes elements. All the magic happens in that first line, where we tell D3 to, for each g element at the top level, bind each point from the line to any circles underneath.
Complete example here.

d3.js create objects on top of each other

I create rectangles in my SVG element using this code:
var rectangles = svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect");
rectangles.attr("x", function (d) {
return xScale(getDate(d));
//return xScale(d.start);
})
.attr("y", function (d, i) {
return (i * 33);
})
.attr("height", 30)
.transition()
.duration(1000)
.attr("width", function (d) {
return d.length;
})
.attr("rx", 5)
.attr("ry", 5)
.attr("class", "rectangle")
.attr("onclick", function (d) {
return "runaction(" + d.start + ")";
});
How can I create new rectangles on top of the previous ones?
This is an answer to this question I got from Scott Murray, author of great introductory tutorials for d3.js http://alignedleft.com/tutorials/d3/ which helped me a lot with understanding its functionality. I hope he won't mind me putting his answer here for everyone's benefit.
Thank you very much Scott!
And yes, that's absolutely possible. Taking your example, let's say you want to draw one set of circles with the dataset called "giraffeData" bound to them. You would use:
svg.selectAll("circle")
.data(giraffeData)
.enter()
.append("circle");
But then you have a second data set (really just an array of values) called "zebraData". So you could use the same code, but change which data set you reference here:
svg.selectAll("circle")
.data(zebraData)
.enter()
.append("circle");
Of course, this will inadvertently select all the circles you already created and bind the new data to them — which isn't really what you want. So you'll have to help D3 differentiate between the giraffe circles and the zebra circles. You could do that by assigning them classes:
svg.selectAll("circle.giraffe")
.data(giraffeData)
.enter()
.append("circle")
.attr("class", "giraffe");
svg.selectAll("circle.zebra")
.data(zebraData)
.enter()
.append("circle")
.attr("class", "zebra");
Or, you could group the circles of each type into a separate SVG 'g' element:
var giraffes = svg.append("g")
.attr("class", "giraffe");
giraffes.selectAll("circle")
.data(giraffeData)
.enter()
.append("circle");
var zebras = svg.append("g")
.attr("class", "zebra");
zebras.selectAll("circle")
.data(zebraData)
.enter()
.append("circle");
I'd probably choose the latter, as then your DOM is more cleanly organized, and you don't have to add a class to every circle. You could just know that any circle inside the g with class zebra is a "zebra circle".

d3.js animate/transition circles on update

I have a simple line graph that checks every 5 seconds for updates & redraws the line/scale if needed. This all works well EXCEPT: the data-point dots.
What am I missing in the redraw to move the dots? The dots are there when the graph is first rendered. But on update, they don't move when the line gets redrawn. So I selected a new data source on update, and the old data-points remained fixed.
Redraw on update
var svgAnimate = d3.select("#animateLine").transition();
svgAnimate.select(".line") // change the line
.duration(750)
.attr("d", valueline(data));
svgAnimate.selectAll(".circle") // change the circle
.duration(750)
.attr("d", valueline(data));
svgAnimate.select(".x.axis") // change the x axis
.duration(750)
.call(xAxis);
svgAnimate.select(".y.axis") // change the y axis
.duration(750)
.call(yAxis);
Initial drawing:
svgAnimate.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("class", "circle")
.attr("r", 5)
.style("fill", function(d) {
if (d.close <= 400) {return "red"}
else { return "black" }
;})
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.close); })
This is what I don't want.
Your problem is that the function valueLine is the function you use to draw the line. Thus, when calling it again with a new data you redraw the line.
For the circles the attribute d has no sense. However, if we consider that the y axis does not change, then you can do something like:
svgAnimate.selectAll(".circle") // change the circle
.data(newData)
.duration(750)
.attr("cx", function(d){return x(d.date)};
If you need to change the y coordinates, then you have to modify the cy attribute of the circle.
My code might not be as rigorous as necessary, please post a jsFiddle if you still have problems.
I had some issues with updating circles in charts too.
Here is a working fiddle and might some people for future searches if they have the same problem:
http://jsfiddle.net/noo8k17n/
The problem is this line:
var svgAnimate = d3.select("#animateLine").transition();
It needs to be removed and then in the update method you can add and remove circles.

Categories