From 1f1974b2997be471bbec831500bdff59d83079da Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Wed, 27 May 2020 18:31:51 -0500 Subject: [PATCH] [ML] Extend population preview chart to show actual and typical values as well in focusData --- .../explorer_chart_distribution.js | 17 +++++----- .../ml/public/application/util/chart_utils.js | 32 +++++++++++++++++++ 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js index 6c7c3e9040216..91df210a4b69b 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js @@ -25,6 +25,7 @@ import { getTickValues, numTicksForDateFormat, removeLabelOverlap, + chartExtendedLimits, } from '../../util/chart_utils'; import { LoadingIndicator } from '../../components/loading_indicator/loading_indicator'; import { getTimeBucketsFromCache } from '../../util/time_buckets'; @@ -137,22 +138,20 @@ export class ExplorerChartDistribution extends React.Component { }); if (chartType === CHART_TYPE.POPULATION_DISTRIBUTION) { - const focusData = chartData - .filter((d) => { - return d.entity === highlight; - }) - .map((d) => d.value); - const focusExtent = d3.extent(focusData); - + const focusData = chartData.filter((d) => { + return d.entity === highlight; + }); + // calculate the max y domain based on value, typical, and actual + const { min: yScaleDomainMin, max: yScaleDomainMax } = chartExtendedLimits(focusData); // now again filter chartData to include only the data points within the domain chartData = chartData.filter((d) => { - return d.value <= focusExtent[1]; + return d.value <= yScaleDomainMax; }); lineChartYScale = d3.scale .linear() .range([chartHeight, 0]) - .domain([0, focusExtent[1]]) + .domain([yScaleDomainMin < 0 ? yScaleDomainMin : 0, yScaleDomainMax]) .nice(); } else if (chartType === CHART_TYPE.EVENT_DISTRIBUTION) { // avoid overflowing the border of the highlighted area diff --git a/x-pack/plugins/ml/public/application/util/chart_utils.js b/x-pack/plugins/ml/public/application/util/chart_utils.js index 2caf964cb9774..9d240b8e1e96d 100644 --- a/x-pack/plugins/ml/public/application/util/chart_utils.js +++ b/x-pack/plugins/ml/public/application/util/chart_utils.js @@ -65,6 +65,38 @@ export function chartLimits(data = []) { return limits; } +export function chartExtendedLimits(data = []) { + let _min = Infinity; + let _max = -Infinity; + data.forEach((d) => { + let metricValue = d.value; + const actualValue = Array.isArray(d.actual) ? d.actual[0] : d.actual; + const typicalValue = Array.isArray(d.typical) ? d.typical[0] : d.typical; + + if (metricValue === null && d.anomalyScore !== undefined && d.actual !== undefined) { + // If an anomaly coincides with a gap in the data, use the anomaly actual value. + metricValue = actualValue; + } + + if (d.anomalyScore !== undefined) { + _min = Math.min(_min, metricValue, actualValue, typicalValue); + _max = Math.max(_max, metricValue, actualValue, typicalValue); + } else { + _min = Math.min(_min, metricValue); + _max = Math.max(_max, metricValue); + } + }); + const limits = { max: _max, min: _min }; + + // add padding of 5% of the difference between max and min + // if we ended up with the same value for both of them + if (limits.max === limits.min) { + const padding = limits.max * 0.05; + limits.max += padding; + limits.min -= padding; + } + return limits; +} export function drawLineChartDots(data, lineChartGroup, lineChartValuesLine, radius = 1.5) { // We need to do this because when creating a line for a chart which has data gaps, // if there are single datapoints without any valid data before and after them,