I want to set up the year interval to every 3 years and start from 2012 in this example: https://www.tutorialsteacher.com/d3js/create-bar-chart-using-d3js. How should I modify the code to make this happen? My desired output is as in the picture above.
Hide ticks with tickFormat:
d3.axisBottom(xScale)
.tickFormat((d, i) => i % 3 === 0 ? 1900 + d.getYear() : null)
const from = new Date('2010-01-01')
const to = new Date('2022-01-01')
const xScale = d3.scaleTime()
.domain([from, to])
.range([0, 400]);
const xAxis = d3.axisBottom(xScale)
.tickFormat((d, i) => i % 3 === 0 ? 1900 + d.getYear() : null)
d3.select('svg').append("g")
.attr("transform", `translate(50,50)`)
.call(xAxis);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="500" height="100" />
Related
I have some SVG elements and I would like to add the ability to resize using mouse.
It works great when element doesn't have any transforms (matrix, rotate, scale etc).
If I remove transform (transform="matrix(1.1,1.1,1,0,0,0)") from the rect element - it works great.
My understanding is that I should use matrix.inverse() function and transform it so I get mouse coordinates in the element space, but everything I tried didn't work for me.
I need to correctly calculate the x, y difference between start drag point and current point, but it doesn't work.
var rect = document.getElementById('rect');
var grip = document.getElementById('grip');
function moveGrip() {
var baseMatrix = rect.getCTM(),
x = rect.width.baseVal.value + rect.x.baseVal.value,
y = rect.height.baseVal.value + rect.y.baseVal.value;
grip.cx.baseVal.value = x * baseMatrix.a + y * baseMatrix.c + baseMatrix.e;
grip.cy.baseVal.value = x * baseMatrix.b + y * baseMatrix.d + baseMatrix.f;
}
grip.addEventListener('mousedown', onGripMouseDown);
document.addEventListener('mousemove', onDocMouseMove);
var startDragPoint, startDragSize;
function onGripMouseDown(evt) {
startDragSize = {w: rect.width.baseVal.value, h: rect.height.baseVal.value};
startDragPoint = {x: evt.clientX, y: evt.clientY};
}
function onDocMouseMove(evt) {
if (evt.buttons & 1) {
// dragging
rect.width.baseVal.value = startDragSize.w + evt.clientX - startDragPoint.x;
rect.height.baseVal.value = startDragSize.h + evt.clientY - startDragPoint.y;
moveGrip();
}
}
moveGrip();
<svg width="500" height="400">
<rect id="rect" x="20" y="20" width="200" height="100"
transform="matrix(1.1,1.1,1,0,0,0)"
style="fill:none;stroke: #3a2dd0; stroke-width: 2px;"></rect>
<g>
<circle id="grip" r="3" stroke-width="1" stroke="green"></circle>
</g>
</svg>
Probably, you'd need to apply the inverse matrix to delta-x,y as follows;
function onDocMouseMove(evt) {
if (evt.buttons & 1) {
// dragging
var invMatrix = rect.getCTM().inverse(),
x = evt.clientX - startDragPoint.x,
y = evt.clientY - startDragPoint.y;
rect.width.baseVal.value = startDragSize.w + x * invMatrix.a + y * invMatrix.c;
rect.height.baseVal.value = startDragSize.h + x * invMatrix.b + y * invMatrix.d;
moveGrip();
}
}
I have an svg picture like this, with M and m in the path. I use raphael.js to calculate. I can get all the points by calling Raphael.getTotalLength(path), but how can I know from which point is the path behind m.
I want to know all the absolute coordinate points of the inner border of the svg, so that I can get a data format similar to the following [[points of the outer border], [points of the inner border]].
This is how I achieved it. I can get all the points, but I can’t distinguish which are the points of the inner frame and which are the points of the outer frame.
import Raphael from 'raphael';
function getPoints(path) {
let points = [];
for (let i = 0; i < Raphael.getTotalLength(path); i += step_point) {
const point = Raphael.getPointAtLength(path, i);
points.push(point);
}
}
This is the svg content:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120">
<path
d="M72.8525 8.0439c-.405 0-.816.022-1.222.064-4.973.523-8.939 4.112-10.048 8.833-.73-.028-1.453-.043-2.162-.043-5.453 0-10.307.814-14.464 2.423-1.988-3.703-5.849-6.071-10.173-6.071-.973 0-1.949.124-2.899.37-2.994.773-5.508 2.667-7.078 5.331-1.571 2.664-2.01 5.781-1.236 8.775.772 2.986 2.693 5.506 5.301 7.052-1.056 2.419-1.535 4.533-1.814 6.02-.623 3.314-2.519 13.398 5.355 20.728 3.209 2.988 6.672 4.84 10.937 5.8-3.558 4.888-7.226 11.138-8.02 16.945-.349 2.543-.027 4.749.956 6.576l-.149.355c-.034.081-.062.165-.085.25-.315 1.166-.025 2.398.778 3.295.672.754 1.639 1.187 2.649 1.187.044 0 .088-.001.131-.002.27.406.626.758 1.053 1.029.518.33 1.109.519 1.719.55l-1.044 4.167c-.571 2.283.813 4.636 3.086 5.245l10.106 2.708c.372.1.753.15 1.132.15 1.299 0 2.521-.582 3.351-1.595.825-1.008 1.151-2.32.894-3.6-.013-.064-.007-.13.016-.189l1.1-2.829 3.729 6.22c.78 1.3 2.206 2.109 3.723 2.109.759 0 1.509-.202 2.168-.582l9.061-5.232c1.308-.756 2.119-2.108 2.167-3.619.03-.959-.249-1.873-.779-2.627.6-.052 1.175-.255 1.679-.595.42-.283.766-.644 1.024-1.058h.035c1.047 0 2.037-.459 2.713-1.259.778-.92 1.035-2.16.687-3.318-.025-.084-.056-.166-.092-.246l-.158-.35c.933-1.854 1.194-4.068.777-6.6-1.181-7.17-6.763-14.845-10.84-19.646 1.556-.529 3.061-1.122 4.547-1.793 6.708-3.027 9.062-8.913 9.395-11.913.346-3.113-.969-9.08-2.01-12.015-1.056-2.977-3.244-8.332-6.599-12.674 1.647-2.29 2.406-5.105 2.106-7.957-.621-5.911-5.566-10.369-11.503-10.369m0 2c4.84 0 8.997 3.657 9.514 8.578.312 2.97-.769 5.764-2.716 7.735 4.039 4.59 6.48 11.006 7.313 13.355 1.049 2.957 2.192 8.566 1.908 11.126-.285 2.56-2.435 7.696-8.23 10.311-2.229 1.006-4.658 1.897-7.363 2.639.216.171.429.348.617.556 3.231 3.599 10.589 12.513 11.888 20.392.453 2.753-.065 4.727-1.052 6.153l.583 1.294c.149.498.04 1.049-.299 1.451-.296.349-.728.55-1.186.55-.367 0-.722-.13-1.002-.367-.127-.107-.234-.231-.32-.372l-.182-.406c-.053.03-.108.056-.161.085l.17 1.142c.01.521-.245 1.023-.682 1.318-.258.174-.558.266-.867.266-.518 0-.999-.256-1.288-.685-.093-.138-.163-.287-.208-.447l-.078-.525s0 .001-.001.001c-.092.021-.184.022-.277.035-.125.259-.314.488-.566.645-.247.154-.531.235-.82.235-.22 0-.427-.055-.621-.14l1.058 2.404c.18.409.472.758.845 1.006 1.444.961 1.377 3.104-.126 3.973l-9.061 5.23c-.368.213-.77.315-1.168.315-.795 0-1.57-.407-2.008-1.137l-4.403-7.347c-.752-.015-1.524-.056-2.306-.11l-1.698 4.368c-.163.417-.202.87-.114 1.309.3 1.49-.864 2.801-2.284 2.801-.201 0-.407-.026-.614-.082l-10.106-2.708c-1.234-.33-1.974-1.589-1.664-2.827l1.907-7.612c-.062-.027-.127-.044-.185-.077-.256-.151-.451-.374-.584-.631-.092-.01-.185-.009-.278-.027 0 0-.001 0-.001-.001l-.065.532c-.04.159-.105.309-.193.448-.287.45-.777.72-1.312.72-.294 0-.582-.084-.832-.243-.444-.283-.713-.777-.717-1.298l.139-1.147c-.054-.027-.11-.052-.163-.081l-.174.415c-.082.142-.184.268-.307.377-.285.254-.652.395-1.034.395-.441 0-.864-.19-1.158-.519-.35-.392-.474-.939-.339-1.441l.548-1.311c-1.026-1.397-1.598-3.356-1.219-6.121.917-6.699 6.151-14.247 9.637-18.644-4.885-.547-9.142-2.083-13.173-5.836-6.746-6.28-5.521-14.805-4.752-18.894.384-2.041 1.039-4.558 2.526-7.33-2.881-1.035-5.218-3.424-6.041-6.612-1.324-5.122 1.756-10.347 6.877-11.67.802-.207 1.607-.306 2.399-.306 4.097 0 7.844 2.654 9.119 6.698 5.236-2.473 11.057-3.05 15.518-3.05 1.264 0 2.419.047 3.42.109 0 0 .169.006.449.024.281-4.587 3.828-8.437 8.55-8.934.34-.035.678-.053 1.013-.053"
fill="#F00" stroke="#000" />
</svg>
I really look forward to your answers, thank you!
As I mentioned elsewhere. You can split on M. If for some reason this doesn't make sense (as you mentioned multiple 'm's) as you say "behind m", then you need to amend your question to be more precise about where the split would be, if this is part of a more generic issue.
So we can split the string on "m"
var match = new RegExp("(^[^m]*)(.*)", "").exec(path);
And get the last point of the first path part...
var point = Raphael.getPointAtLength(match[1], p.getTotalLength(match[1]));
Then add the final point to the first path...
var p2 = r.path("M" + point.x + "," + point.y + match[2]).attr('stroke','blue')
And I've amended getPoints()...
function getPoints(path) {
let points = [];
let step_point = 10;
for (let i = 0; i < path.getTotalLength(); i += step_point) {
const point = path.getPointAtLength(i);
points.push(point);
}
return points;
}
And get the sets of points...
console.log(getPoints(p1));
console.log(getPoints(p2));
jsfiddle showing different colours for outer/inner and it dumps in the console the two sets of points.
My current task is attempting to combine objects with similar matrices under the same transformation matrix. The two matrices will always have the first 4 digits of it's transform be equal. I am having difficulty calculating the x="???" and y="???" for the second tspan. Any help towards the proper equation would be greatly appreciated.
Input
<svg>
<text transform="matrix(0 1 1 0 100 100)"><tspan x=0 y=0>foo</tspan></text>
<text transform="matrix(0 1 1 0 110 110)"><tspan x=0 y=0>bar</tspan></text>
</svg>
Output
<svg>
<text transform="matrix(0 1 1 0 100 100)">
<tspan x="0" y="0">foo</tspan>
<tspan x="???" y="???">bar</tspan>
</text>
</svg>
EDIT 1
I guess my question is more along the lines of given a point (x,y), how do I apply an existing matrix transformation to that point so that the position will not move, but the element will now be nested inside of another element.
EDIT 2
I have got this code to work for matrices with 0s in the (a,d) or (b,c) positions. Slanted/Skewed matrices I still have not got working. Any thoughts on this?
var aX = floatX[0];
var bX = floatX[1];
var cX = floatX[2];
var dX = floatX[3];
var eX = floatX[4];
var fX = floatX[5];
var aY = floatY[0];
var bY = floatY[1];
var cY = floatY[2];
var dY = floatY[3];
var eY = floatY[4];
var fY = floatY[5];
var xX = (eX * aX) + (fX * bX);
var xY = (eX * cX) + (fX * dX);
var yX = (eY * aY) + (fY * bY);
var yY = (eY * cY) + (fY * dY);
var c1 = cX - aX;
var c2 = dX + bX;
return new float[] { (yX - xX) / (c1 * c2), (yY - xY) / (c1 * c2) };
One thought that may work if my logic isn't flawed, is to find the transform for one element to the other, which can then be used to transform a point of 0,0 (as that's the original x,y) to a new location.
Once we know what the difference in transforms is (assuming that the first 4 figures in the matrix are the same as mentioned in the question, it won't work otherwise), we can figure what the difference in x,y is.
First, there's a bit of code as some browsers have removed this feature..
SVGElement.prototype.getTransformToElement = SVGElement.prototype.getTransformToElement || function(elem) {
return elem.getScreenCTM().inverse().multiply(this.getScreenCTM());
};
This is an svg method that some browsers support, but including as a polyfill in case yours doesn't (like Chrome). It finds the transform from one element to another.
We can then use this, to find the transform from the first to the second text element.
var text1 = document.querySelector('#myText1')
var text2 = document.querySelector('#myText2')
var transform = text2.getTransformToElement( text1 )
Or if you don't want the polyfill, this 'may' work (matrices aren't a strong point of mine!). getCTM() gets the current transformation matrix of an element.
var transform = text1.getCTM().inverse().multiply( text2.getCTM() )
Now we know what the transform between them was. We also know the original x,y was 0,0. So we can create an svg point 0,0 and then transform it with that matrix we've just figured, to find the new x,y.
var pt = document.querySelector('svg').createSVGPoint();
pt.x = 0; pt.y = 0;
var npt = pt.matrixTransform( transform );
Then just a delayed example to show it being moved. Set the tspan with the new x,y we've just figured from the previous transform.
setTimeout( function() {
alert('new x,y is ' + npt.x + ',' + npt.y)
tspan2.setAttribute('x', npt.x);
tspan2.setAttribute('y', npt.y);
},2000);
jsfiddle with polyfill
jsfiddle without polyfill
There is a path element of "M 1050000 80 A 40 40 0 1 0 1050000 40", getBBox() result width and height are: 0.0000711679458618164, 0.00007629334868397564
If move the path 10000 pixels towards left, become "M 1040000 80 A 40 40 0 1 0 1040000 40", then getBBox() result width and height are: 74.625, 79.99937438964844
The element's width and height are not changed but getBBox() give different results.
What's the reason and how to avoid this issue ? getBoundingClientRect() ?
Testing snippets:
function $$(id){return document.getElementById(id);}
var b1 = $$("p1").getBBox();
$$("r1").innerHTML=('p1: width:'+b1.width +', height:'+ b1.height);
//0.0000711679458618164, 0.00007629334868397564
var b2 = $$("p2").getBBox();
$$("r2").innerHTML=('p2: width:'+b2.width +', height:'+ b2.height);
//74.625, 79.99937438964844
<svg width="110" height="110" viewBox="1050000 0 110 110">
<path id="p1" d="M 1050000 80 A 40 40 0 1 0 1050000 40" stroke="#880000" stroke-width="1" fill="none"></path>
</svg>
<svg width="110" height="110" viewBox="1040000 0 110 110">
<path id="p2" d="M 1040000 80 A 40 40 0 1 0 1040000 40" stroke="#880000" stroke-width="1" fill="none"></path>
</svg>
<p id="r1"></p>
<p id="r2"></p>
Has confirmed this is a bug of chromium: issue 678162. It's originally caused by the code bug of Skia engine. The fixing is committed.
Only some versions of Chrome has this bug. As my testing, Chrome 52 is ok, Chrome 55 has bug.
On my case, I just need to multiple the wrong bbox by 0x100000.
0.0000711679458618164 * 0x100000 == 74.625
0.00007629334868397564 * 0x100000 == 79.99937438964844
Finally I use a custom function to calculate bbox for chrome.
This function cannot handle the nested svg(s) or nested transforms.
function bbox (element) {
var svg = element.farthestViewportElement,
mtr = element.getScreenCTM().inverse(),
bcr = element.getBoundingClientRect(),
x1 = bcr.left, x2 = bcr.right,
y1 = bcr.top, y2 = bcr.bottom,
pts = [[x1,y1],[x1,y2],[x2,y1],[x2,y2]],
pt = svg.createSVGPoint(), pt2,
bb = {}, u;
if(x1===0 && x2===0 && y1===0 && y2===0){
return {x:0,y:0,width:0,height:0};
}
for(var i=0; i < 4; i++){
pt.x = pts[i][0];
pt.y = pts[i][1];
pt2 = pt.matrixTransform(mtr);
if(bb.xmin === u){
bb.xmin = bb.xmax = pt2.x;
bb.ymin = bb.ymax = pt2.y;
}else{
if(bb.xmin > pt2.x){
bb.xmin = pt2.x;
}else if(bb.xmax < pt2.x){
bb.xmax = pt2.x;
}
if(bb.ymin > pt2.y){
bb.ymin = pt2.y;
}else if(bb.ymax < pt2.y){
bb.ymax = pt2.y;
}
}
}
return {
x: bb.xmin,
y: bb.ymin,
width: bb.xmax - bb.xmin,
height: bb.ymax - bb.ymin
};
}
i want to scale the below element in fixed position.
<path id="container_svg_rectsymbol1" fill="red" stroke-width="1" stroke="Gray" d="M 73.1111111111111 -71.75 L 83.1111111111111 -71.75 L 83.1111111111111 -61.75 L 73.1111111111111 -61.75 L 73.1111111111111 -71.75" transform="scale(1)"/>
when am start scaling it moves from one location to another location. i don't want to move the object i just want to enlarge the size of object.
i have referred following link.
http://commons.oreilly.com/wiki/index.php/SVG_Essentials/Transforming_the_Coordinate_System
how to do fixed scaling ?
i want to animate the element i.e enlarge the size in fixed position. i have implemented in following way. but it sill moves the element from origin. please refer below code.
var box = element.getBBox();
var scaleVal=null, x=null, y=null;
$(element).animate(
{
scale: 1,
centerX:box.x+(4*transX),
centerY:box.y+(4*transY)
},
{
duration: 4000,
step: function(now,fx) {
if (fx.prop == "scale") {
scaleVal = now;
} else if (fx.prop == "centerX") {
x = now;
} else if (fx.prop == "centerY") {
y = now;
}
if(!sf.util.isNullOrUndefined(scaleVal) && !sf.util.isNullOrUndefined(x) && !sf.util.isNullOrUndefined(y)) {
$(element).attr("transform", "translate("+(-x*(scaleVal-1))+","+(-y*(scaleVal-1))+")scale(" + scaleVal + ")");
}
}
)
referred the below link for scaling in centered point.
http://commons.oreilly.com/wiki/index.php/SVG_Essentials/Transforming_the_Coordinate_System
but it still starts from origin and enlarges the element.
Thanks,
Siva
Scaling is centred on the origin (0, 0), so if your shape is not centred on (0, 0) it will appear to move. To fix this first translate your shape so it is centred on the origin, then scale it, then translate it back:
transform="translate(78.11 -66.75) scale(2) translate(-78.11 66.75)"
Note that the transformations are done in reverse order.
You could simplify things by creating a shape centred on the origin to start with and then scaling and transforming it.
<path id="container_svg_rectsymbol1" fill="red" stroke="Gray" d="M -5 -5 v10 h10 v-10 h-10" transform="translate(78.11 -66.75) scale(3)"/>
You could also convert the transform into matrix, which would be more efficient:
<path opacity="0.5" fill="red" stroke-width="1" stroke="Gray" d="M -5 -5 v10 h10 v-10 h-10" transform="matrix(3 0 0 3 78.11 -66.75)"/>
[EDIT] To use jQuery animate, this should work (scaling from 0 to 1 over 4 seconds):
var box = element.getBBox();
var cx = box.x + box.width/2;
var cy = box.y + box.height/2;
$(element).animate(
{ scale: 1 },
{ duration: 4000,
step: function(now, fx) {
scaleVal = now;
$(element).attr("transform", "translate(" + cx + " " + cy + ") scale(" + scaleVal + ") translate(" + (-cx) + " " + (-cy) + ")");
}
}
);
OK, now that I have reread your question, it seems that you want to use transform="scale()" and encountering the mysterious "move" also, which is confusing for most beginners learning SVG (me included).
Scaling is measured from the origin(0,0), hence if the object is at location (50,-100), when applying scale(2), the object location is doubled to (50*2, -100*2) => (100, -200). Hence you need to correct this by translate(-50,100).
Using matrix() would be the next area to explore as it is quite intuitive. The above example would require matrix(2 0 0 2 -50 100) to scale and move it back to original. Also with matrix() code, you can perform flip() and mirror() easily with the 2nd and 3rd field. Again you have to translate the length and/or width of object for these two transformation.