Skip to content

Commit

Permalink
[ML] Fix Anomaly Explorer annotations misaligned (#105696) (#105840)
Browse files Browse the repository at this point in the history
* Move swimlane annotation out of container, add new axis width prop

* [ML] Fix alignment if some annotations overflow

* [ML] Move utils function to inside anomaly_timeline_service

* [ML] Fix y chart overflow

* [ML] Enforce stricter check on isOverallSwimlaneData

* [ML] Update logic to check if only one label

Co-authored-by: Quynh Nguyen <[email protected]>
  • Loading branch information
kibanamachine and qn895 authored Jul 15, 2021
1 parent 495195d commit 0913711
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ import { NoOverallData } from './components/no_overall_data';
import { SeverityControl } from '../components/severity_control';
import { AnomalyTimelineHelpPopover } from './anomaly_timeline_help_popover';
import { isDefined } from '../../../common/types/guards';
import { MlTooltipComponent } from '../components/chart_tooltip';
import { SwimlaneAnnotationContainer } from './swimlane_annotation_container';
import { AnomalyTimelineService } from '../services/anomaly_timeline_service';

function mapSwimlaneOptionsToEuiOptions(options: string[]) {
return options.map((option) => ({
Expand Down Expand Up @@ -92,6 +95,7 @@ export const AnomalyTimeline: FC<AnomalyTimelineProps> = React.memo(
swimLaneSeverity,
overallSwimlaneData,
viewBySwimlaneData,
swimlaneContainerWidth,
} = explorerState;

const [severityUpdate, setSeverityUpdate] = useState(swimLaneSeverity);
Expand Down Expand Up @@ -140,6 +144,18 @@ export const AnomalyTimeline: FC<AnomalyTimelineProps> = React.memo(
};
}, [selectedCells]);

const annotationXDomain = useMemo(
() =>
AnomalyTimelineService.isOverallSwimlaneData(overallSwimlaneData)
? {
min: overallSwimlaneData.earliest * 1000,
max: overallSwimlaneData.latest * 1000,
minInterval: overallSwimlaneData.interval * 1000,
}
: undefined,
[overallSwimlaneData]
);

return (
<>
<EuiPanel paddingSize="m">
Expand Down Expand Up @@ -259,6 +275,21 @@ export const AnomalyTimeline: FC<AnomalyTimelineProps> = React.memo(
</EuiFlexGroup>

<EuiSpacer size="m" />
{annotationXDomain && Array.isArray(annotations) && annotations.length > 0 ? (
<>
<MlTooltipComponent>
{(tooltipService) => (
<SwimlaneAnnotationContainer
chartWidth={swimlaneContainerWidth}
domain={annotationXDomain}
annotationsData={annotations}
tooltipService={tooltipService}
/>
)}
</MlTooltipComponent>
<EuiSpacer size="m" />
</>
) : null}

<SwimlaneContainer
id="overall"
Expand All @@ -279,7 +310,6 @@ export const AnomalyTimeline: FC<AnomalyTimelineProps> = React.memo(
/>

<EuiSpacer size="m" />

{viewBySwimlaneOptions.length > 0 && (
<SwimlaneContainer
id="view_by"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ export declare interface SwimlaneData {
points: SwimlanePoint[];
interval: number;
}

interface ChartRecord extends RecordForInfluencer {
function: string;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { formatHumanReadableDateTimeSeconds } from '../../../common/util/date_ut
import { AnnotationsTable } from '../../../common/types/annotations';
import { ChartTooltipService } from '../components/chart_tooltip';

export const X_AXIS_RIGHT_OVERFLOW = 50;
export const Y_AXIS_LABEL_WIDTH = 170;
export const Y_AXIS_LABEL_PADDING = 8;
export const Y_AXIS_LABEL_FONT_COLOR = '#6a717d';
Expand Down Expand Up @@ -45,7 +46,7 @@ export const SwimlaneAnnotationContainer: FC<SwimlaneAnnotationContainerProps> =
const dimensions = canvasRef.current.getBoundingClientRect();

const startingXPos = Y_AXIS_LABEL_WIDTH + 2 * Y_AXIS_LABEL_PADDING;
const endingXPos = dimensions.width - 2 * Y_AXIS_LABEL_PADDING - 4;
const endingXPos = dimensions.width - X_AXIS_RIGHT_OVERFLOW;

const svg = chartElement
.append('svg')
Expand Down Expand Up @@ -82,18 +83,23 @@ export const SwimlaneAnnotationContainer: FC<SwimlaneAnnotationContainerProps> =

// Add annotation marker
annotationsData.forEach((d) => {
const annotationWidth = d.end_timestamp
? xScale(Math.min(d.end_timestamp, domain.max)) -
Math.max(xScale(d.timestamp), startingXPos)
: 0;

const annotationWidth = Math.max(
d.end_timestamp
? xScale(Math.min(d.end_timestamp, domain.max)) -
Math.max(xScale(d.timestamp), startingXPos)
: 0,
ANNOTATION_MIN_WIDTH
);

const xPos = d.timestamp >= domain.min ? xScale(d.timestamp) : startingXPos;
svg
.append('rect')
.classed('mlAnnotationRect', true)
.attr('x', d.timestamp >= domain.min ? xScale(d.timestamp) : startingXPos)
// If annotation is at the end, prevent overflow by shifting it back
.attr('x', xPos + annotationWidth >= endingXPos ? endingXPos - annotationWidth : xPos)
.attr('y', 0)
.attr('height', ANNOTATION_CONTAINER_HEIGHT)
.attr('width', Math.max(annotationWidth, ANNOTATION_MIN_WIDTH))
.attr('width', annotationWidth)
.on('mouseover', function () {
const startingTime = formatHumanReadableDateTimeSeconds(d.timestamp);
const endingTime =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,17 @@ import { ANOMALY_THRESHOLD, SEVERITY_COLORS } from '../../../common';
import { TimeBuckets as TimeBucketsClass } from '../util/time_buckets';
import { SWIMLANE_TYPE, SwimlaneType } from './explorer_constants';
import { mlEscape } from '../util/string_utils';
import { FormattedTooltip, MlTooltipComponent } from '../components/chart_tooltip/chart_tooltip';
import { FormattedTooltip } from '../components/chart_tooltip/chart_tooltip';
import { formatHumanReadableDateTime } from '../../../common/util/date_utils';

import './_explorer.scss';
import { EMPTY_FIELD_VALUE_LABEL } from '../timeseriesexplorer/components/entity_control/entity_control';
import { useUiSettings } from '../contexts/kibana';
import {
SwimlaneAnnotationContainer,
Y_AXIS_LABEL_WIDTH,
Y_AXIS_LABEL_PADDING,
Y_AXIS_LABEL_FONT_COLOR,
X_AXIS_RIGHT_OVERFLOW,
} from './swimlane_annotation_container';
import { AnnotationsTable } from '../../../common/types/annotations';

Expand Down Expand Up @@ -333,6 +333,8 @@ export const SwimlaneContainer: FC<SwimlaneProps> = ({
return moment(v).format(scaledDateFormat);
},
fontSize: 12,
// Required to calculate where the swimlane ends
width: X_AXIS_RIGHT_OVERFLOW * 2,
},
brushMask: {
fill: isDarkTheme ? 'rgb(30,31,35,80%)' : 'rgb(247,247,247,50%)',
Expand Down Expand Up @@ -480,21 +482,6 @@ export const SwimlaneContainer: FC<SwimlaneProps> = ({
)}
</div>
</div>
{swimlaneType === SWIMLANE_TYPE.OVERALL &&
showSwimlane &&
xDomain !== undefined &&
!isLoading && (
<MlTooltipComponent>
{(tooltipService) => (
<SwimlaneAnnotationContainer
chartWidth={chartWidth}
domain={xDomain}
annotationsData={annotationsData}
tooltipService={tooltipService}
/>
)}
</MlTooltipComponent>
)}
</>
</EuiFlexItem>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { OVERALL_LABEL, VIEW_BY_JOB_LABEL } from '../explorer/explorer_constants
import { MlResultsService } from './results_service';
import { EntityField } from '../../../common/util/anomaly_utils';
import { InfluencersFilterQuery } from '../../../common/types/es_client';
import { isPopulatedObject } from '../../../common';

/**
* Service for retrieving anomaly swim lanes data.
Expand All @@ -49,6 +50,21 @@ export class AnomalyTimelineService {
this.timeFilter.enableTimeRangeSelector();
}

public static isSwimlaneData(arg: unknown): arg is SwimlaneData {
return isPopulatedObject(arg, ['interval', 'points', 'laneLabels']);
}

public static isOverallSwimlaneData(arg: unknown): arg is OverallSwimlaneData {
// Important to check if all laneLabels are 'Overall'
// because ViewBySwimLaneData also extends OverallSwimlaneData
return (
this.isSwimlaneData(arg) &&
isPopulatedObject(arg, ['earliest', 'latest']) &&
arg.laneLabels.length === 1 &&
arg.laneLabels[0] === OVERALL_LABEL
);
}

public setTimeRange(timeRange: TimeRange) {
this._customTimeRange = timeRange;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1132,10 +1132,12 @@ class TimeseriesChartIntl extends Component {
.attr('height', ANNOTATION_SYMBOL_HEIGHT)
.attr('width', (d) => {
const start = Math.max(this.contextXScale(moment(d.timestamp)) + 1, contextXRangeStart);
const end =
const end = Math.min(
contextXRangeEnd,
typeof d.end_timestamp !== 'undefined'
? this.contextXScale(moment(d.end_timestamp)) - 1
: start + ANNOTATION_MIN_WIDTH;
: start + ANNOTATION_MIN_WIDTH
);
const width = Math.max(ANNOTATION_MIN_WIDTH, end - start);
return width;
});
Expand Down

0 comments on commit 0913711

Please sign in to comment.