I want to display a series of images as an animation -- I'd use a GIF, except I want to manipulate specific frames.
I've displayed the image on an SVG canvas and now I want to change xlink:href dynamically.
function startAnimation(){
c += 1;
d3.select(this)
.transition()
.attr("xlink:href", "images/image-" + c.toString() +".png")
.each("end", startAnimation);
};
This approach works fine if I try to change an attribute like image height, but it doesn't seem to work with the attribute xlink:href.
Is there a way to dynamically update images like this?
You need to preload and cache the images before you start the animation sequence. Otherwise, the images will be loaded lazily—and it's not unreasonable for an image to take 250ms to load, which means that by the time the image is loaded, you're on to the next frame.
For HTML elements, you can preload by creating Image objects (new Image), setting the src attribute, and then listening for load events (onload) to see when all the images are ready. Browsers use the cached in-memory image when you set the src of a displayed HTML img element from a preloaded Image.
This technique may or may not work for SVG images, though. An even better technique is to use a sprite sheet, where you combine all the frames of your animation into a single image, which you then crop on the client. In HTML, you commonly use the background-position style for this; in SVG, you can do the same thing by clipping or overflow: hidden.
Yet another option is to create multiple svg:image elements and then hide all but one (e.g., via opacity or positioning the elements off-screen).
(Also, since you are not interpolating attributes, it might be simpler to use setInterval for your animation rather than d3.transition.)
nodeEnter.append("svg:image")
.attr('x',-9)
.attr('y',-12)
.attr('width', 15)
.attr('height', 24)
.attr("xlink:href", function(d) {
if(d.Type=="name"){
return "/images/icon-1.png";}
else{
return "/images/icon-2.png";}
});
Related
I'm porting over a d3 application into Aurelia and need to access the width and height of the chart's SVG parent in order to fit the chart properly to the current screen dimensions.
Before porting, it looked like this, filling up the whole container properly:
This is what it looks like in Aurelia:
It sets its dimensions by calling d3.select('#timeline-svg').style('width') and d3.select('#timeline-svg').style('height'). But now, in Aurelia, whenever I call those it returns dimensions of 300 x 150, no matter what the dimensions of the SVG actually are. I tried calling the same code on the SVG's div parent (which has identical dimensions) and that didn't help either.
So then I thought to try two-way data binding and changed my SVG tag to <svg id="timeline-svg" width.two-way="width"></svg> (and declared a corresponding width variable in my view-model), but I get the error: Error: Observation of a "svg" element's "width" property is not supported.
I've even tried using aurelia-pal, injecting it as DOM and calling:
attached() {console.log(this.DOM.getElementById("timeline-svg").style.width);} but all that gives me is an empty string.
There's a gist here (minus the d3 code, but all I want to figure out is how to access the dimensions of the SVG element in app.html from within app.js). What am I doing wrong? Thanks!
I'm not sure if this is exactly what you're looking for, but you could use the ref custom attribute to get a reference the svg element itself.
<svg ref="svgElement"></svg>
Then use the getBBox() function. The result of that function will have a width property you can use.
this.svgElement.getBBox().width
Here's a simple gist example: https://gist.run/?id=0ed86f511533512a22d002675221d812
I have two div on html page having id container1 and container2 i have created svg for each div and each svg contain circle, Now i want to connect two circle
Is it possible to connect two circle from two svg file on same page (cx,cy of both circle should genrate automatically)
My code snnipet..
Html
<div id="container1 " style="width:900px;height:800px;border:solid;"></div>
<div id="container2 " style="width:900px;height:100px;border:solid; margin-top: 25px;"></div>
created svg for container1 ,container2 using below code
var svg = d3.select("#"+id).append("svg")
.attr("width", $("#"+id).css("width"))
.attr("height",$("#"+id).css("height"));
and draw circle for each container using force layout
Now I want to connect these two circle using line
How is it possible ???
For your general question "how is it possible?", here is a general approach to get you started:
Super-impose a third, mostly transparent SVG over the whole page using absolute positioning. Draw the line inside this SVG.
Use .getScreenCTM() (get screen cumulative transformation matrix) to calculate the position of each circle on the page.
Use the same function to figure out the transformation from the screen to your overlay SVG, and multiply one screen CTM by the inverse of the other to get the net transformation so you can figure the start and end coordinates of your lines from the coordinates of your circles.
Add a listener to the web page as a whole for any re-layout events, and re-do the calculations above as necessary.
If all of that sounds too confusing, you might want to come up with an alternate web page design that puts all the graphics in one SVG. Or one which allows for a different way to indicate that elements are connected (same colour, or hover over one causes highlight on the other).
P.S. You might be able to use .getTransformToElement() to replace steps 2 and 3, but you'll want to test that out thoroughly. I've never tried using it to find the transformation between elements in different SVGs on the same web page.
Take a look at this jsfiddle. Magnifying on hover works well for text and images, but I would like to have the same effect for the chart.
I would like the solution, if possible, to be applicable to any D3 SVG-based chart.
The example uses jquery plugin AnythingZoomer, it looked to me as a good starting point, however, you don't need to stick to it, you can use anything else if you wish.
I am aware of D3 fisheye pluging, this is related, but not quite what I want.
You can do this by not explicitly declaring width and height in the SVG (which is overwritten by CSS anyway), using the viewBox attribute, and then allowing AnythingZoom to clone the content of your original chart.
Demo (Fragile): http://jsfiddle.net/H9psX/ http://jsfiddle.net/H9psX/38/
Changes
var svg = d3.select("#small-chart").append("svg")
// .attr("width", diameter + 300)
// .attr("height", diameter)
.attr('viewBox', "0 0 " + 225 + " " + 225);
// ...
$("#zoom3").anythingZoomer({
clone: true
});
Separation of concerns
Since you are drawing in SVG using D3 (where you need to know the width and height for the pack layout) and using a jquery plugin which zooms by setting classes and absolute positioning, you have to share the coordinates (the 225px magic number) in CSS and in JS.
Ideally, you would want to keep the magic number at only one place. To do that you can declare the value only in CSS and then read them in your JS after creating your SVG element.
The SVG logo on this site doesn't look sharp on every zoom level. I read once, that SVG is just sharp on a multiple of its original size. But when I rightclick on the graphic and display it alone (without an img tag around it), it looks sharp on every possible zoom. There is no width or height given to the image.
It appears that Firefox renders the SVG to an image when referenced via an <img> tag. Use an <object> tag
I believe the issue is in Firefox.
Try to set the image width to 100% and height to the actual height of the SVG and this will solve the issue.
For Instance.
img{width:100%;
height:xxpx; /* Where 'xx' is the value of the image height in pixels */
}
Hope this Helps.
Back story: I have an SVG canvas with some polylines on it. I also have some HTML <span> and <textarea> elements that need to be positioned precisely in relation to those polylines.
I started by putting the HTML elements in the SVG in <foreignElement> tags, but I had a problem there because IE doesn't see them at all and Firefox doesn't see the <textarea>s. So I took them out of the SVG and now every browser sees them.
So far so good. Now the only way I know to make sure they position correctly with the polylines is to give both the HTML elements and the SVG canvas absolute positions with CSS.
Here's my problem. Above all these elements is a header div. I want the whole SVG business to sit at a reasonable distance below the header. Say 15px. But since the SVG is absolutely positioned, I need to know the height of that header div to get the SVG and related HTML elements into the right place.
I've tried jQuery's .height() method and some related methods. The problem with all of them is that Firefox and Chrome give two different results. I know this doesn't reflect a real pixel height difference between the two, because I can see visually that the header is slightly taller in FF, yet FF gives a smaller height reading.
How can I get a browser-consistent height reading for my header div? Or at least one that I can use to absolutely position other elements at the same distance below it in every browser.
You could try with this function
function getHeight() {
return Math.min(
Math.min(document.body.scrollHeight, document.documentElement.scrollHeight),
Math.min(document.body.offsetHeight, document.documentElement.offsetHeight),
Math.min(document.body.clientHeight, document.documentElement.clientHeight)
);
}
or simply with document.documentElement.clientHeight, which usually does the trick for me in all the browsers I use for testing (Chrome, Firefox, Safari and Opera)
[edit] The function above returns the width and height of the body, in order to use it for any div, use this one
function getHeight(div) {
return Math.min(div.scrollHeight, div.offsetHeight, div.clientHeight);
}
You can use it like this
var myDiv = document.getElementById('myDivId');
console.log('the height is ' + getHeight(myDiv));
[edit2]Keep in mind that the divs might actually have a different size depending on the browser.
Let's say this is google chrome and the green bar at top is the navigation bar, with a height of 75px. You have it at 100%, filling up your screen, who has 1000px height, and you place an 100 pixels div to the top and also stick an 100px div to the bottom of the screen (with blue). The purple div between them will have an 725px height.
And this below is firefox. It's placed on the same 1000px screen, also at 100%, but its navigation bar has 100px height. With the same 100px blue divs to the top and the bottom, the purple div will have a height of 700px here, different from chrome.
Of course, this is a very, very simple example and I doubt this is your case. But you might have a similar problem with div placements and it's something you should try to check.