diff options
Diffstat (limited to 'lib/components/wave_renderer.js')
| -rw-r--r-- | lib/components/wave_renderer.js | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/lib/components/wave_renderer.js b/lib/components/wave_renderer.js new file mode 100644 index 0000000..022f9ff --- /dev/null +++ b/lib/components/wave_renderer.js @@ -0,0 +1,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); + } +}); |