showing only one coordinate - scatter-plot

I have the following but it only shows the coordinate of x, I need both to appeared when the mouse hover on the plot, I tried to do another one for (dc) which is the y coordinate, but nothing is working, I even tried to change return (dm) to return (d) but it will show (object) instead of the point, any ideas please?
pplot.append("title")
.text(function(d,i) {
return (dm)
});

I'm assuming you want to return a String with the x and y coordinates.
So you should be able to just access each coordinate from the d object separately and use the + operator to concatenate them:
ascatter.append("title")
.text(function(d,i) {
return "x: " + d.a + "y: " + d.b;
});

Related

d3.js v4 svg semantic zooming and panning (pan along both x axis and y axis, zoom only along x axis)

I want to achieve panning the svg objects along both x and y axis and semantic zooming only along x axis.
As long as the d3.event.transform object holds the computed values for both zooming and panning, anytime I call panning and zooming one after each other, the newly translated y values are wrong because they ignore\do not ignore previous opposite actions. (zoom ignores the d3.event.transform.y of pan actions and pan does not ignore d3.event.transform.y of zoom actions)
If you zoom anywhere in the svg area, the circles should only translate the x coordinate and the y coordinate should stay the same as before (taking into account previous pannings)
Now the circles are "jumping" due to wrong y values.
if (isZoom)
{//zoom
return "translate(" + t.apply(d)[0] + "," + (d[1]) +")"; //ignores previous panning-only values and positions to static initial value
}
//panning
return "translate(" + t.apply(d)[0] + "," + (t.y + d[1]) +")"; //how to ignore portion of t.y belonging to previous zooming?
You can uncomment this line of code to prevent circles from jumping but the y is changing while zooming (which should not)
//ignore isZoom and apply both for panning and zooming
return "translate(" + t.apply(d)[0] + "," + (t.y + d[1]) +")";
https://jsfiddle.net/197cz2vj/
Thanks!
UPDATE
Finally I came up with a hack-like solution. I do not like it and I am still looking for a proper solution(I don't like deciding the isZoom action and also the deltaPanY solution. It is all succeptible for future changes in d3 libray). Here it is:
Every time the transformation changes and the change is triggered by the mousemove(panning), update the deltaPanY variable comparing the new value with the old remembered value of the transformation. (I make a copy also of the t.x and t.k but for my purposes only t.y and deltaPanY is necessary).
function copyLastTransform(t)
{
lastTransform =
{
x: t.x,
y: t.y,
k: t.k
};
};
Every time the transform occurs, set the delta variables and store the last transform:
if (isZoom)
{
deltaZoomY += t.y - lastTransform.y;
}
else
{
deltaPanY += t.y - lastTransform.y;
}
copyLastTransform(t);
Translate function looks like this now:
return "translate(" + t.apply(d)[0] + "," + (deltaPanY + d[1]) +")";
Forked fiddle:
https://jsfiddle.net/xpr364uo/

Rotate every arc of pie chart 180 (like sun) with D3 JS. How to calculate translate parameters

I am working on pie chart with d3 js. I want to rotate every arc of my pie chart 180. I know that I am unable to explain completely show here is my fiddle link.
[fiddle]: https://jsfiddle.net/dsLonquL/
How can i get dynamic parameters for translate() function.
Basically you need to work out the centre point of the edge of each arc. I used this example for help : How to get coordinates of slices along the edge of a pie chart?
This works okay, but I needed to rotate the points to get them in the correct positions. As it is in radians the rotation is the following :
var rotationInRadians = 1.5708 * 1.5;
Now using the example before I used the data for the paths, so the start and end angle and got the center points like so :
var thisAngle = (d.startAngle + rotationInRadians + (d.endAngle + rotationInRadians - d.startAngle + rotationInRadians) / 2);
var x = centreOfPie[0] + radius * 2 * Math.cos(thisAngle)
var y = centreOfPie[1] + radius * 2 * Math.sin(thisAngle)
I created a function to show circles at these points to clarify :
function drawCircle(points, colour) {
svg.append('circle')
.attr('cx', points[0])
.attr('cy', points[1])
.attr('r', 5)
.attr('fill', colour);
}
Called it inside the current function like so :
drawCircle([x, y], color(d.data.label))
And then translated and rotated accordingly :
return 'translate(' + (x) + ',' + y + ') rotate(180)';
I added a transition so you can see it working. Here is the final fiddle :
https://jsfiddle.net/thatOneGuy/dsLonquL/7/
EDIT
In your comments you say you want the biggest segment to be kept in the middle. So we need to run through the segments and get the biggest. I have also taken care of duplicates, i.e if two or more segments are the same size.
Here is the added code :
var biggestSegment = {
angle: 0,
index: []
};
path.each(function(d, i) {
var thisAngle = (d.endAngle - d.startAngle).toFixed(6);//i had to round them as the numbers after around the 7th or 8th decimal point tend to differ tet theyre suppose to be the same value
if (i == 0) {
biggestSegment.angle = thisAngle
} else {
if (biggestSegment.angle < thisAngle) {
biggestSegment.angle = thisAngle;
biggestSegment.index = [i];
} else if (biggestSegment.angle == thisAngle) {
console.log('push')
biggestSegment.index.push(i);
}
}
})
Now this goes through each path checks if its bigger than the current value, if it is overwrite the biggest value and make note of the index. If its the same, add index to index array.
Now when translating the paths, you need to check the current index against the index array above to see if it needs rotating. Like so :
if (biggestSegment.index.indexOf(i) > -1) {
return 'translate(' + (centreOfPie[0]) + ',' + (centreOfPie[1]) + ')' // rotate(180)';
} else {
return 'translate(' + (x) + ',' + y + ') rotate(180)';
}
Updated fiddle : https://jsfiddle.net/thatOneGuy/dsLonquL/8/
I have editted 3 values to be different to the rest. Go ahead and change these, see what you think :)
This is a pure middle school geometry job.
CASE 1: The vertex of each sector rotation is on the outer line of the circle
fiddle
// ... previous code there
.attr('fill', function(d, i) {
return color(d.data.label);
})
.attr("transform", function(d, i) {
var a = (d.endAngle + d.startAngle) / 2, // angle of vertex
dx = 2 * radius * Math.sin(a), // shift/translate is two times of the vertex coordinate
dy = - 2 * radius * Math.cos(a); // the same
return ("translate(" + dx + " " + dy + ") rotate(180)"); // output
});
CASE 2: The vertex on the center of the chord
fiddle
// ... previous code there
.attr('fill', function(d, i) {
return color(d.data.label);
})
.attr("transform", function(d, i) {
var dx = radius * (Math.sin(d.endAngle) + Math.sin(d.startAngle)), // shift/translation as coordinate of vertex
dy = - radius * (Math.cos(d.endAngle) + Math.cos(d.startAngle)); // the same for Y
return ("translate(" + dx + " " + dy + ") rotate(180)"); // output
});

Inversion with ordinal scale

Is there any way to find inversion of ordinal scale?
I am using string value on x axis which is using ordinal scale and i on mouse move i want to find inversion with x axis to find which string is there at mouse position?
Is there any way to find this?
var barLabels = dataset.map(function(datum) {
return datum.image;
});
console.log(barLabels);
var imageScale = d3.scale.ordinal()
.domain(barLabels)
.rangeRoundBands([0, w], 0.1);
// divides bands equally among total width, with 10% spacing.
console.log("imageScale....................");
console.log(imageScale.domain());
.
.
var xPos = d3.mouse(this)[0];
xScale.invert(xPos);
I actually think it doesn't make sense that there isn't an invert method for ordinal scales, but you can figure it out using the ordinal.range() method, which will give you back the start values for each bar, and the ordinal.rangeBand() method for their width.
Example here:
http://fiddle.jshell.net/dMpbh/2/
The relevant code is
.on("click", function(d,i) {
var xPos = d3.mouse(this)[0];
console.log("Clicked at " + xPos);
//console.log(imageScale.invert(xPos));
var leftEdges = imageScale.range();
var width = imageScale.rangeBand();
var j;
for(j=0; xPos > (leftEdges[j] + width); j++) {}
//do nothing, just increment j until case fails
console.log("Clicked on " + imageScale.domain()[j]);
});
I found a shorter implementation here in this rejected pull request which worked perfectly.
var ypos = domain[d3.bisect(range, xpos) - 1];
where domain and range are scale domain and range:
var domain = x.domain(),
range = x.range();
I have in the past reversed the domain and range when this is needed
> var a = d3.scale.linear().domain([0,100]).range([0, w]);
> var b = d3.scale.linear().domain([0,w]).range([0, 100]);
> b(a(5));
5
However with ordinal the answer is not as simple. I have checked the documentation & code and it does not seem to be a simple way. I would start by mapping the items from the domain and working out the start and stop point. Here is a start.
imageScale.domain().map(function(d){
return {
'item':d,
'start':imageScale(d)
};
})
Consider posting your question as a feature request at https://github.com/mbostock/d3/issues?state=open in case
There is sufficient demand for such feature
That I haven't overlooked anything or that there is something more hidden below the documentation that would help in this case
If you just want to know which mouse position corresponds to which data, then d3 is already doing that for you.
.on("click", function(d,i) {
console.log("Clicked on " + d);
});
I have updated the Fiddle from #AmeliaBR http://fiddle.jshell.net/dMpbh/17/
I recently found myself in the same situation as OP.
I needed to get the inverse of a categorical scale for a slider. The slider has 3 discrete values and looks and behaves like a three-way toggle switch. It changes the blending mode on some SVG elements. I created an inverse scale with scaleQuantize() as follows:
var modeArray = ["normal", "multiply", "screen"];
var modeScale = d3.scalePoint()
.domain(modeArray)
.range([0, 120]);
var inverseModeScale = d3.scaleQuantize()
.domain(modeScale.range())
.range(modeScale.domain());
I feed this inverseModeScale the mouse x-position (d3.mouse(this)[0]) on drag:
.call( d3.drag()
.on("start.interrupt", function() { modeSlider.interrupt(); })
.on("start drag", function() { inverseModeScale(d3.mouse(this)[0]); })
)
It returns the element from modeArray that is closest to the mouse's x-position. Even if that value is out of bounds (-400 or 940), it returns the correct element.
Answer may seem a bit specific to sliders but posting anyway because it's valid (I think) and this question is in the top results for " d3 invert ordinal " on Google.
Note: This answer uses d3 v4.
I understand why Mike Bostock may be reluctant to include invert on ordinal scales since you can't return a singular true value. However, here is my version of it.
The function takes a position and returns the surrounding datums. Maybe I'll follow up with a binary search version later :-)
function ordinalInvert(pos, scale) {
var previous = null
var domain = scale.domain()
for(idx in domain) {
if(scale(datum[idx]) > pos) {
return [previous, datum[idx]];
}
previous = datum[idx];
}
return [previous, null];
}
I solved it by constructing a second linear scale with the same domain and range, and then calling invert on that.
var scale = d3.scale.ordinal()
.domain(domain)
.range(range);
var continousScale = d3.scale.linear()
.domain(domain)
.range(range)
var data = _.map(range, function(i) {
return continousScale.invert(i);
});
You can easily get the object's index/data in callback
.on("click", function(d,i) {
console.log("Clicked on index = " + i);
console.log("Clicked on data = " + d);
// d == imageScale.domain()[1]
});
d is the invert value itself.
You don't need to use obj.domain()[index] .

Using the zoom and pan functionality of d3

I'm trying to use the pan/zoom ability of d3 to draw boxes on the screen so that when you click on a box a new box appears and shifts the rest of the boxes to the right so that the new box is on the center of the canvas. The panning would allow me to scroll through all the boxes I've drawn.
Here is my jsfiddle: http://jsfiddle.net/uUTBE/1/
And here is my code for initializing the zoom/pan:
svg.call(d3.behavior.zoom().on("zoom", redraw));
function redraw() {
d3.select(".canvas").attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}
And here is my code for drawing the boxes:
function drawBox(x, y) {
var boxGroup = canvas.append("g");
boxGroup.append("rect")
.attr("x", x)
.attr("y", y)
.attr("height", 100)
.attr("width", 100)
.attr("fill", function () {
var i = Math.floor(Math.random() * 4);
if (i === 1) return "red";
else if (i === 2) return "blue";
else if (i === 3) return "yellow";
else return "green";
})
.on("click", function () {
counter++;
d3.select(".canvas")
.transition()
.duration(1000)
.attr('transform', "translate(300,0)");
drawBox(x - counter * 120, y);
});
}
I have multiple problems with this fiddle, but two of my main concerns is:
1) How do I make it so that when I click on a new box a second time the boxes move accordingly (i.e. when I click on the box initially the old box shifts to the right and a new box appears, but when I click on the new box, the older boxes doesnn't shift to the right).
2)Why is it that when I click on the new box, the newer box has a big spacing between it? (only happens after trying to put 3 boxes on the screen).
Thanks any hints are appreciated!
I think there's some confusion here around transform. The transform attribute is static, not cumulative, for a single element - so setting .attr('transform', "translate(300,0)") more than once will have no effect after the first time. It also looks like your placement logic for the new boxes is off.
The positioning logic required here is pretty straightforward if you take a step back (assuming I understand what you're trying to do):
Every time a new box is added, the frame all boxes are in moves right 120px, so it needs a x-translation of 120 * counter.
New boxes need to be offset from the new frame position, so they need an x setting of -120 * counter.
Zoom needs to take the current canvas offset into account.
(1) above can be done in your click handler:
canvas
.transition()
.duration(1000)
.attr('transform', "translate(" + (offset * counter) + ",0)");
(2) is pretty easily applied to the g element you're wrapping boxes in:
var boxGroup = canvas.append("g")
.attr('transform', 'translate(' + (-offset * counter) + ',0)');
(3) can be added to your redraw handler:
function redraw() {
var translation = d3.event.translate,
newx = translation[0] + offset * counter,
newy = translation[1];
canvas.attr("transform",
"translate(" + newx + "," + newy + ")" + " scale(" + d3.event.scale + ")");
}
See the working fiddle here: http://jsfiddle.net/nrabinowitz/p3m8A/

In d3 is it possible to dynamically change where a path position starts for tweening?

to: clarify. Picture a circle. We start drawing the circle from a particular coordinate. Now lets draw the circle starting from another coordinate.
I am playing with path data derived from SVG glyphs and then using d3js tween to animate the change between the paths.
For this example, counting from 1 -> 9,0 and then repeating.
http://jsfiddle.net/chrisloughnane/HL2ET/
As you can see some of the transitions are not as nice as others. They draw a line that closes the path for the next path. (I'm guessing that) this happens when the start and end of the path are very far apart when the calculation for the new shape is made. When it works it's very nice.
Could anybody suggest a possible solution to the ugly lines?
CODE without path data
svg.append("path")
.attr("transform", "translate(150,300)scale(.2,-.2)")
.style("stroke", "red")
.style("fill", "gray")
.style("stroke-width", "9")
.attr("d", d0)
.call(transition, digits[0], digits[position]);
function transition(path, d0, d1) {
position++;
if(position==10)
{
position=0;
}
path.transition()
.duration(2000)
.attrTween("d", pathTween(d1, 4))
.each("end", function() { d3.select(this).call(transition, d1, digits[position]); });
}
function pathTween(d1, precision) {
return function() {
var path0 = this,
path1 = path0.cloneNode(),
n0 = path0.getTotalLength(),
n1 = (path1.setAttribute("d", d1), path1).getTotalLength();
// Uniform sampling of distance based on specified precision.
var distances = [0], i = 0, dt = precision / Math.max(n0, n1);
while ((i += dt) < 1) distances.push(i);
distances.push(1);
// Compute point-interpolators at each distance.
var points = distances.map(function(t) {
var p0 = path0.getPointAtLength(t * n0),
p1 = path1.getPointAtLength(t * n1);
return d3.interpolate([p0.x, p0.y], [p1.x, p1.y]);
});
return function(t) {
return t < 1 ? "M" + points.map(function(p) { return p(t); }).join("L") : d1;
};
};
}
Unfortunately it fails on chrome mobile too where as http://bl.ocks.org/mbostock/3081153 works fine.
The next step is to apply this effect to sentences.
The difference between your example and that in Bostock's is that in his example there is a single continuous path that he tweens into another single continuous path.
Whereas, in your example, digits like 1, 2, 3, 5, 6, 7 can be drawn using single continuous path. But, in order to draw digits like 4, 6, 9 and 0 you need 2 paths- one on top of the other. And, for digit 8, you need to have 2 paths on top of an outer path.
So, my suggestion would be to keep 2 paths at all times atop the outer path that you are using at present & give them appropriate dimensions whenever any peculiar digit is to be shown.
Refer image for more details:

Resources