)
}
}
)
(
}
{
)
)
(
)
(
(
{
}
)
(
)
}
)
)
{
(
(
)
)
}
)
(
}

Catmull Rom to Bezier SVG

  1. // from: 
  2. //   https://github.com/DmitryBaranovskiy/raphael/blob/b5fdbdc9850827eb49622cd0bc81b26025f8128a/dev/raphael.core.js#L1019
  3. // and: 
  4. //   http://schepers.cc/getting-to-the-point
  5.  
  6. function catmullRom2bezier(crp, close) {
  7.   const d = [];
  8.   for (let i = 0, iLen = crp.length; iLen - 2 * !close > i; i += 2) {
  9.     const p = [
  10.       { x: crp[i - 2], y: crp[i - 1] },
  11.       { x: crp[i], y: crp[i + 1] },
  12.       { x: crp[i + 2], y: crp[i + 3] },
  13.       { x: crp[i + 4], y: crp[i + 5] }
  14.     ];
  15.     if (close) {
  16.       if (!i) {
  17.         p[0] = { x: crp[iLen - 2], y: crp[iLen - 1] };
  18.       } else if (iLen - 4 == i) {
  19.         p[3] = { x: crp[0], y: crp[1] };
  20.       } else if (iLen - 2 == i) {
  21.         p[2] = { x: crp[0], y: crp[1] };
  22.         p[3] = { x: crp[2], y: crp[3] };
  23.       }
  24.     } else {
  25.       if (iLen - 4 == i) {
  26.         p[3] = p[2];
  27.       } else if (!i) {
  28.         p[0] = { x: crp[i], y: crp[i + 1] };
  29.       }
  30.     }
  31.     d.push([
  32.       'C',
  33.       (-p[0].x + 6 * p[1].x + p[2].x) / 6,
  34.       (-p[0].y + 6 * p[1].y + p[2].y) / 6,
  35.       (p[1].x + 6 * p[2].x - p[3].x) / 6,
  36.       (p[1].y + 6 * p[2].y - p[3].y) / 6,
  37.       p[2].x,
  38.       p[2].y
  39.     ]);
  40.   }
  41.  
  42.   // really for demo purposes:
  43.  
  44.   // draw a tiny rect if less than 2 points
  45.   console.log(d.length);
  46.   if (d.length < 2) {
  47.     const x = crp[0];
  48.     const y = crp[1];
  49.     return `M ${x} ${y} h 1 v 1 h -1 v-1`;
  50.  
  51.     // draw a line if 2 points
  52.   } else if (d.length === 2) {
  53.     return `M ${crp[0]} ${crp[1]} L ` + crp.flat().join` `;
  54.   }
  55.   // draw curves if 3 or more (raphael only ever did this, other numbers of points
  56.   // were considered invalid)
  57.   return  `M ${crp[0]} ${crp[1]}` + d.flat().join` `;
  58. }
  59.  
  60. // try it out:
  61.  
  62. const pathCommands = catmullRom2bezier([
  63.   100, 100, 200, 100, 200, 300, 170, 150, 120, 180, 120, 120
  64.   ], true)
  65.  
  66. const svg = `
  67.   click 3 or more times anywhere<br>
  68.   <button>clear</button>
  69.   <svg>
  70.     <path d="M 100 100 ${pathCommands}" stroke="blue" fill="none" />
  71.   </svg>
  72.   <style>
  73.   svg {
  74.     position:absolute;
  75.     left:0;top:0;
  76.     overflow:visible;
  77.     pointer-events:none;
  78.   }
  79.   </style>
  80. `;
  81.  
  82. const div = document.body.appendChild(
  83.   document.createElement('div')
  84. );
  85. div.innerHTML = svg;
  86. const path = div.querySelector('path')
  87.  
  88. let pnts = [];
  89.  
  90. function isButton(e) {
  91.   if (e.target.tagName === 'BUTTON') {
  92.     pnts = []
  93.     path.setAttribute('d', 'M 0 0L0 0');
  94.     return true;
  95.   }
  96. }
  97.  
  98. document.addEventListener('click', e => {
  99.   if (isButton(e)) return;
  100.   pnts.push(e.clientX, e.clientY);
  101.   path.setAttribute('d', catmullRom2bezier(pnts, true))
  102. });
  103.  
  104. document.addEventListener('touchend', e => {
  105.   if (isButton(e)) return;
  106.   pnts.push(e.touchs[0].clientX, e.touchs[0].e.clientY);
  107.   path.setAttribute('d', catmullRom2bezier(pnts, true))
  108. });

Raphaël was my first deep dive into SVG. Back then, only VML was supported in older IE versions, so using Raphaël was key for tons of commercial work I did (Raphaël generated VML as well as SVG). This snippet is the Catmull Rom code from Raphaël. I’m pretty sure I didn’t know what a Catmull Rom spline was until I found out that using `R` in a path wasn’t supported natively in SVG… and was actually some magic thing that existed in Raphaël alone.

// curves // javascript // math // svg

John Wallis Pi (Wallis Product)

  1. /*
  2. It was always my affectation even from a child, in all pieces
  3. of Learning or Knowledge, not merely to learn by rote, which 
  4. is soon forgotten, but to know the grounds or reasons of what 
  5. I learn; to inform my Judgement, as well as furnish my Memory; 
  6. and thereby, make a better Impression on both.
  7.  
  8. - John Wallis
  9.  
  10. */
  11.  
  12. i = c = 1
  13. setInterval(_ => {
  14.   for (j = 0; j < 1000; j++, i += .5)
  15.     c *= (~~i * 2) / (~~(i - .5) * 2 + 1)
  16.  
  17.   console.log(c * 2 + '\n' + 3.141592653589793)
  18. }, 16);
π = 2
2 / 1
·
2 / 3
·
4 / 3
·
4 / 5
·
6 / 5
·
6 / 7
·
8 / 7
·
8 / 9
·   · · ·

I was at an old book store a few days ago and randomly picked up History of Mathematics Vol. I by David Eugene Smith 1923 for $7.50. After enjoying flipping around and reading different parts, I pulled up Wikipedia to read more about the author… From there I found some interesting quotes from talking about John Wallis – when I saw the Wallis Product I was inspired to write a quick snippet.

// golfed // javascript // math // pi

ASCII Circles (Bresenham Fun)

  1. // some circles made of text
  2.  
  3. const cols = 50;
  4. const rows = 50;
  5. const sym = '.';
  6. const body = document.body;
  7.  
  8. Object.assign(body.style, {
  9.   userSelect: 'none',
  10.   fontFamily: 'Courier, monospace',
  11.   position: 'fixed', 
  12.   width: '100%',
  13.   height: '100%',
  14.   margin: 0
  15. });
  16.  
  17. const el = body.appendChild(
  18.   document.createElement('span')
  19. );
  20. el.innerHTML = sym;
  21.  
  22. Object.assign(el.style, {
  23.   position: 'absolute',
  24.   left: '50%',
  25.   top: '50%',
  26.   wordWrap: 'break-word',
  27.   cursor: 'pointer'
  28. });
  29.  
  30. const charSize = el.getBoundingClientRect().width;
  31. const elWidth = charSize * cols;
  32. el.style.display = 'block'
  33. el.style.width = `${elWidth}px`;
  34.  
  35. const info = body.appendChild(
  36.   document.createElement('div')
  37. );
  38.  
  39. Object.assign(info.style, {
  40.   background: '#fff',
  41.   padding: '4px',
  42.   position: 'absolute'
  43. })
  44. info.innerHTML = 'click/tap and hold for different fx';
  45.  
  46. function resize() {
  47.   const scl = Math.min(
  48.     1.23, 
  49.     Math.min(innerWidth, innerHeight) / elWidth * .93
  50.   );
  51.   el.style.transform = `translate(-50%, -50%) scale(${scl}, ${scl * .55})`
  52. }
  53. addEventListener('resize', resize);
  54. resize();
  55.  
  56. const size = cols * rows;
  57. const pix = sym.repeat(size);
  58. el.innerHTML = pix;
  59.  
  60. let cells = pix.split('')
  61. const blank = cells.concat();
  62.  
  63. function setSym(x, y, col) {
  64.   const idx = x + y * cols;
  65.   if (cells[idx] != null) {
  66.     cells[idx] = col;
  67.   }
  68.   return setSym
  69. }
  70.  
  71. const grad = '::;|0UU888NN';
  72. function circ(shooter) {
  73.   let x = Math.round(Math.random() * cols);
  74.   let y = Math.round(Math.random() * rows);
  75.   const rad = Math.round(
  76.     shooter ? Math.random() * 2 :
  77.       Math.random() * Math.random() * 13 + 1
  78.   );
  79.  
  80.   const sym = shooter ? '#' : grad.charAt(rad % grad.length);
  81.   let speed = rad / 10 + .1;
  82.   let dir = Math.random() * 2 - 1;
  83.   return () => {
  84.     drawCircle(x, y, rad, sym, shooter);
  85.     y += speed;
  86.     if (shooter) x += speed * 3 * dir;
  87.     if (y > rows + 10) y = -14;
  88.   }
  89. }
  90.  
  91. const circs = [];
  92. const NUM = 40;
  93. for (let i = 0; i < NUM; i++) {
  94.   circs.push(circ(Math.random() > 0.5))
  95. }
  96.  
  97. let down;
  98. document.addEventListener('mousedown', () => {
  99.   down = true;
  100. });
  101. document.addEventListener('mouseup', () => {
  102.   down = false;
  103. });
  104. document.addEventListener('touchstart', () => {
  105.   down = true;
  106. });
  107. document.addEventListener('touchend', () => {
  108.   down = false;
  109. });
  110.  
  111. let tweak;
  112. let tweakChoice;
  113. let tweakChance = 0.4;
  114. const clear = () => cells = blank.concat();
  115.  
  116. function draw() {
  117.   if (!down) {
  118.     tweak = false;
  119.     tweakChoice = Math.random();
  120.     clear();
  121.   } else {
  122.     if (tweakChoice < tweakChance) {
  123.       tweak = true;
  124.       clear();
  125.     }
  126.   }
  127.  
  128.   circs.forEach(circ => circ());
  129.   el.innerHTML = cells.join('');
  130. }
  131.  
  132. // 60fps is too fast, so use 30ms interval
  133. setInterval(draw, 30);
  134.  
  135. function hLine(xp, yp, w, col) {
  136.   for (let i = 0; i < w; i++) {
  137.     setSym(xp + i, yp, col);
  138.   }
  139.   return hLine;
  140. }
  141.  
  142. // bresenham circle
  143. function drawCircle(xp, yp, radius, sym = '@', isFilled) {
  144.   if (isFilled && tweak) sym = '';
  145.  
  146.   xp = parseInt(xp, 10);
  147.   yp = parseInt(yp, 10);
  148.   radius = parseInt(radius, 10);
  149.   let balance = -radius,
  150.     xoff = 0,
  151.     yoff = radius;
  152.  
  153.   while (xoff <= yoff) {
  154.     const p0 = xp - xoff;
  155.     const p1 = xp + xoff;
  156.  
  157.     const p2 = yp + yoff;
  158.     const p3 = yp - yoff;
  159.     const p4 = yp + xoff;
  160.     const p5 = xp + yoff;
  161.     const p6 = xp - yoff;
  162.     const p7 = yp - xoff;
  163.  
  164.     if (isFilled) {
  165.       const w0 = xoff + xoff;
  166.       const w1 = yoff + yoff;
  167.  
  168.       hLine
  169.         (p0, yp + yoff, w0, sym)
  170.         (p0, yp - yoff, w0, sym)
  171.         (p6, yp + xoff, w1, sym)
  172.         (p6, yp - xoff, w1, sym);
  173.  
  174.     } else {
  175.       setSym
  176.         (p1, p2, sym)
  177.         (p0, p2, sym)
  178.         (p0, p3, sym)
  179.         (p1, p3, sym)
  180.         (p5, p4, sym)
  181.         (p6, p4, sym)
  182.         (p6, p7, sym)
  183.         (p5, p7, sym);
  184.     }
  185.  
  186.     // never been able to find the original 
  187.     // source for the below condition 
  188.     // more info here: https://actionsnippet.com/?p=492
  189.     if ((balance += xoff++ + xoff) >= 0) {
  190.       balance -= --yoff + yoff;
  191.     }
  192.   }
  193. }

This is a bit of a longer snippet that uses the Bresenham circle drawing algorithm to draw some circles with text. I recommend looking at it with the fullscreen button.

I like to do this:

  1. hLine
  2.   (p0, yp + yoff, w0, sym)
  3.   (p0, yp - yoff, w0, sym)
  4.   (p6, yp + xoff, w1, sym)
  5.   (p6, yp - xoff, w1, sym);

Make a function return itself, so that if you need to call it many times without having to repeat the function name. Because this isn’t a common style it is generally frowned upon, I’m always tempted to use it at work for some reason… maybe next April fools.

30% Chance Math.random()

  1. // 30%
  2. if (Math.random() < .3) {
  3.   // 30% chance this will run
  4.   console.log('30%');
  5. } else {
  6.   console.log('nope');
  7. }

I do this all the time when creative coding. I usually use a seeded random number generator instead of Math.random. It’s very useful to have something happen X% of the time.

Let’s run this a few times and look at the results. Click/tap try it out.

  1. for (let i = 0; i < 111; i++) {
  2.   if (Math.random() < .3) {
  3.     console.log('30%');
  4.   } else {
  5.     console.log('nope');
  6.   }
  7. }

Scroll through the results of the console window.

Cistercian Numerals (semi-golfed)

  1. N = -1
  2. S = 30
  3. H = 90
  4. h = 45
  5. d = document
  6. b = d.body
  7. m = _ => d.createElement(_)
  8. a = _ => b.appendChild(_)
  9.  
  10. with(a(
  11.     Object.assign(
  12.       m`canvas`,
  13.       { width: 80, height: 120 }
  14.     )
  15.   ).getContext`2d`
  16. ) { 
  17.   a(m`br`)
  18.  
  19.   L = (a, b, C, d) => {
  20.     moveTo(a, b)
  21.     lineTo(C, d)
  22.   }
  23.  
  24.   M = _ => L(0, 0, 0, H)
  25.   O = _ => L(0, 0, S, 0)
  26.   W = _ => L(0, S, S, S)
  27.   T = _ => L(0, 0, S, S)
  28.   F = _ => L(0, S, S, 0)
  29.   X = _ => L(S, 0, S, S)
  30.  
  31.   D = [
  32.     _ => {},
  33.     O, W, T, F, 
  34.     [O,F],
  35.     X,
  36.     [O,X],
  37.     [W,X],
  38.     [O,W,X]
  39.   ]
  40.  
  41.   n = s => {
  42.     [...s].reverse().map((l, i) => {
  43.       save()
  44.       translate(S+9, h+9)
  45.       scale(N**i, N**~~(i/2))
  46.       translate(0, -h)
  47.       M() ;[D[l]].flat().map(x => x && x())
  48.       restore()
  49.     })
  50.   }
  51.  
  52.   a(m`input`).oninput = e => {
  53.     clearRect(0, 0, 80, 120)
  54.     beginPath()
  55.     n(~~e.target.value+'')
  56.     stroke()
  57.   }
  58. }

Type any number from 0-9999 into the input field and see the corresponding Cistercian Numeral. This snippet is partially golfed, I left the canvas commands intact to make things a bit easier to understand.

Simpler SVG Version

This one hardcodes all numbers 0-9 as paths, unlike the canvas version which only defines 1,2,3,4 and 6 as paths and then combines them to create 5,7,8 and 9.

  1. h='innerHTML'
  2. C='children'
  3.  
  4. document.body[h]=`
  5. <svg id=G width=99 viewBox="0 0 80 120" 
  6.   style="stroke:black;fill:none;overflow:visible">
  7.   <g transform="translate(30,2)">
  8.     <path d="M0 0L 30 0"/>
  9.     <path d="M0 30L 30 30"/>
  10.     <path d="M0 0L 30 30"/>
  11.     <path d="M0 30L30 0"/>
  12.     <path d="M0 30L30 0 0 0"/>
  13.     <path d="M30 0L30 30"/>
  14.     <path d="M30 30L30 0 0 0"/>
  15.     <path d="M0 30L30 30 30 0"/>
  16.     <path d="M0 30L30 30 30 0 0 0"/>
  17.   </g>
  18.   <g transform=translate(30,2)scale(-1,1)></g>
  19.   <g transform=translate(30,92)scale(1,-1)></g>
  20.   <g transform=translate(30,92)scale(-1,-1)></g>
  21.   <path id=m d="M 30 2 L 30 92"/>
  22. </svg>
  23. <style>path:not(#m){opacity:0}</style><br>
  24. <input id=I>`
  25.  
  26. c=G[C]
  27. p=c[0][h] 
  28.  
  29. n = s => 
  30.   [...s].reverse().map((l, i) => 
  31.    l-1>-1 && (c[i][C][l-1].style.opacity=1))
  32.  
  33. I.oninput = e => {
  34.   for(i=0;i<4;i++)c[i][h]=p
  35.   n(~~e.target.value+'')
  36. }
snippet.zone ~ 2021-24 /// {s/z}