import React , { MouseEvent , RefObject } from 'react' ; import { connect } from 'react-redux' ; import { bindActionCreators , Dispatch } from 'redux' ; import { Color , Colors } from '../../../../common/colors' ; import { ScreenReaderSummary , ScreenReaderPartitionTable } from '../../../../components/accessibility' ; import { clearCanvas } from '../../../../renderers/canvas' ; import { SettingsSpec } from '../../../../specs/settings' ; import { onChartRendered } from '../../../../state/actions/chart' ; import { GlobalChartState } from '../../../../state/chart_state' ; import { A11ySettings , DEFAULT_A11Y_SETTINGS , getA11ySettingsSelector , } from '../../../../state/selectors/get_accessibility_config' ; import { getChartContainerDimensionsSelector } from '../../../../state/selectors/get_chart_container_dimensions' ; import { getChartThemeSelector } from '../../../../state/selectors/get_chart_theme' ; import { getInternalIsInitializedSelector , InitStatus } from '../../../../state/selectors/get_internal_is_intialized' ; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs' ; import { Dimensions } from '../../../../utils/dimensions' ; import { MODEL_KEY } from '../../layout/config' ; import { hasMostlyRTLLabels , nullShapeViewModel , QuadViewModel , ShapeViewModel , SmallMultiplesDescriptors , } from '../../layout/types/viewmodel_types' ; import { INPUT_KEY } from '../../layout/utils/group_by_rollup' ; import { isSimpleLinear , isWaffle } from '../../layout/viewmodel/viewmodel' ; import { partitionDrilldownFocus , partitionMultiGeometries } from '../../state/selectors/geometries' ; import { renderLinearPartitionCanvas2d } from './canvas_linear_renderers' ; import { renderPartitionCanvas2d } from './canvas_renderers' ; import { renderWrappedPartitionCanvas2d } from './canvas_wrapped_renderers' ; internal export interface ContinuousDomainFocus { currentFocusX0 : number ; currentFocusX1 : number ; prevFocusX0 : number ; prevFocusX1 : number ; } internal export interface IndexedContinuousDomainFocus extends ContinuousDomainFocus , SmallMultiplesDescriptors { } interface ReactiveChartStateProps { isRTL : boolean ; initialized : boolean ; geometries : ShapeViewModel ; geometriesFoci : ContinuousDomainFocus [ ] ; multiGeometries : ShapeViewModel [ ] ; chartDimensions : Dimensions ; a11ySettings : A11ySettings ; debug : SettingsSpec [ 'debug' ] ; background : Color ; } interface ReactiveChartDispatchProps { onChartRendered : typeof onChartRendered ; } interface ReactiveChartOwnProps { forwardStageRef : RefObject < HTMLCanvasElement > ; } type PartitionProps = ReactiveChartStateProps & ReactiveChartDispatchProps & ReactiveChartOwnProps ; internal export type AnimationState = { rafId : number ; } ; class PartitionComponent extends React . Component < PartitionProps > { static displayName = 'Partition' ; private ctx : CanvasRenderingContext2D | null ; private animationState : AnimationState ; private readonly devicePixelRatio : number ; constructor ( props ) { super ( props ) ; this . ctx = null ; this . devicePixelRatio = window . devicePixelRatio ; this . animationState = { rafId : NaN } ; } componentDidMount ( ) { this . tryCanvasContext ( ) ; if ( this . props . initialized ) { this . drawCanvas ( ) ; this . props . onChartRendered ( ) ; } } componentDidUpdate ( ) { if ( ! this . ctx ) { this . tryCanvasContext ( ) ; } if ( this . props . initialized ) { this . drawCanvas ( ) ; this . props . onChartRendered ( ) ; } } handleMouseMove ( e ) { const { initialized , chartDimensions : { width , height } , forwardStageRef , } = this . props ; if ( ! forwardStageRef . current || ! this . ctx || ! initialized || width === 0 || height === 0 ) { return ; } const picker = this . props . geometries . pickQuads ; const focus = this . props . geometriesFoci [ 0 ] ; const box = forwardStageRef . current . getBoundingClientRect ( ) ; const { diskCenter } = this . props . geometries ; const x = e . clientX - box . left - diskCenter . x ; const y = e . clientY - box . top - diskCenter . y ; const pickedShapes = picker ( x , y , focus ) ; const datumIndices = new Set ( ) ; pickedShapes . forEach ( ( shape ) => { const node = shape [ MODEL_KEY ] ; const shapeNode = node . children . find ( ( [ key ] ) => key === shape . dataName ) ; if ( shapeNode ) { const indices = shapeNode [ 1 ] [ INPUT_KEY ] || [ ] ; indices . forEach ( ( i ) => datumIndices . add ( i ) ) ; } } ) ; return pickedShapes ; } render ( ) { const { forwardStageRef , initialized , chartDimensions : { width , height } , a11ySettings , debug , isRTL , } = this . props ; return width === 0 || height === 0 || ! initialized ? null : ( < figure aria - labelledby = { a11ySettings . labelId } aria - describedby = { a11ySettings . descriptionId } > < canvas dir = { isRTL ? 'rtl' : 'ltr' } ref = { forwardStageRef } className = "echCanvasRenderer" width = { width * this . devicePixelRatio } height = { height * this . devicePixelRatio } onMouseMove = { this . handleMouseMove . bind ( this ) } style = { { width , height , } } role = "presentation" > < ScreenReaderSummary /> { ! debug && < ScreenReaderPartitionTable />} < /canvas> { debug && < ScreenReaderPartitionTable />} < /figure>); } private drawCanvas ( ) { if ( this . ctx ) { const { ctx , devicePixelRatio , props } = this ; clearCanvas ( ctx , props . background ) ; props . multiGeometries . forEach ( ( geometries , geometryIndex ) => { const renderer = isSimpleLinear ( geometries . layout , geometries . style . fillLabel , geometries . layers ) ? renderLinearPartitionCanvas2d : isWaffle ( geometries . layout ) ? renderWrappedPartitionCanvas2d : renderPartitionCanvas2d ; renderer ( ctx , devicePixelRatio , geometries , props . geometriesFoci [ geometryIndex ] , this . animationState ) ; } ) ; } } private tryCanvasContext ( ) { const canvas = this . props . forwardStageRef . current ; this . ctx = canvas && canvas . getContext ( '2d' ) ; } } const mapDispatchToProps = ( dispatch ) : ReactiveChartDispatchProps => bindActionCreators ( { onChartRendered } , dispatch ) ; const DEFAULT_PROPS = { isRTL : false , initialized : false , geometries : nullShapeViewModel ( ) , geometriesFoci : [ ] , multiGeometries : [ ] , chartDimensions : { width : 0 , height : 0 , left : 0 , top : 0 , } , a11ySettings : DEFAULT_A11Y_SETTINGS , debug : false , background : Colors . Transparent . keyword , } ; const mapStateToProps = ( state ) : ReactiveChartStateProps => { if ( getInternalIsInitializedSelector ( state ) !== InitStatus . Initialized ) { return DEFAULT_PROPS ; } const multiGeometries = partitionMultiGeometries ( state ) ; return { isRTL : hasMostlyRTLLabels ( multiGeometries ) , initialized : true , geometries : multiGeometries . length > 0 ? multiGeometries [ 0 ] : nullShapeViewModel ( ) , multiGeometries , chartDimensions : getChartContainerDimensionsSelector ( state ) , geometriesFoci : partitionDrilldownFocus ( state ) , a11ySettings : getA11ySettingsSelector ( state ) , debug : getSettingsSpecSelector ( state ) . debug , background : getChartThemeSelector ( state ) . background . color , } ; } ; internal export const Partition = connect ( mapStateToProps , mapDispatchToProps ) ( PartitionComponent ) ;