Skip to content

Commit

Permalink
ReactUI: adopt grafana flot fix for stacked graphs (#6603)
Browse files Browse the repository at this point in the history
* adopt grafana flot fix for stacked graphs

Signed-off-by: blalov <boiskila@gmail.com>
Signed-off-by: Boyko Lalov <boiskila@gmail.com>

* remove flot as dependencie

Signed-off-by: blalov <boiskila@gmail.com>
Signed-off-by: Boyko Lalov <boiskila@gmail.com>

* disable eslint error in flot.js

Signed-off-by: blalov <boiskila@gmail.com>
Signed-off-by: Boyko Lalov <boiskila@gmail.com>

* fix broken test due to wrong import

Signed-off-by: blalov <boiskila@gmail.com>
Signed-off-by: Boyko Lalov <boiskila@gmail.com>

* bring back flot crosshair plugin

Signed-off-by: blalov <boiskila@gmail.com>
Signed-off-by: Boyko Lalov <boiskila@gmail.com>

* trying to prevent CI out of memory by adding test script --runInBand flag

Signed-off-by: blalov <boiskila@gmail.com>
Signed-off-by: Boyko Lalov <boiskila@gmail.com>

* additional notices regarding origin of the flot vendor

Signed-off-by: Boyko Lalov <boiskila@gmail.com>

* move flot in own folder

Signed-off-by: Boyko Lalov <boiskila@gmail.com>

* move text on top

Signed-off-by: Boyko Lalov <boiskila@gmail.com>
  • Loading branch information
boyskila authored and juliusv committed Jan 14, 2020
1 parent 3b9304d commit e12e5ec
Show file tree
Hide file tree
Showing 9 changed files with 4,307 additions and 29 deletions.
3 changes: 1 addition & 2 deletions web/ui/react-app/package.json
Expand Up @@ -20,7 +20,6 @@
"bootstrap": "^4.2.1",
"downshift": "^3.2.2",
"enzyme-to-json": "^3.4.3",
"flot": "^3.2.13",
"fuzzy": "^0.1.3",
"i": "^0.3.6",
"jest-fetch-mock": "^2.1.2",
Expand All @@ -45,7 +44,7 @@
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"test": "react-scripts test --runInBand",
"test:debug": "react-scripts --inspect-brk test --runInBand --no-cache",
"eject": "react-scripts eject",
"lint:ci": "eslint --quiet \"src/**/*.{ts,tsx}\"",
Expand Down
2 changes: 1 addition & 1 deletion web/ui/react-app/src/graph/Graph.test.tsx
Expand Up @@ -104,7 +104,7 @@ describe('Graph', () => {
chartData: [
{
color: 'rgb(237,194,64)',
data: [[1572128592000, 0]],
data: [[1572128592000, null]],
index: 0,
labels: {},
},
Expand Down
9 changes: 4 additions & 5 deletions web/ui/react-app/src/graph/Graph.tsx
Expand Up @@ -7,11 +7,10 @@ import { Metric, QueryParams } from '../types/types';
import { isPresent } from '../utils/func';
import { normalizeData, getOptions, toHoverColor } from './GraphHelpers';

require('flot');
require('flot/source/jquery.flot.crosshair');
require('flot/source/jquery.flot.legend');
require('flot/source/jquery.flot.time');
require('flot/source/jquery.canvaswrapper');
require('../vendor/flot/jquery.flot');
require('../vendor/flot/jquery.flot.stack');
require('../vendor/flot/jquery.flot.time');
require('../vendor/flot/jquery.flot.crosshair');
require('jquery.flot.tooltip');

export interface GraphProps {
Expand Down
8 changes: 4 additions & 4 deletions web/ui/react-app/src/graph/GraphHelpers.test.ts
@@ -1,5 +1,5 @@
import { formatValue, getColors, parseValue, getOptions } from './GraphHelpers';
require('flot'); // need for $.colors
require('../vendor/flot/jquery.flot'); // need for $.colors

describe('GraphHelpers', () => {
describe('formatValue', () => {
Expand Down Expand Up @@ -88,13 +88,13 @@ describe('GraphHelpers', () => {
});
describe('parseValue', () => {
it('should parse number properly', () => {
expect(parseValue('12.3e', true)).toEqual(12.3);
expect(parseValue('12.3e')).toEqual(12.3);
});
it('should return 0 if value is NaN and stacked prop is true', () => {
expect(parseValue('asd', true)).toEqual(0);
expect(parseValue('asd')).toEqual(null);
});
it('should return null if value is NaN and stacked prop is false', () => {
expect(parseValue('asd', false)).toBeNull();
expect(parseValue('asd')).toBeNull();
});
});
describe('Plot options', () => {
Expand Down
24 changes: 7 additions & 17 deletions web/ui/react-app/src/graph/GraphHelpers.ts
Expand Up @@ -155,7 +155,7 @@ export const getColors = (data: { resultType: string; result: Array<{ metric: Me
});
};

export const normalizeData = ({ stacked, queryParams, data }: GraphProps): GraphSeries[] => {
export const normalizeData = ({ queryParams, data }: GraphProps): GraphSeries[] => {
const colors = getColors(data);
const { startTime, endTime, resolution } = queryParams!;
return data.result.map(({ values, metric }, index) => {
Expand All @@ -167,13 +167,10 @@ export const normalizeData = ({ stacked, queryParams, data }: GraphProps): Graph
// Allow for floating point inaccuracy.
const currentValue = values[pos];
if (values.length > pos && currentValue[0] < t + resolution / 100) {
data.push([currentValue[0] * 1000, parseValue(currentValue[1], stacked)]);
data.push([currentValue[0] * 1000, parseValue(currentValue[1])]);
pos++;
} else {
// TODO: Flot has problems displaying intermittent "null" values when stacked,
// resort to 0 now. In Grafana this works for some reason, figure out how they
// do it.
data.push([t * 1000, stacked ? 0 : null]);
data.push([t * 1000, null]);
}
}

Expand All @@ -186,16 +183,9 @@ export const normalizeData = ({ stacked, queryParams, data }: GraphProps): Graph
});
};

export const parseValue = (value: string, stacked: boolean) => {
export const parseValue = (value: string) => {
const val = parseFloat(value);
if (isNaN(val)) {
// "+Inf", "-Inf", "+Inf" will be parsed into NaN by parseFloat(). They
// can't be graphed, so show them as gaps (null).

// TODO: Flot has problems displaying intermittent "null" values when stacked,
// resort to 0 now. In Grafana this works for some reason, figure out how they
// do it.
return stacked ? 0 : null;
}
return val;
// "+Inf", "-Inf", "+Inf" will be parsed into NaN by parseFloat(). They
// can't be graphed, so show them as gaps (null).
return isNaN(val) ? null : val;
};
191 changes: 191 additions & 0 deletions web/ui/react-app/src/vendor/flot/jquery.flot.crosshair.js
@@ -0,0 +1,191 @@
/**
*
* THIS FILE WAS COPIED INTO PROMETHEUS FROM GRAFANA'S VENDORED FORK OF FLOT
* (LIVING AT https://github.com/grafana/grafana/tree/master/public/vendor/flot),
* WHICH CONTAINS FIXES FOR DISPLAYING NULL VALUES IN STACKED GRAPHS. THE ORIGINAL
* FLOT CODE WAS LICENSED UNDER THE MIT LICENSE AS STATED BELOW. ADDITIONAL
* CHANGES HAVE BEEN CONTRIBUTED TO THE GRAFANA FORK UNDER AN APACHE 2 LICENSE, SEE
* https://github.com/grafana/grafana/blob/master/license.
*
*/

/* eslint-disable prefer-spread */
/* eslint-disable no-loop-func */
/* eslint-disable @typescript-eslint/no-this-alias */
/* eslint-disable no-redeclare */
/* eslint-disable no-useless-escape */
/* eslint-disable prefer-const */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable @typescript-eslint/no-use-before-define */
/* eslint-disable eqeqeq */
/* eslint-disable no-var */
/* Flot plugin for showing crosshairs when the mouse hovers over the plot.
Copyright (c) 2007-2014 IOLA and Ole Laursen.
Licensed under the MIT license.
The plugin supports these options:
crosshair: {
mode: null or "x" or "y" or "xy"
color: color
lineWidth: number
}
Set the mode to one of "x", "y" or "xy". The "x" mode enables a vertical
crosshair that lets you trace the values on the x axis, "y" enables a
horizontal crosshair and "xy" enables them both. "color" is the color of the
crosshair (default is "rgba(170, 0, 0, 0.80)"), "lineWidth" is the width of
the drawn lines (default is 1).
The plugin also adds four public methods:
- setCrosshair( pos )
Set the position of the crosshair. Note that this is cleared if the user
moves the mouse. "pos" is in coordinates of the plot and should be on the
form { x: xpos, y: ypos } (you can use x2/x3/... if you're using multiple
axes), which is coincidentally the same format as what you get from a
"plothover" event. If "pos" is null, the crosshair is cleared.
- clearCrosshair()
Clear the crosshair.
- lockCrosshair(pos)
Cause the crosshair to lock to the current location, no longer updating if
the user moves the mouse. Optionally supply a position (passed on to
setCrosshair()) to move it to.
Example usage:
var myFlot = $.plot( $("#graph"), ..., { crosshair: { mode: "x" } } };
$("#graph").bind( "plothover", function ( evt, position, item ) {
if ( item ) {
// Lock the crosshair to the data point being hovered
myFlot.lockCrosshair({
x: item.datapoint[ 0 ],
y: item.datapoint[ 1 ]
});
} else {
// Return normal crosshair operation
myFlot.unlockCrosshair();
}
});
- unlockCrosshair()
Free the crosshair to move again after locking it.
*/

(function($) {
const options = {
crosshair: {
mode: null, // one of null, "x", "y" or "xy",
color: 'rgba(170, 0, 0, 0.80)',
lineWidth: 1,
},
};

function init(plot) {
// position of crosshair in pixels
const crosshair = { x: -1, y: -1, locked: false };

plot.setCrosshair = function setCrosshair(pos) {
if (!pos) crosshair.x = -1;
else {
const o = plot.p2c(pos);
crosshair.x = Math.max(0, Math.min(o.left, plot.width()));
crosshair.y = Math.max(0, Math.min(o.top, plot.height()));
}

plot.triggerRedrawOverlay();
};

plot.clearCrosshair = plot.setCrosshair; // passes null for pos

plot.lockCrosshair = function lockCrosshair(pos) {
if (pos) plot.setCrosshair(pos);
crosshair.locked = true;
};

plot.unlockCrosshair = function unlockCrosshair() {
crosshair.locked = false;
};

function onMouseOut() {
if (crosshair.locked) return;

if (crosshair.x != -1) {
crosshair.x = -1;
plot.triggerRedrawOverlay();
}
}

function onMouseMove(e) {
if (crosshair.locked) return;

if (plot.getSelection && plot.getSelection()) {
crosshair.x = -1; // hide the crosshair while selecting
return;
}

const offset = plot.offset();
crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width()));
crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height()));
plot.triggerRedrawOverlay();
}

plot.hooks.bindEvents.push(function(plot, eventHolder) {
if (!plot.getOptions().crosshair.mode) return;

eventHolder.mouseout(onMouseOut);
eventHolder.mousemove(onMouseMove);
});

plot.hooks.drawOverlay.push(function(plot, ctx) {
const c = plot.getOptions().crosshair;
if (!c.mode) return;

const plotOffset = plot.getPlotOffset();

ctx.save();
ctx.translate(plotOffset.left, plotOffset.top);

if (crosshair.x != -1) {
const adj = plot.getOptions().crosshair.lineWidth % 2 ? 0.5 : 0;

ctx.strokeStyle = c.color;
ctx.lineWidth = c.lineWidth;
ctx.lineJoin = 'round';

ctx.beginPath();
if (c.mode.indexOf('x') != -1) {
const drawX = Math.floor(crosshair.x) + adj;
ctx.moveTo(drawX, 0);
ctx.lineTo(drawX, plot.height());
}
if (c.mode.indexOf('y') != -1) {
const drawY = Math.floor(crosshair.y) + adj;
ctx.moveTo(0, drawY);
ctx.lineTo(plot.width(), drawY);
}
ctx.stroke();
}
ctx.restore();
});

plot.hooks.shutdown.push(function(plot, eventHolder) {
eventHolder.unbind('mouseout', onMouseOut);
eventHolder.unbind('mousemove', onMouseMove);
});
}

$.plot.plugins.push({
init: init,
options: options,
name: 'crosshair',
version: '1.0',
});
})(window.jQuery);

0 comments on commit e12e5ec

Please sign in to comment.