In an attempt to build a responsive scatter graph with d3.js, I'm using %-based coordinates in a 100% x 100% svg element.
How can I .call(axis) and get it to layout the axis using % and not px values, so that they always fit the svg and the plotted data?
Do I need to manually draw the axes in this case? If so how would I get the regular tick values for each axis?
<svg width="100%" height="100%">
<g class='data'>
<circle cx='1%' cy='2%' />
<circle cx='3%' cy='12%' />
<circle cx='10%' cy='24%' />
</g>
<g class='axis'>
<!-- is there a way to generate the axis ticks with x=% y=% ? -->
</g>
</svg>
This isn't possible with D3. The axis component will generate an axis that corresponds to the associated range, i.e. to make it any particular size, you have to modify the output range of the associated scale. You can do this quite easily in a responsive manner though by computing the size of the range in relation to the window size and updating the axis when that changes, for example
xScale.range([0, window.innerWidth]);
Related
I want to build a simple svg donut chart, with labels and polylines connecting the sectors to the label text like this.
I know this is possible using d3.js like implemented here, but I am restricted to using a simple chart without any libraries.
This is the code I have now;
<div class="wrapper">
<svg viewBox="0 0 42 42" preserveAspectRatio="xMidYMid meet" class="donut">
<circle class="donut-hole" cx="21" cy="21" r="15.91549430918954" fill="#fff"></circle>
<circle class="donut-ring" cx="21" cy="21" r="15.91549430918954" fill="transparent" stroke="#d2d3d4" stroke-width="3"></circle>
<circle class="donut-segment" data-per="20" cx="21" cy="21" r="15.91549430918954" fill="transparent" stroke="#49A0CC" stroke-width="3" stroke-dasharray="50 50" stroke-dashoffset="25"></circle>
<circle class="donut-segment" data-per="30" cx="21" cy="21" r="15.91549430918954" fill="transparent" stroke="#254C66" stroke-width="3" stroke-dasharray="50 50" stroke-dashoffset="75"></circle>
<!-- stroke-dashoffset formula:
100 − All preceding segments’ total length + First segment’s offset = Current segment offset
-->
</svg>
</div>
Any tips on how to draw polylines and position them properly without overlap?
EDIT: I want to input dynamic data to the component so that it will draw the chart whenever the data is changed.
implementations in d3: impl1
My main doubt was calculating the points for the arcs that have to be drawn for a donut chart, which I then understood thanks to this amazing answer by enxaneta.
All I had to figure out was adding the third point for the polyline, if the text.xCoordinate was closer to either side of the svg, I moved it either left or right by a preset amount. I also had to split the labels into multiple <tspan>s as <text> elements would not break the long text and long labels would get clipped off by the SVG. It is also possible to append HTML within a <foreignObject> and position it correctly, to overcome the text wrapping issue.
Another option is to use <path> elements for generating arcs, but I am not sure how to calculate the centroid of each of the arcs.
Recommended reading:
Medium article for <circle> donut chart kinda like enxaneta's answer, with the attributes explained.
Codepen with another <circle> donut chart
<path> donut chart as mentioned above, implemented beautifully by Mustapha.
Lets say I have an arbitrary svg with paths:
<svg>
<g>
<path d="...." />
</g>
<g>
<path d="...." />
</g>
</svg>
For each path, I have calculated the exact width and height bbox values (in pixels) that I want them to be scaled down to on render. These values are correct and (when needed) will preserve the aspect ratio of the paths.
With no change to scaling (scale(1)), the default size of the paths are very big (hundreds of pixels), covering a lot of the svg.
I have figured out that once I know the original width and height values of a given path at scale(1), then I can divide width by the original width and height by the original height to get the scale value I am looking for. This method, however, requires rendering the path first at scale(1) to determine the original dimensions.
Is there a way to calculate the transform: scale(x) value that yeilds a path of size width and height before rendering the svg? Or are there other beneficial scaling methods or factors at play that I am missing? I am using jsx/React components for rendering the svg although this is probably inconsequential.
Update
The SVG.js library solves this issue another way by scaling the path strings themselves instead of using transforms. This is not as efficient as vanilla js but still quite fast.
Set the paths to visibility: hidden and then show them only after they are scaled. You can then render them at scale(1), calculate the scale you want and have no visible artifacts affecting your work.
Have you tried manipulating the viewBox attribute on the parent SVG element?
viewBox reference
Excellent in-depth article regarding SVG coordinate system
A simple example:
<svg id="my-svg">
<g style="background:white;">
<path d="M0,0 L0,6 L9,3 z" fill="#003" />
</g>
</svg>
And then the following javascript as a simplistic example:
let svg = document.getElementById("my-svg");
svg.setAttribute("viewBox", "0, 0, 100, 80");
I have an svg viewbox element to draw waveform as follows:
<svg id="waveform" viewBox="0 -1 2000 2" preserveAspectRatio="none">
<g id="waveform">
<path id="waveform1" d="{{some data}}"/>
</g>
</svg>
It plots the waveform as expected. However it doesn't give me the x and y label of the viewbox, is there any alternate way by which we can add the x and y label of viewbox on webpage?
Your svg is rendering a single path, the waveform. If you want labeled axes to be drawn, you need to render them. For example, inspect the source of these SVG graphs with labeled axes: http://www.goat1000.com/svggraph-titles.php. You'll probably want to use a library to do this.
The svg element is not a "smart" graphing widget, it is a plain canvas.
The viewbox attribute on the svg element sets the coordinate system within which the content of the svg is drawn (its internal dimensions, as opposed to the actual pixel dimensions when it is rendered).
I'm using the following code to generate an x axis, tick marks, and tick labels in an SVG element, and I can't really figure out the best way to center align the tick labels with the tick marks, given the width of the tick labels is not fixed.
<svg viewBox='0 0 1000 300'>
<!-- main x-axis line -->
<line x1='0' y1='225' x2='950' y2='225' />
<!-- tick marks and labels -->
<line x1='95' y1='215' x2='95' y2='235' />
<text x='95' y='250'>8PM</text>
<line x1='190' y1='215' x2='190' y2='235' />
<text x='190' y='250'>10PM</text>
...
</svg>
Ultimately, I'm not quite sure how to calculate the width of the text so I can adjust the x coordinate appropriately. Is there any way of doing this with native JavaScript (i.e. no jQuery)? I only ask because I hate to import jQuery just for this...
It seems that what you want is the text-anchor attribute: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor
This is my first SVG project, and I’m not a programmer, but I dabble in interactive infographics. My previous experience in this area comes from working with ActionScript.
I’m using plain SVG (no Raphael, D3, etc.) and trying to create an interactive barchart. After some initial difficulty with the SVG coordinate system and scaling, I found some code online that handles the postscaling translation:
<text x="x_coord0" y="y_coord0" transform="scale(x_scale, y_scale) translate(-x_coord0*(x_scale-1)/x_scale, -y_coord0*(y_scale-1)/y_scale)" …>text</text>
And I converted it into this JavaScript:
var translationfactor = ((0 - y_position)*(y_scalefactor - 1) / y_scalefactor);
var matrix = "scale(1," + y_scalefactor + ") translate(0," + Number(translationfactor) + ")";
targetbar.setAttribute("transform", matrix);
The problem is that I need the bars “translated” back to the chart’s baseline, not the original locations of their topmost points. Currently the correctly scaled bars are hugging the top of the chart:
http://billgregg.net/miscellany/upsidedown-barchart.png
I’ve tried several fixes, including plugging the bars’ ”missing height” into translationfactor (the bars start out the full height of the chart and get scaled down dynamically). Nothing has worked. Part of my problem is that, besides being new to SVGs, I can stare at that code all day and my brain still can’t parse it. Multiplying negative numbers is too abstract and at a fundamental level I just don’t “get” the math, which of course makes modifying the code difficult.
My questions:
(1) What’s the fix for the code above to position the bars back on the baseline of the chart?
(2) Is there a more transparent, more pedestrian way of accomplishing the translation? My first thought along these lines was that if a bar’s height is reduced to 40% of its original value, then multiplying the original Y coordinate value by 250% should reset the bar to its original location (at least its topmost point), but that doesn’t seem to work.
(3) Is there a way to set a bar’s point of origin to its bottom? In Flash it’s possible, though as far as I know it’s a manual, not a programmatic task.
(4) Is there a method similar to .localToGlobal() in ActionScript that would allow me to avoid having to mess with the local coordinate system at all?
Behind the scenes there is matrix math going on and it can be hard to get your head around the pre and post multiplication of arrays.
It's not entirely clear what you are trying to achieve, but reading between the lines, it sounds like you are wanting to provide graph coordinates in their raw(ish) form and have the SVG scale and position them for you(?)
If that's the case, then I think the solution is simpler than what you think.
Assuming I'm right, we'll start with something that looks like this:
<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<g transform="">
<rect x="0" width="1" height="5" fill="red"/>
<rect x="1" width="1" height="11" fill="green"/>
<rect x="2" width="1" height="12" fill="orange"/>
<rect x="3" width="1" height="8" fill="blue"/>
</g>
</svg>
Where x is obvious and the bar length is in height. y defaults to 0, so we don't need it here.
You basically want to know what goes in the transform to scale and position the bars on your page. The fact that your graph is "upside-down" helps a little. Because the origin in an SVG is at the top left.
First apply a scale. Let's make the bars 20 pixels wide, and scale the lengths up by 10.
<g transform="scale(20,10)">
Next you want to position the graph on the page. Let's put the top-left corner at (40,40).
In SVG the transformations are concatenated in order (post-multiplied) so in order for the translation to be what you specify and not be multiplied by the scale, you should put it first.
<g transform="translate(40,40) scale(20,10)">
So the final SVG looks like this:
<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<g transform="translate(40,40) scale(20,10)">
<rect x="0" width="1" height="5" fill="red"/>
<rect x="1" width="1" height="11" fill="green"/>
<rect x="2" width="1" height="12" fill="orange"/>
<rect x="3" width="1" height="8" fill="blue"/>
</g>
</svg>
The above has been simplified by assuming you have already subtracted the values from your base 20%. If you wanted to keep the pure raw values, it's possible, but things get a bit trickier. You would need to either tinker with both the y and height value of each bar, or use clipping to hide the part of the bar above 20%.
For "right way up"/normal graphs. All you need to do is make the y scale negative and translate the graph so that the bottom-left is where you want it.
<g transform="translate(40,140) scale(20,-10)">
Hope this helps.