import { dateTime , dateTimeParse , DisplayProcessor , Field , getDisplayProcessor , GrafanaTheme2 , TimeRange , TimeZone , } from '@grafana/data' ; import * as d3 from 'd3' ; export interface Point { time : number ; value : number ; } interface PointSet { time : number ; values : Point [ ] ; } export interface BucketPoint { dayMillis : number ; bucketStartMillis : number ; value : number ; } export interface BucketData { numBuckets : number ; points : BucketPoint [ ] ; valueField : Field < number > ; timeField : Field < number > ; } const reduce = ( agg , calculation ) : Point [ ] => { return agg . map ( ( { time , values } ) => ( { time : time , value : calculation ( values . map ( ( { value } ) => value ) ) , } ) ) ; } ; export const groupByMinutes = ( points , minutes , timeZone ) : PointSet [ ] => { const rounded = points . map ( ( point ) => { const intervalStart = dateTime ( point . time ) ; intervalStart . subtract ( intervalStart . minute ? intervalStart . minute ( ) % minutes : 0.0 , 'minutes' ) ; return { [ intervalStart . valueOf ( ) ] : point , } ; } ) ; return Object . entries ( rounded . reduce ( ( acc , val ) => { const intervalStartMillis = parseFloat ( Object . keys ( val ) [ 0 ] ) ; ( acc [ intervalStartMillis ] = acc [ intervalStartMillis ] || [ ] ) . push ( val [ intervalStartMillis ] ) ; return acc ; } , { } ) ) . map ( ( [ key , values ] ) => ( { time : parseFloat ( key ) , values : values as Point [ ] , } ) ) ; } ; export const groupByDay = ( points ) : PointSet [ ] => { const rounded = points . map ( ( point ) => ( { [ dateTime ( point . time ) . startOf ( 'day' ) . valueOf ( ) ] : point , } ) ) ; return Object . entries ( rounded . reduce ( ( acc , val ) => { const dayMillis = parseFloat ( Object . keys ( val ) [ 0 ] ) ; ( acc [ dayMillis ] = acc [ dayMillis ] || [ ] ) . push ( val [ dayMillis ] ) ; return acc ; } , { } ) ) . map ( ( [ key , values ] ) => ( { time : parseFloat ( key ) , values : values as Point [ ] , } ) ) ; } ; export const defaultDisplay = ( value ) => ( { numeric : value , text : value . toString ( ) } ) ; const minutesPerDay = 24 * 60 ; export const bucketize = ( timeField , valueField , timeZone , timeRange , dailyInterval , theme ) : BucketData => { const rows = Array . from ( { length : timeField . values . length } , ( _ , i ) => ( { time : timeField . values . get ( i ) , value : valueField . values . get ( i ) , } ) ) ; const extents = [ dateTimeParse ( timeRange . from . valueOf ( ) , { timeZone } ) . startOf ( 'day' ) , dateTimeParse ( timeRange . to . valueOf ( ) , { timeZone } ) . endOf ( 'day' ) , ] ; const rowsWithinTimeRange = rows . filter ( ( row ) => { const curr = dateTimeParse ( row . time , { timeZone } ) ; return extents [ 0 ] . valueOf ( ) <= curr . valueOf ( ) && curr . valueOf ( ) < extents [ 1 ] . valueOf ( ) ; } ) ; const rowsWithinDailyInterval = rowsWithinTimeRange . filter ( ( row ) => { const dt = dateTimeParse ( row . time , { timeZone } ) ; const hour = dt . hour ? dt . hour ( ) : 0.0 ; return dailyInterval [ 0 ] <= hour && hour < dailyInterval [ 1 ] ; } ) ; const customData = valueField . config . custom ; const groupedByMinutes = groupByMinutes ( rowsWithinDailyInterval , customData . groupBy , timeZone ) ; const reducedMinutes = reduce ( groupedByMinutes , calculations [ customData . calculation ] ) ; const aggregatedPoints = groupByDay ( reducedMinutes ) . flatMap ( ( { time , values } ) => values . map ( ( { time , value } ) => ( { dayMillis : time , bucketStartMillis : time , value , } ) ) ) ; recalculateMinMax ( valueField , aggregatedPoints . map ( ( { value } ) => value ) , theme , timeZone ) ; return { numBuckets : Math . floor ( minutesPerDay / customData . groupBy ) , points : aggregatedPoints , valueField , timeField , } ; } ; const recalculateMinMax = ( field , values , theme , timeZone ) => { const autoMin = ! field . config . min || field . config . min === field . state ?. calcs ?. min ; const autoMax = ! field . config . max || field . config . max === field . state ?. calcs ?. max ; if ( autoMin ) { field . config . min = d3 . min ( values ) ; } if ( autoMax ) { field . config . max = d3 . max ( values ) ; } field . display = getDisplayProcessor ( { field , theme , timeZone , } ) ; } ; const calculations = { mean : ( vals ) => d3 . mean ( vals ) , sum : ( vals ) => d3 . sum ( vals ) , count : ( vals ) => vals . length , min : ( vals ) => d3 . min ( vals ) , max : ( vals ) => d3 . max ( vals ) , first : ( vals ) => vals [ 0 ] , last : ( vals ) => vals [ vals . length - 1 ] , } ;