UNPKG

@patricksurry/g3

Version:

A flexible Javascript framework for building steam gauge instrument panels that display live external metrics from flight (or other) simulators like XPlane or MS FS2020

128 lines (115 loc) 4.4 kB
import * as d3 from 'd3'; import {stylable, appendable, transformable} from './mixin.js'; import {gaugeController} from './controller.js'; import {element} from './common.js'; import {grid} from './grid.js'; // global defs we append to panel's svg element const globalDefs = (width, height) => [ element('radialGradient', { id: 'highlightGradient', cx: '50%', cy: '50%', fx: '25%', fy: '40%', r: '50%', }).append( ...['white', 'black'].map( d => element('stop', {'stop-color': d, offset: d == 'white' ? '0%': '100%'}) ) ), ...[1, 2, 3].map(d => element('filter', { id: 'dropShadow' + d, // need userSpaceOnUse for drop-shadow to work on 0-width items // but then need explicit extent in target units? filterUnits: 'userSpaceOnUse', x: -width, width: 2*width, y: -height, height: 2*height, }).append(element('feDropShadow', {stdDeviation: d, dx: 0, dy: 0})) ), ...[1, 2, 3].map(d => element('filter', { id: 'gaussianBlur' + d, }).append(element('feGaussianBlur', {in: 'SourceGraphic', stdDeviation: d})) ), ]; export function panel() { var width = 1024, height = 768, interval = 250, showgrid = false, smooth = true, url; function panel(sel) { if (typeof sel === 'string') sel = d3.select(sel); // draw and start updating panel let controller = gaugeController(), // establish context for gauges transition = smooth ? (sel => sel.transition().duration(interval || 250).ease(d3.easeLinear)) : (sel => sel), _ = sel.append('svg') .attr('width', width).attr('height', height); // insert the global defs now that we know the panel size panel.defs.append(...globalDefs(width, height)); _ = _.append('g'); panel.stylable(_); panel.transformable(_); panel.appendable(_); if (showgrid) grid().width(width).height(height)(_); console.log('Starting panel expecting metrics for:', controller.indicators()); if (!url) { // fake metrics setInterval(() => { controller(controller.fakeMetrics(), transition); }, interval || 250); } else if (interval) { // with non-zero interval, poll an endpoint let latest=0; setInterval(() => { let params = { latest: latest, units: latest == 0, }; // add the matched metrics once we've determined them if (latest) params.metrics = controller.mappedMetrics(); url.search = new URLSearchParams(params).toString(); fetch(url) .then(response => response.json()) .then(data => { controller(data, transition); latest = data.latest; }); }, interval); } else { // set interval to 0 or None to use server-sent event endpoint let source = new EventSource(url); url.search = new URLSearchParams({ // server should determine best match metrics indicators: controller.indicators() }).toString(); source.onmessage = function(e) { controller(JSON.parse(e.data), transition); }; } } panel.width = function(_) { return arguments.length ? (width = _, panel): width; } panel.height = function(_) { return arguments.length ? (height = _, panel): height; } panel.grid = function(_) { return arguments.length ? (showgrid = !!_, panel): showgrid; } panel.url = function(_) { return arguments.length ? (url = new URL(_, document.location), panel): url; } panel.interval = function(_) { return arguments.length ? (interval = _, panel): interval; } panel.smooth = function(_) { return arguments.length ? (smooth = _, panel): smooth; } stylable(appendable(transformable(panel))).class('g3-panel') panel.defs = element('defs') panel.append(panel.defs); return panel; }