diff --git a/package-lock.json b/package-lock.json index 294a93544c..97bcee358e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -996,6 +996,14 @@ "@types/react": "*" } }, + "@types/react-resize-detector": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/react-resize-detector/-/react-resize-detector-3.1.0.tgz", + "integrity": "sha512-q/kuav2WHLqCOypvNMWR7S3UKSphE0urlvgkiaKpnGXOPsy6/3BCrr+HzcoaMOvuZW7bFngbheS2gITRl4B1xQ==", + "requires": { + "@types/react": "*" + } + }, "@types/reactstrap": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@types/reactstrap/-/reactstrap-7.1.3.tgz", @@ -8917,6 +8925,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" }, + "lodash-es": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.11.tgz", + "integrity": "sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q==" + }, "lodash._reinterpolate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", @@ -16969,6 +16982,17 @@ "prop-types": "^15.6.1" } }, + "react-resize-detector": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-3.4.0.tgz", + "integrity": "sha512-T96I8Iqa1hGWyooeFA2Sl6FdPoMhXWINfEKg2/EJLxhP37+/94VNuyuyz9CRqpmApD83IWRR+lbB3r0ADMoKJg==", + "requires": { + "lodash": "^4.17.11", + "lodash-es": "^4.17.11", + "prop-types": "^15.6.2", + "resize-observer-polyfill": "^1.5.1" + } + }, "react-scripts": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-2.1.3.tgz", @@ -17678,6 +17702,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, + "resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" + }, "resolve": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", diff --git a/package.json b/package.json index 5cde64f56e..6b6ef47772 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "@types/node": "^11.9.3", "@types/react": "^16.8.2", "@types/react-dom": "^16.8.0", + "@types/react-resize-detector": "^3.1.0", "bootstrap": "^4.2.1", "downshift": "^3.2.2", "flot": "^2.1.6", @@ -24,6 +25,7 @@ "npm": "^6.7.0", "react": "^16.7.0", "react-dom": "^16.7.0", + "react-resize-detector": "^3.4.0", "react-scripts": "2.1.3", "reactstrap": "^7.1.0", "tempusdominus-bootstrap-4": "^5.1.2", diff --git a/src/App.css b/src/App.css index 989c972e53..38323bbf74 100644 --- a/src/App.css +++ b/src/App.css @@ -2,6 +2,10 @@ body { padding-top: 10px; /* TODO remove */ } +.panel { + margin-bottom: 20px; +} + .expression-input { margin-bottom: 10px; } @@ -113,6 +117,13 @@ div.endtime-input { .graph-legend { margin: 15px 0 15px 25px; + font-size: 0.8em; +} + +.graph-legend .legend-swatch { + width: 10px; + height: 10px; + background-color: red; } .graph { @@ -122,6 +133,10 @@ div.endtime-input { .graph-chart { height: 500px; width: 100%; + /* This is picked up by Flot's axis label font renderer, + which ignores "color" and uses "fill" instead. */ + fill: #495057; + font-size: 0.8em; } .graph-chart .flot-overlay { @@ -151,5 +166,6 @@ div.endtime-input { } .add-panel-btn { + margin-top: -20px; margin-bottom: 20px; } diff --git a/src/Graph.tsx b/src/Graph.tsx index 69745c79cd..caf6354cc5 100644 --- a/src/Graph.tsx +++ b/src/Graph.tsx @@ -1,5 +1,6 @@ import $ from 'jquery'; import React, { PureComponent } from 'react'; +import ReactResizeDetector from 'react-resize-detector'; import { Alert } from 'reactstrap'; @@ -7,10 +8,10 @@ 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('flot/source/jquery.flot.resize'); +require('flot/source/jquery.canvaswrapper'); require('jquery.flot.tooltip'); +import Legend from './Legend'; import metricToSeriesName from './MetricFomat'; var graphID = 0; @@ -32,7 +33,6 @@ interface GraphProps { class Graph extends PureComponent { private id: number = getGraphID(); private chartRef = React.createRef(); - private legendRef = React.createRef(); escapeHTML(str: string) { var entityMap: {[key: string]: string} = { @@ -112,9 +112,7 @@ class Graph extends PureComponent { mouseActiveRadius: 100, }, legend: { - //show: true, - labelFormatter: (s: string) => {return '  ' + s}, - container: this.legendRef.current!, + show: false, }, xaxis: { mode: 'time', @@ -201,12 +199,11 @@ class Graph extends PureComponent { } plot() { - this.destroyPlot(); - if (this.chartRef.current === null || this.legendRef.current === null) { + if (this.chartRef.current === null) { return; } + this.destroyPlot(); $.plot($(this.chartRef.current!), this.getData(), this.getOptions()); - //window.addEventListener('resize', () => this.plot()); } destroyPlot() { @@ -231,8 +228,9 @@ class Graph extends PureComponent { return (
+ this.plot()} />
-
+
); } diff --git a/src/Legend.tsx b/src/Legend.tsx new file mode 100644 index 0000000000..5d84b4e9da --- /dev/null +++ b/src/Legend.tsx @@ -0,0 +1,28 @@ +import React, { PureComponent } from 'react'; + +import metricToSeriesName from './MetricFomat'; + +interface LegendProps { + series: any; // TODO: Type this. +} + +class Legend extends PureComponent { + renderLegendItem(s: any) { + return ( +
+ . + {s.label} +
+ ); + } + + render() { + return ( +
+ {this.props.series.map((s: any) => {return this.renderLegendItem(s)})} +
+ ); + } +} + +export default Legend; diff --git a/src/Panel.tsx b/src/Panel.tsx index 678c4ca617..d4dec49c70 100644 --- a/src/Panel.tsx +++ b/src/Panel.tsx @@ -187,49 +187,13 @@ class Panel extends Component { } } - // getEndDate = () => { - // var self = this; - // if (!self.endDate || !self.endDate.val()) { - // return moment(); - // } - // return self.endDate.data('DateTimePicker').date(); - // }; - - // getOrSetEndDate = () => { - // var self = this; - // var date = self.getEndDate(); - // self.setEndDate(date); - // return date; - // }; - - // setEndDate = (date) => { - // var self = this; - // self.endDate.data('DateTimePicker').date(date); - // }; - - // increaseEnd = () => { - // var self = this; - // var newDate = moment(self.getOrSetEndDate()); - // newDate.add(self.parseDuration(self.rangeInput.val()) / 2, 'seconds'); - // self.setEndDate(newDate); - // self.submitQuery(); - // }; - - // decreaseEnd = () => { - // var self = this; - // var newDate = moment(self.getOrSetEndDate()); - // newDate.subtract(self.parseDuration(self.rangeInput.val()) / 2, 'seconds'); - // self.setEndDate(newDate); - // self.submitQuery(); - // }; - handleChangeStacking = (stacked: boolean) => { this.setState({stacked: stacked}); } render() { return ( - <> +
{ - + - +
); } }