import { ComputedShapeParams , Ellipse , Line , Rectangle , Shape , ShapeParams , Triangle } from "./oncoprintshape" ; import heatmapColors from "./heatmapcolors" ; import binarysearch from "./binarysearch" ; import { Omit , cloneShallow , ifndef , objectValues , shallowExtend , z_comparator } from "./utils" ; import { ActiveRules , ColumnProp , Datum , RuleSetId } from "./oncoprintmodel" ; import _ from "lodash" ; import extractrgba , { hexToRGBA , rgbaToHex } from "./extractrgba" ; export type RuleSetParams = ILinearInterpRuleSetParams | ICategoricalRuleSetParams | IGradientRuleSetParams | IBarRuleSetParams | IStackedBarRuleSetParams | IGradientAndCategoricalRuleSetParams | IGeneticAlterationRuleSetParams ; interface IGeneralRuleSetParams { type ? : RuleSetType ; legend_label ? : string ; legend_base_color ? : RGBAColor ; exclude_from_legend ? : boolean ; na_z ? : number ; na_legend_label ? : string ; na_shapes ? : ShapeParams [ ] ; } interface ILinearInterpRuleSetParams extends IGeneralRuleSetParams { log_scale ? : boolean ; value_key : string ; value_range : [ number , number ] ; } export interface ICategoricalRuleSetParams extends IGeneralRuleSetParams { type : RuleSetType . CATEGORICAL ; category_key : string ; category_to_color ? : { [ category ] : RGBAColor ; } ; universal_rule_categories ? : { [ category ] : any ; } ; } export interface IGradientRuleSetParams extends ILinearInterpRuleSetParams { type : RuleSetType . GRADIENT ; colors ? : RGBAColor [ ] ; colormap_name ? : string ; value_stop_points : number [ ] ; null_color ? : RGBAColor ; null_legend_label ? : string ; } export interface IGradientAndCategoricalRuleSetParams extends IGeneralRuleSetParams { type : RuleSetType . GRADIENT_AND_CATEGORICAL ; colors ? : RGBAColor [ ] ; colormap_name ? : string ; value_stop_points : number [ ] ; null_color ? : RGBAColor ; log_scale ? : boolean ; value_key : string ; value_range : [ number , number ] ; category_key : string ; category_to_color ? : { [ category ] : RGBAColor ; } ; } export interface IBarRuleSetParams extends ILinearInterpRuleSetParams { type : RuleSetType . BAR ; fill ? : RGBAColor ; negative_fill ? : RGBAColor ; } export interface IStackedBarRuleSetParams extends IGeneralRuleSetParams { type : RuleSetType . STACKED_BAR ; value_key : string ; categories : string [ ] ; fills ? : RGBAColor [ ] ; } export interface IGeneticAlterationRuleSetParams extends IGeneralRuleSetParams { type : RuleSetType . GENE ; rule_params : GeneticAlterationRuleParams ; } type GeneticAlterationSingleRuleParams = { shapes : ShapeParams [ ] ; legend_label : string ; exclude_from_legend ? : boolean ; legend_order ? : number ; } ; export type GeneticAlterationRuleParams = { always ? : GeneticAlterationSingleRuleParams ; conditional : { [ datumKey ] : { [ commaSeparatedDatumValues ] : GeneticAlterationSingleRuleParams ; } ; } ; } ; export type RGBAColor = [ number , number , number , number ] ; type RuleParams = { shapes : ShapeParams [ ] ; legend_label ? : string ; exclude_from_legend ? : boolean ; legend_config ? : RuleLegendConfig ; legend_order ? : number ; legend_base_color ? : RGBAColor ; } ; type RuleLegendConfig = { type : "rule" ; target : any ; } | { type : "number" ; range : [ number , number ] ; range_type : LinearInterpRangeType ; positive_color : RGBAColor ; negative_color : RGBAColor ; interpFn : ( val : number ) => number ; } | { type : "gradient" ; range : [ number , number ] ; colorFn : ( val : number ) => RGBAColor ; } ; export enum RuleSetType { CATEGORICAL = "categorical" , GRADIENT = "gradient" , GRADIENT_AND_CATEGORICAL = "gradient+categorical" , BAR = "bar" , STACKED_BAR = "stacked_bar" , GENE = "gene" } export type RuleId = number ; export type RuleWithId = { id : RuleId ; rule : Rule ; } ; function makeIdCounter ( ) { let id = 0 ; return function ( ) { id += 1 ; return id ; } ; } function intRange ( length ) { const ret = [ ] ; for ( let i = 0 ; i < length ; i ++ ) { ret . push ( i ) ; } return ret ; } function makeUniqueColorGetter ( init_used_colors ) { init_used_colors = init_used_colors || [ ] ; const colors = [ "#3366cc" , "#dc3912" , "#ff9900" , "#109618" , "#990099" , "#0099c6" , "#dd4477" , "#66aa00" , "#b82e2e" , "#316395" , "#994499" , "#22aa99" , "#aaaa11" , "#6633cc" , "#e67300" , "#8b0707" , "#651067" , "#329262" , "#5574a6" , "#3b3eac" , "#b77322" , "#16d620" , "#b91383" , "#f4359e" , "#9c5935" , "#a9c413" , "#2a778d" , "#668d1c" , "#bea413" , "#0c5922" , "#743411" ] ; ; let index = 0 ; const used_colors = { } ; for ( let i = 0 ; i < init_used_colors . length ; i ++ ) { used_colors [ init_used_colors [ i ] ] = true ; } return function ( color ? ) { if ( color ) { used_colors [ color ] = true ; } else { let next_color = colors [ index % colors . length ] ; while ( used_colors [ next_color ] ) { const darker_next_color = darkenHexColor ( next_color ) ; if ( darker_next_color === next_color ) { break ; } next_color = darker_next_color ; } used_colors [ next_color ] = true ; index += 1 ; return hexToRGBA ( next_color ) ; } } ; } function makeNAShapes ( z ) { return [ { 'type' : 'rectangle' , 'fill' : [ 255 , 255 , 255 , 1 ] , 'z' : z } , { 'type' : 'line' , 'stroke' : [ 190 , 190 , 190 , 1 ] , 'stroke-width' : 1 , 'x1' : 0 , 'x2' : 100 , 'y1' : 50 , 'y2' : 50 , 'z' : z } ] ; } const NA_STRING = "na" ; const NA_LABEL = "No data" ; function colorToHex ( color ) { let r ; let g ; let b ; const rgba_match = color . match ( /^[\s]*rgba\([\s]*([0-9]+)[\s]*,[\s]*([0-9]+)[\s]*,[\s]*([0-9]+)[\s]*,[\s]*([0-9.]+)[\s]*\)[\s]*$/ ) ; if ( rgba_match && rgba_match . length === 5 ) { r = parseInt ( rgba_match [ 1 ] ) . toString ( 16 ) ; g = parseInt ( rgba_match [ 2 ] ) . toString ( 16 ) ; b = parseInt ( rgba_match [ 3 ] ) . toString ( 16 ) ; if ( r . length === 1 ) { r = '0' + r ; } if ( g . length === 1 ) { g = '0' + g ; } if ( b . length === 1 ) { b = '0' + b ; } return '#' + r + g + b ; } const rgb_match = color . match ( /^[\s]*rgb\([\s]*([0-9]+)[\s]*,[\s]*([0-9]+)[\s]*,[\s]*([0-9]+)[\s]*\)[\s]*$/ ) ; if ( rgb_match && rgb_match . length === 4 ) { r = parseInt ( rgb_match [ 1 ] ) . toString ( 16 ) ; g = parseInt ( rgb_match [ 2 ] ) . toString ( 16 ) ; b = parseInt ( rgb_match [ 3 ] ) . toString ( 16 ) ; if ( r . length === 1 ) { r = '0' + r ; } if ( g . length === 1 ) { g = '0' + g ; } if ( b . length === 1 ) { b = '0' + b ; } return '#' + r + g + b ; } return color ; } function darkenHexChannel ( c ) { let numC = parseInt ( c , 16 ) ; numC *= 0.95 ; numC = Math . round ( numC ) ; c = numC . toString ( 16 ) ; if ( c . length === 1 ) { c = '0' + c ; } return c ; } function darkenHexColor ( color ) { let r = color [ 1 ] + color [ 2 ] ; let g = color [ 3 ] + color [ 4 ] ; let b = color [ 5 ] + color [ 6 ] ; r = darkenHexChannel ( r ) ; g = darkenHexChannel ( g ) ; b = darkenHexChannel ( b ) ; return '#' + r + g + b ; } export class RuleSet { static getRuleSetId = makeIdCounter ( ) ; static getRuleId = makeIdCounter ( ) ; public rule_set_id : RuleSetId ; public legend_label ? : string ; protected legend_base_color ? : RGBAColor ; public exclude_from_legend ? : boolean ; protected active_rule_ids : ActiveRules ; protected rules_with_id : RuleWithId [ ] ; protected universal_rule ? : RuleWithId ; constructor ( params ) { this . rule_set_id = RuleSet . getRuleSetId ( ) ; this . legend_label = params . legend_label ; this . legend_base_color = params . legend_base_color ; this . exclude_from_legend = params . exclude_from_legend ; this . active_rule_ids = { } ; this . rules_with_id = [ ] ; } public getLegendLabel ( ) { return this . legend_label ; } public getRuleSetId ( ) { return this . rule_set_id ; } public addRules ( list_of_params ) { const self = this ; return list_of_params . map ( function ( params ) { return self . _addRule ( params ) ; } ) ; } public _addRule ( params , rule_id ? ) { if ( typeof rule_id === "undefined" ) { rule_id = RuleSet . getRuleId ( ) ; } this . rules_with_id . push ( { id : rule_id , rule : new Rule ( params ) } ) ; return rule_id ; } public setUniversalRule ( r ) { this . universal_rule = r ; } public removeRule ( rule_id ) { var index = - 1 ; for ( let i = 0 ; i < this . rules_with_id . length ; i ++ ) { if ( this . rules_with_id [ i ] . id === rule_id ) { index = i ; break ; } } if ( index > - 1 ) { this . rules_with_id . splice ( index , 1 ) ; } delete this . active_rule_ids [ rule_id ] ; } public getRuleWithId ( rule_id ) { let ret = null ; for ( let i = 0 ; i < this . rules_with_id . length ; i ++ ) { if ( this . rules_with_id [ i ] . id === rule_id ) { ret = this . rules_with_id [ i ] ; break ; } } return ret ; } public isExcludedFromLegend ( ) { return this . exclude_from_legend ; } public getRule ( rule_id ) { return this . getRuleWithId ( rule_id ) . rule ; } public getRecentlyUsedRules ( ) { const self = this ; return Object . keys ( this . active_rule_ids ) . map ( function ( rule_id ) { return self . getRule ( parseInt ( rule_id , 10 ) ) ; } ) ; } public applyRulesToDatum ( rules_with_id , datum , cell_width , cell_height ) { let shapes = [ ] ; const rules_len = rules_with_id . length ; for ( let j = 0 ; j < rules_len ; j ++ ) { shapes = shapes . concat ( rules_with_id [ j ] . rule . apply ( datum , cell_width , cell_height ) ) ; } return shapes ; } public getSpecificRulesForDatum ( datum ? ) { throw "Not implemented on base class" ; } public getUniversalRule ( ) { return this . universal_rule ; } public getUniversalShapes ( cell_width , cell_height ) { if ( this . getUniversalRule ( ) ) { const shapes = this . getUniversalRule ( ) . rule . apply ( { } , cell_width , cell_height ) ; shapes . sort ( z_comparator ) ; return shapes ; } else { return [ ] ; } } public getSpecificShapesForDatum ( data , cell_width , cell_height , out_active_rules ? , data_id_key ? , important_ids ? ) { const ret = [ ] ; for ( var i = 0 ; i < data . length ; i ++ ) { const datum = data [ i ] ; const should_mark_active = ! important_ids || ! ! important_ids [ datum [ data_id_key ! ] ] ; const rules = this . getSpecificRulesForDatum ( datum ) ; if ( typeof out_active_rules !== 'undefined' && should_mark_active ) { for ( let j = 0 ; j < rules . length ; j ++ ) { out_active_rules [ rules [ j ] . id ] = true ; } } const shapes = this . applyRulesToDatum ( rules , data [ i ] , cell_width , cell_height ) ; shapes . sort ( z_comparator ) ; ret . push ( shapes ) ; } if ( this . getUniversalRule ( ) && typeof out_active_rules !== 'undefined' ) { out_active_rules [ this . getUniversalRule ( ) . id ] = true ; } return ret ; } } class LookupRuleSet extends RuleSet { private lookup_map_by_key_and_value : { [ key ] : { [ value ] : RuleWithId ; } ; } = { } ; private lookup_map_by_key : { [ key ] : RuleWithId ; } = { } ; private rule_id_to_conditions : { [ ruleId ] : { key : string ; value : string ; } [ ] ; } = { } ; public getSpecificRulesForDatum ( datum ? ) { if ( typeof datum === 'undefined' ) { return this . rules_with_id ; } let ret = [ ] ; for ( var key in datum ) { if ( ( key in datum ) && typeof datum [ key ] !== 'undefined' ) { var key_rule = this . lookup_map_by_key [ key ] ; if ( typeof key_rule !== 'undefined' ) { ret . push ( key_rule ) ; } var key_and_value_rule = ( this . lookup_map_by_key_and_value [ key ] && this . lookup_map_by_key_and_value [ key ] [ datum [ key ] ] ) || undefined ; if ( typeof key_and_value_rule !== 'undefined' ) { ret . push ( key_and_value_rule ) ; } } } return ret ; } private indexRuleForLookup ( condition_key , condition_value , rule_with_id ) { if ( condition_key === null ) { this . setUniversalRule ( rule_with_id ) ; } else { if ( condition_value === null ) { this . lookup_map_by_key [ condition_key ] = rule_with_id ; } else { this . lookup_map_by_key_and_value [ condition_key ] = this . lookup_map_by_key_and_value [ condition_key ] || { } ; this . lookup_map_by_key_and_value [ condition_key ] [ condition_value ] = rule_with_id ; } } this . rule_id_to_conditions [ rule_with_id . id ] = this . rule_id_to_conditions [ rule_with_id . id ] || [ ] ; this . rule_id_to_conditions [ rule_with_id . id ] . push ( { key : condition_key , value : condition_value } ) ; } ; public addRule ( condition_key , condition_value , params ) { const rule_id = this . _addRule ( params ) ; this . indexRuleForLookup ( condition_key , condition_value , this . getRuleWithId ( rule_id ) ) ; return rule_id ; } public linkExistingRule ( condition_key , condition_value , existing_rule_id ) { this . indexRuleForLookup ( condition_key , condition_value , this . getRuleWithId ( existing_rule_id ) ) ; } public removeRule ( rule_id ) { super . removeRule ( rule_id ) ; while ( this . rule_id_to_conditions [ rule_id ] . length > 0 ) { var condition = this . rule_id_to_conditions [ rule_id ] . pop ( ) ; if ( condition . key === null ) { this . universal_rule = undefined ; } else { if ( condition . value === null ) { delete this . lookup_map_by_key [ condition . key ] ; } else { delete this . lookup_map_by_key_and_value [ condition . key ] [ condition . value ] ; } } } delete this . rule_id_to_conditions [ rule_id ] ; } } type ConditionRuleSetCondition = ( d : Datum ) => boolean ; class ConditionRuleSet extends RuleSet { private rule_id_to_condition : { [ ruleId ] : ConditionRuleSetCondition ; } = { } ; constructor ( params , omitNArule ? ) { super ( params ) ; if ( ! omitNArule ) { this . addRule ( function ( d ) { return d [ NA_STRING ] === true ; } , { shapes : params . na_shapes || makeNAShapes ( params . na_z || 1000 ) , legend_label : params . na_legend_label || NA_LABEL , exclude_from_legend : false , legend_config : { 'type' : 'rule' , 'target' : { 'na' : true } } , legend_order : Number . POSITIVE_INFINITY } ) ; } } public getSpecificRulesForDatum ( datum ? ) { if ( typeof datum === 'undefined' ) { return this . rules_with_id ; } const ret = [ ] ; for ( let i = 0 ; i < this . rules_with_id . length ; i ++ ) { if ( this . rule_id_to_condition [ this . rules_with_id [ i ] . id ] ( datum ) ) { ret . push ( this . rules_with_id [ i ] ) ; } } return ret ; } public addRule ( condition , params , rule_id ? ) { rule_id = this . _addRule ( params , rule_id ) ; this . rule_id_to_condition [ rule_id ] = condition ; return rule_id ; } public removeRule ( rule_id ) { super . removeRule ( rule_id ) ; delete this . rule_id_to_condition [ rule_id ] ; } } class CategoricalRuleSet extends LookupRuleSet { public readonly category_key : string ; private readonly category_to_color : { [ category ] : RGBAColor ; } ; private readonly getUnusedColor : ( color ? : string ) => RGBAColor ; private readonly universal_rule_categories ? : { [ category ] : any ; } ; constructor ( params , omitNArule ? ) { super ( params ) ; if ( ! omitNArule ) { this . addRule ( NA_STRING , true , { shapes : params . na_shapes || makeNAShapes ( params . na_z || 1000 ) , legend_label : params . na_legend_label || NA_LABEL , exclude_from_legend : false , legend_config : { 'type' : 'rule' , 'target' : { 'na' : true } } , legend_order : Number . POSITIVE_INFINITY } ) ; } this . category_key = params . category_key ; this . universal_rule_categories = params . universal_rule_categories ; this . category_to_color = cloneShallow ( ifndef ( params . category_to_color , { } ) ) ; this . getUnusedColor = makeUniqueColorGetter ( objectValues ( this . category_to_color ) . map ( rgbaToHex ) ) ; for ( const category of Object . keys ( this . category_to_color ) ) { const color = this . category_to_color [ category ] ; this . addCategoryRule ( category , color ) ; this . getUnusedColor ( rgbaToHex ( color ) ) ; } } private addCategoryRule ( category , color ) { const legend_rule_target = { } ; legend_rule_target [ this . category_key ] = category ; const rule_params = { shapes : [ { type : 'rectangle' , fill : color , } ] , legend_label : category , exclude_from_legend : false , legend_config : { 'type' : 'rule' , 'target' : legend_rule_target } } ; if ( this . universal_rule_categories && this . universal_rule_categories . hasOwnProperty ( category ) ) { this . addRule ( null , category , rule_params ) ; } else { this . addRule ( this . category_key , category , rule_params ) ; } } public getSpecificShapesForDatum ( data , cell_width , cell_height , out_active_rules , data_id_key , important_ids ? ) { for ( let i = 0 , data_len = data . length ; i < data_len ; i ++ ) { if ( data [ i ] [ NA_STRING ] ) { continue ; } const category = data [ i ] [ this . category_key ] ; if ( ! ( category in this . category_to_color ) ) { const color = this . getUnusedColor ( ) ; this . category_to_color [ category ] = color ; this . addCategoryRule ( category , color ) ; } } return super . getSpecificShapesForDatum ( data , cell_width , cell_height , out_active_rules , data_id_key , important_ids ) ; } } export enum LinearInterpRangeType { ALL = 'ALL' , NON_NEGATIVE = 'NON_NEGATIVE' , NON_POSITIVE = 'NON_POSITIVE' } class LinearInterpRuleSet extends ConditionRuleSet { protected value_key : string ; protected value_range : [ number , number ] ; protected log_scale ? : boolean ; protected type : string ; protected makeInterpFn : ( ) => ( ( valToConvert : number ) => number ) ; protected inferred_value_range : [ number , number ] ; constructor ( params ) { super ( params ) ; this . value_key = params . value_key ; this . value_range = params . value_range ; this . log_scale = params . log_scale ; this . type = params . type ; this . makeInterpFn = function ( ) { const range = this . getEffectiveValueRange ( ) ; const rangeType = this . getValueRangeType ( ) ; const plotType = this . type ; if ( this . log_scale ) { var shift_to_make_pos = Math . abs ( range [ 0 ] ) + 1 ; var log_range = Math . log ( range [ 1 ] + shift_to_make_pos ) - Math . log ( range [ 0 ] + shift_to_make_pos ) ; var log_range_lower = Math . log ( range [ 0 ] + shift_to_make_pos ) ; return function ( val ) { return ( Math . log ( val + shift_to_make_pos ) - log_range_lower ) / log_range ; } ; } else { return function ( val ) { var range_spread = range [ 1 ] - range [ 0 ] , range_lower = range [ 0 ] , range_higher = range [ 1 ] ; if ( plotType === 'bar' ) { if ( rangeType === LinearInterpRangeType . NON_POSITIVE ) { return ( val - range_higher ) / range_spread ; } else if ( rangeType === LinearInterpRangeType . NON_NEGATIVE ) { return ( val - range_lower ) / range_spread ; } else if ( rangeType === LinearInterpRangeType . ALL ) { range_spread = Math . abs ( range [ 0 ] ) > range [ 1 ] ? Math . abs ( range [ 0 ] ) : range [ 1 ] ; return val / range_spread ; } } else { return ( val - range_lower ) / range_spread ; } } ; } } ; } protected getEffectiveValueRange ( ) { const ret = ( this . value_range && this . value_range . slice ( ) ) || [ undefined , undefined ] ; if ( typeof ret [ 0 ] === "undefined" ) { ret [ 0 ] = this . inferred_value_range [ 0 ] ; } if ( typeof ret [ 1 ] === "undefined" ) { ret [ 1 ] = this . inferred_value_range [ 1 ] ; } if ( ret [ 0 ] === ret [ 1 ] ) { ret [ 0 ] -= ret [ 0 ] / 2 ; ret [ 1 ] += ret [ 1 ] / 2 ; } return ret as [ number , number ] ; } protected getValueRangeType ( ) { var range = this . getEffectiveValueRange ( ) ; if ( range [ 0 ] < 0 && range [ 1 ] <= 0 ) { return LinearInterpRangeType . NON_POSITIVE ; } else if ( range [ 0 ] >= 0 && range [ 1 ] > 0 ) { return LinearInterpRangeType . NON_NEGATIVE ; } else { return LinearInterpRangeType . ALL ; } } public getSpecificShapesForDatum ( data , cell_width , cell_height , out_active_rules , data_id_key , important_ids ? ) { let value_min = Number . POSITIVE_INFINITY ; let value_max = Number . NEGATIVE_INFINITY ; for ( var i = 0 , datalen = data . length ; i < datalen ; i ++ ) { const d = data [ i ] ; if ( isNaN ( d [ this . value_key ] ) ) { continue ; } value_min = Math . min ( value_min , d [ this . value_key ] ) ; value_max = Math . max ( value_max , d [ this . value_key ] ) ; } if ( value_min === Number . POSITIVE_INFINITY ) { value_min = 0 ; } if ( value_max === Number . NEGATIVE_INFINITY ) { value_max = 0 ; } this . inferred_value_range = [ value_min , value_max ] ; this . updateLinearRules ( ) ; return super . getSpecificShapesForDatum ( data , cell_width , cell_height , out_active_rules , data_id_key , important_ids ) ; } protected updateLinearRules ( ) { throw "Not implemented in abstract class" ; } } class GradientRuleSet extends LinearInterpRuleSet { private colors : RGBAColor [ ] = [ ] ; private value_stop_points : number [ ] ; private null_color ? : RGBAColor ; private gradient_rule : RuleId ; constructor ( params ) { super ( params ) ; if ( params . colors ) { this . colors = params . colors || [ ] ; } else if ( params . colormap_name ) { this . colors = heatmapColors [ params . colormap_name ] || [ ] ; } if ( this . colors . length === 0 ) { this . colors . push ( [ 0 , 0 , 0 , 1 ] , [ 255 , 0 , 0 , 1 ] ) ; } this . value_stop_points = params . value_stop_points ; this . null_color = params . null_color || [ 211 , 211 , 211 , 1 ] ; var self = this ; var value_key = this . value_key ; this . addRule ( function ( d ) { return d [ NA_STRING ] !== true && d [ value_key ] === null ; } , { shapes : [ { type : 'rectangle' , fill : self . null_color } ] , legend_label : params . null_legend_label || "Not a number" , exclude_from_legend : false , legend_config : { 'type' : 'rule' , 'target' : { [ value_key ] : null } } } ) ; } static linInterpColors ( t , begin_color , end_color ) { return [ Math . round ( begin_color [ 0 ] * ( 1 - t ) + end_color [ 0 ] * t ) , Math . round ( begin_color [ 1 ] * ( 1 - t ) + end_color [ 1 ] * t ) , Math . round ( begin_color [ 2 ] * ( 1 - t ) + end_color [ 2 ] * t ) , begin_color [ 3 ] * ( 1 - t ) + end_color [ 3 ] * t ] ; } private makeColorFn ( colors , interpFn ) { const value_stop_points = this . value_stop_points ; let stop_points ; if ( value_stop_points ) { stop_points = value_stop_points . map ( interpFn ) ; } else { stop_points = intRange ( colors . length ) . map ( function ( x ) { return x / ( colors . length - 1 ) ; } ) ; } return function ( t ) : RGBAColor { var begin_interval_index = binarysearch ( stop_points , t , function ( x ) { return x ; } , true ) ; if ( begin_interval_index === - 1 ) { return [ 0 , 0 , 0 , 1 ] ; } var end_interval_index = Math . min ( colors . length - 1 , begin_interval_index + 1 ) ; var spread = stop_points [ end_interval_index ] - stop_points [ begin_interval_index ] ; if ( spread === 0 ) { return colors [ end_interval_index ] ; } else { var interval_t = ( t - stop_points [ begin_interval_index ] ) / spread ; var begin_color = colors [ begin_interval_index ] ; var end_color = colors [ end_interval_index ] ; return GradientRuleSet . linInterpColors ( interval_t , begin_color , end_color ) ; } } ; } protected updateLinearRules ( ) { let rule_id ; if ( typeof this . gradient_rule !== "undefined" ) { rule_id = this . gradient_rule ; this . removeRule ( this . gradient_rule ) ; } const interpFn = this . makeInterpFn ( ) ; const colorFn = this . makeColorFn ( this . colors , interpFn ) ; const value_key = this . value_key ; const null_color = this . null_color ; this . gradient_rule = this . addRule ( function ( d ) { return d [ NA_STRING ] !== true && d [ value_key ] !== null ; } , { shapes : [ { type : 'rectangle' , fill : function ( d ) { var t = interpFn ( d [ value_key ] ) ; return colorFn ( t ) as RGBAColor ; } } ] , exclude_from_legend : false , legend_config : { 'type' : "gradient" as "gradient" , 'range' : this . getEffectiveValueRange ( ) , 'colorFn' : colorFn } } , rule_id ) ; } } class BarRuleSet extends LinearInterpRuleSet { private fill : RGBAColor ; private negative_fill : RGBAColor ; private bar_rule ? : RuleId ; constructor ( params ) { super ( params ) ; this . fill = params . fill || [ 0 , 128 , 0 , 1 ] ; this . negative_fill = params . negative_fill || [ 255 , 0 , 0 , 1 ] ; } protected updateLinearRules ( ) { let rule_id ; if ( typeof this . bar_rule !== "undefined" ) { rule_id = this . bar_rule ; this . removeRule ( this . bar_rule ) ; } const interpFn = this . makeInterpFn ( ) ; const value_key = this . value_key ; const positive_color = this . fill ; const negative_color = this . negative_fill ; const yPosFn = this . getYPosPercentagesFn ( ) ; const cellHeightFn = this . getCellHeightPercentagesFn ( ) ; this . bar_rule = this . addRule ( function ( d ) { return d [ NA_STRING ] !== true ; } , { shapes : [ { type : 'rectangle' , y : function ( d ) { var t = interpFn ( d [ value_key ] ) ; return yPosFn ( t ) ; } , height : function ( d ) { var t = interpFn ( d [ value_key ] ) ; return cellHeightFn ( t ) ; } , fill : function ( d ) { return d [ value_key ] < 0 ? negative_color : positive_color ; } } ] , exclude_from_legend : false , legend_config : { 'type' : 'number' as 'number' , 'range' : this . getEffectiveValueRange ( ) , 'range_type' : this . getValueRangeType ( ) , 'positive_color' : positive_color , 'negative_color' : negative_color , 'interpFn' : interpFn } } , rule_id ) ; } public getYPosPercentagesFn ( ) { let ret ; switch ( this . getValueRangeType ( ) ) { case LinearInterpRangeType . NON_POSITIVE : ret = ( function ( t ) { return 0 ; } ) ; break ; case LinearInterpRangeType . NON_NEGATIVE : ret = ( function ( t ) { return ( 1 - t ) * 100 ; } ) ; break ; case LinearInterpRangeType . ALL : ret = ( function ( t ) { return Math . min ( 1 - t , 1 ) * 50 ; } ) ; break ; } return ret ; } public getCellHeightPercentagesFn ( ) { let ret ; switch ( this . getValueRangeType ( ) ) { case LinearInterpRangeType . NON_POSITIVE : ret = ( function ( t ) { return - t * 100 ; } ) ; break ; case LinearInterpRangeType . NON_NEGATIVE : ret = ( function ( t ) { return t * 100 ; } ) ; break ; case LinearInterpRangeType . ALL : ret = ( function ( t ) { return Math . abs ( t ) * 50 ; } ) ; break ; } return ret ; } } class StackedBarRuleSet extends ConditionRuleSet { constructor ( params ) { super ( params ) ; const value_key = params . value_key ; const fills = params . fills || [ ] ; const categories = params . categories || [ ] ; const getUnusedColor = makeUniqueColorGetter ( fills . map ( rgbaToHex ) ) ; while ( fills . length < categories . length ) { fills . push ( getUnusedColor ( ) ) ; } const self = this ; for ( let i = 0 ; i < categories . length ; i ++ ) { ( function ( I ) { const legend_target = { } ; legend_target [ value_key ] = { } ; for ( let j = 0 ; j < categories . length ; j ++ ) { legend_target [ value_key ] [ categories [ j ] ] = 0 ; } legend_target [ value_key ] [ categories [ I ] ] = 1 ; self . addRule ( function ( d ) { return d [ NA_STRING ] !== true ; } , { shapes : [ { type : 'rectangle' , fill : fills [ I ] , width : 100 , height : function ( d ) { var total = 0 ; for ( var j = 0 ; j < categories . length ; j ++ ) { total += parseFloat ( d [ value_key ] [ categories [ j ] ] ) ; } return parseFloat ( d [ value_key ] [ categories [ I ] ] ) * 100 / total ; } , y : function ( d ) { var total = 0 ; var prev_vals_sum = 0 ; for ( var j = 0 ; j < categories . length ; j ++ ) { var new_val = parseFloat ( d [ value_key ] [ categories [ j ] ] ) ; if ( j < I ) { prev_vals_sum += new_val ; } total += new_val ; } return prev_vals_sum * 100 / total ; } } ] , exclude_from_legend : false , legend_config : { 'type' : 'rule' , 'target' : legend_target } , legend_label : categories [ I ] } ) ; } ) ( i ) ; } } } export class GeneticAlterationRuleSet extends LookupRuleSet { constructor ( params ) { super ( params ) ; this . addRulesFromParams ( params ) ; this . addRule ( NA_STRING , true , { shapes : params . na_shapes || makeNAShapes ( params . na_z || 1 ) , legend_label : params . na_legend_label || NA_LABEL , exclude_from_legend : false , legend_config : { 'type' : 'rule' , 'target' : { 'na' : true } } , legend_order : Number . POSITIVE_INFINITY } ) ; } private addRulesFromParams ( params ) { const rule_params = params . rule_params ; _ . forEach ( rule_params . conditional , ( datumValuesToRuleParams , datumKey ) => { _ . forEach ( datumValuesToRuleParams , ( ruleParams , commaSeparatedDatumValues ) => { const equiv_values = commaSeparatedDatumValues . split ( "," ) ; const legend_rule_target = { } ; legend_rule_target [ equiv_values [ 0 ] ] = commaSeparatedDatumValues ; const rule_id = this . addRule ( datumKey , ( equiv_values [ 0 ] === '*' ? null : equiv_values [ 0 ] ) , shallowExtend ( ruleParams , { shapes : ruleParams . shapes , legend_config : { 'type' : 'rule' as 'rule' , 'target' : legend_rule_target } , legend_base_color : ifndef ( this . legend_base_color , [ 255 , 255 , 255 , 1 ] ) } ) ) ; for ( let i = 1 ; i < equiv_values . length ; i ++ ) { this . linkExistingRule ( datumKey , ( equiv_values [ i ] === '*' ? null : equiv_values [ i ] ) , rule_id ) ; } } ) ; } ) ; if ( rule_params . always ) { this . addRule ( null , null , shallowExtend ( rule_params . always , { shapes : rule_params . always . shapes , legend_config : { 'type' : 'rule' as 'rule' , 'target' : { } } } ) ) ; } } } export class Rule { private shapes : Shape [ ] ; public legend_label : string ; public legend_base_color ? : RGBAColor ; public exclude_from_legend ? : boolean ; private legend_config ? : RuleLegendConfig ; public legend_order ? : number ; constructor ( params ) { this . shapes = params . shapes . map ( function ( shape ) { if ( shape . type === 'rectangle' ) { return new Rectangle ( shape ) ; } else if ( shape . type === 'triangle' ) { return new Triangle ( shape ) ; } else if ( shape . type === 'ellipse' ) { return new Ellipse ( shape ) ; } else if ( shape . type === 'line' ) { return new Line ( shape ) ; } } ) ; this . legend_label = typeof params . legend_label === "undefined" ? "" : params . legend_label ; this . legend_base_color = params . legend_base_color ; this . exclude_from_legend = params . exclude_from_legend ; this . legend_config = params . legend_config ; this . legend_order = params . legend_order ; } public getLegendConfig ( ) { return this . legend_config ; } public apply ( d , cell_width , cell_height ) { const concrete_shapes = [ ] ; for ( let i = 0 , shapes_len = this . shapes . length ; i < shapes_len ; i ++ ) { concrete_shapes . push ( this . shapes [ i ] . getComputedParams ( d , cell_width , cell_height ) ) ; } return concrete_shapes ; } public isExcludedFromLegend ( ) { return this . exclude_from_legend ; } } class GradientCategoricalRuleSet extends RuleSet { private gradientRuleSet : GradientRuleSet ; private categoricalRuleSet : CategoricalRuleSet ; constructor ( params ) { super ( params ) ; this . gradientRuleSet = new GradientRuleSet ( params ) ; this . categoricalRuleSet = new CategoricalRuleSet ( params , true ) ; } public getSpecificShapesForDatum ( data , cell_width , cell_height , out_active_rules , data_id_key , important_ids ? ) { const shapes = [ ] ; for ( let i = 0 ; i < data . length ; i ++ ) { const datum = data [ i ] ; if ( this . isCategorical ( datum ) ) { shapes . push ( this . categoricalRuleSet . getSpecificShapesForDatum ( [ datum ] , cell_width , cell_height , out_active_rules , data_id_key , important_ids ) [ 0 ] ) ; } else { shapes . push ( this . gradientRuleSet . getSpecificShapesForDatum ( [ datum ] , cell_width , cell_height , out_active_rules , data_id_key , important_ids ) [ 0 ] ) ; } } return shapes ; } public getSpecificRulesForDatum ( datum ? ) { const categoricalRules = this . categoricalRuleSet . getSpecificRulesForDatum ( datum ) ; const gradientRules = this . gradientRuleSet . getSpecificRulesForDatum ( datum ) ; const rules = categoricalRules . concat ( gradientRules ) ; return rules ; } public isCategorical ( datum ) { return datum [ this . categoricalRuleSet . category_key ] !== undefined ; } } export default function ( params ) { let ret ; switch ( params . type ) { case RuleSetType . CATEGORICAL : ret = new CategoricalRuleSet ( params as ICategoricalRuleSetParams ) ; break ; case RuleSetType . GRADIENT : ret = new GradientRuleSet ( params as IGradientRuleSetParams ) ; break ; case RuleSetType . GRADIENT_AND_CATEGORICAL : ret = new GradientCategoricalRuleSet ( params as IGradientAndCategoricalRuleSetParams ) ; break ; case RuleSetType . BAR : ret = new BarRuleSet ( params as IBarRuleSetParams ) ; break ; case RuleSetType . STACKED_BAR : ret = new StackedBarRuleSet ( params as IStackedBarRuleSetParams ) ; break ; case RuleSetType . GENE : default : ret = new GeneticAlterationRuleSet ( params as IGeneticAlterationRuleSetParams ) ; break ; } return ret ; } ;