Skip to content

Commit

Permalink
Fixes #15501 - Change xaxis formatter to honor dateFormat:tz; fix tic…
Browse files Browse the repository at this point in the history
…k count to accommodate new formatting rules (#15512)
  • Loading branch information
simianhacker committed Dec 21, 2017
1 parent c4d5018 commit 6c92c10
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 132 deletions.
@@ -0,0 +1,17 @@
import moment from 'moment';
export function getFormat(interval, rules, dateFormat) {
for (let i = rules.length - 1; i >= 0; i--) {
const rule = rules[i];
if (!rule[0] || interval >= moment.duration(rule[0])) {
return rule[1];
}
}
return dateFormat;
}

export function createXaxisFormatter(interval, rules, dateFormat) {
return val => {
return moment(val).format(getFormat(interval, rules, dateFormat));
};
}

278 changes: 150 additions & 128 deletions src/core_plugins/metrics/public/components/vis_types/timeseries/vis.js
@@ -1,165 +1,187 @@
import PropTypes from 'prop-types';
import React from 'react';
import React, { Component } from 'react';
import tickFormatter from '../../lib/tick_formatter';
import _ from 'lodash';
import Timeseries from 'plugins/metrics/visualizations/components/timeseries';
import color from 'color';
import replaceVars from '../../lib/replace_vars';
import { getAxisLabelString } from '../../lib/get_axis_label_string';
import { createXaxisFormatter } from '../../lib/create_xaxis_formatter';

function hasSeperateAxis(row) {
return row.seperate_axis;
}

function TimeseriesVisualization(props) {
const { backgroundColor, model, visData } = props;
const series = _.get(visData, `${model.id}.series`, []);
let annotations;
if (model.annotations && Array.isArray(model.annotations)) {
annotations = model.annotations.map(annotation => {
const data = _.get(visData, `${model.id}.annotations.${annotation.id}`, [])
.map(item => [item.key, item.docs]);
return {
id: annotation.id,
color: annotation.color,
icon: annotation.icon,
series: data.map(s => {
return [s[0], s[1].map(doc => {
return replaceVars(annotation.template, null, doc);
})];
})
};
});
class TimeseriesVisualization extends Component {

constructor(props) {
super(props);
}
const seriesModel = model.series.map(s => _.cloneDeep(s));
const firstSeries = seriesModel.find(s => s.formatter && !s.seperate_axis);
const formatter = tickFormatter(_.get(firstSeries, 'formatter'), _.get(firstSeries, 'value_template'));

const mainAxis = {
position: model.axis_position,
tickFormatter: formatter,
axisFormatter: _.get(firstSeries, 'formatter', 'number'),
axisFormatterTemplate: _.get(firstSeries, 'value_template')
};
getInterval = () => {
const { visData, model } = this.props;
const series = _.get(visData, `${model.id}.series`, []);
return series.reduce((currentInterval, item) => {
if (item.data.length > 1) {
const seriesInterval = item.data[1][0] - item.data[0][0];
if (!currentInterval || seriesInterval < currentInterval) return seriesInterval;
}
return currentInterval;
}, 0);
}

xaxisFormatter = (val) => {
const { visData } = this.props;
const formatter = createXaxisFormatter(this.getInterval(), visData.scaledDataFormat, visData.dateFormat);
return formatter(val);
}

if (model.axis_min) mainAxis.min = model.axis_min;
if (model.axis_max) mainAxis.max = model.axis_max;
render() {
const { backgroundColor, model, visData } = this.props;
const series = _.get(visData, `${model.id}.series`, []);
let annotations;
if (model.annotations && Array.isArray(model.annotations)) {
annotations = model.annotations.map(annotation => {
const data = _.get(visData, `${model.id}.annotations.${annotation.id}`, [])
.map(item => [item.key, item.docs]);
return {
id: annotation.id,
color: annotation.color,
icon: annotation.icon,
series: data.map(s => {
return [s[0], s[1].map(doc => {
return replaceVars(annotation.template, null, doc);
})];
})
};
});
}
const seriesModel = model.series.map(s => _.cloneDeep(s));
const firstSeries = seriesModel.find(s => s.formatter && !s.seperate_axis);
const formatter = tickFormatter(_.get(firstSeries, 'formatter'), _.get(firstSeries, 'value_template'));

const yaxes = [mainAxis];
const mainAxis = {
position: model.axis_position,
tickFormatter: formatter,
axisFormatter: _.get(firstSeries, 'formatter', 'number'),
axisFormatterTemplate: _.get(firstSeries, 'value_template')
};


seriesModel.forEach(s => {
series
.filter(r => _.startsWith(r.id, s.id))
.forEach(r => r.tickFormatter = tickFormatter(s.formatter, s.value_template));
if (model.axis_min) mainAxis.min = model.axis_min;
if (model.axis_max) mainAxis.max = model.axis_max;

if (s.hide_in_legend) {
series
.filter(r => _.startsWith(r.id, s.id))
.forEach(r => delete r.label);
}
if (s.stacked !== 'none') {
const yaxes = [mainAxis];


seriesModel.forEach(s => {
series
.filter(r => _.startsWith(r.id, s.id))
.forEach(row => {
row.data = row.data.map(point => {
if (!point[1]) return [point[0], 0];
return point;
.forEach(r => r.tickFormatter = tickFormatter(s.formatter, s.value_template));

if (s.hide_in_legend) {
series
.filter(r => _.startsWith(r.id, s.id))
.forEach(r => delete r.label);
}
if (s.stacked !== 'none') {
series
.filter(r => _.startsWith(r.id, s.id))
.forEach(row => {
row.data = row.data.map(point => {
if (!point[1]) return [point[0], 0];
return point;
});
});
});
}
if (s.stacked === 'percent') {
s.seperate_axis = true;
s.axisFormatter = 'percent';
s.axis_min = 0;
s.axis_max = 1;
s.axis_position = model.axis_position;
const seriesData = series.filter(r => _.startsWith(r.id, s.id));
const first = seriesData[0];
if (first) {
first.data.forEach((row, index) => {
const rowSum = seriesData.reduce((acc, item) => {
return item.data[index][1] + acc;
}, 0);
seriesData.forEach(item => {
item.data[index][1] = rowSum && item.data[index][1] / rowSum || 0;
}
if (s.stacked === 'percent') {
s.seperate_axis = true;
s.axisFormatter = 'percent';
s.axis_min = 0;
s.axis_max = 1;
s.axis_position = model.axis_position;
const seriesData = series.filter(r => _.startsWith(r.id, s.id));
const first = seriesData[0];
if (first) {
first.data.forEach((row, index) => {
const rowSum = seriesData.reduce((acc, item) => {
return item.data[index][1] + acc;
}, 0);
seriesData.forEach(item => {
item.data[index][1] = rowSum && item.data[index][1] / rowSum || 0;
});
});
});
}
}
}
});
});

const interval = series.reduce((currentInterval, item) => {
if (item.data.length > 1) {
const seriesInterval = item.data[1][0] - item.data[0][0];
if (!currentInterval || seriesInterval < currentInterval) return seriesInterval;
}
return currentInterval;
}, 0);

let axisCount = 1;
if (seriesModel.some(hasSeperateAxis)) {
seriesModel.forEach((row) => {
if (row.seperate_axis) {
axisCount++;

const formatter = tickFormatter(row.formatter, row.value_template);

const yaxis = {
alignTicksWithAxis: 1,
position: row.axis_position,
tickFormatter: formatter,
axisFormatter: row.axis_formatter,
axisFormatterTemplate: row.value_template
};
const interval = this.getInterval();

let axisCount = 1;
if (seriesModel.some(hasSeperateAxis)) {
seriesModel.forEach((row) => {
if (row.seperate_axis) {
axisCount++;

const formatter = tickFormatter(row.formatter, row.value_template);

if (row.axis_min != null) yaxis.min = row.axis_min;
if (row.axis_max != null) yaxis.max = row.axis_max;
const yaxis = {
alignTicksWithAxis: 1,
position: row.axis_position,
tickFormatter: formatter,
axisFormatter: row.axis_formatter,
axisFormatterTemplate: row.value_template
};

yaxes.push(yaxis);

// Assign axis and formatter to each series
series
.filter(r => _.startsWith(r.id, row.id))
.forEach(r => {
r.yaxis = axisCount;
});
}
});
}

const params = {
dateFormat: props.dateFormat,
crosshair: true,
tickFormatter: formatter,
legendPosition: model.legend_position || 'right',
series,
annotations,
yaxes,
reversed: props.reversed,
showGrid: Boolean(model.show_grid),
legend: Boolean(model.show_legend),
onBrush: (ranges) => {
if (props.onBrush) props.onBrush(ranges);
if (row.axis_min != null) yaxis.min = row.axis_min;
if (row.axis_max != null) yaxis.max = row.axis_max;

yaxes.push(yaxis);

// Assign axis and formatter to each series
series
.filter(r => _.startsWith(r.id, row.id))
.forEach(r => {
r.yaxis = axisCount;
});
}
});
}
};
if (interval) {
params.xaxisLabel = getAxisLabelString(interval);
}
const style = { };
const panelBackgroundColor = model.background_color || backgroundColor;
if (panelBackgroundColor) {
style.backgroundColor = panelBackgroundColor;
params.reversed = color(panelBackgroundColor || backgroundColor).luminosity() < 0.45;

const params = {
dateFormat: this.props.dateFormat,
crosshair: true,
tickFormatter: formatter,
legendPosition: model.legend_position || 'right',
series,
annotations,
yaxes,
reversed: this.props.reversed,
showGrid: Boolean(model.show_grid),
legend: Boolean(model.show_legend),
xAxisFormatter: this.xaxisFormatter,
onBrush: (ranges) => {
if (this.props.onBrush) this.props.onBrush(ranges);
}
};
if (interval) {
params.xaxisLabel = getAxisLabelString(interval);
}
const style = { };
const panelBackgroundColor = model.background_color || backgroundColor;
if (panelBackgroundColor) {
style.backgroundColor = panelBackgroundColor;
params.reversed = color(panelBackgroundColor || backgroundColor).luminosity() < 0.45;
}
return (
<div className="dashboard__visualization" style={style}>
<Timeseries {...params}/>
</div>
);

}
return (
<div className="dashboard__visualization" style={style}>
<Timeseries {...params}/>
</div>
);

}

Expand Down
Expand Up @@ -14,6 +14,8 @@ const MetricsRequestHandlerProvider = function (Private, Notifier, config, timef
const panel = vis.params;
const uiStateObj = uiState.get(panel.type, {});
const timeRange = vis.params.timeRange || timefilter.getBounds();
const scaledDataFormat = config.get('dateFormat:scaled');
const dateFormat = config.get('dateFormat');
if (panel && panel.id) {
const params = {
timerange: { timezone, ...timeRange },
Expand All @@ -26,7 +28,7 @@ const MetricsRequestHandlerProvider = function (Private, Notifier, config, timef
const maxBuckets = config.get('metrics:max_buckets');
validateInterval(timeRange, panel, maxBuckets);
const httpResult = $http.post('../api/metrics/vis/data', params)
.then(resp => resp.data)
.then(resp => ({ dateFormat, scaledDataFormat, timezone, ...resp.data }))
.catch(resp => { throw resp.data; });

return httpResult
Expand Down

0 comments on commit 6c92c10

Please sign in to comment.