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
158
159
160
161
162
163
164
165
166
167
|
import Vue from 'vue';
import DataService from '../services/data';
/* global window */
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;
}
};
/**
* The wave renderer, draws some waves in a canvas to represent a set of
* cateogirzed averages
*
* @class WaveRenderer
*/
export default Vue.component('waveRenderer', {
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((averagesObject, averageCategory) => {
const runningAverage = this.runningAverages[averageCategory];
averagesObject[averageCategory] = runningAverage.average;
return averagesObject;
}, {});
const max = Math.max(...Object.values(averages));
const min = Math.min(...Object.values(averages));
const scaledAverages = Object.keys(averages).reduce((scaledAveragesObject, averageCategory) => {
const value = averages[averageCategory];
scaledAveragesObject[averageCategory] = internals.scale(value, min, max);
return scaledAveragesObject;
}, {});
return scaledAverages;
}
},
methods: {
// Reset the size of the canvas on resize
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 i = 0; i < segments; ++i) {
const segmentStart = i * period;
const category = keys[i];
const magnitude = values[i];
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 = i * gradientBandWidth + internals.kGradientFade;
fillGradient.addColorStop(currentGradientPosition, color);
currentGradientPosition = (i + 1) * gradientBandWidth - internals.kGradientFade;
fillGradient.addColorStop(currentGradientPosition, color);
// This draws the sine wave
for (let j = 0; j < 180; ++j) {
const currentPixel = segmentStart + j * period / 180;
const currentAngle = j + 180 * (i % 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);
}
});
|