import d3 from 'd3' ; import moment from 'moment' ; import { ANNOTATION_TYPE } from '../../../../../common/constants/annotations' ; import { Annotation , Annotations } from '../../../../../common/types/annotations' ; import { Dictionary } from '../../../../../common/types/common' ; import { TimeseriesChart } from './timeseries_chart' ; import { AnnotationUpdatesService } from '../../../services/annotations_service' ; export const ANNOTATION_MASK_ID = 'mlAnnotationMask' ; export function getAnnotationBrush ( this ) { const { annotationUpdatesService } = this . props ; const focusXScale = this . focusXScale ; const annotateBrush = d3 . svg . brush ( ) . x ( focusXScale ) . on ( 'brushend' , brushend . bind ( this ) ) ; function brushend ( this ) { const { selectedJob } = this . props ; const extent = annotateBrush . extent ( ) as any ; const timestamp = extent [ 0 ] . getTime ( ) ; const endTimestamp = extent [ 1 ] . getTime ( ) ; if ( timestamp === endTimestamp ) { annotationUpdatesService . setValue ( null ) ; return ; } const annotation = { timestamp , end_timestamp : endTimestamp , annotation : '' , job_id : selectedJob . job_id , type : ANNOTATION_TYPE . ANNOTATION , } ; annotationUpdatesService . setValue ( annotation ) ; } return annotateBrush ; } export function getAnnotationLevels ( focusAnnotationData ) { const levels = { } ; focusAnnotationData . forEach ( ( d , i ) => { if ( d . key !== undefined ) { const longerAnnotations = focusAnnotationData . filter ( ( d2 , i2 ) => i2 < i ) ; levels [ d . key ] = longerAnnotations . reduce ( ( level , d2 ) => { if ( d . end_timestamp === undefined || d2 . end_timestamp === undefined || d2 . key === undefined ) { return level ; } if ( ( d2 . timestamp < d . timestamp && d2 . end_timestamp < d . timestamp ) || ( d2 . timestamp > d . end_timestamp && d2 . end_timestamp > d . end_timestamp ) ) { return level ; } return levels [ d2 . key ] + 1 ; } , 0 ) ; } } ) ; return levels ; } const ANNOTATION_DEFAULT_LEVEL = 1 ; const ANNOTATION_LEVEL_HEIGHT = 28 ; const ANNOTATION_UPPER_RECT_MARGIN = 0 ; const ANNOTATION_UPPER_TEXT_MARGIN = - 7 ; export const ANNOTATION_MIN_WIDTH = 8 ; const ANNOTATION_TEXT_VERTICAL_OFFSET = 26 ; const ANNOTATION_TEXT_RECT_VERTICAL_OFFSET = 12 ; const ANNOTATION_TEXT_RECT_WIDTH = 24 ; const ANNOTATION_TEXT_RECT_HEIGHT = 20 ; export function renderAnnotations ( focusChart , focusAnnotationData , focusZoomPanelHeight , focusChartHeight , focusXScale , showAnnotations , showFocusChartTooltip , hideFocusChartTooltip , annotationUpdatesService ) { const upperRectMargin = ANNOTATION_UPPER_RECT_MARGIN ; const upperTextMargin = ANNOTATION_UPPER_TEXT_MARGIN ; const durations = { } ; focusAnnotationData . forEach ( ( d ) => { if ( d . key !== undefined ) { const duration = ( d . end_timestamp || 0 ) - d . timestamp ; durations [ d . key ] = duration ; } } ) ; focusAnnotationData . sort ( ( a , b ) => { if ( a . key === undefined || b . key === undefined ) { return 0 ; } return durations [ b . key ] - durations [ a . key ] ; } ) ; const levelHeight = ANNOTATION_LEVEL_HEIGHT ; const levels = getAnnotationLevels ( focusAnnotationData ) ; const onAnnotationMouseOver = function ( this , d ) { showFocusChartTooltip ( d , this ) ; } ; const onAnnotationClick = ( d ) => { annotationUpdatesService . setValue ( null ) ; annotationUpdatesService . setValue ( d ) ; } ; const annotations = focusChart . select ( '.mlAnnotations' ) . selectAll ( 'g.mlAnnotation' ) . data ( focusAnnotationData || [ ] , ( d ) => d . _id || '' ) ; annotations . enter ( ) . append ( 'g' ) . classed ( 'mlAnnotation' , true ) ; const rects = annotations . selectAll ( '.mlAnnotationRect' ) . data ( ( d ) => [ d ] ) ; rects . enter ( ) . append ( 'rect' ) . classed ( 'mlAnnotationRect' , true ) . attr ( 'mask' , `url(#${ ANNOTATION_MASK_ID })` ) . on ( 'mouseover' , onAnnotationMouseOver ) . on ( 'mouseout' , hideFocusChartTooltip ) . on ( 'click' , onAnnotationClick ) ; rects . attr ( 'x' , ( d ) => { const date = moment ( d . timestamp ) ; return focusXScale ( date ) ; } ) . attr ( 'y' , ( d ) => { const level = d . key !== undefined ? levels [ d . key ] : ANNOTATION_DEFAULT_LEVEL ; return focusZoomPanelHeight + 1 + upperRectMargin + level * levelHeight ; } ) . attr ( 'height' , ( d ) => { const level = d . key !== undefined ? levels [ d . key ] : ANNOTATION_DEFAULT_LEVEL ; return focusChartHeight - 2 - upperRectMargin - level * levelHeight ; } ) . attr ( 'width' , ( d ) => { const s = focusXScale ( moment ( d . timestamp ) ) + 1 ; const e = typeof d . end_timestamp !== 'undefined' ? focusXScale ( moment ( d . end_timestamp ) ) - 1 : s + ANNOTATION_MIN_WIDTH ; const width = Math . max ( ANNOTATION_MIN_WIDTH , e - s ) ; return width ; } ) ; rects . exit ( ) . remove ( ) ; const textRects = annotations . selectAll ( '.mlAnnotationTextRect' ) . data ( ( d ) => [ d ] ) ; const texts = annotations . selectAll ( '.mlAnnotationText' ) . data ( ( d ) => [ d ] ) ; textRects . enter ( ) . append ( 'rect' ) . classed ( 'mlAnnotationTextRect' , true ) . attr ( 'width' , ANNOTATION_TEXT_RECT_WIDTH ) . attr ( 'height' , ANNOTATION_TEXT_RECT_HEIGHT ) . on ( 'mouseover' , onAnnotationMouseOver ) . on ( 'mouseout' , hideFocusChartTooltip ) . on ( 'click' , onAnnotationClick ) ; texts . enter ( ) . append ( 'text' ) . classed ( 'mlAnnotationText' , true ) . on ( 'mouseover' , onAnnotationMouseOver ) . on ( 'mouseout' , hideFocusChartTooltip ) . on ( 'click' , onAnnotationClick ) ; function labelXOffset ( ts ) { const earliestMs = focusXScale . domain ( ) [ 0 ] ; const latestMs = focusXScale . domain ( ) [ 1 ] ; const date = moment ( ts ) ; const minX = Math . max ( focusXScale ( earliestMs ) , focusXScale ( date ) ) ; const maxOffset = 32 ; return Math . min ( focusXScale ( latestMs ) - maxOffset , minX ) ; } texts . attr ( 'x' , ( d ) => { const leftInnerOffset = 17 ; return labelXOffset ( d . timestamp ) + leftInnerOffset ; } ) . attr ( 'y' , ( d ) => { const level = d . key !== undefined ? levels [ d . key ] : ANNOTATION_DEFAULT_LEVEL ; return ( focusZoomPanelHeight + upperTextMargin + ANNOTATION_TEXT_VERTICAL_OFFSET + level * levelHeight ) ; } ) . text ( ( d ) => d . key as any ) ; textRects . attr ( 'x' , ( d ) => { const leftInnerOffset = 5 ; return labelXOffset ( d . timestamp ) + leftInnerOffset ; } ) . attr ( 'y' , ( d ) => { const level = d . key !== undefined ? levels [ d . key ] : ANNOTATION_DEFAULT_LEVEL ; return ( focusZoomPanelHeight + upperTextMargin + ANNOTATION_TEXT_RECT_VERTICAL_OFFSET + level * levelHeight ) ; } ) ; textRects . exit ( ) . remove ( ) ; texts . exit ( ) . remove ( ) ; annotations . classed ( 'mlAnnotationHidden' , ! showAnnotations ) ; annotations . exit ( ) . remove ( ) ; } export function getAnnotationWidth ( annotation , focusXScale ) { const start = focusXScale ( annotation . timestamp ) + 1 ; const end = typeof annotation . end_timestamp !== 'undefined' ? focusXScale ( annotation . end_timestamp ) - 1 : start + ANNOTATION_MIN_WIDTH ; const width = Math . max ( ANNOTATION_MIN_WIDTH , end - start ) ; return width ; } export function highlightFocusChartAnnotation ( annotation ) { const annotations = d3 . selectAll ( '.mlAnnotation' ) ; annotations . each ( function ( d ) { const element = d3 . select ( this ) ; if ( d . _id === annotation . _id ) { element . selectAll ( '.mlAnnotationRect' ) . classed ( 'mlAnnotationRect-isHighlight' , true ) ; } else { element . selectAll ( '.mlAnnotationTextRect' ) . classed ( 'mlAnnotationTextRect-isBlur' , true ) ; element . selectAll ( '.mlAnnotationText' ) . classed ( 'mlAnnotationText-isBlur' , true ) ; element . selectAll ( '.mlAnnotationRect' ) . classed ( 'mlAnnotationRect-isBlur' , true ) ; } } ) ; } export function unhighlightFocusChartAnnotation ( ) { const annotations = d3 . selectAll ( '.mlAnnotation' ) ; annotations . each ( function ( ) { const element = d3 . select ( this ) ; element . selectAll ( '.mlAnnotationTextRect' ) . classed ( 'mlAnnotationTextRect-isBlur' , false ) ; element . selectAll ( '.mlAnnotationRect' ) . classed ( 'mlAnnotationRect-isHighlight' , false ) . classed ( 'mlAnnotationRect-isBlur' , false ) ; element . selectAll ( '.mlAnnotationText' ) . classed ( 'mlAnnotationText-isBlur' , false ) ; } ) ; }