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