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: '' + '' + '', 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); } });