I'm trying to implement a slider in mpld3, much like this previous question.
I'm trying to build off of the draggable points example to do this. I'm having a bit of trouble understanding how the following bit of code works:
function dragged(d, i) {
d[0] = obj.ax.x.invert(d3.event.x);
d[1] = obj.ax.y.invert(d3.event.y);
d3.select(this)
.attr("transform", "translate(" + [d3.event.x,d3.event.y] + ")");
}
In particular, what does this refer to in this context. I originally thought that I could replace d3.select(this) with something like d3.select("#"+foo) where foo = this.props.id (at the top of the draw() function). But this doesn't work, as shown in this notebook I made. (The second piece of code doesn't allow you to drag the red dots around).
In case what I'm trying to do isn't clear... have a look at this notebook. I've made a plugin that allows the red square (the slider) to be dragged horizontally. What I would like to do is make dragging the red dot change the position of the blue dot. So I want to do something like:
function dragged(d, i) {
d[0] = obj.ax.x.invert(d3.event.x);
sliderPosition = obj.ax.x(d[0]);
targetPosition = obj.ax.x(-d[0]); // inverted sign
d3.select("#redsquare")
.attr("transform", "translate(" + [sliderPosition,sliderObj.ax.y(d[1])] + ")");
d3.select("#bluedot")
.attr("transform", "translate(" + [targetPosition,targetObj.ax.y(d[1])] + ")");
}
The intended behavior for this simple example is to have the blue dot move in the opposite direction of the red square when it is dragged. The question is, what do I put in place of "#redsquare" and "#bluedot"?
Many thanks.
I know a hacky way to do this. Instead of d3.select(this), you can find the specific element you are interested in the obj element array as follows:
d3.select(obj.elements()[0][i])
There must be a prettier way, though.
Related
I just started learning D3 and am having trouble understanding how to manipulate selections. My visualization is functioning very slowly and someone recommended to me to use Timeline to find the source of the problem. I looked over it and found that this snippet of code is most likely the culprit:
for (var i = 0; i < staticSvgLength; i++) {
var curritem = d3.select(svgChildren[0][i]);
if (curritem.attr("class") != "graphbutton") {
curritem.remove();
}
}
Here is a snapshot of the timeline that suggests to me that the problem is remove:
svgChildren elements are selected using:
var svgChildren = d3.selectAll(svgContainer[0][0].childNodes);
There are thousands of elements, most of which can be found within the following variable:
var rectangles = svgContainer.selectAll("svg")
.data(rawDataStore)
.enter()
.append("rect")
This is the graph I am trying to create:
The elements which I'm trying to prevent from being removed are the buttons in the upper left corner which are also stored in svgContainer. As a side question, is it bad practice to store the buttons in the same svg element I store the main graph?
EDIT: Here is where I define svgContainer:
var svgContainer = d3.select("body").append("svg")
.attr("Call", "svgContainer")
.attr("height", max_y)
.attr("width", max_x)
.append("g")
.attr("transform", "translate(" + margin.left + "," + (margin.top) + ")");
I select all the SVGs to be removed because when you click a button, it switches to a different graph. I don't want the two buttons to be removed, only the graphs, so I tried to exclude them from the selection by giving them a class and then removing all elements in svgContainer excluding the items that have the button class.
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 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
I've generated a D3 visualization (a force directed graph) that requires zooming and panning. I've got 2 problems however when it comes to zooming, and I can't find any decent examples on how I might overcome these problems:
The first problem is I've followed all the examples I can find about zooming, which involves adding groupings and adding a rectangle to ensure that the entire area is zoomeable. If I style the rectangle a slightly opaque blue then I get SVG that looks like this when I zoom out:
The problem with this is that I can zoom in/out absolutely fine while I've got my mouse over the blue rectangle area. The problem is I want this to be fully opaque, which means that when I zoom right out, it's very easy to place the cursor outside of this box and then you're unable to zoom in. Is there a way I can make the SVG itself zoomeable or pick up on these events?
This is how I go about generating the various layers and the zoomed function:
function zoomed() {
group2.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
svg = d3.select(target)
.attr("pointer-events", "all")
.append("svg");
group = svg.append('svg:g')
.call(d3.behavior.zoom().on('zoom', zoomed))
.on("dblclick.zoom", null);
group2 = group.append("g");
rect = group2.append('svg:rect')
.style("opacity", 0.3)
.attr('width', width)
.attr('height', height);
The second problem I have is that I'm trying to automatically size my text based on this http://bl.ocks.org/mbostock/1846692 example. When I've tried this however I seem to be getting text that renders really poorly. It seems to suffer from:
Being difficult to read
Not appearing contained within the circle
Being so small the entire thing compresses (image 2)
var texts = planets.append("text")
.text(function(d) { return d.name; })
.style("font-size", "24px") // initial guess
.style("font-size", function(d) {
return Math.min( 2 * d.size, (2 * d.size - 8) / this.getComputedTextLength() * 24) + "px";
})
.attr("dx", function(d) { return -d.size; })
.attr("dy", ".35em")
.style("fill", "white");
I thought that SVG would just handle this, I understand that some of the font-sizes can come out small, but if you zoom in should that not all sort itself out?
I've got a JSFiddle http://jsfiddle.net/IPWright83/vo7Lpefs/22/ to demonstrate.
I've not yet managed to work out a resolution to my first issue (regarding the zooming box) however I did manage to track down the text rendering issue.
This was actually because the each circle/node had a stroke property to provide the white border. This was also applying to the text element, and when the font was very small the stroke was much larger than the overall fill of the text. Removing the stroke from the text elements ensured that they rendered even when very small.
I would like to create a mashup of the functionalities as seen from
http://bl.ocks.org/4063423 and http://philogb.github.com/jit/static/v20/Jit/Examples/Sunburst/example2.html
I would like to use d3.js or at least a pure javascript solution but a solution that will respond to mouse clicks to display more information about the selected section.
Zooming in and out is not mandatory, but if I can achieve it, it will be good.
Now my question, Is there a framework that can support this or do I have to mash them up on my own.
Disclaimer: google was not that helpful!
It is easy to do with D3 alone: http://bl.ocks.org/4678148 If you click any element, the element will be focused and transitioned to 90 deg with the selected class set on it.
Also, the legend text on the top right changes to the name of the element selected. The part of code which achieves this coupling is:
d3.selectAll("path").on("click", function (d, i) {
var newAngle = - (d.x + d.dx / 2);
innerG
.transition()
.duration(1500)
.attr("transform", "rotate(" + (180 / Math.PI * newAngle) + ")");
// Set the class "selected" on the chosen element.
path
.classed("selected", function (x) { return d.name == x.name; });
// Update the text box with the right context
// This can be arbitrarily complex to show as many details about the
// object as required.
textBox.data(["Clicked: " + d.name])
.text(String);
});
Update
For the zoomable behavior such that the clicked element transitions to the center, you can use almost the same code as used as here or here. I have made small changes to the code to show how to extract information about which item was clicked: http://bl.ocks.org/4747342
The change in code required is simpler than before:
d3.selectAll("path").on("click", function (d, i) {
// Zooming
path.transition()
.duration(750)
.attrTween("d", arcTween(d));
// Update the text box with the right context
// This can be arbitrarily complex to show as many details about the
// object as required.
textBox.data(["Clicked: " + d.name])
.text(String);
});