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