@ -1,11 +1,12 @@
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 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" ;
const esc = utils . escapeHtml ;
@ -23,12 +24,12 @@ const TPL = `<div class="note-map-widget" style="position: relative;">
z - index : 10 ; /* should be below dropdown (note actions) */
}
. map - type - switcher button . bx {
font - size : 130 % ;
padding : 1 px 10 px 1 px 10 px ;
}
/* Style Ui Element to Drag Nodes */
. fixnodes - type - switcher {
position : absolute ;
top : 10 px ;
@ -37,6 +38,7 @@ const TPL = `<div class="note-map-widget" style="position: relative;">
border - radius : 0.2 rem ;
}
/* Start of styling the slider */
input [ type = "range" ] {
/* removing default appearance */
@ -49,7 +51,7 @@ const TPL = `<div class="note-map-widget" style="position: relative;">
/* Track: webkit browsers */
/* Changing slider tracker */
input [ type = "range" ] : : - webkit - slider - runnable - track {
height : 6 px ;
background : # ccc ;
@ -57,7 +59,7 @@ const TPL = `<div class="note-map-widget" style="position: relative;">
}
/* Thumb: webkit */
/* Changing Slider Thumb*/
input [ type = "range" ] : : - webkit - slider - thumb {
/* removing default appearance */
- webkit - appearance : none ;
@ -68,14 +70,19 @@ const TPL = `<div class="note-map-widget" style="position: relative;">
margin - top : - 4 px ;
background - color : # 661822 ;
border - radius : 50 % ;
/* End of styling the slider */
< / s t y l e >
< div class = "btn-group btn-group-sm map-type-switcher" role = "group" >
< button type = "button" class = "btn bx bx-network-chart" title = " Link Map " data - type = "link" > < / b u t t o n >
< button type = "button" class = "btn bx bx-sitemap" title = " Tree map " data - type = "tree" > < / b u t t o n >
< 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 >
< / d i v >
< div class = " btn-group-sm fixnodes-type-switcher" role = "group" >
< ! UI for dragging Notes and link force >
< div class = " btn-group-sm fixnodes-type-switcher" role = "group" >
< 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" >
@ -89,7 +96,7 @@ const TPL = `<div class="note-map-widget" style="position: relative;">
export default class NoteMapWidget extends NoteContextAwareWidget {
constructor ( widgetMode ) {
super ( ) ;
this . fixNodes = false ; // sets a variable to fix the nodes when dragged
this . fixNodes = false ; // needed to save the status of the UI element. Is set later in the code
this . widgetMode = widgetMode ; // 'type' or 'ribbon'
}
@ -99,65 +106,59 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
const documentStyle = window . getComputedStyle ( document . documentElement ) ;
this . themeStyle = documentStyle . getPropertyValue ( '--theme-style' ) ? . trim ( ) ;
this . $container = this . $widget . find ( '.note-map-container' ) ;
this . $container = this . $widget . find ( ".note-map-container" ) ;
this . $styleResolver = this . $widget . find ( '.style-resolver' ) ;
new ResizeObserver ( ( ) => this . setDimensions ( ) ) . observe ( this . $container [ 0 ] ) ;
this . $widget . find ( '.map-type-switcher button' ) . on ( 'click' , async e => {
const type = $ ( e . target ) . closest ( 'button' ) . attr ( 'data-type' ) ;
this . $widget . find ( ".map-type-switcher button" ) . on ( "click" , async e => {
const type = $ ( e . target ) . closest ( "button" ) . attr ( "data-type" ) ;
await attributeService . setLabel ( this . noteId , 'mapType' , type ) ;
} ) ;
// Code for the fix node after Dragging. Later in the script is more to fix the nodes in the canvas. This code here is to control the ui element
// Reading the status of the Drag nodes Ui element. Changing it´ s color when activated. Reading Force value of the link distance.
this . $widget . find ( '.fixnodes-type-switcher' ) . on ( 'click' , async event => {
this . fixNodes = ! this . fixNodes ;
console . log ( this . fixNodes ) ;
event . target . style . backgroundColor = this . fixNodes ? '#661822' : 'transparent' ;
let Distancevalue1 = 40 ;
this . $widget . find ( '.fixnodes-type-switcher input' ) . on ( 'change' , async e => {
Distancevalue1 = e . target . closest ( 'input' ) . value ;
return e . target . closest ( 'input' ) . value ;
} ) ;
} ) ;
super . doRender ( ) ;
}
setDimensions ( ) {
if ( ! this . graph ) {
// no graph has been even rendered
if ( ! this . graph ) { // no graph has been even rendered
return ;
}
const $parent = this . $widget . parent ( ) ;
this . graph . height ( $parent . height ( ) ) . width ( $parent . width ( ) ) ;
this . graph
. height ( $parent . height ( ) )
. width ( $parent . width ( ) ) ;
}
async refreshWithNote ( note ) {
this . $widget . show ( ) ;
this . css = {
fontFamily : this . $container . css ( 'font-family' ) ,
textColor : this . rgb2hex ( this . $container . css ( 'color' ) ) ,
mutedTextColor : this . rgb2hex ( this . $styleResolver . css ( 'color' ) )
fontFamily : this . $container . css ( "font-family" ) ,
textColor : this . rgb2hex ( this . $container . css ( "color" ) ) ,
mutedTextColor : this . rgb2hex ( this . $styleResolver . css ( "color" ) )
} ;
this . mapType = this . note . getLabelValue ( 'mapType' ) === 'tree' ? 'tree' : 'link' ;
this . mapType = this . note . getLabelValue ( "mapType" ) === "tree" ? "tree" : "link" ;
await libraryLoader . requireLibrary ( libraryLoader . FORCE _GRAPH ) ;
// Variablen for hoverfeature
// 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
let hoverNode = null ;
const highlightLinks = new Set ( ) ;
const neighbours = new Set ( ) ;
this . graph = ForceGraph ( ) ( this . $container [ 0 ] )
. width ( this . $container . width ( ) )
. height ( this . $container . height ( ) )
. onZoom ( zoom => this . setZoomLevel ( zoom . k ) )
@ -174,37 +175,39 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
node . fy = null ;
}
} )
// saves the hovered node in a variable to paint it then yellow in the if clause of the .nodeCanvasObject function
// 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
. onNodeHover ( node => {
hoverNode = node || null ;
highlightLinks . clear ( ) ;
} )
// set link width to show connections on hover.
// set link width to immitate a highlight effekt. Checking the condition if any links are saved in the previous defined set highlightlinks
. linkWidth ( link => ( highlightLinks . has ( link ) ? 3 : 0.4 ) )
. linkColor ( link => ( highlightLinks . has ( link ) ? 'white' : this . css . mutedTextColor ) )
//Code for painting the node when hovered
. 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.
. nodeCanvasObject ( ( node , ctx ) => {
if ( hoverNode == node ) {
if ( hoverNode == node ) { //paint only hovered node
this . paintNode ( node , '#661822' , ctx ) ;
neighbours . clear ( ) ;
for ( const link of data . links ) {
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
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 ) ;
console . log ( data ) ;
}
}
} else if ( neighbours . has ( node ) && hoverNode != null ) {
} else if ( neighbours . has ( node ) && hoverNode != null ) { //paint neighbours
this . paintNode ( node , '#9d6363' , ctx ) ;
} else {
this . paintNode ( node , this . getColorForNode ( node ) , ctx ) ;
this . paintNode ( node , this . getColorForNode ( node ) , ctx ) ; //paint rest of nodes in canvas
}
} )
. nodePointerAreaPaint ( ( node , ctx ) =>
this . paintNode ( node , this . getColorForNode ( node ) , ctx )
)
. nodePointerAreaPaint ( ( node , ctx ) => this . paintNode ( node , this . getColorForNode ( node ) , ctx ) )
. nodePointerAreaPaint ( ( node , color , ctx ) => {
ctx . fillStyle = color ;
ctx . beginPath ( ) ;
@ -214,37 +217,28 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
. nodeLabel ( node => esc ( node . name ) )
. maxZoom ( 7 )
. warmupTicks ( 30 )
. linkDirectionalArrowLength ( 4 )
. linkDirectionalArrowRelPos ( 0.95 )
//Julien Code Ende
. onNodeClick ( node => appContext . tabManager . getActiveContext ( ) . setNote ( node . id ) )
. onNodeRightClick ( ( node , e ) => linkContextMenuService . openContextMenu ( node . id , e ) ) ;
if ( this . mapType === 'link' ) {
this . graph
. linkLabel (
l =>
` ${ esc ( l . source . name ) } - <strong> ${ esc ( l . name ) } </strong> - ${ esc (
l . target . name
) } `
)
. linkLabel ( l => ` ${ esc ( l . source . name ) } - <strong> ${ esc ( l . name ) } </strong> - ${ esc ( l . target . name ) } ` )
. linkCanvasObject ( ( link , ctx ) => this . paintLink ( link , ctx ) )
. linkCanvasObjectMode ( ( ) => 'after' ) ;
. linkCanvasObjectMode ( ( ) => "after" ) ;
}
const mapRootNoteId = this . getMapRootNoteId ( ) ;
const data = await this . loadNotesAndRelations ( mapRootNoteId ) ;
const nodeLinkRatio = data . nodes . length / data . links . length ;
const magnifiedRatio = Math . pow ( nodeLinkRatio , 1.5 ) ;
const charge = - 20 / magnifiedRatio ;
const boundedCharge = Math . min ( - 3 , charge ) ;
let Distancevalue = 40 ; // Feature für liveänderungen in note_map wie link distance
let distancevalue = 40 ; // default value for the link force of the nodes
this . $widget . find ( '.fixnodes-type-switcher input' ) . on ( 'change' , async e => {
D istancevalue = e . target . closest ( 'input' ) . value ;
this . graph . d3Force ( 'link' ) . distance ( D istancevalue) ;
d istancevalue = e . target . closest ( 'input' ) . value ;
this . graph . d3Force ( 'link' ) . distance ( d istancevalue) ;
this . renderData ( data ) ;
} ) ;
@ -252,6 +246,7 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
this . graph . d3Force ( 'center' ) . strength ( 0.2 ) ;
this . graph . d3Force ( 'charge' ) . strength ( boundedCharge ) ;
this . graph . d3Force ( 'charge' ) . distanceMax ( 1000 ) ;
this . renderData ( data ) ;
}
@ -260,7 +255,7 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
return this . noteId ;
}
let mapRootNoteId = this . note . getLabelValue ( 'mapRootNoteId' ) ;
let mapRootNoteId = this . note . getLabelValue ( "mapRootNoteId" ) ;
if ( mapRootNoteId === 'hoisted' ) {
mapRootNoteId = hoistedNoteService . getHoistedNoteId ( ) ;
@ -282,7 +277,7 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
}
generateColorFromString ( str ) {
if ( this . themeStyle === 'dark' ) {
if ( this . themeStyle === "dark" ) {
str = ` 0 ${ str } ` ; // magic lightning modifier
}
@ -293,19 +288,18 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
let color = '#' ;
for ( let i = 0 ; i < 3 ; i ++ ) {
const value = ( hash >> ( i * 8 ) ) & 0x ff ;
const value = ( hash >> ( i * 8 ) ) & 0x FF ;
color += ` 00 ${ value . toString ( 16 ) } ` . substr ( - 2 ) ;
color += ( ` 00 ${ value . toString ( 16 ) } ` ) . substr ( - 2 ) ;
}
return color ;
}
rgb2hex ( rgb ) {
return ` # ${ rgb
. match ( /^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/ )
return ` # ${ rgb . match ( /^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/ )
. slice ( 1 )
. map ( n => parseInt ( n , 10 ) . toString ( 16 ) . padStart ( 2 , '0' ) )
. join ( '' ) } ` ;
. join ( '' ) } `
}
setZoomLevel ( level ) {
@ -313,18 +307,17 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
}
paintNode ( node , color , ctx ) {
const { x , y } = node ;
const { x , y } = node ;
const size = this . noteIdToSizeMap [ node . id ] ;
ctx . fillStyle = color ;
ctx . beginPath ( ) ;
ctx . arc ( x , y , size * 0.8 , 0 , 2 * Math . PI , false ) ;
ctx . arc ( x , y , size * 0.8 , 0 , 2 * Math . PI , false ) ;
ctx . fill ( ) ;
const toRender =
this . zoomLevel > 2 ||
( this . zoomLevel > 1 && size > 6 ) ||
( this . zoomLevel > 0.3 && size > 10 ) ;
const toRender = this . zoomLevel > 2
|| ( this . zoomLevel > 1 && size > 6 )
|| ( this . zoomLevel > 0.3 && size > 10 ) ;
if ( ! toRender ) {
return ;
@ -349,16 +342,16 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
return ;
}
ctx . font = ` 2 px ${ this . css . fontFamily } ` ;
ctx . font = ` 3 px ${ this . css . fontFamily } ` ;
ctx . textAlign = 'center' ;
ctx . textBaseline = 'middle' ;
ctx . fillStyle = this . css . mutedTextColor ;
const { source , target } = link ;
const { source , target } = link ;
const x = ( source . x + target . x ) / 2 ;
const y = ( source . y + target . y ) / 2 ;
console . log ( x ) ;
ctx . save ( ) ;
ctx . translate ( x , y ) ;
@ -384,6 +377,7 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
this . calculateNodeSizes ( resp ) ;
const links = this . getGroupedLinks ( resp . links ) ;
this . nodes = resp . notes . map ( ( [ noteId , title , type , color ] ) => ( {
id : noteId ,
name : title ,
@ -397,7 +391,7 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
id : ` ${ link . sourceNoteId } - ${ link . targetNoteId } ` ,
source : link . sourceNoteId ,
target : link . targetNoteId ,
name : link . names . join ( ', ' )
name : link . names . join ( ", " )
} ) )
} ;
}
@ -418,7 +412,7 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
sourceNoteId : link . sourceNoteId ,
targetNoteId : link . targetNoteId ,
names : [ link . name ]
} ;
}
}
}
@ -429,7 +423,7 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
this . noteIdToSizeMap = { } ;
if ( this . mapType === 'tree' ) {
const { noteIdToDescendantCountMap } = resp ;
const { noteIdToDescendantCountMap } = resp ;
for ( const noteId in noteIdToDescendantCountMap ) {
this . noteIdToSizeMap [ noteId ] = 4 ;
@ -440,22 +434,19 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
this . noteIdToSizeMap [ noteId ] += 1 + Math . round ( Math . log ( count ) / Math . log ( 1.5 ) ) ;
}
}
} else if ( this . mapType === 'link' ) {
}
else if ( this . mapType === 'link' ) {
const noteIdToLinkCount = { } ;
for ( const link of resp . links ) {
noteIdToLinkCount [ link . targetNoteId ] =
1 + ( noteIdToLinkCount [ link . targetNoteId ] || 0 ) ;
noteIdToLinkCount [ link . targetNoteId ] = 1 + ( noteIdToLinkCount [ link . targetNoteId ] || 0 ) ;
}
for ( const [ noteId ] of resp . notes ) {
this . noteIdToSizeMap [ noteId ] = 4 ;
if ( noteId in noteIdToLinkCount ) {
this . noteIdToSizeMap [ noteId ] += Math . min (
Math . pow ( noteIdToLinkCount [ noteId ] , 0.5 ) ,
15
) ;
this . noteIdToSizeMap [ noteId ] += Math . min ( Math . pow ( noteIdToLinkCount [ noteId ] , 0.5 ) , 15 ) ;
}
}
}
@ -463,19 +454,21 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
renderData ( data ) {
this . graph . graphData ( data ) ;
if ( this . widgetMode === 'ribbon' && this . note ? . type !== 'search' ) {
setTimeout ( ( ) => {
this . setDimensions ( ) ;
const subGraphNoteIds = this . getSubGraphConnectedToCurrentNote ( data ) ;
this . graph . zoomToFit ( 400 , 50 , node => subGraphNoteIds . has ( node . id ) ) ; // zoomed immer doof, ggf ausklammern
this . graph . zoomToFit ( 400 , 50 , node => subGraphNoteIds . has ( node . id ) ) ;
if ( subGraphNoteIds . size < 30 ) {
this . graph . d3VelocityDecay ( 0.4 ) ;
}
} , 1000 ) ;
} else {
}
else {
if ( data . nodes . length > 1 ) {
setTimeout ( ( ) => {
this . setDimensions ( ) ;
@ -483,7 +476,7 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
const noteIdsWithLinks = this . getNoteIdsWithLinks ( data ) ;
if ( noteIdsWithLinks . size > 0 ) {
this . graph . zoomToFit ( 400 , 30 , node => noteIdsWithLinks . has ( node . id ) ) ; // zoomed immer doof, ggf ausklammern
this . graph . zoomToFit ( 400 , 30 , node => noteIdsWithLinks . has ( node . id ) ) ;
}
if ( noteIdsWithLinks . size < 30 ) {
@ -518,8 +511,8 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
return map ;
}
const linksBySource = getGroupedLinks ( data . links , 'source' ) ;
const linksByTarget = getGroupedLinks ( data . links , 'target' ) ;
const linksBySource = getGroupedLinks ( data . links , "source" ) ;
const linksByTarget = getGroupedLinks ( data . links , "target" ) ;
const subGraphNoteIds = new Set ( ) ;
@ -547,17 +540,13 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
this . $container . html ( '' ) ;
}
entitiesReloadedEvent ( { loadResults } ) {
if (
loadResults
. getAttributeRows ( this . componentId )
. find (
attr =>
attr . type === 'label' &&
[ 'mapType' , 'mapRootNoteId' ] . includes ( attr . name ) &&
attributeService . isAffecting ( attr , this . note )
)
) {
entitiesReloadedEvent ( { loadResults } ) {
if ( loadResults . getAttributeRows ( this . componentId ) . find (
attr =>
attr . type === 'label'
&& [ 'mapType' , 'mapRootNoteId' ] . includes ( attr . name )
&& attributeService . isAffecting ( attr , this . note )
) ) {
this . refresh ( ) ;
}
}