import { uuid , resolvePromisesDict , Dict , ViewList , WidgetModel , } from '@jupyter-widgets/base' ; import * as _ from 'underscore' ; import { MessageLoop } from '@lumino/messaging' ; import { Widget } from '@lumino/widgets' ; import { Scale } from 'bqscales' ; import * as d3 from 'd3' ; import { Figure } from './Figure' ; import { MarketMapModel } from './MarketMapModel' ; import { Tooltip } from './Tooltip' ; import * as popperreference from './PopperReference' ; import popper from 'popper.js' ; import { applyAttrs , applyStyles } from './utils' ; export class MarketMap extends Figure { protected renderImpl ( ) { const figureSize = this . getFigureSize ( ) ; this . width = figureSize . width ; this . height = figureSize . height ; this . id = uuid ( ) ; this . scales = { } ; this . set_top_el_style ( ) ; this . margin = this . model . get ( 'map_margin' ) ; this . num_rows = this . model . get ( 'rows' ) ; this . num_cols = this . model . get ( 'cols' ) ; this . row_groups = this . model . get ( 'row_groups' ) ; this . enable_select = this . model . get ( 'enable_select' ) ; this . update_data ( ) ; this . set_area_dimensions ( this . data . length ) ; if ( this . model . get ( 'theme' ) ) { this . svg . classed ( this . model . get ( 'theme' ) , true ) ; } this . fig = this . svg . append ( 'g' ) . attr ( 'transform' , 'translate(' + this . margin . left + ',' + this . margin . top + ')' ) ; this . fig_map = this . fig . append ( 'g' ) ; this . fig_axes = this . fig . append ( 'g' ) ; this . fig_click = this . fig . append ( 'g' ) ; this . fig_hover = this . fig . append ( 'g' ) ; this . fig_names = this . fig . append ( 'g' ) . style ( 'display' , this . model . get ( 'show_groups' ) ? 'inline' : 'none' ) ; this . fig_map . classed ( 'g-map' , true ) ; this . fig_axes . classed ( 'g-axes' , true ) ; this . fig_click . classed ( 'g-click' , true ) ; this . fig_hover . classed ( 'g-hover' , true ) ; this . fig_names . classed ( 'g-names' , true ) ; this . tooltip_div = d3 . select ( document . createElement ( 'div' ) ) . attr ( 'class' , 'mark_tooltip' ) ; applyStyles ( this . tooltip_div , { opacity : 0 , 'pointer-events' : 'none' } ) ; const freeze_tooltip_loc = this . model . get ( 'freeze_tooltip_location' ) ; if ( freeze_tooltip_loc ) { this . popper_reference = new popperreference . ElementReference ( this . svg . node ( ) ) ; } else { this . popper_reference = new popperreference . PositionReference ( { x : 0 , y : 0 , width : 20 , height : 20 , } ) ; } this . popper = new popper ( this . popper_reference , this . tooltip_div . node ( ) , { placement : 'auto' , } ) ; this . update_default_tooltip ( ) ; this . selected_stroke = this . model . get ( 'selected_stroke' ) ; this . hovered_stroke = this . model . get ( 'hovered_stroke' ) ; this . update_plotarea_dimensions ( ) ; this . title = this . fig . append ( 'text' ) . attr ( 'class' , 'mainheading' ) . text ( this . model . get ( 'title' ) ) ; applyAttrs ( this . title , { x : 0.5 * this . plotarea_width , y : - ( this . margin . top / 2.0 ) , dy : '1em' , } ) ; applyStyles ( this . title , this . model . get ( 'title_style' ) ) ; return this . create_scale_views ( ) . then ( ( ) => { this . create_listeners ( ) ; this . axis_views = new ViewList ( this . add_axis , null , this ) ; const axis_views_updated = this . axis_views . update ( this . model . get ( 'axes' ) ) ; this . model . on ( 'change:axes' , ( model , value , options ) => { this . axis_views . update ( value ) ; } ) ; this . draw_map ( ) ; document . body . appendChild ( this . tooltip_div . node ( ) ) ; this . create_tooltip_widget ( ) ; window . addEventListener ( 'resize' , this . debouncedRelayout ) ; this . once ( 'remove' , ( ) => { window . removeEventListener ( 'resize' , this . debouncedRelayout ) ; } ) ; return Promise . all ( [ Promise . resolve ( ) , axis_views_updated ] ) ; } ) ; } set_top_el_style ( ) { this . el . style [ 'user-select' ] = 'none' ; this . el . style [ 'ms-user-select' ] = 'none' ; this . el . style [ 'moz-user-select' ] = 'none' ; this . el . style [ 'khtml-user-select' ] = 'none' ; this . el . style [ 'webkit-user-select' ] = 'none' ; } update_plotarea_dimensions ( ) { this . plotarea_width = this . width - this . margin . left - this . margin . right ; this . plotarea_height = this . height - this . margin . top - this . margin . bottom ; this . column_width = parseFloat ( ( this . plotarea_width / this . num_cols ) . toFixed ( 2 ) ) ; this . row_height = parseFloat ( ( this . plotarea_height / this . num_rows ) . toFixed ( 2 ) ) ; } reset_drawing_controls ( ) { this . prev_x = 0 ; this . prev_y = - 1 ; this . y_direction = 1 ; this . x_direction = 1 ; this . group_iter = 1 ; } create_listeners ( ) { this . listenTo ( this . model , 'change:scales' , this . create_scale_views ) ; this . listenTo ( this . model , 'change:color' , this . recolor_chart ) ; this . listenTo ( this . model , 'change:colors' , this . colors_updated ) ; this . listenTo ( this . model , 'change:show_groups' , this . show_groups ) ; this . listenTo ( this . model , 'change:selected_stroke' , this . update_selected_stroke ) ; this . listenTo ( this . model , 'change:hovered_stroke' , this . update_hovered_stroke ) ; this . listenTo ( this . model , 'change:font_style' , this . update_font_style ) ; this . model . on_some_change ( [ 'title' , 'title_style' ] , this . update_title , this ) ; this . listenTo ( this . model , 'change:selected' , function ( ) { this . clear_selected ( ) ; this . apply_selected ( ) ; } ) ; this . model . on_some_change ( [ 'names' , 'groups' , 'ref_data' ] , function ( ) { this . update_data ( ) ; this . compute_dimensions_and_draw ( ) ; } , this ) ; this . listenTo ( this . model , 'change:rows' , function ( model , value ) { this . num_rows = value ; this . compute_dimensions_and_draw ( ) ; } ) ; this . listenTo ( this . model , 'change:cols' , function ( model , value ) { this . num_cols = value ; this . compute_dimensions_and_draw ( ) ; } ) ; this . listenTo ( this . model , 'change:row_groups' , function ( model , value ) { this . row_groups = value ; this . compute_dimensions_and_draw ( ) ; } ) ; this . listenTo ( this . model , 'change:tooltip_widget' , this . create_tooltip_widget ) ; this . listenTo ( this . model , 'change:tooltip_fields' , this . update_default_tooltip ) ; this . listenTo ( this . model , 'change:tooltip_formats' , this . update_default_tooltip ) ; } update_title ( model , value ) { this . title . text ( this . model . get ( 'title' ) ) ; applyStyles ( this . title , this . model . get ( 'title_style' ) ) ; } relayout ( ) { const that = this ; const figureSize = this . getFigureSize ( ) ; this . width = figureSize . width ; this . height = figureSize . height ; window . requestAnimationFrame ( ( ) => { that . margin = that . model . get ( 'map_margin' ) ; that . update_plotarea_dimensions ( ) ; that . fig . attr ( 'transform' , 'translate(' + that . margin . left + ',' + that . margin . top + ')' ) ; applyAttrs ( that . title , { x : 0.5 * that . plotarea_width , y : - ( that . margin . top / 2.0 ) , dy : '1em' , } ) ; that . draw_map ( ) ; that . clear_selected ( ) ; that . apply_selected ( ) ; that . fig_hover . selectAll ( 'rect' ) . remove ( ) ; that . hide_tooltip ( ) ; that . trigger ( 'margin_updated' ) ; } ) ; } update_data ( ) { const that = this ; this . model . set ( 'selected' , [ ] ) ; this . data = this . model . get ( 'names' ) || [ ] ; this . ref_data = this . model . get ( 'ref_data' ) ; this . group_data = this . model . get ( 'groups' ) ; this . groups = _ . uniq ( this . group_data , true ) ; let display_text = this . model . get ( 'display_text' ) ; display_text = display_text === null || display_text . length === 0 ? this . data : display_text ; this . colors = this . model . get ( 'colors' ) ; const num_colors = this . colors . length ; this . colors_map = function ( d ) { return that . get_color ( d , num_colors ) ; } ; const color_data = this . model . get ( 'color' ) ; const mapped_data = _ . map ( this . data , ( d , i ) => { return { display : display_text [ i ] , name : d , color : Number . isNaN ( color_data [ i ] ) ? undefined : color_data [ i ] , group : this . group_data [ i ] , ref_data : this . ref_data === null || this . ref_data === undefined ? null : this . ref_data [ i ] , } ; } ) ; this . update_domains ( ) ; this . grouped_data = _ . groupBy ( mapped_data , ( d , i ) => { return that . group_data [ i ] ; } ) ; this . groups = [ ] ; this . running_sums = [ ] ; this . running_sums [ 0 ] = 0 ; let count = 0 ; for ( const key in this . grouped_data ) { this . groups . push ( key ) ; count += this . grouped_data [ key ] . length ; this . running_sums . push ( count ) ; } this . running_sums . pop ( ) ; } update_domains ( ) { const color_scale_model = this . model . getScales ( ) . color ; const color_data = this . model . get ( 'color' ) ; if ( color_scale_model && color_data . length > 0 ) { color_scale_model . computeAndSetDomain ( color_data , this . model . model_id ) ; } } set_area_dimensions ( num_items ) { this . num_rows = this . model . get ( 'rows' ) ; this . num_cols = this . model . get ( 'cols' ) ; this . row_groups = this . model . get ( 'row_groups' ) ; if ( this . num_cols !== undefined && this . num_cols !== null && this . num_cols !== 0 ) { if ( this . row_groups > 1 && this . num_cols % 2 === 0 ) { this . num_cols ++ ; } this . num_rows = Math . floor ( num_items / this . num_cols ) ; this . num_rows = num_items % this . num_cols === 0 ? this . num_rows : this . num_rows + 1 ; } else if ( this . num_rows !== undefined && this . num_rows !== null && this . num_rows !== 0 ) { this . num_cols = Math . floor ( num_items / this . num_rows ) ; this . num_cols = num_items % this . num_rows === 0 ? this . num_cols : this . num_cols + 1 ; if ( this . row_groups > 1 && this . num_cols % 2 === 0 ) { this . num_cols ++ ; } } else { this . num_cols = Math . floor ( Math . sqrt ( num_items ) ) ; if ( this . row_groups > 1 && this . num_cols % 2 === 0 ) { this . num_cols ++ ; } this . num_rows = Math . floor ( num_items / this . num_cols ) ; this . num_rows = num_items % this . num_cols === 0 ? this . num_rows : this . num_rows + 1 ; } this . row_groups = Math . min ( this . row_groups , this . num_rows ) ; if ( this . row_groups == 1 ) { this . num_cols = Math . floor ( num_items / this . num_rows ) ; this . num_cols = num_items % this . num_rows === 0 ? this . num_cols : this . num_cols + 1 ; } this . set_row_limits ( ) ; } compute_dimensions_and_draw ( ) { this . set_area_dimensions ( this . data . length ) ; this . update_plotarea_dimensions ( ) ; this . draw_map ( ) ; this . clear_selected ( ) ; this . apply_selected ( ) ; this . fig_hover . selectAll ( 'rect' ) . remove ( ) ; this . hide_tooltip ( ) ; } update_default_tooltip ( ) { this . tooltip_fields = this . model . get ( 'tooltip_fields' ) ; const formats = this . model . get ( 'tooltip_formats' ) ; this . tooltip_formats = this . tooltip_fields . map ( ( field , index ) => { const fmt = formats [ index ] ; if ( fmt === undefined || fmt === '' ) { return ( d ) => d ; } else { return d3 . format ( fmt ) as ( d : number ) => string ; } } ) ; } create_scale_views ( ) { for ( const key in this . scales ) { this . stopListening ( this . scales [ key ] ) ; } const scale_models = this . model . getScales ( ) ; const scale_promises = { } ; _ . each ( scale_models , ( model , key ) => { scale_promises [ key ] = this . create_child_view ( model ) ; } ) ; return resolvePromisesDict < Scale > ( scale_promises ) . then ( ( d ) => { this . scales = d ; this . set_scales ( ) ; } ) ; } set_scales ( ) { const that = this ; const color_scale = this . scales . color ; if ( color_scale ) { color_scale . on ( 'color_scale_range_changed' , that . update_map_colors , that ) ; this . update_domains ( ) ; this . listenTo ( color_scale , 'domain_changed' , ( ) => { that . update_map_colors ( ) ; } ) ; this . update_map_colors ( ) ; } } show_groups ( model , value ) { this . fig_names . style ( 'display' , value ? 'inline' : 'none' ) ; this . fig_map . selectAll ( '.market_map_text' ) . style ( 'opacity' , value ? 0.2 : 1 ) ; this . fig_map . selectAll ( '.market_map_rect' ) . style ( 'stroke-opacity' , value ? 0.2 : 1 ) ; } draw_map ( ) { this . reset_drawing_controls ( ) ; this . fig_map . selectAll ( '.element_group' ) . remove ( ) ; this . fig_names . selectAll ( '.names_object' ) . remove ( ) ; this . rect_groups = this . fig_map . selectAll ( '.element_group' ) . data ( this . groups ) ; const color_scale = this . scales . color ; const that = this ; this . rect_groups = this . rect_groups . enter ( ) . append ( 'g' ) . attr ( 'class' , 'element_group' ) . attr ( 'transform' , ( d , i ) => { return that . get_group_transform ( i ) ; } ) . merge ( this . rect_groups as d3 . Selection < SVGGElement , any , SVGGraphicsElement , any > ) ; this . rect_groups . exit ( ) . remove ( ) ; this . end_points = [ ] ; this . rect_groups . nodes ( ) . forEach ( ( d , i ) => { const data = that . grouped_data [ that . groups [ i ] ] ; const return_arr = that . get_new_cords ( ) ; const ends = that . get_end_points ( return_arr [ 2 ] , data . length , return_arr [ 0 ] , return_arr [ 1 ] , return_arr [ 3 ] , return_arr [ 4 ] ) ; ends . forEach ( ( point ) => { that . end_points . push ( point ) ; } ) ; const element_count = that . running_sums [ i ] ; let groups = d3 . select ( d ) . selectAll < SVGGElement , undefined > ( '.rect_element' ) . data ( data ) ; const new_groups = groups . enter ( ) . append ( 'g' ) . classed ( 'rect_element' , true ) ; new_groups . append ( 'rect' ) . attr ( 'x' , 0 ) . attr ( 'y' , 0 ) . classed ( 'market_map_rect' , true ) ; new_groups . append ( 'text' ) . classed ( 'market_map_text' , true ) . style ( 'text-anchor' , 'middle' ) . style ( 'pointer-events' , 'none' ) . style ( 'dominant-baseline' , 'central' ) ; groups = new_groups . merge ( groups ) ; groups . attr ( 'transform' , ( data , ind ) => { return that . get_cell_transform ( ind ) ; } ) . on ( 'click' , function ( data , ind ) { that . cell_click_handler ( data , element_count + ind , this ) ; } ) . on ( 'mouseover' , function ( data , ind ) { that . mouseover_handler ( data , element_count + ind , this ) ; } ) . on ( 'mousemove' , ( data ) => { that . mousemove_handler ( ) ; } ) . on ( 'mouseout' , function ( data , ind ) { that . mouseout_handler ( data , element_count + ind , this ) ; } ) . attr ( 'class' , function ( data , index ) { return ( d3 . select ( this ) . attr ( 'class' ) + ' ' + 'rect_' + ( element_count + index ) ) ; } ) ; groups . selectAll ( '.market_map_rect' ) . attr ( 'width' , that . column_width ) . attr ( 'height' , that . row_height ) . style ( 'stroke-opacity' , that . model . get ( 'show_groups' ) ? 0.2 : 1.0 ) . style ( 'stroke' , that . model . get ( 'stroke' ) ) . style ( 'fill' , ( elem , j ) => { return color_scale && elem . color !== undefined && elem . color !== null ? color_scale . scale ( elem . color ) : that . colors_map ( i ) ; } ) ; groups . selectAll ( '.market_map_text' ) . attr ( 'x' , that . column_width / 2.0 ) . attr ( 'y' , that . row_height / 2.0 ) . text ( ( data , j ) => { return data . display ; } ) . style ( 'opacity' , that . model . get ( 'show_groups' ) ? 0.2 : 1.0 ) ; groups . exit ( ) . remove ( ) ; that . create_bounding_path ( d , ends ) ; const min_x = d3 . min ( ends , ( end_point ) => { return end_point . x ; } ) ; const min_y = d3 . min ( ends , ( end_point ) => { return end_point . y ; } ) ; const obj = that . fig_names . append ( 'foreignObject' ) . attr ( 'class' , 'names_object' ) . attr ( 'x' , min_x ) . attr ( 'y' , min_y ) . append ( 'xhtml:div' ) . attr ( 'class' , 'names_div' ) . text ( that . groups [ i ] ) ; applyStyles ( obj , { display : 'flex' , 'flex-direction' : 'row' , 'align-content' : 'center' , 'align-items' : 'center' , width : '100%' , height : '100%' , 'justify-content' : 'center' , 'word-wrap' : 'break-word' , font : '24px sans-serif' , color : 'black' , } ) ; } ) ; this . update_font_style ( ) ; this . draw_group_names ( ) ; } draw_group_names ( ) { const paths = this . svg . selectAll ( '.bounding_path' ) . nodes ( ) as SVGPathElement [ ] ; const clientRects = paths . map ( ( path ) => { return path . getBoundingClientRect ( ) ; } ) ; const text_elements = this . fig_names . selectAll ( '.names_object' ) . data ( clientRects ) ; text_elements . attr ( 'width' , ( d ) => { return d . width ; } ) . attr ( 'height' , ( d ) => { return d . height ; } ) ; } recolor_chart ( ) { const that = this ; this . update_data ( ) ; this . rect_groups = this . fig . selectAll ( '.element_group' ) . data ( this . groups ) ; const color_scale = this . scales . color ; this . rect_groups . nodes ( ) . forEach ( ( d , i ) => { const data = that . grouped_data [ that . groups [ i ] ] ; d3 . select ( d ) . selectAll ( '.rect_element' ) . data ( data ) . select ( 'rect' ) . style ( 'stroke' , that . model . get ( 'stroke' ) ) . style ( 'fill' , ( elem , j ) => { return color_scale && elem . color !== undefined && elem . color !== null ? color_scale . scale ( elem . color ) : that . colors_map ( i ) ; } ) ; } ) ; } update_font_style ( ) { const x = this . svg . selectAll ( '.market_map_text' ) ; applyStyles ( x , this . model . get ( 'font_style' ) ) ; } update_map_colors ( ) { if ( this . rect_groups !== undefined && this . rect_groups !== null ) { this . recolor_chart ( ) ; } } cell_click_handler ( data , id , cell ) { if ( this . model . get ( 'enable_select' ) ) { const selected = Object . assign ( this . model . get ( 'selected' ) . slice ( ) , this . model . get ( 'selected' ) ) ; const index = selected . indexOf ( data . name ) ; if ( index == - 1 ) { selected . push ( data . name ) ; } else { selected . splice ( index , 1 ) ; } this . model . set ( 'selected' , selected ) ; this . touch ( ) ; } } apply_selected ( ) { const selected = this . model . get ( 'selected' ) ; const that = this ; if ( selected === undefined || selected === null || selected . length === 0 ) { this . clear_selected ( ) ; } else { selected . forEach ( ( data ) => { const selected_cell = that . fig_map . selectAll ( '.rect_element' ) . filter ( ( d , i ) => { return d . name === data ; } ) ; const rect = that . fig_click . append ( 'rect' ) . data ( selected_cell . data ( ) ) . attr ( 'transform' , selected_cell . attr ( 'transform' ) ) . attr ( 'x' , 0 ) . attr ( 'y' , 0 ) . attr ( 'width' , that . column_width ) . attr ( 'height' , that . row_height ) ; applyStyles ( rect , { stroke : that . selected_stroke , 'stroke-width' : '3px' , fill : 'none' , } ) ; } ) ; } } clear_selected ( ) { this . fig_click . selectAll ( 'rect' ) . remove ( ) ; } mouseover_handler ( data , id , cell ) { const transform = d3 . select ( cell ) . attr ( 'transform' ) ; if ( this . model . get ( 'enable_hover' ) ) { const rect = this . fig_hover . append ( 'rect' ) . attr ( 'class' , 'hover_' + id ) . attr ( 'transform' , transform ) . attr ( 'x' , 0 ) . attr ( 'y' , 0 ) . attr ( 'width' , this . column_width ) . attr ( 'height' , this . row_height ) ; applyStyles ( rect , { stroke : this . hovered_stroke , 'stroke-width' : '3px' , fill : 'none' , 'pointer-events' : 'none' , } ) ; this . show_tooltip ( d3 . event , data ) ; this . send ( { event : 'hover' , data : data . name , ref_data : data . ref_data } ) ; } } update_selected_stroke ( model , value ) { this . selected_stroke = value ; this . fig_click . selectAll ( 'rect' ) . style ( 'stroke' , value ) ; } update_hovered_stroke ( model , value ) { this . hovered_stroke = value ; } mouseout_handler ( data , id , cell ) { this . fig_hover . select ( '.hover_' + id ) . remove ( ) ; this . hide_tooltip ( ) ; } show_tooltip ( event , data ) { const that = this ; if ( ! this . tooltip_view && ( ! this . tooltip_fields || this . tooltip_fields . length == 0 ) ) { return ; } else { const tooltip_div = this . tooltip_div ; tooltip_div . transition ( ) . style ( 'opacity' , 0.9 ) . style ( 'display' , null ) ; this . move_tooltip ( ) ; tooltip_div . select ( 'table' ) . remove ( ) ; const ref_data = data . ref_data ; if ( ! this . tooltip_view ) { const tooltip_table = tooltip_div . append ( 'table' ) . selectAll ( 'tr' ) . data ( this . tooltip_fields ) ; tooltip_table . exit ( ) . remove ( ) ; const table_rows = tooltip_table . enter ( ) . append ( 'tr' ) ; table_rows . append ( 'td' ) . attr ( 'class' , 'tooltiptext' ) . text ( ( datum ) => { return datum ; } ) ; table_rows . append ( 'td' ) . attr ( 'class' , 'tooltiptext' ) . text ( ( datum , index ) => { if ( ref_data === null || ref_data === undefined ) { return null ; } if ( ref_data [ datum ] === null || ref_data [ datum ] === undefined ) { return 'N/A' ; } return that . tooltip_formats [ index ] ( ref_data [ datum ] ) ; } ) ; } this . popper . enableEventListeners ( ) ; this . move_tooltip ( ) ; } } mousemove_handler ( ) { this . move_tooltip ( ) ; } move_tooltip ( ) { ( this . popper_reference as any ) . x = d3 . event . clientX ; ( this . popper_reference as any ) . y = d3 . event . clientY ; this . popper . scheduleUpdate ( ) ; } hide_tooltip ( ) { this . tooltip_div . style ( 'pointer-events' , 'none' ) ; this . tooltip_div . transition ( ) . style ( 'opacity' , 0 ) . style ( 'display' , 'none' ) ; this . popper . disableEventListeners ( ) ; } create_tooltip_widget ( ) { const tooltip_model = this . model . get ( 'tooltip_widget' ) ; if ( this . tooltip_view !== null && this . tooltip_view !== undefined ) { this . tooltip_view . remove ( ) ; this . tooltip_view = null ; } const that = this ; if ( tooltip_model ) { const tooltip_widget_creation_promise = this . create_child_view ( tooltip_model ) ; tooltip_widget_creation_promise . then ( ( view ) => { that . tooltip_view = view as Tooltip ; MessageLoop . sendMessage ( view . pWidget , Widget . Msg . BeforeAttach ) ; that . tooltip_div . node ( ) . appendChild ( view . el ) ; MessageLoop . sendMessage ( view . pWidget , Widget . Msg . AfterAttach ) ; } ) ; } } get_group_transform ( index ) { return 'translate(' + '0' + ', 0)' ; } get_cell_transform ( index ) { if ( ! this . past_border_y ( ) ) { if ( this . past_border_x ( ) ) { this . y_direction = - 1 * this . y_direction ; this . prev_x += this . x_direction ; } else { this . x_direction = - 1 * this . x_direction ; this . prev_y += this . y_direction ; this . group_iter += 1 ; } } else { this . prev_y += this . y_direction ; } return ( 'translate(' + this . prev_x * this . column_width + ', ' + this . prev_y * this . row_height + ')' ) ; } get_new_cords ( ) { let new_x = this . prev_x ; let new_y = this . prev_y ; let y_direction = this . y_direction ; let x_direction = this . x_direction ; let group_iter = this . group_iter ; if ( ! this . past_border_y ( ) ) { if ( this . past_border_x ( ) ) { y_direction = - 1 * this . y_direction ; new_x += this . x_direction ; } else { x_direction = - 1 * this . x_direction ; new_y += this . y_direction ; group_iter += 1 ; } } else { new_y += this . y_direction ; } return [ new_x , new_y , group_iter , x_direction , y_direction , new_x * this . column_width , new_y * this . row_height , ] ; } past_border_y ( ) { if ( this . y_direction == 1 ) { return this . prev_y + 1 < this . row_limits [ this . group_iter ] ; } else { return this . prev_y - 1 > this . row_limits [ this . group_iter - 1 ] - 1 ; } } past_border_x ( ) { if ( this . x_direction == 1 ) { return this . prev_x + 1 < this . num_cols ; } else { return this . prev_x - 1 > - 1 ; } } colors_updated ( ) { this . colors = this . model . get ( 'colors' ) ; this . recolor_chart ( ) ; } get_color ( index , length ) { return this . colors [ index % length ] ; } set_row_limits ( ) { const step = Math . floor ( this . num_rows / this . row_groups ) ; this . row_limits = [ ] ; for ( let iter = this . row_groups - 1 ; iter > - 1 ; iter -- ) { this . row_limits . unshift ( iter * step ) ; } this . row_limits [ this . row_groups ] = this . num_rows ; } get_end_points ( group_iter , num_cells , start_col , start_row , x_direction , y_direction ) { let top_row = this . row_limits [ group_iter - 1 ] ; let bottom_row = this . row_limits [ group_iter ] ; let across = false ; let init_x = x_direction ; let init_y = y_direction ; const end_points = [ ] ; let current_row ; const rows_remaining = init_y == 1 ? bottom_row - start_row : start_row - top_row + 1 ; let cols_remaining = init_x == 1 ? this . num_cols - 1 - start_col : start_col ; let elem_remaining = num_cells ; let num_rows = bottom_row - top_row ; if ( elem_remaining !== 0 ) { this . calc_end_point_source ( start_col , start_row , init_x , init_y ) . forEach ( ( d ) => { end_points . push ( d ) ; } ) ; const elem_filled = Math . min ( rows_remaining , elem_remaining ) ; if ( elem_filled === elem_remaining ) { this . calc_end_point_source ( start_col , start_row , - 1 * init_x , init_y ) . forEach ( ( d ) => { end_points . push ( d ) ; } ) ; current_row = start_row + ( elem_remaining - 1 ) * init_y ; this . calc_end_point_dest ( start_col , current_row , - 1 * init_x , init_y ) . forEach ( ( e ) => { end_points . push ( e ) ; } ) ; this . calc_end_point_dest ( start_col , current_row , init_x , init_y ) . forEach ( ( e ) => { end_points . push ( e ) ; } ) ; return end_points ; } elem_remaining = elem_remaining - elem_filled ; if ( cols_remaining === 0 ) { this . calc_end_point_source ( start_col , start_row , init_x * - 1 , init_y ) . forEach ( ( d ) => { end_points . push ( d ) ; } ) ; } else if ( rows_remaining !== bottom_row - top_row ) { this . calc_end_point_source ( start_col , start_row , init_x * - 1 , init_y ) . forEach ( ( d ) => { end_points . push ( d ) ; } ) ; if ( elem_remaining > num_rows ) { this . calc_end_point_dest ( start_col + init_x , init_y == 1 ? top_row : bottom_row - 1 , init_x * - 1 , init_y * - 1 ) . forEach ( ( d ) => { end_points . push ( d ) ; } ) ; } } else if ( elem_remaining < num_rows ) { this . calc_end_point_source ( start_col , start_row , - 1 * init_x , init_y ) . forEach ( ( d ) => { end_points . push ( d ) ; } ) ; } start_row = start_row + init_y * ( elem_filled - 1 ) ; this . calc_end_point_dest ( start_col , start_row , - 1 * init_x , init_y ) . forEach ( ( d ) => { end_points . push ( d ) ; } ) ; if ( elem_remaining === 0 ) { this . calc_end_point_dest ( start_col , start_row , init_x , init_y ) . forEach ( ( d ) => { end_points . push ( d ) ; } ) ; return end_points ; } if ( cols_remaining !== 0 && elem_remaining > num_rows ) { start_col = start_col + init_x ; } } while ( elem_remaining > num_rows ) { let no_cont_cols ; if ( num_rows * cols_remaining < elem_remaining ) { no_cont_cols = cols_remaining ; const leftover_elem = elem_remaining - no_cont_cols * num_rows ; no_cont_cols += Math . floor ( leftover_elem / ( this . row_limits [ group_iter + 1 ] - this . row_limits [ group_iter ] ) ) ; } else { no_cont_cols = Math . floor ( elem_remaining / num_rows ) ; } if ( no_cont_cols > cols_remaining ) { start_col = init_x === 1 ? this . num_cols - 1 : 0 ; if ( cols_remaining !== 0 ) { this . calc_end_point_dest ( start_col , top_row , init_x , - 1 ) . forEach ( ( d ) => { end_points . push ( d ) ; } ) ; } no_cont_cols = cols_remaining ; cols_remaining = this . num_cols ; group_iter += 1 ; top_row = bottom_row ; bottom_row = this . row_limits [ group_iter ] ; start_row = top_row ; init_x = - 1 * init_x ; init_y = Math . pow ( - 1 , no_cont_cols ) * init_y * - 1 ; this . calc_end_point_dest ( start_col , bottom_row - 1 , - 1 * init_x , 1 ) . forEach ( ( d ) => { end_points . push ( d ) ; } ) ; } else if ( no_cont_cols === cols_remaining ) { start_col = init_x === 1 ? this . num_cols - 1 : 0 ; if ( cols_remaining !== 0 ) { this . calc_end_point_dest ( start_col , top_row , init_x , - 1 ) . forEach ( ( d ) => { end_points . push ( d ) ; } ) ; } no_cont_cols = cols_remaining ; cols_remaining = this . num_cols ; group_iter += 1 ; if ( group_iter < this . row_limits . length ) { top_row = bottom_row ; bottom_row = this . row_limits [ group_iter ] ; start_row = top_row ; init_x = - 1 * init_x ; init_y = Math . pow ( - 1 , no_cont_cols ) * init_y * - 1 ; across = true ; } else { init_y = 1 ; init_x = 1 ; } } else { init_y = Math . pow ( - 1 , no_cont_cols ) * init_y ; start_row = init_y === 1 ? top_row : bottom_row - 1 ; start_col = start_col + init_x * ( no_cont_cols - 1 ) ; this . calc_end_point_source ( start_col , start_row , - 1 * init_x , init_y ) . forEach ( ( d ) => { end_points . push ( d ) ; } ) ; } elem_remaining -= no_cont_cols * num_rows ; num_rows = bottom_row - top_row ; } if ( elem_remaining === 0 ) { start_row = init_y === 1 ? bottom_row - 1 : top_row ; init_x = across ? - 1 * init_x : init_x ; this . calc_end_point_dest ( start_col , start_row , init_x , init_y ) . forEach ( ( d ) => { end_points . push ( d ) ; } ) ; } else { init_y = - 1 * init_y ; start_row = init_y === 1 ? top_row : bottom_row - 1 ; start_col = across ? start_col : start_col + init_x ; this . calc_end_point_source ( start_col , start_row , init_x * - 1 , init_y ) . forEach ( ( d ) => { end_points . push ( d ) ; } ) ; current_row = start_row + ( elem_remaining - 1 ) * init_y ; this . calc_end_point_dest ( start_col , current_row , init_x , init_y ) . forEach ( ( d ) => { end_points . push ( d ) ; } ) ; this . calc_end_point_dest ( start_col , current_row , - 1 * init_x , init_y ) . forEach ( ( d ) => { end_points . push ( d ) ; } ) ; } return end_points ; } create_bounding_path ( elem , end_points ) { const values = [ ] ; const editing_copy = end_points . slice ( ) ; values . push ( end_points [ 0 ] ) ; editing_copy . splice ( 0 , 1 ) ; const props = [ 'x' , 'y' ] ; let iter = 0 ; let prop = props [ iter % 2 ] ; let other_prop = props [ ( iter + 1 ) % 2 ] ; const curr_elem = values [ 0 ] ; let match = curr_elem [ prop ] ; let dim = curr_elem [ other_prop ] ; let max_iter = 2 * editing_copy . length ; let final_val = 0 ; while ( editing_copy . length > 1 && max_iter > 0 ) { const filtered_array = editing_copy . filter ( ( elem ) => { return elem [ prop ] == match ; } ) ; if ( filtered_array . length > 0 ) { iter ++ ; const min_elem = d3 . min ( filtered_array , ( elem ) => { return elem [ other_prop ] as number ; } ) ; const max_elem = d3 . max ( filtered_array , ( elem ) => { return elem [ other_prop ] as number ; } ) ; if ( min_elem < dim && max_elem > dim ) { if ( prop == 'y' ) { if ( this . x_direction == 1 ) { final_val = max_elem ; } else { final_val = min_elem ; } } else { const lesser_arr = filtered_array . filter ( ( elem ) => { return elem [ other_prop ] < dim ; } ) ; const greater_arr = filtered_array . filter ( ( elem ) => { return elem [ other_prop ] > dim ; } ) ; if ( lesser_arr . length == 1 ) { final_val = min_elem ; } else if ( greater_arr . length == 1 ) { final_val = max_elem ; } else { final_val = d3 . max ( lesser_arr , ( elem ) => { return elem [ other_prop ] as number ; } ) ; } } } else { if ( min_elem > dim ) { final_val = min_elem ; } else { final_val = max_elem ; } } const match_elem = editing_copy . filter ( ( elem ) => { return elem [ prop ] == match && elem [ other_prop ] == final_val ; } ) ; match_elem . forEach ( ( elem ) => { editing_copy . splice ( editing_copy . indexOf ( elem ) , 1 ) ; } ) ; const value = { } ; value [ prop ] = match ; value [ other_prop ] = final_val ; values . push ( value ) ; } else { final_val = dim ; } const temp = prop ; prop = other_prop ; other_prop = temp ; dim = match ; match = final_val ; max_iter -- ; } if ( editing_copy . length > 0 ) { values . push ( editing_copy [ 0 ] ) ; } values . push ( end_points [ 0 ] ) ; const line = d3 . line ( ) . curve ( d3 . curveLinear ) . x ( ( d ) => { return d . x ; } ) . y ( ( d ) => { return d . y ; } ) ; const bounding_path = d3 . select ( elem ) . append ( 'path' ) . attr ( 'class' , 'bounding_path' ) . attr ( 'd' , ( ) => { return line ( values ) ; } ) . attr ( 'fill' , 'none' ) . style ( 'stroke' , this . model . get ( 'group_stroke' ) ) . style ( 'stroke-width' , 3 ) ; return bounding_path ; } calc_end_point_source ( curr_x , curr_y , x_direction , y_direction ) { curr_y = y_direction == 1 ? curr_y : curr_y + 1 ; curr_x = x_direction == 1 ? curr_x : curr_x + 1 ; return [ { x : curr_x * this . column_width , y : curr_y * this . row_height } ] ; } calc_end_point_dest ( curr_x , curr_y , x_direction , y_direction ) { curr_y = y_direction == - 1 ? curr_y : curr_y + 1 ; curr_x = x_direction == - 1 ? curr_x : curr_x + 1 ; return [ { x : curr_x * this . column_width , y : curr_y * this . row_height } ] ; } scales : Dict < Scale > ; num_rows : number ; num_cols : number ; row_groups : number ; enable_select : boolean ; data : string [ ] ; fig_map : d3 . Selection < SVGGElement , any , any , any > ; fig_click : d3 . Selection < SVGGElement , any , any , any > ; fig_hover : d3 . Selection < SVGGElement , any , any , any > ; fig_names : d3 . Selection < SVGGElement , any , any , any > ; selected_stroke : string ; hovered_stroke : string ; column_width : number ; row_height : number ; prev_x : number ; prev_y : number ; y_direction : number ; x_direction : number ; group_iter : number ; ref_data : any [ ] ; group_data : number [ ] ; groups : any [ ] ; colors : string [ ] ; colors_map : any ; grouped_data : { [ key ] : { [ key ] : any ; } [ ] ; } ; running_sums : number [ ] ; tooltip_fields : ( number | string ) [ ] ; tooltip_formats : ( ( d : number | string ) => number | string ) [ ] ; rect_groups : d3 . Selection < d3 . BaseType , any , SVGGraphicsElement , any > ; end_points : number [ ] ; tooltip_view : Tooltip ; row_limits : number [ ] ; model : MarketMapModel ; }