import { scaleLinear as d3ScaleLinear , scaleBand as d3ScaleBand , } from 'd3-scale' ; import { ARGUMENT_DOMAIN , VALUE_DOMAIN , } from '../constants' ; import { ScaleObject , FactoryFn , DomainInfo , NumberArray , DomainBounds , DomainItems , } from '../types' ; internal export const scaleLinear = d3ScaleLinear as any ; export const scaleBand = ( ) => ( d3ScaleBand ( ) . paddingInner ( 0.3 ) . paddingOuter ( 0.15 ) as any ) ; internal export const isHorizontal = ( name , rotated ) => ( name === ARGUMENT_DOMAIN === ! rotated ) ; const makeScaleHelper = < T extends Function > ( linear , band ) => { const func = ( scale , ... args ) => { const choosen = 'bandwidth' in scale ? band : linear ; return choosen ( scale , ... args ) ; } ; return func as T ; } ; const getLinearScaleWidth = ( _ ) => 0 ; const getBandScaleWidth = ( scale ) => scale . bandwidth ! ( ) ; internal export const getWidth = makeScaleHelper ( getLinearScaleWidth , getBandScaleWidth ) ; internal export const getValueDomainName = ( name ? ) => name || VALUE_DOMAIN ; const floatsEqual = ( a , b ) => Math . abs ( a - b ) < Number . EPSILON ; internal export const rangesEqual = ( r1 , r2 ) => floatsEqual ( r1 [ 0 ] , r2 [ 0 ] ) && floatsEqual ( r1 [ 1 ] , r2 [ 1 ] ) ; const wrapLinearScale = ( scale ) => scale ; const wrapBandScale = ( scale ) : ScaleObject => { const ret = ( value ) => scale ( value ) + scale . bandwidth ! ( ) / 2 ; Object . assign ( ret , scale ) ; return ret ; } ; const wrapScale = makeScaleHelper ( wrapLinearScale , wrapBandScale ) ; internal export const makeScale = ( { factory , domain } , range ) => { const scale = ( factory || scaleLinear ) ( ) . domain ( domain ) . range ( range ) ; return wrapScale ( scale ) ; } ; const scaleLinearBounds = ( scale , bounds ) : NumberArray => ( bounds . map ( scale ) as NumberArray ) ; const scaleBandBounds = ( scale , bounds ) : NumberArray => { const cleanScale = scale . copy ( ) . paddingInner ! ( 0 ) . paddingOuter ! ( 0 ) ; const fullRange = scale . range ( ) ; const sign = Math . sign ( fullRange [ 1 ] - fullRange [ 0 ] ) ; return sign >= 0 ? [ cleanScale ( bounds [ 0 ] ) , cleanScale ( bounds [ 1 ] ) + cleanScale . bandwidth ! ( ) ] : [ cleanScale ( bounds [ 0 ] ) + cleanScale . bandwidth ! ( ) , cleanScale ( bounds [ 1 ] ) ] ; } ; const moveLinearScaleBounds = ( scale , bounds , delta ) : DomainBounds => { const fullRange = scale . range ( ) ; const sign = Math . sign ( fullRange [ 1 ] - fullRange [ 0 ] ) ; const range = scaleLinearBounds ( scale , bounds ) ; let r0 = range [ 0 ] + delta ; let r1 = range [ 1 ] + delta ; if ( Math . sign ( r0 - fullRange [ 0 ] ) !== sign ) { r0 = fullRange [ 0 ] ; r1 = r0 + range [ 1 ] - range [ 0 ] ; } if ( Math . sign ( fullRange [ 1 ] - r1 ) !== sign ) { r1 = fullRange [ 1 ] ; r0 = r1 - range [ 1 ] + range [ 0 ] ; } const newBounds = [ scale . invert ! ( r0 ) , scale . invert ! ( r1 ) ] ; return rangesEqual ( bounds , newBounds ) ? bounds : newBounds ; } ; const adjustBandScaleMoveStep = ( delta , step ) => { const ratio = Math . abs ( delta / step ) ; const sign = Math . sign ( delta / step ) ; if ( ratio >= 0.5 ) { return sign * Math . round ( ratio ) ; } if ( ratio >= 0.3 ) { return sign ; } if ( Math . abs ( delta ) > 30 ) { return sign ; } return 0 ; } ; const moveBandScaleBounds = ( scale , bounds , delta ) : DomainBounds => { const domain = scale . domain ( ) ; const fullRange = scale . range ( ) ; const step = ( fullRange [ 1 ] - fullRange [ 0 ] ) / domain . length ; const rangeStep = adjustBandScaleMoveStep ( delta , step ) ; if ( rangeStep === 0 ) { return bounds ; } const range = scaleBounds ( scale , bounds ) ; const range0 = Math . round ( ( range [ 0 ] - fullRange [ 0 ] ) / step ) ; const range1 = range0 + Math . round ( ( range [ 1 ] - range [ 0 ] ) / step ) - 1 ; let new0 = range0 + rangeStep ; let new1 = range1 + rangeStep ; if ( new0 < 0 ) { new0 = 0 ; new1 = new0 + range1 - range0 ; } if ( new1 > domain . length - 1 ) { new1 = domain . length - 1 ; new0 = new1 - range1 + range0 ; } if ( new0 === range0 || new1 === range1 ) { return bounds ; } return [ domain [ new0 ] , domain [ new1 ] ] ; } ; const LINEAR_SCALE_ZOOMING_THRESHOLD = 1000 ; const growLinearScaleBounds = ( scale , bounds , delta , anchor ) : DomainBounds => { const fullRange = scale . range ( ) ; const minRangeThreshold = ( fullRange [ 1 ] - fullRange [ 0 ] ) / LINEAR_SCALE_ZOOMING_THRESHOLD ; const sign = Math . sign ( fullRange [ 1 ] - fullRange [ 0 ] ) ; const range = scaleBounds ( scale , bounds ) ; if ( delta > 0 && Math . abs ( range [ 1 ] - range [ 0 ] ) <= Math . abs ( minRangeThreshold ) ) { return bounds ; } if ( delta < 0 && Math . abs ( range [ 1 ] - range [ 0 ] ) >= Math . abs ( fullRange [ 1 ] - fullRange [ 0 ] ) ) { return bounds ; } const t = Math . abs ( ( anchor - range [ 0 ] ) / ( range [ 1 ] - range [ 0 ] ) ) ; let r0 = range [ 0 ] + sign * delta * 2 * t ; let r1 = range [ 1 ] - sign * delta * 2 * ( 1 - t ) ; if ( Math . sign ( r0 - fullRange [ 0 ] ) !== sign ) { r0 = fullRange [ 0 ] ; } if ( Math . sign ( fullRange [ 1 ] - r1 ) !== sign ) { r1 = fullRange [ 1 ] ; } if ( Math . sign ( r1 - r0 ) !== sign || Math . abs ( r1 - r0 ) < Math . abs ( minRangeThreshold ) ) { if ( Math . abs ( r0 - range [ 0 ] ) < Math . abs ( minRangeThreshold / 2 ) ) { r0 = range [ 0 ] ; r1 = r0 + minRangeThreshold ; } else if ( Math . abs ( r1 - range [ 1 ] ) < Math . abs ( minRangeThreshold / 2 ) ) { r1 = range [ 1 ] ; r0 = r1 - minRangeThreshold ; } else { r0 = anchor - minRangeThreshold / 2 ; r1 = anchor + minRangeThreshold / 2 ; } } const newBounds = [ scale . invert ! ( r0 ) , scale . invert ! ( r1 ) ] ; return rangesEqual ( bounds , newBounds ) ? bounds : newBounds ; } ; const growBandScaleBounds = ( scale , bounds , delta , anchor ) : DomainBounds => { const domain = scale . domain ( ) ; const fullRange = scale . range ( ) ; const step = ( fullRange [ 1 ] - fullRange [ 0 ] ) / domain . length ; const range = scaleBounds ( scale , bounds ) ; const range0 = Math . round ( ( range [ 0 ] - fullRange [ 0 ] ) / step ) ; const range1 = range0 + Math . round ( ( range [ 1 ] - range [ 0 ] ) / step ) - 1 ; const rangeStep = Math . sign ( delta ) ; if ( ( rangeStep === 0 ) || ( rangeStep > 0 && range0 === range1 ) || ( rangeStep < 0 && range0 === 0 && range1 === domain . length - 1 ) ) { return bounds ; } const t = Math . abs ( ( anchor - range [ 0 ] ) / ( range [ 1 ] - range [ 0 ] ) ) ; let new0 = range0 + Math . round ( rangeStep * 2 * t ) ; let new1 = range1 - Math . round ( rangeStep * 2 * ( 1 - t ) ) ; if ( new0 < 0 ) { new0 = 0 ; } if ( new1 > domain . length - 1 ) { new1 = domain . length - 1 ; } if ( new0 > new1 ) { if ( t <= 0.5 ) { new1 = new0 ; } else { new0 = new1 ; } } if ( new0 === range0 && new1 === range1 ) { return bounds ; } return [ domain [ new0 ] , domain [ new1 ] ] ; } ; const invertLinearScaleBounds = ( scale , range ) : DomainBounds => { const fullRange = scale . range ( ) ; const match = Math . sign ( fullRange [ 1 ] - fullRange [ 0 ] ) === Math . sign ( range [ 1 ] - range [ 0 ] ) ; return [ scale . invert ! ( range [ match ? 0 : 1 ] ) , scale . invert ! ( range [ match ? 1 : 0 ] ) , ] ; } ; const matchPointToBand = ( domain , range , p ) => { const i = Math . floor ( domain . length * ( p - range [ 0 ] ) / ( range [ 1 ] - range [ 0 ] ) ) ; return domain [ Math . min ( i , domain . length - 1 ) ] ; } ; const invertBandScaleBounds = ( scale , range ) : DomainBounds => { const domain = scale . domain ( ) ; const fullRange = scale . range ( ) ; return [ matchPointToBand ( domain , fullRange , range [ 0 ] ) , matchPointToBand ( domain , fullRange , range [ 1 ] ) , ] ; } ; internal export const scaleBounds = makeScaleHelper ( scaleLinearBounds , scaleBandBounds ) ; internal export const moveBounds = makeScaleHelper ( moveLinearScaleBounds , moveBandScaleBounds ) ; internal export const growBounds = makeScaleHelper ( growLinearScaleBounds , growBandScaleBounds ) ; internal export const invertBoundsRange = makeScaleHelper ( invertLinearScaleBounds , invertBandScaleBounds ) ;