Dynamically fill area under the polyline- svg - javascript

I am getting the points dynamically, I have to fill the area under the polyline. I have tried the fill property but that would not result in what I have expected, screenshot attached.
<svg
viewBox={`0 0 ${points.length * 10} 100`}
height="100%"
width="150px"
>
<polyline fill="rgba(0,116,217,0.5)" stroke="#0074d9" stroke-width="5" points={points} />
</svg>
the output is something like this
and I want it to be something like this
I have already tried different solutions to that, like by using a path instead of using a polyline, but It is a little bit more complex if we have dynamic co-ordinates (points).

I guess you could manually add points to your {points} list.
First point in your list should be (0,0) (as i understood that should be your bottom left).
And your last point should be (max_x, 0), here you could easily use your last point to get max_x.
Then fill should do what you expected.
EDIT: This made the top of the table colored instead of the bottom one, thanks to #Danny '365CSI' Engelman for pointing out you can simply invert that with (0,max_y) and (max_x,max_y)

As suggested by #enxaneta, you can just prepend a starting and append an end point to your array.
svg vs. cartesian coordinate system
In svg the x/y origin is in the upper left.
So you might need to flip your y coordinates as well to display your values in a cartesian coordinate system.
See also: Understanding SVG Coordinate Systems and Transformations (Part 1).
So your starting point would be:
let pointStart = [0, svgHeight];
Example
let points = [
[0, 10],
[10, 20],
[20, 50],
[30, 20],
[40, 10],
[50, 30]
];
let svgHeight=100;
let svgWidth=(points.length-1)*10;
let strokeWidth= 5;
let pointStart = [0, svgHeight];
let pointEnd = [svgWidth, svgHeight];
// convert to mathematical (cartesian) coordinate system
let pointsFlipped = points.map(val=>{return [val[0], svgHeight-val[1]] });
points = pointsFlipped;
// add points:append end, prepend start
points.push(pointEnd);
points = pointStart.concat(points);
//adjust viewBox:
svg.setAttribute('viewBox', [0, 0, svgWidth, svgHeight].join(' '));
polyline.setAttribute('points', points.join(' '));
svg{
border:1px solid red;
max-height:70vh;
width: auto;
overflow:visible;
}
polyline {
stroke-linejoin: miter;
stroke-miterlimit: 10;
paint-order: stroke;
}
<svg id="svg" viewBox="0 0 50 100" >
<polyline id="polyline" paint-order="stroke"
fill="#5cb3ff" stroke="#0074d9" stroke-width="5" />
</svg>
Besides, you might need paint-order: stroke to avoid borders around the graph.
This property will render the stroke behind the fill area. This will require to use opaque fill colors.
In the example above, I've set overflow:visible to illustrate the effect.

Related

How to convert svg element coordinates to screen coordinates?

Is there a way to get the screen/window coordinates from a svg element ?
I have seen solutions for the other way around like:
function transformPoint(screenX, screenY) {
var p = this.node.createSVGPoint()
p.x = screenX
p.y = screenY
return p.matrixTransform(this.node.getScreenCTM().inverse())
}
But what i need in my case are the screen coordinates.
Sory if it's an obvious question, but i'm new to svg.
Thanks.
The code you included in your question converts screen coordinates to SVG coordinates. To go the other way, you have to do the opposite of what that function does.
getScreenCTM() returns the matrix that you need to convert the coordinates. Notice that the code calls inverse()? That is inverting the matrix so it does the conversion in the other direction.
So all you need to do is remove the inverse() call from that code.
var svg = document.getElementById("mysvg");
function screenToSVG(screenX, screenY) {
var p = svg.createSVGPoint()
p.x = screenX
p.y = screenY
return p.matrixTransform(svg.getScreenCTM().inverse());
}
function SVGToScreen(svgX, svgY) {
var p = svg.createSVGPoint()
p.x = svgX
p.y = svgY
return p.matrixTransform(svg.getScreenCTM());
}
var pt = screenToSVG(20, 30);
console.log("screenToSVG: ", pt);
var pt = SVGToScreen(pt.x, pt.y);
console.log("SVGToScreen: ", pt);
<svg id="mysvg" viewBox="42 100 36 40" width="100%">
</svg>
I was playing around with this snippet below when I wanted to do the same (learn which screen coordinates correspond to the SVG coordinates). I think in short this is what you need:
Learn current transformation matrix of the SVG element (which coordinates you are interested in), roughly: matrix = element.getCTM();
Then get screen position by doing, roughly: position = point.matrixTransform(matrix), where "point" is a SVGPoint.
See the snippet below. I was playing with this by changing browser window size and was altering svg coordinates to match those of the div element
// main SVG:
var rootSVG = document.getElementById("rootSVG");
// SVG element (group with rectangle inside):
var rect = document.getElementById("rect");
// SVGPoint that we create to use transformation methods:
var point = rootSVG.createSVGPoint();
// declare vars we will use below:
var matrix, position;
// this method is called by rootSVG after load:
function init() {
// first we learn current transform matrix (CTM) of the element' whose screen (not SVG) coordinates we want to learn:
matrix = rect.getCTM();
// then we "load" SVG coordinates in question into SVGPoint here:
point.x = 100; // replace this with the x co-ordinate of the path segment
point.y = 300; // replace this with the y co-ordinate of the path segment
// now position var will contain screen coordinates:
position = point.matrixTransform(matrix);
console.log(position)
// to validate that the coordinates are correct - take these x,y screen coordinates and apply to CSS #htmlRect to change left, top pixel position. You will see that the HTML div element will get placed into the top left corner of the current svg element position.
}
html, body {
margin: 0;
padding: 0;
border: 0;
overflow:hidden;
background-color: #fff;
}
svg {
position: fixed;
top:0%;
left:0%;
width:100%;
height:100%;
background:#fff;
}
#htmlRect {
width: 10px;
height: 10px;
background: green;
position: fixed;
left: 44px;
top: 132px;
}
<body>
<svg id="rootSVG" width="100%" height="100%" viewbox="0 0 480 800" preserveAspectRatio="xMinYMin meet" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" onload="init()">
<g id="rect">
<rect id="rectangle" x="100" y="300" width="400" height="150"/>
</g>
</svg>
<div id="htmlRect"></div>
</body>
Not sure why it hasn't been suggested before, but `Element.getBoundingClientRect() should be enough:
const {
top, // x position on viewport (window)
left, // y position on viewport (window)
} = document.querySelector('rect').getBoundingClientRect()
I think other answers might be derived from a method promoted by Craig Buckler on SitePoint, where he explains using the SVGElement API (instead of getBoudingClientRect, from the - DOM - Element API) to convert DOM to SVG coordinates and vice-versa.
But 1. only DOM coordinates are required here 2. he claims that using getBoundingClientRect when transformations (via CSS or SVG) are applied will return incorrect values to translate to SVG coordinates, but the current specification for getBoundingClientRect takes those transformations into account.
The getClientRects() method, when invoked, must return the result of the following algorithm: [...]
If the element has an associated SVG layout box return a DOMRectList object containing a single DOMRect object that describes the bounding box of the element as defined by the SVG specification, applying the transforms that apply to the element and its ancestors.
Specification: https://drafts.csswg.org/cssom-view/#extension-to-the-element-interface
Support: https://caniuse.com/#feat=getboundingclientrect
2020
⚠️ Safari currently has several bugs that make this pretty difficult if you're working with SVGs (or SVG containers) that are transitioning, rotated, or scaled.
getScreenCTM() does not include ancestor scale and rotation transforms in the returned matrix. (If your svgs are neither rotated or scaled, then this is the way to go though.)
However, if you know the ancestor elements that are being scaled and/or rotated, and their transformation values, you can manually fix the matrix provided by getScreenCTM(). The workaround will look something like this:
let ctm = ele.getScreenCTM();
// -- adjust
let ancestorScale = // track yourself or derive from window.getComputedStyle()
let ancestorRotation = // track yourself or derive from window.getComputedStyle()
ctm = ctm.scale(ancestorScale)
ctm = ctm.rotate(ancestorRotation)
// !! Note: avoid ctm.scaleSelf etc. On some systems the matrix is a true blue SVGMatrix (opposed to a DOMMatrix) and may not support these transform-in-place methods
// --
// repeat 'adjust' for each ancestor, in order from closest to furthest from ele. Mind the order of the scale/rotation transformations on each ancestor.
If you don't know the ancestors... the best I've come up with is a trek up the tree looking for transformations via getComputedStyle, which could be incredibly slow depending on the depth of the tree...
getBoundingClientRect() may return incorrect values when transitioning. If you're not animating things but you are transforming things, then this may be the way to go, though I'm pretty sure it's notably less performant than getScreenCTM. Ideally, insert a very small element into the SVG such that its bounding rect will effectively be a point.
window.getComputedStyles().transform has the same issue as above.
Playing with innerWidth, screenX, clientX etc...
I'm not sure about what you are searching for, but as you question is arround screenX, screenY and SVG, I would let you play with snippet editor and some little tries.
Note that SVG bounding box is fixed to [0, 0, 500, 200] and show with width="100%" height="100%".
The last line of tspan with print x and y of pointer when circle is clicked.
function test(e) {
var sctm=new DOMMatrix();
var invs=new DOMMatrix();
sctm=e.target.getScreenCTM();
invs=sctm.inverse();
document.getElementById("txt1").innerHTML=
sctm.a+", "+sctm.b+", "+sctm.c+", "+sctm.d+", "+sctm.e+", "+sctm.f;
document.getElementById("txt2").innerHTML=
invs.a+", "+invs.b+", "+invs.c+", "+invs.d+", "+invs.e+", "+invs.f;
document.getElementById("txt3").innerHTML=
e.screenX+", "+e.screenY+", "+e.clientX+", "+e.clientY;
var vbox=document.getElementById("svg").getAttribute('viewBox').split(" ");
var sx=1.0*innerWidth/(1.0*vbox[2]-1.0*vbox[0]);
var sy=1.0*innerHeight/(1.0*vbox[3]-1.0*vbox[0]);
var scale;
if (sy>sx) scale=sx;else scale= sy;
document.getElementById("txt4").innerHTML=
e.clientX/scale+", "+e.clientY/scale;
}
<svg id="svg" viewBox="0 0 500 200" width="100%" height="100%" >
<circle cx="25" cy="25" r="15" onclick="javascript:test(evt);" />
<text>
<tspan x="10" y="60" id="txt1">test</tspan>
<tspan x="10" y="90" id="txt2">test</tspan>
<tspan x="10" y="120" id="txt3">test</tspan>
<tspan x="10" y="150" id="txt4">test</tspan>
</text>
</svg>

How to zoom in on a polygon in D3.js

I am trying to figure out how to do the same zooming behavior as shown in the example below, but with a normal polygon instead of the geo paths.
https://bl.ocks.org/mbostock/4699541
I have seen some answers here on SO that kind of address this, but the animation is choppy or jumps around strangely.
The html I have is
<div id="map-container">
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg"
id="canvas"
viewBox="0 0 4328 2880">
<defs>
<pattern id="mapPattern"
patternUnits="userSpaceOnUse"
x="0"
y="0"
width="4328"
height="2880">
<image xlink:href="/development/data/masterplan.png"
x="0"
y="0"
width="4328"
height="2880"></image>
</pattern>
</defs>
<g id="masterGroup">
<rect fill="url(#mapPattern)"
x="0"
y="0"
width="4328"
height="2880" />
</g>
</svg>
I would like to be able to add some polygons in the same group as the map rectangle and then zoom on the polygon's boundary. Can anyone please show me a fiddle of such behaviour?
I should also add that I do not want to use the scroll wheel or panning. Just zooming in on a clicked polygon and then zooming out on another click.
Maybe this will help you. I answered a question here earlier today : D3js outer limits
Here is the fiddle I put together : http://jsfiddle.net/thatOneGuy/JnNwu/921/
I have added a transition : svg.transition().duration(1000).attr('transform',function(d){
Notice if you click one of the nodes the area moves to cater for the size of the new layout.
The basics are explained in the link to the question, but basically I got the bounding box and translated the SVG accordingly. So I translated and scaled to the size of the new rectangle.
Take a look, quite easy to understand. Here is the main part of the transition :
svg.transition().duration(1000).attr('transform',function(d){
var testScale = Math.max(rectAttr[0].width,rectAttr[0].height)
var widthScale = width/testScale
var heightScale = height/testScale
var scale = Math.max(widthScale,heightScale);
var transX = -(parseInt(d3.select('#invisRect').attr("x")) + parseInt(d3.select('#invisRect').attr("width"))/2) *scale + width/2;
var transY = -(parseInt(d3.select('#invisRect').attr("y")) + parseInt(d3.select('#invisRect').attr("height"))/2) *scale + height/2;
return 'translate(' + transX + ','+ transY + ')scale('+scale+')' ;
})
So with your code, your rectAttr values as seen in the snippet above would be the values retrieved from the getBoundingClientRect() of your polygon : x, y, width and height.
Where I have used d3.select('#invisRect'), this should be your boundingBoxRect() also. And the rest should just work.
EDIT
Here are the edits I made with the fiddle provided : http://jsfiddle.net/thatOneGuy/nzt39dym/3/
I used this function to get the bounding box of the polygon and set the rectangles values accordingly :
var bbox = d3.select(polygon).node().getBBox();
var rectAttr = {
x: bbox.x,
y: bbox.y,
width: bbox.width,
height: bbox.height,
};

Animating growing/shrinking pie using SVG and Javascript

I want to create a growing pie animation using Javascript and SVG embedded in HTML. Input should be percentage and output should be an image. It should animate like this:
This should work like GUI mouse hold action feedback (user needs long press something). This is also why I can't use GIF animation as the timeout may vary.
I tried to make this in Inkscape and then reverse-engineer the XML but I don't understand it at all. There's a <path> node which has property d full of gibberish numbers:
d="m 250.78761,446.46564 a 28.183382,28.183382 0 0 1 -24.596,27.95413 28.183382,28.183382 0 0 1 -30.85751,-20.83773"
I assume these are some points of path. But can't I just make circle and mention percentage of how it's full? How are these points even generated?
This is what I played with:
body, html {
padding: 0px;
margin: 0px;
border: 1px solid grey;
}
svg {
/** to fit svg in the viewbox**/
max-height: 400px;
border: 1px solid black;
}
<svg class="test" viewBox="-20 -20 1000 1000">
<path
id="circle4146"
style="stroke:#61a4ff;stroke-width:15.00000095;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;"
sodipodi:type="arc"
sodipodi:cx="140.71873"
sodipodi:cy="446.46564"
sodipodi:rx="28.183382"
sodipodi:ry="28.183382"
sodipodi:start="0"
sodipodi:end="1.1720792"
sodipodi:open="true"
d="m 168.90212,446.46564 a 28.183382,28.183382 0 0 1 -17.24157,25.97267" />
</svg>
The sodipodi stuff is probably used by inkscape, changing it has no effect. I know that the d attribute describes complex path. What I really need is for someone to highlight me which points should be moved (using sin and cos I assume) to achieve desired effect.
Also I was unable to adjust the viewport to the circle. Apparently some of the coordinates are not X and Y.
Something like this? Here I am just calculating what the length of the circumference that the percentage represents. Then I give the circle a stroke dash pattern with that length and a big gap.
setCircleTo(70);
function setCircleTo(percent)
{
// Get the circle element via the DOM
var circle = document.getElementById('mycircle');
// Calculate the circle circumference via the circles 'r' attribute
var circumference = Math.PI * 2 * circle.r.baseVal.value;
// Calculate what <percent> of the circumference is
var adjustedLen = percent * circumference / 100;
// Set the circle's dashpattern one with a dash that length
circle.setAttribute('stroke-dasharray', adjustedLen+' '+circumference);
}
<svg class="test" viewBox="-20 -20 1000 1000">
<circle id="mycircle" cx="100" cy="75" r="50" stroke-width="30" fill="none" stroke="#61a4ff"/>
</svg>

Regular polygon with different height and width

I want to create a polygon with Kinetic.js and I know the width, height, rotation and number of points for the polygon.
I thought this would be possible by using the RegularPolygon object, but for it I have to set a value for radius. A triangle would be created like this:
var hexagon = new Kinetic.RegularPolygon({
x: stage.width()/2,
y: stage.height()/2,
sides: 3,
radius: 70,
fill: 'red',
});
See a similar polygon being created here:
http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-regular-polygon-tutorial/
The result would look something like this:
But what if I want to create a triangle of which the width should be twice the height? Looking something like this:
As far as I understand, this not possible by just adjusting the radius.
How can I achieve this for any polygon? Note that I don't know the values for the points to begin with (they could be calculated though). I think that scaleX and scaleY might be possible to use, but is it possible to achieve it in an easier way? I would like just to set width and height directly.
KineticJS polygons are regular polygons (all sides are equal length).
Scaling a regular-polygon is awkward if you want to stroke the polygon because the stroke is also scaled and therefore the stroke deforms.
So your best solution might be to just draw a poly-line forming your triangles.
Here's example code and a Demo:
var stage = new Kinetic.Stage({
container: 'container',
width: 350,
height: 350
});
var layer = new Kinetic.Layer();
stage.add(layer);
var PI=Math.PI;
var PI2=PI*2;
scaledRegularPolygon(100,100,30,5,2,1,'red');
function scaledRegularPolygon(cx,cy,radius,sides,scaleX,scaleY,fill){
var points=[];
for(var i=0;i<sides;i++){
var sweep=PI2/sides;
var midbottom=PI/2;
var rightbottom=midbottom-sweep/2;
var start=rightbottom-sweep;
var angle=start+sweep*i;
var x=cx+radius*Math.cos(angle);
var y=cy+radius*Math.sin(angle);
x=cx+(x-cx)*scaleX;
y=cy+(y-cy)*scaleY;
points.push(x,y);
}
var poly=new Kinetic.Line({
points:points,
closed:true,
fill:fill,
stroke: 'black',
strokeWidth:4
});
layer.add(poly);
layer.draw();
}
body{padding:20px;}
#container{
border:solid 1px #ccc;
margin-top: 10px;
width:350px;
height:350px;
}
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v5.1.0.min.js"></script>
<div id="container"></div>
Kinetic.Transform allows us to calculate the position of a point from a transform matrix by passing in the point to Kinetic.Transform.point(). From the object you want to transform the points for, get its transform matrix with Kinetic.Node.getAbsoluteTransform().copy() (or Kinetic.Node.getTransform().copy(), whatever seems suitable for your purposes). Then we call Kinetic.Transform.scale(2,2) on the transform matrix to get a matrix with twice the scale. Then for each point, use Kinetic.Transform.point() to get its new position.

How to know absolute position of a SVG PATH?

I have a SVG generated with InkScape, all PATHs has moveto relative position (m):
<path xmlns="http://www.w3.org/2000/svg"
style="fill: #07fe01;
fill-opacity: 1;
display: inline;
stroke-width: 4px;
cursor: auto;
stroke: none;
opacity: 0.4;
filter: none; "
d="m 431.36764,202.65372 -20.46139,7.94003 -2.875,8.84375 -3.0625,13.21875 8.8125,0.96875 13.34375,6.84375 9.94451,-6.04527 11.96344,-1.95225 -2.3183,-6.56201 0.1291,-10.53422 z"
id="Rossello"
inkscape:connector-curvature="0" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
sodipodi:nodetypes="ccccccccccc" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
transform="translate(-0.03124997,-689.42566)"
onclick="mapmed.click(this)"
onmouseover="mapmed.over(this)"
onmouseout="mapmed.out(this)">
<title id="title3042">Rosselló</title>
</path>
I need to know the absolute position of each path from javascript, but I can't.
Looks to me like normalizedPathSegList is what you want.
Try something like this:
ele=$('#Rosello')[0]
var bbox = ele.getBBox()
var top = bbox.y + bbox.y2
The same should work in x-direction.
I am not sure, but I observe that getBBox is pretty slow. So take care.
I found a way to know the absolute position. The mistake was try to find the absolute coords without apply the base layer translate and count pixes like in InkScape, from down to up, and not up to down as in SVG.
Follow this steeps to know the absolute position of the first node of a PATH in pixels:
1st get the width and height of the svg object. svgWidth, svgHeight.
2nd get the transform translate of the base layout. baseTransX, baseTransY.
3rd get the first node of the path. pathX, pathY.
4th get the transform translate of the path if exists, if not 0. pathTransX, pathTransY.
With this values, we can found absolute X and Y for the first node in InkScape:
X = baseTransX + pathTransX + pathX
Y = svgHeight - (baseTransY + pathTransY + pathY)

Categories

Resources