This example uses the list entering/leaving and state transition systems of Vue.js, along with Greensock's GSAP to transition properties of SVG circles.
Last active
May 8, 2019 20:52
-
-
Save tonmcg/c01705078cdf4cd907393b2e91f2b1d8 to your computer and use it in GitHub Desktop.
SVG Transitions using Vue.js + GSAP
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |
<meta name="viewport" content="width=device-width,initial-scale=1.0"> | |
<title>Scales with Vue + GASP</title> | |
<style type="text/css" media="screen"> | |
body { | |
font-family: "Courier New", Georgia; | |
background: #f4f4f4; | |
} | |
h1 { | |
font-size: 3em; | |
padding: 0px; | |
font-weight: normal; | |
text-align: center; | |
text-transform: uppercase; | |
} | |
.lede { | |
font-style: italic; | |
font-weight: 400; | |
font-size: .9em; | |
font-size: 14px; | |
line-height: 1.4em; | |
text-align: center; | |
} | |
label { | |
display: inline-block; | |
margin-left: 10px; | |
width: 450px; | |
} | |
input { | |
width: 84px; | |
} | |
.control { | |
height: 30px; | |
} | |
</style> | |
</head> | |
<body> | |
<!-- x-axis template --> | |
<script type="text/x-template" id="svg-xaxis"> | |
</script> | |
<!-- Vue render --> | |
<div id="chart"> | |
<h1 class="g-header centered">Scales with Vue + GASP</h1> | |
<p class="lede centered" style="margin-bottom:0rem">Tweening D3 Scales with Vue.js & GASP</p> | |
<svg :width="chart.width" :height="chart.height"> | |
<g | |
:transform="'translate(0,' + (chart.height - margins.bottom) + ')'" | |
class="x axis" | |
fill="none" | |
font-size="10" | |
font-family="sans-serif" | |
text-anchor="middle" | |
ref='xAxis'> | |
<path | |
class="domain" | |
stroke="#000" | |
:d="'M' + range0 + ',' + k * tickSizeOuter + 'V0.5H' + range1 + 'V' + k * tickSizeOuter" | |
></path> | |
<g | |
v-for="(tick, index) in ticks" | |
v-bind:key="'g_' + index" | |
class="tick" | |
:transform="transformX(tick.x)" | |
> | |
<line | |
stroke="#000" | |
:y2="k * tickSizeInner" | |
></line> | |
<text | |
fill="#000" | |
:y="k * spacing" | |
dy="0.71em" | |
>{{ Math.round(tick.x) }}</text> | |
</g> | |
</g> | |
</svg> | |
<br/> | |
<label>Transition Interval (in milliseconds)</label> | |
<br/> | |
<input type="range" min="1000" max="5000" v-model.number="updateInterval"> | |
<input type="text" v-model.number="updateInterval"> | |
</div> | |
<script src="https://unpkg.com/vue"></script> | |
<script src="https://d3js.org/d3-array.v2.min.js"></script> | |
<script src="https://d3js.org/d3-interpolate.v1.min.js"></script> | |
<script src="https://d3js.org/d3-scale.v3.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.2/TweenMax.min.js"></script> | |
<!-- Vue instance --> | |
<script src="main.js"></script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
new Vue({ | |
el: '#chart', | |
data: function () { | |
var value = 10; | |
var elements = d3.range(0, value, 1).map(d => d); | |
return { | |
elements: elements, | |
ticks: generateTicks(elements), | |
scale: null, | |
chart: { | |
width: window.innerWidth, | |
height: 180 | |
}, | |
margins: { | |
top: 10, | |
right: 20, | |
bottom: 20, | |
left: 40 | |
}, | |
tickSizeInner: 6, | |
tickSizeOuter: 6, | |
tickPadding: 3, | |
k: 1, | |
interval: null, | |
updateInterval: 2000 | |
}; | |
}, | |
watch: { | |
updateInterval: function () { | |
this.resetInterval(); | |
}, | |
elements: function (newElms, oldElms) { | |
// a la Sara Drasner | |
// https://codepen.io/sdras/pen/OWZRZL | |
var vm = this; | |
// Create a dummy object that will get updated by GSAP | |
var tweenedData = {}; | |
// Update function that is invoked on each tween step | |
// we use vm to push the data | |
var update = function () { | |
let obj = Object.values(tweenedData); | |
obj.pop(); | |
vm.ticks = generateTicks(obj); | |
}; | |
// Create an object to hold the source data to be tweened and the | |
// function pointer for update events | |
var tweenSourceData = { | |
onUpdate: update, | |
onUpdateScope: vm | |
}; | |
for (let i = 0; i < oldElms.length; i++) { | |
// Turn the current index into a string | |
let key = i.toString(); | |
tweenedData[key] = oldElms[i]; | |
tweenSourceData[key] = newElms[i]; | |
} | |
// Tween over the our target dummy object, but only for the specific key | |
TweenMax.to(tweenedData, this.updateInterval / 1000, tweenSourceData); | |
} | |
}, | |
created: function () { | |
this.calculateScale(); | |
}, | |
mounted: function () { | |
this.resetInterval(); | |
}, | |
methods: { | |
calculateScale: function () { | |
// linear x scale | |
var [minXValue, maxXValue] = d3.extent(this.elements, d => d); | |
var x = d3.scaleLinear(); | |
var xScale = x.domain([minXValue, maxXValue]).range([this.margins.left, this.chart.width - this.margins.right]); | |
this.scale = xScale; | |
}, | |
randomizeRange: function () { | |
var value = Math.ceil(Math.random() * 20); | |
this.elements = d3.range(value, value + 10, 1).map(d => d); | |
}, | |
resetInterval: function () { | |
var vm = this; | |
clearInterval(this.interval); | |
this.randomizeRange(); | |
this.calculateScale(); | |
this.interval = setInterval(function () { | |
vm.randomizeRange(); | |
vm.calculateScale(); | |
}, this.updateInterval); | |
}, | |
transformX: function (x) { | |
return "translate(" + (this.scale(x) + 0.5) + ",0)"; | |
} | |
}, | |
computed: { | |
range: function () { | |
return this.scale.range(); | |
}, | |
range0: function () { | |
return this.range[0] + 0.5; | |
}, | |
range1: function () { | |
return this.range[this.range.length - 1] + 0.5; | |
}, | |
spacing: function () { | |
return Math.max(this.tickSizeInner, 0) + this.tickPadding; | |
} | |
} | |
}); | |
function generateTicks(elements) { | |
return elements.map(function (element) { | |
return { | |
x: element | |
}; | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Vue.component('svg-xaxis', { | |
template: '#svg-xaxis', | |
props: ['els','scales','margin'], | |
watch: { | |
els: function() { | |
this.renderXAxis(); | |
} | |
}, | |
methods: { | |
renderXAxis: function() { | |
const xAxis = d3.axisBottom().scale(this.scales.xScale).tickFormat(this.format); | |
d3.select(this.$refs.xAxis).attr('transform',"translate(0, " + this.margin + ")").call(xAxis); | |
} | |
} | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Vue.component('svg-yaxis', { | |
template: '#svg-yaxis', | |
props: ['els','scales','margins'], | |
watch: { | |
els: function() { | |
this.renderYAxis(); | |
} | |
}, | |
methods: { | |
renderYAxis: function() { | |
const yAxis = d3.axisLeft().scale(this.scales.yScale).tickFormat(this.format); | |
d3.select(this.$refs.yAxis).attr("transform", "translate(" + this.margins.left + ",0)").call(yAxis); | |
} | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment