d3 Voronoi overlay min distance on Chrome? (Firefox works) - javascript

I am trying to add an Voronoi overlay on my chart, but it seems like when the interval between points are smaller than 20px, some path will never get selected, especially for the points in the start and of the end. Thanks to musically_ut, who notice that this is only happening on Chrome browser.
I went back to play with the original post on Voronoi, and found the same problem (JSFiddle).
var vertices = d3.range(100).map(function(d, i) {
// return [Math.random() * w, Math.random() * h]; //original line
return [i * 10, 10]; // change this line!
});
I thought that this has something to do with the r value of the clipPath, but it was not the case: the problem still persist with a r value of 5.

I went back to the documentation, and turns out that it recommended to add a clipPath when setting up the Voronoi function, which will solve the problem.
Instead of:
d3.geom.voronoi(data)
use
var padding = 0;
var vor = d3.geom.voronoi().clipExtent([[padding, padding], [w - padding, h - padding]])
paths.selectAll("path")
.data(vor(vertices))
JSFiddle

I am posting my workaround on the problem, for anyone who faced a similar situation needing a quick fix. I am still looking for the reason on why this is happening.
My workaround was to create the overlay area manually, in this case the layout logic was fairly easy.
var col = 64;
var cr = 3;
var rwidth = (w / col);
var offset = (rwidth / 2);
// overlay: a rect that is placed around the circle space
svg.selectAll("blank")
.data(d3.range(nbr))
.enter()
.append("rect")
.attr({
"x": function(d,i){ return (i % col) * (rwidth) - offset; },
"y": function(d,i){ return (Math.floor(i / col) + 1) * (rwidth) - offset; },
"width": rwidth,
"height": rwidth,
"fill": "white"
})
.on("mousemove", function(d, i){
// do what you want to do when overlay is triggered
d3.selectAll(".glss" + i)
.transition()
})
// the actual circle
svg.selectAll("blank")
.data(d3.range(nbr))
.enter()
.append("circle")
.attr({
"cx": function(d,i){ return (i % col) * (w / col)},
"cy": function(d,i){ return (Math.floor(i / col) + 1) * (w / col); },
"r": cr,
"fill": function(d,i){ return "#7E07A9"; },
"class": function(d,i){ return "glss glss" + i; }
})

Related

rotate and zoom svg with d3 javascript

I want to rotate and zoom graphic around its center with D3.js. When I zoom graphic I want to zoom it with current aspect ratio and vice versa when I rotate graphic I want to zoom it to the current point that my mouse points. For zooming I use wheel of the mouse and for rotation I use the button of the mouse.
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
transform = d3.zoomIdentity;
var points = d3.range(2000).map(phyllotaxis(10));
var g = svg.append("g");
g.append("line")
.attr("x1", "20")
.attr("y1", "20")
.attr("x2", "60")
.attr("y2", "60")
.attr("stroke", "black")
.attr("stroke-width", "10");
svg.call(d3.drag()
.on("drag",onDrag)
)
// ##########################
var boxCenter = [100, 100];
// #############################
function onDrag(){
var x = d3.event.sourceEvent.pageX,
y = d3.event.sourceEvent.pageY;
var angle = Math.atan2(x - boxCenter[0],
- (y - boxCenter[1]) )*(180/Math.PI);
g.attr("transform", "rotate("+angle+")");
}
svg.call(d3.zoom()
.scaleExtent([1 / 2, 8])
.on("zoom", zoomed));
function zoomed() {
g.attr("transform", d3.event.transform);
}
function phyllotaxis(radius) {
var theta = Math.PI * (3 - Math.sqrt(5));
return function(i) {
var r = radius * Math.sqrt(i), a = theta * i;
return {
x: width / 2 + r * Math.cos(a),
y: height / 2 + r * Math.sin(a)
};
};
}
Here is my example:
https://jsfiddle.net/6Lyjz35L/
For the rotation around center to be correct at the initial zoom you need to add a 'transform-origin' attribute to 'g'.
g.attr("transform-origin", "50% 50%");
The other problems you're having stem from assigning the 'transform' attribute in two separate places. An element ('g') can only have one 'transform' attribute applied at a time, so you're overwriting one or the other each time you rotate or zoom. To fix this you can create a helper method which will append both of the transforms you want in a single string.
var currentAngle = 0;
var currentZoom = '';
function getTransform(p_angle, p_zoom) {
return `${p_zoom} rotate(${p_angle})`;
// return p_zoom + " rotate(" + p_angle + ")";
}
// In the rotate:
currentAngle = angle;
g.attr("transform", getTransform(currentAngle, currentZoom));
// In the zoom:
currentZoom = d3.event.transform;
g.attr("transform", getTransform(currentAngle, currentZoom));
There is one more issue which is introduced by the zoom, and that is that you'll have to calculate a new transform-origin at different zoom levels.
The issue I said was introduced by the zoom was actually the result of applying the operations in the incorrect order. Originally I applied the rotation and THEN then translation. It actually needs to be reversed, translation and THEN rotation. This will keep the correct transform-origin.
Here's a fiddle with those changes: https://jsfiddle.net/scmxcszz/1/

d3.js radial clock, adding dots instead of clock hands

I'm trying to make a radial clock, but instead of clock arms, I need to have dots at the end of every path indicating time (blue). Thanks for the help!
Edit: like this: https://puu.sh/sH03Y/c59281fb5e.png
Line drawing part:
var clockLine = linesLayer.selectAll('.clock-line')
.data(fields);
clockLine.enter().append('line')
.attr('class', function (d) { return 'line clock-line ' + d.id; })
.attr('x1', 0).attr('y1', 0);
Full fiddle: http://jsfiddle.net/zh3owyr3/
Instead of a <line>, append a <circle>:
var clockCircle = linesLayer.selectAll('.clock-line')
.data(fields);
clockCircle.enter().append('circle')
.attr('class', function(d) {
return 'circle clock-circle ' + d.id;
})
.attr("r", 6)
.attr("fill", "teal");
And change its position in the tick function:
clockCircle.attr('cx', function(d) {
return d.index * radius * Math.cos(d.value * 2 * Math.PI - Math.PI / 2);
})
.attr('cy', function(d) {
return d.index * radius * Math.sin(d.value * 2 * Math.PI - Math.PI / 2);
});
Here is your updated fiddle: http://jsfiddle.net/mjru5ta8/
PS: you'll have to change your viewbox to avoid the seconds' circle being cropped.

How to center (horizontal and vertical) text along an textPath inside an arc using d3.js?

After playing a while with d3.js and looking at a lot of examples, I was able to draw multiple arcs. Each one starting and ending at an specific degree and and given radius.
var dataset = {
"2":[{"degree1":0, "degree2":1.5707963267949,
"label":"Sample Text Test"},
{"degree1":1.5707963267949, "degree2":3.1415926535898,
"label":"Lorem ipsum sample text"},
{"degree1":3.1415926535898, "degree2":4.7123889803847,
"label":"Sample Text Text"},
{"degree1":4.7123889803847, "degree2":6.2831853071796,
"label":"Lorem ipsum"}],
"1":[{"degree1":0, "degree2":3.1415926535898,
"label":"Sample"},
{"degree1":3.1415926535898, "degree2":6.2831853071796,
"label":"Text"}],
"0":[{"degree1":0, "degree2":6.2831853071796,
"label":""}]
},
width = 450,
height = 450,
radius = 75;
// Helper methods
var innerRadius = function(d, i, j) {
return 1 + radius * j;
};
var outerRadius = function(d, i, j) {
return radius * (j + 1);
};
var startAngle = function(d, i, j) {
return d.data.degree1;
};
var endAngle = function(d, i, j) {
return d.data.degree2;
};
var pie = d3.layout.pie()
.sort(null);
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius)
.startAngle(startAngle)
.endAngle(endAngle);
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + (width >> 1) + ',' + (height >> 1) + ')');
var level = svg.selectAll('g')
.data(function(d) {
return d3.values(dataset);
})
.enter()
.append('g');
var entry = level.selectAll('g')
.data(function(d, i) {
return pie(d);
})
.enter()
.append('g');
entry.append('path')
.attr('fill', '#aaa')
.attr('d', arc)
.attr('id', function(d, i, j) {
return 'arc' + i + '-' + j;
});
var label = entry.append('text')
.style('font-size', '20px')
.attr('dx', function(d, i, j) {
return Math.round((d.data.degree2 - d.data.degree1) * 180 / Math.PI);
})
.attr('dy', function(d, i, j) {
return ((radius * (j + 1)) - (1 + radius * j)) >> 1;
});
label.append('textPath')
.attr('xlink:href', function(d, i, j) {
return '#arc' + i + '-' + j;
})
.style('fill', '#000')
.text(function(d) {
return d.data.label;
});
See http://jsfiddle.net/3FP6P/2/ :
But some problem still exists:
How to center (horizonal und vertical) an text along an textpath of an any length inside an arc described by innerRadius, outerRadius, startAngle and endAngle?
The text occurs sometimes bold, sometimes not. Why?
The character spacing does not appear to be the same as if it is written inside a . Some letters stick more together as other ones. Why?
The letters are not located directly on the path. Some seem to have a little slip up or down. Why?
Vertical alignment
You can use another arc with radius (innerRadius + outerRadius) / 2 and use it as the textPath for the labels.
Note that even if you set the innerRadius == outerRadius, D3 will draw an path which moves clockwise and then anti-clockwise to doubles over itself. This becomes important while trying to figure out the horizontal centre of the path: it is at the 25% and 75% points while 0% and 50% points lie on the two tips of the arc.
Horizontal alignment
Use text-anchor: middle on the text element and set startOffset to 25% (or 75%) on the textPath.
Demo.
This is a more robust way than calculating the dx and dy by hand.
You should try out Lars's suggestions to further improve the quality and centring of the text, e.g. you might want to set text-rendering to optimizeLegibility and play with the baseline a bit.
Issues 2-4 are because of the font rendering. In my browser, the spacing and character size etc is consistent. You can try playing around with the text-rendering attribute to improve things.
To get the text centred, you will need to set the alignment-baseline and/or dominant-baseline attributes.
If this still doesn't give you the results you're looking for, try decreasing the font size. This may help because a slight rotation of a character will be less visible.

Undesired shimmering effect using D3 to create adjacent rectangles

I'm generating a musical waveform using D3 by pushing rectangles next to one another. Here's the fiddle: http://jsfiddle.net/s4dML/
var data = [ 0.0534973, /* ...lots and lots of data... */ 0.290771];
data = data.filter(function(datum, index){
return index % 3 == 0;
});
var width = 340,
height = 70,
svg = d3
.select('body')
.append('svg')
.attr('width', width)
.attr('height', height);
svg
.selectAll('rect')
.data(data.map(function(datum){
return (datum * height)/2;
}))
// .data(dataset)
.enter()
.append('rect')
.attr('x', function(d, i){
return i * (width / data.length);
})
.attr('y', function(d){
return (height /2) - d ;
})
.attr('width', function(d, i){
return width / data.length;
})
.attr('height', function(d){
return d*2;
})
.attr('fill', 'teal');
Does anyone know why the result ins't single, flat color as expected? There is a kind of shimmering effect throughout. This might be desirable, but regardless I'd like to know how it got there and how to get rid of it if I'm so inclined.
This is an artifact of SVG rendering (or really, any vector graphics rendering). Suppose that you have two rects that meet 40% of the way into a pixel. Then the first rect will paint into that pixel with 40% opacity, and the second with 60% opacity, meaning that the pixel is only (40 + 0.6 * 60 =) 76% colored, even though logically it is 100% covered by colored shapes.
A fix for this is to define the graph as a single <path> object tracing out the top and bottom edges with no "cracks" like this between rects.
I'm not familiar with D3, but in ordinary Javascript:
var path = "M 0," + (height / 2);
for(var i = 0; i < data.length; i++) {
var x = (i + 1) * (width / data.length);
var y = height / 2 - (data[i] * height)/2;
path += " V " + y + " H " + x;
}
for(var i = data.length - 1; i >= 0; i--) {
var x = i * (width / data.length);
var y = height / 2 + (data[i] * height)/2;
path += " V " + y + " H " + x;
}
path += " Z";
Russell's answer is a good one, though you'll end up with a monstrous path. This shouldn't be too much of a problem.
I encountered the same problem the other day when trying to make a bar chart of about 500 data points using very thin bars. The advantage of doing so is that it is much easier to make a mouseover that highlights an individual bar. In cases like this, I find that you have to use integer values for the widths and x positions.
For your example, setting the width and the interval to 1 completely fixes the problem while only making it about 10 percent shorter:
http://jsfiddle.net/s4dML/1/
.attr('x', function(d, i){
return i;// * (width / data.length);
})
.attr('y', function(d){
return (height /2) - d ;
})
.attr('width', function(d, i){
return 1;
})
Of course, this not an extensible solution--just depends on your plans for the widget. I added a mouseover in the above example for demo purposes.

Labels do not fit correctly on Pie

In effort to learn D3.js, I took this example and made a plunker out of it
http://plnkr.co/edit/ynWB0GznvrMMRmqkaqaA?p=preview
Problem?
The data doesn't fit correctly and you can see that data is incomplete
How shall I fix it?
First thing is that your labels are incorrect. So change this:
.text(function(d) {
return 'd.data.age'; });
to this:
.text(function(d) {
return d.data.age; });
and you'll get the correct labels.
Second, the labels are getting cut off. This appears to be to do with the sizes specified for the pie chart. You can either increase the width specified (looks fine at 360 say):
var width = 360,
height = 300,
radius = Math.min(width, height) / 2;
Or bring the labels in closer to the graph:
.attr("transform", function(d) {
return "translate(" + ( (radius -50) * Math.sin( ((d.endAngle - d.startAngle) / 2) + d.startAngle ) ) + "," + ( -1 * (radius - 50) * Math.cos( ((d.endAngle - d.startAngle) / 2) + d.startAngle ) ) + ")"; })
Where it says radius - x determines how close or far the labels will appear to the graph. The bigger x is, the closer they will be.

Categories