1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
import Vue from 'vue';
import DataService from '../services/data';
const internals = {
// Constants
kColors: {
donatello: 'purple',
leonardo: 'blue',
michaelangelo: 'orange',
raphael: 'red'
},
kFrequency: 100,
kGradientFade: 0.1,
kMinValue: 25,
kMaxValue: 100,
kRandomJitter: 10,
kTargetFPS: 60,
// Utility functions
scale(value, min, max) {
return ((value - min) / (max - min)) * (internals.kMaxValue - internals.kMinValue) - internals.kMinValue;
}
};
export default Vue.component('posts', {
template: '<transition name="fade">' +
'<canvas v-show="state === 1" class="wave-renderer"></canvas>' +
'</transition>',
data: DataService.data,
computed: {
// Convert from the min / max value in the measurement to a
// predefined min value between 0 and 100 (see kMinValue and
// kMaxValue for actual values)
scaledAverages() {
const keys = Object.keys(this.runningAverages);
const averages = keys.reduce((averages, averageCategory) => {
const runningAverage = this.runningAverages[averageCategory];
averages[averageCategory] = runningAverage.average;
return averages;
}, {});
const max = Math.max(...Object.values(averages));
const min = Math.min(...Object.values(averages));
const scaledAverages = Object.keys(averages).reduce((scaledAverages, averageCategory) => {
const value = averages[averageCategory];
scaledAverages[averageCategory] = internals.scale(value, min, max);
return scaledAverages;
}, {});
return scaledAverages;
}
},
methods: {
onResize() {
this.$el.width = window.innerWidth;
this.$el.height = window.innerHeight;
}
},
// Initiates the animation loop
mounted() {
// Make sure we resize, do an initial sizing
window.addEventListener('resize', this.onResize.bind(this));
this.onResize();
// Start the whole animation (Sorry it's a mess)
const canvas = this.$el;
const context = canvas.getContext('2d');
const interval = 1000 / internals.kTargetFPS;
let lastTimestamp = 0;
const animationHandler = (timestamp) => {
window.requestAnimationFrame(animationHandler);
const delta = timestamp - lastTimestamp;
if (delta > interval) {
const keys = Object.keys(this.scaledAverages);
const values = Object.values(this.scaledAverages);
const segments = keys.length;
const period = canvas.width / segments;
const fillGradient = context.createLinearGradient(0, 0, canvas.width, 0);
const gradientBandWidth = 1 / segments;
// Position the drawing cursor left-center of screen
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
context.moveTo(0, canvas.height);
context.lineTo(0, canvas.height / 2);
// Iterate over the segments
for (let currentSegment = 0; currentSegment < segments; ++currentSegment) {
const segmentStart = currentSegment * period;
const segmentEnd = currentSegment + period;
const category = keys[currentSegment];
const magnitude = values[currentSegment];
const segmentHeight = Math.round(Math.random() * internals.kRandomJitter + magnitude * (canvas.height / 2) / 100);
// Calculate the gradient using the correct color according to
// scale
const color = internals.kColors[category] || 'black';
let currentGradientPosition = currentSegment * gradientBandWidth + internals.kGradientFade;
fillGradient.addColorStop(currentGradientPosition, color);
currentGradientPosition = (currentSegment + 1) * gradientBandWidth - internals.kGradientFade;
fillGradient.addColorStop(currentGradientPosition, color);
// This draws the sine wave
for (let angle = 0; angle < 180; ++angle) {
const currentPixel = segmentStart + angle * period / 180;
const currentAngle = angle + 180 * (currentSegment % 2);
const currentRadians = currentAngle * Math.PI / 180;
const currentHeight = segmentHeight * Math.sin(internals.kFrequency * currentRadians);
context.lineTo(currentPixel, currentHeight + canvas.height / 2);
}
}
context.lineTo(canvas.width, canvas.height / 2);
context.lineTo(canvas.width, canvas.height);
context.fillStyle = fillGradient;
context.fill();
lastTimestamp = timestamp;
}
};
window.requestAnimationFrame(animationHandler);
}
});
|