comparison weather_server/static/graph.js @ 28:f817fa785c93

Make graph only consider visible points when setting range.
author Paul Fisher <paul@pfish.zone>
date Sun, 19 Jan 2020 17:05:11 -0500
parents 47987502bf4c
children beedfa8eaa3f
comparison
equal deleted inserted replaced
27:99b0759386b1 28:f817fa785c93
1 var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 1 var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2 function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
2 return new (P || (P = Promise))(function (resolve, reject) { 3 return new (P || (P = Promise))(function (resolve, reject) {
3 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 4 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
4 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 5 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
5 function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 6 function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
6 step((generator = generator.apply(thisArg, _arguments || [])).next()); 7 step((generator = generator.apply(thisArg, _arguments || [])).next());
7 }); 8 });
8 }; 9 };
9 define("math", ["require", "exports"], function (require, exports) { 10 define("math", ["require", "exports"], function (require, exports) {
10 "use strict"; 11 "use strict";
11 Object.defineProperty(exports, "__esModule", { value: true }); 12 Object.defineProperty(exports, "__esModule", { value: true });
12 /** Converts Celsius to Fahrenheit. */
13 function cToF(tempC) { 13 function cToF(tempC) {
14 return tempC * 9 / 5 + 32; 14 return tempC * 9 / 5 + 32;
15 } 15 }
16 exports.cToF = cToF; 16 exports.cToF = cToF;
17 const MAGNUS_B = 17.62; 17 const MAGNUS_B = 17.62;
18 const MAGNUS_C = 243.12; 18 const MAGNUS_C = 243.12;
19 /** The gamma function to calculate dew point. */
20 function gammaFn(tempC, rhPct) { 19 function gammaFn(tempC, rhPct) {
21 return Math.log(rhPct / 100) + MAGNUS_B * tempC / (MAGNUS_C + tempC); 20 return Math.log(rhPct / 100) + MAGNUS_B * tempC / (MAGNUS_C + tempC);
22 } 21 }
23 /** Calculates the dew point. */
24 function dewPointC(tempC, rhPct) { 22 function dewPointC(tempC, rhPct) {
25 const gamma = gammaFn(tempC, rhPct); 23 const gamma = gammaFn(tempC, rhPct);
26 return MAGNUS_C * gamma / (MAGNUS_B - gamma); 24 return MAGNUS_C * gamma / (MAGNUS_B - gamma);
27 } 25 }
28 exports.dewPointC = dewPointC; 26 exports.dewPointC = dewPointC;
29 }); 27 });
30 define("graph", ["require", "exports", "math"], function (require, exports, math_1) { 28 define("graph", ["require", "exports", "math"], function (require, exports, math_1) {
31 "use strict"; 29 "use strict";
32 Object.defineProperty(exports, "__esModule", { value: true }); 30 Object.defineProperty(exports, "__esModule", { value: true });
33 /** The amount of time we will draw on the graph. */
34 const HISTORY_SECONDS = 60 * 60 * 4; 31 const HISTORY_SECONDS = 60 * 60 * 4;
35 /** The amount of buffer room we will request before HISTORY_SECONDS. */
36 const BUFFER_SECONDS = 300; 32 const BUFFER_SECONDS = 300;
37 /**
38 * Sets up everything.
39 * @param tempElement The element where temperature data is.
40 * @param dewPointElement The element where the dew point is.
41 */
42 function setUp(root, tempElement, dewPointElement) { 33 function setUp(root, tempElement, dewPointElement) {
43 return __awaiter(this, void 0, void 0, function* () { 34 return __awaiter(this, void 0, void 0, function* () {
44 const nowTS = new Date().getTime() / 1000; 35 const nowTS = new Date().getTime() / 1000;
45 const startTS = nowTS - HISTORY_SECONDS; 36 const startTS = nowTS - HISTORY_SECONDS;
46 const query = new URL('?', location.href); 37 const query = new URL('?', location.href);
60 setUpElement(tempElement, [startTS, nowTS], tempsF); 51 setUpElement(tempElement, [startTS, nowTS], tempsF);
61 setUpElement(dewPointElement, [startTS, nowTS], dewPointsF); 52 setUpElement(dewPointElement, [startTS, nowTS], dewPointsF);
62 }); 53 });
63 } 54 }
64 exports.setUp = setUp; 55 exports.setUp = setUp;
65 /**
66 * Sets up charting for this element.
67 * @param element The element to put a graph in.
68 * @param timeRange The `[start, end]` of the time range.
69 * @param data The data to chart.
70 */
71 function setUpElement(element, timeRange, data) { 56 function setUpElement(element, timeRange, data) {
72 if (data.length === 0) 57 if (data.length === 0)
73 return; 58 return;
74 const chart = new Chart(element, timeRange, data); 59 const chart = new Chart(element, timeRange, data);
75 chart.resize(); 60 chart.resize();
76 addEventListener('resize', () => chart.resize()); 61 addEventListener('resize', () => chart.resize());
77 } 62 }
78 /** The number of degrees that the graph shows vertically. */
79 const Y_DEGREE_RANGE = 10; 63 const Y_DEGREE_RANGE = 10;
80 const LINE_WIDTH_PX = 2; 64 const LINE_WIDTH_PX = 2;
81 const FONT_SIZE = '40px'; 65 const FONT_SIZE = '40px';
82 class Chart { 66 class Chart {
83 /**
84 * Creates a new chart.
85 * @param element The parent element to create `<canvas>`-based chart in.
86 * @param timeRange `[start, end]` of the range to chart as Unix timestamps.
87 * @param data The data to chart.
88 */
89 constructor(element, timeRange, data) { 67 constructor(element, timeRange, data) {
90 this.element = element; 68 this.element = element;
91 this.timeRange = timeRange; 69 this.timeRange = timeRange;
92 this.data = data; 70 this.data = data;
93 this.canvas = document.createElement('canvas'); 71 this.canvas = document.createElement('canvas');
111 const family = getComputedStyle(this.element).fontFamily; 89 const family = getComputedStyle(this.element).fontFamily;
112 ctx.strokeStyle = stroke; 90 ctx.strokeStyle = stroke;
113 ctx.lineJoin = 'round'; 91 ctx.lineJoin = 'round';
114 ctx.lineWidth = LINE_WIDTH_PX; 92 ctx.lineWidth = LINE_WIDTH_PX;
115 ctx.font = `bold ${FONT_SIZE} ${family}`; 93 ctx.font = `bold ${FONT_SIZE} ${family}`;
116 const yRange = calculateYRange(this.data.map(d => d[1])); 94 const onScreenData = this.data.filter(([time, _]) => this.timeRange[0] <= time);
95 const yRange = calculateYRange(onScreenData.map(d => d[1]));
117 const [fullW, fullH] = this.size(); 96 const [fullW, fullH] = this.size();
118 const [xPad, yMargin] = this.measureMargin(ctx); 97 const [xPad, yMargin] = this.measureMargin(ctx);
119 const graphSize = [fullW - xPad, fullH]; 98 const graphSize = [fullW - xPad, fullH];
120 ctx.beginPath(); 99 ctx.beginPath();
121 for (const pt of this.data) { 100 for (const pt of this.data) {
135 ctx.textBaseline = 'top'; 114 ctx.textBaseline = 'top';
136 ctx.fillText(`${niceNumber(lastPt[1])} ${this.unit}`, center[0] + 5 * LINE_WIDTH_PX, center[1] + yMargin); 115 ctx.fillText(`${niceNumber(lastPt[1])} ${this.unit}`, center[0] + 5 * LINE_WIDTH_PX, center[1] + yMargin);
137 } 116 }
138 measureMargin(ctx) { 117 measureMargin(ctx) {
139 const bbox = ctx.measureText(`−99 ${this.unit}`); 118 const bbox = ctx.measureText(`−99 ${this.unit}`);
140 const margin = 5 * LINE_WIDTH_PX + // margin to text 119 const margin = 5 * LINE_WIDTH_PX +
141 bbox.width + // max (?) width of text 120 bbox.width +
142 16; // Pixel margin to wall. 121 16;
143 return [margin, -31.5 / 2]; 122 return [margin, -31.5 / 2];
144 } 123 }
145 size() { 124 size() {
146 const cssSize = this.element.getBoundingClientRect(); 125 const cssSize = this.element.getBoundingClientRect();
147 return [cssSize.width, cssSize.height]; 126 return [cssSize.width, cssSize.height];
148 } 127 }
149 } 128 }
150 function niceNumber(n) { 129 function niceNumber(n) {
151 return Math.round(n).toLocaleString('en-us').replace('-', '−'); 130 return Math.round(n).toLocaleString('en-us').replace('-', '−');
152 } 131 }
153 /** The closest that the last point will be allowed to get to the edge. */
154 const EDGE_FRACTION = 0.125; 132 const EDGE_FRACTION = 0.125;
155 /**
156 * Determines what the Y range of the chart should be.
157 * @param ys The Y values of the chart.
158 * @return The lowest and highest values of the range.
159 */
160 function calculateYRange(ys) { 133 function calculateYRange(ys) {
161 const yMax = Math.max(...ys); 134 const yMax = Math.max(...ys);
162 const yMin = Math.min(...ys); 135 const yMin = Math.min(...ys);
163 const yMid = (yMin + yMax) / 2; 136 const yMid = (yMin + yMax) / 2;
164 const lastY = ys[ys.length - 1]; 137 const lastY = ys[ys.length - 1];
165 const yLo = yMid - Y_DEGREE_RANGE / 2; 138 const yLo = yMid - Y_DEGREE_RANGE / 2;
166 const yProportion = Math.max(Math.min((lastY - yLo) / Y_DEGREE_RANGE, 1 - EDGE_FRACTION), EDGE_FRACTION); 139 const yProportion = Math.max(Math.min((lastY - yLo) / Y_DEGREE_RANGE, 1 - EDGE_FRACTION), EDGE_FRACTION);
167 const rangeLo = lastY - yProportion * Y_DEGREE_RANGE; 140 const rangeLo = lastY - yProportion * Y_DEGREE_RANGE;
168 return [rangeLo, rangeLo + Y_DEGREE_RANGE]; 141 return [rangeLo, rangeLo + Y_DEGREE_RANGE];
169 } 142 }
170 /**
171 * Projects a Cartesian coordinate into Canvas space.
172 *
173 * @param coord The `[x, y]` coordinate to project.
174 * @param size The `[width, height]` of the context.
175 * @param xRange The range of X values in the context.
176 * @param yRange The range of Y values in the context.
177 * @return The `[x, y]` coordinate in Canvas space.
178 */
179 function project(coord, size, xRange, yRange) { 143 function project(coord, size, xRange, yRange) {
180 const [x, y] = coord; 144 const [x, y] = coord;
181 const [xMin, xMax] = xRange; 145 const [xMin, xMax] = xRange;
182 const xSpan = xMax - xMin; 146 const xSpan = xMax - xMin;
183 const [yMin, yMax] = yRange; 147 const [yMin, yMax] = yRange;