2024-11-30 19:20:07 +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';
|
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: 10px;
|
2022-08-05 16:44:26 +02:00
|
|
|
left: 10px;
|
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
|
|
|
|
2024-11-30 19:20:07 +01:00
|
|
|
|
2022-08-05 16:44:26 +02:00
|
|
|
.map-type-switcher button.bx {
|
|
|
|
font-size: 130%;
|
|
|
|
padding: 1px 10px 1px 10px;
|
2021-06-02 21:23:40 +02:00
|
|
|
}
|
2024-11-30 19:20:07 +01:00
|
|
|
|
|
|
|
.fixnodes-type-switcher {
|
|
|
|
position: absolute;
|
|
|
|
top: 10px;
|
|
|
|
left: 45%;
|
|
|
|
z-index: 10; /* should be below dropdown (note actions) */
|
|
|
|
border-radius:0.2rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
input[type="range"] {
|
|
|
|
|
|
|
|
/* removing default appearance */
|
|
|
|
-webkit-appearance: none;
|
|
|
|
appearance: none;
|
|
|
|
margin-left: 15px;
|
|
|
|
width:50%
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Track: webkit browsers */
|
|
|
|
input[type="range"]::-webkit-slider-runnable-track {
|
|
|
|
height: 6px;
|
|
|
|
background: #ccc;
|
|
|
|
border-radius: 16px;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Thumb: webkit */
|
|
|
|
input[type="range"]::-webkit-slider-thumb {
|
|
|
|
/* removing default appearance */
|
|
|
|
-webkit-appearance: none;
|
|
|
|
appearance: none;
|
|
|
|
/* creating a custom design */
|
|
|
|
height: 15px;
|
|
|
|
width: 15px;
|
|
|
|
margin-top:-4px;
|
|
|
|
background-color: #661822;
|
|
|
|
border-radius: 50%;
|
|
|
|
|
2021-05-28 23:52:42 +02:00
|
|
|
</style>
|
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:20:07 +01:00
|
|
|
<button type="button" class="btn bx bx-network-chart" title="Link Map" data-type="link"></button>
|
|
|
|
<button type="button" class="btn bx bx-sitemap" title="Tree map" data-type="tree"></button>
|
|
|
|
</div>
|
|
|
|
<div class=" btn-group-sm fixnodes-type-switcher" role="group">
|
|
|
|
<button type="button" class="btn bx bx-expand" title="Fixation" data-type="moveable"></button>
|
|
|
|
<input type="range" class=" slider" min="1" title="Link distance" max="100" value="40" >
|
|
|
|
|
2021-09-22 21:11:36 +02:00
|
|
|
</div>
|
2021-06-27 12:53:05 +02:00
|
|
|
|
2021-09-22 22:25:39 +02:00
|
|
|
<div class="style-resolver"></div>
|
|
|
|
|
2021-09-22 21:11:36 +02:00
|
|
|
<div class="note-map-container"></div>
|
|
|
|
</div>`;
|
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:20:07 +01:00
|
|
|
this.fixNodes = false; //sets a variable to fix the nodes when dragged
|
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:20:07 +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:20:07 +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:20:07 +01:00
|
|
|
// 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
|
|
|
|
|
|
|
|
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;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
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:20:07 +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:20:07 +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:20:07 +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:20:07 +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:20:07 +01:00
|
|
|
//Variablen for hoverfeature
|
|
|
|
|
|
|
|
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])
|
2024-11-30 19:20:07 +01:00
|
|
|
|
2021-05-31 21:31:07 +02:00
|
|
|
.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;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
//saves the hovered node in a variable to paint it then yellow in the if clause of the .nodeCanvasObject function
|
|
|
|
.onNodeHover(node => {
|
|
|
|
hoverNode = node || null;
|
|
|
|
highlightLinks.clear();
|
|
|
|
})
|
|
|
|
// set link width to show connections on hover.
|
|
|
|
.linkWidth(link => (highlightLinks.has(link) ? 3 : 0.4))
|
|
|
|
.linkColor(link => (highlightLinks.has(link) ? 'white' : this.css.mutedTextColor))
|
|
|
|
//Code for painting the node when hovered
|
|
|
|
.nodeCanvasObject((node, ctx) => {
|
|
|
|
if (hoverNode == node) {
|
|
|
|
this.paintNode(node, '#661822', ctx);
|
|
|
|
neighbours.clear();
|
|
|
|
for (const link of data.links) {
|
|
|
|
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) {
|
|
|
|
this.paintNode(node, '#9d6363', ctx);
|
|
|
|
} else {
|
|
|
|
this.paintNode(node, this.getColorForNode(node), ctx);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.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)
|
2024-11-30 19:20:07 +01:00
|
|
|
.linkDirectionalArrowLength(4)
|
|
|
|
.linkDirectionalArrowRelPos(0.95)
|
|
|
|
|
|
|
|
//Julien Code Ende
|
|
|
|
|
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:20:07 +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:20:07 +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);
|
|
|
|
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:20:07 +01:00
|
|
|
let Distancevalue = 40; // Feature für liveänderungen in note_map wie link distance
|
|
|
|
|
|
|
|
this.$widget.find('.fixnodes-type-switcher input').on('change', async e => {
|
|
|
|
Distancevalue = e.target.closest('input').value;
|
|
|
|
this.graph.d3Force('link').distance(Distancevalue);
|
|
|
|
|
|
|
|
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);
|
2021-09-22 22:25:39 +02:00
|
|
|
this.renderData(data);
|
|
|
|
}
|
|
|
|
|
|
|
|
getMapRootNoteId() {
|
|
|
|
if (this.widgetMode === 'ribbon') {
|
|
|
|
return this.noteId;
|
|
|
|
}
|
|
|
|
|
2024-11-30 19:20:07 +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:20:07 +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:20:07 +01:00
|
|
|
const value = (hash >> (i * 8)) & 0xff;
|
2021-10-08 16:38:37 +02:00
|
|
|
|
2024-11-30 19:20:07 +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:20:07 +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:20:07 +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:20:07 +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:20:07 +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:20:07 +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:20:07 +01:00
|
|
|
ctx.font = `2px ${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:20:07 +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:20:07 +01:00
|
|
|
console.log(x);
|
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);
|
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:20:07 +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:20:07 +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:20:07 +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:20:07 +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:20:07 +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:20:07 +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);
|
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:20:07 +01:00
|
|
|
this.graph.zoomToFit(400, 50, node => subGraphNoteIds.has(node.id)); // zoomed immer doof, ggf ausklammern
|
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:20:07 +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:20:07 +01:00
|
|
|
this.graph.zoomToFit(400, 30, node => noteIdsWithLinks.has(node.id)); // zoomed immer doof, ggf ausklammern
|
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:20:07 +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:20:07 +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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|