DC.js, D3.js | How to remove animation from postRender? - d3.js

I'm using postRender to change the x position of some y axis labels in a couple of DC charts:
function moveLabels() {
var obj = d3.selectAll('text.y-axis-label');
for (var i=0; i<obj[0].length; i++) {
var t = d3.transform(d3.select(obj[0][i]).attr("transform"));
var keepY = t.translate[1];
d3.select(obj[0][i]).attr("transform","translate(18,"+keepY+") rotate(-90)");
}
}
It works fine, but there's an animation applied to the move. Every time I change a dropdown and redraw the chart, the y axis label slides around. Does anyone know how to get rid of it? I've tried:
d3.select(obj[0][i]).transition().attr("delay","0").attr("transform","translate(18,"+keepY+") rotate(-90)");
and
d3.select(obj[0][i]).transition().attr("duration","0").attr("transform","translate(18,"+keepY+") rotate(-90)");

Related

D3 Donut chart projected to sphere/globe

I want to use d3 for the next task:
display rotating globe with donut chart in center of every country. It should be possible to interact with globe (select country, zoom, rotate).
Seems d3 provide an easy way to implement every part of it but I can not get donuts part working as I need.
There is an easy way draw donut chart with the help of d3.arc:
var arc = d3.arc();
var data = [3, 23, 17, 35, 4];
var radius = 15/scale;
var _arc = arc.innerRadius(radius - 7/scale)
.outerRadius(radius).context(donutsContext);
var pieData = pie(data);
for (var i = 0; i < pieData.length; i++) {
donutsContext.beginPath();
donutsContext.fillStyle = color(i);
_arc(pieData[i]);
}
by with code as it is donuts are displayed on a plane on top of the globe, like:
globe with donut
​
while I want them to be 'wrapped' around the globe
There is d3.geoCircle method that can be projected to globe correctly. I got 'ring' projected correctly to the globe with the help of two circles:
var circle = d3.geoCircle()
.center(centroid)
.radius(2);
var outerCircle = circle();
var circle = d3.geoCircle()
.center(centroid)
.radius(1);
var innerCircle = circle();
var interCircleCoordinates = [];
for (var i = innerCircle.coordinates[0].length - 1; i >= 0; i--) {
interCircleCoordinates.push(innerCircle.coordinates[0][i]);
}
outerCircle.coordinates.push(interCircleCoordinates);
​globe with rings
but I really need to get a donut.
The other way I tried is getting image from donuts and wrapping this image around globe with the help of pixels manipulation:
var image = new Image;
image.onload = onload;
image.src = img;
function onload() {
window.dx = image.width;
window.dy = image.height;
context.drawImage(image, 0, 0, dx, dy);
sourceData = context.getImageData(0, 0, dx, dy).data;
target = context.createImageData(width, height);
targetData = target.data;
for (var y = 0, i = -1; y < height; ++y) {
for (var x = 0; x < width; ++x) {
var p = projection.invert([x, y]), λ = p[0], φ = p[1];
if (λ > 180 || λ < -180 || φ > 90 || φ < -90) { i += 4; continue; }
var q = ((90 - φ) / 180 * dy | 0) * dx + ((180 + λ) / 360 * dx | 0) << 2;
var r = sourceData[q];
var g = sourceData[++q];
var b = sourceData[++q];
targetData[++i] = r;
targetData[++i] = g;
targetData[++i] = b;
targetData[++i] = 125;//
}
}
context.clearRect(0,0, width, height);
context.putImageData(target, 0, 0);
};
by this way I get extremely slow rotating and interaction with a globe for a globe size I need (1000px)
So my questions are:
Is there is some way to project donuts that are generated with the help of d3.arc to a sphere (globe, orthographic projection)?
Is there is some way to get a donut from geoCircle?
Maybe there is some other way to achieve my goal I do not see
There is one way that comes to mind to display donuts on a globe. The key challenge is that d3 doesn't project three dimensional objects very well - with one exception, geographic features. Consequently, an "easy" solution is to convert your pie charts into geographic features and project them with the rest of your features.
To do this you need to:
Use a pie/donut generator as you normally would
Go along the paths generated to get points approximating the pie shape.
Convert the points to long/lat points
Assemble those points into geojson
Project them onto the map.
The first point is easy enough, just make a pie chart with an inner radius.
Now you have to select each path and find points along its perimeter using path.getPointAtLength(), this will be dependent on path length, so path.getTotalLength() will be handy (and corners are important, so you might want to incorporate a little bit of complexity for these corner cases to ensure you get them)).
Once you have the points, you need the use of a second projection, azimuthal equidistant would be best. If the pie chart is centered on [0,0] in svg coordinate space, rotate the azimuthal (don't center), so that the centroid coordinate is located at [0,0] in svg space (you can use translates on the pies to position them, but it will just add extra steps). Take each point and run it through projection.invert() using the second projection. You will need to update the projection for each donut chart as each one will have a different geographic centroid.
Once you have lat long points, it's easy - you've already done it with the geo circle function - convert to geojson and project with the orthographic projection.
This approach gave me something like:
Notes: Depending on your data, it might be easiest to preprocess your data into geojson and store that as opposed to calculating the geojson each page load.
You are using canvas, while you don't need to actually use an svg, you need to still be able to access svg functions like getPointAtLength, you do not need to have an svg or display svg elements by using a custom element replicating a path :
document.createElementNS(d3.namespaces.svg, 'path');
Oh, and make sure the second projection's translate is set - the default is [480,250] for all (most?) d3 projections, that will throw things off if unaccounted for.

Highcharts chart.renderer.images not lining up with platbands

I am adding images using chart.renderer.image. I would like the images to line up with the beginning of each plotBand. I have the plotBand from position in axis units. However when I call toValue the images do not line up.
https://jsfiddle.net/uxeL76a9/23/
for (var i = 0; i < plotBands.length; ++i) {
var artist = plotBands[i];
var xPos = chart.xAxis[0].toValue(artist['from'], true);
...
Your chart is inverted, so axes are swapped, and you have small mess with toValue(), see fixed code:
var xPos = chart.xAxis[0].toValue(artist['from'], true);
Should be:
var xPos = chart.yAxis[0].toPixels(artist.from);
Working demo: https://jsfiddle.net/uxeL76a9/40/

Zooming into canvas via js causes content to be lost?

http://deepschool.kd.io/Pages/Experiments/draw.htm
This is a Image editor I am working on, But it has a bug. Let's say you have created an image a 15x Zoom, when you change the zoom, the image is lost. Why is this? and what is the Remedy?
HTML:
Zoom:
<input type="number" id="zoom" min="1" max="50" onchange="zoomy()">
JS:
var zoomy = function() {
var zoomamount = zoom.value
var canvassize = zoomamount * 16
c.width = canvassize
c.height = canvassize
};
Thanks In Advance
If you want to zoom into canvas, it means you have to redraw it with zoom.
So instead of drawing pixels on click right onto canvas, which is made of pure pixels... You need to first create some representation of your grid of pixels.
var gridOfPixels = [];
Let's say you are ok with static size for now. Make it 8x8 pixels. At start you want to initialize your array:
for (var i=0; i < 8*8; i++) gridOfPixels[i] = 0;
So the grid canvas is ready, now we need to draw it.
function renderGrid() {
for (var y=0; y < 8; y++)
for (var x=0; x < 8; x++)
renderPixel( x, y, gridOfPixels[x+y*8] );
}
You already know how to renderPixel - calculate the rectangle position (posX = x*pixWidth, posY*pixHeight), where pixWidth is canvasWidth/8, etc.. Now you draw all your pixels, using the third parameter for the color.
To finish, you have to connect onclick to put a pixel on grid, and then call renderGrid so the user sees the change.
$('#my-canvas').click(function(e) {
var x = ...;
var y = ...; // calculate the position of pixels from mouse position inside canvas
// dont forget to check that x,y are in the 0-7 range
// dont forget to convert x,y to whole number using parseInt()
gridOfPixels[x+y*8] = 1;
renderGrid(); // update the grid canvas
});
Now, every time you resize the canvas or change some variables, the original canvas content will be saved in your grid, and you can renderGrid() any time you need to. You could even do it in realtime, animating the color of the pixels, etc..
Have fun. :)

Flot - display labels on top of stacked bars

I am using the code snippet from this stackoverflow question to label my flot data points. So far this has served me well, but now I have to label the overall values of stacked bars. There are two data series and so far I've managed to calculate the sums, but I can't seem to work out a proper positioning for my labels. I'd like to place them on top of the stacks, but pointOffset only gives me the offsets based on non-stacked bars.
This is the code I am currently using, it places the labels where the second series' data points would be, if the bars weren't stacked, which puts them somewhere in the top bars.
$.each(p.getData()[1].data, function(i, el){
var series0 = p.getData()[0].data;
sum = el[1] + series0[i][2]
var o = p.pointOffset({x: el[0], y: el[1]});
$('<div class="data-point-label">' + sum + '</div>').css( {
position: 'absolute',
left: o.left - 5,
top: o.top ,
display: 'none'
}).appendTo(p.getPlaceholder()).fadeIn('slow');
});
Edit #1: So far I've tried using c2p/p2c, calculating the top value using the single data points' top values and finding more documentation on the stack plugin. I am afraid none of this has helped me much.
Edit #2: I've also tried the code given in this stackoverflow answer but it doesn't work for me. I suspect the author is using some label plugin ...
The solution to put the labels in the top of the bars in stack usinjg canvas is that you have to calculate the xPoint in base of the sum of the values in the complete stack.
Here is a example of code
var sumaArr = [];
for (var u = 0; u < p.getData().length; u++) {
$.each(p.getData()[u].data, function (i, el) {
sumaArr[i] > 0 ? sumaArr[i] = sumaArr[i] + el[1] : sumaArr[i] = el[1];
});
}
var ctx = p.getCanvas().getContext("2d");
var data = p.getData()[p.getData().length - 1].data;
var xaxis = p.getXAxes()[0];
var yaxis = p.getYAxes()[0];
var offset = p.getPlotOffset();
ctx.font = "12px 'Segoe UI'";
ctx.fillStyle = "gray";
for (var i = 0; i < data.length; i++) {
var text = sumaArr[i];
var metrics = ctx.measureText(text);
var xPos = (xaxis.p2c(data[i][0]) + offset.left) - metrics.width / 2;
var yPos = yaxis.p2c(sumaArr[i]) + offset.top - 5;
ctx.fillText(text, xPos, yPos);
}
The var yPos use the sume of the values that make the complete stack.

Snapping vertical line cursor to data point in jqPlot

I have just started using jqPlot for a line chart with multiple series. It seems great.
I have added the Cursor plugin with the intention of showing a vertical line on the nearest data point on the x axis. In other words, it snaps to the nearest point. The Cursor plugin, however always shows the vertical cursor right where the mouse is.
It seems like I just want to "override" or replace moveLine to change the current functionality.
What's the most appropriate way of doing so?
It seems a little much to copy/past all of the cursor plugin just to modify a very small subset.
Thanks!
I know I'm a kind of archaeologist by edited this post but I think the following can be useful for someone (I hope).
I've made a piece of code which allow to draw a vertical line following the cursor and displaying a tooltip on the nearest point (according to the cursor). You can find a demo of it on this JSFiddle.
I also post the corresponding code below (only the part which calculate nearest point and display tooltip):
//Show nearest point's tooltip
$("#chart1").bind('jqplotMouseMove', function(ev, gridpos, datapos, neighbor, data){
var c_x = datapos.xaxis;
var index_x = -1;
var pos_index = 0;
var low = 0;
var high = data.data[0].length-1;
while(high - low > 1){
var mid = Math.round((low+high)/2);
var current = data.data[0][mid][0];
if(current <= c_x)
low = mid;
else
high = mid;
}
if(data.data[0][low][0] == c_x){
high = low;
index_x = high;
}else{
var c_low = data.data[0][low][0];
var c_high = data.data[0][high][0];
if(Math.abs(c_low - c_x) < Math.abs(c_high - c_x)){
index_x = low;
}else{
index_x = high;
}
}
//Display marker and tooltip
if(data.series[0].data[index_x]){
var x = data.series[0].gridData[index_x][0];
var y = data.series[0].gridData[index_x][1];
var r = 5;
var highlightCanvas = $(".jqplot-highlight-canvas")[0];
var context = highlightCanvas.getContext('2d');
context.clearRect(0,0,highlightCanvas.width,highlightCanvas.height);
context.strokeStyle = 'rgba(47,164,255,1)';
context.fillStyle = 'rgba(47,164,255,1)';
context.beginPath();
context.arc(x,y,r,0,Math.PI*2,true);
context.closePath();
context.stroke();
context.fill();
//Display tooltip on nearest point
var highlightTooltip = $(".jqplot-highlighter-tooltip");
var val_x = data.data[0][index_x][0];
var val_y = data.data[0][index_x][1];
highlightTooltip.html("X : "+val_x+"<br/>Y : "+val_y);
highlightTooltip.css({'left': x+'px', 'top': (y-10)+'px', 'display': 'block'});
}
});
Feel please to use it and to modify it as you need it.
Try a bar graph series on top of everything else that has alpha set to 0.

Resources