diff --git a/ui/src/data_explorer/components/VisView.js b/ui/src/data_explorer/components/VisView.js index 987ab279fc..cebd722fe4 100644 --- a/ui/src/data_explorer/components/VisView.js +++ b/ui/src/data_explorer/components/VisView.js @@ -63,6 +63,7 @@ const VisView = ({ activeQueryIndex={activeQueryIndex} isInDataExplorer={isInDataExplorer} showSingleStat={cellType === 'line-plus-single-stat'} + isBarChart={cellType === 'bar'} displayOptions={displayOptions} editQueryStatus={editQueryStatus} /> diff --git a/ui/src/shared/components/Dygraph.js b/ui/src/shared/components/Dygraph.js index 1c7c0db477..b6fc30aa12 100644 --- a/ui/src/shared/components/Dygraph.js +++ b/ui/src/shared/components/Dygraph.js @@ -23,6 +23,71 @@ const LINE_COLORS = [ '#a0725b', ] +const darkenColor = colorStr => { + // Defined in dygraph-utils.js + const color = Dygraphs.toRGB_(colorStr) + color.r = Math.floor((255 + color.r) / 2) + color.g = Math.floor((255 + color.g) / 2) + color.b = Math.floor((255 + color.b) / 2) + return `rgb(${color.r},${color.g},${color.b})` +} + +const multiColumnBarPlotter = e => { + // We need to handle all the series simultaneously. + if (e.seriesIndex !== 0) { + return + } + + const g = e.dygraph + const ctx = e.drawingContext + const sets = e.allSeriesPoints + const yBottom = e.dygraph.toDomYCoord(0) + + // Find the minimum separation between x-values. + // This determines the bar width. + let minSep = Infinity + for (let j = 0; j < sets.length; j++) { + const points = sets[j] + for (let i = 1; i < points.length; i++) { + const sep = points[i].canvasx - points[i - 1].canvasx + if (sep < minSep) { + minSep = sep + } + } + } + const barWidth = Math.floor(2.0 / 3 * minSep) + + const fillColors = [] + const strokeColors = g.getColors() + for (let i = 0; i < strokeColors.length; i++) { + fillColors.push(darkenColor(strokeColors[i])) + } + + for (let j = 0; j < sets.length; j++) { + ctx.fillStyle = fillColors[j] + ctx.strokeStyle = strokeColors[j] + for (let i = 0; i < sets[j].length; i++) { + const p = sets[j][i] + const centerX = p.canvasx + const xLeft = centerX - barWidth / 2 * (1 - j / (sets.length - 1)) + + ctx.fillRect( + xLeft, + p.canvasy, + barWidth / sets.length, + yBottom - p.canvasy + ) + + ctx.strokeRect( + xLeft, + p.canvasy, + barWidth / sets.length, + yBottom - p.canvasy + ) + } + } +} + export default class Dygraph extends Component { constructor(props) { super(props) @@ -61,6 +126,7 @@ export default class Dygraph extends Component { ruleValues, overrideLineColors, isGraphFilled, + isBarChart, options, } = this.props @@ -155,6 +221,10 @@ export default class Dygraph extends Component { }, } + if (isBarChart) { + defaultOptions.plotter = multiColumnBarPlotter + } + this.dygraph = new Dygraphs(graphContainerNode, timeSeries, { ...defaultOptions, ...options, @@ -189,7 +259,14 @@ export default class Dygraph extends Component { } componentDidUpdate() { - const {labels, ranges, options, dygraphSeries, ruleValues} = this.props + const { + labels, + ranges, + options, + dygraphSeries, + ruleValues, + isBarChart, + } = this.props const dygraph = this.dygraph if (!dygraph) { throw new Error( @@ -217,6 +294,7 @@ export default class Dygraph extends Component { stackedGraph: options.stackedGraph, underlayCallback: options.underlayCallback, series: dygraphSeries, + plotter: isBarChart ? multiColumnBarPlotter : null, }) // part of optional workaround for preventing updateOptions from breaking legend // if (this.lastMouseMoveEvent) { @@ -265,6 +343,7 @@ Dygraph.propTypes = { options: shape({}), containerStyle: shape({}), isGraphFilled: bool, + isBarChart: bool, overrideLineColors: array, dygraphSeries: shape({}).isRequired, ruleValues: shape({ diff --git a/ui/src/shared/components/LayoutRenderer.js b/ui/src/shared/components/LayoutRenderer.js index fc19b656cd..55162aeb5b 100644 --- a/ui/src/shared/components/LayoutRenderer.js +++ b/ui/src/shared/components/LayoutRenderer.js @@ -110,6 +110,7 @@ export const LayoutRenderer = React.createClass({ timeRange={timeRange} autoRefresh={autoRefresh} showSingleStat={type === 'line-plus-single-stat'} + isBarChart={type === 'bar'} displayOptions={displayOptions} synchronizer={synchronizer} /> diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js index 1a3f6511aa..f828edd914 100644 --- a/ui/src/shared/components/LineGraph.js +++ b/ui/src/shared/components/LineGraph.js @@ -22,6 +22,7 @@ export default React.createClass({ isRefreshing: bool, underlayCallback: func, isGraphFilled: bool, + isBarChart: bool, overrideLineColors: array, queries: arrayOf(shape({}).isRequired).isRequired, showSingleStat: bool, @@ -80,6 +81,7 @@ export default React.createClass({ isFetchingInitially, isRefreshing, isGraphFilled, + isBarChart, overrideLineColors, title, underlayCallback, @@ -101,25 +103,22 @@ export default React.createClass({ ) } - const options = Object.assign( - {}, - { - labels, - connectSeparatedPoints: true, - labelsKMB: true, - axisLineColor: '#383846', - gridLineColor: '#383846', - title, - rightGap: 0, - yRangePad: 10, - axisLabelWidth: 38, - drawAxesAtZero: true, - underlayCallback, - ylabel: _.get(queries, ['0', 'label'], ''), - y2label: _.get(queries, ['1', 'label'], ''), - }, - displayOptions - ) + const options = { + labels, + connectSeparatedPoints: true, + labelsKMB: true, + axisLineColor: '#383846', + gridLineColor: '#383846', + title, + rightGap: 0, + yRangePad: 10, + axisLabelWidth: 38, + drawAxesAtZero: true, + underlayCallback, + ylabel: _.get(queries, ['0', 'label'], ''), + y2label: _.get(queries, ['1', 'label'], ''), + ...displayOptions, + } let roundedValue if (showSingleStat) { @@ -141,6 +140,7 @@ export default React.createClass({ containerStyle={{width: '100%', height: '100%'}} overrideLineColors={overrideLineColors} isGraphFilled={isGraphFilled} + isBarChart={isBarChart} timeSeries={timeSeries} labels={labels} options={options} diff --git a/ui/src/shared/data/graphTypes.hson b/ui/src/shared/data/graphTypes.hson index 16a727447a..30a3565ab5 100644 --- a/ui/src/shared/data/graphTypes.hson +++ b/ui/src/shared/data/graphTypes.hson @@ -4,4 +4,5 @@ {type: "line-stepplot", menuOption: "Step-Plot"}, {type: "single-stat", menuOption: "SingleStat"}, {type: "line-plus-single-stat", menuOption: "Line + Stat"}, + {type: "bar", menuOption: "Bar"}, ]