I have an SVG which I have imported using d3.xml and I am trying to zoom in when I have clicked one of the ellipses in my SVG. This almost works, though I am missing some functionality of locating the elements when the size of the SVG changes. Here is the code I am using to zoom:
svg.transition().duration(750).call(
zoom.transform,
d3.zoomIdentity
.translate(widthContainer/2 , heightContainer/2)
.scale(2)
.translate(-d3.select(this).attr('cx'), -d3.select(this).attr('cy')),
d3.pointer(event, svg.node())
);
This works when the size of the browser window represents the size in which the svg was created, because the cx and cy values of the ellipses then can be used to locate them, though when I increase the size of the browser window this of course doesn't work anymore because the cx and cy values are now way off. How can I center and zoom to an element independent of the current context? I'm guessing there is a way to find out the current scale of the parent div and then calculate the translate parameters from there but I haven't found anything after a couple hours of trying.
I found a solution to my problem, my biggest error was assuming that to make my map responsive I would need to update my vars widthContainer and heightContainer (which are used to make calculations in my code for the zoom position etc.) with the current viewport width when the page is loaded and on resize. This introduced a bunch of very weird behavior and led to me spending hours on debugging that could have been avoided.
The solution to making a map with zoom features responsive is to have these two vars set to fixed values. If you are using an SVG that you are reading as xml with d3, then use the values that are set in the SVG's viewbox attribute, e.g. viewBox="0 0 657.58 344.67".
Then you can handle your container, where your SVG element is in, as usual in css and set your width to 100% or whatever you please, and the map should act responsively. I'm a total noob in d3 and JS so this behaviour didn't seem logical to me, especially because the documentation on d3's zoomIdentity is very poor. To regular d3 users this probably seems obvious but I hope this reaches fellow d3 beginners out there.
Related
The most recent version of amCharts (I'm using v4.7.8) includes a couple of mechanisms to allow for responsive chart designs. I'm trying to take advantage of the chart.responsive.rules feature, which allows for different chart property values based on the size of the chart (and/or inner chart elements).
At the same time, we've implemented a "zoom" feature on all of our charts. Each chart is wrapped in a "zoomable" container element, and the charts are styled to be 95% of the container width. When the "zoom" icon-button is clicked, the container is set to position: fixed; top: 3rem; right: 3rem; bottom: 3rem; left: 3rem;, mostly filling the screen.
The problem is that the chart just... doesn't seem to notice this. If I create the chart at the zoomed size, it uses the correct ruleset, but if I create the chart at a smaller size, then resize it to be large, it continues to use the "small" ruleset. I've tried calling chart.appear, chart.invalidate, and chart.deepInvalidate after resizing, but the result is always the same: the chart renders with whatever ruleset it was originally initialized with.
The one thing that makes me think that at least SOMETHING is happening (though mis-timed) is the fact that when I zoom in (so the chart is fullscreen) and then use the horizontal scrollbar, the scrollbar moves faster than my mouse cursor - almost as if it thinks it's smaller than it is. That holds up with my initial impressions. However, once I resize the chart BACK to being small, the scrollbar moves slower than my mouse cursor, as if it thinks the chart is BIGGER than it is.
This whole thing is all done with multiple LitElement web components, so it's difficult to provide a fiddle/codepen example, but if necessary I can mock up an example that should at least demonstrate the problem. I'm not sure if that would be necessary, as I suspect someone who knows what they're talking about would know the answer to this offhand, but if you think it'll help, let me know and I'll see what I can do.
Edit: I've found a workaround, but it's hardly ideal. I found that if I completely dispose of the existing chart on zoom in/out and completely recreate it at the new size, then it behaves the way I want, but it is SERIOUSLY processor intensive to recreate some of these charts. I'd still very much prefer to find a solution that can take advantage of the current chart and just recalculate its layout.
try using setTimeout(()=>{chart.responsive.enabled = false;chart.responsive.enabled = true; }) after resize
Turns out, the answer is that amCharts 4 does not support the actual properties I'm trying to change via the responsive rules. The properties in question are groupData and groupCount. Support tells me that the logic surrounding those features was considered too complex to support dynamic updates. I asked them to please reconsider that decision. We shall see if they do.
What I've done in the meantime to solve my problem is to create two charts, one configured for the larger size and one for the smaller size, and I just swap out which one is displayed when zooming in/out.
I have a website which sends about 7 megs of json data to the client, where d3 (actually dimplejs) charts are rendered. The page is getting to be pretty slow and simply doesn't work in some browsers.
I'm thinking of rendering the svg in the server, instead of having the client browser do it. I've seen several references this technique. However, since labels, bar widths and so many elements depend the pixel height and width of the chart, and these dimensions are not known until client's browser renders the page, how do people handle such issues?
I'm not a web/front-end developer normally so I don't know if I'm missing something obvious or if my assumptions are wrong.
The simplest thing is to take the rendered <svg> and add a viewBox attribute to it. That allows you to specify how you want the browser to scale/stretch or crop the <svg> when it's placed inside a parent container (eg <div>).
Anything beyond that — like selectively scaling or repositioning text or rects or axes — can only be achieved the "hard" way; i.e. with code that has some awareness of the chart's semantics and programmatically alters certain svg attributes to react to container size.
Finally, and this is a long-shot: If you have full control of the output (I'm guessing your don't, since you're using dimple), you could attempt using a combination of absolute and percentage units to size and position things, as conceptually demonstrated here
I have a large chart produced with Raphael, that I want to display in a printer-friendly page. With smaller charts this works beautifully even down to IE7 (min requirement).
But if the chart is too long vertically it doesn't split across pages, instead the bottom is just cut off; this happens in all browsers.
Any suggestions as to how I might solve this problem? Preferably without just making it smaller.
Thanks
What you want to achieve is not directly possible. Raphael container element marks overflow:hidden style and as such for browsers that considers partial page split as overflow, it would be very difficult to split the output - especially if the overflow is horizontal.
As a workaround, you may convert Raphael's SVG into canvas by using the canvg JS library. Canvas, being a single raster element, will allow seamless overflow.
You can have a look at this fiddle where I use FusionCharts' (internally using Raphael) SVG to output to a canvas.
canvg("myCanvasElementId", myRaphaelPaper.canvas.parentNode.innerHTML);
In the above code snippet, I assume that you already have a canvas element with id myCanvasElementId and a Raphael paper called myRaphaelPaper.
Refer to this fiddle where I have rendered Raphael SVG and exported it to canvas and then made it visible only for CSS print media. The Raphael element is made to render 15in by 15in to overflow document.
Note that this will be limited to modern browsers supporting canvas.
Basically, what I'm trying to do is use a map viewer as an image viewer with the same sort of efficient tile-loading, zoom/pan awesomeness without having to build it myself.
Specifically, I need an image viewer that will allow the image to grow and change while not altering the coordinates of any older (unchanged) tiles. This means that the center point (0,0), where the image started growing from, must always remain (0,0). So I'm looking for a library that will allow me to use a very basic Cartesian coordinate system (no map projection!), which will ask for tiles infinitely in all directions with no repetition (as opposed to how map libraries just ignore y-axis above and below the map, but the x axis repeats).
There's another catch. I need zoom level 0 to be zoomed in all the way. Since the image is constantly growing, there's no way to tell what the max zoom level will be, and the coordinates need to be based on the base image layer tiles so that every tile in zoom level z contains 2^z base layer tiles.
I am wondering if this is possible with OpenLayers and how to do it. If it's not, any suggestions of other (open-source javascript) libraries that can do this would be very appreciated! I've tried playing around with Polymaps, but the documentation is lacking too much for me to be able to tell if it will work. So far no luck.
Please let me know if none of this made sense, and I'll try to include some images or better explanations. Thanks!
I ended up using Polymaps after all, since I like it more than OpenLayers, because it's faster and has much smoother scrolling and panning. I wasn't able to do exactly what I wanted, but what I did was close enough.
I ended up writing my own layer (based on the po.image() layer), which disabled infinite horizontal looping of the map. I then wrote my own version of po.url() that modified the requests going to the server for tiles so that zooming was reversed (I just arbitrarily picked a 'max' zoom of 20, then when making a request subtract the zoom level from 20) and the x and y coordinates were converted to cartesian coordinates from the standard row, column coordinates Polymaps uses, based on the zoom level and the map centered at (0,0).
If anyone is interested in the code I can post it here. Let me know!
EDIT: I've posted the code on github at https://github.com/camupod/polymaps
The relevant files are src/Backwards* and examples/backwards (though it actually doesn't work, you might be able to clean some information about how it should work).
I'm trying to draw an interactive map in Javascript, using Raphael to do the heavy lifting.
The map background is a fairly complicated thing containing a grid, the map elements, labels, etc. On top of this I'm then going to draw the stuff the user is actually working with. Because the background is complex, I don't want to have to rerender it every frame. So, after drawing it I would like to reuse those drawing elements, merely changing the translation, rotation, scaling of the background as the user pans, zooms, etc.
Unfortunately I'm rather confused by Raphael's transformation primitives: they're not behaving as I would expect. If I call scale(), the scaling appears to apply to the original size of the drawing element; but translate() is cumulative, so it applies to the previous translation. rotate() can be either, as it has an option I can set...
Is it possible to do absolute translation? That is, to be able to specify the absolute coordinates of the new center of my objects (which are usually paths)? Failing that, is keeping track of the old location so I can apply a delta when I want to move it to the new location a reasonable way of doing this?
Or would I be better off simply rerendering the whole thing every frame? (I see suggestions that Raphael isn't good at transformations of complex drawings, as most of it is done in Javascript; looking at the SVG that's being produced, I see that the translation appears to be getting backed into the path data, which would bear this out...)
(BTW, FWIW I'm using the GWT Raphael interface for all this.)
You can use Element.attr to set absolute positions. Just change x and y properties:
myElement.attr("x", myX);
myElement.attr("y", myY);
I've used the raphael-zpd plugin with success. I'm not sure if that will plug into GWT - you could check out their source code and adapt it to your use case.
Project: https://github.com/somnidea/raphael-zpd
Source: https://github.com/somnidea/raphael-zpd/blob/master/raphael-zpd.js