and an svg path that follows the outline of an svg polygon - html

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.

Related

SVG: generate increasing numbers on grid

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>

SVG - Inline position element after text

I want to position SVG several elements inline inside a single SVG.
I have the following:
<svg>
<g>
<use width="28" height="28"class="cls-11"></use>
<text id="policyRectText" transform="translate(50,20)" class="cls-54">Runs on a small number of endpoints</text>
<circle r="15" stroke-width="4" transform="translate(250,15)" class="cls-8"></circle>
</g>
</svg>
So to position an element before dynamic text is easy, but how can i position after it?
This is how I would do it:
First I'm putting the <text> in a <g> element since it's transformed and I need to get the bounding box:
let bb = theTransformedText.getBBox();
Once I have the position and the size of the text (bb) I'm using the data to set the cx and the cy attributes for the circle.
I've commented out the <use> element since it has no xlink:href attribute.
let bb = theTransformedText.getBBox();
let r = parseFloat(theCircle.getAttribute("r"));// the circle's radius.
let sw = parseFloat(theCircle.getAttribute("stroke-width"));// the stroke width
theCircle.setAttributeNS(null, "cx", bb.x + bb.width + r + sw/2);
// assuming that the font size is 16 you need to offset the circle half font size
theCircle.setAttributeNS(null, "cy", bb.y + 8);
<svg viewBox="0 -50 400 100">
<g>
<!--<use width="28" height="28"class="cls-11"></use>-->
<g id="theTransformedText">
<text id="policyRectText" transform="translate(50,20)" class="cls-54">Runs on a small number of endpoints</text>
</g>
<circle id="theCircle" r="15" stroke-width="4" class="cls-8"></circle>
</g>
</svg>

Change svg fill color onScroll

How can i change the fill color of an SVG logo onScroll; ¿thats posible with html or any css property?
The color change when you scroll when on another DIV
The idea is to use mix-blend-mode: differencein CSS together with isolation: isolate; for the group.
It's up to you how you want to move the layers. I'm using an input type range for this. You may use scroll or wheel.
I hope this helps.
percent.addEventListener("input",()=>{
let val = ~~(percent.value);
let _var = map(100-val,0,100,3,27);
txt.textContent = val+"%";
pth.setAttributeNS(null,"d",`M3,27H27V${_var}H3z`)
})
function map(n, a, b, _a, _b) {
let d = b - a;
let _d = _b - _a;
let u = _d / d;
return _a + n * u;
}
svg{border:1px solid; font-size:10px; background:lightblue}
[type="range"]{width:150px;}
<svg viewBox="0 0 30 30" width="150">
<g style="isolation: isolate;">
<path d="M3,27H27V3H3z" fill="white" />
<path id="pth" d="M3,27H27V15H3z" />
<text id="txt" x="15" y="15" dominant-baseline="middle" text-anchor="middle" fill="white" style="mix-blend-mode: difference;">50%</text>
</g>
</svg>
<p><input id="percent" type="range" value="50" /></p>

Svg Spray chart

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>

How to draw a sine wave pattern with SVG

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);
}