import * as _ from 'underscore' ; import * as d3 from 'd3' ; import { LinearScale , OrdinalScale , isOrdinalScale } from 'bqscales' ; import { d3GetEvent } from './utils' ; import { Mark } from './Mark' ; import { BarData , BarGroupValue , BarsModel } from './BarsModel' ; import { applyStyles } from './utils' ; export class Bars extends Mark { async render ( ) { const base_creation_promise = super . render . apply ( this ) ; this . stackedScale = d3 . scaleBand ( ) ; this . groupedScale = d3 . scaleBand ( ) ; this . selected_indices = this . model . get ( 'selected' ) ; this . selected_style = this . model . get ( 'selected_style' ) ; this . unselected_style = this . model . get ( 'unselected_style' ) ; this . display_el_classes = [ 'bar' , 'legendtext' ] ; this . displayed . then ( ( ) => { this . parent . tooltip_div . node ( ) . appendChild ( this . tooltip_div . node ( ) ) ; this . create_tooltip ( ) ; } ) ; await base_creation_promise ; this . event_listeners = { } ; this . process_interactions ( ) ; this . create_listeners ( ) ; this . compute_view_padding ( ) ; this . draw ( false ) ; } private setScaleOrientation ( ) { this . domScale = this . scales . x ; this . rangeScale = this . scales . y ; } set_ranges ( ) { const orient = this . model . get ( 'orientation' ) ; this . setScaleOrientation ( ) ; const dom = orient === 'vertical' ? 'x' : 'y' ; const rang = orient === 'vertical' ? 'y' : 'x' ; if ( ! isOrdinalScale ( this . domScale ) ) { this . domScale . setRange ( this . parent . padded_range ( dom , this . domScale . model ) ) ; } else { this . domScale . setRange ( this . parent . padded_range ( dom , this . domScale . model ) , this . model . get ( 'padding' ) ) ; } this . rangeScale . setRange ( this . parent . padded_range ( rang , this . rangeScale . model ) ) ; this . domOffset = 0 ; } set_positional_scales ( ) { const x_scale = this . scales . x , y_scale = this . scales . y ; this . listenTo ( x_scale , 'domain_changed' , function ( ) { if ( ! this . model . dirty ) { this . draw ( true ) ; } } ) ; this . listenTo ( y_scale , 'domain_changed' , function ( ) { if ( ! this . model . dirty ) { this . draw ( true ) ; } } ) ; } private adjustOffset ( ) { if ( ! isOrdinalScale ( this . domScale ) ) { if ( this . align === 'center' ) { this . domOffset = - ( this . stackedScale . bandwidth ( ) / 2 ) . toFixed ( 2 ) ; } else if ( this . align === 'left' ) { this . domOffset = - this . stackedScale . bandwidth ( ) . toFixed ( 2 ) ; } else { this . domOffset = 0 ; } } else { if ( this . align === 'center' ) { this . domOffset = 0 ; } else if ( this . align === 'left' ) { this . domOffset = - ( this . stackedScale . bandwidth ( ) / 2 ) ; } else { this . domOffset = this . stackedScale . bandwidth ( ) / 2 ; } } } create_listeners ( ) { super . create_listeners . apply ( this ) ; this . d3el . on ( 'mouseover' , ( ) => { this . event_dispatcher ( 'mouse_over' ) ; } ) . on ( 'mousemove' , ( ) => { this . event_dispatcher ( 'mouse_move' ) ; } ) . on ( 'mouseout' , ( ) => { this . event_dispatcher ( 'mouse_out' ) ; } ) ; this . listenTo ( this . model , 'data_updated' , ( ) => { const animate = true ; this . draw ( animate ) ; } ) ; this . listenTo ( this . model , 'change:colors' , this . updateColors ) ; this . listenTo ( this . model , 'change:opacities' , this . updateColors ) ; this . listenTo ( this . model , 'colors_updated' , this . updateColors ) ; this . listenTo ( this . model , 'change:type' , this . updateType ) ; this . listenTo ( this . model , 'change:align' , this . relayout ) ; this . listenTo ( this . model , 'change:padding' , this . relayout ) ; this . listenTo ( this . model , 'change:orientation' , this . relayout ) ; this . listenTo ( this . model , 'change:tooltip' , this . create_tooltip ) ; this . model . on_some_change ( [ 'stroke' , 'fill' , 'stroke_width' ] , this . apply_styles , this ) ; this . listenTo ( this . model , 'change:selected' , this . updateSelected ) ; this . listenTo ( this . model , 'change:interactions' , this . process_interactions ) ; this . listenTo ( this . parent , 'bg_clicked' , ( ) => { this . event_dispatcher ( 'parent_clicked' ) ; } ) ; this . model . on_some_change ( [ 'label_display_format' , 'label_font_style' , 'label_display' , 'label_display_vertical_offset' , 'label_display_horizontal_offset' , ] , this . draw , this ) ; } process_click ( interaction ) { super . process_click ( interaction ) ; if ( interaction === 'select' ) { this . event_listeners . parent_clicked = this . reset_selection ; this . event_listeners . element_clicked = this . bar_click_handler ; } } private drawZeroLine ( ) { this . setScaleOrientation ( ) ; const rangeScale = this . rangeScale . scale ; const orient = this . model . get ( 'orientation' ) ; if ( orient === 'vertical' ) { this . d3el . select ( '.zeroLine' ) . attr ( 'x1' , 0 ) . attr ( 'x2' , this . parent . plotarea_width ) . attr ( 'y1' , rangeScale ( this . model . baseValue ) ) . attr ( 'y2' , rangeScale ( this . model . baseValue ) ) ; } else { this . d3el . select ( '.zeroLine' ) . attr ( 'x1' , rangeScale ( this . model . baseValue ) ) . attr ( 'x2' , rangeScale ( this . model . baseValue ) ) . attr ( 'y1' , 0 ) . attr ( 'y2' , this . parent . plotarea_height ) ; } } private updateInternalScales ( ) { this . stackedScale . rangeRound ( this . set_x_range ( ) ) ; const padding = this . model . get ( 'padding' ) ; this . stackedScale . paddingInner ( padding ) ; this . stackedScale . paddingOuter ( padding / 2.0 ) ; this . adjustOffset ( ) ; this . groupedScale . rangeRound ( [ 0 , Math . round ( this . stackedScale . bandwidth ( ) * 100 ) / 100 , ] ) ; } relayout ( ) { this . set_ranges ( ) ; this . compute_view_padding ( ) ; this . drawZeroLine ( ) ; this . updateInternalScales ( ) ; this . drawBars ( ) ; } invert_point ( pixel ) { if ( pixel === undefined ) { this . model . set ( 'selected' , null ) ; this . touch ( ) ; return ; } const abs_diff = this . xPixels . map ( ( elem ) => { return Math . abs ( elem - pixel ) ; } ) ; this . model . set ( 'selected' , new Uint32Array ( [ abs_diff . indexOf ( d3 . min ( abs_diff ) ) ] ) ) ; this . touch ( ) ; } selector_changed ( point_selector , rect_selector ) { if ( point_selector === undefined ) { this . model . set ( 'selected' , null ) ; this . touch ( ) ; return [ ] ; } const pixels = this . pixelCoords ; const indices = new Uint32Array ( _ . range ( pixels . length ) ) ; const selected_groups = indices . filter ( ( index ) => { const bars = pixels [ index ] ; for ( let i = 0 ; i < bars . length ; i ++ ) { if ( rect_selector ( bars [ i ] ) ) { return true ; } } return false ; } ) ; this . model . set ( 'selected' , selected_groups ) ; this . touch ( ) ; } private updateSelected ( ) { this . selected_indices = this . model . get ( 'selected' ) ; this . apply_styles ( ) ; } draw ( animate ? ) { this . set_ranges ( ) ; let barGroups = this . d3el . selectAll ( '.bargroup' ) . data ( this . model . mark_data , ( d ) => d . key ) ; if ( ! isOrdinalScale ( this . domScale ) ) { const modelDomain = this . model . mark_data . map ( ( elem ) => elem . key ) ; this . stackedScale . domain ( modelDomain ) ; } else { this . stackedScale . domain ( this . domScale . scale . domain ( ) ) ; } this . updateInternalScales ( ) ; if ( this . model . mark_data . length > 0 ) { this . groupedScale . domain ( _ . range ( this . model . mark_data [ 0 ] . values . length ) ) . rangeRound ( [ 0 , Math . round ( this . stackedScale . bandwidth ( ) * 100 ) / 100 ] ) ; } barGroups . exit ( ) . remove ( ) ; barGroups = barGroups . enter ( ) . append ( 'g' ) . attr ( 'class' , 'bargroup' ) . merge ( barGroups ) ; barGroups . order ( ) ; barGroups . on ( 'click' , ( d , i ) => { return this . event_dispatcher ( 'element_clicked' , { data : d , index : i } ) ; } ) ; const barsSel = barGroups . selectAll ( '.bar' ) . data ( ( d ) => d . values ) ; barsSel . enter ( ) . append ( 'rect' ) . attr ( 'class' , 'bar' ) . attr ( 'width' , 0 ) . attr ( 'height' , 0 ) ; barsSel . exit ( ) . remove ( ) ; if ( ! this . model . get ( 'label_display' ) ) { barGroups . selectAll ( 'text' ) . remove ( ) ; } if ( this . model . get ( 'label_display' ) ) { const barLabels = barGroups . selectAll ( '.bar_label' ) . data ( ( d ) => d . values ) ; barLabels . exit ( ) . remove ( ) ; barLabels . enter ( ) . append ( 'text' ) . attr ( 'class' , 'bar_label' ) . attr ( 'width' , 0 ) . attr ( 'height' , 0 ) ; } this . drawBars ( animate ) ; this . apply_styles ( ) ; this . d3el . selectAll ( '.zeroLine' ) . remove ( ) ; this . d3el . append ( 'g' ) . append ( 'line' ) . attr ( 'class' , 'zeroLine' ) ; this . drawZeroLine ( ) ; } private drawBars ( animate ? ) { const barGroups = this . d3el . selectAll ( '.bargroup' ) ; const barsSel = barGroups . selectAll ( '.bar' ) ; const animationDuration = animate === true ? this . parent . model . get ( 'animation_duration' ) : 0 ; const domScale = this . domScale ; const rangeScale = this . rangeScale . scale ; const dom = this . orientation === 'vertical' ? 'x' : 'y' ; const rang = this . orientation === 'vertical' ? 'y' : 'x' ; const domControl = this . orientation === 'vertical' ? 'width' : 'height' ; const rangeControl = this . orientation === 'vertical' ? 'height' : 'width' ; if ( isOrdinalScale ( domScale ) ) { const domMax = d3 . max ( this . parent . range ( dom ) ) ; barGroups . attr ( 'transform' , ( d ) => { if ( this . orientation === 'vertical' ) { return ( 'translate(' + ( ( domScale . scale ( d . key ) !== undefined ? domScale . scale ( d . key ) : domMax ) + this . domOffset ) + ', 0)' ) ; } else { return ( 'translate(0, ' + ( ( domScale . scale ( d . key ) !== undefined ? domScale . scale ( d . key ) : domMax ) + this . domOffset ) + ')' ) ; } } ) ; } else { barGroups . attr ( 'transform' , ( d ) => { if ( this . orientation === 'vertical' ) { return ( 'translate(' + ( domScale . scale ( d . key ) + this . domOffset ) + ', 0)' ) ; } else { return ( 'translate(0, ' + ( domScale . scale ( d . key ) + this . domOffset ) + ')' ) ; } } ) ; } const isStacked = this . model . get ( 'type' ) === 'stacked' ; let bandWidth = 1.0 ; if ( isStacked ) { bandWidth = Math . max ( 1.0 , this . stackedScale . bandwidth ( ) ) ; barsSel . transition ( ) . duration ( animationDuration ) . attr ( dom , 0 ) . attr ( domControl , bandWidth . toFixed ( 2 ) ) . attr ( rang , ( d ) => rang === 'y' ? rangeScale ( d . y1 ) : rangeScale ( d . y0 ) ) . attr ( rangeControl , ( d ) => Math . abs ( rangeScale ( d . y1 + d . yRef ) - rangeScale ( d . y1 ) ) ) ; } else { bandWidth = Math . max ( 1.0 , this . groupedScale . bandwidth ( ) ) ; barsSel . transition ( ) . duration ( animationDuration ) . attr ( dom , ( datum , index ) => this . groupedScale ( index ) ) . attr ( domControl , bandWidth . toFixed ( 2 ) ) . attr ( rang , ( d ) => d3 . min ( [ rangeScale ( d . y ) , rangeScale ( this . model . baseValue ) ] ) ) . attr ( rangeControl , ( d ) => Math . abs ( rangeScale ( this . model . baseValue ) - rangeScale ( d . y ) ) ) ; } this . manageBarLabels ( barGroups , bandWidth , dom , rang ) ; this . pixelCoords = this . model . mark_data . map ( ( d ) => { const key = d . key ; const groupDom = domScale . scale ( key ) + this . domOffset ; return d . values . map ( ( d ) => { const rectCoords = { x : 0 , y : 0 , width : 0 , height : 0 } ; rectCoords [ dom ] = isStacked ? groupDom : groupDom + this . groupedScale ( d . subIndex ) ; rectCoords [ rang ] = isStacked ? rang === 'y' ? rangeScale ( d . y1 ) : rangeScale ( d . y0 ) : d3 . min ( [ rangeScale ( d . y ) , rangeScale ( this . model . baseValue ) ] ) ; rectCoords [ domControl ] = bandWidth ; rectCoords [ rangeControl ] = isStacked ? Math . abs ( rangeScale ( d . y1 + d . yRef ) - rangeScale ( d . y1 ) ) : Math . abs ( rangeScale ( this . model . baseValue ) - rangeScale ( d . yRef ) ) ; return [ [ rectCoords . x , rectCoords . x + rectCoords . width ] , [ rectCoords . y , rectCoords . y + rectCoords . height ] , ] ; } ) ; } ) ; this . xPixels = this . model . mark_data . map ( ( el ) => { return domScale . scale ( el . key ) + domScale . offset ; } ) ; } /**
     * Get the vertical label offset from the model
     */ get offsetVertical ( ) : number { return this . model . get ( 'label_display_vertical_offset' ) ; } /**
     * Get the horizontal label offset from the model
     */ get offsetHorizontal ( ) : number { return this . model . get ( 'label_display_horizontal_offset' ) ; } /**
     * Get the baseline parameter from the model
     */ get baseLine ( ) : number { return this . model . get ( 'base' ) ; } /**
     * Get the bar chart's orientation
     */ get orientation ( ) : 'horizontal' | 'vertical' { return this . model . get ( 'orientation' ) ; } /**
     * Get the bar chart's alignment
     */ get align ( ) : 'center' | 'left' | 'right' { return this . model . get ( 'align' ) ; } param barGroups param bandWidth param dom param rang private manageBarLabels ( barGroups , bandWidth , dom , rang ) { if ( ! this . model . get ( 'label_display' ) ) { return ; } if ( this . model . get ( 'type' ) === 'stacked' ) { this . stackedBarLabels ( barGroups , bandWidth , dom , rang ) ; } else { this . groupedBarLabels ( barGroups , bandWidth ) ; } this . updateBarLabelsStyle ( ) ; } param barGroups param bandWidth param dom param rang private stackedBarLabels ( barGroups , bandWidth , dom , rang ) { const barLabels = barGroups . selectAll ( '.bar_label' ) ; barLabels . attr ( dom , ( d ) => 0 ) . attr ( rang , ( d ) => { if ( d . y <= this . baseLine ) { return this . rangeScale . scale ( d . y0 ) ; } else { return this . rangeScale . scale ( d . y1 ) ; } } ) . style ( 'font-weight' , '400' ) . style ( 'text-anchor' , ( d ) => this . styleBarLabelTextAnchor ( d ) ) . style ( 'dominant-baseline' , ( d ) => this . styleBarLabelDominantBaseline ( d ) ) . attr ( 'transform' , ( d , i ) => { return this . transformBarLabel ( d , this . offsetHorizontal , this . offsetVertical , bandWidth ) ; } ) ; } param barGroups param bandWidth private groupedBarLabels ( barGroups , bandWidth ) { const barLabels = barGroups . selectAll ( '.bar_label' ) ; barLabels . attr ( 'x' , ( d , i ) => { if ( this . orientation === 'horizontal' ) { return this . rangeScale . scale ( d . y ) ; } else { return this . groupedScale ( i ) ; } } ) . attr ( 'y' , ( d , i ) => { if ( this . orientation === 'horizontal' ) { return this . groupedScale ( i ) ; } else { return this . rangeScale . scale ( d . y ) ; } } ) . style ( 'font-weight' , '400' ) . style ( 'text-anchor' , ( d ) => this . styleBarLabelTextAnchor ( d ) ) . style ( 'dominant-baseline' , ( d ) => this . styleBarLabelDominantBaseline ( d ) ) . attr ( 'transform' , ( d ) => this . transformBarLabel ( d , this . offsetHorizontal , this . offsetVertical , bandWidth ) ) ; } param d param offsetHorizontal param offsetVertical param bandWidth private transformBarLabel ( d , offsetHorizontal , offsetVertical , bandWidth ) { if ( this . orientation === 'horizontal' ) { return d . y <= this . baseLine ? `translate(${ d . y0 <= this . baseLine ? 0 - offsetVertical : 0 + offsetVertical }, ${ bandWidth / 2 + offsetHorizontal })` : `translate(${ d . y1 <= this . baseLine ? 0 - offsetVertical : 0 + offsetVertical }, ${ bandWidth / 2 + offsetHorizontal })` ; } else { return d . y <= this . baseLine ? `translate(${ bandWidth / 2 + offsetHorizontal },
                ${ d . y0 <= this . baseLine ? 0 - offsetVertical : 0 + offsetVertical })` : `translate(${ bandWidth / 2 + offsetHorizontal },
                ${ d . y1 <= this . baseLine ? 0 - offsetVertical : 0 + offsetVertical })` ; } } param d param i private styleBarLabelTextAnchor ( d ) { if ( this . orientation === 'horizontal' ) { return d . y <= this . baseLine ? 'start' : 'end' ; } else { return 'middle' ; } } param d param i private styleBarLabelDominantBaseline ( d ) { if ( this . orientation === 'horizontal' ) { return 'central' ; } else { return d . y <= this . baseLine ? 'text-after-edge' : 'text-before-edge' ; } } /**
     * Adds CSS styling to the bar labels
     */ private updateBarLabelsStyle ( ) { const displayFormatStr = this . model . get ( 'label_display_format' ) ; const displayFormat = displayFormatStr ? d3 . format ( displayFormatStr ) : null ; let fonts = this . d3el . selectAll ( '.bar_label' ) . text ( ( d , i ) => ( displayFormat ? displayFormat ( d . y ) : null ) ) ; const fontStyle = this . model . get ( 'label_font_style' ) ; for ( const styleKey in fontStyle ) { fonts = fonts . style ( styleKey , fontStyle [ styleKey ] ) ; } } private updateType ( ) { this . model . update_domains ( ) ; this . draw ( ) ; } private updateColors ( ) { const stroke = this . model . get ( 'stroke' ) ; if ( this . model . mark_data . length > 0 ) { if ( ! this . model . yIs2d ) { this . d3el . selectAll ( '.bar' ) . style ( 'fill' , this . get_mark_color . bind ( this ) ) . style ( 'stroke' , stroke ? stroke : this . get_mark_color . bind ( this ) ) . style ( 'opacity' , this . get_mark_opacity . bind ( this ) ) ; } else { this . d3el . selectAll ( '.bargroup' ) . selectAll ( '.bar' ) . style ( 'fill' , this . get_mark_color . bind ( this ) ) . style ( 'stroke' , stroke ? stroke : this . get_mark_color . bind ( this ) ) . style ( 'opacity' , this . get_mark_opacity . bind ( this ) ) ; } } if ( this . legendEl ) { this . legendEl . selectAll ( '.legendrect' ) . style ( 'fill' , this . get_mark_color . bind ( this ) ) . style ( 'stroke' , stroke ? stroke : this . get_mark_color . bind ( this ) ) . style ( 'opacity' , this . get_mark_opacity . bind ( this ) ) ; this . legendEl . selectAll ( '.legendtext' ) . style ( 'fill' , this . get_mark_color . bind ( this ) ) . style ( 'stroke' , stroke ? stroke : this . get_mark_color . bind ( this ) ) . style ( 'opacity' , this . get_mark_opacity . bind ( this ) ) ; } } draw_legend ( elem , x_disp , y_disp , inter_x_disp , inter_y_disp ) { if ( ! this . model . yIs2d && this . model . get ( 'colors' ) . length !== 1 && this . model . get ( 'color_mode' ) !== 'element' ) { return [ 0 , 0 ] ; } const legendData = this . model . mark_data [ 0 ] . values . map ( ( data ) => { return { index : data . subIndex , colorIndex : data . colorIndex , opacityIndex : data . opacityIndex , } ; } ) ; this . legendEl = elem . selectAll ( '.legend' + this . uuid ) . data ( legendData ) ; const rect_dim = inter_y_disp * 0.8 ; const legend = this . legendEl . enter ( ) . append ( 'g' ) . attr ( 'class' , 'legend' + this . uuid ) . attr ( 'transform' , ( d , i ) => { return 'translate(0, ' + ( i * inter_y_disp + y_disp ) + ')' ; } ) . on ( 'mouseover' , ( ) => { this . event_dispatcher ( 'legend_mouse_over' ) ; } ) . on ( 'mouseout' , ( ) => { this . event_dispatcher ( 'legend_mouse_out' ) ; } ) . on ( 'click' , ( ) => { this . event_dispatcher ( 'legend_clicked' ) ; } ) ; legend . append ( 'rect' ) . classed ( 'legendrect' , true ) . style ( 'fill' , this . get_mark_color . bind ( this ) ) . attr ( 'x' , 0 ) . attr ( 'y' , 0 ) . attr ( 'width' , rect_dim ) . attr ( 'height' , rect_dim ) ; legend . append ( 'text' ) . attr ( 'class' , 'legendtext' ) . attr ( 'x' , rect_dim * 1.2 ) . attr ( 'y' , rect_dim / 2 ) . attr ( 'dy' , '0.35em' ) . text ( ( d , i ) => this . model . get ( 'labels' ) [ i ] ) . style ( 'fill' , this . get_mark_color . bind ( this ) ) ; this . legendEl = legend . merge ( this . legendEl ) ; const max_length = d3 . max ( this . model . get ( 'labels' ) , ( d ) => d . length ) ; this . legendEl . exit ( ) . remove ( ) ; return [ this . model . mark_data [ 0 ] . values . length , max_length ] ; } clear_style ( style , indices ) { if ( ! indices || indices . length === 0 ) { return ; } if ( Object . keys ( style ) . length === 0 ) { return ; } const elements = this . d3el . selectAll ( '.bargroup' ) . filter ( ( d , index ) => { return indices . indexOf ( index ) !== - 1 ; } ) ; const clearing_style = { } ; for ( const key in style ) { clearing_style [ key ] = null ; } applyStyles ( elements . selectAll ( '.bar' ) , clearing_style ) ; } set_style_on_elements ( style , indices ) { if ( ! indices || indices . length === 0 ) { return ; } if ( Object . keys ( style ) . length === 0 ) { return ; } const elements = this . d3el . selectAll ( '.bargroup' ) . filter ( ( data , index ) => { return indices . indexOf ( index ) !== - 1 ; } ) ; applyStyles ( elements . selectAll ( '.bar' ) , style ) ; } set_default_style ( indices ) { if ( ! indices || indices . length === 0 ) { return ; } const fill = this . model . get ( 'fill' ) , stroke = this . model . get ( 'stroke' ) , stroke_width = this . model . get ( 'stroke_width' ) ; this . d3el . selectAll ( '.bargroup' ) . filter ( ( data , index ) => { return indices . indexOf ( index ) !== - 1 ; } ) . selectAll ( '.bar' ) . style ( 'fill' , fill ? this . get_mark_color . bind ( this ) : 'none' ) . style ( 'stroke' , stroke ? stroke : 'none' ) . style ( 'opacity' , this . get_mark_opacity . bind ( this ) ) . style ( 'stroke-width' , stroke_width ) ; } get_mark_color ( data , index ) { return super . get_mark_color ( data , data . colorIndex ) ; } get_mark_opacity ( data , index ) { return super . get_mark_opacity ( data , data . opacityIndex ) ; } private set_x_range ( ) { const domScale = this . domScale ; if ( isOrdinalScale ( domScale ) ) { return domScale . scale . range ( ) as [ number , number ] ; } else { return [ domScale . scale ( d3 . min ( this . stackedScale . domain ( ) ) ) , domScale . scale ( d3 . max ( this . stackedScale . domain ( ) ) ) , ] ; } } private bar_click_handler ( args ) { const index = args . index ; const idx = this . model . get ( 'selected' ) || [ ] ; let selected = Array . from ( idx ) ; const elem_index = selected . indexOf ( index ) ; const accelKey = d3GetEvent ( ) . ctrlKey || d3GetEvent ( ) . metaKey ; if ( elem_index > - 1 && accelKey ) { selected . splice ( elem_index , 1 ) ; } else { if ( d3GetEvent ( ) . shiftKey ) { if ( elem_index > - 1 ) { return ; } const min_index = selected . length !== 0 ? d3 . min ( selected ) : - 1 ; const max_index = selected . length !== 0 ? d3 . max ( selected ) : this . model . mark_data . length ; if ( index > max_index ) { _ . range ( max_index + 1 , index + 1 ) . forEach ( ( i ) => { selected . push ( i ) ; } ) ; } else if ( index < min_index ) { _ . range ( index , min_index ) . forEach ( ( i ) => { selected . push ( i ) ; } ) ; } } else if ( accelKey ) { selected . push ( index ) ; } else { selected = [ ] ; selected . push ( index ) ; } } this . model . set ( 'selected' , selected . length === 0 ? null : new Uint32Array ( selected ) , { updated_view : this } ) ; this . touch ( ) ; const e = d3GetEvent ( ) ; if ( e . cancelBubble !== undefined ) { e . cancelBubble = true ; } if ( e . stopPropagation ) { e . stopPropagation ( ) ; } e . preventDefault ( ) ; } private reset_selection ( ) { this . model . set ( 'selected' , null ) ; this . selected_indices = null ; this . touch ( ) ; } compute_view_padding ( ) { const domScale = this . domScale ; const orient = this . model . get ( 'orientation' ) ; let xPadding = 0 ; const avail_space = orient === 'vertical' ? this . parent . plotarea_width : this . parent . plotarea_height ; if ( domScale ) { if ( this . stackedScale !== null && this . stackedScale !== undefined && this . stackedScale . domain ( ) . length !== 0 ) { if ( ! isOrdinalScale ( domScale ) ) { if ( this . align === 'center' ) { xPadding = avail_space / ( 2.0 * this . stackedScale . domain ( ) . length ) + 1 ; } else if ( this . align === 'left' || this . align === 'right' ) { xPadding = avail_space / this . stackedScale . domain ( ) . length + 1 ; } } else { if ( this . align === 'left' || this . align === 'right' ) { xPadding = parseFloat ( ( this . stackedScale . bandwidth ( ) / 2 ) . toFixed ( 2 ) ) ; } } } } if ( orient === 'vertical' ) { if ( xPadding !== this . xPadding ) { this . xPadding = xPadding ; this . trigger ( 'mark_padding_updated' ) ; } } else { if ( xPadding !== this . yPadding ) { this . yPadding = xPadding ; this . trigger ( 'mark_padding_updated' ) ; } } } private domOffset : number ; private domScale : LinearScale | OrdinalScale ; private legendEl : d3 . Selection < any , any , any , any > ; private pixelCoords : [ [ number , number ] , [ number , number ] ] [ ] [ ] ; private rangeScale : LinearScale | OrdinalScale ; private stackedScale : d3 . ScaleBand < any > ; private groupedScale : d3 . ScaleBand < any > ; private xPixels : number [ ] ; model : BarsModel ; }