Skip to content

Commit ff2a03e

Browse files
committed
Add animations and percentage display
1 parent 3546842 commit ff2a03e

File tree

4 files changed

+160
-5
lines changed

4 files changed

+160
-5
lines changed

package-lock.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
{
22
"name": "funnel-vue",
3-
"version": "0.1.0",
3+
"version": "0.1.1",
44
"private": true,
55
"scripts": {
66
"serve": "vue-cli-service serve",
77
"build": "vue-cli-service build",
8+
"build-bundle": "vue-cli-service build --target lib --name FunnelGraph ./src/components/FunnelGraph.vue",
9+
"build-wc": "vue-cli-service build --target wc --name funnel-graph ./src/components/FunnelGraph.vue",
810
"lint": "vue-cli-service lint"
911
},
1012
"dependencies": {
13+
"@tweenjs/tween.js": "^17.3.0",
1114
"funnel-graph-js": "^1.3.4",
15+
"polymorph-js": "^0.2.4",
1216
"vue": "^2.6.6"
1317
},
1418
"devDependencies": {

src/App.vue

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,17 @@
44
<funnel-graph :width="width" :height="height" :labels="labels"
55
:values="values" :colors="colors" :subLabels="subLabels" :direction="direction"
66
:gradient-direction="gradientDirection"
7+
:animated="true" :display-percentage="true"
78
></funnel-graph>
89
</div>
910
<div class="fields">
1011
<button @click="makeData1">Make One Dim</button>
12+
<button @click="makeData3">Make One Dim 3</button>
13+
<button @click="makeData4">Make One Dim 4</button>
1114
<button @click="makeData2">Make Two Dim</button>
15+
<button @click="makeData5">Make Two Dim 5</button>
16+
<button @click="makeData6">Make Two Dim 6</button>
17+
<button @click="makeData7">Make Two Dim 7</button>
1218
<button @click="(direction === 'horizontal') ? makeVertical() : makeHorizontal()">Rotate</button>
1319
<button @click="(gradientDirection === 'horizontal') ? gradientV() : gradientH()">Rotate Gradient</button>
1420
</div>
@@ -56,6 +62,60 @@ export default {
5662
['#A0F9FF', '#7795FF']
5763
];
5864
},
65+
makeData3() {
66+
this.values = [12000, 8700, 3330];
67+
this.colors = ['#FFB178', '#FF3C8E'];
68+
},
69+
makeData4() {
70+
this.labels = ['Impressions', 'Add To Cart', 'Buy', 'More'];
71+
this.colors = ['#FFB178', '#FF3C8E'];
72+
this.values = [12000, 8700, 3330, 400];
73+
},
74+
makeData5() {
75+
this.labels = ['Impressions', 'Add To Cart', 'Buy'];
76+
this.subLabels = ['Direct', 'Social Media', 'Ads'];
77+
this.values = [
78+
[3000, 2500, 6500, 3000],
79+
[3000, 1700, 1000, 500],
80+
[600, 200, 130, 75]
81+
];
82+
this.colors = [
83+
['#FFB178', '#FF78B1', '#FF3C8E'],
84+
['#A0BBFF', '#EC77FF'],
85+
['#A0F9FF', '#7795FF']
86+
];
87+
},
88+
makeData6() {
89+
this.labels = ['Impressions', 'Add To Cart', 'Buy', 'Review'];
90+
this.subLabels = ['Direct', 'Social Media', 'Ads', 'Other'];
91+
this.values = [
92+
[2000, 1500, 3500, 5000],
93+
[4000, 2700, 5000, 700],
94+
[700, 200, 130, 75]
95+
];
96+
this.colors = [
97+
['#FFB178', '#FF78B1', '#FF3C8E'],
98+
['#A0BBFF', '#EC77FF'],
99+
['#A0F9FF', '#7795FF'],
100+
'red'
101+
];
102+
},
103+
makeData7() {
104+
this.labels = ['Impressions', 'Add To Cart', 'Buy', 'Review'];
105+
this.subLabels = ['Direct', 'Social Media', 'Ads', 'Other'];
106+
this.values = [
107+
[2000, 1500, 3500, 5000],
108+
[4000, 2700, 5000, 700],
109+
[700, 200, 130, 75],
110+
[100, 70, 80, 25]
111+
];
112+
this.colors = [
113+
['#FFB178', '#FF78B1', '#FF3C8E'],
114+
['#A0BBFF', '#EC77FF'],
115+
['#A0F9FF', '#7795FF'],
116+
'red'
117+
];
118+
},
59119
makeVertical() {
60120
this.direction = 'vertical';
61121
this.height = 500;

src/components/FunnelGraph.vue

Lines changed: 85 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
>
2727
<div class="label__value">{{ value }}</div>
2828
<div class="label__title" v-if="labels">{{ labels[index] }}</div>
29+
<div class="label__percentage" v-if="displayPercentage && percentages()[index] !== 100">
30+
{{ percentages()[index] }}%
31+
</div>
2932
<div class="label__segment-percentages" v-if="is2d()">
3033
<ul class="segment-percentage__list">
3134
<li v-for="(subLabel, j) in subLabels" :key="j">
@@ -40,6 +43,8 @@
4043
</template>
4144

4245
<script>
46+
import { interpolate } from 'polymorph-js';
47+
import TWEEN from '@tweenjs/tween.js';
4348
import FunnelGraph from 'funnel-graph-js';
4449
import { formatNumber } from 'funnel-graph-js/src/js/number';
4550
import { getDefaultColors } from 'funnel-graph-js/src/js/graph';
@@ -48,6 +53,10 @@ import 'funnel-graph-js/src/scss/main.scss';
4853
export default {
4954
name: 'FunnelGraph',
5055
props: {
56+
animated: {
57+
type: Boolean,
58+
default: false
59+
},
5160
width: [String, Number],
5261
height: [String, Number],
5362
values: Array,
@@ -64,13 +73,19 @@ export default {
6473
gradientDirection: {
6574
type: String,
6675
default: 'horizontal'
76+
},
77+
displayPercentage: {
78+
type: Boolean,
79+
default: true
6780
}
6881
},
6982
data() {
7083
return {
7184
paths: [],
85+
prevPaths: [], // paths before update, used for animations
7286
graph: null,
73-
valueSize: 0
87+
animationRunning: false,
88+
defaultColors: getDefaultColors(10)
7489
};
7590
},
7691
computed: {
@@ -84,8 +99,8 @@ export default {
8499
const colorSet = [];
85100
let gradientCount = 0;
86101
87-
for (let i = 0; i < this.valueSize; i++) {
88-
const values = (this.graph.is2d()) ? this.getColors[i] : this.getColors;
102+
for (let i = 0; i < this.paths.length; i++) {
103+
const values = this.graph.is2d() ? this.getColors[i] : this.getColors;
89104
const fillMode = (typeof values === 'string' || values.length === 1) ? 'solid' : 'gradient';
90105
if (fillMode === 'gradient') gradientCount += 1;
91106
colorSet.push({
@@ -109,6 +124,11 @@ export default {
109124
if (this.colors instanceof Array && this.colors.length === 0) {
110125
return getDefaultColors(this.is2d() ? this.values[0].length : 2);
111126
}
127+
if (this.colors.length < this.paths.length) {
128+
return [...this.colors].concat(
129+
[...this.defaultColors].splice(this.paths.length, this.paths.length - this.colors.length)
130+
);
131+
}
112132
return this.colors;
113133
},
114134
gradientAngle() {
@@ -119,6 +139,9 @@ export default {
119139
is2d() {
120140
return this.graph.is2d();
121141
},
142+
percentages() {
143+
return this.graph.createPercentages();
144+
},
122145
twoDimPercentages() {
123146
if (!this.is2d()) {
124147
return [];
@@ -128,10 +151,66 @@ export default {
128151
offsetColor(index, length) {
129152
return `${Math.round(100 * index / (length - 1))}%`;
130153
},
154+
makeAnimations() {
155+
const interpolators = [];
156+
const dimensionChanged = this.prevPaths.length !== this.paths.length;
157+
158+
let origin = { x: 0.5, y: 0.5 };
159+
if (dimensionChanged) {
160+
origin = { x: 0, y: 0.5 };
161+
if (this.graph.isVertical()) {
162+
origin = { x: 1, y: 1 };
163+
}
164+
if (!this.graph.is2d()) {
165+
origin = { x: 0, y: 1 };
166+
}
167+
}
168+
169+
this.paths.forEach((path, index) => {
170+
let oldPath = this.prevPaths[index] || this.graph.getPathMedian(index);
171+
if (dimensionChanged) oldPath = this.graph.getPathMedian(index);
172+
const interpolator = interpolate([oldPath, path], {
173+
addPoints: 1,
174+
origin,
175+
optimize: 'fill',
176+
precision: 1
177+
});
178+
179+
interpolators.push(interpolator);
180+
});
181+
182+
function animate() {
183+
if (TWEEN.update()) {
184+
requestAnimationFrame(animate);
185+
}
186+
}
187+
188+
const position = { value: 0 };
189+
const tween = new TWEEN.Tween(position)
190+
.to({ value: 1 }, 700)
191+
.easing(TWEEN.Easing.Cubic.InOut)
192+
.onStart(() => {
193+
this.animationRunning = true;
194+
})
195+
.onUpdate(() => {
196+
for (let index = 0; index < this.paths.length; index++) {
197+
this.paths[index] = interpolators[index](position.value);
198+
// eslint-disable-next-line no-underscore-dangle
199+
this.paths.__ob__.dep.notify();
200+
}
201+
})
202+
.onComplete(() => {
203+
this.animationRunning = false;
204+
});
205+
206+
if (this.animationRunning === false) tween.start();
207+
208+
animate();
209+
},
131210
drawPaths() {
211+
this.prevPaths = this.paths;
132212
this.paths = [];
133213
const definitions = this.graph.getPathDefinitions();
134-
this.valueSize = definitions.length;
135214
136215
definitions.forEach((d) => {
137216
this.paths.push(d);
@@ -148,11 +227,13 @@ export default {
148227
}
149228
});
150229
this.drawPaths();
230+
if (this.animated) this.makeAnimations();
151231
},
152232
watch: {
153233
values() {
154234
this.graph.setValues(this.values);
155235
this.drawPaths();
236+
if (this.animated) this.makeAnimations();
156237
},
157238
direction() {
158239
this.graph.setDirection(this.direction)

0 commit comments

Comments
 (0)