Skip to content

Instantly share code, notes, and snippets.

@tonmcg
Last active May 8, 2019 20:52
Show Gist options
  • Save tonmcg/c01705078cdf4cd907393b2e91f2b1d8 to your computer and use it in GitHub Desktop.
Save tonmcg/c01705078cdf4cd907393b2e91f2b1d8 to your computer and use it in GitHub Desktop.
SVG Transitions using Vue.js + GSAP
<!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>
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
};
});
}
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);
}
}
});
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