Files
WorkNote/.obsidian/plugins/obsidian-webpage-export-master/assets/graph-render-worker.txt.js
2025-04-10 14:07:13 +08:00

418 lines
12 KiB
JavaScript

// Import Pixi.js library
if( 'function' === typeof importScripts)
{
importScripts('https://d157l7jdn8e5sf.cloudfront.net/v7.2.0/webworker.js', './tinycolor.js');
addEventListener('message', onMessage);
let app;
let container;
let graphics;
isDrawing = false;
let linkCount = 0;
let linkSources = [];
let linkTargets = [];
let nodeCount = 0;
let radii = [];
let labels = [];
let labelFade = [];
let labelWidths = [];
let pixiLabels = [];
let cameraOffset = {x: 0, y: 0};
let positions = new Float32Array(0);
let linkLength = 0;
let edgePruning = 0;
let colors =
{
background: 0x232323,
link: 0xAAAAAA,
node: 0xCCCCCC,
outline: 0xAAAAAA,
text: 0xFFFFFF,
accent: 0x4023AA
}
let hoveredNode = -1;
let lastHoveredNode = -1;
let grabbedNode = -1;
let updateAttached = false;
let attachedToGrabbed = [];
let activeNode = -1;
let attachedToActive = [];
let cameraScale = 1;
let cameraScaleRoot = 1;
function toScreenSpace(x, y, floor = true)
{
if (floor)
{
return {x: Math.floor((x * cameraScale) + cameraOffset.x), y: Math.floor((y * cameraScale) + cameraOffset.y)};
}
else
{
return {x: (x * cameraScale) + cameraOffset.x, y: (y * cameraScale) + cameraOffset.y};
}
}
function vecToScreenSpace({x, y}, floor = true)
{
return toScreenSpace(x, y, floor);
}
function toWorldspace(x, y)
{
return {x: (x - cameraOffset.x) / cameraScale, y: (y - cameraOffset.y) / cameraScale};
}
function vecToWorldspace({x, y})
{
return toWorldspace(x, y);
}
function setCameraCenterWorldspace({x, y})
{
cameraOffset.x = (canvas.width / 2) - (x * cameraScale);
cameraOffset.y = (canvas.height / 2) - (y * cameraScale);
}
function getCameraCenterWorldspace()
{
return toWorldspace(canvas.width / 2, canvas.height / 2);
}
function getNodeScreenRadius(radius)
{
return radius * cameraScaleRoot;
}
function getNodeWorldspaceRadius(radius)
{
return radius / cameraScaleRoot;
}
function getPosition(index)
{
return {x: positions[index * 2], y: positions[index * 2 + 1]};
}
function mixColors(hexStart, hexEnd, factor)
{
return tinycolor.mix(tinycolor(hexStart.toString(16)), tinycolor(hexEnd.toString(16)), factor).toHexNumber()
}
function darkenColor(hexColor, factor)
{
return tinycolor(hexColor.toString(16)).darken(factor).toHexNumber();
}
function lightenColor(hexColor, factor)
{
return tinycolor(hexColor.toString(16)).lighten(factor).toHexNumber();
}
function invertColor(hex, bw)
{
hex = hex.toString(16); // force conversion
// fill extra space up to 6 characters with 0
while (hex.length < 6) hex = "0" + hex;
if (hex.indexOf('#') === 0) {
hex = hex.slice(1);
}
// convert 3-digit hex to 6-digits.
if (hex.length === 3) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
if (hex.length !== 6) {
throw new Error('Invalid HEX color:' + hex);
}
var r = parseInt(hex.slice(0, 2), 16),
g = parseInt(hex.slice(2, 4), 16),
b = parseInt(hex.slice(4, 6), 16);
if (bw) {
// https://stackoverflow.com/a/3943023/112731
return (r * 0.299 + g * 0.587 + b * 0.114) > 186
? '#000000'
: '#FFFFFF';
}
// invert color components
r = (255 - r).toString(16);
g = (255 - g).toString(16);
b = (255 - b).toString(16);
// pad each with zeros and return
return "#" + padZero(r) + padZero(g) + padZero(b);
}
function clamp(value, min, max)
{
return Math.min(Math.max(value, min), max);
}
function lerp(a, b, t)
{
return a + (b - a) * t;
}
let hoverFade = 0;
let hoverFadeSpeed = 0.2;
let hoverFontSize = 15;
let normalFontSize = 12;
let fontRatio = hoverFontSize / normalFontSize;
function showLabel(index, fade, hovered = false)
{
let label = pixiLabels[index];
if (!label) return;
labelFade[index] = fade;
if(fade > 0.01) label.visible = true;
else
{
hideLabel(index);
return;
}
if (hovered) label.style.fontSize = hoverFontSize;
else label.style.fontSize = normalFontSize;
let nodePos = vecToScreenSpace(getPosition(index));
let width = (labelWidths[index] * (hovered ? fontRatio : 1)) / 2;
label.x = nodePos.x - width;
label.y = nodePos.y + getNodeScreenRadius(radii[index]) + 9;
label.alpha = fade;
}
function hideLabel(index)
{
let label = pixiLabels[index];
label.visible = false;
}
function draw()
{
graphics.clear();
let topLines = [];
if (updateAttached)
{
attachedToGrabbed = [];
// hoverFade = 0;
}
if (hoveredNode != -1 || grabbedNode != -1)
{
hoverFade = Math.min(1, hoverFade + hoverFadeSpeed);
}
else
{
hoverFade = Math.max(0, hoverFade - hoverFadeSpeed);
}
graphics.lineStyle(1, mixColors(colors.link, colors.background, hoverFade * 50), 0.7);
for (let i = 0; i < linkCount; i++)
{
let target = linkTargets[i];
let source = linkSources[i];
if (hoveredNode == source || hoveredNode == target || ((lastHoveredNode == source || lastHoveredNode == target) && hoverFade != 0))
{
if (updateAttached && hoveredNode == source)
attachedToGrabbed.push(target);
else if (updateAttached && hoveredNode == target)
attachedToGrabbed.push(source);
topLines.push(i);
}
let startWorld = getPosition(source);
let endWorld = getPosition(target);
let start = vecToScreenSpace(startWorld);
let end = vecToScreenSpace(endWorld);
let dist = Math.sqrt(Math.pow(startWorld.x - endWorld.x, 2) + Math.pow(startWorld.y - endWorld.y, 2));
if (dist < (radii[source] + radii[target]) * edgePruning)
{
graphics.moveTo(start.x, start.y);
graphics.lineTo(end.x, end.y);
}
}
let opacity = 1 - (hoverFade * 0.5);
graphics.beginFill(mixColors(colors.node, colors.background, hoverFade * 50), opacity);
graphics.lineStyle(0, 0xffffff);
for (let i = 0; i < nodeCount; i++)
{
let screenRadius = getNodeScreenRadius(radii[i]);
if (hoveredNode != i)
{
if (screenRadius > 2)
{
let labelFade = lerp(0, (screenRadius - 4) / 8 - (1/cameraScaleRoot)/6 * 0.9, Math.max(1 - hoverFade, 0.2));
showLabel(i, labelFade);
}
else
{
hideLabel(i);
}
}
if (hoveredNode == i || (lastHoveredNode == i && hoverFade != 0) || (hoveredNode != -1 && attachedToGrabbed.includes(i))) continue;
let pos = vecToScreenSpace(getPosition(i));
graphics.drawCircle(pos.x, pos.y, screenRadius);
}
graphics.endFill();
opacity = hoverFade * 0.7;
graphics.lineStyle(1, mixColors(mixColors(colors.link, colors.accent, hoverFade * 100), colors.background, 20), opacity);
for (let i = 0; i < topLines.length; i++)
{
let target = linkTargets[topLines[i]];
let source = linkSources[topLines[i]];
// draw lines on top when hovered
let start = vecToScreenSpace(getPosition(source));
let end = vecToScreenSpace(getPosition(target));
graphics.moveTo(start.x, start.y);
graphics.lineTo(end.x, end.y);
}
if(hoveredNode != -1 || (lastHoveredNode != -1 && hoverFade != 0))
{
graphics.beginFill(mixColors(colors.node, colors.accent, hoverFade * 20), 0.9);
graphics.lineStyle(0, 0xffffff);
for (let i = 0; i < attachedToGrabbed.length; i++)
{
let point = attachedToGrabbed[i];
let pos = vecToScreenSpace(getPosition(point));
graphics.drawCircle(pos.x, pos.y, getNodeScreenRadius(radii[point]));
showLabel(point, Math.max(hoverFade * 0.6, labelFade[point]));
}
graphics.endFill();
let index = hoveredNode != -1 ? hoveredNode : lastHoveredNode;
let pos = vecToScreenSpace(getPosition(index));
graphics.beginFill(mixColors(colors.node, colors.accent, hoverFade * 100), 1);
graphics.lineStyle(hoverFade, mixColors(invertColor(colors.background, true), colors.accent, 50));
graphics.drawCircle(pos.x, pos.y, getNodeScreenRadius(radii[index]));
graphics.endFill();
showLabel(index, Math.max(hoverFade, labelFade[index]), true);
}
updateAttached = false;
graphics.lineStyle(2, colors.accent);
// draw the active node
if (activeNode != -1)
{
let pos = vecToScreenSpace(getPosition(activeNode));
graphics.drawCircle(pos.x, pos.y, getNodeScreenRadius(radii[activeNode]) + 4);
}
}
function onMessage(event)
{
if(event.data.type == "draw")
{
positions = new Float32Array(event.data.positions);
draw();
}
else if(event.data.type == "update_camera")
{
cameraOffset = event.data.cameraOffset;
cameraScale = event.data.cameraScale;
cameraScaleRoot = Math.sqrt(cameraScale);
}
else if(event.data.type == "update_interaction")
{
if(hoveredNode != event.data.hoveredNode && event.data.hoveredNode != -1) updateAttached = true;
if(grabbedNode != event.data.grabbedNode && event.data.hoveredNode != -1) updateAttached = true;
if(event.data.hoveredNode == -1) lastHoveredNode = hoveredNode;
else lastHoveredNode = -1;
hoveredNode = event.data.hoveredNode;
grabbedNode = event.data.grabbedNode;
}
else if(event.data.type == "resize")
{
app.renderer.resize(event.data.width, event.data.height);
}
else if(event.data.type == "set_active")
{
activeNode = event.data.active;
}
else if(event.data.type == "update_colors")
{
colors = event.data.colors;
for (let label of pixiLabels)
{
label.style.fill = invertColor(colors.background, true);
}
}
else if(event.data.type == "init")
{
// Extract data from message
linkCount = event.data.linkCount;
linkSources = event.data.linkSources;
linkTargets = event.data.linkTargets;
nodeCount = event.data.nodeCount;
radii = event.data.radii;
labels = event.data.labels;
linkLength = event.data.linkLength;
edgePruning = event.data.edgePruning;
app = new PIXI.Application({... event.data.options, antialias: true, resolution: 2, backgroundAlpha: 0, transparent: true});
container = new PIXI.Container();
graphics = new PIXI.Graphics();
app.stage.addChild(container);
container.addChild(graphics);
pixiLabels = [];
for (let i = 0; i < nodeCount; i++)
{
let label = new PIXI.Text(labels[i], {fontFamily : 'Arial', fontSize: 12, fontWeight: "normal", fill : invertColor(colors.background, true), align : 'center', anchor: 0.5});
pixiLabels.push(label);
labelWidths.push(label.width);
labelFade.push(0);
app.stage.addChild(label);
}
}
else
{
console.log("Unknown message type sent to graph worker: " + event.data.type);
}
}
}