import { Datum } from "./oncoprintmodel" ; import { RGBAColor } from "./oncoprintruleset" ; type StringParameter = "type" ; type PercentNumberParameter = "width" | "height" | "x" | "y" | "x1" | "x2" | "x3" | "y1" | "y2" | "y3" ; type PlainNumberParameter = "z" | "stroke-width" | "stroke-opacity" ; type RGBAParameter = "stroke" | "fill" ; type NumberParameter = PercentNumberParameter | PlainNumberParameter ; type Parameter = StringParameter | NumberParameter | RGBAParameter ; const default_parameter_values = { 'width' : 100 , 'height' : 100 , 'x' : 0 , 'y' : 0 , 'z' : 0 , 'x1' : 0 , 'x2' : 0 , 'x3' : 0 , 'y1' : 0 , 'y2' : 0 , 'y3' : 0 , 'stroke' : [ 0 , 0 , 0 , 0 ] , 'fill' : [ 23 , 23 , 23 , 1 ] , 'stroke-width' : 0 , 'stroke-opacity' : 0 } ; const percent_parameter_name_to_dimension_index = { 'width' : 0 , 'x' : 0 , 'x1' : 0 , 'x2' : 0 , 'x3' : 0 , 'height' : 1 , 'y' : 1 , 'y1' : 1 , 'y2' : 1 , 'y3' : 1 } ; const hash_parameter_order = [ "width" , "height" , "x" , "y" , "z" , "x1" , "x2" , "x3" , "y1" , "y2" , "y3" , "stroke" , "fill" , "stroke-width" , "stroke-opacity" , "type" ] ; type StringParamFunction = ( d : Datum ) => string ; type NumberParamFunction = ( d : Datum ) => number ; type RGBAParamFunction = ( d : Datum ) => RGBAColor ; type ParamFunction = StringParamFunction | NumberParamFunction | RGBAParamFunction ; export type ShapeParams = { [ x in StringParameter ] ? : string | StringParamFunction ; } & { [ x in NumberParameter ] ? : number | NumberParamFunction ; } & { [ x in RGBAParameter ] ? : RGBAColor | RGBAParamFunction ; } ; type ShapeParamsWithType = { [ x in StringParameter ] ? : ( { type : "function" ; value : StringParamFunction ; } | { type : "value" ; value : string ; } ) ; } & { [ x in NumberParameter ] ? : ( { type : "function" ; value : NumberParamFunction ; } | { type : "value" ; value : number ; } ) ; } & { [ x in RGBAParameter ] ? : ( { type : "function" ; value : RGBAParamFunction ; } | { type : "value" ; value : RGBAColor ; } ) ; } ; export type ComputedShapeParams = { [ x in StringParameter ] ? : string ; } & { [ x in NumberParameter ] ? : number ; } & { [ x in RGBAParameter ] ? : RGBAColor ; } ; function isPercentParam ( param_name ) { return param_name in percent_parameter_name_to_dimension_index ; } export class Shape { private static cache : { [ hash ] : ComputedShapeParams ; } = { } ; private params_with_type : ShapeParamsWithType = { } ; private onlyDependsOnWidthAndHeight : boolean ; private instanceCache = { lastComputedParams : null as ComputedShapeParams | null , lastWidth : - 1 , lastHeight : - 1 } ; constructor ( private params ) { this . completeWithDefaults ( ) ; this . markParameterTypes ( ) ; } public static hashComputedShape ( computed_params , z_index ? ) { return hash_parameter_order . reduce ( function ( hash , param_name ) { return hash + "," + computed_params [ param_name ] ; } , "" ) + "," + z_index ; } private static getCachedShape ( computed_params ) { const hash = Shape . hashComputedShape ( computed_params ) ; Shape . cache [ hash ] = Shape . cache [ hash ] || Object . freeze ( computed_params ) ; return Shape . cache [ hash ] ; } public getRequiredParameters ( ) { throw "Not defined for base class" ; } public completeWithDefaults ( ) { const required_parameters = this . getRequiredParameters ( ) ; for ( let i = 0 ; i < required_parameters . length ; i ++ ) { const param = required_parameters [ i ] ; this . params [ param ] = ( typeof this . params [ param ] === 'undefined' ? default_parameter_values [ param ] : this . params [ param ] ) as any ; } } public markParameterTypes ( ) { const parameters = Object . keys ( this . params ) as Parameter [ ] ; let onlyDependsOnWidthAndHeight = true ; for ( let i = 0 ; i < parameters . length ; i ++ ) { const param_name = parameters [ i ] ; const param_val = this . params [ param_name ] ; if ( typeof param_val === 'function' ) { this . params_with_type [ param_name ] = { 'type' : 'function' , 'value' : param_val } ; onlyDependsOnWidthAndHeight = false ; } else { this . params_with_type [ param_name ] = { 'type' : 'value' , 'value' : param_val } ; } } this . onlyDependsOnWidthAndHeight = onlyDependsOnWidthAndHeight ; } public getComputedParams ( d , base_width , base_height ) { if ( this . onlyDependsOnWidthAndHeight && this . instanceCache . lastWidth === base_width && this . instanceCache . lastHeight === base_height ) { return this . instanceCache . lastComputedParams ! ; } const computed_params = { } ; const param_names = Object . keys ( this . params_with_type ) as Parameter [ ] ; const dimensions = [ base_width , base_height ] ; for ( let i = 0 ; i < param_names . length ; i ++ ) { const param_name = param_names [ i ] ; const param_val_map = this . params_with_type [ param_name ] ; let param_val = param_val_map . value ; if ( param_name !== 'type' ) { if ( param_val_map . type === 'function' ) { param_val = ( param_val as ParamFunction ) ( d ) ; } if ( isPercentParam ( param_name ) ) { param_val = ( param_val as number ) / 100 * dimensions [ percent_parameter_name_to_dimension_index [ param_name ] ] ; } } computed_params [ param_name ] = param_val ; } if ( this . onlyDependsOnWidthAndHeight ) { this . instanceCache . lastHeight = base_height ; this . instanceCache . lastWidth = base_width ; this . instanceCache . lastComputedParams = Shape . getCachedShape ( computed_params ) ; } return Shape . getCachedShape ( computed_params ) ; } ; } type SpecificComputedShapeParams < ShapeParamType > = { [ x in ShapeParamType & StringParameter ] : string ; } & { [ x in ShapeParamType & NumberParameter ] : number ; } & { [ x in ShapeParamType & RGBAParameter ] : RGBAColor ; } ; type RectangleParameter = "width" | "height" | "x" | "y" | "z" | "stroke" | "stroke-width" | "fill" ; export type ComputedRectangleParams = SpecificComputedShapeParams < RectangleParameter > ; export class Rectangle extends Shape { public getRequiredParameters ( ) { return [ 'width' , 'height' , 'x' , 'y' , 'z' , 'stroke' , 'fill' , 'stroke-width' ] ; } } type TriangleParameter = "x1" | "x2" | "x3" | "y1" | "y2" | "y3" | "z" | "stroke" | "stroke-width" | "fill" ; export type ComputedTriangleParams = SpecificComputedShapeParams < TriangleParameter > ; export class Triangle extends Shape { public getRequiredParameters ( ) { return [ 'x1' , 'x2' , 'x3' , 'y1' , 'y2' , 'y3' , 'z' , 'stroke' , 'fill' , 'stroke-width' ] ; } } export type EllipseParameter = "width" | "height" | "x" | "y" | "z" | "stroke" | "stroke-width" | "fill" ; export type ComputedEllipseParams = SpecificComputedShapeParams < EllipseParameter > ; export class Ellipse extends Shape { public getRequiredParameters ( ) { return [ 'width' , 'height' , 'x' , 'y' , 'z' , 'stroke' , 'fill' , 'stroke-width' ] ; } } export type LineParameter = "x1" | "y1" | "x2" | "y2" | "z" | "stroke" | "stroke-width" ; export type ComputedLineParams = SpecificComputedShapeParams < LineParameter > ; export class Line extends Shape { public getRequiredParameters ( ) { return [ 'x1' , 'x2' , 'y1' , 'y2' , 'z' , 'stroke' , 'stroke-width' ] ; } }