Convert Graph to TS

Signed-off-by: Julius Volz <julius.volz@gmail.com>
This commit is contained in:
Julius Volz 2019-02-15 11:35:33 +01:00
parent 461b022a77
commit d9c6bb299e
4 changed files with 84 additions and 140 deletions

36
package-lock.json generated
View file

@ -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",

View file

@ -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"
}

View file

@ -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;
}

View file

@ -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<GraphProps> {
private id: number = getGraphID();
private chartRef = React.createRef<HTMLDivElement>();
private legendRef = React.createRef<HTMLDivElement>();
escapeHTML(str: string) {
var entityMap: {[key: string]: string} = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
@ -35,42 +43,22 @@ class Graph extends PureComponent {
'/': '&#x2F;'
};
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('<strong>' + label + '</strong>: ' + this.escapeHTML(labels[label]));
}
}
return labels = '<div class="labels">' + labelStrings.join('<br>') + '</div>';
return '<div class="labels">' + labelStrings.join('<br>') + '</div>';
};
// 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 '&nbsp;&nbsp;' + s}
container: $(this.legendRef.current as any),
labelFormatter: (s: string) => {return '&nbsp;&nbsp;' + 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 = '<span class="date">' + new Date(xval).toUTCString() + '</span>';
var swatch = '<span class="detail-swatch" style="background-color: ' + series.color + '"></span>';
var content = swatch + (series.labels.__name__ || 'value') + ": <strong>" + yval + '</strong>';
@ -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 <Alert color="light">No data queried yet</Alert>;
@ -271,18 +216,8 @@ class Graph extends PureComponent {
return (
<div className="graph">
{this.state.legendRef &&
<ReactFlot
id={this.id.toString()}
data={this.getData()}
options={this.getOptions()}
height="500px"
width="100%"
/>
}
{/* Really nasty hack below with setState to trigger a second render after the legend div starts to exist. */}
<div className="graph-legend" ref={ref => {!this.state.legendRef && this.setState({legendRef: ref})}}></div>
<div className="graph-chart" ref={this.chartRef} />
<div className="graph-legend" ref={this.legendRef} />
</div>
);
}