Rcharts nvd3 2-D zoom possible? - javascript

Is there a zooming function in nvd3 that I can directly type in my R source code (doesn't matter if it requires javascript as long as I don't have to change nvd3 source code)? I tried the lineWithFocusChart, but that only zooms along the x-axis, whereas I would like to ideally draw a box around the zoom section and it will zoom to where I drew the box. Even if that's not possible, if nvd3 supports any kind of 2-d zoom, that would be awesome! I have provided a reproducible example of what I have so far, but I have not found a feature for the zoom I am looking for yet. Thank you!
library(rCharts)
temp <- data.frame(x = 1:100, y = 1:100, z = c(rep(1,50), rep(0,50)))
g <- nPlot(y ~ x, group = "z", data = temp, type = "lineChart")
g$templates$script <- "http://timelyportfolio.github.io/rCharts_nvd3_templates/chartWithTitle_styled.html"
g$set(title = "Example")
g$chart(transitionDuration = -1,
tooltipContent = "#! function(key, x, y) {
return 'z: ' + key + '<br/>' + 'x: ' + x + '<br/>' + 'y: ' + y
}!#",
showLegend = FALSE, margin = list(left = 200,
right = 100,
bottom = 100,
top = 100))
g$xAxis(axisLabel = "x")
g$yAxis(axisLabel = "y", width = 40)
g

You could use Highcharts with the zoomType option.
For example:
require(rCharts)
names(iris) = gsub("\\.", "", names(iris))
g<-hPlot(SepalLength ~ SepalWidth, data = iris, color = 'Species', type = 'line')
g$chart(zoomType = 'xy')
g
You can then drag and hold on the plot to zoom in an area.

Related

Get crosshair values across multiple Flot Charts

I'm using the jQuery Flot Charts plugin in one of my projects. I have several charts standing in the same column and what I'm trying to do is: Show the crosshair on all of the charts if you hover on any of them. I'm doing this successfully using the following code.
//graphs - this is an object which contains the references to all of my graphs.
$.each(graphs, function(key, value) {
$(key).bind("plothover", function (event, pos, item) {
$.each(graphs, function(innerKey, innerValue) {
if(key != innerKey) {
innerValue.setCrosshair({x: pos.x});
}
});
if(item) {
var x = item.datapoint[0].toFixed(2),
y = item.datapoint[1].toFixed(2);
console.log("x:" + x + ", " + "y:" + y);
}
});
});
I'm iterating over the graphs, adding the crosshair and bind it to each other. So, now, when you hover over one of the graphs you'll see the crosshair in the same position on all of the others.
No problems with this. However I'm having problems with the second part of my code:
if(item) {
var x = item.datapoint[0].toFixed(2),
y = item.datapoint[1].toFixed(2);
console.log("x:" + x + ", " + "y:" + y);
}
And the problem is that I'm getting the console.log to print values only when I hover the actual point with my mouse while I want to get that value whenever the crosshair crosses that point, not necessarily the mouse pointer. Any clues what I'm doing wrong or maybe there's a setting in the graph options for that to work?
And another thing is that I can get the value for one graph only - the one my mouse is on, I don't seem to be able to get the values for the rest of the graphs where the crosshair is also moving.
The highlighting with
if(item) {
var x = item.datapoint[0].toFixed(2),
y = item.datapoint[1].toFixed(2);
console.log("x:" + x + ", " + "y:" + y);
}
only works when the cursor is near a point (otherwise item is null).
To get the nearest point to the crosshair, you have to do the highlighting manually by searching the nearest point and interpolate (for every graph). The code for that could look like this:
var axes = value.getAxes();
if (pos.x < axes.xaxis.min || pos.x > axes.xaxis.max ||
pos.y < axes.yaxis.min || pos.y > axes.yaxis.max) {
return;
}
$('#output').html("x: " + pos.x.toPrecision(2));
$.each(graphs, function(innerKey, innerValue) {
var i, series = innerValue.getData()[0];
// Find the nearest points, x-wise
for (i = 0; i < series.data.length; ++i) {
if (series.data[i][0] > pos.x) {
break;
}
}
// Now Interpolate
var y,
p1 = series.data[i - 1],
p2 = series.data[i];
if (p1 == null) {
y = p2[1];
} else if (p2 == null) {
y = p1[1];
} else {
y = p1[1] + (p2[1] - p1[1]) * (pos.x - p1[0]) / (p2[0] - p1[0]);
}
$('#output').html($('#output').html() + "<br />" + "y (" + innerValue.getData()[0].label + "): " + y.toPrecision(2));
See this fiddle for a full working example. Some Remarks to the new code and fiddle:
has sine and cosine values as example data, so uses floating point numbers, change accordingly for int numbers and/or date values
uses a <p> element for output instead of the console
point-finding and interpolation code can be optimized further if needed (basic version here taken from the example on the Flot page)
works only if there is only one data series per graph

d3.js version 4 - rect zoom - cannot get the transformation right

d3.js verison 4:
I have a line chart, which should have a rectangle zoom.
I used this example: http://bl.ocks.org/jasondavies/3689931
I don't want to apply the rectangle data to the scales, like in the example
Instead I want to apply this to my normal zoom Element.
For that I have the math:
.on("mouseup.zoomRect", function() {
d3.select(window).on("mousemove.zoomRect", null).on("mouseup.zoomRect", null);
d3.select("body").classed("noselect", false);
var m = d3.mouse(e);
m[0] = Math.max(0, Math.min(width, m[0]));
m[1] = Math.max(0, Math.min(height, m[1]));
if (m[0] !== origin[0] && m[1] !== origin[1]) {
//different code here
//I have the scale factor
var zoomRectWidth = Math.abs(m[0] - origin[0]);
scaleFactor = width / zoomRectWidth;
//Getting the translation
var translateX = Math.min(m[0], origin[0]);
//Apply to __zoom Element
var t = d3.zoomTransform(e.node());
e.transition()
.duration(that.chart.animationDuration)
.call(that.chart.zoomX.transform, t
.translate(translateX, 0)
.scale(scaleFactor)
.translate(-translateX, 0)
);
}
rect.remove();
refresh();
}, true);
So I actually get the scaleFactor right and it zooms in smoothly.
Only problem is, that I don't seem to get the translation correct.
So it zooms in to the wrong position.
So, now I got it right:
All transformations by earlier zooms need to be undone.
so that k = 1, x = 0, y = 0;
This is the d3.zoomIdentity.
From that point the current zoom needs to be applied and afterwards the translation.
After that the old transform needs to be applied, first translate and then scale
var t = d3.zoomTransform(e.node());
//store x translation
var x = t.x;
//store scaling factor
var k = t.k;
//apply first rect zoom scale and then translation
//then old translate and old zoom scale
e.transition()
.call(that.chart.zoomX.transform, d3.zoomIdentity
.scale(scaleFactor)
.translate(-translateX, 0)
.translate(x)
.scale(k)
);
Working Fiddle only for X-Axis here: https://jsfiddle.net/9j4kqq1v/3/
Working fiddle for X and Y-axis here: https://jsfiddle.net/9j4kqq1v/5/

How to keep current zoom scale and translate level when re-rendering graph

I have a d3 graph that allows for pan and zoom behavior. It also has subGraph functionality that allows a user to expand and collapse the subGraph. When the user clicks on the icon to open and close the subGraph this code runs:
btn.on("click", function(d:any, i:any) {
parentNode = nodeObject; // gives me he x and y coordinates of the parent node
d3.event["stopPropagation"]();
e["subGraph"].expand = !expand; // add or remove subGraph
window.resetGraph = !window.resetGraph;
console.log(window.resetGraph);
if (window.resetGraph) {
window.scale = window.zoom.scale();
window.translate = window.zoom.translate();
window.zoom.scale(1).translate([0 , 0]);
} else {
window.zoom.scale(window.scale).translate(window.translate);
}
console.log(window.zoom.scale());
console.log(translate);
renderGraph();
});
I'm essentially adding and removing the subGraph property of the node on click of the icon. Then redrawing the graph completely.
I can get the x and y coordinates of the parent container, but if I am re-rendering the graph, how can I go about rendering the graph so that the graph stays at the same scale and translation as it was when I clicked on the toggle subGraph icon. I am trying to redraw the graph at the same position as it was before just with the subGraph either expanded or collapsed. Below is the code that seems to be fighting me when the graph renders:
var scaleGraph = function() {
var graphWidth = g.graph().width + 4;
var graphHeight = g.graph().height + 4;
var width = parseInt(svg.style("width").replace(/px/, ""), 10);
var height = parseInt(svg.style("height").replace(/px/, ""), 10);
var zoomScale = originalZoomScale;
// Zoom and scale to fit
if (ctrl.autoResizeGraph === "original") {
zoomScale = initialZoomScale;
}
translate = [(width / 2) - ((graphWidth * zoomScale) / 2) + 2, 1];
zoom.center([width / 2, height / 2]);
zoom.size([width, height]);
zoom.translate(translate);
zoom.scale(zoomScale);
zoom.event(svg);
};
If I check if the toggle Icon has been clicked, can I redraw the graph with the same scale and translation as before it was redrawn?
Thanks
I just solved it by keeping track of the current scale and translation values:
zoom.on("zoom", function() {
svgGroup.attr("transform", "translate(" + (<any>d3.event).translate + ")" + "scale(" + (<any>d3.event).scale + ")");
// keep track of the currentZoomScale and currentPosition at all times
currentZoomScale = (<any>d3.event).scale;
currentPosition = (<any>d3.event).translate;
});
Then when re-rendering the graph in my scaleGraph function above I just checked to see if the graph was re-rendered because of the subGraph being toggled, if it has then I use the current scale and translation values:
if (ctrl.autoResizeGraph !== "original" && togglingSubGraph) {
zoomScale = currentZoomScale; // render the graph at its current scale
translate = currentPosition; // render the graph at its current position
}
I have created one fiddle for you.
fiddle
Please check the function refreshGraph
var refreshGraph =function(){
window.resetGraph = !window.resetGraph;
console.log(window.resetGraph);
if(window.resetGraph){
window.scale = window.zoom.scale();
window.translate = window.zoom.translate();
window.zoom.scale(1).translate([0,0]);
}else{
window.zoom.scale(window.scale)
.translate(window.translate);
}
console.log(window.zoom.scale());
console.log(translate);
draw();
}
I have used window.zoom (used window to let you know that this is shared)
Button reset toggles the zoom level to 0 scale and toggles back to where it was if clicked again.
Hope it helps.

How to interact with shapes in Pixijs?

I am trying to get the id of the shape my mouse is currently hovering over.
my shapes are in a container
// creating the layers
gridLayer = new PIXI.DisplayObjectContainer ();
gridLayer.setInteractive(true);
stage.addChild(gridLayer);
and i am creating each shape like this;
function drawHexagon(x,y, size, gap,scale, color, iterI, iterJ, type) {
var shape = new PIXI.Graphics();
// set a fill and line style
shape.beginFill(color);
shape.lineStyle(1, 0xa0a0a0, 1);
size = size-gap;
for (i = 0; i < 7; i++) {
angle = 2 * Math.PI / 6 * (i + 0.5);
var x_i = x + size * Math.cos(angle);
var y_i = y + size * Math.sin(angle);
if (i === 0) {
shape.moveTo(x_i, scale *y_i)
}
else {
shape.lineTo(x_i, scale * y_i)
}
};
shape.endFill();
// calculate and save the axial coordinates
var cX = iterJ - (iterI - (iterI&1)) / 2;
var cZ = iterI;
var cY = -1*(cX+cZ);
shape.hexId = cX + "x" + cY + "y" + cZ + "z";
shape.hexPosX = x;
shape.hexPosY = y;
shape.setInteractive(true);
shape.mouseover = function(mouseData){
console.log("MOUSE OVER " + shape.hexId);
}
shape.click = function(mouseData){
console.log("MOUSE CLICK " + shape.hexId);
}
gridLayer.addChild(shape);
}
However, clicking on any shape or hovering over it is not showing me anything in the console. what am i doing wrong?
i have tried both
shape.setInteractive(true)
and
shape.interactive = true
but neither seems to work for me.
EDIT: i have added a jsfiddle. it doesnt works (i dont know how to link things in jsfiddle) but you can see my entire code in there.
http://jsfiddle.net/9aqHz/1/
For a PIXI.Graphics object to be interactive you need to set a hitArea shape (it can be a Rectangle, Circle or a Polygon):
shape.hitArea = new PIXI.Polygon([
new PIXI.Point(/* first point */),
new PIXI.Point(/* second point */),
new PIXI.Point(/* third point */),
new PIXI.Point(/* fourth point */),
new PIXI.Point(/* fifth point */)
]);
Another approach would be to generate a texture from the shape and use a Sprite, but the hit area would be the entire rectangular bounds of the hexagon:
var texture = shape.generateTexture();
var sprite = new PIXI.Sprite(texture);
sprite.setInteractive(true);
sprite.anchor.set(0.5, 0.5);
Fiddle with this applied to your example
#imcg I updated your code so it workes with Pixi 3.0.8
- sprite.setInteractive(true);
+ shape.interactive = true;
+ shape.buttonMode = true;
- sprite.setInteractive(true)
+ sprite.interactive = true;
+ sprite.buttonMode = true;
http://jsfiddle.net/LP2j8/56/
I will add a bit of info for anyone who is in the same boat i was in;
When you define a shape as a geom, you have to explicitly state a hitarea.
So adding the following code makes it work;
shape.hitArea = new PIXI.Polygon(vertices);
shape.interactive = true;
shape.click = function(mouseData){
console.log("MOUSE CLICK " + shape.hexId);
}
But, when you define a shape as a sprite/texture, you dont need to do this.
in cases of sprites, just setting shape.interactive = true for the sprite is sufficient. You dont need to set the interactive property for the parent object or the stage.

Flot Graph Number of Data points

I have a requirement to Plot a graph with zoom feature using x axis selection, i am using Flot for plotting graph. I have to show the number of points present after zooming.
I am alwayz getting the total length not the Zoomed length.
Kindly Help
In your "plotzoom" callback, you can get min & max x / y co-ordinates back like so (from the examples on the site):
placeholder.bind('plotzoom', function (event, plot) {
var axes = plot.getAxes();
$(".message").html("Zooming to x: " + axes.xaxis.min.toFixed(2)
+ " – " + axes.xaxis.max.toFixed(2)
+ " and y: " + axes.yaxis.min.toFixed(2)
+ " – " + axes.yaxis.max.toFixed(2));
});
You can the use these to count how many points are within this rectangle.
May i suggest using underscore.js filter function to simplify this?
Eg.
var xmin = axes.xaxis.min.toFixed(2);
var xmax = axes.xaxis.max.toFixed(2);
var ymin = axes.yaxis.min.toFixed(2);
var ymax = axes.yaxis.max.toFixed(2);
var points = [{x: 1, y:20} ..some points.. {x: 30, y: -23}];
var shownpoints = _.filter(points, function(point){
return point.x > xmin &&
point.x < xmax &&
point.y > ymin &&
point.y < ymax;
}
);
This will give an array with points in-between x & y maxims. You can then use length to get the count!

Categories