2024-11-30 19:57:06 +01:00
import libraryLoader from "../services/library_loader.js" ;
import server from "../services/server.js" ;
import attributeService from "../services/attributes.js" ;
import hoistedNoteService from "../services/hoisted_note.js" ;
import appContext from "../components/app_context.js" ;
import NoteContextAwareWidget from "./note_context_aware_widget.js" ;
import linkContextMenuService from "../menus/link_context_menu.js" ;
import utils from "../services/utils.js" ;
import { t } from "../services/i18n.js" ;
2022-12-26 10:52:28 +01:00
const esc = utils . escapeHtml ;
2021-09-22 21:11:36 +02:00
const TPL = ` <div class="note-map-widget" style="position: relative;">
2021-05-28 23:52:42 +02:00
< style >
2022-08-02 17:17:27 +02:00
. note - detail - note - map {
2021-09-22 21:11:36 +02:00
height : 100 % ;
2022-08-02 17:17:27 +02:00
overflow : hidden ;
2021-05-28 23:52:42 +02:00
}
2021-05-29 22:52:32 +02:00
2021-09-22 21:11:36 +02:00
. map - type - switcher {
position : absolute ;
top : 10 px ;
2022-08-05 16:44:26 +02:00
left : 10 px ;
2021-10-21 21:28:44 +02:00
z - index : 10 ; /* should be below dropdown (note actions) */
2021-05-28 23:52:42 +02:00
}
2021-06-02 21:23:40 +02:00
2022-08-05 16:44:26 +02:00
. map - type - switcher button . bx {
font - size : 130 % ;
padding : 1 px 10 px 1 px 10 px ;
2021-06-02 21:23:40 +02:00
}
2024-11-30 19:20:07 +01:00
2024-11-30 19:57:06 +01:00
/* Style Ui Element to Drag Nodes */
2024-11-30 19:20:07 +01:00
. fixnodes - type - switcher {
position : absolute ;
top : 10 px ;
left : 45 % ;
z - index : 10 ; /* should be below dropdown (note actions) */
border - radius : 0.2 rem ;
}
2024-11-30 19:57:06 +01:00
/* Start of styling the slider */
2024-11-30 19:20:07 +01:00
input [ type = "range" ] {
/* removing default appearance */
- webkit - appearance : none ;
appearance : none ;
margin - left : 15 px ;
width : 50 %
}
2024-11-30 19:57:06 +01:00
/* Changing slider tracker */
2024-11-30 19:20:07 +01:00
input [ type = "range" ] : : - webkit - slider - runnable - track {
height : 6 px ;
background : # ccc ;
border - radius : 16 px ;
}
2024-11-30 19:57:06 +01:00
/* Changing Slider Thumb*/
2024-11-30 19:20:07 +01:00
input [ type = "range" ] : : - webkit - slider - thumb {
/* removing default appearance */
- webkit - appearance : none ;
appearance : none ;
/* creating a custom design */
height : 15 px ;
width : 15 px ;
margin - top : - 4 px ;
background - color : # 661822 ;
border - radius : 50 % ;
2024-11-30 19:57:06 +01:00
/* End of styling the slider */
2021-05-28 23:52:42 +02:00
< / s t y l e >
2021-06-02 21:23:40 +02:00
2022-07-24 14:30:42 +02:00
< div class = "btn-group btn-group-sm map-type-switcher" role = "group" >
2024-11-30 19:57:06 +01:00
< button type = "button" class = "btn bx bx-network-chart" title = "${t(" note - map . button - link - map ")}" data - type = "link" > < / b u t t o n >
< button type = "button" class = "btn bx bx-sitemap" title = "${t(" note - map . button - tree - map ")}" data - type = "tree" > < / b u t t o n >
2024-11-30 19:20:07 +01:00
< / d i v >
2024-11-30 19:57:06 +01:00
< ! UI for dragging Notes and link force >
< div class = " btn-group-sm fixnodes-type-switcher" role = "group" >
2024-11-30 19:20:07 +01:00
< button type = "button" class = "btn bx bx-expand" title = "Fixation" data - type = "moveable" > < / b u t t o n >
< input type = "range" class = " slider" min = "1" title = "Link distance" max = "100" value = "40" >
2021-09-22 21:11:36 +02:00
< / d i v >
2021-06-27 12:53:05 +02:00
2021-09-22 22:25:39 +02:00
< div class = "style-resolver" > < / d i v >
2021-09-22 21:11:36 +02:00
< div class = "note-map-container" > < / d i v >
< / d i v > ` ;
2021-05-28 23:19:11 +02:00
2021-09-22 21:11:36 +02:00
export default class NoteMapWidget extends NoteContextAwareWidget {
2021-09-22 22:25:39 +02:00
constructor ( widgetMode ) {
super ( ) ;
2024-11-30 19:57:06 +01:00
this . fixNodes = false ; // needed to save the status of the UI element. Is set later in the code
2021-09-22 22:25:39 +02:00
this . widgetMode = widgetMode ; // 'type' or 'ribbon'
}
2021-05-28 23:19:11 +02:00
doRender ( ) {
this . $widget = $ ( TPL ) ;
2021-05-31 21:20:30 +02:00
2021-10-08 16:38:37 +02:00
const documentStyle = window . getComputedStyle ( document . documentElement ) ;
this . themeStyle = documentStyle . getPropertyValue ( '--theme-style' ) ? . trim ( ) ;
2024-11-30 19:57:06 +01:00
this . $container = this . $widget . find ( ".note-map-container" ) ;
2021-09-22 22:25:39 +02:00
this . $styleResolver = this . $widget . find ( '.style-resolver' ) ;
2021-05-31 23:38:47 +02:00
2023-08-15 22:50:13 +02:00
new ResizeObserver ( ( ) => this . setDimensions ( ) ) . observe ( this . $container [ 0 ] ) ;
2021-05-31 21:20:30 +02:00
2024-11-30 19:57:06 +01:00
this . $widget . find ( ".map-type-switcher button" ) . on ( "click" , async e => {
const type = $ ( e . target ) . closest ( "button" ) . attr ( "data-type" ) ;
2021-09-22 21:11:36 +02:00
await attributeService . setLabel ( this . noteId , 'mapType' , type ) ;
2021-05-31 21:20:30 +02:00
} ) ;
2024-11-30 19:57:06 +01:00
// Reading the status of the Drag nodes Ui element. Changing it´ s color when activated. Reading Force value of the link distance.
2024-11-30 19:20:07 +01:00
this . $widget . find ( '.fixnodes-type-switcher' ) . on ( 'click' , async event => {
this . fixNodes = ! this . fixNodes ;
event . target . style . backgroundColor = this . fixNodes ? '#661822' : 'transparent' ;
} ) ;
2021-09-22 21:11:36 +02:00
super . doRender ( ) ;
2021-05-31 23:38:47 +02:00
}
2022-08-02 17:17:27 +02:00
setDimensions ( ) {
2024-11-30 19:57:06 +01:00
if ( ! this . graph ) { // no graph has been even rendered
2021-09-22 21:11:36 +02:00
return ;
}
2021-09-22 22:25:39 +02:00
const $parent = this . $widget . parent ( ) ;
2021-05-31 23:38:47 +02:00
2024-11-30 19:57:06 +01:00
this . graph
. height ( $parent . height ( ) )
. width ( $parent . width ( ) ) ;
2021-05-28 23:19:11 +02:00
}
2023-05-05 23:17:23 +02:00
async refreshWithNote ( note ) {
2021-09-22 21:11:36 +02:00
this . $widget . show ( ) ;
2021-05-31 21:31:07 +02:00
2021-09-22 22:25:39 +02:00
this . css = {
2024-11-30 19:57:06 +01:00
fontFamily : this . $container . css ( "font-family" ) ,
textColor : this . rgb2hex ( this . $container . css ( "color" ) ) ,
mutedTextColor : this . rgb2hex ( this . $styleResolver . css ( "color" ) )
2021-09-22 22:25:39 +02:00
} ;
2024-11-30 19:57:06 +01:00
this . mapType = this . note . getLabelValue ( "mapType" ) === "tree" ? "tree" : "link" ;
2021-06-01 22:03:38 +02:00
2021-05-31 21:31:07 +02:00
await libraryLoader . requireLibrary ( libraryLoader . FORCE _GRAPH ) ;
2024-11-30 19:57:06 +01:00
//variables for the hover effekt. We have to save the neighbours of a hovered node in a set. Also we need to save the links as well as the hovered node itself
2024-11-30 19:20:07 +01:00
let hoverNode = null ;
const highlightLinks = new Set ( ) ;
const neighbours = new Set ( ) ;
2021-05-31 21:31:07 +02:00
this . graph = ForceGraph ( ) ( this . $container [ 0 ] )
. width ( this . $container . width ( ) )
. height ( this . $container . height ( ) )
. onZoom ( zoom => this . setZoomLevel ( zoom . k ) )
2021-09-22 21:11:36 +02:00
. d3AlphaDecay ( 0.01 )
. d3VelocityDecay ( 0.08 )
2024-11-30 19:20:07 +01:00
//Code to fixate nodes when dragged
. onNodeDragEnd ( node => {
if ( this . fixNodes ) {
node . fx = node . x ;
node . fy = node . y ;
} else {
node . fx = null ;
node . fy = null ;
}
} )
2024-11-30 19:57:06 +01:00
//check if hovered and set the hovernode variable, saving the hovered node object into it. Clear links variable everytime you hover. Without clearing links will stay highlighted
2024-11-30 19:20:07 +01:00
. onNodeHover ( node => {
hoverNode = node || null ;
highlightLinks . clear ( ) ;
} )
2024-11-30 19:57:06 +01:00
// set link width to immitate a highlight effekt. Checking the condition if any links are saved in the previous defined set highlightlinks
2024-11-30 19:20:07 +01:00
. linkWidth ( link => ( highlightLinks . has ( link ) ? 3 : 0.4 ) )
. linkColor ( link => ( highlightLinks . has ( link ) ? 'white' : this . css . mutedTextColor ) )
2024-11-30 19:57:06 +01:00
. linkDirectionalArrowLength ( 4 )
. linkDirectionalArrowRelPos ( 0.95 )
// main code for highlighting hovered nodes and neighbours. here we "style" the nodes. the nodes are rendered several hundred times per second.
2024-11-30 19:20:07 +01:00
. nodeCanvasObject ( ( node , ctx ) => {
2024-11-30 19:57:06 +01:00
if ( hoverNode == node ) { //paint only hovered node
2024-11-30 19:20:07 +01:00
this . paintNode ( node , '#661822' , ctx ) ;
2024-11-30 19:57:06 +01:00
neighbours . clear ( ) ; //clearing neighbours or the effect would be maintained after hovering is over
for ( const link of data . links ) { //check if node is part of a link in the canvas, if so add it´ s neighbours and related links to the previous defined variables to paint the nodes
2024-11-30 19:20:07 +01:00
if ( link . source . id == node . id || link . target . id == node . id ) {
neighbours . add ( link . source ) ;
neighbours . add ( link . target ) ;
highlightLinks . add ( link ) ;
neighbours . delete ( node ) ;
}
}
2024-11-30 19:57:06 +01:00
} else if ( neighbours . has ( node ) && hoverNode != null ) { //paint neighbours
2024-11-30 19:20:07 +01:00
this . paintNode ( node , '#9d6363' , ctx ) ;
} else {
2024-11-30 19:57:06 +01:00
this . paintNode ( node , this . getColorForNode ( node ) , ctx ) ; //paint rest of nodes in canvas
2024-11-30 19:20:07 +01:00
}
} )
2024-11-30 19:57:06 +01:00
. nodePointerAreaPaint ( ( node , ctx ) => this . paintNode ( node , this . getColorForNode ( node ) , ctx ) )
2021-05-31 21:31:07 +02:00
. nodePointerAreaPaint ( ( node , color , ctx ) => {
ctx . fillStyle = color ;
ctx . beginPath ( ) ;
2021-09-22 21:11:36 +02:00
ctx . arc ( node . x , node . y , this . noteIdToSizeMap [ node . id ] , 0 , 2 * Math . PI , false ) ;
2021-05-31 21:31:07 +02:00
ctx . fill ( ) ;
} )
2022-12-26 10:52:28 +01:00
. nodeLabel ( node => esc ( node . name ) )
2021-09-22 21:11:36 +02:00
. maxZoom ( 7 )
2021-09-29 12:39:28 +02:00
. warmupTicks ( 30 )
2021-09-24 21:40:02 +02:00
. onNodeClick ( node => appContext . tabManager . getActiveContext ( ) . setNote ( node . id ) )
2023-04-03 23:47:24 +02:00
. onNodeRightClick ( ( node , e ) => linkContextMenuService . openContextMenu ( node . id , e ) ) ;
2021-05-31 21:31:07 +02:00
2021-09-22 21:11:36 +02:00
if ( this . mapType === 'link' ) {
this . graph
2024-11-30 19:57:06 +01:00
. linkLabel ( l => ` ${ esc ( l . source . name ) } - <strong> ${ esc ( l . name ) } </strong> - ${ esc ( l . target . name ) } ` )
2021-09-22 21:11:36 +02:00
. linkCanvasObject ( ( link , ctx ) => this . paintLink ( link , ctx ) )
2024-11-30 19:57:06 +01:00
. linkCanvasObjectMode ( ( ) => "after" ) ;
2021-09-22 21:11:36 +02:00
}
2021-05-31 21:31:07 +02:00
2022-11-05 22:32:50 +01:00
const mapRootNoteId = this . getMapRootNoteId ( ) ;
2021-09-28 13:27:21 +02:00
const data = await this . loadNotesAndRelations ( mapRootNoteId ) ;
2024-11-30 19:57:06 +01:00
2021-09-28 13:27:21 +02:00
const nodeLinkRatio = data . nodes . length / data . links . length ;
2021-10-03 11:04:56 +02:00
const magnifiedRatio = Math . pow ( nodeLinkRatio , 1.5 ) ;
const charge = - 20 / magnifiedRatio ;
const boundedCharge = Math . min ( - 3 , charge ) ;
2024-11-30 19:57:06 +01:00
let distancevalue = 40 ; // default value for the link force of the nodes
2024-11-30 19:20:07 +01:00
this . $widget . find ( '.fixnodes-type-switcher input' ) . on ( 'change' , async e => {
2024-11-30 19:57:06 +01:00
distancevalue = e . target . closest ( 'input' ) . value ;
this . graph . d3Force ( 'link' ) . distance ( distancevalue ) ;
2024-11-30 19:20:07 +01:00
this . renderData ( data ) ;
} ) ;
2021-09-28 13:27:21 +02:00
2021-09-29 12:39:28 +02:00
this . graph . d3Force ( 'center' ) . strength ( 0.2 ) ;
2021-10-03 11:04:56 +02:00
this . graph . d3Force ( 'charge' ) . strength ( boundedCharge ) ;
2021-09-22 21:11:36 +02:00
this . graph . d3Force ( 'charge' ) . distanceMax ( 1000 ) ;
2024-11-30 19:57:06 +01:00
2021-09-22 22:25:39 +02:00
this . renderData ( data ) ;
}
getMapRootNoteId ( ) {
if ( this . widgetMode === 'ribbon' ) {
return this . noteId ;
}
2024-11-30 19:57:06 +01:00
let mapRootNoteId = this . note . getLabelValue ( "mapRootNoteId" ) ;
2021-05-31 21:31:07 +02:00
2021-09-22 21:11:36 +02:00
if ( mapRootNoteId === 'hoisted' ) {
mapRootNoteId = hoistedNoteService . getHoistedNoteId ( ) ;
2021-09-22 22:25:39 +02:00
} else if ( ! mapRootNoteId ) {
2021-09-22 21:11:36 +02:00
mapRootNoteId = appContext . tabManager . getActiveContext ( ) . parentNoteId ;
}
2021-09-22 22:25:39 +02:00
return mapRootNoteId ;
2021-05-31 21:31:07 +02:00
}
2022-12-20 20:41:51 +01:00
getColorForNode ( node ) {
if ( node . color ) {
return node . color ;
} else if ( this . widgetMode === 'ribbon' && node . id === this . noteId ) {
return 'red' ; // subtree root mark as red
} else {
return this . generateColorFromString ( node . type ) ;
}
}
generateColorFromString ( str ) {
2024-11-30 19:57:06 +01:00
if ( this . themeStyle === "dark" ) {
2023-06-23 00:26:47 +08:00
str = ` 0 ${ str } ` ; // magic lightning modifier
2021-10-08 16:45:21 +02:00
}
2021-09-22 21:11:36 +02:00
let hash = 0 ;
for ( let i = 0 ; i < str . length ; i ++ ) {
hash = str . charCodeAt ( i ) + ( ( hash << 5 ) - hash ) ;
2021-06-01 22:03:38 +02:00
}
2021-10-08 16:38:37 +02:00
2021-10-08 16:45:21 +02:00
let color = '#' ;
for ( let i = 0 ; i < 3 ; i ++ ) {
2024-11-30 19:57:06 +01:00
const value = ( hash >> ( i * 8 ) ) & 0xFF ;
2021-10-08 16:38:37 +02:00
2024-11-30 19:57:06 +01:00
color += ( ` 00 ${ value . toString ( 16 ) } ` ) . substr ( - 2 ) ;
2021-05-31 21:31:07 +02:00
}
2021-10-08 16:45:21 +02:00
return color ;
2021-05-31 21:31:07 +02:00
}
2021-05-28 23:19:11 +02:00
2021-09-22 21:11:36 +02:00
rgb2hex ( rgb ) {
2024-11-30 19:57:06 +01:00
return ` # ${ rgb . match ( /^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/ )
2021-09-22 21:11:36 +02:00
. slice ( 1 )
. map ( n => parseInt ( n , 10 ) . toString ( 16 ) . padStart ( 2 , '0' ) )
2024-11-30 19:57:06 +01:00
. join ( '' ) } `
2021-09-22 21:11:36 +02:00
}
2021-05-28 23:19:11 +02:00
2021-09-22 21:11:36 +02:00
setZoomLevel ( level ) {
this . zoomLevel = level ;
}
2021-06-01 22:03:38 +02:00
2021-09-22 21:11:36 +02:00
paintNode ( node , color , ctx ) {
2024-11-30 19:57:06 +01:00
const { x , y } = node ;
2021-09-22 21:11:36 +02:00
const size = this . noteIdToSizeMap [ node . id ] ;
2021-06-01 22:03:38 +02:00
2022-12-20 20:41:51 +01:00
ctx . fillStyle = color ;
2021-09-22 21:11:36 +02:00
ctx . beginPath ( ) ;
2024-11-30 19:57:06 +01:00
ctx . arc ( x , y , size * 0.8 , 0 , 2 * Math . PI , false ) ;
2021-09-22 21:11:36 +02:00
ctx . fill ( ) ;
2021-05-31 23:38:47 +02:00
2024-11-30 19:57:06 +01:00
const toRender = this . zoomLevel > 2
|| ( this . zoomLevel > 1 && size > 6 )
|| ( this . zoomLevel > 0.3 && size > 10 ) ;
2021-05-31 21:31:07 +02:00
2021-09-22 21:11:36 +02:00
if ( ! toRender ) {
return ;
}
2021-05-31 23:38:47 +02:00
2021-09-22 21:11:36 +02:00
ctx . fillStyle = this . css . textColor ;
2022-12-21 15:19:05 +01:00
ctx . font = ` ${ size } px ${ this . css . fontFamily } ` ;
2021-09-22 21:11:36 +02:00
ctx . textAlign = 'center' ;
ctx . textBaseline = 'middle' ;
2021-05-31 21:31:07 +02:00
2021-09-22 21:11:36 +02:00
let title = node . name ;
if ( title . length > 15 ) {
2022-12-21 15:19:05 +01:00
title = ` ${ title . substr ( 0 , 15 ) } ... ` ;
2021-06-01 22:03:38 +02:00
}
2021-05-31 21:31:07 +02:00
2021-09-22 21:11:36 +02:00
ctx . fillText ( title , x , y + Math . round ( size * 1.5 ) ) ;
2021-05-28 23:19:11 +02:00
}
2021-05-31 21:31:07 +02:00
paintLink ( link , ctx ) {
2021-06-02 21:39:18 +02:00
if ( this . zoomLevel < 5 ) {
2021-05-31 21:31:07 +02:00
return ;
}
2024-11-30 19:57:06 +01:00
ctx . font = ` 3px ${ this . css . fontFamily } ` ;
2021-05-31 21:31:07 +02:00
ctx . textAlign = 'center' ;
ctx . textBaseline = 'middle' ;
2021-06-02 21:23:40 +02:00
ctx . fillStyle = this . css . mutedTextColor ;
2021-05-31 21:31:07 +02:00
2024-11-30 19:57:06 +01:00
const { source , target } = link ;
2021-05-31 21:31:07 +02:00
const x = ( source . x + target . x ) / 2 ;
const y = ( source . y + target . y ) / 2 ;
2024-11-30 19:57:06 +01:00
2021-05-31 21:31:07 +02:00
ctx . save ( ) ;
ctx . translate ( x , y ) ;
const deltaY = source . y - target . y ;
const deltaX = source . x - target . x ;
let angle = Math . atan2 ( deltaY , deltaX ) ;
let moveY = 2 ;
if ( angle < - Math . PI / 2 || angle > Math . PI / 2 ) {
angle += Math . PI ;
moveY = - 2 ;
}
ctx . rotate ( angle ) ;
ctx . fillText ( link . name , 0 , moveY ) ;
ctx . restore ( ) ;
}
2021-09-22 21:11:36 +02:00
async loadNotesAndRelations ( mapRootNoteId ) {
const resp = await server . post ( ` note-map/ ${ mapRootNoteId } / ${ this . mapType } ` ) ;
2021-05-31 21:31:07 +02:00
2021-09-22 23:02:38 +02:00
this . calculateNodeSizes ( resp ) ;
2021-09-22 21:11:36 +02:00
2021-09-22 22:41:31 +02:00
const links = this . getGroupedLinks ( resp . links ) ;
2024-11-30 19:57:06 +01:00
2022-12-20 20:41:51 +01:00
this . nodes = resp . notes . map ( ( [ noteId , title , type , color ] ) => ( {
2021-09-22 22:41:31 +02:00
id : noteId ,
name : title ,
type : type ,
2022-12-20 20:41:51 +01:00
color : color
2021-09-22 22:41:31 +02:00
} ) ) ;
2021-05-31 21:31:07 +02:00
2021-09-22 22:41:31 +02:00
return {
nodes : this . nodes ,
links : links . map ( link => ( {
2022-11-05 22:32:50 +01:00
id : ` ${ link . sourceNoteId } - ${ link . targetNoteId } ` ,
2021-09-22 22:41:31 +02:00
source : link . sourceNoteId ,
target : link . targetNoteId ,
2024-11-30 19:57:06 +01:00
name : link . names . join ( ", " )
2021-09-22 22:41:31 +02:00
} ) )
} ;
}
2021-05-31 21:31:07 +02:00
2021-09-22 22:41:31 +02:00
getGroupedLinks ( links ) {
const linksGroupedBySourceTarget = { } ;
2021-09-22 21:11:36 +02:00
2021-09-22 22:41:31 +02:00
for ( const link of links ) {
2021-09-22 21:11:36 +02:00
const key = ` ${ link . sourceNoteId } - ${ link . targetNoteId } ` ;
if ( key in linksGroupedBySourceTarget ) {
if ( ! linksGroupedBySourceTarget [ key ] . names . includes ( link . name ) ) {
linksGroupedBySourceTarget [ key ] . names . push ( link . name ) ;
}
2021-09-22 22:41:31 +02:00
} else {
2021-09-22 21:11:36 +02:00
linksGroupedBySourceTarget [ key ] = {
id : key ,
sourceNoteId : link . sourceNoteId ,
targetNoteId : link . targetNoteId ,
names : [ link . name ]
2024-11-30 19:57:06 +01:00
}
2021-09-22 21:11:36 +02:00
}
2021-05-31 21:31:07 +02:00
}
2021-09-22 22:41:31 +02:00
return Object . values ( linksGroupedBySourceTarget ) ;
2021-05-31 21:31:07 +02:00
}
2021-09-22 23:02:38 +02:00
calculateNodeSizes ( resp ) {
2021-09-22 21:11:36 +02:00
this . noteIdToSizeMap = { } ;
2021-09-22 23:02:38 +02:00
if ( this . mapType === 'tree' ) {
2024-11-30 19:57:06 +01:00
const { noteIdToDescendantCountMap } = resp ;
2021-09-22 23:02:38 +02:00
for ( const noteId in noteIdToDescendantCountMap ) {
this . noteIdToSizeMap [ noteId ] = 4 ;
const count = noteIdToDescendantCountMap [ noteId ] ;
if ( count > 0 ) {
this . noteIdToSizeMap [ noteId ] += 1 + Math . round ( Math . log ( count ) / Math . log ( 1.5 ) ) ;
}
}
2024-11-30 19:57:06 +01:00
}
else if ( this . mapType === 'link' ) {
2021-09-22 23:02:38 +02:00
const noteIdToLinkCount = { } ;
for ( const link of resp . links ) {
2024-11-30 19:57:06 +01:00
noteIdToLinkCount [ link . targetNoteId ] = 1 + ( noteIdToLinkCount [ link . targetNoteId ] || 0 ) ;
2021-09-22 23:02:38 +02:00
}
2021-09-22 21:11:36 +02:00
2021-09-22 23:02:38 +02:00
for ( const [ noteId ] of resp . notes ) {
this . noteIdToSizeMap [ noteId ] = 4 ;
2021-09-22 21:11:36 +02:00
2021-09-22 23:02:38 +02:00
if ( noteId in noteIdToLinkCount ) {
2024-11-30 19:57:06 +01:00
this . noteIdToSizeMap [ noteId ] += Math . min ( Math . pow ( noteIdToLinkCount [ noteId ] , 0.5 ) , 15 ) ;
2021-09-22 23:02:38 +02:00
}
2021-09-22 21:11:36 +02:00
}
2021-05-31 21:31:07 +02:00
}
2021-09-22 21:11:36 +02:00
}
2021-09-22 22:25:39 +02:00
renderData ( data ) {
2021-09-22 21:11:36 +02:00
this . graph . graphData ( data ) ;
2024-11-30 19:57:06 +01:00
2022-11-07 23:19:38 +01:00
if ( this . widgetMode === 'ribbon' && this . note ? . type !== 'search' ) {
2021-09-22 22:25:39 +02:00
setTimeout ( ( ) => {
2022-11-05 22:32:50 +01:00
this . setDimensions ( ) ;
2021-10-21 22:52:52 +02:00
const subGraphNoteIds = this . getSubGraphConnectedToCurrentNote ( data ) ;
2021-09-22 22:25:39 +02:00
2024-11-30 19:57:06 +01:00
this . graph . zoomToFit ( 400 , 50 , node => subGraphNoteIds . has ( node . id ) ) ;
2021-11-13 22:48:30 +01:00
if ( subGraphNoteIds . size < 30 ) {
this . graph . d3VelocityDecay ( 0.4 ) ;
}
2021-09-22 22:25:39 +02:00
} , 1000 ) ;
2024-11-30 19:57:06 +01:00
}
else {
2021-09-22 22:25:39 +02:00
if ( data . nodes . length > 1 ) {
2021-11-13 22:48:30 +01:00
setTimeout ( ( ) => {
2022-11-05 22:32:50 +01:00
this . setDimensions ( ) ;
2022-11-07 23:19:38 +01:00
const noteIdsWithLinks = this . getNoteIdsWithLinks ( data ) ;
if ( noteIdsWithLinks . size > 0 ) {
2024-11-30 19:57:06 +01:00
this . graph . zoomToFit ( 400 , 30 , node => noteIdsWithLinks . has ( node . id ) ) ;
2022-11-07 23:19:38 +01:00
}
2021-11-13 22:48:30 +01:00
2022-11-07 23:19:38 +01:00
if ( noteIdsWithLinks . size < 30 ) {
2021-11-13 22:48:30 +01:00
this . graph . d3VelocityDecay ( 0.4 ) ;
}
} , 1000 ) ;
2021-09-22 22:25:39 +02:00
}
2021-05-28 23:19:11 +02:00
}
}
2022-11-07 23:19:38 +01:00
getNoteIdsWithLinks ( data ) {
const noteIds = new Set ( ) ;
for ( const link of data . links ) {
noteIds . add ( link . source . id ) ;
noteIds . add ( link . target . id ) ;
}
return noteIds ;
}
2021-10-21 22:52:52 +02:00
getSubGraphConnectedToCurrentNote ( data ) {
2021-11-13 22:48:30 +01:00
function getGroupedLinks ( links , type ) {
2021-10-21 22:52:52 +02:00
const map = { } ;
for ( const link of links ) {
2021-11-13 22:48:30 +01:00
const key = link [ type ] . id ;
2021-10-21 22:52:52 +02:00
map [ key ] = map [ key ] || [ ] ;
map [ key ] . push ( link ) ;
}
return map ;
}
2024-11-30 19:57:06 +01:00
const linksBySource = getGroupedLinks ( data . links , "source" ) ;
const linksByTarget = getGroupedLinks ( data . links , "target" ) ;
2021-10-21 22:52:52 +02:00
const subGraphNoteIds = new Set ( ) ;
function traverseGraph ( noteId ) {
if ( subGraphNoteIds . has ( noteId ) ) {
return ;
}
subGraphNoteIds . add ( noteId ) ;
for ( const link of linksBySource [ noteId ] || [ ] ) {
traverseGraph ( link . target . id ) ;
}
2021-11-13 22:48:30 +01:00
for ( const link of linksByTarget [ noteId ] || [ ] ) {
traverseGraph ( link . source . id ) ;
}
2021-10-21 22:52:52 +02:00
}
traverseGraph ( this . noteId ) ;
return subGraphNoteIds ;
}
2021-09-22 21:11:36 +02:00
cleanup ( ) {
this . $container . html ( '' ) ;
2021-06-02 21:23:40 +02:00
}
2024-11-30 19:57:06 +01:00
entitiesReloadedEvent ( { loadResults } ) {
if ( loadResults . getAttributeRows ( this . componentId ) . find (
attr =>
attr . type === 'label'
&& [ 'mapType' , 'mapRootNoteId' ] . includes ( attr . name )
&& attributeService . isAffecting ( attr , this . note )
) ) {
2021-06-01 22:03:38 +02:00
this . refresh ( ) ;
2021-05-28 23:19:11 +02:00
}
}
}