Published
Edited
Mar 21, 2021
Insert cell
md`# SVG 2 Canvas`
Insert cell
commandMapSize = ({
M: 2,
L: 2,
Q: 4,
T: 2,
H: 1, // x
V: 1, // y
C: 6,
S: 4,
A: 7, // 曲线
Z: -1
})
Insert cell
numnicReg = /[0-9.\-]{1,}/g
Insert cell
commandNumnicReg = /[MLQTHVCSA]|[0-9.\s\,\-]*/g
Insert cell
function convertstandardCommand(pathStr) {
if(typeof pathStr !== 'string') return
let path = pathStr.trim()
let commndCorssNumnic = path.match(commandNumnicReg)
let len = commndCorssNumnic.length
let allCommand = []
for(let i = 1; i < len; i += 2) {
if(!commndCorssNumnic[i]) continue
commndCorssNumnic[i] = commndCorssNumnic[i].match(numnicReg)
let cmd = commndCorssNumnic[i-1]
allCommand = allCommand.concat(sliceSection(cmd, commandMapSize[cmd], commndCorssNumnic[i] || []))
}
return allCommand
}
Insert cell
function sliceSection(cmd, size, coords) {
let total = coords.length
if (total % size !== 0) {
console.log('error', cmd, total, size, coords)
throw 'coords length is not size multiple'
}
let len = total / size
let newCommand = []
for (let i = 0; i < len; i++) {
newCommand.push({cmd: cmd, coords: coords.slice(i * size, (i + 1) * size)})
}
return newCommand
}
Insert cell
function abasolute2relative(path) {
let commands = convertstandardCommand(path)
let tail = commands.length - 1
let prev_x = 0
let prev_y = 0
let last, prev
while (tail > 0) {
last = commands[tail]
prev = commands[tail - 1]
prev_x = prev.coords[prev.coords.length - 2]
prev_y = prev.coords[prev.coords.length - 1]
switch(last.cmd) {
case 'H':
last.coords[0] = last.coords[0] - prev_x;
break;
case 'V':
last.coords[0] = last.coords[0] - prev_y;
break;
case 'A':
last.coords[5] = last.coords[5] - prev_x;
last.coords[6] = last.coords[6] - prev_y;
break;
case 'M':
case 'L':
case 'Q':
case 'T':
case 'C':
case 'S':
last.coords.forEach((coord, index) =>{
if(index % 2 === 0) {
last.coords[index] = coord - prev_x
}

if(index % 2 === 1) {
last.coords[index] = coord - prev_y
}
})
}
last.cmd = last.cmd.toLowerCase();
tail--;
}
commands[0].cmd = 'm'
return commands
}
Insert cell
function abasolute2CanvasRenderContextText(path, funcName, noFunction) {
let commands = convertstandardCommand(path)
let len = commands.length
let i = 0
let prevCmd
let contextText = noFunction ? '' : `var ${funcName ? funcName : 'draw'} = function(ctx) {\n`
while (i < len) {
let curCmd = commands[i]
switch(curCmd.cmd) {
case 'H':
prevCmd = commands[i - 1]
let _y = prevCmd ? prevCmd.coords.slice(-2, -1) : 0
contextText += ` ctx.lineTo(${curCmd.coords[0]}, ${_y});\n`
break;
case 'V':
prevCmd = commands[i - 1]
let _x = prevCmd ? prevCmd.coords.slice(-1) : 0
contextText += ` ctx.lineTo(${_x}, ${curCmd.coords[0]});\n`
break;
case 'M':
contextText += ` ctx.moveTo(${curCmd.coords.join(', ')});\n`
break;
case 'L':
contextText += ` ctx.lineTo(${curCmd.coords.join(', ')});\n`
break;
case 'Q':
contextText += ` ctx.quadraticCurveTo(${curCmd.coords.join(', ')});\n`
break;
case 'T':
prevCmd = commands[i - 1]
if (prevCmd && (prevCmd.cmd === 'T' || prevCmd.cmd === 'Q')) {
let xy = prevCmd.coords.slice(-2)
let dxdy = prevCmd.coords.slice(-4,-2)
let _dx1 = xy[0] * 2 - dxdy[0]
let _dy1 = xy[1] * 2 - dxdy[1]
curCmd.cmd = 'Q'
curCmd.coords.unshift(_dx1, _dy1)
contextText += ` ctx.quadraticCurveTo(${curCmd.coords.join(', ')});\n`
} else {
throw new Error('ShortHead quadraticCurve is Error')
}
break;
case 'C':
contextText += ` ctx.bezierCurveTo(${curCmd.coords.join(', ')});\n`
break;
case 'S':
prevCmd = commands[i - 1]
if (prevCmd && (prevCmd.cmd === 'S' || prevCmd.cmd === 'C')) {
let xy = prevCmd.coords.slice(-2)
let dxdy = prevCmd.coords.slice(-4,-2)
let _dx1 = xy[0] * 2 - dxdy[0]
let _dy1 = xy[1] * 2 - dxdy[1]
curCmd.cmd = 'C'
curCmd.coords.unshift(_dx1, _dy1)
contextText += ` ctx.bezierCurveTo(${curCmd.coords.join(', ')});\n`
} else {
throw new Error('ShortHead bezierCurve is Error')
}
break;
case 'A':
prevCmd = commands[i - 1]
// console.log('curCmd', curCmd)
let xy = prevCmd ? prevCmd.coords.slice(-2) : [0, 0]
let rx = +curCmd.coords[0]
let ry = +curCmd.coords[1]
let xAxisRotationDeg = +curCmd.coords[2]
let largeArcFlag = +curCmd.coords[3]
let sweepFlag = +curCmd.coords[4]
let x1 = +curCmd.coords[5]
let y1 = +curCmd.coords[6]
console.log(xy[0], xy[1], x1, y1, rx, ry, xAxisRotationDeg, largeArcFlag, sweepFlag)
let ellipseParams = conversionEndPointToCenterPointParameterization(
+xy[0], +xy[1], x1, y1, largeArcFlag, sweepFlag, rx, ry, xAxisRotationDeg)
console.log(ellipseParams)
contextText += ` ctx.ellipse(${ellipseParams.join(', ')});\n`
break;
}
i++;
}
contextText += noFunction ? '' : '}\n'
return contextText
}
Insert cell
sword = 'M 4 8 L 10 1 L 13 0 L 12 3 L 5 9 C 6 10 6 11 7 10 C 7 11 8 12 7 12 A 1.42 1.42 0 0 1 6 13 A 5 5 0 0 0 4 10 Q 3.5 9.9 3.5 10.5 T 2 11.8 T 1.2 11 T 2.5 9.5 T 3 9 A 5 5 90 0 0 0 7 A 1.42 1.42 0 0 1 1 6 C 1 5 2 6 3 6 C 2 7 3 7 4 8 M 10 1 L 10 3 L 12 3 L 10.2 2.8 L 10 1'

Insert cell
draw = new Function('ctx', abasolute2CanvasRenderContextText(sword, '', true))
Insert cell
Type JavaScript, then Shift-Enter. Ctrl-space for more options. Arrow ↑/↓ to switch modes.

Insert cell
{
var ctx = DOM.context2d(600, 600)
ctx.beginPath();
ctx.fillStyle = 'orange'
ctx.translate(100, 100)
ctx.scale(10, 10)
draw(ctx)
ctx.fill()
return ctx.canvas
}
Insert cell
import { conversionEndPointToCenterPointParameterization } from '@musixnotmusic/svg-ellipticalarc'
Insert cell
import { endpointToCenterParameterization } from '@awhitty/svg-2-elliptical-arc-to-canvas-path2d'
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more