Drew a straight line, but it is crooked d3 - d3.js

I tried to make a straight line with this:
enter.append('line')
.attr('class', 'lineClass')
.style("stroke", "blue")
.style("stroke-width", "1.5px;")
.attr('x1', function(d) { return 500; })
.attr('y1', function(d) { return 50; })
.attr('x2', function(d) { return 800; })
.attr('y2', function(d) { return 40; });
The line attrs will be actual functions with data. Look at my image, why is the line crooked? If there is no problem with the code, do you have any ideas as to what could be causing this?

The reason is you are drawing same line again and again on the same x1/x2/y1/y2 for dataset.
This will make your line crooked:
var svg = d3.select('svg');
var dataSet = [10,20,30,20,30,20,30,20,30,20,30,20,30,20,30,20,30];//many data 17 times you will draw line.
var myLine = svg.selectAll('line')
.data(dataSet)
.enter()
.append('line')
.style("stroke", "blue")
.attr('x1', function(d) { return 100; })
.attr('y1', function(d) { return 200; })
.attr('x2', function(d) { return 300; })
.attr('y2', function(d) { return 40; });
Working example here
Now the crookedness will go coz you are making a single line on the x1/x2/y1/y2
var svg = d3.select('svg');
var dataSet = [10];//you will draw line ones
var myLine = svg.selectAll('line')
.data(dataSet)
.enter()
.append('line')
.style("stroke", "blue")
.attr('x1', function(d) { return 100; })
.attr('y1', function(d) { return 200; })
.attr('x2', function(d) { return 300; })
.attr('y2', function(d) { return 40; });
Working example here
So in short you should not be drawing same line over and over again...
Hope this helps!

First of all, this is not to blame on D3 at all. This effect is known as aliasing and is very common to all sorts of computer graphics. There is a vast arsenal of countermeasures against it, which are referred to as anti-aliasing. However, it will always be a trade-off between precision and aesthetics.
For SVGs you are — at least to a certain degree — able to control the way the browser or any other user agent deals with this by setting the shape-rendering attribute. Have a look at this example which demonstrates the effect:
line {
stroke: blue;
stroke-width: 1;
}
text { font-family: monospace; }
<svg width="500" height="210" xmlns="http://www.w3.org/2000/svg">
<text y="25">auto</text>
<line shape-rendering="auto" x1="150" y1="20" x2="450" y2="30" />
<text y="75">optimizeSpeed</text>
<line shape-rendering="optimizeSpeed" x1="150" y1="70" x2="450" y2="80" />
<text y="125">crispEdges</text>
<line shape-rendering="crispEdges" x1="150" y1="120" x2="450" y2="130" />
<text y="175">geometricPrecision</text>
<line shape-rendering="geometricPrecision" x1="150" y1="170" x2="450" y2="180" />
</svg>
Because line #3 having shape-rendering="crispEdges" closely resembles your screenshot, this is most likely the cause of your problem. For this value the SVG spec states:
To achieve crisp edges, the user agent might turn off anti-aliasing for all lines...
To get a smoother line, try setting another value for this property. Please note, that this property is inherited. There is no need to repeat yourself setting this on every element. You might just as well define this somewhere up the DOM hierarchy or even on the root SVG element itself. Additionally, you may opt for setting this via a CSS rule instead of specifying it as an attribute to an element.

The line in your picture is not crooked - it is straight between the points (500,50) and (800,40).

Related

Using scaleQuantile with "viridis" color scheme

I have data whose values have a range (0, 100) but most of them have values ranging between 80 and 100.
Example of data: 97.00 93.30 92.20 92.70 91.10 89.10 89.90 89.10 89.70 88.90
89.00 89.30 88.76 88.46 87.45 85.05
I have to do a visualization using colors and using a linear scale is not the best because it does not allow me to distinguish colors quite easily.
So I thought about using a scaleQuantile.
I read this post that uses colors from black to red but I would like to use the Viridis scale.
How can I do that?
This is my piece of code:
var colorScale = d3.scaleQuantile(d3.interpolateViridis)
.domain([0, 100]);
// other code
var cells = svg.selectAll('rect')
.data(data)
.enter().append('g').append('rect')
.attr('class', 'cell')
.attr('width', cellSize)
.attr('height', cellSize)
.attr("rx", 4)
.attr("ry", 4)
.attr('y', function(d) {
return yScale(d.nuts_name);
})
.attr('x', function(d) {
return xScale(d.year);
})
.attr('fill', function(d) {
return colorScale(d.value);
}
})
Thanks
You have two problems here:
The domain in a quantile scale, unlike a quantize scale, is not a range between two values. It has to be the array with all the values. The API is clear about that:
If domain is specified, sets the domain of the quantile scale to the specified set of discrete numeric values. (emphasis mine)
That's not the correct way to use d3.interpolateViridis. Again, the API is clear:
Given a number t in the range [0,1], returns the corresponding color from the “viridis” perceptually-uniform color scheme
So, a simple solution is creating the quantile scale in such a way that it returns a number from 0 to 1 according to your data array (here, I'm creating 10 bins):
var colorScale = d3.scaleQuantile()
.domain(data)
.range(d3.range(0, 1.1, 0.1));
And then pass that value to d3.interpolateViridis:
d3.interpolateViridis(colorScale(d))
Here is a demo. The first row of <divs> use the data as they are, the second one uses a sorted array:
var data = [97.00, 93.30, 92.20, 92.70, 91.10, 89.10, 89.90, 89.10, 89.70, 88.90, 89.00, 89.30, 88.76, 88.46, 87.45, 85.05];
var sortedData = data.concat().sort();
var colorScale = d3.scaleQuantile()
.domain(data)
.range(d3.range(0, 1.1, 0.1));
var divs = d3.select("body").selectAll(null)
.data(data)
.enter()
.append("div")
.attr("class", "cell")
.style("background-color", function(d) {
return d3.interpolateViridis(colorScale(d))
});
d3.select("body").append("div")
.style("height", "40px")
var div2 = d3.select("body").selectAll(null)
.data(sortedData)
.enter()
.append("div")
.attr("class", "cell")
.style("background-color", function(d) {
return d3.interpolateViridis(colorScale(d))
});
.cell {
width: 20px;
height: 20px;
margin: 2px;
display: inline-block;
}
<script src="https://d3js.org/d3.v4.min.js"></script>

d3.js trying to call data method twice on same selector produces strange results

I'm new to D3 and am trying to build a table like structure out of rectangles. I would like the header to be a different color than the rest of the rectangles. I've written the following code:
table = svgContainer.selectAll('rect')
.data([managedObj])
.enter()
.append('rect')
.attr("width", 120)
.attr("height", 20)
.attr("fill", "blue")
.text(function(d) {
return d.name;
});
// create table body
table.selectAll('rect')
.data(managedObj.data)
.enter()
.append('rect')
.attr("y", function() {
shift += 20;
return shift;
})
.attr("width", 120)
.attr("height", 20)
.attr("fill", "red")
.text(function(d) {
return d.name;
});
This is producing the following results:
This is almost what I intended except it is nesting the second group of rectangles inside the first rectangle. This causes only the first blue rectangle to be visible. I'm assuming this has something to do with calling the data method twice. How can I fix this issue?
I think I understand the intended result, so I'll give it a go:
This line :
table.selectAll('rect')
is selecting the rectangle just created here:
table = svgContainer.selectAll('rect')....append('rect')....
You don't want to append rectangles to that rectangle (or any rectangle for that matter) because this won't work, but you do want to append them to the SVG itself.
So instead of table.selectAll you should be using svgContainer.selectAll, but there are two other issues:
if you use svgContainer.selectAll('rect') you will be selecting the rect you have already appended, when you actually want an empty selection. See the answer here.
you cannot place text in a rect (See answer here), instead you could append g elements and then append text and rect elements to those. And, for ease of positioning, you could translate the g elements so that positioning the rectangles and text is more straight forward.
So, your code could look like:
var data = ["test1","test2","test3","test4"];
var svgContainer = d3.select('body').append('svg').attr('width',900).attr('height',400);
var header = svgContainer.selectAll('g')
.data([data])
.enter()
.append('g')
.attr('transform','translate(0,0)');
header.append('rect')
.attr("width", 120)
.attr("height", 20)
.attr("fill", "blue");
header.append('text')
.attr('y',15)
.attr('x',5)
.text(function(d) {
return "header";
});
// create table body
var boxes = svgContainer.selectAll('.box')
.data(data)
.enter()
.append('g')
.attr('class','box')
.attr('transform',function(d,i) { return 'translate(0,'+((i+1)*20)+')'; });
boxes.append('rect').attr("width", 120)
.attr("height", 20)
.attr("fill", "red");
boxes.append('text')
.attr('y',15)
.attr('x',5)
.text(function(d) {
return d;
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

d3.js add a label in the center of a path

how to add a label in the center of path programmatically without using the BBOX method because it does not work with banana shapes
d3.json("mapgeo.json", function(json) {
//Bind data and create one path per GeoJSON feature
paths = g.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr('name', function(d) {
return d.properties.name.toLowerCase();
})
.attr("d", path)
.attr("id", function(d, i) { return 'polygon'+i;})
.style("fill", "steelblue");
for(var i=0;i<paths[0].length;i++){
var pp = paths[0][i].__data__.properties;
svg
.append('text')
.attr("x", 145)
.attr("dy", 105)
.append("textPath")
.attr("xlink:href","#polygon"+i)
.text(paths[0][i].__data__.properties.temperature+' C°');
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width="400" height="300">
<g>
<path name="cf40" d="M590.3383838385344,295.20151514932513 C 756 327,756 327, 878.5818181820214,279.5361111164093L822.186363636516,527.0494949556887L728.1939393933862,555.2472222223878Z" id="polygon2" style="fill: steelblue;" transform="translate(-500,-260)"></path>
</g>
<text x="145" dy="105"><textPath xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#polygon2">CF40</textPath></text>
</svg>
(I confess that I quite didn't understand what you want to achieve with your code, so, I'm going to address specifically your question's title: "how to add a label in the center of a path").
D3 have a handy function for locating the center of the path, called path.centroid:
Returns the projected planar centroid (typically in pixels) for the specified GeoJSON object. This is handy for, say, labeling state or county boundaries, or displaying a symbol map.
You can use it to position your labels:
.attr("x", function(d) {
return path.centroid(d)[0];
})
.attr("y", function(d) {
return path.centroid(d)[1];
})
Here is a demo with a USA map (just found the code online). I'm locating the center of each path using centroid and labelling it with "foo":
var width = 500,
height = 400;
var projection = d3.geoAlbersUsa()
.scale(700)
.translate([width / 2, height / 2]);
var path = d3.geoPath()
.projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("https://dl.dropboxusercontent.com/u/232969/cnn/us.json", function(error, us) {
svg.selectAll(".state")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path)
.attr('class', 'state');
svg.selectAll(".stateText")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("text")
.attr("x", function(d) {
return path.centroid(d)[0];
})
.attr("y", function(d) {
return path.centroid(d)[1];
})
.attr("text-anchor", "middle")
.attr("font-size", "12px")
.text("foo")
});
.state {
fill: none;
stroke: black;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>

D3.js: how to follow general update pattern to transition nested elements?

I am working with D3.js, and learning the general update pattern.
I understand it for simple data structures (I think!), but I would like to create a nested set of DOM elements: groups which contain paths and text elements. I'm not clear on how to access the update/enter/exit selection for the nested elements.
In summary, this is the SVG structure that I want to end up with:
<g class="team">
<path class="outer" d="..."></path>
<text class="legend" x="890" dy=".2em" y="23">Red Sox</text>
</g>
<g class="team">
<path class="outer" d="..."></path>
<text class="legend" x="890" dy=".2em" y="23">Yankees</text>
</g>
And I'd like to be able to access the update/enter/select selection explicitly on each element. My data looks like this:
[
{ "name": "Red Sox", "code": "RED", "results": [...] },
{ "name": "Yankees", "code": "YAN", "results": [...] },
]
And this is my code - see it in full at the jsfiddle: http://jsfiddle.net/e2vdt/6/
function update(data) {
// Trying to follow the general update pattern:
// http://bl.ocks.org/mbostock/3808234
// Join new data with old elements, if any.
var g = vis.selectAll("g.team").data(data, function(d) {
return d.code;
});
// Update old elements as needed.
// QUESTION: How to get the update selection for the text elements?
// Currently the text element is not moving to the right position.
g.selectAll("text.legend").transition()
.duration(750)
.attr("y", function(d, i) { return i * 32 + 100; });
// Create new elements as needed.
var gEnter = g.enter()
.append("g").attr("class","team");
gEnter.append("text").attr("class", "legend")
.attr("dy", ".35em")
.attr("y", function(d, i) { return i * 32 + 100; })
.attr("x", "20")
.style("fill-opacity", 1e-6)
.text(function(d) { return d.name; })
.transition()
.duration(750)
.style("fill-opacity", 1);
// TBA: Add path element as well.
// Remove old elements as needed.
g.exit()
.transition()
.duration(750)
.attr("y", "0")
.style("fill-opacity", 1e-6)
.remove();
}
The enter and exit selections are both working fine, but I don't know how to get the update selection so I can move the text labels to the correct position. The "Red Sox" entry should be moving down the page, but it isn't.
It's working fine, you just have to change how you position the g element.
g.transition()
.duration(750)
.attr("transform", function(d, i) {
return "translate(0,"+(i * 32 + 100)+")";
});
The <g> element has no x & y attributes, so you must position it using svg transforms.
Here's some more information about it: https://developer.mozilla.org/en-US/docs/SVG/Element/g
The adjusted example: http://jsfiddle.net/e2vdt/10/

d3.js axis - making it have 0 ticks and no markings

Is it possible to create a d3.js axis and have there be no tick marks and no numbering scheme? Basically, can I make the axis invisible? I'm using the code below to create my axes:
svg.selectAll("axis")
.data(d3.range(angle.domain()[1]))
.enter().append("g")
.attr("class", "axis")
.attr("transform", function(d) { return "rotate(" + angle(d) * 180 / Math.PI + ")"; })
.call(d3.svg.axis()
.scale(radius.copy().range([0,0]))
.ticks(1)
.orient("left"))
.append("text")
.style("color", "white")
.attr("y",
function (d) {
if (window.innerWidth < 455){
console.log("innerWidth less than 455: ",window.innerWidth);
return -(0);
}
else{
console.log("innerWidth greater than 455: ",window.innerWidth);
return -(0);
}
})
.attr("dy", "0em");
If you don't want your axis to be visible, just don't draw them (basically comment out this code).
If you really just want to turn them white, you can use the following classes:
.axis line, .axis text, .axis path {
color: white;
}
This would be the easiest way to manipulate them to turn them 'on' and 'off'. Also, if you ever need to figure out how to style a d3 diagram, you can navigate through the SVG just like you do html and style with CSS the same way too.
For example, here is the SVG for the tick marks in the axis.
<line class="tick" y2="6" x2="0"></line>
You can see that I targeted the element (line) but you could also target (.tick) as well.

Resources