import EditorActionsManager from "../managers/EditorActionsManager" ; import GraphUtilities from "../utils/GraphUtilities" ; export default class SVGExporter { SVGNameSpace = "http://www.w3.org/2000/svg" ; svg = document . createElementNS ( this . SVGNameSpace , "svg" ) ; NODE_FILL_COLOR = "rgb(255,255,255)" ; FAMILY_FILL_COLOR = "rgb(255,255,255)" ; FAMILY_STROKE_COLOR = "rgb(204,204,204)" ; NODE_STROKE_COLOR = "rgb(0,0,0)" ; COMPARTMENT_STROKE_WIDTH = 2 ; NODE_STROKE_WIDTH = 1 ; NODE_OPACITY = 0.5 ; ROUNDING_FACTOR = 6 ; GENOMICDATA_LABEL_Y_OFFSET = - 15 ; EDGE_ARROW_SCALE = 1.7 ; EDGE_WIDTH = 1 ; T_ARROW_HEAD_WIDTH = 2 * this . EDGE_ARROW_SCALE ; T_ARROW_HEAD_HEIGHT = 8 * this . EDGE_ARROW_SCALE ; TRIANGLE_ARROW_HEAD_HEIGHT = 8 * this . EDGE_ARROW_SCALE ; TRIANGLE_ARROW_HEAD_WIDTH = 8 * this . EDGE_ARROW_SCALE ; DASH_PARAMETERS = "5, 3" ; COMPOUND_MARGIN = 8 ; NODE_FONT_SIZE = 14 ; edgeEditing : any ; editor : EditorActionsManager ; constructor ( edgeEditing , editor ) { this . edgeEditing = edgeEditing ; this . editor = editor ; } resetSVG ( ) { this . svg = document . createElementNS ( this . SVGNameSpace , "svg" ) ; } exportGraph ( nodes , edges ) { this . resetSVG ( ) ; this . svg . setAttribute ( "version" , "1.1" ) ; this . svg . setAttribute ( "xmlns" , "http://www.w3.org/2000/svg" ) ; var cyBounds = this . editor . cy . extent ( ) ; this . svg . setAttribute ( "viewBox" , cyBounds . x1 + " " + cyBounds . y1 + " " + cyBounds . w + " " + cyBounds . h ) ; var self = this ; var nodeMap = { } ; var nodeTree = GraphUtilities . createGraphHierarchy ( nodes ) ; var traverseFunction = function ( node ) { nodeMap [ node . id ( ) ] = node ; var genomicDataSVG = self . editor . getGenomicDataSVG ( node ) . children ; var oncoprintDataSVG = self . editor . getOncoprintDataSVG ( node ) ; self . svg . appendChild ( self . createRect ( node ) ) ; var labelOffset = ( genomicDataSVG && genomicDataSVG . length > 0 ) || oncoprintDataSVG . outerHTML !== "" ? self . GENOMICDATA_LABEL_Y_OFFSET : 0 ; self . svg . appendChild ( self . createNodeLabel ( node , labelOffset ) ) ; if ( genomicDataSVG ) { while ( genomicDataSVG . length > 0 ) { var elemSVG = genomicDataSVG [ 0 ] ; var nodePosition = node . position ( ) ; var svgX = elemSVG . getAttribute ( "x" ) ; var svgY = elemSVG . getAttribute ( "y" ) ; elemSVG . setAttribute ( "x" , nodePosition . x - node . width ( ) / 2 + parseFloat ( svgX ) ) ; elemSVG . setAttribute ( "y" , nodePosition . y - node . height ( ) / 2 + parseFloat ( svgY ) ) ; self . svg . appendChild ( elemSVG ) ; } } else if ( oncoprintDataSVG . outerHTML !== "" ) { var nodePosition = node . position ( ) ; const width = parseInt ( oncoprintDataSVG . getAttribute ( "width" ) ) ; const height = parseInt ( oncoprintDataSVG . getAttribute ( "height" ) ) ; const verticalPadding = 8 ; const y = nodePosition . y + node . height ( ) / 2 - ( height + verticalPadding ) ; oncoprintDataSVG . setAttribute ( "x" , nodePosition . x - width / 2 ) ; oncoprintDataSVG . setAttribute ( "y" , y ) ; self . svg . appendChild ( oncoprintDataSVG ) ; } if ( node . childNodes ) { for ( var i in node . childNodes ) { traverseFunction ( node . childNodes [ i ] ) ; } } } ; for ( var i in nodeTree ) { var rootLevelNode = nodeTree [ i ] ; traverseFunction ( rootLevelNode ) ; } edges . forEach ( function ( edge ) { self . drawEdge ( edge ) ; } ) ; return this . svg . outerHTML ; } drawEdge ( edge ) { const sourceEndpoint = edge . sourceEndpoint ( ) ; const targetEndpoint = edge . targetEndpoint ( ) ; const targetArrowShape = edge . style ( "target-arrow-shape" ) ; const lineStyle = edge . style ( "line-style" ) ; const curveStyle = edge . style ( "curve-style" ) ; const anchorPoints = curveStyle === "segments" ? edge . segmentPoints ( ) : edge . controlPoints ( ) ; const labelElement = this . createEdgeLabel ( edge ) ; if ( labelElement ) { this . svg . appendChild ( labelElement ) ; } let lastAnchor = { x : sourceEndpoint . x , y : sourceEndpoint . y } ; let edgeEndpoint = { x : targetEndpoint . x , y : targetEndpoint . y } ; if ( anchorPoints && anchorPoints . length > 0 ) { lastAnchor = { x : anchorPoints [ anchorPoints . length - 1 ] . x , y : anchorPoints [ anchorPoints . length - 1 ] . y } ; } var unitV = this . unitVector ( { x : targetEndpoint . x - lastAnchor . x , y : targetEndpoint . y - lastAnchor . y , } ) ; var inverseUnitV = this . scale ( unitV , - 1 ) ; var targetX = targetEndpoint . x ; var targetY = targetEndpoint . y ; if ( targetArrowShape === "triangle" ) { targetX = targetEndpoint . x + this . TRIANGLE_ARROW_HEAD_HEIGHT * inverseUnitV . x ; targetY = targetEndpoint . y + this . TRIANGLE_ARROW_HEAD_HEIGHT * inverseUnitV . y ; var point1Vector = this . rotateVector ( unitV , Math . PI / 2 ) ; var point2Vector = this . rotateVector ( unitV , - Math . PI / 2 ) ; point1Vector = this . scale ( point1Vector , this . TRIANGLE_ARROW_HEAD_WIDTH / 2 ) ; point2Vector = this . scale ( point2Vector , this . TRIANGLE_ARROW_HEAD_WIDTH / 2 ) ; var point1X = targetX + point1Vector . x ; var point1Y = targetY + point1Vector . y ; var point2X = targetX + point2Vector . x ; var point2Y = targetY + point2Vector . y ; edgeEndpoint = { x : ( point1X + point2X ) / 2 , y : ( point1Y + point2Y ) / 2 } ; var polySVG = document . createElementNS ( this . SVGNameSpace , "polygon" ) ; polySVG . setAttribute ( "points" , point1X + "," + point1Y + "," + point2X + "," + point2Y + "," + targetEndpoint . x + "," + targetEndpoint . y ) ; this . svg . appendChild ( polySVG ) ; } else if ( targetArrowShape === "tee" ) { targetX = targetEndpoint . x ; targetY = targetEndpoint . y ; var point1Vector = this . rotateVector ( unitV , Math . PI / 2 ) ; var point2Vector = this . rotateVector ( unitV , - Math . PI / 2 ) ; point1Vector = this . scale ( point1Vector , this . T_ARROW_HEAD_HEIGHT / 2 ) ; point2Vector = this . scale ( point2Vector , this . T_ARROW_HEAD_HEIGHT / 2 ) ; var point1X = targetX + point1Vector . x ; var point1Y = targetY + point1Vector . y ; var point2X = targetX + point2Vector . x ; var point2Y = targetY + point2Vector . y ; edgeEndpoint = { x : ( point1X + point2X ) / 2 , y : ( point1Y + point2Y ) / 2 } ; var lineSVG = document . createElementNS ( this . SVGNameSpace , "line" ) ; lineSVG . setAttribute ( "x1" , point1X ) ; lineSVG . setAttribute ( "y1" , point1Y ) ; lineSVG . setAttribute ( "x2" , point2X ) ; lineSVG . setAttribute ( "y2" , point2Y ) ; lineSVG . setAttribute ( "stroke-width" , this . T_ARROW_HEAD_WIDTH . toString ( ) ) ; lineSVG . setAttribute ( "stroke" , "black" ) ; this . svg . appendChild ( lineSVG ) ; } if ( ! anchorPoints || anchorPoints . length < 1 ) { const lineSVG = document . createElementNS ( this . SVGNameSpace , "line" ) ; lineSVG . setAttribute ( "x1" , sourceEndpoint . x ) ; lineSVG . setAttribute ( "y1" , sourceEndpoint . y ) ; lineSVG . setAttribute ( "x2" , edgeEndpoint . x ) ; lineSVG . setAttribute ( "y2" , edgeEndpoint . y ) ; lineSVG . setAttribute ( "stroke-width" , this . EDGE_WIDTH . toString ( ) ) ; lineSVG . setAttribute ( "stroke" , "black" ) ; if ( lineStyle === "dashed" ) { lineSVG . setAttribute ( "stroke-dasharray" , this . DASH_PARAMETERS ) ; } this . svg . appendChild ( lineSVG ) ; } else if ( curveStyle === "unbundled-bezier" || curveStyle === "bezier" ) { const pathSVG = document . createElementNS ( this . SVGNameSpace , "path" ) ; let pathPoints = [ ] ; for ( let i = 0 ; i < anchorPoints . length ; i ++ ) { if ( i === 0 ) { pathPoints . push ( "M" + sourceEndpoint . x + "," + sourceEndpoint . y ) ; pathPoints . push ( "Q" + anchorPoints [ i ] . x + "," + anchorPoints [ i ] . y ) ; if ( anchorPoints . length === 1 ) { pathPoints . push ( edgeEndpoint . x + "," + edgeEndpoint . y ) ; } else { const furtherEndPoint = { x : ( anchorPoints [ i ] . x + anchorPoints [ i + 1 ] . x ) / 2 , y : ( anchorPoints [ i ] . y + anchorPoints [ i + 1 ] . y ) / 2 } ; pathPoints . push ( furtherEndPoint . x + "," + furtherEndPoint . y ) ; } } else { if ( i < anchorPoints . length - 1 ) { const furtherEndPoint = { x : ( anchorPoints [ i ] . x + anchorPoints [ i + 1 ] . x ) / 2 , y : ( anchorPoints [ i ] . y + anchorPoints [ i + 1 ] . y ) / 2 } ; pathPoints . push ( "T" + furtherEndPoint . x + "," + furtherEndPoint . y ) ; } else { pathPoints . push ( "T" + edgeEndpoint . x + "," + edgeEndpoint . y ) ; } } } pathSVG . setAttribute ( 'd' , pathPoints . join ( " " ) ) ; pathSVG . setAttribute ( 'stroke-width' , this . EDGE_WIDTH . toString ( ) ) ; pathSVG . setAttribute ( 'stroke' , 'black' ) ; if ( lineStyle === "dashed" ) { pathSVG . setAttribute ( "stroke-dasharray" , this . DASH_PARAMETERS ) ; } pathSVG . setAttribute ( 'fill' , 'none' ) ; this . svg . appendChild ( pathSVG ) ; } else { const polylineSVG = document . createElementNS ( this . SVGNameSpace , "polyline" ) ; let polylinePoints = [ ] ; for ( let i = 0 ; i < anchorPoints . length ; i ++ ) { if ( i === 0 ) { polylinePoints . push ( sourceEndpoint . x + "," + sourceEndpoint . y ) ; polylinePoints . push ( anchorPoints [ i ] . x + "," + anchorPoints [ i ] . y ) ; if ( anchorPoints . length === 1 ) { polylinePoints . push ( edgeEndpoint . x + "," + edgeEndpoint . y ) ; } } else if ( i === anchorPoints . length - 1 ) { polylinePoints . push ( anchorPoints [ i ] . x + "," + anchorPoints [ i ] . y ) ; polylinePoints . push ( edgeEndpoint . x + "," + edgeEndpoint . y ) ; } else { polylinePoints . push ( anchorPoints [ i ] . x + "," + anchorPoints [ i ] . y ) ; } } polylineSVG . setAttribute ( 'points' , polylinePoints . join ( " " ) ) ; polylineSVG . setAttribute ( 'stroke-width' , this . EDGE_WIDTH . toString ( ) ) ; polylineSVG . setAttribute ( 'stroke' , 'black' ) ; if ( lineStyle === "dashed" ) { lineSVG . setAttribute ( "stroke-dasharray" , this . DASH_PARAMETERS ) ; } polylineSVG . setAttribute ( 'fill' , 'none' ) ; this . svg . appendChild ( polylineSVG ) ; } } createRect ( node ) { var nodeRectangle = document . createElementNS ( this . SVGNameSpace , "rect" ) ; var nodePosition = node . position ( ) ; if ( node . isParent ( ) ) { nodeRectangle . setAttribute ( "x" , nodePosition . x - node . width ( ) / 2 - this . COMPOUND_MARGIN / 2 + "" ) ; nodeRectangle . setAttribute ( "y" , nodePosition . y - node . height ( ) / 2 - this . COMPOUND_MARGIN / 2 + "" ) ; nodeRectangle . setAttribute ( "width" , node . width ( ) + this . COMPOUND_MARGIN ) ; nodeRectangle . setAttribute ( "height" , node . height ( ) + this . COMPOUND_MARGIN ) ; } else { nodeRectangle . setAttribute ( "x" , nodePosition . x - node . width ( ) / 2 + "" ) ; nodeRectangle . setAttribute ( "y" , nodePosition . y - node . height ( ) / 2 + "" ) ; nodeRectangle . setAttribute ( "width" , node . width ( ) ) ; nodeRectangle . setAttribute ( "height" , node . height ( ) ) ; } nodeRectangle = this . createStyleForNodes ( node , nodeRectangle ) ; return nodeRectangle ; } createNodeLabel ( node , genomicDataOffset ) { var verticalTextOffset = 5 ; var nodePosition = node . position ( ) ; var svgText = document . createElementNS ( this . SVGNameSpace , "text" ) ; if ( node . isParent ( ) ) { verticalTextOffset = 20 ; svgText . setAttribute ( "x" , nodePosition . x ) ; svgText . setAttribute ( "y" , nodePosition . y + node . height ( ) / 2 + verticalTextOffset ) ; } else { svgText . setAttribute ( "x" , nodePosition . x ) ; svgText . setAttribute ( "y" , nodePosition . y + verticalTextOffset + genomicDataOffset ) ; } svgText . setAttribute ( "font-family" , "Arial" ) ; svgText . setAttribute ( "text-anchor" , "middle" ) ; svgText . setAttribute ( "font-size" , this . NODE_FONT_SIZE . toString ( ) ) ; svgText . innerHTML = node . data ( "name" ) ; return svgText ; } createStyleForNodes ( node , nodeRectangle ) { var nodeType = node . data ( ) . type ; var strokeWidth = node . css ( "border-width" ) || this . NODE_STROKE_WIDTH ; var strokeColor = this . NODE_STROKE_COLOR ; var fillColor = this . NODE_FILL_COLOR ; var opacity = this . NODE_OPACITY ; var strokeOpacity = 1 ; if ( nodeType == "GENE" || nodeType == "COMPARTMENT" ) { nodeRectangle . setAttribute ( "rx" , this . ROUNDING_FACTOR ) ; nodeRectangle . setAttribute ( "ry" , this . ROUNDING_FACTOR ) ; if ( nodeType == "COMPARTMENT" ) strokeWidth = this . COMPARTMENT_STROKE_WIDTH ; } if ( nodeType == "PROCESS" ) { opacity = 0 ; strokeOpacity = 0 ; } if ( nodeType == "FAMILY" ) { fillColor = this . FAMILY_FILL_COLOR ; strokeColor = this . FAMILY_STROKE_COLOR ; } var styleString = "stroke-width:" + strokeWidth + ";" + "stroke:" + strokeColor + ";" + "fill-opacity:" + opacity + ";" + "fill:" + fillColor + ";" + "stroke-opacity:" + strokeOpacity + ";" ; nodeRectangle . setAttribute ( "style" , styleString ) ; return nodeRectangle ; } createEdgeLabel ( edge ) { const labelText = edge . data ( "name" ) ; if ( labelText === "" ) { return undefined ; } const svgTextElement = document . createElementNS ( this . SVGNameSpace , "text" ) ; const fontSize = edge . style ( "font-size" ) ; const fontFamily = edge . style ( "font-family" ) ; const labelRotationAngle = this . getEdgeLabelRotationAngle ( edge ) ; const lineHeight = edge . _private . rscratch . labelLineHeight ; const dx = ( lineHeight / 4 ) * Math . sin ( edge . _private . rscratch . labelAngle ) ; const dy = ( lineHeight / 4 ) * Math . cos ( edge . _private . rscratch . labelAngle ) ; const labelPos = { x : edge . _private . rscratch . labelX + dx , y : edge . _private . rscratch . labelY - dy } ; svgTextElement . setAttribute ( 'x' , labelPos . x . toString ( ) ) ; svgTextElement . setAttribute ( 'y' , labelPos . y . toString ( ) ) ; svgTextElement . setAttribute ( "font-family" , fontFamily ) ; svgTextElement . setAttribute ( "text-anchor" , "middle" ) ; svgTextElement . setAttribute ( "font-size" , fontSize ) ; svgTextElement . innerHTML = labelText ; svgTextElement . setAttribute ( "transform" , "rotate(" + labelRotationAngle + " " + labelPos . x . toString ( ) + " " + labelPos . y . toString ( ) + ")" ) ; return svgTextElement ; } getEdgeLabelRotationAngle ( edge ) { const labelAngle = edge . _private . rscratch . labelAngle ; if ( ! labelAngle ) { return 0 ; } return this . toDegrees ( labelAngle ) ; } toDegrees ( radians ) { const pi = Math . PI ; return radians * ( 180 / pi ) ; } unitVector ( v ) { var inverseLength = 1 / Math . sqrt ( v . x * v . x + v . y * v . y ) ; return { x : v . x * inverseLength , y : v . y * inverseLength , } ; } rotateVector ( v , radians ) { var newX = v . x * Math . cos ( radians ) - v . y * Math . sin ( radians ) ; var newY = v . x * Math . sin ( radians ) + v . y * Math . cos ( radians ) ; return { x : newX , y : newY } ; } scale ( v , scalar ) { var newX = v . x * scalar ; var newY = v . y * scalar ; return { x : newX , y : newY } ; } }