Commit bfbfac4b authored by Miguel Rincon's avatar Miguel Rincon

Display y-axis range matching data

Instead of anchoring the range of the y axis at 0, the chart now
displays the data in the relevent range.

This moves the deployment data to another series so it does not
interfere with the data axis.
parent cd4a5674
...@@ -18,6 +18,17 @@ import { ...@@ -18,6 +18,17 @@ import {
import { makeDataSeries } from '~/helpers/monitor_helper'; import { makeDataSeries } from '~/helpers/monitor_helper';
import { graphDataValidatorForValues } from '../../utils'; import { graphDataValidatorForValues } from '../../utils';
/**
* A "virtual" coordinates system for the deployment icons.
* Deployment icons are displayed along the [min, max]
* range at height `pos`.
*/
const deploymentYAxisCoords = {
min: 0,
pos: 3, // 3% height of chart's grid
max: 100,
};
const THROTTLED_DATAZOOM_WAIT = 1000; // miliseconds const THROTTLED_DATAZOOM_WAIT = 1000; // miliseconds
const timestampToISODate = timestamp => new Date(timestamp).toISOString(); const timestampToISODate = timestamp => new Date(timestamp).toISOString();
...@@ -145,10 +156,33 @@ export default { ...@@ -145,10 +156,33 @@ export default {
}, []); }, []);
}, },
chartOptionSeries() { chartOptionSeries() {
return (this.option.series || []).concat(this.scatterSeries ? [this.scatterSeries] : []); return (this.option.series || []).concat(
this.deploymentSeries ? [this.deploymentSeries] : [],
);
}, },
chartOptions() { chartOptions() {
const option = omit(this.option, 'series'); const option = omit(this.option, 'series');
const dataYAxis = {
name: this.yAxisLabel,
nameGap: 50, // same as gitlab-ui's default
nameLocation: 'center', // same as gitlab-ui's default
boundaryGap: [0.1, 0.1],
scale: true,
axisLabel: {
formatter: num => roundOffFloat(num, 3).toString(),
},
};
const deploymentsYAxis = {
show: false,
min: deploymentYAxisCoords.min,
max: deploymentYAxisCoords.max,
axisLabel: {
// formatter fn required to trigger tooltip re-positioning
formatter: () => {},
},
};
return { return {
series: this.chartOptionSeries, series: this.chartOptionSeries,
xAxis: { xAxis: {
...@@ -161,12 +195,7 @@ export default { ...@@ -161,12 +195,7 @@ export default {
snap: true, snap: true,
}, },
}, },
yAxis: { yAxis: [dataYAxis, deploymentsYAxis],
name: this.yAxisLabel,
axisLabel: {
formatter: num => roundOffFloat(num, 3).toString(),
},
},
dataZoom: [this.dataZoomConfig], dataZoom: [this.dataZoomConfig],
...option, ...option,
}; };
...@@ -228,10 +257,16 @@ export default { ...@@ -228,10 +257,16 @@ export default {
return acc; return acc;
}, []); }, []);
}, },
scatterSeries() { deploymentSeries() {
return { return {
type: graphTypes.deploymentData, type: graphTypes.deploymentData,
data: this.recentDeployments.map(deployment => [deployment.createdAt, 0]),
yAxisIndex: 1, // deploymentsYAxis index
data: this.recentDeployments.map(deployment => [
deployment.createdAt,
deploymentYAxisCoords.pos,
]),
symbol: this.svgs.rocket, symbol: this.svgs.rocket,
symbolSize: symbolSizes.default, symbolSize: symbolSizes.default,
itemStyle: { itemStyle: {
...@@ -265,6 +300,7 @@ export default { ...@@ -265,6 +300,7 @@ export default {
formatTooltipText(params) { formatTooltipText(params) {
this.tooltip.title = dateFormat(params.value, dateFormats.default); this.tooltip.title = dateFormat(params.value, dateFormats.default);
this.tooltip.content = []; this.tooltip.content = [];
params.seriesData.forEach(dataPoint => { params.seriesData.forEach(dataPoint => {
if (dataPoint.value) { if (dataPoint.value) {
const [xVal, yVal] = dataPoint.value; const [xVal, yVal] = dataPoint.value;
......
---
title: Display the y-axis on the range of data value in the chart
merge_request: 24953
author:
type: added
...@@ -74,6 +74,8 @@ describe('Time series component', () => { ...@@ -74,6 +74,8 @@ describe('Time series component', () => {
describe('general functions', () => { describe('general functions', () => {
let timeSeriesChart; let timeSeriesChart;
const findChart = () => timeSeriesChart.find({ ref: 'chart' });
beforeEach(done => { beforeEach(done => {
timeSeriesChart = makeTimeSeriesChart(mockGraphData, 'area-chart'); timeSeriesChart = makeTimeSeriesChart(mockGraphData, 'area-chart');
timeSeriesChart.vm.$nextTick(done); timeSeriesChart.vm.$nextTick(done);
...@@ -109,8 +111,6 @@ describe('Time series component', () => { ...@@ -109,8 +111,6 @@ describe('Time series component', () => {
let startValue; let startValue;
let endValue; let endValue;
const findChart = () => timeSeriesChart.find({ ref: 'chart' });
beforeEach(done => { beforeEach(done => {
eChartMock = { eChartMock = {
handlers: {}, handlers: {},
...@@ -285,6 +285,8 @@ describe('Time series component', () => { ...@@ -285,6 +285,8 @@ describe('Time series component', () => {
}); });
describe('computed', () => { describe('computed', () => {
const getChartOptions = () => findChart().props('option');
describe('chartData', () => { describe('chartData', () => {
let chartData; let chartData;
const seriesData = () => chartData[0]; const seriesData = () => chartData[0];
...@@ -329,7 +331,7 @@ describe('Time series component', () => { ...@@ -329,7 +331,7 @@ describe('Time series component', () => {
}); });
return timeSeriesChart.vm.$nextTick().then(() => { return timeSeriesChart.vm.$nextTick().then(() => {
expect(timeSeriesChart.vm.chartOptions).toEqual(expect.objectContaining(mockOption)); expect(getChartOptions()).toEqual(expect.objectContaining(mockOption));
}); });
}); });
...@@ -345,7 +347,7 @@ describe('Time series component', () => { ...@@ -345,7 +347,7 @@ describe('Time series component', () => {
}); });
return timeSeriesChart.vm.$nextTick().then(() => { return timeSeriesChart.vm.$nextTick().then(() => {
const optionSeries = timeSeriesChart.vm.chartOptions.series; const optionSeries = getChartOptions().series;
expect(optionSeries.length).toEqual(2); expect(optionSeries.length).toEqual(2);
expect(optionSeries[0].name).toEqual(mockSeriesName); expect(optionSeries[0].name).toEqual(mockSeriesName);
...@@ -354,33 +356,58 @@ describe('Time series component', () => { ...@@ -354,33 +356,58 @@ describe('Time series component', () => {
}); });
describe('yAxis formatter', () => { describe('yAxis formatter', () => {
let format; let dataFormatter;
let deploymentFormatter;
beforeEach(() => { beforeEach(() => {
format = timeSeriesChart.vm.chartOptions.yAxis.axisLabel.formatter; dataFormatter = getChartOptions().yAxis[0].axisLabel.formatter;
deploymentFormatter = getChartOptions().yAxis[1].axisLabel.formatter;
}); });
it('rounds to 3 decimal places', () => { it('rounds to 3 decimal places', () => {
expect(format(0.88888)).toBe('0.889'); expect(dataFormatter(0.88888)).toBe('0.889');
});
it('deployment formatter is set as is required to display a tooltip', () => {
expect(deploymentFormatter).toEqual(expect.any(Function));
}); });
}); });
}); });
describe('scatterSeries', () => { describe('deploymentSeries', () => {
it('utilizes deployment data', () => { it('utilizes deployment data', () => {
expect(timeSeriesChart.vm.scatterSeries.data).toEqual([ expect(timeSeriesChart.vm.deploymentSeries.yAxisIndex).toBe(1); // same as deployment y axis
['2019-07-16T10:14:25.589Z', 0], expect(timeSeriesChart.vm.deploymentSeries.data).toEqual([
['2019-07-16T11:14:25.589Z', 0], ['2019-07-16T10:14:25.589Z', expect.any(Number)],
['2019-07-16T12:14:25.589Z', 0], ['2019-07-16T11:14:25.589Z', expect.any(Number)],
['2019-07-16T12:14:25.589Z', expect.any(Number)],
]); ]);
expect(timeSeriesChart.vm.scatterSeries.symbolSize).toBe(14); expect(timeSeriesChart.vm.deploymentSeries.symbolSize).toBe(14);
}); });
}); });
describe('yAxisLabel', () => { describe('yAxisLabel', () => {
it('y axis is configured correctly', () => {
const { yAxis } = getChartOptions();
expect(yAxis).toHaveLength(2);
const [dataAxis, deploymentAxis] = yAxis;
expect(dataAxis.boundaryGap).toHaveLength(2);
expect(dataAxis.scale).toBe(true);
expect(deploymentAxis.show).toBe(false);
expect(deploymentAxis.min).toEqual(expect.any(Number));
expect(deploymentAxis.max).toEqual(expect.any(Number));
expect(deploymentAxis.min).toBeLessThan(deploymentAxis.max);
});
it('constructs a label for the chart y-axis', () => { it('constructs a label for the chart y-axis', () => {
expect(timeSeriesChart.vm.yAxisLabel).toBe('Memory Used per Pod'); const { yAxis } = getChartOptions();
expect(yAxis[0].name).toBe('Memory Used per Pod');
}); });
}); });
}); });
...@@ -405,7 +432,7 @@ describe('Time series component', () => { ...@@ -405,7 +432,7 @@ describe('Time series component', () => {
glChartComponents.forEach(dynamicComponent => { glChartComponents.forEach(dynamicComponent => {
describe(`GitLab UI: ${dynamicComponent.chartType}`, () => { describe(`GitLab UI: ${dynamicComponent.chartType}`, () => {
let timeSeriesAreaChart; let timeSeriesAreaChart;
const findChart = () => timeSeriesAreaChart.find(dynamicComponent.component); const findChartComponent = () => timeSeriesAreaChart.find(dynamicComponent.component);
beforeEach(done => { beforeEach(done => {
timeSeriesAreaChart = makeTimeSeriesChart(mockGraphData, dynamicComponent.chartType); timeSeriesAreaChart = makeTimeSeriesChart(mockGraphData, dynamicComponent.chartType);
...@@ -417,12 +444,12 @@ describe('Time series component', () => { ...@@ -417,12 +444,12 @@ describe('Time series component', () => {
}); });
it('is a Vue instance', () => { it('is a Vue instance', () => {
expect(findChart().exists()).toBe(true); expect(findChartComponent().exists()).toBe(true);
expect(findChart().isVueInstance()).toBe(true); expect(findChartComponent().isVueInstance()).toBe(true);
}); });
it('receives data properties needed for proper chart render', () => { it('receives data properties needed for proper chart render', () => {
const props = findChart().props(); const props = findChartComponent().props();
expect(props.data).toBe(timeSeriesAreaChart.vm.chartData); expect(props.data).toBe(timeSeriesAreaChart.vm.chartData);
expect(props.option).toBe(timeSeriesAreaChart.vm.chartOptions); expect(props.option).toBe(timeSeriesAreaChart.vm.chartOptions);
...@@ -435,9 +462,9 @@ describe('Time series component', () => { ...@@ -435,9 +462,9 @@ describe('Time series component', () => {
timeSeriesAreaChart.vm.tooltip.title = mockTitle; timeSeriesAreaChart.vm.tooltip.title = mockTitle;
timeSeriesAreaChart.vm.$nextTick(() => { timeSeriesAreaChart.vm.$nextTick(() => {
expect(shallowWrapperContainsSlotText(findChart(), 'tooltipTitle', mockTitle)).toBe( expect(
true, shallowWrapperContainsSlotText(findChartComponent(), 'tooltipTitle', mockTitle),
); ).toBe(true);
done(); done();
}); });
}); });
...@@ -452,9 +479,9 @@ describe('Time series component', () => { ...@@ -452,9 +479,9 @@ describe('Time series component', () => {
}); });
it('uses deployment title', () => { it('uses deployment title', () => {
expect(shallowWrapperContainsSlotText(findChart(), 'tooltipTitle', 'Deployed')).toBe( expect(
true, shallowWrapperContainsSlotText(findChartComponent(), 'tooltipTitle', 'Deployed'),
); ).toBe(true);
}); });
it('renders clickable commit sha in tooltip content', done => { it('renders clickable commit sha in tooltip content', done => {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment