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
Related
I am building a d3js chart that plots some dots on a gradient scale of agree to disagree. I need it to be able to zoom and pan and I have all of that working except for a rectangle holding a linearGradient. The gradient zooms just as I need it, but it scales up both horizontally AND vertically, expanding past the original 20px height:
Or shrinking excessively:
I tried to use a clip path which is clearly not working, it seems that the clip path scales with the gradient. How can I clamp the rectangle to the axis and keep it the same size?
Here is my block
Thank you!!
There are two primary challenges from what I can see:
the rectangle filled with the gradient scales on the y axis as you note.
the clip path isn't working as intended.
Let's drop the clip path, that'll make things a bit easier. Secondly, let's not scale the rectangle at all when we zoom (we can keep the translate that is applied though).
Now that I've destroyed what we had, let's build it back up.
Instead of scaling the rectangle with transform(scale()) let's modify its width directly. If d3.event.transform.k is the factor at which we were scaling (both x and y), let's just modify the width of the rectangle. Instead of:
gradientScale
.attr("transform","translate("+d3.event.transform.x+","+(height- spacer*3)+")scale("+d3.event.transform.k+")");
Let's use:
gradientScale
.attr("transform", "translate( " +d3.event.transform.x+ " , " + (height - spacer*3) + ")")
.attr("width", width * d3.event.transform.k);
By removing the scaling, the above won't warp any coordinates, which won't lead to stretching of the y axis. It doesn't modify any y coordiantes, so the bar stays where it is height wise with the same height.
It does modify the width - by the same amount we were scaling it before (the rectangle's width at k = 1 is width). This achieves the stretching of the scale as we zoom in. The x translate factor is unchanged.
Here's a bl.ock of the modified code.
My initial thought before looking closely was to try a completely different approach. So, for comparison, here's a completely different approach modifying the axis itself.
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
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.
I have a bit of an annoying problem.
I'm trying to position a bunch of SVG circle elements according to an existing bunch of SVG text elements that share similar properties.
The circle elements are created in a very separate process than the text elements, so positioning the new elements just using the same transforms etc. as the old one isn't a viable option.
I'm trying to use .getBoundingClientRect() to get the positions since the text elements are transformed into position (so .getBBox() isn't an option) rather than positioned by x and y attributes.
With .getBoundingClientRect(), I can get the correct size/arrangement of the new elements, but since the width of the svg-containing div is variable, there's always a bit of a weird offset that I can't quite account for.
I created a simplified example of my issue here. Resize and refresh the page to see the issue in action.
The code I use to position the circle elements is replicated below.
var circs = theSvg.selectAll("circle")
.data(theCircles)
.enter()
.append("circle")
.attr("r", 15)
.attr("fill", "#f00")
.style("opacity", 0.3)
.attr("transform", function(d){
var sizeDif = 800/(d3.select(".svgTestHolder")[0][0].getBoundingClientRect()["width"]);
var theNum = parseInt(d.split("&")[1]);
var thePosition = theSvg.selectAll("text").filter(function(e){
return e == theNum;})[0];
var theCoords = thePosition[0].getBoundingClientRect();
var leftOffset = d3.select(".svgTestHolder")[0][0].getBoundingClientRect()["left"];
var leftOffset2 = d3.select(".svgTest")[0][0].getBoundingClientRect()["left"];
var bottomOffset = d3.select(".svgTestHolder")[0][0].getBoundingClientRect()["top"];
var bottomOffset2 = d3.select(".svgTest")[0][0].getBoundingClientRect()["top"];
return
"translate(" + ((theCoords["left"] - leftOffset - leftOffset2)
* sizeDif) + "," + ((theCoords["top"] - bottomOffset - bottomOffset2)
* sizeDif) + ")";
})
EDIT:
This is a very delayed update just to note that while I was unable to answer my question as stated, I was able to make a workable solution based on Paul LeBeau's suggestion to extract the transforms from the target element.
In my case, I had to use a series of consecutive transforms rather than a combination of transforming and changing the x/y position (due to certain realities of the project not represented in the linked example). But I'm happy to have found an answer!
Your example works fine for me on Chrome. But really that's only because the SVG is the only thing on the page. If I add some text above the SVG everything goes wrong.
https://jsfiddle.net/rrpfmm6d/1/
Is this the problem you are talking about?
If so, the reason is because you are making the wrong choice in using getBoundingClientRect(). It provides coordinates in screen space. It's origin is the top left of the window (or iframe in the case of jsfiddle).
You should be using getBBox(). The values it returns are in the same coordinate space as the SVG elements. It's origin is (normally) at the top left of the SVG.
In summary, use the coordinates returned by calling getBBox() on your <text> element to calculate the position for your circle. If the circles are inserted into the same SVG as the text, there will be no need to do any adjusting with the div or svg offsets.
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.