import { bisector } from 'd3-array' ; import { Annotation , Annotations , AnnotationValue , Threshold , YAnnotation } from '../types' ; import { displayDate } from '../../../../utils/time' ; import { Primitive , ViewPort } from '../../../../utils/dataTypes' ; import { isValid } from '../../../../utils/predicates' ; import { isNumeric } from '../../../../utils/number' ; import { COMPARISON_OPERATOR } from '../constants' ; param thresholds export const getNumberThresholds = ( thresholds ) : Threshold [ ] => thresholds . filter ( threshold => isNumeric ( threshold . value ) ) ; param annotations export const getNumberAnnotations = ( annotations ) : Annotations => { const yAnnotations = annotations && Array . isArray ( annotations . y ) && annotations . y ; if ( ! yAnnotations ) { return annotations ; } const numberAnnotations = yAnnotations . filter ( annotation => isNumeric ( annotation . value ) ) ; if ( numberAnnotations . length < 1 ) { const { y , ... annotationProps } = annotations ; return annotationProps ; } return { ... annotations , y : numberAnnotations , } ; } ; const formatValueString = ( thresholdValue ) : string => { const upperLimit = 99999 ; const lowerLimit = - 9999 ; const lowerBound = 0.001 ; const digits = 5 ; const limit = 4 ; if ( thresholdValue > upperLimit || thresholdValue < lowerLimit ) { return thresholdValue . toExponential ( digits - limit ) . toString ( ) ; } if ( ( thresholdValue < lowerBound && thresholdValue > 0 ) || ( thresholdValue > - lowerBound && thresholdValue < 0 ) ) { return thresholdValue . toExponential ( digits - limit ) . toString ( ) ; } return thresholdValue . toString ( ) . length > digits ? thresholdValue . toString ( ) . substr ( 0 , digits ) : thresholdValue . toString ( ) ; } ; const valueDisplayText = ( { value , resolution , viewport , formatText , } ) : string => { if ( typeof value === 'number' ) { if ( formatText ) { return formatValueString ( value ) ; } return value . toString ( ) ; } if ( typeof value === 'string' ) { return value ; } return displayDate ( value , resolution , viewport ) ; } ; /**
 * Getters
 */ export const getColor = ( annotation ) => annotation . color ; export const getValueAndText = ( { annotation , resolution , viewport , } ) : string => { const valueText = annotation . showValue ? valueDisplayText ( { value : annotation . value , resolution , viewport , formatText : false } ) : null ; const labelText = annotation . label && annotation . label . show ? annotation . label . text : null ; if ( labelText && valueText ) { return `${ labelText } (${ valueText })` ; } if ( ! valueText && labelText ) { return labelText ; } if ( ! labelText && valueText ) { return `(${ valueText })` ; } return '' ; } ; export const getText = ( annotation ) : string => { const labelText = annotation . label && annotation . label . show ? annotation . label . text : null ; if ( labelText ) { return `${ labelText }` ; } return '' ; } ; export const getLabelTextVisibility = ( annotation ) : string => { if ( annotation . label && annotation . label . show ) { return 'inline' ; } return 'none' ; } ; export const getValueTextVisibility = ( annotation ) : string => { if ( annotation . showValue ) { return 'inline' ; } return 'none' ; } ; export const getValueAndTextVisibility = ( annotation ) : string => { const showValueText = annotation . showValue ; const showLabelText = annotation . label && annotation . label . show ; if ( showValueText || showLabelText ) { return 'inline' ; } return 'none' ; } ; export const getValueText = ( { annotation , resolution , viewport , formatText , } ) : string => { const valueText = annotation . showValue ? valueDisplayText ( { value : annotation . value , resolution , viewport , formatText } ) : null ; if ( valueText ) { return `${ valueText }` ; } return '' ; } ; export const isThresholdBreached = ( value , threshold ) : boolean => { const dataStreamValue = isNumeric ( value ) ? Number ( value ) : value ; const thresholdValue = isNumeric ( threshold . value ) ? Number ( threshold . value ) : threshold . value ; const thresholdComparison = threshold . comparisonOperator ; if ( typeof dataStreamValue === 'number' && typeof thresholdValue === 'number' ) { switch ( thresholdComparison ) { case COMPARISON_OPERATOR . GREATER_THAN : return dataStreamValue > thresholdValue ; case COMPARISON_OPERATOR . GREATER_THAN_EQUAL : return dataStreamValue >= thresholdValue ; case COMPARISON_OPERATOR . LESS_THAN : return dataStreamValue < thresholdValue ; case COMPARISON_OPERATOR . LESS_THAN_EQUAL : return dataStreamValue <= thresholdValue ; case COMPARISON_OPERATOR . EQUAL : return dataStreamValue === thresholdValue ; default : throw new Error ( `Unsupported number threshold comparison operator: ${ thresholdComparison }` ) ; } } if ( typeof dataStreamValue === 'string' && typeof thresholdValue === 'string' ) { if ( thresholdComparison === COMPARISON_OPERATOR . EQUAL ) { return dataStreamValue === thresholdValue ; } throw new Error ( `Unsupported string threshold comparison operator: ${ thresholdComparison }` ) ; } return false ; } ; const thresholdBisector = bisector ( ( threshold ) => threshold . value ) . left ; /**
 * This a map of comparison operator to order. The higher the order means higher the precedence.
 */ const operatorOrder = { [ COMPARISON_OPERATOR . LESS_THAN_EQUAL ] : 1 , [ COMPARISON_OPERATOR . LESS_THAN ] : 2 , [ COMPARISON_OPERATOR . GREATER_THAN_EQUAL ] : 3 , [ COMPARISON_OPERATOR . GREATER_THAN ] : 4 , [ COMPARISON_OPERATOR . EQUAL ] : 5 , } ; /**
 * Given a list of thresholds, we sort the by the value of the threshold from least to greatest and
 * by the comparators order from least to greatest
 *
 * In the event of multiple thresholds with the same value, the threshold with the highest order will be the
 * one that takes precedence, and other rules colliding will be ignored.
 *
 * Below is an example of sorted threshold:
 *   2 2  2 2     5 5  5 5
 *  >= > <= <    >= > <= <
 */ export const sortThreshold = ( thresholds ) : Threshold [ ] => [ ... thresholds ] . sort ( ( a , b ) => { if ( a . value === b . value ) { return operatorOrder [ a . comparisonOperator ] - operatorOrder [ b . comparisonOperator ] ; } return ( a . value as number ) - ( b . value as number ) ; } ) ; /**
 * Gets the most relevant threshold which is considered breached by a given value.
 *
 * The most relevant threshold to a point is determined by the threshold value and its comparator.
 *
 * When there are two or more relevant thresholds to a point,
 *
 * 1) When the value is positive, then we will take the upper threshold, which is the greater one
 *
 * 2) When the value is negative, then we will take the lower threshold, which is the lesser one.
 */ export const getBreachedThreshold = ( value , thresholds ) : Threshold | undefined => { if ( thresholds . length === 0 ) { return undefined ; } if ( typeof value === 'string' ) { return thresholds . find ( threshold => isThresholdBreached ( value , threshold ) ) || undefined ; } const numberThresholds = getNumberThresholds ( thresholds ) ; const sortedThresholds = sortThreshold ( numberThresholds ) ; const idx = thresholdBisector ( sortedThresholds , value ) ; let annotationLeft = sortedThresholds [ idx - 1 ] ; let annotationRight = sortedThresholds [ idx ] ; if ( idx === numberThresholds . length && numberThresholds . length > 1 && sortedThresholds [ idx - 1 ] . value === sortedThresholds [ idx - 2 ] . value ) { annotationLeft = sortedThresholds [ idx - 2 ] ; annotationRight = sortedThresholds [ idx - 1 ] ; } if ( idx === 0 && numberThresholds . length > 1 && sortedThresholds [ idx ] . value === sortedThresholds [ idx + 1 ] . value ) { annotationLeft = sortedThresholds [ idx ] ; annotationRight = sortedThresholds [ idx + 1 ] ; } if ( annotationLeft == null && annotationRight == null ) { return undefined ; } if ( annotationLeft != null && annotationRight == null ) { return isThresholdBreached ( value , annotationLeft ) ? annotationLeft : undefined ; } if ( annotationLeft == null && annotationRight != null ) { return isThresholdBreached ( value , annotationRight ) ? annotationRight : undefined ; } if ( isThresholdBreached ( value , annotationLeft ) && isThresholdBreached ( value , annotationRight ) ) { return value >= 0 ? annotationRight : annotationLeft ; } if ( isThresholdBreached ( value , annotationLeft ) && ! isThresholdBreached ( value , annotationRight ) ) { return annotationLeft ; } if ( ! isThresholdBreached ( value , annotationLeft ) && isThresholdBreached ( value , annotationRight ) ) { return annotationRight ; } return undefined ; } ; export const isThreshold = isValid ( ( maybeThreshold ) => maybeThreshold . comparisonOperator != null ) ; export const getThresholds = ( annotations ) : Threshold [ ] => annotations && annotations . y ? annotations . y . filter ( isThreshold ) : [ ] ;