From d9c6bb299e5143cab27d3774703b5c0c2ace231c Mon Sep 17 00:00:00 2001 From: Julius Volz Date: Fri, 15 Feb 2019 11:35:33 +0100 Subject: [PATCH] Convert Graph to TS Signed-off-by: Julius Volz --- package-lock.json | 36 ++++---- package.json | 4 +- src/App.css | 7 +- src/{Graph.js => Graph.tsx} | 177 ++++++++++++------------------------ 4 files changed, 84 insertions(+), 140 deletions(-) rename src/{Graph.js => Graph.tsx} (55%) diff --git a/package-lock.json b/package-lock.json index 0ed287932d..294a93544c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -925,6 +925,15 @@ "loader-utils": "^1.1.0" } }, + "@types/flot": { + "version": "0.0.31", + "resolved": "https://registry.npmjs.org/@types/flot/-/flot-0.0.31.tgz", + "integrity": "sha512-X+RcMQCqPlQo8zPT6cUFTd/PoYBShMQlHUeOXf05jWlfYnvLuRmluB9z+2EsOKFgUzqzZve5brx+gnFxBaHEUw==", + "dev": true, + "requires": { + "@types/jquery": "*" + } + }, "@types/jest": { "version": "24.0.4", "resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.0.4.tgz", @@ -5375,6 +5384,11 @@ "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=" }, + "flot": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/flot/-/flot-2.1.6.tgz", + "integrity": "sha512-W82uI2eoYCOTcFuRX71kYTde1k8BZO/l0ueLcFELCPuB3Vl0GvXMsDCiAeAHhc53pPsNp1GJ5ckwcM7yn+AsZQ==" + }, "flush-write-stream": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", @@ -8549,6 +8563,11 @@ "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz", "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==" }, + "jquery.flot.tooltip": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/jquery.flot.tooltip/-/jquery.flot.tooltip-0.9.0.tgz", + "integrity": "sha1-rha/lLJsLtmrTbFnu6Ut/bYVwd8=" + }, "js-levenshtein": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", @@ -16931,23 +16950,6 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-5.1.2.tgz", "integrity": "sha512-7kEBKwU9R8fKnZJBRa5RSIfay4KJwnYvKB6gODGicUmDSAhQJ7Tdnll5S0RLtYrzRfMVXlqYw61rzrSpP4ThLQ==" }, - "react-flot": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/react-flot/-/react-flot-1.3.0.tgz", - "integrity": "sha1-Q9LeNvY5RZnS/vt+jWsD1MViDno=", - "requires": { - "@types/react": "^15.0.38", - "deep-equal": "^1.0.1", - "jquery": "^3.1.1" - }, - "dependencies": { - "@types/react": { - "version": "15.6.21", - "resolved": "https://registry.npmjs.org/@types/react/-/react-15.6.21.tgz", - "integrity": "sha512-XpKrM3ohs7pPOWpwPnaAoxbXMI5REcBTZm/c+WTLpfaAoDf99pnQAkTkg6DyPpnkmBbykhowaBd0sHP0+K7n0g==" - } - } - }, "react-is": { "version": "16.8.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.1.tgz", diff --git a/package.json b/package.json index a03e43d869..5cde64f56e 100644 --- a/package.json +++ b/package.json @@ -13,16 +13,17 @@ "@types/react-dom": "^16.8.0", "bootstrap": "^4.2.1", "downshift": "^3.2.2", + "flot": "^2.1.6", "fuzzy": "^0.1.3", "i": "^0.3.6", "jquery": "^3.3.1", + "jquery.flot.tooltip": "^0.9.0", "jsdom": "^9.6.0", "moment": "^2.24.0", "moment-timezone": "^0.5.23", "npm": "^6.7.0", "react": "^16.7.0", "react-dom": "^16.7.0", - "react-flot": "^1.3.0", "react-scripts": "2.1.3", "reactstrap": "^7.1.0", "tempusdominus-bootstrap-4": "^5.1.2", @@ -45,6 +46,7 @@ "not op_mini all" ], "devDependencies": { + "@types/flot": "0.0.31", "@types/moment-timezone": "^0.5.10", "@types/reactstrap": "^7.1.3" } diff --git a/src/App.css b/src/App.css index 65642e7ab1..989c972e53 100644 --- a/src/App.css +++ b/src/App.css @@ -119,7 +119,12 @@ div.endtime-input { margin: 0 5px 0 5px; } -.graph .flot-overlay { +.graph-chart { + height: 500px; + width: 100%; +} + +.graph-chart .flot-overlay { cursor: crosshair; } diff --git a/src/Graph.js b/src/Graph.tsx similarity index 55% rename from src/Graph.js rename to src/Graph.tsx index 77e9bb9114..0f657a268f 100644 --- a/src/Graph.js +++ b/src/Graph.tsx @@ -1,12 +1,14 @@ +import $ from 'jquery'; import React, { PureComponent } from 'react'; import { Alert } from 'reactstrap'; -import ReactFlot from 'react-flot'; -import '../node_modules/react-flot/flot/jquery.flot.time.min'; -import '../node_modules/react-flot/flot/jquery.flot.crosshair.min'; -import '../node_modules/react-flot/flot/jquery.flot.tooltip.min'; -import '../node_modules/react-flot/flot/jquery.flot.stack.min'; +require('flot'); +require('flot/source/jquery.flot.crosshair'); +require('flot/source/jquery.flot.legend'); +require('flot/source/jquery.flot.time'); +require('flot/source/jquery.flot.hover'); +require('jquery.flot.tooltip'); import metricToSeriesName from './MetricFomat'; @@ -16,17 +18,23 @@ function getGraphID() { return graphID++; } -class Graph extends PureComponent { - constructor(props) { - super(props); - this.state = { - legendRef: null, - }; - this.id = getGraphID(); - } +interface GraphProps { + data: any; // TODO: Type this. + stacked: boolean; + queryParams: { + startTime: number, + endTime: number, + resolution: number, + } | null; +} - escapeHTML(string) { - var entityMap = { +class Graph extends PureComponent { + private id: number = getGraphID(); + private chartRef = React.createRef(); + private legendRef = React.createRef(); + + escapeHTML(str: string) { + var entityMap: {[key: string]: string} = { '&': '&', '<': '<', '>': '>', @@ -35,42 +43,22 @@ class Graph extends PureComponent { '/': '/' }; - return String(string).replace(/[&<>"'/]/g, function (s) { + return String(str).replace(/[&<>"'/]/g, function (s) { return entityMap[s]; }); } - renderLabels(labels) { - let labelStrings = []; + renderLabels(labels: {[key: string]: string}) { + let labelStrings: string[] = []; for (let label in labels) { if (label !== '__name__') { labelStrings.push('' + label + ': ' + this.escapeHTML(labels[label])); } } - return labels = '
' + labelStrings.join('
') + '
'; + return '
' + labelStrings.join('
') + '
'; }; - // axisUnits = [ - // {unit: 'Y', factor: 1e24}, - // {unit: 'Z', factor: 1e21}, - // {unit: 'E', factor: 1e18}, - // {unit: 'P', factor: 1e15}, - // {unit: 'T', factor: 1e12}, - // {unit: 'G', factor: 1e9}, - // {unit: 'M', factor: 1e6}, - // {unit: 'K', factor: 1e3}, - // {unit: null,factor: 1}, - // {unit: 'm', factor: 1e-3}, - // {unit: 'ยต', factor: 1e-6}, - // {unit: 'n', factor: 1e-9}, - // {unit: 'p', factor: 1e-12}, - // {unit: 'f', factor: 1e-15}, - // {unit: 'a', factor: 1e-18}, - // {unit: 'z', factor: 1e-21}, - // {unit: 'y', factor: 1e-24}, - // ] - - formatValue = (y) => { + formatValue = (y: number): string => { var abs_y = Math.abs(y); if (abs_y >= 1e24) { return (y / 1e24).toFixed(2) + "Y"; @@ -111,68 +99,11 @@ class Graph extends PureComponent { } else if (abs_y <= 1) { return y.toFixed(2) } + throw Error("couldn't format a value, this is a bug"); } - getOptions() { + getOptions(): any { return { - // colors: [ - // '#7EB26D', // 0: pale green - // '#EAB839', // 1: mustard - // '#6ED0E0', // 2: light blue - // '#EF843C', // 3: orange - // '#E24D42', // 4: red - // '#1F78C1', // 5: ocean - // '#BA43A9', // 6: purple - // '#705DA0', // 7: violet - // '#508642', // 8: dark green - // '#CCA300', // 9: dark sand - // '#447EBC', - // '#C15C17', - // '#890F02', - // '#0A437C', - // '#6D1F62', - // '#584477', - // '#B7DBAB', - // '#F4D598', - // '#70DBED', - // '#F9BA8F', - // '#F29191', - // '#82B5D8', - // '#E5A8E2', - // '#AEA2E0', - // '#629E51', - // '#E5AC0E', - // '#64B0C8', - // '#E0752D', - // '#BF1B00', - // '#0A50A1', - // '#962D82', - // '#614D93', - // '#9AC48A', - // '#F2C96D', - // '#65C5DB', - // '#F9934E', - // '#EA6460', - // '#5195CE', - // '#D683CE', - // '#806EB7', - // '#3F6833', - // '#967302', - // '#2F575E', - // '#99440A', - // '#58140C', - // '#052B51', - // '#511749', - // '#3F2B5B', - // '#E0F9D7', - // '#FCEACA', - // '#CFFAFF', - // '#F9E2D2', - // '#FCE2DE', - // '#BADFF4', - // '#F9D9F9', - // '#DEDAF7', - // ], grid: { hoverable: true, clickable: true, @@ -180,15 +111,13 @@ class Graph extends PureComponent { mouseActiveRadius: 100, }, legend: { - container: this.state.legendRef, - labelFormatter: (s) => {return '  ' + s} + container: $(this.legendRef.current as any), + labelFormatter: (s: string) => {return '  ' + s} }, xaxis: { mode: 'time', showTicks: true, showMinorTicks: true, - // min: (new Date()).getTime(), - // max: (new Date(2000, 1, 1)).getTime(), }, yaxis: { tickFormatter: this.formatValue, @@ -200,8 +129,8 @@ class Graph extends PureComponent { tooltip: { show: true, cssClass: 'graph-tooltip', - content: (label, xval, yval, flotItem) => { - const series = flotItem.series; + content: (label: string, xval: number, yval: number, flotItem: any) => { + const series = flotItem.series; // TODO: type this. var date = '' + new Date(xval).toUTCString() + ''; var swatch = ''; var content = swatch + (series.labels.__name__ || 'value') + ": " + yval + ''; @@ -223,11 +152,12 @@ class Graph extends PureComponent { } getData() { - return this.props.data.result.map(ts => { + return this.props.data.result.map((ts: any /* TODO: Type this*/) => { // Insert nulls for all missing steps. let data = []; let pos = 0; - const params = this.props.queryParams; + const params = this.props.queryParams!; + console.log(this.props.queryParams); for (let t = params.startTime; t <= params.endTime; t += params.resolution) { // Allow for floating point inaccuracy. if (ts.values.length > pos && ts.values[pos][0] < t + params.resolution / 100) { @@ -246,16 +176,31 @@ class Graph extends PureComponent { }) } - parseValue(value) { + parseValue(value: string) { var val = parseFloat(value); if (isNaN(val)) { - // "+Inf", "-Inf", "+Inf" will be parsed into NaN by parseFloat(). The + // "+Inf", "-Inf", "+Inf" will be parsed into NaN by parseFloat(). They // can't be graphed, so show them as gaps (null). return null; } return val; }; + componentDidMount() { + this.plot(); + } + + componentDidUpdate() { + this.plot(); + } + + plot() { + if (this.chartRef.current === null || this.legendRef.current === null) { + return; + } + $.plot($(this.chartRef.current!), this.getData(), this.getOptions()); + } + render() { if (this.props.data === null) { return No data queried yet; @@ -271,18 +216,8 @@ class Graph extends PureComponent { return (
- {this.state.legendRef && - - } - - {/* Really nasty hack below with setState to trigger a second render after the legend div starts to exist. */} -
{!this.state.legendRef && this.setState({legendRef: ref})}}>
+
+
); }