Javascript D3.js drag a chart out of its svg Element - javascript

I want to make chart, that you can not drag out of its svg element.
I'm doing this at the moment like this jsfiddle
As you can see, you can zoom and drag this freely. What i want is this:
If you drag it for example to the right and the y axis hits the edge of your screen on the left it should stop and not be able to be dragged anymore to the right.
Which also means, that you can't drag it around while not zoomed in, because it already fills its svg area.
I guess i have to somehow restrict my redraw method. At the moment it's just this
function redraw() {
plotChart.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
};
It probably has to check if for example the left edge of the chart hits coordinate [0][x] and then somehow stop drawing it any further out.

To know the axis point on scaling and on translation you need to get the CTM and then transform to get the real points.
//gets CTM on the first y line that is the group
var ctm = d3.select(".x").select(".tick")[0][0].getCTM();
//the first y line
var k = d3.select("svg")[0][0].createSVGPoint();
//current point without transform
k.x = d3.select(".x").select("line").attr("x2");
k.y = d3.select(".x").select("line").attr("y2")
j = k.matrixTransform(ctm)
//j is the real point of the line post transform.
//your code to zoom or pan depending on the value of j

Related

SVG coordinate conversions

I am using SnapSVG to display a pretty complex image. Part of the image is a circle that I would like to rotate when the user does a click/drag.
I've been reading all evening about coordinate systems and it just isn't clicking. I read this page and tried to adapt the code to convert the screen location of the click to my SVG coordinates.
Here is the code I'm trying...
function dragStart(x, y, event) {
var inverse = event.srcElement.getScreenCTM().inverse();
var point = svg.createSVGPoint();
point.x = event.clientX;
point.y = event.clientY;
var final = point.matrixTransform(inverse);
console.log('Before: (' + point.x + ', ' + point.y + ')');
console.log('Final: (' + final.x + ', ' + final.y + ')');
}
The odd thing is that the X value is returning exactly what I would expect. But the Y value makes no sense at all.
EDIT: Fiddle added here
I've stripped the SVG down to the bare minimum. The black outer ring is static. The white inner ring will rotate when the user clicks and drags. The SVG was created so that the center of the ring is at (0,0) and the SVG has a width and height of 600. Open your browser's developer tools console and click on the image. As you can see, when you click close to the center of the red circle the x coordinate is near zero as expected. But the Y value is way off.

D3.js - Fading in data points sequentially

I've just started learning d3. I've made some progress on learning, but I've run into something I haven't been able to figure out on my own.
Here is what I have so far: http://tributary.io/inlet/83fba4500986b4638326
What I've been trying to figure out how to do is fade in the data points as the line path animates through them. The best idea I had was dividing the transition time by the number of points and then have the delay for each data point be decided by that, but I had trouble getting that working properly.
Is there a reasonable way to do this?
P.S. I also seem to have lost my y-axis labels and am not sure why... any ideas?
Thanks for your time and help!
So let's start by the easy part. The Y axis is missing because your svg elements have an overflow:hidden and the second svg element is stuck to the top left of corner of the first one. some x,y space plus overflow:auto solve the problem.
For fading the circle when the path go trought it, I don't think you can do that with one transition. So, a solution, because the path drawing works on offset from total length to 0, you can calculate the distance between each circle and "transition" the path from circle to circle, fading it at the end of the transition. To do so, get the circles coordinates, calculate the distances, and set the transition loop.
//Get coordinates
svg.selectAll("circle").each(function(){
coordinates.push({x:d3.select(this).attr("cx"),y:d3.select(this).attr("cy")});
});
//Get Distances
for(var j=0;j<coordinates.length-2;j++)
{
var a,b,distance;
a= coordinates[j];
b= coordinates[j+1];
distance = Math.sqrt((Math.pow(b.x-a.x,2))+(Math.pow(b.y-a.y,2)));
//console.log("j:" + j + " a: " + JSON.stringify(a) + " b: " + JSON.stringify(b) + " distance: " + distance);
distanceData.push(distance)
}
//Loop transition
var counterTransition =0,currentLength=distanceData[counterTransition];
path
.attr("stroke-dasharray", totalLength)
.attr("stroke-dashoffset", totalLength)
.transition()
.duration(500)
.ease("cubic")
.attr("stroke-dashoffset",totalLength-currentLength)
.attr("class", "line").each("end",animeNext);
function animeNext(){
counterTransition +=1;
if(counterTransition<=distanceData.length)
{
var circle =svg.selectAll("circle")[0][counterTransition];
circle=d3.select(circle);
circle.attr("opacity",0.5);
currentLength += distanceData[counterTransition] ;
path
.transition()
.duration(500)
.ease("cubic")
.attr("stroke-dashoffset",totalLength - currentLength)
.attr("class", "line").each("end",animeNext);;
}
Example : http://tributary.io/inlet/e2eab2e689479008f11c
I simplified the data generation for the sake of the test. Removed the draw on click. It looks like that the code executes 1 to 3 times randomly but I think it is tributary.
You can play with the duration to improve transition smoothness.
Hope it helps!
EDIT: Other solution with one transition, using a short interval(100ms) and checking if the path's stroke-dashoffset is equal or greater than each circle's distance and if so fade the circle. Clear the interval at the end of the transition.

How to give the same boundary to multiple draggable objects?

I wish to set a boundary for two rectangles in my SVG.
I found this example: http://bl.ocks.org/mbostock/1557377
In the example the boundaries get worked out from the position of the object that is dragged. Every circle in the example can only move a certain distance from where it started. What I wish to do is to create one drag function and use it on multiple shapes. This drag function will stop the shapes from going out of a certain area.
For example: I have a rectangle on the left side of the screen and one on the right but I don't want any of them to be able to go off screen. I started working on this but figured out this worked with regards to the position of the object getting dragged. So this works for the left hand rectangle but the right hand rectangle can go offscreen to the right but only so far to the left
.on("drag", function(d) {
g = this;
translate = d3.transform(g.getAttribute("transform")).translate;
x = d3.event.dx + translate[0],
y = d3.event.dy + translate[1];
if(x<-10){x=-10}
if(x>width-10){width-10}
if(y<-10){y=-10}
if(y>height-10){y=height-10}
d3.select(g).attr("transform", "translate(" + x + "," + y + ")");
d3.event.sourceEvent.stopPropagation();
My question is: how do I impose the same boundary on anything that is dragged? i.e I don't want it to go off screen. I have variables width and height which are screen width and screen height respectively

Positioning a transformed SVG element within an svg-canvas

I have written a function that will resize an SVG path, or any shape. However when i use it the path does gets resized but unfortunatetly it also changes position within my svg-canvas.
this is my function
function output()
{
var transformw=prompt("Enter your new width");
var transformh=prompt("Enter your new height");
var lastw = svg_1.getBoundingClientRect().width;
var lasth = svg_1.getBoundingClientRect().height;
newW=transformw/lastw;
newH=transformh/lasth;
alert(newH);
alert(newW);
svgCanvas.changeSelectedAttribute("transform",
"matrix(" + newW + ", 0, 0, " + newH + ", 0, 0)");
svgCanvas.recalculateAllSelectedDimensions();
}
I only want the shapes to be positioned on the top corner of my canvas once they get transformed. Ideally i would want them to have the same x,y position they had before the transformation however i wouldnt mind to have a fixed point if the original x,y position is difficult to achieve.
i am answering my own question.
When we resize an SVG element using transform the elements gets moved in the x,y axis relative to the transformation we did.
To counteract this effect we just need to apply a negative translation on the element that has the same ''transformation'' parameters albeit negatively(it moves it to the opposite direction than what the transformation does.
This way we counteract the positioning effects of a tranformation and we only get the resizing effects.

Raphael svg invert y-axis coordinates

I am using the Raphael library from http://raphaeljs.com/ and work on a chart library. For this library it is useful when the Y-axis are inverted. Now 0,0 is at the top left but I want it to be at the bottom left.
There is a possibility to apply a scale matrix to an element but I want the coordinates to be inverted for whatever I draw. Any clues?
The only way I could figure out to do this was to apply a negative scaling to the svg element using CSS (see this fiddle). (I used jQuery to add the styles).
This is not without problems, though. For example, text is going to be mirrored, unless you do something to un-mirror it (like applying the invert() method I added to elements using Raphael.el):
Raphael.el.invert = function() {
this.transform('s1,-1');
};
Also, if you are going to be interacting with the elements using your mouse, you will have to tweak things. Note that the black circle uses a pretty standard mouseMove function, but it doesn't work - it moves in the wrong direction in y. So you have to do something like I did with the other circles:
function cMove(dx, dy, x,y) {
this.attr('cx', x);
this.attr('cy', paperHeight - y);
};
In short, this is not at all elegant, and no other things I tried were really any better. I know this isn't what you want to hear, but I would recommend getting used to the coordinate system as it is, unless you just plan on displaying static charts.
One small issue with Mike C's resolution is that you have to know if the text is going to be inverted in the end when you create the text. If you want to ensure right-side-up text at the end (after applying other transformations) I found it works well to alter the text element's .transform() to flip the scale of text to right side up at the end.
function InvertText(ObjSet){
// This function resets the inversion of text such that it is always right side up
// ObjSet is a raphael paper.set() object
for (var i=0; i<ObjSet.items.length; i++){
var ThisObj = ObjSet.items[i];
if (ThisObj.type == 'text'){
var tArr = ThisObj.transform();
// Find the scaling factor
for (var j=0; j<tArr.length; j++){
if (tArr[j][0] == 's'){
tArr[0][2] = 1;
break;
}
}
ThisObj.transform(tArr);
}
}
}
You can use like this:
var ObjSet = paper.set().push(
paper.text(0,10,'FirstText'),
paper.path('M,0,0,v,100,h,20,v,-100,h,-20'),
paper.circle(0,0,5)
);
//Flip everything on the y-axis
ObjSet.transform('s,1,-1, T,100,100');
// Make the text right-side-up
InvertText(ObjSet);
Here's how to do it just with RaphaelJS transforms, no CSS transforms.
var SCALE = 2;
var paper = Raphael(0, 0, 400, 700);
// box notched at bottom-center and right-center
var p = paper.path("M0,0 L100,0 L100,40 L90,50 L100,60 L100,100 L60,100 L50,90 L40,100 L0,100 Z");
var bounds = p.getBBox();
p.attr({
stroke: 'none',
fill: [90, '#578A6E', '#34573E'].join("-")
})
.transform("t"+ (-bounds.width/2) +","+ (-bounds.height/2) +
"s"+ SCALE +","+ (-SCALE) +
"t"+ (bounds.width/2) +","+ (-bounds.height/2));
Raphael applies scale transforms from the center of the element's bounding box, rather than its origin. To invert the y-axis, offset before scaling, then offset again after.

Categories

Resources