I am trying to replace my Kendo charts with NVD3 charts. In NVD3 pie chart, I did not see explode option on slice click. Is there easy way to achieve it. Thanks!
nvd3 does not currently support exploding the pie chart. it's community driven though, so you can send them a pull request if you add the feature! You'll want to modify the pie.js and pieChart.js model files.
I able to explode slice of pie chart using below function. I got the help from http://jsfiddle.net/zephod/L4pyk79e/2/
I added below function in nv.d3.js
var explode = function (x, index) {
var offset = 10;
var angle = (x.startAngle + x.endAngle) / 2;
var xOff = Math.sin(angle) * offset;
var yOff = -Math.cos(angle) * offset;
return "translate(" + xOff + "," + yOff + ")";
}
Calling it as below from ae.on('click', function (d, i).
d3.select(this).select("path").transition()
.duration(70)
.attr("transform", explode);
to undo, call same function with offset=0
Related
Looking at the following d3 snippet and if you try dragging any circle up or down and thus, resizing the stacked bar elements you will notice that the cursor goes either faster or slower than the edge being resized actually the bars to the left-most are slower than the bars to the right-most... why is that? and how can be fixed?
The drag behavior is defined from line #110
var drag2 = d3.behavior.drag()
.on("drag", function(d) {
var datum = data[d.group];
var dy = d3.event.dy * 0.3; // <<<<<<<<<<<<<<<<< here is scaled down
if(d.i == 0) { // but no matter what weight I put here
datum.bars[0].y1 -= dy; // it will always be too fast or too slow ..
datum.bars[1].y0 -= dy;
} else {
datum.bars[1].y1 -= dy;
datum.bars[2].y0 -= dy;
}
d.y -= dy;
render(rects)
})
Basically the affecting line is 113 var dy = d3.event.dy * 0.3; here the event.dy is scaled down by 0.3 but why? I have played with that weight but no value seem to keep the edge being dragged and the cursor equal.
UDPATE This other example How can I click to add or drag in D3? shows that a correct way to calculate is using transform/translate but these are attributes of an element and in my OP the affected target is the data itself and not a visual element.
You have to use an inverse scale to calculate the dy, like so:
var drag2 = d3.behavior.drag()
.on("drag", function(d) {
var datum = data[d.group];
y.domain([d.total, 0]);
var dy = y.invert(d3.event.dy);
// ...
});
Since this is just a linear scale you could also do it like this:
var dy = d3.event.dy * d.total / height;
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] .
in a html page we have two divs.
one div consist of tree layout and second div consist of other graph .both are written in d3.
d3.select("g").transition().duration(duration).attr("transform",
"translate(" + x + "," + y + ")scale(" + scale + ")");
the above statement d3.select('g') is causing issue,it is trying to select the other div as well and it is effecting it.
tried adding id to each container but didnt worked.
thanks in advance
There are a few things you can do to differentiate between elements.
Give IDs to the divs and use them in the selector. d3.select("#divone > svg > g")
Assign different classes to the g elements. d3.select("g.classone")
Keep references to the SVGs when creating them and select from those.
Here's some example code for this way:
var svg1 = d3.select("#divone").append("svg"),
svg2 = d3.select("#divtwo").append("svg");
// more code
svg1.select("g");
Which way is the best depends entirely on your application, but in general the last solution is the safest one as you're keeping explicit references to your subselections.
Use something like this
function animateFirstStep() {
d3.select(this).transition().delay(0).duration(
100)
.attr("r", function(d) {
return d.r + 4;
});
or pass selector in place of this.
say the name if your function is generateChart(selector)
call the function like this generateChart("#NameofDiv")
it should work
Use something like this
.......
var g = d3.select(this)
redraw(g);
.....
function redraw(g) {
g.selectAll(".resize").attr("transform", function (d) {
"translate(" + x + "," + y + ")scale(" + scale + ")");
});
}
I am attempting to simplify a d3 map on zoom, and I am using this example as a starting point. However, when I replace the json file in the example with my own (http://weather-bell.com/res/nws_regions.topojson), I get a tiny upside-down little map.
Here is my jsfiddle: http://jsfiddle.net/8ejmH
code:
var width = 900,
height = 500;
var chesapeake = [-75.959, 38.250];
var scale,
translate,
visibleArea, // minimum area threshold for points inside viewport
invisibleArea; // minimum area threshold for points outside viewport
var simplify = d3.geo.transform({
point: function (x, y, z) {
if (z < visibleArea) return;
x = x * scale + translate[0];
y = y * scale + translate[1];
if (x >= 0 && x <= width && y >= 0 && y <= height || z >= invisibleArea) this.stream.point(x, y);
}
});
var zoom = d3.behavior.zoom()
.size([width, height])
.on("zoom", zoomed);
// This projection is baked into the TopoJSON file,
// but is used here to compute the desired zoom translate.
var projection = d3.geo.mercator().translate([0, 0])
var canvas = d3.select("#map").append("canvas")
.attr("width", width)
.attr("height", height);
var context = canvas.node().getContext("2d");
var path = d3.geo.path()
.projection(simplify)
.context(context);
d3.json("http://weather-bell.com/res/nws_regions.topojson", function (error, json) {
canvas.datum(topojson.mesh(topojson.presimplify(json)))
.call(zoomTo(chesapeake, 0.05).event)
.transition()
.duration(5000)
.each(jump);
});
function zoomTo(location, scale) {
var point = projection(location);
return zoom.translate([width / 2 - point[0] * scale, height / 2 - point[1] * scale])
.scale(scale);
}
function zoomed(d) {
translate = zoom.translate();
scale = zoom.scale();
visibleArea = 1 / scale / scale;
invisibleArea = 200 * visibleArea;
context.clearRect(0, 0, width, height);
context.beginPath();
path(d);
context.stroke();
}
function jump() {
var t = d3.select(this);
(function repeat() {
t = t.transition()
.call(zoomTo(chesapeake, 100).event)
.transition()
.call(zoomTo(chesapeake, 0.05).event)
.each("end", repeat);
})();
}
My guess is that the topojson file I am using already has the projection built in, so I should be using a null projection in d3.
The map renders properly if I do not use a projection at all: (http://jsfiddle.net/KQfrK/1/) - but then I cannot simplify on zoom.
I feel like I am missing something basic... perhaps I just need to somehow rotate and zoom into the map in my first fiddle.
Either way, I'd appreciate some help. Been struggling with this one.
Edit: I used QGIS to save the geojson file with a "EPSG:3857 - WGS 84 / Pseudo Mercator" projection.
However, when I convert this to topojson with the topojson command-line utility and then display it with D3 using the same code as above I get a blank screen.
Should I specify the projection within the topojson command-line utility? I tried to do that but I got an error message:
topojson --projection EPSG:3857 E:\gitstore\public\res\nws.geojson -o E:\gitstore\public\res\nws.topojson --id-property NAME
[SyntaxError: Unexpected token :]
The TopoJSON file doesn't have a projection built-in, you're simply using the default projection when you don't specify one (which is albersUsa, see the documentation). You can retrieve this projection by calling d3.geo.projection() without an argument. Then you can modify this projection in the usual way for zoom etc.
I set up this fiddle using the Mercator projection and I took a different approach to zooming in and out based on this block, which to me was a simpler approach. I have a feeling that there was an issue in the zoomTo function in the translate bit, but I could exactly what it was. So I replaced with the code below and included a recursive call:
function clicked(k) {
if (typeof k === 'undefined') k = 8;
g.transition()
.duration(5000)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -projection(chesapeake)[0] + "," + -projection(chesapeake)[1] + ")")
.each("end", function () {
(k === 8) ? k = 1 : k = 8;
clicked(k);
});
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/