S G
Paweł Grabarz
Recap:
<html>
<svg></svg>
</html>
Recap:
<svg width="300" height="300">
<circle r="100" cx="150" cy="150"/>
</svg>
Recap:
<svg width="300" height="300">
<circle r="100" cx="150" cy="150"/>
</svg>
circle {
fill: $rainbow;
}
Vue
new Vue({
el: '#app',
template: `
<svg width="300" height="300">
<circle r="100" cx="150" cy="150"/>
</svg>
`
})
Bindings
<div id="app">
<div>
radius: <input type="range" v-model="r" min="10" max="150"/>
</div>
<svg width="300" height="300">
<circle :r="r" cx="150" cy="150"/>
</svg>
</div>
new Vue({
el: '#app',
data: { r: 25 }
})
React
const App = () => (
<svg width="300" height="300">
<circle r="100" cx="150" cy="150"/>
</svg>
)
ReactDOM.render(<App/>, document.querySelector('#app'))
Pick your poison
Data driven SVG
D3
=
Layout
Utilities
DOM Mapping
Lots of modules
export {version} from "./build/package";
export * from "d3-array";
export * from "d3-axis";
export * from "d3-brush";
export * from "d3-chord";
export * from "d3-collection";
export * from "d3-color";
export * from "d3-dispatch";
export * from "d3-drag";
export * from "d3-dsv";
export * from "d3-ease";
export * from "d3-force";
export * from "d3-format";
export * from "d3-geo";
export * from "d3-hierarchy";
export * from "d3-interpolate";
export * from "d3-path";
export * from "d3-polygon";
export * from "d3-quadtree";
export * from "d3-queue";
export * from "d3-random";
export * from "d3-request";
export * from "d3-scale";
export * from "d3-selection";
export * from "d3-shape";
export * from "d3-time";
export * from "d3-time-format";
export * from "d3-timer";
export * from "d3-transition";
export * from "d3-voronoi";
export * from "d3-zoom";
computed: {
chartPath() {
return generateCurve(
this.chartWidth,
this.chartHeight,
this.dataRangeX,
this.dataRangeY,
this.data);
}
}
<path :d="chartPath" />
Using D3 modules
Using D3 modules
import { curveMonotoneX } from "d3-shape";
export function generateCurve(width, height, rangeX, rangeY, data) {
const scaleY = height / rangeY;
const segments = [];
const curve = curveMonotoneX({
moveTo(x, y) {
segments.push(`M${x},${y}`);
},
closePath() {},
lineTo(x, y) {
segments.push(`L${x},${y}`);
},
bezierCurveTo(x1, y1, x2, y2, x, y) {
segments.push(`C${x1},${y1} ${x2},${y2} ${x},${y}`);
},
});
curve.areaStart();
curve.lineStart();
curve.point(-50, 0);
data.forEach((point) => {
const x = dataToOffset(rangeX, width, 0, point.x);
const y = point.y * scaleY;
curve.point(x, y);
});
curve.point(width + 50, 0);
curve.lineEnd();
curve.areaEnd();
return segments.join(" ");
}
teach D3 to
generate SVG path
Feed it with data
import necessary module
Data driven SVG
Layout
Utilities
DOM Mapping
Reactive by default
D3 Parts
Custom JS
}
D3 to Vue
var dataSet = [10, 20, 30];
d3.select('svg').selectAll()
.data(dataSet)
.enter()
.append('circle')
.attr({
r: d => d,
cx: (d, i) => i * 100 + 50,
cy: 50,
fill: '#9b59b6'
});
<svg></svg>
D3 to Vue
<svg>
<circle
v-for="(d, i) in dataSet"
:r="d"
:cx="i * 100 + 50"
cy="50"
fill="#9b59b6"
/>
</svg>
new Vue({
el: 'svg',
data: { dataSet: [10, 20, 30] }
})
d3.select('svg').selectAll()
.data(dataSet)
.enter()
.append('circle')
.attr({
r: d => d,
cx: (d, i) => i * 100 + 50,
cy: 50,
fill: '#9b59b6'
});
<circle
v-for="(d, i) in dataSet"
:r="d"
:cx="i * 100 + 50"
cy="50"
fill="#9b59b6"
/>
Animations
data: { numbers: [] },
mounted () {
this.randomize()
setInterval(this.randomize, 1000)
},
methods: {
randomize () {
this.numbers =
Array.apply(null, Array(10))
.map(() => 0.1 + 0.9 * Math.random())
}
}
Every second...
...change the data
<g v-for="(num, i) in numbers"
:style="barTransform(num, i)">
<rect x="0" y="0" width="40" :height="num * 300"/>
<text :x="20" :y="20">{{ ~~(num * 100) }}</text>
</g>
For every data point:
- draw a bar and label
- and setup it's size based on data value
barTransform(num, i) {
const x = 15 + i * 50
const y = 300 - num * 300
return `transform: translate(${x}px, ${y}px)`
}
Custom behaviours
- very easy to add in Vue's computed
horizontalTicks() {
if (window.innerWidth < 640) {
return this.generatedTicks.filter(
(n, index) => (index % 2 === 0));
}
return this.generatedTicks;
},
Draw less ticks
for small screens
And hey, it's SVG! We can do @events with that too!
<svg>
<path
:d="myCalculatedPath"
@click="sendMissionToMars"
/>
<svg>
Serious stuff...
How about...
...running a 60fps animation
interactive
Just a bunch of circles
<circle v-for="(c, i) in circles"
:r="(1 - i / 60) * 0.07" :cx="c.x" :cy="c.y" />
followMouse() {
let x = this.mouseX
let y = this.mouseY
const circles = this.circles
const force = this.force
for (let i = 0; i < circles.length; i++) {
const circle = circles[i]
x = circle.x = lerp(force, x, circle.x)
y = circle.y = lerp(force, y, circle.y)
}
}
Following mouse and each other
Every frame...
Gather
input
Simulate
Display
Simulation loop
Gather input
mounted () {
document.addEventListener('mousemove', this.capturePos)
},
methods: {
capturePos ($ev) {
const size = Math.min(innerWidth, innerHeight)
const offsetX = (size - innerWidth) / 2
const offsetY = (size - innerHeight) / 2
this.mouseX = (offsetX + $ev.clientX) / size
this.mouseY = (offsetY + $ev.clientY) / size
}
}
Bind some events
Protip: @event also work well for local stuff
Math voodoo to convert event position to uniform scale-independent svg space
<svg viewBox="0 0 1 1">
svg {
position: absolute;
width: 100%;
height: 100%;
}
Simulate
60FPS?
observed data update => render
requestAnimationFrame(render)
requestAnimationFrame(mutateData)
Simulate
const timestep = 1000 / 120
let accumulator = 0
let lastTime = performance.now()
const tick = (t) => {
accumulator += t - lastTime
lastTime = t
if(accumulator > 250)
accumulator = 0
while(accumulator > timestep) {
this.followMouse()
accumulator -= timestep
}
requestAnimationFrame(tick)
}
requestAnimationFrame(tick)
Every frame:
calculate new state
Keep track of real time
In fixed timesteps
until we kept up with real time
schedule next frame
escape the spiral of death
(ignore too much accumulated time)
Display?
followMouse() {
let x = this.mouseX
let y = this.mouseY
const circles = this.circles
const force = this.force
for (let i = 0; i < circles.length; i++) {
const circle = circles[i]
x = circle.x = lerp(force, x, circle.x)
y = circle.y = lerp(force, y, circle.y)
}
}
reactive!
<circle v-for="(c, i) in circles"
:r="(1 - i / 60) * 0.07" :cx="c.x" :cy="c.y" />
Simulation loop
Gather
input
Simulate
Display
Is it fast enough?
if you stick to vector:
SVG > canvas
Physics?
Is it fast enough for...
Is it fast like...
more than enough?
Thank You!
SVG in Vue.js
By Pawel Grabarz
SVG in Vue.js
- 2,648