I'm trying to turn the following pattern...
...into a perfect sine wave pattern. Which control points should I use for it (how can I calculate them?)? Do I have to make the pattern wider also?
Here is how the above was generated:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="800" width="800">
<defs>
<!-- Geometry -->
<g>
<rect id="square" x="0" y="0" width="200" height="200" />
</g>
<!-- Patterns -->
<pattern id="wave" x="0" y="0" width="10" height="10" patternUnits="userSpaceOnUse">
<path d="M 0 7.5 Q 2.5 7.5 2.5 5 Q 2.5 2.5 5 2.5 Q 7.5 2.5 7.5 5 Q 7.5 7.5 10 7.5" stroke="black" fill="transparent" stroke-width="1" stroke-linecap="square" stroke-linejoin="miter" />
</pattern>
</defs>
<!-- Graphics -->
<use xlink:href="#square" transform="translate(000, 000)" fill="url(#wave)"/>
</svg>
Demo to play with is here: http://jsfiddle.net/uEULF/
Thank you for the help.
Use the example here in this answer by Asad : How to draw sine waves with SVG (+JS)?
Here is the demonstration : http://jsfiddle.net/HyTad/
var svg = document.getElementById('sine_wave').children[0];
var origin = { //origin of axes
x: 100,
y: 100
};
var amplitude = 10; // wave amplitude
var rarity = 1; // point spacing
var freq = 0.1; // angular frequency
var phase = 20; // phase angle
for (var i = -100; i < 1000; i++) {
var line = document.createElementNS("http://www.w3.org/2000/svg", "line");
line.setAttribute('x1', (i - 1) * rarity + origin.x);
line.setAttribute('y1', Math.sin(freq * (i - 1 + phase)) * amplitude + origin.y);
line.setAttribute('x2', i * rarity + origin.x);
line.setAttribute('y2', Math.sin(freq * (i + phase)) * amplitude + origin.y);
line.setAttribute('style', "stroke:black;stroke-width:1");
svg.appendChild(line);
}
Related
So basicly I have a grid that is using parrerns of paths/rects to draw the grid. On this grid I want to put numbers in x & y direction. Like a coordinate system you might know from school.
I tried putting text in the patterns but there are two problems with that:
the text in every rectangle is the same(not iterating)
every rectangle contains text(instead of just the "axes")
My guess is that I need another svg that will go on top of the other one in order to fix the last issue.
These are my patterns:
<defs>
<pattern id="smallGrid" width="15px" height="15px" patternUnits="userSpaceOnUse">
<path d="M 15 0 L 0 0 0 15" fill="none" stroke="gray" stroke-width="0.5"/>
</pattern>
<pattern id="grid" width="75" height="75" patternUnits="userSpaceOnUse">
<text x="20" y="70">foo</text> <== Here is the text I inserted
<rect width="75" height="75" fill="url(#smallGrid)"/>
<path d="M 75 0 L 0 0 0 75" fill="none" stroke="blue" stroke-width="4"/>
</pattern>
</defs>
The patterns get placed in an <svg>-tag refering to their id like this:
<svg>
<g id="viewport">
<rect id="gridParent" fill="url(#grid)"/>
</g>
</svg>
The end result looks smth like this:
But I want to do something like this:
I am thankful for all the hints you can give to me!!! Thanks in advance.
As #Robert Longson already pointed out:
svg <pattern> is rather static – so you can't implement any dynamic counter.
If you don't need to highlight individual columns (e.g with different fill colors) you could append <text> elements with some javaScript.
Essentially you need to loop through all columns/grid cells defined by your grids width and height and add <text> elements with appropriate x and y coordinates.
In this case:
(width/height) 600x300 = 8 columns; 4rows; 32 grid cells
Each time to reach the iteration index is divisible by the column count,
the current y offset is incremented according to the total numbers of rows.
Example: Add labels to svg grid
const svg = document.querySelector('svg');
const bb = svg.getBBox();
const strokeWidth = 4;
//get viewBox and adjust it to avoid overflow caused by grid strokes
const [x, y, width, height] = [bb.x, bb.y, bb.width + strokeWidth / 2, bb.height + strokeWidth / 2];
svg.setAttribute('viewBox', [x, y, width, height].join(' '));
const cols = Math.floor((width - strokeWidth / 2) / 75);
const rows = Math.floor((height - strokeWidth / 2) / 75);
// add labels
addGridLabelsCoord(svg, cols, rows, width, height, strokeWidth);
function addGridLabelsCoord(svg, cols, rows, width = 1, height = 1, strokeWidth = 1) {
// set initial y/x offset according to stroke width to avoid cropped outer strokes
let offsetX = (width - strokeWidth / 2) / cols;
let offsetY = (height - strokeWidth / 2) / rows;
let currentRow = 1;
let currentCol = 1;
let shiftX = 0;
let shiftY = 0;
let cellCount = cols * rows;
let nameSpace = 'http://www.w3.org/2000/svg';
// loop through all columns
for (let i = 0; i < cellCount; i++) {
// if current index is divisible by columns – move to next row
if (i > 0 && i % (cols) === 0) {
shiftX = 0;
shiftY += offsetY;
currentCol = 1;
currentRow++;
}
// add labels only for first row and first columns
if (currentRow == 1 || currentCol == 1) {
let colLabel = currentCol == 1 ? currentRow - 1 : i;
// add new cell to output
let text = document.createElementNS(nameSpace, 'text');
text.setAttribute('x', +(shiftX + offsetX / 2).toFixed(1));
text.setAttribute('y', +(shiftY + offsetY / 2).toFixed(1));
text.setAttribute('dominant-baseline', 'central');
text.setAttribute('text-anchor', 'middle');
text.textContent = colLabel;
svg.appendChild(text);
}
// increment x offset for next column
shiftX += offsetX;
currentCol++;
}
}
<svg viewBox="0 0 600 300">
<defs>
<pattern id="smallGrid" width="15px" height="15px" patternUnits="userSpaceOnUse">
<path d="M 15 0 L 0 0 0 15" fill="none" stroke="gray" stroke-width="0.5" />
</pattern>
<pattern id="grid" width="75" height="75" patternUnits="userSpaceOnUse">
<rect width="75" height="75" fill="url(#smallGrid)" />
<path d="M 75 0 L 0 0 0 75" fill="none" stroke="blue" stroke-width="4" />
</pattern>
</defs>
<rect x="0" y="0" width="100%" height="100%" id="gridParent" fill="url(#grid)" />
</svg>
I have 4 svgs. I would like to show 4 svgs as single <img>, which means let's say image has 4 equal parts, the 1st part has 1 svg and the 2nd part of the img tag has 2nd svg and so on...
4 svg ex:
<svg width="488px" height="531px">....</svg>
<svg width="350px" height="455px">....</svg>
<svg width="560px" height="620px">....</svg>
<svg width="395px" height="421px">....</svg>
<img src= SHOULD BE BELOW IMAGE/>
Like the comments said; combine them in one SVG
You can automate that.
Create your own <svg-grid> Web Component (supported in all modern browsers)
<svg-grid>
<svg width="48px" height="53px"></svg>
<svg width="65px" height="45px"></svg>
<svg width="56px" height="32px"></svg>
<svg width="39px" height="42px"></svg>
</svg-grid>
The Component reads all 4 SVG cildren
records the width/height
records its outerHTML
calculates the width/height needed for compiled SVG
creates that new SVG
adds the 4 SVGs calculating translate positions so they all align around the middle
overwrites the <svg-grid> innerHTML with the created IMG
All code required to output:
<script>
customElements.define('svg-grid', class extends HTMLElement {
connectedCallback() {
setTimeout(() => { // make sure all DOM SVG children are parsed
let colors = ["red", "green", "gold", "blue", "purple"];
let rect = i => `<rect width='100%' height='100%' fill='${colors[i]}'></rect>`;
let sizes = [];
let svgs = [...this.querySelectorAll("svg")].map((svg, idx) => {
svg.innerHTML = rect(idx) // rect color for testing only
sizes.push({
width: svg.width.baseVal.value,
height: svg.height.baseVal.value,
});
return svg.outerHTML;
});
let max = (x, y, wh) => Math.max(sizes[x][wh], sizes[y][wh]);
let c1w = max(0, 2, "width"); // column1width
let c2w = max(1, 3, "width"); // column2width
let r1h = max(0, 1, "height"); // row1height
let r2h = max(2, 3, "height"); // row2height
let grect = (nr,x,y) => `<g transform='translate(${x} ${y})'>${svgs[nr]}</g>`;
let svg = `<svg width='${c1w+c2w}' height='${r1h+r2h}' xmlns='http://www.w3.org/2000/svg'>` +
rect(4) + // extra background rect
grect( 0 , c1w-sizes[0].width , r1h-sizes[0].height ) +
grect( 1 , c1w , r1h-sizes[1].height ) +
grect( 2 , c1w-sizes[2].width , r1h ) +
grect( 3 , c1w , r1h ) +
`</svg>`;
// this.innerHTML = svg; // if you want the bare SVG
this.innerHTML = `<img src="data:image/svg+xml,${svg.replace(/"/g,"'").replace(/#/g, '%23')}">`;
})
}
});
</script>
<style> svg-grid img{ width:260px } </style>
<svg-grid>
<svg width="48px" height="53px"></svg>
<svg width="65px" height="45px"></svg>
<svg width="56px" height="32px"></svg>
<svg width="39px" height="42px"></svg>
</svg-grid>
<svg width="500" height="500">
<image x="20" y="20" width="100" height="100"
xlink:href='data:image/svg+xml,<svg width="100" height="100" version="1.1" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="50" r="50" fill="rgb(255,0,0)"/></svg>' />
<image x="100" y="20" width="100" height="100"
xlink:href='data:image/svg+xml,<svg width="100" height="100" version="1.1" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="50" r="50" fill="rgb(255,255,0)"/></svg>' />
<image x="20" y="100" width="100" height="100"
xlink:href='data:image/svg+xml,<svg width="100" height="100" version="1.1" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="50" r="50" fill="rgb(0,255,0)"/></svg>' />
<image x="100" y="100" width="100" height="100"
xlink:href='data:image/svg+xml,<svg width="100" height="100" version="1.1" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="50" r="50" fill="rgb(0,255,255)"/></svg>' />
</svg>
<image ... xlink:href='data:image/svg+xml,svg1 string.../>
<image ... xlink:href='data:image/svg+xml,svg2 string.../>
...
result url: https://stackblitz.com/edit/js-ryh4kb?file=index.html
Actually I am new to SVG. I need's to draw baseball spray chart as have in below.Please help how to get this exactly and each section dynamically varies.Is it possible to draw using HTML CSS.Thanks.
UPDATE
I'm updating the question with the OP's comments that are important:
I forget to tell you it is a baseball ground. Consider batter on PA and we are showing batter hit location in percentage around the ground in a game.
There are a few steps:
you draw the individual paths
you draw the paths for the text (text on a path)
you draw the text
let R = 200;// the outer radius
let r = 120;// the middle radius
let objects = [];// an array for the paths
class pathObj{
constructor(a1,a2,R,r,path,text){
this.a1 = a1; //starting angle
this.a2 = a2; // ending angle
this.R = R;//the outer radius
this.r = r;// the middle radius
this.rm = r + 2*(R - r)/3; // the radius for the text path
this.path = document.querySelector(`#${path}`);
this.textpath = document.querySelector(`#p${path}`);
this.textElement = document.querySelector(`#t${path}`);
this.textPath = document.querySelector(`#t${path} textPath`);
this.textPath.textContent = text;
// points to draw the paths
this.p1 = {
x:this.R*Math.cos(this.a1),
y:this.R*Math.sin(this.a1)
};
this.p2 = {
x:this.R*Math.cos(this.a2),
y:this.R*Math.sin(this.a2)
}
this.p3 = {
x:this.r*Math.cos(this.a2),
y:this.r*Math.sin(this.a2)
}
this.p4 = {
x:this.r*Math.cos(this.a1),
y:this.r*Math.sin(this.a1)
}
this.p5 = {
x:this.rm*Math.cos(this.a1),
y:this.rm*Math.sin(this.a1)
}
this.p6 = {
x:this.rm*Math.cos(this.a2),
y:this.rm*Math.sin(this.a2)
}
}
draw(){
// the d attribute for the main path
let d = `M${this.p1.x},${this.p1.y}
A${this.R},${this.R} 0 0 1 ${this.p2.x},${this.p2.y}
L${this.p3.x},${this.p3.y}
A${this.r},${this.r} 0 0 0 ${this.p4.x},${this.p4.y}
Z`;
// the d attribute for the text path
let d1 = `M${this.p5.x},${this.p5.y}
A${this.R},${this.R} 0 0 1 ${this.p6.x},${this.p6.y}`
this.path.setAttributeNS(null,"d",d);
this.textpath.setAttributeNS(null,"d",d1);
}
}
// create the objects and push into the objects array
objects.push(new pathObj(0,Math.PI/6,R,r,"a","11%"));
objects.push(new pathObj(Math.PI/6,Math.PI/3,R,r,"b","18%"));
objects.push(new pathObj(Math.PI/3,Math.PI/2,R,r,"c","23%"));
objects.push(new pathObj(0,Math.PI/8,r,0,"d","3%"));
objects.push(new pathObj(Math.PI/8,Math.PI/4,r,0,"e","3%"));
objects.push(new pathObj(Math.PI/4,3*Math.PI/8,r,0,"f","29%"));
objects.push(new pathObj(3*Math.PI/8,Math.PI/2,r,0,"g","13%"));
objects.forEach(o=>o.draw())
svg{border:1px solid }
path{stroke:black; fill:transparent;}
text{ text-anchor:middle;}
<svg viewBox="-200 -250 400 250">
<defs>
<path id="pa" />
<path id="pb" />
<path id="pc" />
<path id="pd" />
<path id="pe" />
<path id="pf" />
<path id="pg" />
</defs>
<g transform="rotate(-135)">
<path id="a" /><text id="ta"><textPath startOffset="50%" xlink:href="#pa"></textPath></text>
<path id="b" /><text id="tb"><textPath startOffset="50%" xlink:href="#pb"></textPath></text>
<path id="c" /><text id="tc"><textPath startOffset="50%" xlink:href="#pc"></textPath></text>
<path id="d" /><text id="td"><textPath startOffset="50%" xlink:href="#pd"></textPath></text>
<path id="e" /><text id="te"><textPath startOffset="50%" xlink:href="#pe"></textPath></text>
<path id="f" /><text id="tf"><textPath startOffset="50%" xlink:href="#pf"></textPath></text>
<path id="g" /><text id="tg"><textPath startOffset="50%" xlink:href="#pg"></textPath></text>
</g>
</svg>
I am trying to create a shape using an svg path that follows the outline of an svg polygon.
I need to set the d attribute of the svg path element because the size of the shape will be different depending on how much text there is in the <text /> element that is added to the document. I will have a number of these shapes but I'm just showing one here for brevity.
MY logic is to try and basically start at the middle, move down and right for 60 degrees, move back to the middle and move down left 60 degrees and then join up what is left of the box.
My shape is not quite right. There are a number of problems:
The shape is not in line with the point of the hexagon.
The lengths of each flat line or H that appears the diagonal line are not the same length.
There is a rogue line trying to join back to the point of the shape.
I need to add curves to the corners and I'm not sure how to do this.
const getPoint = ({
sides,
size,
center,
rotate,
side
}) => {
const degrees = (360 / sides) * side - rotate;
const radians = (Math.PI / 180) * degrees;
return {
x: center.x + size * Math.cos(radians),
y: center.y + size * Math.sin(radians)
};
};
const path = document.querySelector('path');
const gRef = document.querySelector('.hierarchy-label__container');
gRef.setAttribute('transform', 'translate(0, -40)');
const gbBox = gRef.getBBox();
let startingX = gbBox.x + gbBox.width / 2;
const startingY = gbBox.y + gbBox.height;
startingX = startingX - 0.7;
const [bottomRight, bottomLeft] = [1, 4].map((side) =>
getPoint({
sides: 6,
size: 30,
center: { x: startingX, y: startingY },
side,
rotate: 30
})
);
const bottomRightCoords = `${bottomRight.x} ${bottomRight.y}`;
path.setAttribute(
'd',
`M ${startingX} ${startingY} L ${bottomRightCoords} H ${gbBox.width} M ${startingX} ${startingY} L ${
bottomLeft.x
} ${bottomRight.y} H -${gbBox.width + 4} V -${gbBox.height} H ${gbBox.width} L ${gbBox.width} ${
bottomRight.y
} M ${startingX} ${startingY} Z`
);
.hierarchy-label__text {
fill: white;
text-anchor: middle;
font-size: 1.2rem;
}
<svg preserveAspectRatio="xMinYMin meet" viewBox="0 0 990 759">
<g class="vx-group vx-cluster" transform="translate(100, 100)">
<g class="vx-group" transform="translate(0, 0)">
<g class="vx-group node-vertical__container" transform="translate(490, 0)">
<polygon points="25.98076211353316,-14.999999999999998 25.98076211353316,14.999999999999998 1.83697019872103e-15,30 -25.98076211353316,14.999999999999998 -25.980762113533157,-15.000000000000004 -5.510910596163089e-15,-30"></polygon>
<g class="vx-group node-vertical__business-unit" transform="translate(0, 0)">
<use xlink:href="#icon-BusinessUnit"></use>
</g>
<g class="hierarchy-label__container" transform="translate(0,-40)">
<text class="hierarchy-label__text" width="50" fill="white" x="0" y="0" text-anchor="middle" style="pointer-events: none;">
<tspan x="0" dy="0em">Finance</tspan>
</text>
<path></path>
</g>
</g>
</g>
</g>
</svg>
For the sake of clarity I've simplified a lot your code. Also: I've chosen to draw the hexagon in JavaScript in order to be able to use the hexagon vertices to draw the path where you put the text.
Please read code's comments
function drawHex(r){
// this function draws a hexagon with the center in 0,0
// and returns the array of points
// r is the radius of the circumscribed circle
let pointsRy = [];
let a = Math.PI/3;
for( let i = 0; i < 6; i++ ){
let aRad = (a*i) - Math.PI/2;
let Xp = parseFloat(r * Math.cos( aRad )).toFixed(3);
let Yp = parseFloat(r * Math.sin( aRad )).toFixed(3);
pointsRy.push({x:Xp,y:Yp,a:aRad});
}
// the points for the hexagon
let points = pointsRy.map(p => `${p.x}, ${p.y}`).join(" ");
hex.setAttributeNS(null,"points", points)
// the function returns the array of points
return pointsRy;
}
// ry: the array of points used to draw the hexagon: I'll be using the first & the second point to drae the textRect path
let ry = drawHex(30);
function drawTextPath(W,H){
// W: the width of the text "rectangle"
// H: the height of the text "rectangle"
// the textRect & the text art translated upwards (in y). Please see svg
let w = W/2 - (Number(ry[1].x) - Number(ry[0].x));
let d = `M${ry[0].x},${ry[0].y} L${ry[1].x},${ry[1].y} h${w} v-${H} h-${W} v${H} h${w}`;
textRect.setAttributeNS(null,"d",d)
}
drawTextPath(180,50)
svg{font-family: "Century Gothic",CenturyGothic,AppleGothic,sans-serif;}
text{fill:white; text-anchor:middle;pointer-events: none;}
<svg viewBox="-100 -70 200 200">
<polygon id="hex" />
<g transform="translate(0,-6)">
<path id="textRect" />
<text y="-40">
<tspan>Finance</tspan>
</text>
</g>
</svg>
Without any doubt there are other ways to draw this. Y hope you'll find my solution useful.
I am making circle menu, so I use SVG to create a circle, and now I want to show a link with some image inside of part of the circle. How i can do it? My code -
render(){
return(
<svg id={"menuLevel" + index} width={200} height={200}>
<path fill="white" stroke="rgba(0,0,0,0.2)" strokeWidth="2" d={"M"+width+","+width+" L"+previousX+", "+previousY+" A"+width+","+width+" 0 0,0 "+x+", "+y+" z"}></path>
</svg>
)
}
I tried something like this -
<path fill="white" stroke="rgba(0,0,0,0.2)" strokeWidth="2" d={"M"+width+","+width+" L"+previousX+", "+previousY+" A"+width+","+width+" 0 0,0 "+x+", "+y+" z"}>
<foreignobject x="120" y="120" width="180" height="180">
<Link ...><Image .../></Link>
</foreignobject>
</path>
But it doesn't work, this foreign object have still 0 width and 0 height and content doesn't show.
UPDATE
I need to assign link component to all path objects
<svg id={"menuLevel" + index} width={width*2+2} height={width*2+2}>
{arr.map(function(item){
let angleInRadians = -item * Math.PI / 180.0;
let previousX = x;
let previousY = y;
x = width + width * Math.cos(angleInRadians);
y = width + width * Math.sin(angleInRadians);
return(
<path fill="white" stroke="rgba(0,0,0,0.2)" strokeWidth="2" d={"M"+width+","+width+" L"+previousX+", "+previousY+" A"+width+","+width+" 0 0,0 "+x+", "+y+" z"}>
</path>
)
})}
</svg>
Please check it here JSFiddle. Use image element to add the image to SVG: https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/SVG_Image_Tag
<svg width="5cm" height="4cm" version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink= "http://www.w3.org/1999/xlink">
<circle x="0" y="0" r="200"></circle>
<image xlink:href="https://www.google.co.uk/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" x="0" y="0" height="200px" width="200px"/>
</svg>
Please note:
If you do not set the x or y attributes, they will be set to 0.
If you do not set the height or width attributes, they will be set to 0.
Having a height or width attribute of 0 will disable rendering of the image.
Update 1
Here is a working example to add a React component together with the image: JSFiddle. But I make the Link component as a sibling of the SVG, and then using absolute to position them. Not a perfect solution.
Update 2
To make a path clickable: JSFiddle.
Update 3
This is an image with clickable paths, integrated with ReactJS: JSFiddle:
var Link = React.createClass({
render: function() {
return <a className="link" href={this.props.href} target="_blank">{this.props.children}</a>
}
});
var Hello = React.createClass({
render: function() {
return <div id="container"><svg xmlns="http://www.w3.org/2000/svg" width="300px" height="300px">
<Link href="http://www.google.com">
<g transform="translate(100, 100)"><image href="https://www.google.co.uk/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" x="0" y="0" height="200px" width="200px"/></g>
</Link>
<Link href="http://www.facebook.com">
<g><image href="https://www.facebook.com/images/fb_icon_325x325.png" x="0" y="0" height="100px" width="100px"/></g>
</Link>
</svg></div>
}
});