[[[https://codepen.io/thebabydino/pen/zEbVPO]]]
- var o = -.5*d, r = .43*d;
- var a0 = 198*Math.PI/180, a1 = 225*Math.PI/180;
- var x0 = Math.round(r*Math.cos(a0));
- var y0 = Math.round(r*Math.sin(a0));
- var x1 = Math.round(r*Math.cos(a1));
- var y1 = Math.round(r*Math.sin(a1));
svg#demo(viewBox=[o, o, 2*d, d].join(' '))
style
| svg { font: #{fs}px consolas, monaco, monospace }
marker#end(markerWidth=ad markerHeight=ad
viewBox='0 -4 8 8'
orient='auto' refX='7')
path(d='M8 0 0-4V4')
g#arc
circle(r=r)
path(d=`M${x0} ${y0} A${r} ${r} 0 0 1 ${x1} ${y1}`)
use(xlink:href='#arc' x=d)
View Compiled
$demo-dark: #333;
$demo-light: #ccc;
$demo-ll: #95a;
$demo-hl: #f90;
body {
display: flex;
margin: 0;
height: 100vh
}
svg {
display: block;
width: 100vw;
color: $demo-light
}
path { fill: none }
circle {
fill: currentcolor;
stroke: $demo-dark;
stroke-width: 5px
}
text {
fill: currentcolor;
color: #777
}
[id='end'] path {
fill: #b53
}
[id='arc'] {
* { fill: none }
circle { stroke: $demo-light }
path {
stroke: #b53;
stroke-width: 7px;
marker-end: url(#end)
}
}
.curves {
stroke: transparent;
stroke-width: 98px;
cursor: pointer;
color: $demo-dark;
use {
stroke: currentcolor;
stroke-width: 10px;
pointer-events: none
}
}
.guides {
stroke: $demo-ll;
stroke-width: 4px;
pointer-events: none;
path { opacity: 0 }
}
.points {
.end { cursor: pointer }
.ctrl { opacity: 0 }
}
#demo .hl {
opacity: 1;
color: $demo-hl;
transition: .3s
}
View Compiled
const _SVG = document.getElementById('demo'),
D = .5*_SVG.getAttribute('viewBox').split(' ')[2],
NS_URI = 'http://www.w3.org/2000/svg',
XLink_NS = 'http://www.w3.org/1999/xlink',
S = [], N = 2,
P = 5, NCUB = 3, NT = P*NCUB,
PR = (.02*D), PR1 = .5*PR,
ACT = ['add', 'remove'],
BBOX = [];
let hl = null;
function getStarCoord(f = .32) {
const RCO = f*D /* outer (pentagram) circumradius*/,
BAS = 2*(2*Math.PI/P) /* base angle for star poly */,
BAC = 2*Math.PI/P /* base angle for convex poly */,
RI = RCO*Math.cos(.5*BAS) /*pentagram/ inner pentagon inradius */,
RCI = RI/Math.cos(.5*BAC) /* inner pentagon circumradius */,
ND = 2*P /* total number of distinct points we need to get */,
BAD = 2*Math.PI/ND /* base angle for point distribution */,
PTS = [] /* array we fill with point coordinates */;
for(let i = 0; i < ND; i++) {
let /* radius at end point (inner)/ control point (outer) */
cr = i%2 ? RCI : RCO,
/* angle of radial segment from origin to current point */
ca = i*BAD + .5*Math.PI,
x = Math.round(cr*Math.cos(ca)),
y = Math.round(cr*Math.sin(ca));
PTS.push([x, y]);
/* for even indices double it, control points coincide here */
if(!(i%2)) PTS.push([x, y]);
}
return PTS
};
function getHeartCoord(f = .16) {
const R = f*D /* helper circle radius */,
RC = Math.round(R/Math.SQRT2) /* circumradius of square of edge R */,
XT = 0, YT = -RC /* coords of point T */,
XA = 2*RC, YA = -RC /* coords of A points (x in abs value) */,
XB = 2*RC, YB = RC /* coords of B points (x in abs value) */,
XC = 0, YC = 3*RC /* coords of point C */,
XD = RC, YD = -2*RC /* coords of D points (x in abs value) */,
XE = 3*RC, YE = 0 /* coords of E points (x in abs value) */,
/* const for cubic curve approx of quarter circle */
C = .551915,
CC = 1 - C,
/* coords of ctrl points on TD segs */
XTD = Math.round(CC*XT + C*XD), YTD = Math.round(CC*YT + C*YD),
/* coords of ctrl points on AD segs */
XAD = Math.round(CC*XA + C*XD), YAD = Math.round(CC*YA + C*YD),
/* coords of ctrl points on AE segs */
XAE = Math.round(CC*XA + C*XE), YAE = Math.round(CC*YA + C*YE),
/* coords of ctrl points on BE segs */
XBE = Math.round(CC*XB + C*XE), YBE = Math.round(CC*YB + C*YE);
return [
[XC, YC], [XC, YC], [-XB, YB],
[-XBE, YBE], [-XAE, YAE], [-XA, YA],
[-XAD, YAD], [-XTD, YTD], [XT, YT],
[XTD, YTD], [XAD, YAD], [XA, YA],
[XAE, YAE], [XBE, YBE], [XB, YB]
].map(([x, y]) => [x, y - .09*R]);
};
function createShapes() {
let f = document.createDocumentFragment();
S.push(...[
{ coord: getStarCoord() },
{ coord: getHeartCoord() }
]);
for(let i = 0; i < N; i++) {
let _curves, _guides, _points, _labels;
S[i].g = document.createElementNS(NS_URI, 'g');
S[i].g.classList.add('shape');
_curves = document.createElementNS(NS_URI, 'g');
_curves.classList.add('curves');
S[i].c = [];
_guides = document.createElementNS(NS_URI, 'g');
_guides.classList.add('guides');
_points = document.createElementNS(NS_URI, 'g');
_points.classList.add('points');
_labels = document.createElementNS(NS_URI, 'g');
_labels.classList.add('labels');
for(let j = 0; j < P; j++) {
let sidx = j*NCUB,
tmp = S[i].coord.slice(sidx, sidx + NCUB),
unique = (tmp[0][0] == tmp[1][0] && tmp[0][1] == tmp[1][1]) ?
tmp.slice(1) : tmp;
S[i].c.push({
curve: document.createElementNS(NS_URI, 'path'),
ccopy: document.createElementNS(NS_URI, 'use'),
guide: document.createElementNS(NS_URI, 'path'),
ends: [
j ? S[i].c[j - 1].ends[1] : null,
document.createElementNS(NS_URI, 'circle')
],
ctrl: unique.slice(0, unique.length - 1)
.map(_ => document.createElementNS(NS_URI, 'circle')),
text: document.createElementNS(NS_URI, 'text')
});
S[i].c[j].curve.id = `curve-${i}-${j}`;
S[i].c[j].curve.setAttribute(...[
'd',
`M${S[i].coord[(sidx + NT - 1)%NT]}
C${tmp}`
]);
S[i].c[j].ccopy.setAttributeNS(...[
XLink_NS,
'xlink:href',
`#${S[i].c[j].curve.id}`
]);
_curves.appendChild(S[i].c[j].curve);
_curves.appendChild(S[i].c[j].ccopy);
S[i].c[j].guide.setAttribute(...[
'd',
`M${S[i].coord[(sidx + NT - 1)%NT]}
${unique}`
]);
_guides.appendChild(S[i].c[j].guide);
S[i].c[j].ctrl.forEach((c, k) => {
c.classList.add('ctrl');
c.setAttribute('r', PR1);
c.setAttribute('cx', S[i].coord[sidx + k][0]);
c.setAttribute('cy', S[i].coord[sidx + k][1]);
_points.appendChild(c);
});
S[i].c[j].ends[1].id = `point-${i}-${(j + 1)%P}`
S[i].c[j].ends[1].classList.add('end');
S[i].c[j].ends[1].setAttribute('r', PR);
S[i].c[j].ends[1].setAttribute('cx', S[i].coord[sidx + NCUB - 1][0]);
S[i].c[j].ends[1].setAttribute('cy', S[i].coord[sidx + NCUB - 1][1]);
_points.appendChild(S[i].c[j].ends[1]);
S[i].c[j].text.textContent = j;
if(S[i].c[j].ctrl.length === 1) {
S[i].c[j].text.setAttribute(...[
'x',
1.125*S[i].coord[sidx][0] - .5*BBOX[j][0]
]);
S[i].c[j].text.setAttribute(...[
'y',
1.125*S[i].coord[sidx][1] + .5*BBOX[j][0]
]);
}
else {
if(S[i].coord[sidx][0] === S[i].coord[sidx + 1][0]) {
S[i].c[j].text.setAttribute(...[
'x',
1.15*S[i].coord[sidx][0] - .5*BBOX[j][0]
]);
}
else {
S[i].c[j].text.setAttribute(...[
'x',
.5*(S[i].coord[sidx][0] + S[i].coord[sidx + 1][0]) - .5*BBOX[j][0]
]);
S[i].c[j].text.setAttribute(...[
'y',
1.25*S[i].coord[sidx][1] + .5*BBOX[j][0]
]);
}
}
_labels.appendChild(S[i].c[j].text)
}
S[i].c[0].ends[0] = S[i].c[P - 1].ends[1];
S[i].g.appendChild(_curves);
S[i].g.appendChild(_guides);
S[i].g.appendChild(_points);
S[i].g.appendChild(_labels);
f.appendChild(S[i].g)
}
S[1].g.setAttribute('transform', `translate(${D} 0)`)
_SVG.appendChild(f)
};
function textSize() {
const _TT = document.createElementNS(NS_URI, 'text');
_SVG.appendChild(_TT);
for(let j = 0; j < P; j++) {
_TT.textContent = j;
let bb = _TT.getBBox();
BBOX.push([Math.round(bb.width), Math.round(bb.height)]);
}
_SVG.removeChild(_TT);
};
function highlight(hl_obj, add = 1) {
const CURR_ACT = ACT[.5*(1 - add)];
for(let i = 0; i < N; i++) {
if(hl_obj.typ === 'curve') {
S[i].c[hl_obj.idx].ccopy.classList[CURR_ACT]('hl');
S[i].c[hl_obj.idx].guide.classList[CURR_ACT]('hl');
S[i].c[hl_obj.idx].text.classList[CURR_ACT]('hl');
S[i].c[hl_obj.idx].ctrl.forEach(c => c.classList[CURR_ACT]('hl'));
S[i].c[hl_obj.idx].ends[1].classList[CURR_ACT]('hl');
}
}
hl = add > 0 ? hl_obj : null;
};
(function init() {
textSize();
createShapes();
addEventListener('click', e => {
const _T = e.target;
if(hl) highlight(hl, -1);
if(_T.id && /(point)|(curve)/.test(_T.id)) {
const DATA = _T.id.split('-');
highlight({typ: DATA[0], idx: +DATA[2]});
}
}, false);
})();
View Compiled
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.