├── .gitignore ├── docker └── webpack │ └── Dockerfile ├── docker-compose.yml.sample ├── src ├── avionics │ ├── printAltitudeCarriage.js │ ├── helpers.js │ ├── printSpeed.js │ ├── printAltitude.js │ ├── printHeading.js │ ├── printVerticalSpeed.js │ ├── printPitch.js │ ├── styles.css │ ├── template.html │ └── index.js ├── demo.ejs ├── demoKeys.ejs ├── SpecRunner.ejs ├── demoKeys.coffee └── demo.coffee ├── webpack.config.coffee ├── htmlWebpackPluginConfig.js ├── webpack.production.config.coffee ├── LICENSE ├── webpack.common.config.coffee ├── package.json ├── karma.conf.js ├── .travis.yml ├── spec └── all.js ├── README.md └── .eslintrc.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /node_modules/ 3 | .DS_Store 4 | docker-compose.yml 5 | /dist 6 | -------------------------------------------------------------------------------- /docker/webpack/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10.15-alpine 2 | 3 | #RUN apk add --no-cache --virtual .build-deps git 4 | RUN npm config set registry http://registry.npmjs.org/ 5 | 6 | WORKDIR /usr/src/app/ 7 | 8 | CMD yarn install && npm run start:docker 9 | -------------------------------------------------------------------------------- /docker-compose.yml.sample: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | webpack: 5 | # Set up your port here and go to http://localhost: 6 | ports: 7 | - 8880:8080 8 | build: ./docker/webpack 9 | volumes: 10 | - ./:/usr/src/app 11 | networks: 12 | avionics: 13 | 14 | networks: 15 | avionics: -------------------------------------------------------------------------------- /src/avionics/printAltitudeCarriage.js: -------------------------------------------------------------------------------- 1 | import { createElem } from './helpers.js' 2 | 3 | export default function printAltitudeCarriage (altitude_scale) { 4 | const carriage = createElem('altitude_scale_selected'); 5 | altitude_scale.appendChild(carriage); 6 | carriage.setAttribute('visibility', 'hidden'); 7 | 8 | return carriage; 9 | } 10 | -------------------------------------------------------------------------------- /src/demo.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Avionics js 5 | 6 | 7 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /webpack.config.coffee: -------------------------------------------------------------------------------- 1 | { merge } = require('webpack-merge'); 2 | common = require('./webpack.common.config.coffee') 3 | HtmlWebpackPlugin = require('html-webpack-plugin') 4 | htmlWebpackPluginOptions = require('./htmlWebpackPluginConfig.js') 5 | 6 | module.exports = merge(common, { 7 | mode: 'development' 8 | plugins: common.plugins.concat(htmlWebpackPluginOptions.map((i) -> new HtmlWebpackPlugin(i))) 9 | }) 10 | -------------------------------------------------------------------------------- /src/demoKeys.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Avionics js 5 | 6 | 7 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /htmlWebpackPluginConfig.js: -------------------------------------------------------------------------------- 1 | module.exports = [{ 2 | filename: 'demo.html', 3 | template: 'src/demo.ejs', 4 | inject: false, 5 | scriptSrc: "avionics.js" 6 | }, 7 | { 8 | filename: 'demoKeys.html', 9 | template: 'src/demoKeys.ejs', 10 | inject: false, 11 | scriptSrc: "avionics.js" 12 | }, 13 | { 14 | filename: 'SpecRunner.html', 15 | template: 'src/SpecRunner.ejs', 16 | inject: false, 17 | scriptSrc: "avionics.js" 18 | }]; 19 | -------------------------------------------------------------------------------- /webpack.production.config.coffee: -------------------------------------------------------------------------------- 1 | { merge } = require('webpack-merge') 2 | common = require('./webpack.common.config.coffee') 3 | HtmlWebpackPlugin = require('html-webpack-plugin') 4 | htmlWebpackPluginOptions = require('./htmlWebpackPluginConfig.js') 5 | 6 | module.exports = merge(common, { 7 | mode: 'production' 8 | plugins: common.plugins.concat( 9 | htmlWebpackPluginOptions.map( 10 | (i) -> new HtmlWebpackPlugin( 11 | merge(i, scriptSrc: "https://unpkg.com/@ivyknob/avionics_js") 12 | ) 13 | ) 14 | ) 15 | }) 16 | -------------------------------------------------------------------------------- /src/SpecRunner.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Jasmine Spec Runner 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/avionics/helpers.js: -------------------------------------------------------------------------------- 1 | const pad = function (number, n) { 2 | const arr = Math.round(number).toString().split(""); 3 | if (n >= arr.length) { 4 | return (new Array(n - arr.length)).fill('0').concat(arr).join(""); 5 | } 6 | return arr.slice(-n).join(''); 7 | } 8 | 9 | const compoundValue = function (value) { 10 | const thousands = Math.floor(value/1000); 11 | return [ 12 | thousands ? `${thousands}` : '', 13 | pad(value%1000, 3) 14 | ].join(""); 15 | } 16 | 17 | const createElem = function (id) { 18 | const use = document.createElementNS("http://www.w3.org/2000/svg", 'use'); 19 | use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', `#${id}`); 20 | return use; 21 | } 22 | 23 | export { pad, compoundValue, createElem }; 24 | -------------------------------------------------------------------------------- /src/avionics/printSpeed.js: -------------------------------------------------------------------------------- 1 | import { createElem } from './helpers.js' 2 | 3 | const text = document.createElementNS("http://www.w3.org/2000/svg", 'text'); 4 | text.setAttribute('x', -20); 5 | text.classList.add('speed__scale-value'); 6 | 7 | export default function printSpeed (elem) { 8 | const marker = createElem('speed_scale_marker'), 9 | large_marker = createElem('speed_scale_large_marker'); 10 | let i = 0; 11 | while (i <= 100) { 12 | let clone; 13 | 14 | if (i % 2 === 0) { 15 | clone = large_marker.cloneNode(); 16 | const cloneText = text.cloneNode(); 17 | cloneText.textContent = i*5; 18 | cloneText.setAttribute('y', -i * 40); 19 | elem.appendChild(cloneText); 20 | } 21 | else { 22 | clone = marker.cloneNode(); 23 | } 24 | 25 | clone.setAttribute('y', -i * 40); 26 | elem.appendChild(clone); 27 | 28 | i += 1; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/demoKeys.coffee: -------------------------------------------------------------------------------- 1 | document.addEventListener 'keydown', (e) => 2 | switch e.keyCode 3 | when 37 4 | new_roll = Avionics.roll - 2 5 | if new_roll < -180 6 | avionics.roll = new_roll + 360 7 | else 8 | avionics.roll -= 2 9 | when 39 10 | new_roll = avionics.roll + 2 11 | if new_roll > 180 12 | avionics.roll = new_roll - 360 13 | else 14 | avionics.roll += 2 15 | when 38 16 | new_pitch = avionics.pitch + 1 17 | if new_pitch > 180 18 | avionics.pitch = new_pitch-360 19 | else if new_pitch < -180 20 | avionics.pitch = new_pitch+360 21 | else 22 | avionics.pitch = new_pitch 23 | when 40 24 | new_pitch = avionics.pitch - 1 25 | if new_pitch < -180 26 | avionics.pitch = new_pitch+360 27 | else if new_pitch > 180 28 | avionics.pitch = new_pitch-360 29 | else 30 | avionics.pitch = new_pitch 31 | -------------------------------------------------------------------------------- /src/avionics/printAltitude.js: -------------------------------------------------------------------------------- 1 | import { createElem, compoundValue } from './helpers.js' 2 | 3 | const text = document.createElementNS("http://www.w3.org/2000/svg", 'text'); 4 | text.setAttribute('x', 27); 5 | text.classList.add('altitude__scale-value'); 6 | 7 | export default function printAltitude (elem) { 8 | const marker = createElem('altitude_scale_marker'), 9 | large_marker = createElem('altitude_scale_large_marker'); 10 | 11 | let i = 0; 12 | 13 | while (i <= 10000) { 14 | let clone; 15 | 16 | if (i % 100 === 0) { 17 | clone = large_marker.cloneNode(); 18 | const cloneText = text.cloneNode(); 19 | cloneText.innerHTML = i === 0 ? '0' : compoundValue(i); 20 | cloneText.setAttribute('y', -i); 21 | elem.appendChild(cloneText); 22 | } 23 | else { 24 | clone = marker.cloneNode(); 25 | } 26 | 27 | clone.setAttribute('y', -i); 28 | elem.appendChild(clone); 29 | 30 | i += 20; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/avionics/printHeading.js: -------------------------------------------------------------------------------- 1 | import { createElem } from './helpers.js' 2 | 3 | const text = document.createElementNS("http://www.w3.org/2000/svg", 'text'); 4 | text.style.fill = '#fff'; 5 | text.style.fontSize = '16px'; 6 | text.style.fontWeight = 'bold'; 7 | text.setAttribute('text-anchor', 'middle'); 8 | text.setAttribute('y', -10); 9 | 10 | export default function printHeading (elem) { 11 | const marker = createElem('heading_scale_marker'), 12 | large_marker = createElem('heading_scale_large_marker'); 13 | let i = -200; 14 | while (i <= 200) { 15 | let clone; 16 | if (i % 5 === 0) { 17 | clone = large_marker.cloneNode(); 18 | if (i % 10 === 0) { 19 | const cloneText = text.cloneNode(); 20 | cloneText.setAttribute('x', i * 10); 21 | cloneText.textContent = i <= 0 ? 360 + i : i; 22 | elem.appendChild(cloneText); 23 | } 24 | } 25 | else { 26 | clone = marker.cloneNode(); 27 | } 28 | clone.setAttribute('x', i * 10); 29 | elem.appendChild(clone); 30 | i += 1; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/avionics/printVerticalSpeed.js: -------------------------------------------------------------------------------- 1 | import { createElem } from './helpers.js' 2 | 3 | const text = document.createElementNS("http://www.w3.org/2000/svg", 'text'); 4 | text.setAttribute('x', -12); 5 | text.classList.add('vertical-speed__scale-value'); 6 | 7 | export default function printVerticalSpeed (elem) { 8 | const marker = createElem('vertical_speed_scale_marker'), 9 | large_marker = createElem('vertical_speed_scale_large_marker'); 10 | 11 | let i = -15; 12 | while (i <= 15) { 13 | let clone; 14 | 15 | if (i !== 0) { 16 | if (i % 5 === 0) { 17 | clone = large_marker.cloneNode(); 18 | const textValue = Math.abs(i); 19 | if (textValue <= 10) { 20 | const cloneText = text.cloneNode(); 21 | cloneText.textContent = textValue; 22 | cloneText.setAttribute('y', i * 15); 23 | elem.appendChild(cloneText); 24 | } 25 | } 26 | else { 27 | clone = marker.cloneNode(); 28 | } 29 | 30 | clone.setAttribute('y', i * 15); 31 | elem.appendChild(clone); 32 | } 33 | 34 | i += 1; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 ivyknob 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/avionics/printPitch.js: -------------------------------------------------------------------------------- 1 | import { createElem } from './helpers.js' 2 | 3 | const textLeft = document.createElementNS("http://www.w3.org/2000/svg", 'text'); 4 | textLeft.classList.add('pitch-scale-value'); 5 | const textRight = textLeft.cloneNode(); 6 | textLeft.setAttribute('text-anchor', 'end'); 7 | textLeft.setAttribute('x', -45); 8 | textRight.setAttribute('x', 45); 9 | 10 | export default function printPitch (elem) { 11 | let i = -220; 12 | const large = createElem('large-pitch'), 13 | medium = createElem('medium-pitch'), 14 | small = createElem('small-pitch'), 15 | appendFn = (text) => { 16 | const value = Math.abs(i); 17 | text.textContent = value > 180 ? 360 - value : value; 18 | text.setAttribute('y', -i * 8); 19 | elem.appendChild(text); 20 | }; 21 | while (i <= 220) { 22 | let texts, use; 23 | if (i === 0) { 24 | use = document.querySelector('#large-pitch').cloneNode(); 25 | use.setAttribute('x1', -60); 26 | use.setAttribute('x2', 60); 27 | } 28 | else if (i % 10 === 0) { 29 | use = large.cloneNode(); 30 | texts = [ 31 | textLeft.cloneNode(), 32 | textRight.cloneNode() 33 | ]; 34 | texts.forEach(appendFn); 35 | } 36 | else if (i % 5 === 0) { 37 | use = medium.cloneNode(); 38 | } 39 | else if (i % 2.5 === 0) { 40 | use = small.cloneNode(); 41 | } 42 | use.setAttribute('y', i * 8); 43 | elem.appendChild(use); 44 | i += 2.5; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /webpack.common.config.coffee: -------------------------------------------------------------------------------- 1 | path = require('path') 2 | CopyPlugin = require('copy-webpack-plugin') 3 | 4 | module.exports = 5 | entry: 6 | avionics: './src/avionics/index.js' 7 | demo: './src/demo.coffee' 8 | demoKeys: './src/demoKeys.coffee' 9 | output: 10 | path: path.resolve(__dirname, 'dist') 11 | filename: '[name].js', 12 | module: 13 | rules: [{ 14 | test: /\.css$/ 15 | use: ['style-loader', 'css-loader'] 16 | },{ 17 | test: /\.coffee$/ 18 | use: ['coffee-loader'] 19 | },{ 20 | test: /\.html$/ 21 | use: [{ 22 | loader: 'html-loader', 23 | options: { 24 | minimize: true 25 | } 26 | }] 27 | },{ 28 | test: /\.js$/ 29 | exclude: /node_modules/ 30 | use: 31 | loader: 'babel-loader' 32 | options: 33 | presets: ['@babel/preset-env'] 34 | }] 35 | plugins: [ 36 | new CopyPlugin({ 37 | patterns: [ 38 | { from: 'node_modules/jasmine-core/lib/jasmine-core/jasmine.js', to: 'lib/jasmine' } 39 | { from: 'node_modules/jasmine-core/lib/jasmine-core/jasmine-html.js', to: 'lib/jasmine' } 40 | { from: 'node_modules/jasmine-core/lib/jasmine-core/boot.js', to: 'lib/jasmine' } 41 | { from: 'node_modules/jasmine-core/lib/jasmine-core/jasmine.css', to: 'lib/jasmine' } 42 | { from: 'node_modules/jasmine-core/images/jasmine_favicon.png', to: 'lib/jasmine' } 43 | { from: 'spec', to: 'spec' } 44 | ] 45 | }) 46 | ] 47 | resolve: 48 | extensions: ['.js', '.coffee'] 49 | devServer: 50 | contentBase: path.join(__dirname, 'dist') 51 | # закоментировал, иначе не работает coffeescript 52 | watchOptions: { ignored: /node_modules/ } 53 | -------------------------------------------------------------------------------- /src/avionics/styles.css: -------------------------------------------------------------------------------- 1 | html, body, .avionics { 2 | width: 100%; height: 100%; 3 | } 4 | body { 5 | margin: 0; 6 | } 7 | svg.avionics { 8 | display: block; 9 | font-family: monospace; 10 | } 11 | .airspeed-value { 12 | font-size: 40px; 13 | dominant-baseline: central; 14 | fill: #fff; 15 | } 16 | .altitude-value { 17 | font-size:30px; 18 | dominant-baseline: central; 19 | fill: #fff; 20 | text-anchor: end; 21 | } 22 | 23 | #ground_speed_value { 24 | font-size: 30px; 25 | font-weight: bold; 26 | fill: #c748a2; 27 | text-anchor: end; 28 | dominant-baseline: central; 29 | } 30 | 31 | #selected_altitude_value, 32 | #barometric_setting_value { 33 | font-size: 30px; 34 | font-weight: bold; 35 | fill: #47cfe0; 36 | text-anchor: end; 37 | dominant-baseline: central; 38 | } 39 | 40 | .speed__scale-value { 41 | fill: #fff; 42 | font-size: 24px; 43 | font-weight: bold; 44 | text-anchor: end; 45 | dominant-baseline: central; 46 | } 47 | 48 | .vertical-speed__scale-value { 49 | fill: #fff; 50 | font-size: 16px; 51 | font-weight: bold; 52 | text-anchor: end; 53 | dominant-baseline: central; 54 | } 55 | 56 | #heading_current_value { 57 | dominant-baseline: central; 58 | text-anchor: middle; 59 | font-size: 18px; 60 | font-weight: bold; 61 | fill: #fff; 62 | } 63 | 64 | .background-rect { 65 | fill: black; 66 | stroke: #fff; 67 | stroke-width: 1; 68 | opacity: 0.1; 69 | } 70 | 71 | #selected_altitude_bug_value { 72 | dominant-baseline: central; 73 | font-size: 20px; 74 | } 75 | 76 | .pitch-scale-value { 77 | fill: #fff; 78 | font-weight: bold; 79 | dominant-baseline: central; 80 | } 81 | 82 | .roll { 83 | fill: black; 84 | stroke: white; 85 | stroke-width: 1; 86 | } 87 | 88 | .altitude__scale-value { 89 | font-size: 20px; 90 | dominant-baseline: central; 91 | fill: #fff; 92 | } 93 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ivyknob/avionics_js", 3 | "version": "0.0.19", 4 | "description": "js all in one avionics module", 5 | "main": "dist/avionics.js", 6 | "files": [ 7 | "dist/avionics.js", 8 | "src", 9 | "webpack.config.coffee", 10 | "webpack.production.config.coffee", 11 | "yarn.lock" 12 | ], 13 | "unpkg": "dist/avionics.js", 14 | "scripts": { 15 | "start:docker": "webpack-dev-server --host 0.0.0.0", 16 | "start": "webpack-dev-server", 17 | "build": "webpack --config webpack.production.config.coffee", 18 | "test": "karma start karma.conf.js --single-run", 19 | "lint": "npx eslint ./src/avionics/", 20 | "test:local": "karma start karma.conf.js --browsers Chrome,ChromeHeadless", 21 | "gh-pages": "npx push-dir --dir=dist --branch=gh-pages" 22 | }, 23 | "publishConfig": { 24 | "access": "public" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/ivyknob/avionics_js.git" 29 | }, 30 | "author": "Ivy Knob", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/ivyknob/avionics_js/issues" 34 | }, 35 | "homepage": "https://github.com/ivyknob/avionics_js#readme", 36 | "devDependencies": { 37 | "@babel/core": "^7.2.2", 38 | "@babel/preset-env": "^7.2.3", 39 | "babel-loader": "^8.0.5", 40 | "coffee-loader": "^1.0.0", 41 | "coffeescript": "^2.3.2", 42 | "copy-webpack-plugin": "^6.0.2", 43 | "css-loader": "^4.2.1", 44 | "eslint": "^7.2.0", 45 | "eslint-plugin-coffeescript": "^1.0.0", 46 | "html-loader": "^1.1.0", 47 | "html-webpack-plugin": "^4.2.0", 48 | "jasmine-core": "^3.3.0", 49 | "karma": "^5.0.1", 50 | "karma-chrome-launcher": "^3.1.0", 51 | "karma-firefox-launcher": "^1.1.0", 52 | "karma-jasmine": "^4.0.0", 53 | "karma-phantomjs-launcher": "^1.0.4", 54 | "push-dir": "^0.4.1", 55 | "style-loader": "^1.0.0", 56 | "webpack": "^4.29.6", 57 | "webpack-cli": "^3.1.2", 58 | "webpack-dev-server": ">=3.1.11", 59 | "webpack-merge": "^5.0.9" 60 | }, 61 | "dependencies": {} 62 | } 63 | -------------------------------------------------------------------------------- /src/demo.coffee: -------------------------------------------------------------------------------- 1 | DATA_TABLE = [ 2 | { ts: 0, as: 0, gs: 0, p: 5, r: 0, h: 35, sh: 35, a: 0, sa: 0, abp: 1013.05, vsi: 0.0 }, 3 | { ts: 10, as: 70, gs: 70, p: 2, r: 0, h: 35, sh: 35, a: 0, sa: 0, abp: 1013.05, vsi: 0.0 }, 4 | { ts: 13, as: 90, gs: 85, p: 10, r: 0, h: 35, sh: 35, a: 5, sa: 1000, abp: 1013.05, vsi: 1.5 }, 5 | { ts: 14, as: 91, gs: 86, p: 10, r: -5, h: 35, sh: 35, a: 6, sa: 1000, abp: 1013.05, vsi: 1.5 }, 6 | { ts: 60, as: 110, gs: 100, p: 10, r: -5, h: 300, sh: 300, a: 146, sa: 1000, abp: 1013.05, vsi: 3.0 }, 7 | { ts: 61, as: 110, gs: 100, p: 10, r: 0, h: 300, sh: 300, a: 149, sa: 1000, abp: 1013.05, vsi: 3.0 }, 8 | { ts: 120, as: 120, gs: 110, p: 10, r: 0, h: 300, sh: 300, a: 1000, sa: 1000, abp: 1013.05, vsi: 3.0 }, 9 | { ts: 121, as: 120, gs: 110, p: 0, r: 0, h: 300, sh: 300, a: 1000, sa: 1000, abp: 1013.05, vsi: 0.0 }, 10 | { ts: 170, as: 60, gs: 60, p: -10, r: 0, h: 300, sh: 300, a: 0, sa: 0, abp: 1013.05, vsi: -3.0 }, 11 | { ts: 171, as: 50, gs: 50, p: 2, r: 0, h: 300, sh: 300, a: 0, sa: 0, abp: 1013.05, vsi: 0.0 }, 12 | { ts: 180, as: 0, gs: 0, p: 5, r: 0, h: 300, sh: 300, a: 0, sa: 0, abp: 1013.05, vsi: 0.0 } 13 | ] 14 | 15 | current_tick = -> Date.now()%180000/1000 16 | 17 | interpolation = (current_ts, min, max, attribute)-> 18 | min[attribute] + ((max[attribute] - min[attribute]) / (max.ts - min.ts)) * (current_ts - min.ts) 19 | 20 | createData = -> 21 | ct = current_tick() 22 | min = DATA_TABLE.filter( (i) -> i.ts <= ct ).sort((a,b) -> b.ts-a.ts)[0] 23 | max = DATA_TABLE.filter( (i) -> i.ts >= ct ).sort((a,b) -> a.ts-b.ts)[0] 24 | 25 | { 26 | pitch: interpolation(ct, min, max, 'p'), 27 | roll: interpolation(ct, min, max, 'r'), 28 | airspeed: interpolation(ct, min, max, 'as'), 29 | groundSpeed: interpolation(ct, min, max, 'gs'), 30 | heading: interpolation(ct, min, max, 'h'), 31 | # selected_heading: interpolation(ct, min, max, 'sh'), 32 | altitude: interpolation(ct, min, max, 'a'), 33 | selectedAltitude: min.sa, 34 | qnh: interpolation(ct, min, max, 'abp'), 35 | verticalSpeed: interpolation(ct, min, max, 'vsi') 36 | } 37 | 38 | setInterval -> 39 | Object.assign(avionics, createData()) 40 | , 50 41 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Fri Jan 11 2019 22:01:31 GMT+0300 (Москва, стандартное время) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['jasmine'], 14 | 15 | 16 | // list of files / patterns to load in the browser 17 | files: [ 18 | 'dist/avionics.js', 19 | 'spec/all.js' 20 | ], 21 | 22 | 23 | // list of files / patterns to exclude 24 | exclude: [ 25 | ], 26 | 27 | 28 | // preprocess matching files before serving them to the browser 29 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 30 | preprocessors: { 31 | }, 32 | 33 | 34 | // test results reporter to use 35 | // possible values: 'dots', 'progress' 36 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 37 | reporters: ['progress'], 38 | 39 | 40 | // web server port 41 | port: 9876, 42 | 43 | 44 | // enable / disable colors in the output (reporters and logs) 45 | colors: true, 46 | 47 | 48 | // level of logging 49 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 50 | logLevel: config.LOG_INFO, 51 | 52 | 53 | // enable / disable watching file and executing tests whenever any file changes 54 | autoWatch: true, 55 | 56 | 57 | // start these browsers 58 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 59 | browsers: ['Chrome', 'ChromeHeadless', 'Firefox'], 60 | 61 | // plugins : ['karma-jasmine', 'karma-phantomjs-launcher', 'karma-chrome'], 62 | plugins : ['karma-jasmine', 'karma-chrome-launcher', 'karma-firefox-launcher'], 63 | 64 | // Continuous Integration mode 65 | // if true, Karma captures browsers, runs the tests and exits 66 | singleRun: false, 67 | 68 | // Concurrency level 69 | // how many browser should be started simultaneous 70 | concurrency: 1 71 | }) 72 | } 73 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | language: node_js 3 | node_js: 4 | - 12 5 | # stages: 6 | # - build 7 | # - check 8 | # - test 9 | jobs: 10 | include: 11 | - stage: "Build" 12 | name: Make sure the build is successful 13 | script: yarn build 14 | 15 | - stage: "Check" 16 | name: Audit package dependencies for security vulnerabilities 17 | script: yarn audit 18 | 19 | - name: Run linter (ESLint) 20 | script: yarn lint 21 | 22 | - stage: "Test" 23 | name: Run karma test suite 24 | addons: 25 | firefox: latest 26 | chrome: stable 27 | script: 28 | - yarn build 29 | - yarn test 30 | before_script: 31 | - Xvfb :99 & export DISPLAY=:99 32 | - sleep 5 33 | 34 | - stage: "Deploy" 35 | name: "Deploy to NPM JS" 36 | script: skip 37 | before_deploy: 38 | - yarn build 39 | deploy: 40 | provider: npm 41 | on: 42 | tags: true 43 | branch: master 44 | email: poltorakovmb@gmail.com 45 | skip_cleanup: true 46 | # condition: $TRAVIS_TAG =~ ^v[0-9]+\.[0-9]+\.[0-9]+ 47 | api_key: 48 | secure: cEBOnx5WyLZmwNSheZNUBFK9G5e/JwOYmzUGnt+CqEq+bXGmCcpJJ9HUr6EKI0zAPhtEYQZrhkDNMAFHd0pbcSsg7TzIVBMdB6P4KtzANDKcUD8DTivzpasqq+Qun3CCmo0HRxel3G09lKRVu7g9ob0jDk3e9/1ETWMEouIC09XRaYBkL4r2hC7aCDra0pLj5bXESFRjsrT+rT019U7ELN1wsiRaKady6RhplTbcsp1GqpVLuUmwY/GLIr1Pr9lmxCpWt+nIo53VNWHh6XlzW+Jh9mSWDswEc2Yel0D8Ueqa+S3emggfVNNgbgI/FbbQ1aZfqC8X5Kg9DCtRKmBknnIzy3tuE/kZpYqo6grNIwRxbw8APYBEgp1EpnWbgz6bHgN0qh/2QDVVVaoqfbYmyvSc1ny6x2vVz3qv+H84Gc1JFs4v5Y6bZSI6U9SzO8pWAhvBEWpK6mqkibSYD2YPRuibeMQmVoJsoNZukqoCNM3F2hyAAx5TpYKqFm49yv8SHu4JSh/I1aZsx/z5ItXdTTyCbzn0oRwMMSxu2bFng4ksiK+e6KBES1pEq4hbzOjWLV0xZVBMNx/ZuDXdp13T+IzxpcF6arp5meaxt3zudwoR+ktE+uYg8Quvfp1RqvGB3IefW1h0okxkjgn0Uf27r/eGOs2Db2OCEaKl+1DbM1k= 49 | # - stage: release 50 | # name: Deploy to Github Releases 51 | # before_deploy: 52 | # - yarn build 53 | # - cp dist/avionics.js avionics.js 54 | # deploy: 55 | # provider: releases 56 | # api_key: 57 | # secure: "HYOu3rAhBroiqMlJ618UKl7/Us0P1lfeLnw9fmCkZDdJMw3f5Ggr6nwpd4cXzY3B8x3RYkv1rOepItOjGgWBORyHLOq3/cXRJK2kRkzSP7hoYgmVs2Dv07JLMopVP3LqrfM3TgEP/3o2s4Oo/E8NdPORr7dmRofU4ySNAOeTtYWo4aNbc1yHKKL9qCZxoc/dsnXA6bbFWjMc7T4ppWL6YKOnVQmaXTuIkIxDctmlBKFvD84FlFpwPG8OizDgF2mxlzuUdlIWDVGGAoMVZ8+1iu+bGLOSAvOn5ckRC397sxDv0yawWbbQm4MMJia+fdEWDa3znneQuaMYcqQNV+YkO7Ff+0qDqO2oW8nPAzpwsyubyDpzhydydO7RzLkQIJUiY7cuQACg1wIWLd1pHbMeRUTeKL5rzotFuEQk7GprOjiJI8bZO9jjoUrEJrjyGMIRp7Cn8W027w6nZb50ZO9zSvilyPAEhbgx0wICRyw1PoKg0VZQ5Fv6uxZ6StQlEiQNG4E44uFx+gx0wlPpKldIpx3M+G2UgqsAeR5Y6TTRk+pq7WLxRIvjRr7hNxrTnvFeYWzBVJ6KkpXDiCII8QbFseXFZrZ4bQz5sOQPZg8wJ0HbVt/mkwP4tLYGstlSwb8HcN1tPu2pP2IEdGkRt8lszxuVvJPP51h+XxyBJ6m8hkQ=" 58 | # file: 59 | # - avionics.js 60 | # skip_cleanup: true 61 | # on: 62 | # branch: master 63 | # tags: true 64 | -------------------------------------------------------------------------------- /spec/all.js: -------------------------------------------------------------------------------- 1 | describe("Avionics specs", function() { 2 | 3 | it('Global object "Avionics" to be defined', function() { 4 | expect(Avionics).toBeDefined(); 5 | }); 6 | 7 | describe("with real draw", function(){ 8 | function createAvionics() { 9 | const div = document.createElement("div"); 10 | div.id = 'app'; 11 | 12 | document.body.appendChild(div); 13 | return new Avionics(document.querySelector('#app')); 14 | } 15 | 16 | function removeChild(avionics) { 17 | document.body.removeChild(avionics.elem); 18 | }; 19 | 20 | it('Check set airspeed', function() { 21 | const avionics = createAvionics(); 22 | 23 | avionics.airspeed = 993.1; 24 | expect(avionics.elem.querySelector('#airspeed_value').textContent).toEqual('99'); 25 | expect(avionics.elem.querySelector('#airspeed_value_residue_current').textContent).toEqual('3'); 26 | 27 | avionics.airspeed = 58.9; 28 | expect(avionics.elem.querySelector('#airspeed_value').textContent).toEqual('05'); 29 | expect(avionics.elem.querySelector('#airspeed_value_residue_current').textContent).toEqual('9'); 30 | removeChild(avionics); 31 | }); 32 | 33 | it('Check set altitude', function() { 34 | const avionics = createAvionics(); 35 | 36 | avionics.altitude = 12345.32; 37 | expect(avionics.elem.querySelector("#altitude_value").textContent).toEqual('123'); 38 | expect(avionics.elem.querySelector("#altitude_value_residue_current").textContent).toEqual('40'); 39 | 40 | avionics.altitude = 123.23; 41 | expect(avionics.elem.querySelector("#altitude_value").textContent).toEqual('001'); 42 | expect(avionics.elem.querySelector("#altitude_value_residue_current").textContent).toEqual('20'); 43 | removeChild(avionics); 44 | }); 45 | 46 | it('Check set roll', function() { 47 | const avionics = createAvionics(); 48 | 49 | avionics.roll = 10; 50 | expect(avionics.horizont.getAttribute('transform')).toEqual("rotate(10) translate(0 0)"); 51 | expect(avionics.rotor.getAttribute("transform")).toEqual("rotate(10)"); 52 | expect(avionics.pitchElem.getAttribute("transform")).toEqual("translate(0 0)"); 53 | expect(avionics.roll_triangle.getAttribute("transform")).toEqual("rotate(10)"); 54 | removeChild(avionics); 55 | }); 56 | 57 | it('Check set pitch', function() { 58 | const avionics = createAvionics(); 59 | 60 | avionics.pitch = 5.1; 61 | expect(avionics.horizont.getAttribute('transform')).toEqual("rotate(0) translate(0 20.4)"); 62 | expect(avionics.rotor.getAttribute("transform")).toEqual("rotate(0)"); 63 | expect(avionics.pitchElem.getAttribute("transform")).toEqual("translate(0 40.8)"); 64 | removeChild(avionics); 65 | }); 66 | 67 | it('Check set roll and pitch together', function() { 68 | const avionics = createAvionics(); 69 | avionics.roll = 10.2; 70 | avionics.pitch = 5.1; 71 | 72 | expect(avionics.horizont.getAttribute('transform')).toEqual("rotate(10.2) translate(0 20.4)"); 73 | expect(avionics.rotor.getAttribute("transform")).toEqual("rotate(10.2)"); 74 | expect(avionics.pitchElem.getAttribute("transform")).toEqual("translate(0 40.8)"); 75 | expect(avionics.roll_triangle.getAttribute("transform")).toEqual("rotate(10.2)"); 76 | removeChild(avionics); 77 | }); 78 | 79 | it('Check set heading', function() { 80 | const avionics = createAvionics(); 81 | avionics.heading = 350.25; 82 | expect(avionics.heading_current_value.textContent).toEqual('350'); 83 | expect(avionics.heading_scale.getAttribute("transform")).toEqual("translate(97.5,22.5)"); 84 | 85 | avionics.heading = 50.2; 86 | expect(avionics.heading_current_value.textContent).toEqual('050'); 87 | expect(avionics.heading_scale.getAttribute("transform")).toEqual("translate(-502,22.5)"); 88 | removeChild(avionics); 89 | }); 90 | 91 | it('Check set groundSpeed', function() { 92 | const avionics = createAvionics(); 93 | avionics.groundSpeed = 257.2 94 | expect(avionics.elem.querySelector("#ground_speed_value").textContent).toEqual('257'); 95 | removeChild(avionics); 96 | }); 97 | 98 | it('Check set selectedAltitude', function() { 99 | const avionics = createAvionics(); 100 | avionics.selectedAltitude = 344.9 101 | expect(avionics.elem.querySelector("#selected_altitude_value").textContent).toEqual('345'); 102 | removeChild(avionics); 103 | }); 104 | }) 105 | 106 | }); 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Avionics JS [![Build Status](https://travis-ci.org/ivyknob/avionics_js.svg?branch=master)](https://travis-ci.org/ivyknob/avionics_js) [![Maintainability](https://api.codeclimate.com/v1/badges/80ccb6e63a5ac25bbab0/maintainability)](https://codeclimate.com/github/ivyknob/avionics_js/maintainability) [![npm](https://img.shields.io/npm/v/@ivyknob/avionics_js.svg)](https://www.npmjs.com/package/@ivyknob/avionics_js) 2 | 3 | JavaScript library to make glass EFIS from any phone or tablet. Inspired by Garmin G5. 4 | Made by Ivy Knob. 5 | 6 | [Live Demo](https://ivyknob.github.io/avionics_js/demo.html) 7 | 8 | ## Installation 9 | 10 | * Add `@ivyknob/avionics_js` to your project (`npm -i @ivyknob/avionics_js` or `yarn add @ivyknob/avionics_js`) 11 | * Run `yarn` or `npm install` to install all necessary libraries 12 | * Include avionics.js to your project 13 | 14 | ### Using CDN 15 | 16 | If you want to just include js on your page from the hosting, just add `https://unpkg.com/@ivyknob/avionics_js` as a script source 17 | 18 | ## Usage 19 | 20 | ### Quickstart example 21 | 22 | ```html 23 | 24 | 25 | 26 | 27 | Avionics JS 28 | 29 | 44 | 45 | 46 | 47 | 48 | ``` 49 | 50 | Look at the examples code [here](https://github.com/ivyknob/avionics_js/blob/master/src/demo.coffee) 51 | 52 | ### Initialization 53 | 54 | At first, you need to assign avionics.js to html element: 55 | 56 | ```js 57 | var avionics = new Avionics(nodeElement); 58 | ``` 59 | 60 | After initialization, you can set values: 61 | 62 | ```js 63 | avionics.pitch = 10; 64 | ``` 65 | 66 | ### API 67 | 68 | Here is the list of available setter-methods: 69 | 70 | | Available setter-methods | 71 | | :--- | 72 | | airspeed | 73 | | altitude | 74 | | roll | 75 | | pitch | 76 | | verticalSpeed | 77 | | heading | 78 | | groundSpeed | 79 | | selectedAltitude | 80 | | qnh | 81 | 82 | ```js 83 | // You can run it in browser console to see current available methods 84 | Object.getOwnPropertyNames(avionics.__proto__).filter(i => !i.startsWith('_') && i !== 'constructor') 85 | ``` 86 | 87 | #### Roll 88 | 89 | Roll value of the aircraft, deg. Increase roll value to proceed counter clockwise rotation, negative for clockwise. Acceptable values from -180 to 180 deg. Setter name: `roll`. 90 | 91 | #### Pitch 92 | 93 | Pitch values of the aircraft, def. Nose up is for upper semisphere, down for lower. Acceptable values from -180 to 180 deg. Setter name: `roll`. 94 | 95 | #### Airspeed 96 | 97 | Indicates airspeed, knots, m/h or km/h. Can show values from 0 to 500. Setter name: `airspeed`. 98 | 99 | #### Altitude 100 | 101 | Shows altitude, feet or m. Can show values from -10000 to 10000 (but scale only starts from 0). Values below zero is possible with different settings of qnh. Setter name: `altitude`. 102 | 103 | #### Vertical speed (VSI) 104 | 105 | Gives rate information for the climb or descent, m/s. Usually in the range −30 to +30 m/s (-6000 to +6000 fpm). Setter name: `verticalSpeed`. 106 | 107 | #### Heading 108 | 109 | Shows heading of the aircraft, similar to a magnetic compass, deg. North direction corresponds to zero angle. Setter name: `heading`. 110 | 111 | #### Ground speed 112 | 113 | Indicates ground speed, knots, m/h or km/h. Can show values _from 0 to 500_ (why???). Setter name: `groundSpeed`. 114 | 115 | #### Selected altitude 116 | 117 | Shows selected altitude, feet or m. Can show values from -10000 to 10000 (but scale only starts from 0). Values below zero is possible with different settings of qnh. Setter name: `selected Altitude`. 118 | 119 | #### QNH 120 | 121 | Shows QNH: the pressure measured at station then reduced down to mean sea level pressure; mmHg, inHg, _hPA_. Can show values from -900 to 1200 (hPa). Setter name: `qnh`. 122 | 123 | 124 | ## Contributing 125 | 126 | 1. Fork it! 127 | 2. Create your feature branch: `git checkout -b my-new-feature` 128 | 3. Commit your changes: `git commit -am 'Add some feature'` 129 | 4. Push to the branch: `git push origin my-new-feature` 130 | 5. Submit a pull request. 131 | -------------------------------------------------------------------------------- /src/avionics/template.html: -------------------------------------------------------------------------------- 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 | 360 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 0 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 00 104 | 105 | 106 | 1 107 | 0 108 | 9 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 | 2500 135 | 136 | 137 | 138 | 139 | 140 | 141 | 0 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 000 152 | 153 | 154 | 20 155 | 00 156 | 80 157 | 158 | 159 | 160 | 161 | 162 | 163 | 0.00 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /src/avionics/index.js: -------------------------------------------------------------------------------- 1 | import template from './template.html' 2 | import './styles.css' 3 | import printPitch from './printPitch.js' 4 | import printHeading from './printHeading.js' 5 | import printSpeed from './printSpeed.js' 6 | import printVerticalSpeed from './printVerticalSpeed.js' 7 | import printAltitude from './printAltitude.js' 8 | import printAltitudeCarriage from './printAltitudeCarriage.js' 9 | 10 | import { pad, compoundValue } from './helpers.js' 11 | 12 | /* eslint no-undef: "error" */ 13 | /* eslint-env node */ 14 | /* eslint accessor-pairs: ["error", { "setWithoutGet": false }] */ 15 | const VERSION = require('../../package.json').version; 16 | 17 | class Avionics { 18 | constructor(elem) { 19 | this.elem = elem; 20 | elem.innerHTML = template; 21 | 22 | this.airspeedElem = elem.querySelector("#airspeed_value"); 23 | this.altitudeElem = elem.querySelector("#altitude_value"); 24 | this.rotor = elem.querySelector("#rotor"); 25 | this.horizont = elem.querySelector("#horizont"); 26 | this.pitchElem = elem.querySelector("#pitch"); 27 | this.roll_triangle = elem.querySelector("#roll_triangle"); 28 | this.heading_scale = elem.querySelector("#heading_scale"); 29 | this.heading_current_value = elem.querySelector("#heading_current_value"); 30 | this.ground_speed_value = elem.querySelector("#ground_speed_value"); 31 | this.selected_altitude_value = elem.querySelector("#selected_altitude_value"); 32 | this.barometric_setting_value = elem.querySelector("#barometric_setting_value"); 33 | this.speed_scale = elem.querySelector("#speed_scale"); 34 | this.vertical_speed_scale = elem.querySelector("#vertical_speed_scale"); 35 | this.vertical_speed_indicator = elem.querySelector('#vertical_speed_indicator'); 36 | this.vertical_speed_line_indicator = elem.querySelector('#vertical_speed_line_indicator'); 37 | this.altitude_scale = elem.querySelector('#altitude_scale'); 38 | 39 | this.altitudeBarrelElem = this.elem.querySelector('#altitude_value_residue'); 40 | this.altitudeBarrelElemCurrent = this.altitudeBarrelElem.querySelector('#altitude_value_residue_current'); 41 | this.altitudeBarrelElemBefore = this.altitudeBarrelElem.querySelector('#altitude_value_residue_before'); 42 | this.altitudeBarrelElemAfter = this.altitudeBarrelElem.querySelector('#altitude_value_residue_after'); 43 | 44 | this.airspeedBarrelElem = this.elem.querySelector('#airspeed_value_residue'); 45 | this.airspeedBarrelElemCurrent = this.airspeedBarrelElem.querySelector('#airspeed_value_residue_current'); 46 | this.airspeedBarrelElemBefore = this.airspeedBarrelElem.querySelector('#airspeed_value_residue_before'); 47 | this.airspeedBarrelElemAfter = this.airspeedBarrelElem.querySelector('#airspeed_value_residue_after'); 48 | 49 | printPitch(this.pitchElem); 50 | printHeading(this.heading_scale); 51 | printSpeed(this.speed_scale); 52 | this.speedBar = [40, 80, 180, 200] // default 53 | printVerticalSpeed(this.vertical_speed_scale); 54 | printAltitude(this.altitude_scale); 55 | this.selectedAltitudeBugValue = this.altitude_scale.querySelector('#selected_altitude_bug_value'); 56 | this.altitudeCarriageElem = printAltitudeCarriage(this.altitude_scale); 57 | 58 | this._rollValue = 0; 59 | this._pitchValue = 0; 60 | this._airspeed = 0; 61 | this._altitube = 0; 62 | this._heading = 360; 63 | this._verticalSpeed = 0; 64 | } 65 | 66 | static get version() { 67 | return VERSION; 68 | } 69 | 70 | _horizontTransform() { 71 | if (this.pitch > 90) { 72 | this.horizont.setAttribute("transform", `rotate(${this.roll}) scale(1,-1) translate(0 ${parseFloat(720 - this.pitch*4)})`) 73 | } 74 | else if (this.pitch < -90) { 75 | this.horizont.setAttribute("transform", `rotate(${this.roll}) scale(1,-1) translate(0 ${parseFloat(-720 - this.pitch*4)})`) 76 | } 77 | else { 78 | this.horizont.setAttribute("transform", `rotate(${this.roll}) translate(0 ${parseFloat(this.pitch*4)})`) 79 | } 80 | } 81 | 82 | set airspeed(value) { 83 | this._airspeed = value; 84 | this.speed_scale.setAttribute("transform", `translate(100, ${value*8})`); 85 | const roundValue = Math.round(value), 86 | roughRoundValue = roundValue; 87 | 88 | this.airspeedElem.textContent = pad( 89 | roundValue.toString().split('').slice(0, -1).join(""), 90 | 2 91 | ); 92 | 93 | this.airspeedBarrelElem.setAttribute( 94 | 'transform', 95 | `translate(56,${(value-roundValue)*46})` 96 | ); 97 | 98 | this.airspeedBarrelElemCurrent.textContent = pad(roughRoundValue, 1); 99 | this.airspeedBarrelElemBefore.textContent = pad(roughRoundValue - 1, 1); 100 | this.airspeedBarrelElemAfter.textContent = pad(roughRoundValue + 1, 1); 101 | } 102 | 103 | set altitude(value) { 104 | this._altitude = value; 105 | const roundValue = Math.round(value), 106 | roughRoundValue = Math.round(roundValue/20)*20; 107 | this.altitudeElem.textContent = pad( 108 | roundValue.toString().split('').slice(0, -2).join(""), 109 | 3 110 | ); 111 | 112 | this.altitudeBarrelElem.setAttribute( 113 | 'transform', 114 | `translate(102,${(value-roughRoundValue)*1.7})` 115 | ); 116 | this.altitudeBarrelElemCurrent.textContent = pad(roughRoundValue, 2); 117 | this.altitudeBarrelElemBefore.textContent = pad(roughRoundValue - 20, 2); 118 | this.altitudeBarrelElemAfter.textContent = pad(roughRoundValue + 20, 2); 119 | this.altitude_scale.setAttribute('transform', `translate(0, ${value})`); 120 | } 121 | 122 | set roll(value) { 123 | this._rollValue = parseFloat(value); 124 | this._horizontTransform(); 125 | this.rotor.setAttribute("transform", `rotate(${this._rollValue})`); 126 | this.pitchElem.setAttribute("transform", `translate(0 ${this._pitchValue*8})`); 127 | this.roll_triangle.setAttribute("transform", `rotate(${this._rollValue})`); 128 | } 129 | 130 | get roll() { 131 | return this._rollValue; 132 | } 133 | 134 | set pitch(value) { 135 | this._pitchValue = parseFloat(value); 136 | this._horizontTransform(); 137 | this.rotor.setAttribute("transform", `rotate(${this._rollValue})`); 138 | this.pitchElem.setAttribute('transform', `translate(0 ${this._pitchValue*8})`); 139 | } 140 | 141 | get pitch() { 142 | return this._pitchValue; 143 | } 144 | 145 | set verticalSpeed(value) { 146 | this._verticalSpeed = value; 147 | this.vertical_speed_indicator.setAttribute("transform", `translate(0, ${-this._verticalSpeed*15})`); 148 | this.vertical_speed_line_indicator.setAttribute('y2', -this._verticalSpeed*15); 149 | } 150 | 151 | set heading(value) { 152 | this._heading = (value == 0) ? 360 : value; 153 | this.heading_current_value.textContent = pad(this._heading, 3) 154 | 155 | let delta; 156 | if (this._heading > 180) { 157 | delta = (360 - this._heading)*10 158 | } 159 | else { 160 | delta = -this._heading*10 161 | } 162 | this.heading_scale.setAttribute("transform", `translate(${delta},22.5)`) 163 | } 164 | 165 | set groundSpeed (value) { 166 | this.ground_speed_value.textContent = Math.round(value); 167 | } 168 | 169 | set selectedAltitude (value) { 170 | const roundValue = Math.round(value); 171 | this.selected_altitude_value.textContent = roundValue; 172 | 173 | if (value === null) { 174 | this.altitudeCarriageElem.setAttribute('visibility', 'hidden'); 175 | } 176 | else { 177 | this.altitudeCarriageElem.setAttribute('visibility', 'visible'); 178 | this.altitudeCarriageElem.setAttribute('transform', `translate(0, ${-value})`); 179 | this.selectedAltitudeBugValue.innerHTML = compoundValue(roundValue); 180 | } 181 | } 182 | 183 | set qnh (value) { 184 | this.barometric_setting_value.textContent = value.toFixed(2); 185 | } 186 | 187 | set speedBar (arr) { 188 | const [arg1, arg2, arg3, arg4] = arr; 189 | if (!( 190 | arr.every(i => typeof i === 'number') 191 | && arg1 >= 0 && arg2 >= arg1 && arg3 >= arg2 && arg4 >= arg3 192 | )) { 193 | throw new Error('Incorrect speed scale') 194 | } 195 | 196 | const redBar = this.speed_scale.querySelector('#speed_red_bar'), 197 | whiteBar = this.speed_scale.querySelector('#speed_white_bar'), 198 | greenBar = this.speed_scale.querySelector('#speed_green_bar'), 199 | yellowBar = this.speed_scale.querySelector('#speed_yellow_bar'), 200 | redWhiteBar = this.speed_scale.querySelector('#speed_redwhite_bar'); 201 | 202 | redBar.setAttribute('y1', 0); 203 | redBar.setAttribute('y2', arg1 * -8); 204 | 205 | whiteBar.setAttribute('y1', arg1 * -8); 206 | whiteBar.setAttribute('y2', arg2 * -8); 207 | 208 | greenBar.setAttribute('y1', arg2 * -8); 209 | greenBar.setAttribute('y2', arg3 * -8); 210 | 211 | yellowBar.setAttribute('y1', arg3 * -8); 212 | yellowBar.setAttribute('y2', arg4 * -8); 213 | 214 | redWhiteBar.setAttribute('y1', arg4 * -8); 215 | redWhiteBar.setAttribute('y2', (arg4 + 200) * -8); 216 | } 217 | 218 | } 219 | 220 | /* eslint no-undef: "error" */ 221 | /* eslint no-redeclare: "error" */ 222 | 223 | global.Avionics = Avionics; 224 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "globals": { 8 | "Atomics": "readonly", 9 | "SharedArrayBuffer": "readonly" 10 | }, 11 | "parserOptions": { 12 | "ecmaVersion": 2018, 13 | "sourceType": "module" 14 | }, 15 | "rules": { 16 | "accessor-pairs": "error", 17 | "array-bracket-newline": "error", 18 | "array-bracket-spacing": "error", 19 | "array-callback-return": "error", 20 | "array-element-newline": "error", 21 | "arrow-body-style": "error", 22 | "arrow-parens": "error", 23 | "arrow-spacing": "error", 24 | "block-scoped-var": "error", 25 | "block-spacing": "error", 26 | "brace-style": [ 27 | "error", 28 | "stroustrup" 29 | ], 30 | "callback-return": "error", 31 | "capitalized-comments": "error", 32 | "class-methods-use-this": "off", 33 | "comma-dangle": "error", 34 | "comma-spacing": [ 35 | "error", 36 | { 37 | "after": true, 38 | "before": false 39 | } 40 | ], 41 | "comma-style": "error", 42 | "complexity": "error", 43 | "computed-property-spacing": "error", 44 | "consistent-return": "error", 45 | "consistent-this": "error", 46 | "curly": "error", 47 | "default-case": "error", 48 | "dot-location": "error", 49 | "dot-notation": "error", 50 | "eol-last": "error", 51 | "eqeqeq": "off", 52 | "func-call-spacing": "error", 53 | "func-name-matching": "error", 54 | "func-names": "off", 55 | "func-style": "error", 56 | "function-paren-newline": "error", 57 | "generator-star-spacing": "error", 58 | "global-require": "error", 59 | "guard-for-in": "error", 60 | "handle-callback-err": "error", 61 | "id-blacklist": "error", 62 | "id-length": "off", 63 | "id-match": "error", 64 | "implicit-arrow-linebreak": "error", 65 | "indent": "off", 66 | "indent-legacy": "off", 67 | "init-declarations": "off", 68 | "jsx-quotes": "error", 69 | "key-spacing": "error", 70 | "keyword-spacing": [ 71 | "error", 72 | { 73 | "after": true, 74 | "before": true 75 | } 76 | ], 77 | "line-comment-position": "error", 78 | "linebreak-style": [ 79 | "error", 80 | "unix" 81 | ], 82 | "lines-around-comment": "error", 83 | "lines-around-directive": "error", 84 | "lines-between-class-members": [ 85 | "error", 86 | "always" 87 | ], 88 | "max-classes-per-file": "error", 89 | "max-depth": "error", 90 | "max-len": "off", 91 | "max-lines": "error", 92 | "max-lines-per-function": "error", 93 | "max-nested-callbacks": "error", 94 | "max-params": "error", 95 | "max-statements": "off", 96 | "max-statements-per-line": "error", 97 | "multiline-comment-style": "error", 98 | "new-cap": "error", 99 | "new-parens": "error", 100 | "newline-after-var": "off", 101 | "newline-before-return": "off", 102 | "newline-per-chained-call": "off", 103 | "no-alert": "error", 104 | "no-array-constructor": "error", 105 | "no-async-promise-executor": "error", 106 | "no-await-in-loop": "error", 107 | "no-bitwise": "error", 108 | "no-buffer-constructor": "error", 109 | "no-caller": "error", 110 | "no-catch-shadow": "error", 111 | "no-confusing-arrow": "error", 112 | "no-continue": "error", 113 | "no-div-regex": "error", 114 | "no-duplicate-imports": "error", 115 | "no-else-return": "error", 116 | "no-empty-function": "error", 117 | "no-eq-null": "error", 118 | "no-eval": "error", 119 | "no-extend-native": "error", 120 | "no-extra-bind": "error", 121 | "no-extra-label": "error", 122 | "no-extra-parens": "off", 123 | "no-floating-decimal": "error", 124 | "no-implicit-coercion": "error", 125 | "no-implicit-globals": "error", 126 | "no-implied-eval": "error", 127 | "no-inline-comments": "error", 128 | "no-invalid-this": "error", 129 | "no-iterator": "error", 130 | "no-label-var": "error", 131 | "no-labels": "error", 132 | "no-lone-blocks": "error", 133 | "no-lonely-if": "error", 134 | "no-loop-func": "error", 135 | "no-magic-numbers": "off", 136 | "no-misleading-character-class": "error", 137 | "no-mixed-operators": "off", 138 | "no-mixed-requires": "error", 139 | "no-multi-assign": "error", 140 | "no-multi-spaces": "error", 141 | "no-multi-str": "error", 142 | "no-multiple-empty-lines": "error", 143 | "no-native-reassign": "error", 144 | "no-negated-condition": "error", 145 | "no-negated-in-lhs": "error", 146 | "no-nested-ternary": "error", 147 | "no-new": "error", 148 | "no-new-func": "error", 149 | "no-new-object": "error", 150 | "no-new-require": "error", 151 | "no-new-wrappers": "error", 152 | "no-octal-escape": "error", 153 | "no-param-reassign": "error", 154 | "no-path-concat": "error", 155 | "no-plusplus": "error", 156 | "no-process-env": "error", 157 | "no-process-exit": "error", 158 | "no-proto": "error", 159 | "no-prototype-builtins": "error", 160 | "no-restricted-globals": "error", 161 | "no-restricted-imports": "error", 162 | "no-restricted-modules": "error", 163 | "no-restricted-properties": "error", 164 | "no-restricted-syntax": "error", 165 | "no-return-assign": "error", 166 | "no-return-await": "error", 167 | "no-script-url": "error", 168 | "no-self-compare": "error", 169 | "no-sequences": "error", 170 | "no-shadow": "error", 171 | "no-shadow-restricted-names": "error", 172 | "no-spaced-func": "error", 173 | "no-sync": "error", 174 | "no-tabs": "error", 175 | "no-template-curly-in-string": "error", 176 | "no-ternary": "off", 177 | "no-throw-literal": "error", 178 | "no-trailing-spaces": "error", 179 | "no-undef-init": "error", 180 | "no-undefined": "error", 181 | "no-unmodified-loop-condition": "error", 182 | "no-unneeded-ternary": "error", 183 | "no-unused-expressions": "error", 184 | "no-use-before-define": "error", 185 | "no-useless-call": "error", 186 | "no-useless-catch": "error", 187 | "no-useless-computed-key": "error", 188 | "no-useless-concat": "error", 189 | "no-useless-constructor": "error", 190 | "no-useless-rename": "error", 191 | "no-useless-return": "error", 192 | "no-var": "off", 193 | "no-void": "error", 194 | "no-warning-comments": "error", 195 | "no-whitespace-before-property": "error", 196 | "no-with": "error", 197 | "nonblock-statement-body-position": "error", 198 | "object-curly-newline": "error", 199 | "object-curly-spacing": [ 200 | "error", 201 | "always" 202 | ], 203 | "object-property-newline": "error", 204 | "object-shorthand": "error", 205 | "one-var": 0, 206 | "one-var-declaration-per-line": "error", 207 | "operator-assignment": "error", 208 | "operator-linebreak": "error", 209 | "padded-blocks": "off", 210 | "padding-line-between-statements": "error", 211 | "prefer-arrow-callback": "error", 212 | "prefer-const": "error", 213 | "prefer-destructuring": "error", 214 | "prefer-numeric-literals": "error", 215 | "prefer-object-spread": "error", 216 | "prefer-promise-reject-errors": "error", 217 | "prefer-reflect": "error", 218 | "prefer-rest-params": "error", 219 | "prefer-spread": "error", 220 | "prefer-template": "off", 221 | "quote-props": "error", 222 | "quotes": "off", 223 | "radix": "error", 224 | "require-atomic-updates": "error", 225 | "require-await": "error", 226 | "require-jsdoc": 0, 227 | "require-unicode-regexp": "error", 228 | "rest-spread-spacing": "error", 229 | "semi": "off", 230 | "semi-spacing": "error", 231 | "semi-style": [ 232 | "error", 233 | "last" 234 | ], 235 | "sort-keys": "error", 236 | "sort-vars": 0, 237 | "space-before-blocks": "error", 238 | "space-before-function-paren": "off", 239 | "space-in-parens": [ 240 | "error", 241 | "never" 242 | ], 243 | "space-infix-ops": "off", 244 | "space-unary-ops": "error", 245 | "spaced-comment": "error", 246 | "strict": "error", 247 | "switch-colon-spacing": "error", 248 | "symbol-description": "error", 249 | "template-curly-spacing": [ 250 | "error", 251 | "never" 252 | ], 253 | "template-tag-spacing": "error", 254 | "unicode-bom": [ 255 | "error", 256 | "never" 257 | ], 258 | "valid-jsdoc": "error", 259 | "vars-on-top": "off", 260 | "wrap-iife": "error", 261 | "wrap-regex": "error", 262 | "yield-star-spacing": "error", 263 | "yoda": [ 264 | "error", 265 | "never" 266 | ] 267 | } 268 | }; 269 | --------------------------------------------------------------------------------