2020-08-11 19:17:39 +02:00
"use strict" ;
2017-09-22 00:26:38 +02:00
// From : https://bl.ocks.org/d3noob/43a860bc0024792f8803bba8ca0d5ecd
// Set the dimensions and margins of the diagram
2020-08-11 19:17:39 +02:00
let margin = {
2023-01-24 13:03:49 +01:00
top : document . getElementById ( 'menu_horizontal_content' ) . clientHeight + 10 ,
2020-08-11 19:17:39 +02:00
right : 200 ,
2020-12-10 19:23:17 +01:00
bottom : 10 ,
2020-08-11 19:17:39 +02:00
left : 90
} ;
2017-09-22 00:26:38 +02:00
2021-04-06 23:34:57 +02:00
let menuHeight = document . getElementById ( 'menu_vertical' ) . clientHeight ;
2020-12-03 14:15:12 +01:00
let width = 960 - margin . left - margin . right ;
let height = menuHeight * 2 ;
2020-12-03 13:34:15 +01:00
let node _width = 10 ;
2020-08-11 19:17:39 +02:00
let node _height = 55 ;
2021-01-20 01:28:54 +01:00
let center _node = null ;
2017-10-04 15:13:42 +02:00
2020-08-11 19:17:39 +02:00
let main _svg = d3 . select ( "body" ) . append ( "svg" )
2017-09-25 11:23:32 +02:00
. attr ( "width" , width + margin . right + margin . left )
. attr ( "height" , height + margin . top + margin . bottom )
2020-08-06 17:24:06 +02:00
// dummy container for tooltip
d3 . select ( 'body' )
. append ( 'div' )
. attr ( 'id' , 'tooltip' )
. attr ( 'class' , 'tooltip' )
. attr ( 'style' , 'position: absolute; opacity: 0;' ) ;
2020-08-11 19:17:39 +02:00
// Define SVGs
let defs = main _svg . append ( "defs" ) ;
2018-06-29 08:03:52 +02:00
2017-09-25 11:23:32 +02:00
// Add background pattern
2020-08-11 19:17:39 +02:00
let pattern = defs . append ( 'pattern' )
2017-09-25 11:23:32 +02:00
. attr ( 'id' , 'backstripes' )
. attr ( 'x' , margin . left )
2017-09-29 14:43:40 +02:00
. attr ( "width" , node _width * 2 )
2020-12-03 13:34:15 +01:00
. attr ( "height" , height )
2017-09-25 11:23:32 +02:00
. attr ( 'patternUnits' , "userSpaceOnUse" )
pattern . append ( 'rect' )
2017-09-29 14:43:40 +02:00
. attr ( 'width' , node _width )
2017-09-25 11:23:32 +02:00
. attr ( 'height' , height )
. attr ( "fill" , "#EEEEEE" ) ;
2017-09-22 00:26:38 +02:00
// append the svg object to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
2020-08-11 19:17:39 +02:00
let node _container = main _svg . append ( "g" )
2020-12-03 14:15:12 +01:00
. attr ( "transform" , ` translate( ${ margin . left } , ${ margin . top } ) ` ) ;
2017-09-22 00:26:38 +02:00
// Assigns parent, children, height, depth
2020-08-11 19:17:39 +02:00
let root = d3 . hierarchy ( treeData ) ;
2020-12-03 13:34:15 +01:00
root . x0 = height / 2 ;
2017-09-22 00:26:38 +02:00
root . y0 = 0 ;
2019-05-15 12:14:40 +02:00
// declares a tree layout
2020-08-11 19:17:39 +02:00
let tree = d3 . tree ( ) ;
2017-09-22 00:26:38 +02:00
update ( root ) ;
2021-05-28 00:23:04 +02:00
if ( parent _uuid != null ) {
2021-05-28 01:33:17 +02:00
let parent _box _y = root . y - 70 ;
let parent _box _x = root . x - 150 ;
let parent _rect = node _container . append ( 'rect' )
2021-05-28 00:23:04 +02:00
. attr ( "rx" , 6 )
. attr ( "ry" , 6 )
2021-05-28 01:33:17 +02:00
. attr ( "transform" , ` translate( ${ parent _box _y } , ${ parent _box _x } ) ` )
2021-05-28 00:23:04 +02:00
. style ( "opacity" , "0.5" )
. attr ( "stroke" , 'black' )
. attr ( 'stroke-opacity' , "0.8" )
. attr ( "stroke-width" , "2" )
. attr ( "stroke-linecap" , "round" )
. attr ( "fill" , "white" )
let text = node _container
. data ( [
{
"line1" : 'This capture was triggered' ,
"line2" : 'from a previous capture.' ,
"line3" : 'See the parent' ,
"parent_uuid" : parent _uuid
}
] )
. append ( 'text' )
2021-05-28 01:33:17 +02:00
. attr ( "dy" , 0 )
2021-05-28 00:23:04 +02:00
. style ( "font-size" , "12px" )
2021-05-28 01:33:17 +02:00
. style ( 'text-align' , 'center' )
. attr ( "transform" , ` translate( ${ parent _box _y + 3 } , ${ parent _box _x + 15 } ) ` ) ;
2021-05-28 00:23:04 +02:00
text
. append ( 'tspan' )
. text ( d => d . line1 ) ;
text
. append ( 'tspan' )
. attr ( "x" , 8 )
. attr ( "dy" , 18 )
. text ( d => d . line2 ) ;
text
. append ( 'tspan' )
. attr ( "x" , 30 )
. attr ( "dy" , 20 )
. text ( d => d . line3 )
. style ( 'fill' , '#0000EE' )
. attr ( 'cursor' , 'pointer' )
. on ( 'click' , ( event , d ) => { openTreeInNewTab ( d . parent _uuid ) } ) ;
2021-05-28 01:33:17 +02:00
parent _rect
. attr ( 'width' , text . node ( ) . getBBox ( ) . width + 6 )
. attr ( 'height' , text . node ( ) . getBBox ( ) . height + 10 )
2021-05-28 00:23:04 +02:00
let line _arrow = node _container
2021-05-28 01:33:17 +02:00
. append ( 'g' ) ;
//.attr("transform", `translate(${root.y}, ${root.x})`);
2021-05-28 00:23:04 +02:00
let line = d3 . line ( )
// Other options: http://bl.ocks.org/d3indepth/raw/b6d4845973089bc1012dec1674d3aff8/
//.curve(d3.curveCardinal)
. curve ( d3 . curveBundle )
. x ( point => point . lx )
. y ( point => point . ly ) ;
let line _tip = d3 . symbol ( )
. type ( d3 . symbolTriangle )
. size ( 200 ) ;
line _arrow
. append ( "path" )
. attr ( 'stroke-opacity' , "0.7" )
. attr ( "stroke-width" , "2" )
. attr ( "stroke" , "black" )
. attr ( "fill" , "none" )
. data ( [ {
2021-05-28 01:33:17 +02:00
source : { x : 0 , y : parent _box _x + parent _rect . node ( ) . getBBox ( ) . height } ,
target : { x : 50 , y : parent _box _x + parent _rect . node ( ) . getBBox ( ) . height + 42 }
2021-05-28 00:23:04 +02:00
} ] )
. attr ( "class" , "line" )
. attr ( "d" , d => line (
[ { lx : d . source . x , ly : d . source . y } ,
{ lx : d . target . x , ly : d . source . y } ,
{ lx : d . target . x , ly : d . target . y }
] )
) ;
line _arrow
. append ( "path" )
. attr ( "d" , line _tip )
. attr ( "stroke" , 'black' )
. attr ( 'stroke-opacity' , "0.8" )
. style ( 'stroke-width' , '1.5' )
. attr ( "fill-opacity" , '0' )
2021-05-28 01:33:17 +02:00
. attr ( "transform" , ` translate(50, ${ parent _box _x + parent _rect . node ( ) . getBBox ( ) . height + 48 } ) rotate(60) ` ) ;
2021-05-28 00:23:04 +02:00
} ;
2022-08-23 17:48:36 +02:00
function openURLInNewTab ( url ) {
2020-08-11 19:17:39 +02:00
let win = window . open ( url , '_blank' ) ;
if ( win == null ) {
2020-11-04 16:02:05 +01:00
return false ;
2020-08-11 19:17:39 +02:00
}
2020-08-07 18:01:06 +02:00
win . focus ( ) ;
2020-11-04 16:02:05 +01:00
return true ;
2020-08-07 18:01:06 +02:00
}
2022-08-23 17:48:36 +02:00
function openTreeInNewTab ( capture _uuid , hostnode _uuid = null ) {
let url = ` /tree/ ${ capture _uuid } ` ;
if ( hostnode _uuid != null ) {
url += ` / ${ hostnode _uuid } ` ;
}
openURLInNewTab ( url ) ;
}
2020-09-28 15:28:47 +02:00
function open _hostnode _popup ( hostnode _uuid ) {
2020-10-09 18:05:04 +02:00
let win = window . open ( ` /tree/ ${ treeUUID } /host/ ${ hostnode _uuid } ` , '_blank' , 'width=1024,height=768,left=200,top=100' ) ;
2020-08-11 19:17:39 +02:00
if ( win == null ) {
alert ( "The browser didn't allow Lookyloo to open a pop-up. There should be an icon on the right of your URL bar to allow it." ) ;
}
win . focus ( ) ;
}
2020-05-18 18:35:20 +02:00
2020-08-11 19:17:39 +02:00
function LocateNode ( hostnode _uuid ) {
let element = document . getElementById ( ` node_ ${ hostnode _uuid } ` ) ;
2020-08-04 17:27:33 +02:00
element . scrollIntoView ( { behavior : "smooth" , block : "center" , inline : "center" } ) ;
2020-05-18 18:35:20 +02:00
2020-08-11 19:17:39 +02:00
let line _arrow = d3 . select ( ` #node_ ${ hostnode _uuid } ` )
. append ( 'g' )
. attr ( 'cursor' , 'pointer' )
2020-09-28 15:28:47 +02:00
. on ( 'click' , ( event , d ) => { event . currentTarget . remove ( ) ; } ) ;
2020-08-04 16:18:42 +02:00
2020-08-11 19:17:39 +02:00
let line = d3 . line ( )
2020-08-04 16:18:42 +02:00
// Other options: http://bl.ocks.org/d3indepth/raw/b6d4845973089bc1012dec1674d3aff8/
//.curve(d3.curveCardinal)
. curve ( d3 . curveBundle )
2020-08-11 19:17:39 +02:00
. x ( point => point . lx )
. y ( point => point . ly ) ;
2020-08-04 16:18:42 +02:00
2020-08-11 19:17:39 +02:00
let line _tip = d3 . symbol ( )
2020-08-04 16:18:42 +02:00
. type ( d3 . symbolTriangle )
. size ( 200 ) ;
2020-08-11 19:17:39 +02:00
let path = line _arrow
2020-08-04 16:18:42 +02:00
. append ( "path" )
. attr ( "stroke-width" , "3" )
. attr ( "stroke" , "black" )
. attr ( "fill" , "none" )
2020-08-11 19:17:39 +02:00
. data ( [ {
source : { x : node _width / 2 , y : - 100 } ,
target : { x : node _width / 4 , y : - node _height / 2 }
} ] )
2020-08-04 16:18:42 +02:00
. attr ( "class" , "line" )
2020-08-11 19:17:39 +02:00
. attr ( "d" , d => line (
[ { lx : d . source . x , ly : d . source . y } ,
{ lx : d . target . x , ly : d . source . y } ,
{ lx : d . target . x , ly : d . target . y }
] )
) ;
let arrow = line _arrow
2020-08-04 16:18:42 +02:00
. append ( "path" )
. attr ( "d" , line _tip )
. attr ( "stroke" , 'black' )
. style ( 'stroke-width' , '3' )
. attr ( "fill" , 'white' )
2020-08-11 19:17:39 +02:00
. attr ( "transform" , ` translate( ${ node _width / 4 } , ${ - node _height / 1.5 } ) rotate(60) ` ) ;
2020-08-04 16:18:42 +02:00
2020-08-11 19:17:39 +02:00
let glow = ( ) => {
2020-08-04 16:18:42 +02:00
line _arrow . selectAll ( 'path' )
. transition ( ) . duration ( 1000 ) //Set transition
. style ( 'stroke-width' , '7' )
. style ( 'stroke' , 'red' )
. transition ( ) . duration ( 1000 ) //Set transition
. style ( 'stroke-width' , '3' )
. style ( 'stroke' , 'black' )
2020-08-11 19:17:39 +02:00
. on ( "end" , ( ) => {
if ( ++ i > 15 ) {
line _arrow . remove ( ) ;
} else {
glow ( ) ;
}
2020-08-04 16:18:42 +02:00
} ) ;
} ;
2020-08-11 19:17:39 +02:00
let i = 0 ;
2020-08-04 16:18:42 +02:00
glow ( ) ;
2020-05-18 18:35:20 +02:00
} ;
2020-11-03 17:04:04 +01:00
function UnbookmarkAllNodes ( ) {
2020-07-28 18:26:07 +02:00
d3 . selectAll ( '.node_data' ) . select ( 'rect' ) . style ( 'fill' , 'white' ) ;
d3 . selectAll ( '.node_data' ) . select ( 'text' ) . style ( 'fill' , 'black' ) ;
2020-11-03 17:04:04 +01:00
d3 . selectAll ( '.node_data' ) . select ( "#bookmark" )
2020-07-28 18:26:07 +02:00
. text ( "🏁" )
2020-09-28 15:28:47 +02:00
. on ( 'click' , ( event , d ) => NodeHighlight ( d . data . uuid ) )
. on ( 'mouseover' , ( event , d ) => {
2020-08-06 17:47:39 +02:00
d3 . select ( '#tooltip' )
. style ( 'opacity' , 1 )
2020-09-28 15:28:47 +02:00
. style ( 'left' , ` ${ event . pageX + 10 } px ` )
. style ( 'top' , ` ${ event . pageY + 10 } px ` )
2020-11-03 17:04:04 +01:00
. text ( 'Bookmark this node' ) ;
2020-08-06 17:47:39 +02:00
} )
2020-09-28 15:28:47 +02:00
. on ( 'mouseout' , ( event , d ) => d3 . select ( '#tooltip' ) . style ( 'opacity' , 0 ) ) ;
2020-07-28 18:26:07 +02:00
} ;
2020-07-24 17:50:22 +02:00
2020-11-29 23:38:09 +01:00
function MarkAsKnown ( capture _uuid , hostnode _uuid = null , urlnode _uuid = null ) {
2020-08-20 19:39:03 +02:00
let data = { } ;
if ( hostnode _uuid != null ) { data [ 'hostnode_uuid' ] = hostnode _uuid ; } ;
if ( urlnode _uuid != null ) { data [ 'urlnode_uuid' ] = urlnode _uuid ; } ;
$ . post ( ` /tree/ ${ capture _uuid } /mark_as_legitimate ` , data ) ;
} ;
2020-11-03 17:04:04 +01:00
function UnbookmarkHostNode ( hostnode _uuid ) {
2020-08-11 19:17:39 +02:00
d3 . select ( ` #node_ ${ hostnode _uuid } ` ) . select ( 'rect' ) . style ( 'fill' , 'white' ) ;
d3 . select ( ` #node_ ${ hostnode _uuid } ` ) . select ( 'text' ) . style ( 'fill' , 'black' ) ;
2020-11-03 17:04:04 +01:00
d3 . select ( ` #node_ ${ hostnode _uuid } ` ) . select ( "#bookmark" )
2020-07-24 17:50:22 +02:00
. text ( "🏁" )
2020-09-28 15:28:47 +02:00
. on ( 'click' , ( event , d ) => NodeHighlight ( d . data . uuid ) )
. on ( 'mouseover' , ( event , d ) => {
2020-08-06 17:47:39 +02:00
d3 . select ( '#tooltip' )
. style ( 'opacity' , 1 )
2020-09-28 15:28:47 +02:00
. style ( 'left' , ` ${ event . pageX + 10 } px ` )
. style ( 'top' , ` ${ event . pageY + 10 } px ` )
2020-11-03 17:04:04 +01:00
. text ( 'Bookmark this node' ) ;
2020-08-06 17:47:39 +02:00
} )
2020-09-28 15:28:47 +02:00
. on ( 'mouseout' , ( event , d ) => d3 . select ( '#tooltip' ) . style ( 'opacity' , 0 ) ) ;
2020-07-24 17:50:22 +02:00
} ;
2020-08-11 19:17:39 +02:00
function NodeHighlight ( hostnode _uuid ) {
let element = document . getElementById ( ` node_ ${ hostnode _uuid } ` ) ;
2020-07-23 17:47:12 +02:00
element . scrollIntoView ( { behavior : "smooth" , block : "center" , inline : "nearest" } ) ;
2020-08-11 19:17:39 +02:00
d3 . select ( ` #node_ ${ hostnode _uuid } ` ) . select ( 'rect' ) . style ( 'fill' , 'black' ) ;
d3 . select ( ` #node_ ${ hostnode _uuid } ` ) . select ( 'text' ) . style ( 'fill' , 'white' ) ;
2020-11-03 17:04:04 +01:00
d3 . select ( ` #node_ ${ hostnode _uuid } ` ) . select ( "#bookmark" )
2020-07-24 17:50:22 +02:00
. text ( '❌' )
2020-11-03 17:04:04 +01:00
. on ( 'click' , ( event , d ) => UnbookmarkHostNode ( d . data . uuid ) )
2020-09-28 15:28:47 +02:00
. on ( 'mouseover' , ( event , d ) => {
2020-08-06 17:47:39 +02:00
d3 . select ( '#tooltip' )
. style ( 'opacity' , 1 )
2020-09-28 15:28:47 +02:00
. style ( 'left' , ` ${ event . pageX + 10 } px ` )
. style ( 'top' , ` ${ event . pageY + 10 } px ` )
2020-11-03 17:04:04 +01:00
. text ( 'Remove bookmark on this node' ) ;
2020-08-06 17:47:39 +02:00
} )
2020-09-28 15:28:47 +02:00
. on ( 'mouseout' , ( event , d ) => d3 . select ( '#tooltip' ) . style ( 'opacity' , 0 ) ) ;
2018-02-01 23:58:26 +01:00
} ;
2020-08-11 19:17:39 +02:00
function icon _list ( relative _x _pos , relative _y _pos , d ) {
const icon _size = 16 ;
2020-09-28 15:28:47 +02:00
const icon _options = new Map ( [
2021-01-01 21:27:08 +01:00
[ 'js' , { path : "/static/javascript.png" , tooltip : "URL(s) loading Javascript" } ] ,
[ 'exe' , { path : "/static/exe.png" , tooltip : "URL(s) loading executables" } ] ,
[ 'css' , { path : "/static/css.png" , tooltip : "URL(s) loading CSS" } ] ,
[ 'font' , { path : "/static/font.png" , tooltip : "URL(s) loading fonts" } ] ,
[ 'html' , { path : "/static/html.png" , tooltip : "URL(s) loading HTML" } ] ,
[ 'json' , { path : "/static/json.png" , tooltip : "URL(s) loading Json" } ] ,
[ 'iframe' , { path : "/static/ifr.png" , tooltip : "URL(s) loaded from an Iframe" } ] ,
[ 'image' , { path : "/static/img.png" , tooltip : "URL(s) loading images" } ] ,
[ 'unknown_mimetype' , { path : "/static/wtf.png" , tooltip : "URL(s) loading contents of an unknown type" } ] ,
[ 'video' , { path : "/static/video.png" , tooltip : "URL(s) loading videos" } ] ,
[ 'request_cookie' , { path : "/static/cookie_read.png" , tooltip : "cookie(s) sent to the server in the request" } ] ,
[ 'response_cookie' , { path : "/static/cookie_received.png" , tooltip : "cookie(s) received in the response" } ] ,
[ 'redirect' , { path : "/static/redirect.png" , tooltip : "redirect(s)" } ] ,
2023-09-28 16:56:09 +02:00
[ 'redirect_to_nothing' , { path : "/static/cookie_in_url.png" , tooltip : "redirect(s) to URL(s) missing in the capture" } ] ,
[ 'downloaded_filename' , { path : "/static/download.png" , tooltip : "contains a downloaded file." } ]
2020-08-11 19:17:39 +02:00
] ) ;
2019-05-15 18:10:07 +02:00
2020-08-11 19:17:39 +02:00
// Put all the icone in one sub svg document
let icons = d3 . create ( "svg" )
. attr ( 'x' , relative _x _pos )
2020-08-12 20:09:40 +02:00
. attr ( 'y' , relative _y _pos )
. attr ( 'class' , 'icons_list' ) ;
2020-08-11 19:17:39 +02:00
2021-01-01 21:27:08 +01:00
icon _options . forEach ( function ( icon _details , key ) {
2020-08-12 20:09:40 +02:00
let has _icon = false ;
let counter = 0 ;
if ( typeof d . data [ key ] === 'boolean' ) {
has _icon = d . data [ key ] ;
2023-09-28 16:56:09 +02:00
} else if ( typeof d . data [ key ] === 'string' ) {
has _icon = d . data [ key ] ;
2020-08-12 20:09:40 +02:00
} else if ( typeof d . data [ key ] === 'number' ) {
has _icon = d . data [ key ] > 0 ;
2021-01-01 21:27:08 +01:00
counter = d . data [ key ] ;
2020-08-12 20:09:40 +02:00
} else if ( d . data [ key ] instanceof Array ) {
has _icon = d . data [ key ] . length > 0 ;
2021-01-01 21:27:08 +01:00
counter = d . data [ key ] . length ;
2020-08-12 20:09:40 +02:00
} ;
if ( has _icon ) {
let icon _group = icons
. append ( "svg" )
. attr ( 'class' , 'icon' )
. attr ( "id" , ` icons_ ${ key } ` ) ;
icon _group
. append ( 'image' )
. attr ( "width" , icon _size )
. attr ( "height" , icon _size )
2021-01-01 21:27:08 +01:00
. attr ( "xlink:href" , icon _details . path )
. on ( 'mouseover' , ( event , d ) => {
d3 . select ( '#tooltip' )
. style ( 'opacity' , 1 )
. style ( 'left' , ` ${ event . pageX + 10 } px ` )
. style ( 'top' , ` ${ event . pageY + 10 } px ` )
2023-09-28 16:56:09 +02:00
. text ( counter ? ` ${ counter } ${ icon _details . tooltip } ` : icon _details . tooltip ) ;
2021-01-01 21:27:08 +01:00
} )
. on ( 'mouseout' , ( event , d ) => d3 . select ( '#tooltip' ) . style ( 'opacity' , 0 ) ) ;
2020-08-12 20:09:40 +02:00
if ( counter > 0 ) {
icon _group
. append ( 'text' )
. attr ( "dy" , 8 )
. style ( "font-size" , "10px" )
. attr ( 'x' , icon _size + 1 )
. text ( counter ) ;
} ;
} ;
} )
2019-05-15 12:14:40 +02:00
return icons . node ( ) ;
2018-02-01 01:16:20 +01:00
}
2020-09-28 15:28:47 +02:00
function text _entry ( relative _x _pos , relative _y _pos , d ) {
2018-02-01 01:16:20 +01:00
// Avoid hiding the content after the circle
2020-08-11 19:17:39 +02:00
let nodeContent = d3 . create ( "svg" ) // WARNING: svg is required there, "g" doesn't have getBBox
2018-04-05 22:59:45 +02:00
. attr ( 'height' , node _height )
2018-02-01 01:16:20 +01:00
. attr ( 'x' , relative _x _pos )
2019-05-15 12:14:40 +02:00
. attr ( 'y' , relative _y _pos )
2019-05-15 18:10:07 +02:00
. datum ( d ) ;
2018-02-01 01:16:20 +01:00
// Add labels for the nodes
2020-12-31 19:06:17 +01:00
nodeContent . append ( "text" )
2018-02-01 01:16:20 +01:00
. attr ( 'dy' , '.9em' )
. attr ( "stroke" , "white" )
. style ( "font-size" , "16px" )
. attr ( "stroke-width" , ".2px" )
. style ( "opacity" , . 9 )
2020-08-12 13:59:40 +02:00
. attr ( 'cursor' , 'pointer' )
2020-09-28 15:28:47 +02:00
. on ( 'click' , ( event , d ) => open _hostnode _popup ( d . data . uuid ) )
. on ( 'mouseover' , ( event , d ) => {
2020-08-12 13:59:40 +02:00
d3 . select ( '#tooltip' )
. style ( 'opacity' , 1 )
2020-09-28 15:28:47 +02:00
. style ( 'left' , ` ${ event . pageX + 10 } px ` )
. style ( 'top' , ` ${ event . pageY + 10 } px ` )
2020-08-12 13:59:40 +02:00
. text ( 'Open investigation pop-up.' ) ;
} )
2020-09-28 15:28:47 +02:00
. on ( 'mouseout' , ( event , d ) => d3 . select ( '#tooltip' ) . style ( 'opacity' , 0 ) )
2019-05-17 16:59:56 +02:00
. text ( d => {
2020-08-11 19:17:39 +02:00
let to _print ;
2023-11-07 21:12:35 +01:00
if ( d . data . idna ) {
to _print = d . data . idna ;
}
else if ( d . data . name . length > 50 ) {
2020-08-11 19:17:39 +02:00
to _print = ` [...] ${ d . data . name . substring ( d . data . name . length - 50 , d . data . name . length ) } ` ;
} else {
to _print = d . data . name
} ;
return to _print ;
2019-06-27 16:15:53 +02:00
} ) ;
2020-12-31 19:06:17 +01:00
2019-05-15 12:14:40 +02:00
return nodeContent . node ( ) ;
2018-02-01 01:16:20 +01:00
}
2019-05-15 12:14:40 +02:00
// Recursively generate the tree
function update ( root , computed _node _width = 0 ) {
2017-09-22 00:26:38 +02:00
2019-05-15 12:14:40 +02:00
// Current height of the tree (cannot use height because it isn't recomputed when we rename children -> _children)
2020-12-03 13:34:15 +01:00
let max _depth = 0
2019-05-15 12:14:40 +02:00
root . each ( d => {
if ( d . children ) {
max _depth = d . depth > max _depth ? d . depth : max _depth ;
}
} ) ;
2017-09-29 14:43:40 +02:00
2019-05-15 12:14:40 +02:00
if ( computed _node _width != 0 ) {
2019-05-15 22:16:53 +02:00
computed _node _width += 30 ;
2019-05-15 12:14:40 +02:00
// Re-compute SVG size depending on the generated tree
2020-08-11 19:17:39 +02:00
let newWidth = Math . max ( ( max _depth + 1 ) * computed _node _width , node _width ) ;
2019-05-15 12:14:40 +02:00
// Update height
2020-12-03 13:34:15 +01:00
// node_height is the height of a node, menuHeight * 3 is the minimum so the root node isn't behind the menu
2020-12-10 19:23:17 +01:00
let newHeight = Math . max ( root . descendants ( ) . reverse ( ) . length * node _height , menuHeight * 2.5 ) ;
2019-05-15 12:14:40 +02:00
tree . size ( [ newHeight , newWidth ] )
// Set background based on the computed width and height
2020-08-11 19:17:39 +02:00
let background = main _svg . insert ( 'rect' , ':first-child' )
2019-05-15 12:14:40 +02:00
. attr ( 'y' , 0 )
2020-12-11 23:21:07 +01:00
// Note: We want the background width with an extra computed_node_width
// in order to make sure the last node is completely covered
. attr ( 'width' , newWidth + ( margin . right + margin . left + computed _node _width ) )
2019-05-15 12:14:40 +02:00
. attr ( 'height' , newHeight + margin . top + margin . bottom )
. style ( 'fill' , "url(#backstripes)" ) ;
2019-05-15 18:10:07 +02:00
// Update size
2020-08-20 19:39:03 +02:00
main _svg
. attr ( "width" , newWidth + ( margin . right + margin . left ) * 2 )
2019-05-15 12:14:40 +02:00
. attr ( "height" , newHeight + margin . top + margin . bottom )
// Update pattern
main _svg . selectAll ( 'pattern' )
2020-08-12 13:59:40 +02:00
. attr ( 'width' , ` ${ computed _node _width * 2 } px ` )
2019-05-15 12:14:40 +02:00
pattern . selectAll ( 'rect' )
2020-08-12 13:59:40 +02:00
. attr ( 'width' , ` ${ computed _node _width } px ` )
2017-09-29 14:43:40 +02:00
2019-05-15 12:14:40 +02:00
}
2017-09-22 00:26:38 +02:00
2019-05-15 12:14:40 +02:00
// Assigns the x and y position for the nodes
2020-08-11 19:17:39 +02:00
let treemap = tree ( root ) ;
2017-09-22 00:26:38 +02:00
2019-05-15 12:14:40 +02:00
// Compute the new tree layout. => Note: Need d.x & d.y
2020-08-11 19:17:39 +02:00
let nodes = treemap . descendants ( ) ,
2019-05-15 12:14:40 +02:00
links = treemap . descendants ( ) . slice ( 1 ) ;
2017-09-22 00:26:38 +02:00
// ****************** Nodes section ***************************
2020-08-11 19:17:39 +02:00
// Toggle children on click.
2020-09-28 15:28:47 +02:00
let toggle _children _collapse = ( event , d ) => {
2020-08-11 19:17:39 +02:00
if ( d . children ) {
d . _children = d . children ;
d . children = null ;
}
else {
d . children = d . _children ;
d . _children = null ;
}
// Call update on the whole Tree
update ( d . ancestors ( ) . reverse ( ) [ 0 ] ) ;
} ;
2017-09-22 00:26:38 +02:00
// Update the nodes...
2019-05-15 12:14:40 +02:00
const tree _nodes = node _container . selectAll ( 'g.node' )
. data ( nodes , node => node . data . uuid ) ;
tree _nodes . join (
// Enter any new modes at the parent's previous position.
enter => {
2020-08-11 19:17:39 +02:00
let node _group = enter . append ( 'g' )
2019-05-15 12:14:40 +02:00
. attr ( 'class' , 'node' )
2020-08-11 19:17:39 +02:00
. attr ( "id" , d => ` node_ ${ d . data . uuid } ` )
. attr ( "transform" , ` translate( ${ root . y0 } , ${ root . x0 } ) ` ) ;
2019-05-15 18:10:07 +02:00
2020-08-11 19:17:39 +02:00
let node _data = node _group
2019-05-15 18:10:07 +02:00
. append ( 'svg' )
. attr ( 'class' , 'node_data' )
2019-05-15 22:16:53 +02:00
. attr ( 'x' , 0 )
2019-05-15 18:10:07 +02:00
. attr ( 'y' , - 30 ) ;
node _data . append ( 'rect' )
. attr ( "rx" , 6 )
. attr ( "ry" , 6 )
2020-12-09 19:11:19 +01:00
. attr ( 'x' , 0 )
2019-05-15 18:10:07 +02:00
. attr ( 'y' , 0 )
2020-12-10 19:23:17 +01:00
. attr ( 'width' , 10 )
2019-05-15 18:10:07 +02:00
. style ( "opacity" , "0.5" )
2020-08-06 17:24:06 +02:00
. attr ( "stroke" , 'black' )
2019-05-15 18:10:07 +02:00
. attr ( 'stroke-opacity' , "0.8" )
2020-08-06 17:24:06 +02:00
. attr ( "stroke-width" , "2" )
2019-05-15 18:10:07 +02:00
. attr ( "stroke-linecap" , "round" )
2020-12-09 19:11:19 +01:00
. attr ( "fill" , "white" )
2019-05-15 18:10:07 +02:00
2019-05-15 12:14:40 +02:00
// Set Hostname text
2019-05-15 18:10:07 +02:00
node _data
2020-12-09 19:11:19 +01:00
. append ( d => text _entry ( 10 , 5 , d ) ) ; // Popup
2019-05-15 12:14:40 +02:00
// Set list of icons
2019-05-15 18:10:07 +02:00
node _data
2020-12-09 19:11:19 +01:00
. append ( d => icon _list ( 12 , 35 , d ) ) ;
2019-05-15 18:10:07 +02:00
2020-08-11 19:17:39 +02:00
node _group . select ( '.node_data' ) . each ( function ( d ) {
2019-05-22 12:27:20 +02:00
// set position of icons based of their length
2020-08-11 19:17:39 +02:00
let cur _icon _list _len = 0 ;
d3 . select ( this ) . selectAll ( '.icon' ) . each ( function ( ) {
2019-05-22 12:27:20 +02:00
d3 . select ( this ) . attr ( 'x' , cur _icon _list _len ) ;
cur _icon _list _len += d3 . select ( this ) . node ( ) . getBBox ( ) . width ;
} ) ;
2019-05-15 18:10:07 +02:00
2019-05-22 12:27:20 +02:00
// Rectangle around the domain name & icons
d3 . select ( this ) . select ( 'rect' )
2020-08-12 13:59:40 +02:00
. attr ( 'height' , node _height + 5 )
2020-12-31 19:32:08 +01:00
. attr ( 'width' , d3 . select ( this ) . node ( ) . getBBox ( ) . width + 60 ) ;
2020-12-09 19:11:19 +01:00
2019-05-15 22:16:53 +02:00
// Set the width for all the nodes
2021-01-04 10:39:13 +01:00
// Required, as the node width need to include the rectangle
// Note: removing .select('rect') breaks rendering on firefox but not on chrome.
let selected _node _bbox = d3 . select ( this ) . select ( 'rect' ) . node ( ) . getBBox ( ) ;
2020-12-09 19:11:19 +01:00
d . node _width = selected _node _bbox . width ;
2019-05-15 22:16:53 +02:00
node _width = node _width > selected _node _bbox . width ? node _width : selected _node _bbox . width ;
2020-12-31 19:06:17 +01:00
// Set number of URLs after the hostname
if ( d . data . urls _count > 1 ) {
d3 . select ( this ) . append ( "text" )
. attr ( 'x' , d => d3 . select ( this ) . select ( 'text' ) . node ( ) . getBBox ( ) . width + 13 )
. attr ( 'y' , 5 )
. attr ( 'dy' , '.9em' )
. attr ( "stroke" , "white" )
. style ( "font-size" , "16px" )
. attr ( "stroke-width" , ".2px" )
. style ( "opacity" , . 9 )
. on ( 'mouseover' , ( event , d ) => {
d3 . select ( '#tooltip' )
. style ( 'opacity' , 1 )
. style ( 'left' , ` ${ event . pageX + 10 } px ` )
. style ( 'top' , ` ${ event . pageY + 10 } px ` )
. text ( ` This node contains ${ d . data . urls _count } URLs. ` ) ;
} )
. on ( 'mouseout' , ( event , d ) => d3 . select ( '#tooltip' ) . style ( 'opacity' , 0 ) )
. text ( d => {
return ` ( ${ d . data . urls _count } ) ` ;
} ) ;
} ;
2020-11-03 17:04:04 +01:00
// Set Bookmark
2020-11-29 23:56:42 +01:00
if ( enable _bookmark ) {
d3 . select ( this ) . append ( "text" )
. attr ( 'x' , ` ${ selected _node _bbox . width - 12 } px ` )
. attr ( 'y' , '20px' )
. style ( "font-size" , "16px" )
. attr ( "id" , "bookmark" )
. text ( "🏁" )
. attr ( 'cursor' , 'pointer' )
. on ( 'click' , ( event , d ) => NodeHighlight ( d . data . uuid ) )
. on ( 'mouseover' , ( event , d ) => {
d3 . select ( '#tooltip' )
. style ( 'opacity' , 1 )
. style ( 'left' , ` ${ event . pageX + 10 } px ` )
. style ( 'top' , ` ${ event . pageY + 10 } px ` )
. text ( 'Bookmark this node' ) ;
} )
. on ( 'mouseout' , ( event , d ) => d3 . select ( '#tooltip' ) . style ( 'opacity' , 0 ) ) ;
} ;
2020-08-06 17:24:06 +02:00
2020-12-10 19:23:17 +01:00
const thumbnail _size = 64 ;
2020-12-09 19:11:19 +01:00
if ( d . data . contains _rendered _urlnode ) {
2021-01-20 01:28:54 +01:00
center _node = d . data . uuid ;
2023-09-26 18:08:04 +02:00
if ( favicon ) {
2022-08-25 13:28:02 +02:00
d3 . select ( this ) . append ( 'image' )
2023-09-26 18:08:04 +02:00
. attr ( 'x' , selected _node _bbox . width / 6 )
. attr ( 'y' , node _height - 1 )
. attr ( 'id' , 'favicon' )
. attr ( "width" , 32 )
. attr ( "height" , 32 )
2024-02-22 16:14:26 +01:00
. attr ( "xlink:href" , ` data: ${ mime _favicon } ;base64, ${ favicon } ` )
2023-09-26 18:08:04 +02:00
. attr ( 'cursor' , 'pointer' )
. on ( 'mouseover' , ( event , d ) => {
d3 . select ( '#tooltip' )
. style ( 'opacity' , 1 )
. style ( 'left' , ` ${ event . pageX + 10 } px ` )
. style ( 'top' , ` ${ event . pageY + 10 } px ` )
. text ( 'Potential favicon.' ) ;
} ) ;
2022-08-25 13:28:02 +02:00
}
2023-09-26 18:08:04 +02:00
d3 . select ( this ) . append ( "svg" ) . append ( 'rect' )
. attr ( 'x' , selected _node _bbox . width / 2 )
. attr ( 'y' , node _height - 3 )
. attr ( 'width' , thumbnail _size )
. attr ( 'height' , thumbnail _size )
. attr ( 'fill' , 'white' )
. attr ( 'stroke' , 'black' ) ;
d3 . select ( this ) . append ( 'image' )
. attr ( 'x' , selected _node _bbox . width / 2 )
. attr ( 'y' , node _height - 3 )
. attr ( 'id' , 'screenshot_thumbnail' )
. attr ( "width" , thumbnail _size )
. attr ( "height" , thumbnail _size )
. attr ( "xlink:href" , ` data:image/png;base64, ${ screenshot _thumbnail } ` )
. attr ( 'cursor' , 'pointer' )
. on ( 'mouseover' , ( event , d ) => {
d3 . select ( '#tooltip' )
. data ( d )
. style ( 'opacity' , 1 )
. style ( 'left' , ` ${ event . pageX + 10 } px ` )
. style ( 'top' , ` ${ event . pageY + 10 } px ` )
. text ( d => d . data . downloaded _filename ? 'Contains the URL rendered in the browser. It also downloaded a file.' : 'Contains the URL rendered in the browser.' ) ;
} )
. on ( 'click' , ( event , d ) => {
$ ( "#screenshotModal" ) . modal ( 'toggle' ) ;
} )
. on ( 'mouseout' , ( event , d ) => {
d3 . select ( '#tooltip' ) . style ( 'opacity' , 0 )
} ) ;
2020-12-09 19:11:19 +01:00
} ;
2020-12-10 19:23:17 +01:00
const http _icon _size = 24 ;
if ( d . data . http _content ) {
// set lock insecure connection
d3 . select ( this ) . append ( "svg" ) . append ( 'rect' )
. attr ( 'x' , selected _node _bbox . width - 22 )
. attr ( 'y' , selected _node _bbox . height - 13 )
. attr ( 'width' , http _icon _size )
. attr ( 'height' , http _icon _size )
. attr ( 'fill' , 'white' )
. attr ( 'stroke' , 'black' ) ;
d3 . select ( this ) . append ( 'image' )
. attr ( 'x' , selected _node _bbox . width - 22 )
. attr ( 'y' , selected _node _bbox . height - 13 )
. attr ( 'id' , 'insecure_image' )
. attr ( "width" , http _icon _size )
. attr ( "height" , http _icon _size )
. attr ( "xlink:href" , '/static/insecure.svg' )
. on ( 'mouseover' , ( event , d ) => {
d3 . select ( '#tooltip' )
. style ( 'opacity' , 1 )
. style ( 'left' , ` ${ event . pageX + 10 } px ` )
. style ( 'top' , ` ${ event . pageY + 10 } px ` )
. text ( 'This node containts insecure requests' ) ;
} )
. on ( 'mouseout' , ( event , d ) => d3 . select ( '#tooltip' ) . style ( 'opacity' , 0 ) ) ;
} ;
2020-08-31 15:21:41 +02:00
const context _icon _size = 24 ;
2020-08-20 19:39:03 +02:00
if ( d . data . malicious ) {
2020-08-28 19:11:19 +02:00
// set bomb
2020-08-20 19:39:03 +02:00
d3 . select ( this ) . append ( "svg" ) . append ( 'rect' )
. attr ( 'x' , selected _node _bbox . width - 22 - http _icon _size )
. attr ( 'y' , selected _node _bbox . height - 13 )
2020-08-31 15:21:41 +02:00
. attr ( 'width' , context _icon _size )
. attr ( 'height' , context _icon _size )
2020-08-20 19:39:03 +02:00
. attr ( 'fill' , 'white' )
. attr ( 'stroke' , 'black' ) ;
d3 . select ( this ) . append ( 'image' )
. attr ( 'x' , selected _node _bbox . width - 22 - http _icon _size )
. attr ( 'y' , selected _node _bbox . height - 13 )
2020-12-09 19:11:19 +01:00
. attr ( 'id' , 'malicious_image' )
2020-08-31 15:21:41 +02:00
. attr ( "width" , context _icon _size )
. attr ( "height" , context _icon _size )
2020-08-20 19:39:03 +02:00
. attr ( "xlink:href" , '/static/bomb.svg' )
2020-09-28 15:28:47 +02:00
. on ( 'mouseover' , ( event , d ) => {
2020-08-20 19:39:03 +02:00
d3 . select ( '#tooltip' )
. style ( 'opacity' , 1 )
2020-09-28 15:28:47 +02:00
. style ( 'left' , ` ${ event . pageX + 10 } px ` )
. style ( 'top' , ` ${ event . pageY + 10 } px ` )
2020-08-20 19:39:03 +02:00
. text ( 'This node containts known malicious content' ) ;
} )
2020-09-28 15:28:47 +02:00
. on ( 'mouseout' , ( event , d ) => d3 . select ( '#tooltip' ) . style ( 'opacity' , 0 ) ) ;
2020-08-31 15:21:41 +02:00
} else if ( d . data . legitimate ) {
2020-08-28 19:11:19 +02:00
// set checkmark
d3 . select ( this ) . append ( "svg" ) . append ( 'rect' )
. attr ( 'x' , selected _node _bbox . width - 22 - http _icon _size )
. attr ( 'y' , selected _node _bbox . height - 13 )
2020-08-31 15:21:41 +02:00
. attr ( 'width' , context _icon _size )
. attr ( 'height' , context _icon _size )
2020-08-28 19:11:19 +02:00
. attr ( 'fill' , 'white' )
. attr ( 'stroke' , 'black' ) ;
d3 . select ( this ) . append ( 'image' )
. attr ( 'x' , selected _node _bbox . width - 22 - http _icon _size )
. attr ( 'y' , selected _node _bbox . height - 13 )
2020-12-09 19:11:19 +01:00
. attr ( 'id' , 'known_image' )
2020-08-31 15:21:41 +02:00
. attr ( "width" , context _icon _size )
. attr ( "height" , context _icon _size )
2020-08-28 19:11:19 +02:00
. attr ( "xlink:href" , '/static/check.svg' )
2020-09-28 15:28:47 +02:00
. on ( 'mouseover' , ( event , d ) => {
2020-08-28 19:11:19 +02:00
d3 . select ( '#tooltip' )
. style ( 'opacity' , 1 )
2020-09-28 15:28:47 +02:00
. style ( 'left' , ` ${ event . pageX + 10 } px ` )
. style ( 'top' , ` ${ event . pageY + 10 } px ` )
2020-11-29 23:38:09 +01:00
. text ( 'This node has only known content' ) ;
2020-08-28 19:11:19 +02:00
} )
2020-09-28 15:28:47 +02:00
. on ( 'mouseout' , ( event , d ) => d3 . select ( '#tooltip' ) . style ( 'opacity' , 0 ) ) ;
2022-08-24 14:04:47 +02:00
} else if ( d . data . all _empty && ! d . data . contains _rendered _urlnode ) {
2020-08-31 15:21:41 +02:00
// set empty
d3 . select ( this ) . append ( "svg" ) . append ( 'rect' )
. attr ( 'x' , selected _node _bbox . width - 22 - http _icon _size )
. attr ( 'y' , selected _node _bbox . height - 13 )
. attr ( 'width' , context _icon _size )
. attr ( 'height' , context _icon _size )
. attr ( 'fill' , 'white' )
. attr ( 'stroke' , 'black' ) ;
d3 . select ( this ) . append ( 'image' )
. attr ( 'x' , selected _node _bbox . width - 22 - http _icon _size )
. attr ( 'y' , selected _node _bbox . height - 13 )
2020-12-09 19:11:19 +01:00
. attr ( 'id' , 'empty_image' )
2020-08-31 15:21:41 +02:00
. attr ( "width" , context _icon _size )
. attr ( "height" , context _icon _size )
. attr ( "xlink:href" , '/static/empty.svg' )
2020-09-28 15:28:47 +02:00
. on ( 'mouseover' , ( event , d ) => {
2020-08-31 15:21:41 +02:00
d3 . select ( '#tooltip' )
. style ( 'opacity' , 1 )
2020-09-28 15:28:47 +02:00
. style ( 'left' , ` ${ event . pageX + 10 } px ` )
. style ( 'top' , ` ${ event . pageY + 10 } px ` )
2020-08-31 15:21:41 +02:00
. text ( 'This node has only empty content' ) ;
} )
2020-09-28 15:28:47 +02:00
. on ( 'mouseout' , ( event , d ) => d3 . select ( '#tooltip' ) . style ( 'opacity' , 0 ) ) ;
2020-08-31 15:21:41 +02:00
} ;
2020-12-09 19:11:19 +01:00
if ( d . children || d . _children ) {
d3 . select ( this )
// Add Circle for the nodes
. append ( 'circle' )
. attr ( 'class' , 'node' )
. attr ( 'r' , 1e-6 )
. attr ( 'cx' , d => d . node _width )
. attr ( 'cy' , d => node _height / 2 )
. style ( "fill" , d => d . _children ? "lightsteelblue" : "#fff" )
. on ( 'mouseover' , ( event , d ) => {
if ( d . children || d . _children ) {
d3 . select ( '#tooltip' )
. style ( 'opacity' , 1 )
. style ( 'left' , ` ${ event . pageX + 10 } px ` )
. style ( 'top' , ` ${ event . pageY + 10 } px ` )
2020-12-16 02:28:12 +01:00
. text ( d . children ? 'Collapse the URLs loaded by this node.' : 'Expand the URLs loaded by this node.' ) ;
2020-12-09 19:11:19 +01:00
} ;
}
)
. on ( 'mouseout' , ( event , d ) => {
if ( d . children || d . _children ) {
d3 . select ( '#tooltip' ) . style ( 'opacity' , 0 )
} ;
}
)
. on ( 'click' , ( event , d ) => {
if ( d . children || d . _children ) {
toggle _children _collapse ( event , d )
} ;
}
) ;
} ;
2019-05-15 22:16:53 +02:00
} ) ;
2020-08-06 17:24:06 +02:00
2019-05-15 12:14:40 +02:00
return node _group ;
} ,
2020-08-12 20:09:40 +02:00
update => update ,
2019-05-15 12:14:40 +02:00
exit => exit
2020-08-11 19:17:39 +02:00
. transition ( )
2019-05-17 16:59:56 +02:00
// Remove any exiting nodes
2020-08-11 19:17:39 +02:00
. attr ( "transform" , node => ` translate( ${ node . y0 } , ${ node . x0 } ) ` )
2019-05-17 16:59:56 +02:00
// On exit reduce the node circles size to 0
. attr ( 'r' , 1e-6 )
// On exit reduce the opacity of text labels
. style ( 'fill-opacity' , 1e-6 )
. remove ( )
2019-05-15 12:14:40 +02:00
) . call ( node => {
2019-05-17 16:59:56 +02:00
node
// Transition to the proper position for the node
2020-08-11 19:17:39 +02:00
. attr ( "transform" , node => ` translate( ${ node . y } , ${ node . x } ) ` )
2019-05-17 16:59:56 +02:00
// Update the node attributes and style
. select ( 'circle.node' )
. attr ( 'r' , 10 )
. style ( "fill" , node => node . _children ? "lightsteelblue" : "#fff" )
2020-11-04 13:43:34 +01:00
. attr ( 'cursor' , ( d ) => {
if ( d . children || d . _children ) {
return 'pointer' ;
}
} ) ;
2019-05-15 18:10:07 +02:00
2017-10-04 15:13:42 +02:00
} ) ;
2017-09-22 00:26:38 +02:00
2019-05-15 12:14:40 +02:00
nodes . forEach ( d => {
// Store the old positions for transition.
d . x0 = d . x ;
d . y0 = d . y ;
2017-09-29 14:43:40 +02:00
} ) ;
2018-02-01 01:16:20 +01:00
2017-09-22 00:26:38 +02:00
// ****************** links section ***************************
// Update the links...
2020-08-11 19:17:39 +02:00
const link = node _container . selectAll ( 'path.link' ) . data ( links , d => d . id ) ;
// Creates a curved (diagonal) path from parent to the child nodes
2020-12-09 19:11:19 +01:00
let diagonal = d3 . linkHorizontal ( )
. source ( d => { return [ d . y , d . x ] } )
. target ( d => { return [ d . parent . y + d . parent . node _width , d . parent . x ] } ) ;
2019-05-15 12:14:40 +02:00
link . join (
enter => enter
// Enter any new links at the parent's previous position.
. insert ( 'path' , "g" )
. attr ( "class" , "link" )
2020-12-09 19:11:19 +01:00
. attr ( 'd' , diagonal ) ,
2019-05-15 12:14:40 +02:00
update => update ,
2020-12-09 19:11:19 +01:00
exit => exit . call ( exit => exit . attr ( 'd' , diagonal ) . remove ( ) )
) . call ( link => link . attr ( 'd' , diagonal ) ) ;
2017-09-22 00:26:38 +02:00
2019-05-15 12:14:40 +02:00
if ( computed _node _width === 0 ) {
update ( root , node _width )
2017-09-22 00:26:38 +02:00
}
}