完成世界书、骰子、apiconfig页面处理
This commit is contained in:
105
frontend/node_modules/cytoscape/src/extensions/layout/circle.mjs
generated
vendored
Normal file
105
frontend/node_modules/cytoscape/src/extensions/layout/circle.mjs
generated
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
import * as util from '../../util/index.mjs';
|
||||
import * as math from '../../math.mjs';
|
||||
import * as is from '../../is.mjs';
|
||||
|
||||
let defaults = {
|
||||
fit: true, // whether to fit the viewport to the graph
|
||||
padding: 30, // the padding on fit
|
||||
boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h }
|
||||
avoidOverlap: true, // prevents node overlap, may overflow boundingBox and radius if not enough space
|
||||
nodeDimensionsIncludeLabels: false, // Excludes the label when calculating node bounding boxes for the layout algorithm
|
||||
spacingFactor: undefined, // Applies a multiplicative factor (>0) to expand or compress the overall area that the nodes take up
|
||||
radius: undefined, // the radius of the circle
|
||||
startAngle: 3 / 2 * Math.PI, // where nodes start in radians
|
||||
sweep: undefined, // how many radians should be between the first and last node (defaults to full circle)
|
||||
clockwise: true, // whether the layout should go clockwise (true) or counterclockwise/anticlockwise (false)
|
||||
sort: undefined, // a sorting function to order the nodes; e.g. function(a, b){ return a.data('weight') - b.data('weight') }
|
||||
animate: false, // whether to transition the node positions
|
||||
animationDuration: 500, // duration of animation in ms if enabled
|
||||
animationEasing: undefined, // easing of animation if enabled
|
||||
animateFilter: function ( node, i ){ return true; }, // a function that determines whether the node should be animated. All nodes animated by default on animate enabled. Non-animated nodes are positioned immediately when the layout starts
|
||||
ready: undefined, // callback on layoutready
|
||||
stop: undefined, // callback on layoutstop
|
||||
transform: function (node, position ){ return position; } // transform a given node position. Useful for changing flow direction in discrete layouts
|
||||
|
||||
};
|
||||
|
||||
function CircleLayout( options ){
|
||||
this.options = util.extend( {}, defaults, options );
|
||||
}
|
||||
|
||||
CircleLayout.prototype.run = function(){
|
||||
let params = this.options;
|
||||
let options = params;
|
||||
|
||||
let cy = params.cy;
|
||||
let eles = options.eles;
|
||||
|
||||
let clockwise = options.counterclockwise !== undefined ? !options.counterclockwise : options.clockwise;
|
||||
|
||||
let nodes = eles.nodes().not( ':parent' );
|
||||
|
||||
if( options.sort ){
|
||||
nodes = nodes.sort( options.sort );
|
||||
}
|
||||
|
||||
let bb = math.makeBoundingBox( options.boundingBox ? options.boundingBox : {
|
||||
x1: 0, y1: 0, w: cy.width(), h: cy.height()
|
||||
} );
|
||||
|
||||
let center = {
|
||||
x: bb.x1 + bb.w / 2,
|
||||
y: bb.y1 + bb.h / 2
|
||||
};
|
||||
|
||||
let sweep = options.sweep === undefined ? 2 * Math.PI - 2 * Math.PI / nodes.length : options.sweep;
|
||||
let dTheta = sweep / ( Math.max( 1, nodes.length - 1 ) );
|
||||
let r;
|
||||
|
||||
let minDistance = 0;
|
||||
for( let i = 0; i < nodes.length; i++ ){
|
||||
let n = nodes[ i ];
|
||||
let nbb = n.layoutDimensions( options );
|
||||
let w = nbb.w;
|
||||
let h = nbb.h;
|
||||
|
||||
minDistance = Math.max( minDistance, w, h );
|
||||
}
|
||||
|
||||
if( is.number( options.radius ) ){
|
||||
r = options.radius;
|
||||
} else if( nodes.length <= 1 ){
|
||||
r = 0;
|
||||
} else {
|
||||
r = Math.min( bb.h, bb.w ) / 2 - minDistance;
|
||||
}
|
||||
|
||||
// calculate the radius
|
||||
if( nodes.length > 1 && options.avoidOverlap ){ // but only if more than one node (can't overlap)
|
||||
minDistance *= 1.75; // just to have some nice spacing
|
||||
|
||||
let dcos = Math.cos( dTheta ) - Math.cos( 0 );
|
||||
let dsin = Math.sin( dTheta ) - Math.sin( 0 );
|
||||
let rMin = Math.sqrt( minDistance * minDistance / ( dcos * dcos + dsin * dsin ) ); // s.t. no nodes overlapping
|
||||
r = Math.max( rMin, r );
|
||||
}
|
||||
|
||||
let getPos = function( ele, i ){
|
||||
let theta = options.startAngle + i * dTheta * ( clockwise ? 1 : -1 );
|
||||
|
||||
let rx = r * Math.cos( theta );
|
||||
let ry = r * Math.sin( theta );
|
||||
let pos = {
|
||||
x: center.x + rx,
|
||||
y: center.y + ry
|
||||
};
|
||||
|
||||
return pos;
|
||||
};
|
||||
|
||||
eles.nodes().layoutPositions( this, options, getPos );
|
||||
|
||||
return this; // chaining
|
||||
};
|
||||
|
||||
export default CircleLayout;
|
||||
1340
frontend/node_modules/cytoscape/src/extensions/layout/cose.mjs
generated
vendored
Normal file
1340
frontend/node_modules/cytoscape/src/extensions/layout/cose.mjs
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
43
frontend/node_modules/cytoscape/src/extensions/layout/random.mjs
generated
vendored
Normal file
43
frontend/node_modules/cytoscape/src/extensions/layout/random.mjs
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
import * as util from '../../util/index.mjs';
|
||||
import * as math from '../../math.mjs';
|
||||
|
||||
let defaults = {
|
||||
fit: true, // whether to fit to viewport
|
||||
padding: 30, // fit padding
|
||||
boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h }
|
||||
animate: false, // whether to transition the node positions
|
||||
animationDuration: 500, // duration of animation in ms if enabled
|
||||
animationEasing: undefined, // easing of animation if enabled
|
||||
animateFilter: function ( node, i ){ return true; }, // a function that determines whether the node should be animated. All nodes animated by default on animate enabled. Non-animated nodes are positioned immediately when the layout starts
|
||||
ready: undefined, // callback on layoutready
|
||||
stop: undefined, // callback on layoutstop
|
||||
transform: function (node, position ){ return position; } // transform a given node position. Useful for changing flow direction in discrete layouts
|
||||
};
|
||||
|
||||
function RandomLayout( options ){
|
||||
this.options = util.extend( {}, defaults, options );
|
||||
}
|
||||
|
||||
RandomLayout.prototype.run = function(){
|
||||
let options = this.options;
|
||||
let cy = options.cy;
|
||||
let eles = options.eles;
|
||||
|
||||
|
||||
let bb = math.makeBoundingBox( options.boundingBox ? options.boundingBox : {
|
||||
x1: 0, y1: 0, w: cy.width(), h: cy.height()
|
||||
} );
|
||||
|
||||
let getPos = function( node, i ){
|
||||
return {
|
||||
x: bb.x1 + Math.round( Math.random() * bb.w ),
|
||||
y: bb.y1 + Math.round( Math.random() * bb.h )
|
||||
};
|
||||
};
|
||||
|
||||
eles.nodes().layoutPositions( this, options, getPos );
|
||||
|
||||
return this; // chaining
|
||||
};
|
||||
|
||||
export default RandomLayout;
|
||||
29
frontend/node_modules/cytoscape/src/extensions/renderer/base/coord-ele-math/index.mjs
generated
vendored
Normal file
29
frontend/node_modules/cytoscape/src/extensions/renderer/base/coord-ele-math/index.mjs
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
import * as util from '../../../../util/index.mjs';
|
||||
|
||||
import coords from './coords.mjs';
|
||||
import edgeArrows from './edge-arrows.mjs';
|
||||
import edgeControlPoints from './edge-control-points.mjs';
|
||||
import edgeEndpoints from './edge-endpoints.mjs';
|
||||
import edgeProjection from './edge-projection.mjs';
|
||||
import labels from './labels.mjs';
|
||||
import nodes from './nodes.mjs';
|
||||
import renderedStyle from './rendered-style.mjs';
|
||||
import zOrdering from './z-ordering.mjs';
|
||||
|
||||
var BRp = {};
|
||||
|
||||
[
|
||||
coords,
|
||||
edgeArrows,
|
||||
edgeControlPoints,
|
||||
edgeEndpoints,
|
||||
edgeProjection,
|
||||
labels,
|
||||
nodes,
|
||||
renderedStyle,
|
||||
zOrdering
|
||||
].forEach(function( props ){
|
||||
util.extend( BRp, props );
|
||||
});
|
||||
|
||||
export default BRp;
|
||||
38
frontend/node_modules/cytoscape/src/extensions/renderer/base/images.mjs
generated
vendored
Normal file
38
frontend/node_modules/cytoscape/src/extensions/renderer/base/images.mjs
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
var BRp = {};
|
||||
|
||||
BRp.getCachedImage = function( url, crossOrigin, onLoad ){
|
||||
var r = this;
|
||||
var imageCache = r.imageCache = r.imageCache || {};
|
||||
var cache = imageCache[ url ];
|
||||
|
||||
if( cache ){
|
||||
if( !cache.image.complete ){
|
||||
cache.image.addEventListener('load', onLoad);
|
||||
}
|
||||
|
||||
return cache.image;
|
||||
} else {
|
||||
cache = imageCache[ url ] = imageCache[ url ] || {};
|
||||
|
||||
var image = cache.image = new Image(); // eslint-disable-line no-undef
|
||||
|
||||
image.addEventListener('load', onLoad);
|
||||
image.addEventListener('error', function(){ image.error = true; });
|
||||
|
||||
// #1582 safari doesn't load data uris with crossOrigin properly
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=123978
|
||||
var dataUriPrefix = 'data:';
|
||||
var isDataUri = url.substring( 0, dataUriPrefix.length ).toLowerCase() === dataUriPrefix;
|
||||
if( !isDataUri ){
|
||||
// if crossorigin is 'null'(stringified), then manually set it to null
|
||||
crossOrigin = crossOrigin === 'null' ? null : crossOrigin;
|
||||
image.crossOrigin = crossOrigin; // prevent tainted canvas
|
||||
}
|
||||
|
||||
image.src = url;
|
||||
|
||||
return image;
|
||||
}
|
||||
};
|
||||
|
||||
export default BRp;
|
||||
224
frontend/node_modules/cytoscape/src/extensions/renderer/base/index.mjs
generated
vendored
Normal file
224
frontend/node_modules/cytoscape/src/extensions/renderer/base/index.mjs
generated
vendored
Normal file
@@ -0,0 +1,224 @@
|
||||
import * as util from '../../../util/index.mjs';
|
||||
import * as is from '../../../is.mjs';
|
||||
|
||||
import arrowShapes from './arrow-shapes.mjs';
|
||||
import coordEleMath from './coord-ele-math/index.mjs';
|
||||
import images from './images.mjs';
|
||||
import loadListeners from './load-listeners.mjs';
|
||||
import nodeShapes from './node-shapes.mjs';
|
||||
import redraw from './redraw.mjs';
|
||||
|
||||
var BaseRenderer = function( options ){ this.init( options ); };
|
||||
var BR = BaseRenderer;
|
||||
var BRp = BR.prototype;
|
||||
|
||||
BRp.clientFunctions = [ 'redrawHint', 'render', 'renderTo', 'matchCanvasSize', 'nodeShapeImpl', 'arrowShapeImpl' ];
|
||||
|
||||
BRp.init = function( options ){
|
||||
var r = this;
|
||||
|
||||
r.options = options;
|
||||
|
||||
r.cy = options.cy;
|
||||
|
||||
var ctr = r.container = options.cy.container();
|
||||
var containerWindow = r.cy.window();
|
||||
|
||||
|
||||
// prepend a stylesheet in the head such that
|
||||
if( containerWindow ){
|
||||
var document = containerWindow.document;
|
||||
var head = document.head;
|
||||
var stylesheetId = '__________cytoscape_stylesheet';
|
||||
var className = '__________cytoscape_container';
|
||||
var stylesheetAlreadyExists = document.getElementById( stylesheetId ) != null;
|
||||
|
||||
if( ctr.className.indexOf( className ) < 0 ){
|
||||
ctr.className = ( ctr.className || '' ) + ' ' + className;
|
||||
}
|
||||
|
||||
if( !stylesheetAlreadyExists ){
|
||||
var stylesheet = document.createElement('style');
|
||||
|
||||
stylesheet.id = stylesheetId;
|
||||
stylesheet.textContent = '.'+className+' { position: relative; }';
|
||||
|
||||
head.insertBefore( stylesheet, head.children[0] ); // first so lowest priority
|
||||
}
|
||||
|
||||
var computedStyle = containerWindow.getComputedStyle( ctr );
|
||||
var position = computedStyle.getPropertyValue('position');
|
||||
|
||||
if( position === 'static' ){
|
||||
util.warn('A Cytoscape container has style position:static and so can not use UI extensions properly');
|
||||
}
|
||||
}
|
||||
|
||||
r.selection = [ undefined, undefined, undefined, undefined, 0]; // Coordinates for selection box, plus enabled flag
|
||||
|
||||
r.bezierProjPcts = [ 0.05, 0.225, 0.4, 0.5, 0.6, 0.775, 0.95 ];
|
||||
|
||||
//--Pointer-related data
|
||||
r.hoverData = {down: null, last: null,
|
||||
downTime: null, triggerMode: null,
|
||||
dragging: false,
|
||||
initialPan: [ null, null ], capture: false};
|
||||
|
||||
r.dragData = {possibleDragElements: []};
|
||||
|
||||
r.touchData = {
|
||||
start: null, capture: false,
|
||||
|
||||
// These 3 fields related to tap, taphold events
|
||||
startPosition: [ null, null, null, null, null, null ],
|
||||
singleTouchStartTime: null,
|
||||
singleTouchMoved: true,
|
||||
|
||||
now: [ null, null, null, null, null, null ],
|
||||
earlier: [ null, null, null, null, null, null ]
|
||||
};
|
||||
|
||||
r.redraws = 0;
|
||||
r.showFps = options.showFps;
|
||||
r.debug = options.debug;
|
||||
r.webgl = options.webgl;
|
||||
|
||||
r.hideEdgesOnViewport = options.hideEdgesOnViewport;
|
||||
r.textureOnViewport = options.textureOnViewport;
|
||||
r.wheelSensitivity = options.wheelSensitivity;
|
||||
r.motionBlurEnabled = options.motionBlur; // on by default
|
||||
r.forcedPixelRatio = is.number(options.pixelRatio) ? options.pixelRatio : null;
|
||||
r.motionBlur = options.motionBlur; // for initial kick off
|
||||
r.motionBlurOpacity = options.motionBlurOpacity;
|
||||
r.motionBlurTransparency = 1 - r.motionBlurOpacity;
|
||||
r.motionBlurPxRatio = 1;
|
||||
r.mbPxRBlurry = 1; //0.8;
|
||||
r.minMbLowQualFrames = 4;
|
||||
r.fullQualityMb = false;
|
||||
r.clearedForMotionBlur = [];
|
||||
r.desktopTapThreshold = options.desktopTapThreshold;
|
||||
r.desktopTapThreshold2 = options.desktopTapThreshold * options.desktopTapThreshold;
|
||||
r.touchTapThreshold = options.touchTapThreshold;
|
||||
r.touchTapThreshold2 = options.touchTapThreshold * options.touchTapThreshold;
|
||||
r.tapholdDuration = 500;
|
||||
|
||||
r.bindings = [];
|
||||
r.beforeRenderCallbacks = [];
|
||||
r.beforeRenderPriorities = { // higher priority execs before lower one
|
||||
animations: 400,
|
||||
eleCalcs: 300,
|
||||
eleTxrDeq: 200,
|
||||
lyrTxrDeq: 150,
|
||||
lyrTxrSkip: 100,
|
||||
};
|
||||
|
||||
r.registerNodeShapes();
|
||||
r.registerArrowShapes();
|
||||
r.registerCalculationListeners();
|
||||
};
|
||||
|
||||
BRp.notify = function( eventName, eles ) {
|
||||
var r = this;
|
||||
var cy = r.cy;
|
||||
|
||||
// the renderer can't be notified after it's destroyed
|
||||
if( this.destroyed ){ return; }
|
||||
|
||||
if( eventName === 'init' ){
|
||||
r.load();
|
||||
return;
|
||||
}
|
||||
|
||||
if( eventName === 'destroy' ){
|
||||
r.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
if(
|
||||
eventName === 'add'
|
||||
|| eventName === 'remove'
|
||||
|| (eventName === 'move' && cy.hasCompoundNodes())
|
||||
|| eventName === 'load'
|
||||
|| eventName === 'zorder'
|
||||
|| eventName === 'mount'
|
||||
){
|
||||
r.invalidateCachedZSortedEles();
|
||||
}
|
||||
|
||||
if( eventName === 'viewport' ){
|
||||
r.redrawHint( 'select', true );
|
||||
}
|
||||
|
||||
if( eventName === 'gc' ){
|
||||
r.redrawHint( 'gc', true );
|
||||
}
|
||||
|
||||
if( eventName === 'load' || eventName === 'resize' || eventName === 'mount' ){
|
||||
r.invalidateContainerClientCoordsCache();
|
||||
r.matchCanvasSize( r.container );
|
||||
}
|
||||
|
||||
r.redrawHint( 'eles', true );
|
||||
r.redrawHint( 'drag', true );
|
||||
|
||||
this.startRenderLoop();
|
||||
|
||||
this.redraw();
|
||||
};
|
||||
|
||||
BRp.destroy = function(){
|
||||
var r = this;
|
||||
|
||||
r.destroyed = true;
|
||||
|
||||
r.cy.stopAnimationLoop();
|
||||
|
||||
for( var i = 0; i < r.bindings.length; i++ ){
|
||||
var binding = r.bindings[ i ];
|
||||
var b = binding;
|
||||
var tgt = b.target;
|
||||
|
||||
( tgt.off || tgt.removeEventListener ).apply( tgt, b.args );
|
||||
}
|
||||
|
||||
r.bindings = [];
|
||||
r.beforeRenderCallbacks = [];
|
||||
r.onUpdateEleCalcsFns = [];
|
||||
|
||||
if( r.removeObserver ){
|
||||
r.removeObserver.disconnect();
|
||||
}
|
||||
|
||||
if( r.styleObserver ){
|
||||
r.styleObserver.disconnect();
|
||||
}
|
||||
|
||||
if( r.resizeObserver ){
|
||||
r.resizeObserver.disconnect();
|
||||
}
|
||||
|
||||
if( r.labelCalcDiv ){
|
||||
try {
|
||||
document.body.removeChild( r.labelCalcDiv ); // eslint-disable-line no-undef
|
||||
} catch( e ){
|
||||
// ie10 issue #1014
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BRp.isHeadless = function(){
|
||||
return false;
|
||||
};
|
||||
|
||||
[
|
||||
arrowShapes,
|
||||
coordEleMath,
|
||||
images,
|
||||
loadListeners,
|
||||
nodeShapes,
|
||||
redraw
|
||||
].forEach( function( props ){
|
||||
util.extend( BRp, props );
|
||||
} );
|
||||
|
||||
export default BR;
|
||||
431
frontend/node_modules/cytoscape/src/extensions/renderer/canvas/drawing-edges.mjs
generated
vendored
Normal file
431
frontend/node_modules/cytoscape/src/extensions/renderer/canvas/drawing-edges.mjs
generated
vendored
Normal file
@@ -0,0 +1,431 @@
|
||||
/* global Path2D */
|
||||
|
||||
import * as util from '../../../util/index.mjs';
|
||||
import {drawPreparedRoundCorner} from "../../../round.mjs";
|
||||
|
||||
let CRp = {};
|
||||
|
||||
CRp.drawEdge = function( context, edge, shiftToOriginWithBb, drawLabel = true, shouldDrawOverlay = true, shouldDrawOpacity = true ){
|
||||
let r = this;
|
||||
let rs = edge._private.rscratch;
|
||||
|
||||
if( shouldDrawOpacity && !edge.visible() ){ return; }
|
||||
|
||||
// if bezier ctrl pts can not be calculated, then die
|
||||
if( rs.badLine || rs.allpts == null || isNaN(rs.allpts[0]) ){ // isNaN in case edge is impossible and browser bugs (e.g. safari)
|
||||
return;
|
||||
}
|
||||
|
||||
let bb;
|
||||
if( shiftToOriginWithBb ){
|
||||
bb = shiftToOriginWithBb;
|
||||
|
||||
context.translate( -bb.x1, -bb.y1 );
|
||||
}
|
||||
|
||||
let opacity = shouldDrawOpacity ? edge.pstyle('opacity').value : 1;
|
||||
let lineOpacity = shouldDrawOpacity ? edge.pstyle('line-opacity').value : 1;
|
||||
|
||||
let curveStyle = edge.pstyle('curve-style').value;
|
||||
let lineStyle = edge.pstyle('line-style').value;
|
||||
let edgeWidth = edge.pstyle('width').pfValue;
|
||||
let lineCap = edge.pstyle('line-cap').value;
|
||||
let lineOutlineWidth = edge.pstyle('line-outline-width').value;
|
||||
let lineOutlineColor = edge.pstyle('line-outline-color').value;
|
||||
|
||||
let effectiveLineOpacity = opacity * lineOpacity;
|
||||
// separate arrow opacity would require arrow-opacity property
|
||||
let effectiveArrowOpacity = opacity * lineOpacity;
|
||||
|
||||
let drawLine = ( strokeOpacity = effectiveLineOpacity) => {
|
||||
if (curveStyle === 'straight-triangle') {
|
||||
r.eleStrokeStyle( context, edge, strokeOpacity );
|
||||
r.drawEdgeTrianglePath(
|
||||
edge,
|
||||
context,
|
||||
rs.allpts
|
||||
);
|
||||
} else {
|
||||
context.lineWidth = edgeWidth;
|
||||
context.lineCap = lineCap;
|
||||
|
||||
r.eleStrokeStyle( context, edge, strokeOpacity );
|
||||
r.drawEdgePath(
|
||||
edge,
|
||||
context,
|
||||
rs.allpts,
|
||||
lineStyle
|
||||
);
|
||||
|
||||
context.lineCap = 'butt'; // reset for other drawing functions
|
||||
}
|
||||
};
|
||||
|
||||
let drawLineOutline = ( strokeOpacity = effectiveLineOpacity) => {
|
||||
context.lineWidth = edgeWidth + lineOutlineWidth;
|
||||
context.lineCap = lineCap;
|
||||
|
||||
if (lineOutlineWidth > 0) {
|
||||
r.colorStrokeStyle( context, lineOutlineColor[0], lineOutlineColor[1], lineOutlineColor[2], strokeOpacity );
|
||||
} else {
|
||||
// do not draw any lineOutline
|
||||
context.lineCap = 'butt'; // reset for other drawing functions
|
||||
return;
|
||||
}
|
||||
|
||||
if (curveStyle === 'straight-triangle') {
|
||||
r.drawEdgeTrianglePath(
|
||||
edge,
|
||||
context,
|
||||
rs.allpts
|
||||
);
|
||||
} else {
|
||||
r.drawEdgePath(
|
||||
edge,
|
||||
context,
|
||||
rs.allpts,
|
||||
lineStyle
|
||||
);
|
||||
|
||||
context.lineCap = 'butt'; // reset for other drawing functions
|
||||
}
|
||||
};
|
||||
|
||||
let drawOverlay = () => {
|
||||
if( !shouldDrawOverlay ){ return; }
|
||||
|
||||
r.drawEdgeOverlay( context, edge );
|
||||
};
|
||||
|
||||
let drawUnderlay = () => {
|
||||
if( !shouldDrawOverlay ){ return; }
|
||||
|
||||
r.drawEdgeUnderlay( context, edge );
|
||||
};
|
||||
|
||||
let drawArrows = ( arrowOpacity = effectiveArrowOpacity) => {
|
||||
r.drawArrowheads( context, edge, arrowOpacity );
|
||||
};
|
||||
|
||||
let drawText = () => {
|
||||
r.drawElementText( context, edge, null, drawLabel );
|
||||
};
|
||||
|
||||
context.lineJoin = 'round';
|
||||
|
||||
let ghost = edge.pstyle('ghost').value === 'yes';
|
||||
|
||||
if( ghost ){
|
||||
let gx = edge.pstyle('ghost-offset-x').pfValue;
|
||||
let gy = edge.pstyle('ghost-offset-y').pfValue;
|
||||
let ghostOpacity = edge.pstyle('ghost-opacity').value;
|
||||
let effectiveGhostOpacity = effectiveLineOpacity * ghostOpacity;
|
||||
|
||||
context.translate( gx, gy );
|
||||
|
||||
drawLine( effectiveGhostOpacity );
|
||||
drawArrows( effectiveGhostOpacity );
|
||||
|
||||
context.translate( -gx, -gy );
|
||||
} else {
|
||||
drawLineOutline();
|
||||
}
|
||||
|
||||
drawUnderlay();
|
||||
drawLine();
|
||||
drawArrows();
|
||||
drawOverlay();
|
||||
drawText();
|
||||
|
||||
if( shiftToOriginWithBb ){
|
||||
context.translate( bb.x1, bb.y1 );
|
||||
}
|
||||
};
|
||||
|
||||
const drawEdgeOverlayUnderlay = function( overlayOrUnderlay ) {
|
||||
if (!['overlay', 'underlay'].includes(overlayOrUnderlay)) {
|
||||
throw new Error('Invalid state');
|
||||
}
|
||||
|
||||
return function( context, edge ){
|
||||
if( !edge.visible() ){ return; }
|
||||
|
||||
let opacity = edge.pstyle(`${overlayOrUnderlay}-opacity`).value;
|
||||
|
||||
if( opacity === 0 ){ return; }
|
||||
|
||||
let r = this;
|
||||
let usePaths = r.usePaths();
|
||||
let rs = edge._private.rscratch;
|
||||
|
||||
let padding = edge.pstyle(`${overlayOrUnderlay}-padding`).pfValue;
|
||||
let width = 2 * padding;
|
||||
let color = edge.pstyle(`${overlayOrUnderlay}-color`).value;
|
||||
|
||||
context.lineWidth = width;
|
||||
|
||||
if( rs.edgeType === 'self' && !usePaths ){
|
||||
context.lineCap = 'butt';
|
||||
} else {
|
||||
context.lineCap = 'round';
|
||||
}
|
||||
|
||||
r.colorStrokeStyle( context, color[0], color[1], color[2], opacity );
|
||||
|
||||
r.drawEdgePath(
|
||||
edge,
|
||||
context,
|
||||
rs.allpts,
|
||||
'solid'
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
CRp.drawEdgeOverlay = drawEdgeOverlayUnderlay('overlay');
|
||||
|
||||
CRp.drawEdgeUnderlay = drawEdgeOverlayUnderlay('underlay');
|
||||
|
||||
|
||||
CRp.drawEdgePath = function( edge, context, pts, type ){
|
||||
let rs = edge._private.rscratch;
|
||||
let canvasCxt = context;
|
||||
let path;
|
||||
let pathCacheHit = false;
|
||||
let usePaths = this.usePaths();
|
||||
let lineDashPattern = edge.pstyle('line-dash-pattern').pfValue;
|
||||
let lineDashOffset = edge.pstyle('line-dash-offset').pfValue;
|
||||
|
||||
if( usePaths ){
|
||||
let pathCacheKey = pts.join( '$' );
|
||||
let keyMatches = rs.pathCacheKey && rs.pathCacheKey === pathCacheKey;
|
||||
|
||||
if( keyMatches ){
|
||||
path = context = rs.pathCache;
|
||||
pathCacheHit = true;
|
||||
} else {
|
||||
path = context = new Path2D();
|
||||
rs.pathCacheKey = pathCacheKey;
|
||||
rs.pathCache = path;
|
||||
}
|
||||
}
|
||||
|
||||
if( canvasCxt.setLineDash ){ // for very outofdate browsers
|
||||
switch( type ){
|
||||
case 'dotted':
|
||||
canvasCxt.setLineDash( [ 1, 1 ] );
|
||||
break;
|
||||
|
||||
case 'dashed':
|
||||
canvasCxt.setLineDash( lineDashPattern );
|
||||
canvasCxt.lineDashOffset = lineDashOffset;
|
||||
break;
|
||||
|
||||
case 'solid':
|
||||
canvasCxt.setLineDash( [ ] );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( !pathCacheHit && !rs.badLine ){
|
||||
if( context.beginPath ){ context.beginPath(); }
|
||||
context.moveTo( pts[0], pts[1] );
|
||||
|
||||
switch( rs.edgeType ){
|
||||
case 'bezier':
|
||||
case 'self':
|
||||
case 'compound':
|
||||
case 'multibezier':
|
||||
for( let i = 2; i + 3 < pts.length; i += 4 ){
|
||||
context.quadraticCurveTo( pts[ i ], pts[ i + 1], pts[ i + 2], pts[ i + 3] );
|
||||
}
|
||||
break;
|
||||
|
||||
case 'straight':
|
||||
case 'haystack':
|
||||
for( let i = 2; i + 1 < pts.length; i += 2 ) {
|
||||
context.lineTo( pts[ i ], pts[ i + 1] );
|
||||
}
|
||||
break;
|
||||
case 'segments':
|
||||
if (rs.isRound) {
|
||||
for( let corner of rs.roundCorners ){
|
||||
drawPreparedRoundCorner(context, corner);
|
||||
}
|
||||
context.lineTo( pts[ pts.length - 2 ], pts[ pts.length - 1] );
|
||||
} else {
|
||||
for( let i = 2; i + 1 < pts.length; i += 2 ) {
|
||||
context.lineTo( pts[ i ], pts[ i + 1] );
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
context = canvasCxt;
|
||||
if( usePaths ){
|
||||
context.stroke( path );
|
||||
} else {
|
||||
context.stroke();
|
||||
}
|
||||
|
||||
// reset any line dashes
|
||||
if( context.setLineDash ){ // for very outofdate browsers
|
||||
context.setLineDash( [ ] );
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
CRp.drawEdgeTrianglePath = function( edge, context, pts ){
|
||||
// use line stroke style for triangle fill style
|
||||
context.fillStyle = context.strokeStyle;
|
||||
|
||||
let edgeWidth = edge.pstyle('width').pfValue;
|
||||
|
||||
for( let i = 0; i + 1 < pts.length; i += 2 ){
|
||||
const vector = [ pts[ i + 2 ] - pts[ i ], pts[ i + 3 ] - pts[ i + 1 ] ];
|
||||
const length = Math.sqrt( vector[0] * vector[0] + vector[1] * vector[1] );
|
||||
const normal = [ vector[1] / length, -vector[0] / length ];
|
||||
const triangleHead = [ normal[0] * edgeWidth / 2, normal[1] * edgeWidth / 2 ];
|
||||
|
||||
context.beginPath();
|
||||
context.moveTo( pts[ i ] - triangleHead[0], pts[ i + 1 ] - triangleHead[1] );
|
||||
context.lineTo( pts[ i ] + triangleHead[0], pts[ i + 1 ] + triangleHead[1] );
|
||||
context.lineTo( pts[ i + 2 ], pts[ i + 3 ] );
|
||||
context.closePath();
|
||||
context.fill();
|
||||
}
|
||||
};
|
||||
|
||||
CRp.drawArrowheads = function( context, edge, opacity ){
|
||||
let rs = edge._private.rscratch;
|
||||
let isHaystack = rs.edgeType === 'haystack';
|
||||
|
||||
if( !isHaystack ){
|
||||
this.drawArrowhead( context, edge, 'source', rs.arrowStartX, rs.arrowStartY, rs.srcArrowAngle, opacity );
|
||||
}
|
||||
|
||||
this.drawArrowhead( context, edge, 'mid-target', rs.midX, rs.midY, rs.midtgtArrowAngle, opacity );
|
||||
|
||||
this.drawArrowhead( context, edge, 'mid-source', rs.midX, rs.midY, rs.midsrcArrowAngle, opacity );
|
||||
|
||||
if( !isHaystack ){
|
||||
this.drawArrowhead( context, edge, 'target', rs.arrowEndX, rs.arrowEndY, rs.tgtArrowAngle, opacity );
|
||||
}
|
||||
};
|
||||
|
||||
CRp.drawArrowhead = function( context, edge, prefix, x, y, angle, opacity ){
|
||||
if( isNaN( x ) || x == null || isNaN( y ) || y == null || isNaN( angle ) || angle == null ){ return; }
|
||||
|
||||
let self = this;
|
||||
let arrowShape = edge.pstyle( prefix + '-arrow-shape' ).value;
|
||||
if( arrowShape === 'none' ) { return; }
|
||||
|
||||
let arrowClearFill = edge.pstyle( prefix + '-arrow-fill' ).value === 'hollow' ? 'both' : 'filled';
|
||||
let arrowFill = edge.pstyle( prefix + '-arrow-fill' ).value;
|
||||
let edgeWidth = edge.pstyle( 'width' ).pfValue;
|
||||
|
||||
let pArrowWidth = edge.pstyle( prefix + '-arrow-width' );
|
||||
let arrowWidth = pArrowWidth.value === 'match-line' ? edgeWidth : pArrowWidth.pfValue;
|
||||
if (pArrowWidth.units === '%') arrowWidth *= edgeWidth;
|
||||
|
||||
let edgeOpacity = edge.pstyle( 'opacity' ).value;
|
||||
|
||||
if( opacity === undefined ){
|
||||
opacity = edgeOpacity;
|
||||
}
|
||||
|
||||
let gco = context.globalCompositeOperation;
|
||||
|
||||
if( opacity !== 1 || arrowFill === 'hollow' ){ // then extra clear is needed
|
||||
context.globalCompositeOperation = 'destination-out';
|
||||
|
||||
self.colorFillStyle( context, 255, 255, 255, 1 );
|
||||
self.colorStrokeStyle( context, 255, 255, 255, 1 );
|
||||
|
||||
self.drawArrowShape( edge, context,
|
||||
arrowClearFill, edgeWidth, arrowShape, arrowWidth, x, y, angle
|
||||
);
|
||||
|
||||
context.globalCompositeOperation = gco;
|
||||
} // otherwise, the opaque arrow clears it for free :)
|
||||
|
||||
let color = edge.pstyle( prefix + '-arrow-color' ).value;
|
||||
self.colorFillStyle( context, color[0], color[1], color[2], opacity );
|
||||
self.colorStrokeStyle( context, color[0], color[1], color[2], opacity );
|
||||
|
||||
self.drawArrowShape( edge, context,
|
||||
arrowFill, edgeWidth, arrowShape, arrowWidth, x, y, angle
|
||||
);
|
||||
};
|
||||
|
||||
CRp.drawArrowShape = function( edge, context, fill, edgeWidth, shape, shapeWidth, x, y, angle ){
|
||||
let r = this;
|
||||
let usePaths = this.usePaths() && shape !== 'triangle-cross';
|
||||
let pathCacheHit = false;
|
||||
let path;
|
||||
let canvasContext = context;
|
||||
let translation = { x, y };
|
||||
let scale = edge.pstyle( 'arrow-scale' ).value;
|
||||
let size = this.getArrowWidth( edgeWidth, scale );
|
||||
let shapeImpl = r.arrowShapes[ shape ];
|
||||
|
||||
if( usePaths ){
|
||||
let cache = r.arrowPathCache = r.arrowPathCache || [];
|
||||
let key = util.hashString(shape);
|
||||
let cachedPath = cache[ key ];
|
||||
|
||||
if( cachedPath != null ){
|
||||
path = context = cachedPath;
|
||||
pathCacheHit = true;
|
||||
} else {
|
||||
path = context = new Path2D();
|
||||
cache[ key ] = path;
|
||||
}
|
||||
}
|
||||
|
||||
if( !pathCacheHit ){
|
||||
if( context.beginPath ){ context.beginPath(); }
|
||||
if( usePaths ){ // store in the path cache with values easily manipulated later
|
||||
shapeImpl.draw( context, 1, 0, { x: 0, y: 0 }, 1 );
|
||||
} else {
|
||||
shapeImpl.draw( context, size, angle, translation, edgeWidth );
|
||||
}
|
||||
if( context.closePath ){ context.closePath(); }
|
||||
}
|
||||
|
||||
context = canvasContext;
|
||||
|
||||
if( usePaths ){ // set transform to arrow position/orientation
|
||||
context.translate( x, y );
|
||||
context.rotate( angle );
|
||||
context.scale( size, size );
|
||||
}
|
||||
|
||||
if( fill === 'filled' || fill === 'both' ){
|
||||
if( usePaths ){
|
||||
context.fill( path );
|
||||
} else {
|
||||
context.fill();
|
||||
}
|
||||
}
|
||||
|
||||
if( fill === 'hollow' || fill === 'both' ){
|
||||
context.lineWidth = shapeWidth / (usePaths ? size : 1);
|
||||
context.lineJoin = 'miter';
|
||||
|
||||
if( usePaths ){
|
||||
context.stroke( path );
|
||||
} else {
|
||||
context.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
if( usePaths ){ // reset transform by applying inverse
|
||||
context.scale( 1/size, 1/size );
|
||||
context.rotate( -angle );
|
||||
context.translate( -x, -y );
|
||||
}
|
||||
};
|
||||
|
||||
export default CRp;
|
||||
218
frontend/node_modules/cytoscape/src/extensions/renderer/canvas/drawing-elements.mjs
generated
vendored
Normal file
218
frontend/node_modules/cytoscape/src/extensions/renderer/canvas/drawing-elements.mjs
generated
vendored
Normal file
@@ -0,0 +1,218 @@
|
||||
import * as math from '../../../math.mjs';
|
||||
|
||||
let CRp = {};
|
||||
|
||||
CRp.drawElement = function( context, ele, shiftToOriginWithBb, showLabel, showOverlay, showOpacity ){
|
||||
let r = this;
|
||||
|
||||
if( ele.isNode() ){
|
||||
r.drawNode( context, ele, shiftToOriginWithBb, showLabel, showOverlay, showOpacity );
|
||||
} else {
|
||||
r.drawEdge( context, ele, shiftToOriginWithBb, showLabel, showOverlay, showOpacity );
|
||||
}
|
||||
};
|
||||
|
||||
CRp.drawElementOverlay = function( context, ele ){
|
||||
let r = this;
|
||||
|
||||
if( ele.isNode() ){
|
||||
r.drawNodeOverlay( context, ele );
|
||||
} else {
|
||||
r.drawEdgeOverlay( context, ele );
|
||||
}
|
||||
};
|
||||
|
||||
CRp.drawElementUnderlay = function( context, ele ){
|
||||
let r = this;
|
||||
|
||||
if( ele.isNode() ){
|
||||
r.drawNodeUnderlay( context, ele );
|
||||
} else {
|
||||
r.drawEdgeUnderlay( context, ele );
|
||||
}
|
||||
};
|
||||
|
||||
CRp.drawCachedElementPortion = function( context, ele, eleTxrCache, pxRatio, lvl, reason, getRotation, getOpacity ){
|
||||
let r = this;
|
||||
let bb = eleTxrCache.getBoundingBox(ele);
|
||||
|
||||
if( bb.w === 0 || bb.h === 0 ){ return; } // ignore zero size case
|
||||
|
||||
let eleCache = eleTxrCache.getElement( ele, bb, pxRatio, lvl, reason );
|
||||
|
||||
if( eleCache != null ){
|
||||
let opacity = getOpacity(r, ele);
|
||||
|
||||
if( opacity === 0 ){ return; }
|
||||
|
||||
let theta = getRotation(r, ele);
|
||||
let { x1, y1, w, h } = bb;
|
||||
let x, y, sx, sy, smooth;
|
||||
|
||||
if( theta !== 0 ){
|
||||
let rotPt = eleTxrCache.getRotationPoint(ele);
|
||||
|
||||
sx = rotPt.x;
|
||||
sy = rotPt.y;
|
||||
|
||||
context.translate(sx, sy);
|
||||
context.rotate(theta);
|
||||
|
||||
smooth = r.getImgSmoothing(context);
|
||||
|
||||
if( !smooth ){ r.setImgSmoothing(context, true); }
|
||||
|
||||
let off = eleTxrCache.getRotationOffset(ele);
|
||||
|
||||
x = off.x;
|
||||
y = off.y;
|
||||
} else {
|
||||
x = x1;
|
||||
y = y1;
|
||||
}
|
||||
|
||||
let oldGlobalAlpha;
|
||||
|
||||
if( opacity !== 1 ){
|
||||
oldGlobalAlpha = context.globalAlpha;
|
||||
context.globalAlpha = oldGlobalAlpha * opacity;
|
||||
}
|
||||
|
||||
context.drawImage( eleCache.texture.canvas, eleCache.x, 0, eleCache.width, eleCache.height, x, y, w, h );
|
||||
|
||||
if( opacity !== 1 ){
|
||||
context.globalAlpha = oldGlobalAlpha;
|
||||
}
|
||||
|
||||
if( theta !== 0 ){
|
||||
context.rotate(-theta);
|
||||
context.translate(-sx, -sy);
|
||||
|
||||
if( !smooth ){ r.setImgSmoothing(context, false); }
|
||||
}
|
||||
} else {
|
||||
eleTxrCache.drawElement( context, ele ); // direct draw fallback
|
||||
}
|
||||
};
|
||||
|
||||
const getZeroRotation = () => 0;
|
||||
const getLabelRotation = (r, ele) => r.getTextAngle(ele, null);
|
||||
const getSourceLabelRotation = (r, ele) => r.getTextAngle(ele, 'source');
|
||||
const getTargetLabelRotation = (r, ele) => r.getTextAngle(ele, 'target');
|
||||
const getOpacity = (r, ele) => ele.effectiveOpacity();
|
||||
const getTextOpacity = (e, ele) => ele.pstyle('text-opacity').pfValue * ele.effectiveOpacity();
|
||||
|
||||
CRp.drawCachedElement = function( context, ele, pxRatio, extent, lvl, requestHighQuality ){
|
||||
let r = this;
|
||||
let { eleTxrCache, lblTxrCache, slbTxrCache, tlbTxrCache } = r.data;
|
||||
|
||||
let bb = ele.boundingBox();
|
||||
let reason = requestHighQuality === true ? eleTxrCache.reasons.highQuality : null;
|
||||
|
||||
if( bb.w === 0 || bb.h === 0 || !ele.visible() ){ return; }
|
||||
|
||||
if( !extent || math.boundingBoxesIntersect( bb, extent ) ){
|
||||
let isEdge = ele.isEdge();
|
||||
let badLine = ele.element()._private.rscratch.badLine;
|
||||
|
||||
r.drawElementUnderlay( context, ele );
|
||||
|
||||
r.drawCachedElementPortion( context, ele, eleTxrCache, pxRatio, lvl, reason, getZeroRotation, getOpacity );
|
||||
|
||||
if( !isEdge || !badLine ){
|
||||
r.drawCachedElementPortion( context, ele, lblTxrCache, pxRatio, lvl, reason, getLabelRotation, getTextOpacity );
|
||||
}
|
||||
|
||||
if( isEdge && !badLine ){
|
||||
r.drawCachedElementPortion( context, ele, slbTxrCache, pxRatio, lvl, reason, getSourceLabelRotation, getTextOpacity );
|
||||
r.drawCachedElementPortion( context, ele, tlbTxrCache, pxRatio, lvl, reason, getTargetLabelRotation, getTextOpacity );
|
||||
}
|
||||
|
||||
r.drawElementOverlay( context, ele );
|
||||
}
|
||||
};
|
||||
|
||||
CRp.drawElements = function( context, eles ){
|
||||
let r = this;
|
||||
|
||||
for( let i = 0; i < eles.length; i++ ){
|
||||
let ele = eles[ i ];
|
||||
|
||||
r.drawElement( context, ele );
|
||||
}
|
||||
};
|
||||
|
||||
CRp.drawCachedElements = function( context, eles, pxRatio, extent ){
|
||||
let r = this;
|
||||
|
||||
for( let i = 0; i < eles.length; i++ ){
|
||||
let ele = eles[ i ];
|
||||
|
||||
r.drawCachedElement( context, ele, pxRatio, extent );
|
||||
}
|
||||
};
|
||||
|
||||
CRp.drawCachedNodes = function( context, eles, pxRatio, extent ){
|
||||
let r = this;
|
||||
|
||||
for( let i = 0; i < eles.length; i++ ){
|
||||
let ele = eles[ i ];
|
||||
|
||||
if( !ele.isNode() ){ continue; }
|
||||
|
||||
r.drawCachedElement( context, ele, pxRatio, extent );
|
||||
}
|
||||
};
|
||||
|
||||
CRp.drawLayeredElements = function( context, eles, pxRatio, extent ){
|
||||
let r = this;
|
||||
|
||||
let layers = r.data.lyrTxrCache.getLayers( eles, pxRatio );
|
||||
|
||||
if( layers ){
|
||||
for( let i = 0; i < layers.length; i++ ){
|
||||
let layer = layers[i];
|
||||
let bb = layer.bb;
|
||||
|
||||
if( bb.w === 0 || bb.h === 0 ){ continue; }
|
||||
|
||||
context.drawImage( layer.canvas, bb.x1, bb.y1, bb.w, bb.h );
|
||||
}
|
||||
} else { // fall back on plain caching if no layers
|
||||
r.drawCachedElements( context, eles, pxRatio, extent );
|
||||
}
|
||||
};
|
||||
|
||||
if( process.env.NODE_ENV !== 'production' ){
|
||||
CRp.drawDebugPoints = function( context, eles ){
|
||||
let draw = function( x, y, color ){
|
||||
context.fillStyle = color;
|
||||
context.fillRect( x - 1, y - 1, 3, 3 );
|
||||
};
|
||||
|
||||
for( let i = 0; i < eles.length; i++ ){
|
||||
let ele = eles[i];
|
||||
let rs = ele._private.rscratch;
|
||||
|
||||
if( ele.isNode() ){
|
||||
let p = ele.position();
|
||||
|
||||
draw( rs.labelX, rs.labelY, 'red' );
|
||||
draw( p.x, p.y, 'magenta' );
|
||||
} else {
|
||||
let pts = rs.allpts;
|
||||
|
||||
for( let j = 0; j + 1 < pts.length; j += 2 ){
|
||||
let x = pts[ j ];
|
||||
let y = pts[ j + 1 ];
|
||||
|
||||
draw( x, y, 'cyan' );
|
||||
}
|
||||
|
||||
draw( rs.midX, rs.midY, 'yellow' );
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default CRp;
|
||||
804
frontend/node_modules/cytoscape/src/extensions/renderer/canvas/drawing-nodes.mjs
generated
vendored
Normal file
804
frontend/node_modules/cytoscape/src/extensions/renderer/canvas/drawing-nodes.mjs
generated
vendored
Normal file
@@ -0,0 +1,804 @@
|
||||
/* global Path2D */
|
||||
|
||||
import * as is from '../../../is.mjs';
|
||||
import {expandPolygon, joinLines} from '../../../math.mjs';
|
||||
import * as util from '../../../util/index.mjs';
|
||||
import * as round from "../../../round.mjs";
|
||||
import * as math from "../../../math.mjs";
|
||||
|
||||
let CRp = {};
|
||||
|
||||
CRp.drawNode = function( context, node, shiftToOriginWithBb, drawLabel = true, shouldDrawOverlay = true, shouldDrawOpacity = true ){
|
||||
let r = this;
|
||||
let nodeWidth, nodeHeight;
|
||||
let _p = node._private;
|
||||
let rs = _p.rscratch;
|
||||
let pos = node.position();
|
||||
|
||||
if( !is.number( pos.x ) || !is.number( pos.y ) ){
|
||||
return; // can't draw node with undefined position
|
||||
}
|
||||
|
||||
if( shouldDrawOpacity && !node.visible() ){ return; }
|
||||
|
||||
let eleOpacity = shouldDrawOpacity ? node.effectiveOpacity() : 1;
|
||||
|
||||
let usePaths = r.usePaths();
|
||||
let path;
|
||||
let pathCacheHit = false;
|
||||
|
||||
let padding = node.padding();
|
||||
|
||||
nodeWidth = node.width() + 2 * padding;
|
||||
nodeHeight = node.height() + 2 * padding;
|
||||
|
||||
//
|
||||
// setup shift
|
||||
|
||||
let bb;
|
||||
if( shiftToOriginWithBb ){
|
||||
bb = shiftToOriginWithBb;
|
||||
|
||||
context.translate( -bb.x1, -bb.y1 );
|
||||
}
|
||||
|
||||
//
|
||||
// load bg image
|
||||
|
||||
let bgImgProp = node.pstyle( 'background-image' );
|
||||
let urls = bgImgProp.value;
|
||||
let urlDefined = new Array( urls.length );
|
||||
let image = new Array( urls.length );
|
||||
let numImages = 0;
|
||||
for( let i = 0; i < urls.length; i++ ){
|
||||
let url = urls[i];
|
||||
let defd = urlDefined[i] = url != null && url !== 'none';
|
||||
|
||||
if( defd ){
|
||||
let bgImgCrossOrigin = node.cy().style().getIndexedStyle(node, 'background-image-crossorigin', 'value', i);
|
||||
|
||||
numImages++;
|
||||
|
||||
// get image, and if not loaded then ask to redraw when later loaded
|
||||
image[i] = r.getCachedImage( url, bgImgCrossOrigin, function(){
|
||||
_p.backgroundTimestamp = Date.now();
|
||||
|
||||
node.emitAndNotify('background');
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// setup styles
|
||||
|
||||
let darkness = node.pstyle('background-blacken').value;
|
||||
let borderWidth = node.pstyle('border-width').pfValue;
|
||||
let bgOpacity = node.pstyle('background-opacity').value * eleOpacity;
|
||||
let borderColor = node.pstyle('border-color').value;
|
||||
let borderStyle = node.pstyle('border-style').value;
|
||||
let borderJoin = node.pstyle('border-join').value;
|
||||
let borderCap = node.pstyle('border-cap').value;
|
||||
let borderPosition = node.pstyle('border-position').value;
|
||||
let borderPattern = node.pstyle('border-dash-pattern').pfValue;
|
||||
let borderOffset = node.pstyle('border-dash-offset').pfValue;
|
||||
let borderOpacity = node.pstyle('border-opacity').value * eleOpacity;
|
||||
let outlineWidth = node.pstyle('outline-width').pfValue;
|
||||
let outlineColor = node.pstyle('outline-color').value;
|
||||
let outlineStyle = node.pstyle('outline-style').value;
|
||||
let outlineOpacity = node.pstyle('outline-opacity').value * eleOpacity;
|
||||
let outlineOffset = node.pstyle('outline-offset').value;
|
||||
let cornerRadius = node.pstyle('corner-radius').value;
|
||||
if (cornerRadius !== 'auto') cornerRadius = node.pstyle('corner-radius').pfValue;
|
||||
|
||||
let setupShapeColor = ( bgOpy = bgOpacity ) => {
|
||||
r.eleFillStyle( context, node, bgOpy );
|
||||
};
|
||||
|
||||
let setupBorderColor = ( bdrOpy = borderOpacity ) => {
|
||||
r.colorStrokeStyle( context, borderColor[0], borderColor[1], borderColor[2], bdrOpy );
|
||||
};
|
||||
|
||||
let setupOutlineColor = ( otlnOpy = outlineOpacity ) => {
|
||||
r.colorStrokeStyle( context, outlineColor[0], outlineColor[1], outlineColor[2], otlnOpy );
|
||||
};
|
||||
|
||||
//
|
||||
// setup shape
|
||||
|
||||
let getPath = (width, height, shape, points) => {
|
||||
let pathCache = r.nodePathCache = r.nodePathCache || [];
|
||||
|
||||
let key = util.hashStrings(
|
||||
shape === 'polygon' ? shape + ',' + points.join(',') : shape,
|
||||
'' + height,
|
||||
'' + width,
|
||||
'' + cornerRadius
|
||||
);
|
||||
|
||||
let cachedPath = pathCache[ key ];
|
||||
let path;
|
||||
let cacheHit = false;
|
||||
|
||||
if( cachedPath != null ){
|
||||
path = cachedPath;
|
||||
cacheHit = true;
|
||||
rs.pathCache = path;
|
||||
} else {
|
||||
path = new Path2D();
|
||||
pathCache[ key ] = rs.pathCache = path;
|
||||
}
|
||||
|
||||
return {
|
||||
path,
|
||||
cacheHit
|
||||
};
|
||||
};
|
||||
|
||||
let styleShape = node.pstyle('shape').strValue;
|
||||
let shapePts = node.pstyle('shape-polygon-points').pfValue;
|
||||
|
||||
if( usePaths ){
|
||||
context.translate( pos.x, pos.y );
|
||||
|
||||
const shapePath = getPath(nodeWidth, nodeHeight, styleShape, shapePts);
|
||||
path = shapePath.path;
|
||||
pathCacheHit = shapePath.cacheHit;
|
||||
}
|
||||
|
||||
let drawShape = () => {
|
||||
if( !pathCacheHit ){
|
||||
|
||||
let npos = pos;
|
||||
|
||||
if( usePaths ){
|
||||
npos = {
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
}
|
||||
|
||||
r.nodeShapes[ r.getNodeShape( node ) ].draw(
|
||||
( path || context ),
|
||||
npos.x,
|
||||
npos.y,
|
||||
nodeWidth,
|
||||
nodeHeight, cornerRadius, rs );
|
||||
}
|
||||
|
||||
if( usePaths ){
|
||||
context.fill( path );
|
||||
} else {
|
||||
context.fill();
|
||||
}
|
||||
};
|
||||
|
||||
let drawImages = ( nodeOpacity = eleOpacity, inside = true ) => {
|
||||
let prevBging = _p.backgrounding;
|
||||
let totalCompleted = 0;
|
||||
|
||||
for( let i = 0; i < image.length; i++ ){
|
||||
const bgContainment = node.cy().style().getIndexedStyle(node, 'background-image-containment', 'value', i);
|
||||
if( inside && bgContainment === 'over' || !inside && bgContainment === 'inside' ){
|
||||
totalCompleted++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if( urlDefined[i] && image[i].complete && !image[i].error ){
|
||||
totalCompleted++;
|
||||
r.drawInscribedImage( context, image[i], node, i, nodeOpacity );
|
||||
}
|
||||
}
|
||||
|
||||
_p.backgrounding = !(totalCompleted === numImages);
|
||||
if( prevBging !== _p.backgrounding ){ // update style b/c :backgrounding state changed
|
||||
node.updateStyle( false );
|
||||
}
|
||||
};
|
||||
|
||||
let drawPie = ( redrawShape = false, pieOpacity = eleOpacity ) => {
|
||||
if( r.hasPie( node ) ){
|
||||
r.drawPie( context, node, pieOpacity );
|
||||
|
||||
// redraw/restore path if steps after pie need it
|
||||
if( redrawShape ){
|
||||
|
||||
if( !usePaths ){
|
||||
r.nodeShapes[ r.getNodeShape( node ) ].draw(
|
||||
context,
|
||||
pos.x,
|
||||
pos.y,
|
||||
nodeWidth,
|
||||
nodeHeight, cornerRadius, rs );
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let drawStripe = (redrawShape = false, stripeOpacity = eleOpacity) => {
|
||||
if( r.hasStripe( node ) ){
|
||||
context.save();
|
||||
|
||||
if (usePaths) {
|
||||
context.clip( rs.pathCache );
|
||||
} else {
|
||||
r.nodeShapes[ r.getNodeShape( node ) ].draw(
|
||||
context,
|
||||
pos.x,
|
||||
pos.y,
|
||||
nodeWidth,
|
||||
nodeHeight, cornerRadius, rs );
|
||||
context.clip();
|
||||
}
|
||||
|
||||
r.drawStripe( context, node, stripeOpacity );
|
||||
|
||||
context.restore();
|
||||
|
||||
// redraw/restore path if steps after stripes need it
|
||||
if( redrawShape ){
|
||||
|
||||
if( !usePaths ){
|
||||
r.nodeShapes[ r.getNodeShape( node ) ].draw(
|
||||
context,
|
||||
pos.x,
|
||||
pos.y,
|
||||
nodeWidth,
|
||||
nodeHeight, cornerRadius, rs );
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let darken = ( darkenOpacity = eleOpacity ) => {
|
||||
let opacity = ( darkness > 0 ? darkness : -darkness ) * darkenOpacity;
|
||||
let c = darkness > 0 ? 0 : 255;
|
||||
|
||||
if( darkness !== 0 ){
|
||||
r.colorFillStyle( context, c, c, c, opacity );
|
||||
|
||||
if( usePaths ){
|
||||
context.fill( path );
|
||||
} else {
|
||||
context.fill();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let drawBorder = () => {
|
||||
if( borderWidth > 0 ){
|
||||
|
||||
context.lineWidth = borderWidth;
|
||||
context.lineCap = borderCap;
|
||||
context.lineJoin = borderJoin;
|
||||
|
||||
if( context.setLineDash ){ // for very outofdate browsers
|
||||
switch( borderStyle ){
|
||||
case 'dotted':
|
||||
context.setLineDash( [ 1, 1 ] );
|
||||
break;
|
||||
|
||||
case 'dashed':
|
||||
context.setLineDash( borderPattern );
|
||||
context.lineDashOffset = borderOffset;
|
||||
break;
|
||||
|
||||
case 'solid':
|
||||
case 'double':
|
||||
context.setLineDash( [ ] );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( borderPosition !== 'center') {
|
||||
context.save();
|
||||
context.lineWidth *= 2;
|
||||
if (borderPosition === 'inside') {
|
||||
usePaths ? context.clip(path) : context.clip();
|
||||
} else {
|
||||
const region = new Path2D();
|
||||
region.rect(
|
||||
-nodeWidth / 2 - borderWidth,
|
||||
-nodeHeight / 2 - borderWidth,
|
||||
nodeWidth + 2 * borderWidth,
|
||||
nodeHeight + 2 * borderWidth
|
||||
);
|
||||
region.addPath(path);
|
||||
context.clip(region, 'evenodd');
|
||||
}
|
||||
usePaths ? context.stroke(path) : context.stroke();
|
||||
context.restore();
|
||||
} else {
|
||||
usePaths ? context.stroke(path) : context.stroke();
|
||||
}
|
||||
|
||||
if( borderStyle === 'double' ){
|
||||
context.lineWidth = borderWidth / 3;
|
||||
|
||||
let gco = context.globalCompositeOperation;
|
||||
context.globalCompositeOperation = 'destination-out';
|
||||
|
||||
if( usePaths ){
|
||||
context.stroke( path );
|
||||
} else {
|
||||
context.stroke();
|
||||
}
|
||||
|
||||
context.globalCompositeOperation = gco;
|
||||
}
|
||||
|
||||
// reset in case we changed the border style
|
||||
if( context.setLineDash ){ // for very outofdate browsers
|
||||
context.setLineDash( [ ] );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let drawOutline = () => {
|
||||
if( outlineWidth > 0 ){
|
||||
context.lineWidth = outlineWidth;
|
||||
context.lineCap = 'butt';
|
||||
|
||||
if( context.setLineDash ){ // for very outofdate browsers
|
||||
switch( outlineStyle ){
|
||||
case 'dotted':
|
||||
context.setLineDash( [ 1, 1 ] );
|
||||
break;
|
||||
|
||||
case 'dashed':
|
||||
context.setLineDash( [ 4, 2 ] );
|
||||
break;
|
||||
|
||||
case 'solid':
|
||||
case 'double':
|
||||
context.setLineDash( [ ] );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let npos = pos;
|
||||
|
||||
if( usePaths ){
|
||||
npos = {
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
}
|
||||
|
||||
let shape = r.getNodeShape( node );
|
||||
|
||||
let bWidth = borderWidth;
|
||||
if( borderPosition === 'inside' ) bWidth = 0;
|
||||
if( borderPosition === 'outside' ) bWidth *= 2;
|
||||
|
||||
let scaleX = (nodeWidth + bWidth + (outlineWidth + outlineOffset)) / nodeWidth;
|
||||
let scaleY = (nodeHeight + bWidth + (outlineWidth + outlineOffset)) / nodeHeight;
|
||||
let sWidth = nodeWidth * scaleX;
|
||||
let sHeight = nodeHeight * scaleY;
|
||||
|
||||
let points = r.nodeShapes[ shape ].points;
|
||||
let path;
|
||||
|
||||
if (usePaths) {
|
||||
let outlinePath = getPath(sWidth, sHeight, shape, points);
|
||||
path = outlinePath.path;
|
||||
}
|
||||
|
||||
// draw the outline path, either by using expanded points or by scaling
|
||||
// the dimensions, depending on shape
|
||||
if (shape === "ellipse") {
|
||||
r.drawEllipsePath(path || context, npos.x, npos.y, sWidth, sHeight);
|
||||
} else if ([
|
||||
'round-diamond', 'round-heptagon', 'round-hexagon', 'round-octagon',
|
||||
'round-pentagon', 'round-polygon', 'round-triangle', 'round-tag'
|
||||
].includes(shape)) {
|
||||
let sMult = 0;
|
||||
let offsetX = 0;
|
||||
let offsetY = 0;
|
||||
|
||||
if (shape === 'round-diamond') {
|
||||
sMult = (bWidth + outlineOffset + outlineWidth) * 1.4;
|
||||
} else if (shape === 'round-heptagon') {
|
||||
sMult = (bWidth + outlineOffset + outlineWidth) * 1.075;
|
||||
offsetY = -(bWidth/2 + outlineOffset + outlineWidth) / 35;
|
||||
} else if (shape === 'round-hexagon') {
|
||||
sMult = (bWidth + outlineOffset + outlineWidth) * 1.12;
|
||||
} else if (shape === 'round-pentagon') {
|
||||
sMult = (bWidth + outlineOffset + outlineWidth) * 1.13;
|
||||
offsetY = -(bWidth/2 + outlineOffset + outlineWidth) / 15;
|
||||
} else if (shape === 'round-tag') {
|
||||
sMult = (bWidth + outlineOffset + outlineWidth) * 1.12;
|
||||
offsetX = (bWidth/2 + outlineWidth + outlineOffset) * .07;
|
||||
} else if (shape === 'round-triangle') {
|
||||
sMult = (bWidth + outlineOffset + outlineWidth) * (Math.PI/2);
|
||||
offsetY = -(bWidth + outlineOffset/2 + outlineWidth) / Math.PI;
|
||||
}
|
||||
|
||||
if (sMult !== 0) {
|
||||
scaleX = (nodeWidth + sMult)/nodeWidth;
|
||||
sWidth = nodeWidth * scaleX;
|
||||
if ( ! ['round-hexagon', 'round-tag'].includes(shape) ) {
|
||||
scaleY = (nodeHeight + sMult)/nodeHeight;
|
||||
sHeight = nodeHeight * scaleY;
|
||||
}
|
||||
}
|
||||
|
||||
cornerRadius = cornerRadius === 'auto' ? math.getRoundPolygonRadius( sWidth, sHeight ) : cornerRadius;
|
||||
|
||||
const halfW = sWidth / 2;
|
||||
const halfH = sHeight / 2;
|
||||
const radius = cornerRadius + ( bWidth + outlineWidth + outlineOffset ) / 2;
|
||||
const p = new Array( points.length / 2 );
|
||||
const corners = new Array( points.length / 2 );
|
||||
|
||||
for ( let i = 0; i < points.length / 2; i++ ){
|
||||
p[i] = {
|
||||
x: npos.x + offsetX + halfW * points[ i * 2 ],
|
||||
y: npos.y + offsetY + halfH * points[ i * 2 + 1 ]
|
||||
};
|
||||
}
|
||||
|
||||
let i, p1, p2, p3, len = p.length;
|
||||
|
||||
p1 = p[ len - 1 ];
|
||||
// for each point
|
||||
for( i = 0; i < len; i++ ){
|
||||
p2 = p[ (i) % len ];
|
||||
p3 = p[ (i + 1) % len ];
|
||||
corners[ i ] = round.getRoundCorner( p1, p2, p3, radius );
|
||||
p1 = p2;
|
||||
p2 = p3;
|
||||
}
|
||||
|
||||
r.drawRoundPolygonPath(path || context, npos.x + offsetX, npos.y + offsetY, nodeWidth * scaleX, nodeHeight * scaleY, points, corners );
|
||||
} else if (['roundrectangle', 'round-rectangle'].includes(shape)) {
|
||||
cornerRadius = cornerRadius === 'auto' ? math.getRoundRectangleRadius( sWidth, sHeight ) : cornerRadius;
|
||||
r.drawRoundRectanglePath(path || context, npos.x, npos.y, sWidth, sHeight, cornerRadius + ( bWidth + outlineWidth + outlineOffset ) / 2 );
|
||||
} else if (['cutrectangle', 'cut-rectangle'].includes(shape)) {
|
||||
cornerRadius = cornerRadius === 'auto' ? math.getCutRectangleCornerLength( sWidth, sHeight ) : cornerRadius;
|
||||
r.drawCutRectanglePath(path || context, npos.x, npos.y, sWidth, sHeight, null ,cornerRadius + ( bWidth + outlineWidth + outlineOffset ) / 4 );
|
||||
} else if (['bottomroundrectangle', 'bottom-round-rectangle'].includes(shape)) {
|
||||
cornerRadius = cornerRadius === 'auto' ? math.getRoundRectangleRadius( sWidth, sHeight ) : cornerRadius;
|
||||
r.drawBottomRoundRectanglePath(path || context, npos.x, npos.y, sWidth, sHeight, cornerRadius + ( bWidth + outlineWidth + outlineOffset ) / 2 );
|
||||
} else if (shape === "barrel") {
|
||||
r.drawBarrelPath(path || context, npos.x, npos.y, sWidth, sHeight);
|
||||
} else if (shape.startsWith("polygon") || ['rhomboid', 'right-rhomboid', 'round-tag', 'tag', 'vee'].includes(shape)) {
|
||||
let pad = (bWidth + outlineWidth + outlineOffset) / nodeWidth;
|
||||
points = joinLines(expandPolygon(points, pad));
|
||||
r.drawPolygonPath(path || context, npos.x, npos.y, nodeWidth, nodeHeight, points);
|
||||
} else {
|
||||
let pad = (bWidth + outlineWidth + outlineOffset) / nodeWidth;
|
||||
points = joinLines(expandPolygon(points, -pad));
|
||||
r.drawPolygonPath(path || context, npos.x, npos.y, nodeWidth, nodeHeight, points);
|
||||
}
|
||||
|
||||
if( usePaths ){
|
||||
context.stroke( path );
|
||||
} else {
|
||||
context.stroke();
|
||||
}
|
||||
|
||||
if( outlineStyle === 'double' ){
|
||||
context.lineWidth = bWidth / 3;
|
||||
|
||||
let gco = context.globalCompositeOperation;
|
||||
context.globalCompositeOperation = 'destination-out';
|
||||
|
||||
if( usePaths ){
|
||||
context.stroke( path );
|
||||
} else {
|
||||
context.stroke();
|
||||
}
|
||||
|
||||
context.globalCompositeOperation = gco;
|
||||
}
|
||||
|
||||
// reset in case we changed the border style
|
||||
if( context.setLineDash ){ // for very outofdate browsers
|
||||
context.setLineDash( [ ] );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let drawOverlay = () => {
|
||||
if( shouldDrawOverlay ){
|
||||
r.drawNodeOverlay( context, node, pos, nodeWidth, nodeHeight );
|
||||
}
|
||||
};
|
||||
|
||||
let drawUnderlay = () => {
|
||||
if( shouldDrawOverlay ){
|
||||
r.drawNodeUnderlay( context, node, pos, nodeWidth, nodeHeight );
|
||||
}
|
||||
};
|
||||
|
||||
let drawText = () => {
|
||||
r.drawElementText( context, node, null, drawLabel );
|
||||
};
|
||||
|
||||
let ghost = node.pstyle('ghost').value === 'yes';
|
||||
|
||||
if( ghost ){
|
||||
let gx = node.pstyle('ghost-offset-x').pfValue;
|
||||
let gy = node.pstyle('ghost-offset-y').pfValue;
|
||||
let ghostOpacity = node.pstyle('ghost-opacity').value;
|
||||
let effGhostOpacity = ghostOpacity * eleOpacity;
|
||||
|
||||
context.translate( gx, gy );
|
||||
|
||||
setupOutlineColor();
|
||||
drawOutline();
|
||||
setupShapeColor( ghostOpacity * bgOpacity );
|
||||
drawShape();
|
||||
drawImages( effGhostOpacity, true );
|
||||
setupBorderColor( ghostOpacity * borderOpacity );
|
||||
drawBorder();
|
||||
drawPie( darkness !== 0 || borderWidth !== 0 );
|
||||
drawStripe( darkness !== 0 || borderWidth !== 0 );
|
||||
drawImages( effGhostOpacity, false );
|
||||
darken( effGhostOpacity );
|
||||
|
||||
context.translate( -gx, -gy );
|
||||
}
|
||||
|
||||
if( usePaths ){
|
||||
context.translate( -pos.x, -pos.y );
|
||||
}
|
||||
drawUnderlay();
|
||||
if( usePaths ){
|
||||
context.translate( pos.x, pos.y );
|
||||
}
|
||||
|
||||
setupOutlineColor();
|
||||
drawOutline();
|
||||
setupShapeColor();
|
||||
drawShape();
|
||||
drawImages(eleOpacity, true);
|
||||
setupBorderColor();
|
||||
drawBorder();
|
||||
drawPie( darkness !== 0 || borderWidth !== 0 );
|
||||
drawStripe( darkness !== 0 || borderWidth !== 0 );
|
||||
drawImages(eleOpacity, false);
|
||||
|
||||
darken();
|
||||
|
||||
if( usePaths ){
|
||||
context.translate( -pos.x, -pos.y );
|
||||
}
|
||||
|
||||
drawText();
|
||||
|
||||
drawOverlay();
|
||||
|
||||
//
|
||||
// clean up shift
|
||||
|
||||
if( shiftToOriginWithBb ){
|
||||
context.translate( bb.x1, bb.y1 );
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const drawNodeOverlayUnderlay = function( overlayOrUnderlay ) {
|
||||
if (!['overlay', 'underlay'].includes(overlayOrUnderlay)) {
|
||||
throw new Error('Invalid state');
|
||||
}
|
||||
|
||||
return function( context, node, pos, nodeWidth, nodeHeight ){
|
||||
let r = this;
|
||||
|
||||
if( !node.visible() ){ return; }
|
||||
|
||||
let padding = node.pstyle( `${overlayOrUnderlay}-padding` ).pfValue;
|
||||
let opacity = node.pstyle( `${overlayOrUnderlay}-opacity` ).value;
|
||||
let color = node.pstyle( `${overlayOrUnderlay}-color` ).value;
|
||||
let shape = node.pstyle( `${overlayOrUnderlay}-shape` ).value;
|
||||
let radius = node.pstyle( `${overlayOrUnderlay}-corner-radius` ).value;
|
||||
|
||||
if( opacity > 0 ){
|
||||
pos = pos || node.position();
|
||||
|
||||
if( nodeWidth == null || nodeHeight == null ){
|
||||
let padding = node.padding();
|
||||
|
||||
nodeWidth = node.width() + 2 * padding;
|
||||
nodeHeight = node.height() + 2 * padding;
|
||||
}
|
||||
|
||||
r.colorFillStyle( context, color[0], color[1], color[2], opacity );
|
||||
|
||||
r.nodeShapes[shape].draw(
|
||||
context,
|
||||
pos.x,
|
||||
pos.y,
|
||||
nodeWidth + padding * 2,
|
||||
nodeHeight + padding * 2,
|
||||
radius
|
||||
);
|
||||
|
||||
context.fill();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
CRp.drawNodeOverlay = drawNodeOverlayUnderlay('overlay');
|
||||
|
||||
CRp.drawNodeUnderlay = drawNodeOverlayUnderlay('underlay');
|
||||
|
||||
// does the node have at least one pie piece?
|
||||
CRp.hasPie = function( node ){
|
||||
node = node[0]; // ensure ele ref
|
||||
|
||||
return node._private.hasPie;
|
||||
};
|
||||
|
||||
CRp.hasStripe = function( node ){
|
||||
node = node[0]; // ensure ele ref
|
||||
|
||||
return node._private.hasStripe;
|
||||
};
|
||||
|
||||
CRp.drawPie = function( context, node, nodeOpacity, pos ){
|
||||
node = node[0]; // ensure ele ref
|
||||
pos = pos || node.position();
|
||||
|
||||
let cyStyle = node.cy().style();
|
||||
let pieSize = node.pstyle( 'pie-size' );
|
||||
let hole = node.pstyle('pie-hole');
|
||||
let overallStartAngle = node.pstyle('pie-start-angle').pfValue;
|
||||
let x = pos.x;
|
||||
let y = pos.y;
|
||||
let nodeW = node.width();
|
||||
let nodeH = node.height();
|
||||
let radius = Math.min( nodeW, nodeH ) / 2; // must fit in node
|
||||
let holeRadius;
|
||||
let lastPercent = 0; // what % to continue drawing pie slices from on [0, 1]
|
||||
let usePaths = this.usePaths();
|
||||
|
||||
if( usePaths ){
|
||||
x = 0;
|
||||
y = 0;
|
||||
}
|
||||
|
||||
if( pieSize.units === '%' ){
|
||||
radius = radius * pieSize.pfValue;
|
||||
} else if( pieSize.pfValue !== undefined ){
|
||||
radius = pieSize.pfValue / 2; // diameter in pixels => radius
|
||||
}
|
||||
|
||||
if (hole.units === '%') {
|
||||
holeRadius = radius * hole.pfValue;
|
||||
} else if (hole.pfValue !== undefined) {
|
||||
holeRadius = hole.pfValue / 2; // diameter in pixels => radius
|
||||
}
|
||||
|
||||
if (holeRadius >= radius) {
|
||||
return; // the pie would be invisible anyway
|
||||
}
|
||||
|
||||
for( let i = 1; i <= cyStyle.pieBackgroundN; i++ ){ // 1..N
|
||||
let size = node.pstyle( 'pie-' + i + '-background-size' ).value;
|
||||
let color = node.pstyle( 'pie-' + i + '-background-color' ).value;
|
||||
let opacity = node.pstyle( 'pie-' + i + '-background-opacity' ).value * nodeOpacity;
|
||||
let percent = size / 100; // map integer range [0, 100] to [0, 1]
|
||||
|
||||
// percent can't push beyond 1
|
||||
if( percent + lastPercent > 1 ){
|
||||
percent = 1 - lastPercent;
|
||||
}
|
||||
|
||||
let angleStart = (1.5 * Math.PI) + (2 * Math.PI * lastPercent); // start at 12 o'clock and go clockwise
|
||||
angleStart += overallStartAngle; // shift by the overall pie start angle
|
||||
let angleDelta = 2 * Math.PI * percent;
|
||||
let angleEnd = angleStart + angleDelta;
|
||||
|
||||
// ignore if
|
||||
// - zero size
|
||||
// - we're already beyond the full circle
|
||||
// - adding the current slice would go beyond the full circle
|
||||
if( size === 0 || lastPercent >= 1 || lastPercent + percent > 1 ){
|
||||
continue;
|
||||
}
|
||||
|
||||
if (holeRadius === 0) { // make a pie slice
|
||||
context.beginPath();
|
||||
context.moveTo( x, y );
|
||||
context.arc( x, y, radius, angleStart, angleEnd );
|
||||
context.closePath();
|
||||
} else { // make a pie slice that's like the above but with a hole in the middle
|
||||
context.beginPath();
|
||||
context.arc(x, y, radius, angleStart, angleEnd);
|
||||
context.arc(x, y, holeRadius, angleEnd, angleStart, true); // true for anticlockwise
|
||||
context.closePath();
|
||||
}
|
||||
|
||||
this.colorFillStyle( context, color[0], color[1], color[2], opacity );
|
||||
|
||||
context.fill();
|
||||
|
||||
lastPercent += percent;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
CRp.drawStripe = function( context, node, nodeOpacity, pos ){
|
||||
node = node[0]; // ensure ele ref
|
||||
pos = pos || node.position();
|
||||
|
||||
let cyStyle = node.cy().style();
|
||||
let x = pos.x;
|
||||
let y = pos.y;
|
||||
let nodeW = node.width();
|
||||
let nodeH = node.height();
|
||||
let lastPercent = 0; // what % to continue drawing pie slices from on [0, 1]
|
||||
let usePaths = this.usePaths();
|
||||
|
||||
context.save();
|
||||
|
||||
let direction = node.pstyle('stripe-direction').value;
|
||||
let stripeSize = node.pstyle('stripe-size');
|
||||
|
||||
switch (direction) {
|
||||
case 'vertical':
|
||||
break; // default
|
||||
case 'righward':
|
||||
context.rotate(-Math.PI / 2);
|
||||
break;
|
||||
}
|
||||
|
||||
let stripeW = nodeW;
|
||||
let stripeH = nodeH;
|
||||
|
||||
if( stripeSize.units === '%' ){
|
||||
stripeW = stripeW * stripeSize.pfValue;
|
||||
stripeH = stripeH * stripeSize.pfValue;
|
||||
} else if( stripeSize.pfValue !== undefined ){
|
||||
stripeW = stripeSize.pfValue;
|
||||
stripeH = stripeSize.pfValue;
|
||||
}
|
||||
|
||||
if( usePaths ){
|
||||
x = 0;
|
||||
y = 0;
|
||||
}
|
||||
|
||||
// shift up from the centre of the node to the top-left corner
|
||||
y -= stripeW / 2;
|
||||
x -= stripeH / 2;
|
||||
|
||||
for( let i = 1; i <= cyStyle.stripeBackgroundN; i++ ){ // 1..N
|
||||
let size = node.pstyle( 'stripe-' + i + '-background-size' ).value;
|
||||
let color = node.pstyle( 'stripe-' + i + '-background-color' ).value;
|
||||
let opacity = node.pstyle( 'stripe-' + i + '-background-opacity' ).value * nodeOpacity;
|
||||
let percent = size / 100; // map integer range [0, 100] to [0, 1]
|
||||
|
||||
// percent can't push beyond 1
|
||||
if( percent + lastPercent > 1 ){
|
||||
percent = 1 - lastPercent;
|
||||
}
|
||||
|
||||
// ignore if
|
||||
// - zero size
|
||||
// - we're already beyond the full chart
|
||||
// - adding the current slice would go beyond the full chart
|
||||
if( size === 0 || lastPercent >= 1 || lastPercent + percent > 1 ){
|
||||
continue;
|
||||
}
|
||||
|
||||
// draw rect for the current stripe
|
||||
context.beginPath();
|
||||
context.rect( x, y + stripeH * lastPercent, stripeW, stripeH * percent );
|
||||
context.closePath();
|
||||
|
||||
this.colorFillStyle( context, color[0], color[1], color[2], opacity );
|
||||
|
||||
context.fill();
|
||||
|
||||
lastPercent += percent;
|
||||
}
|
||||
|
||||
context.restore();
|
||||
|
||||
};
|
||||
|
||||
|
||||
export default CRp;
|
||||
555
frontend/node_modules/cytoscape/src/extensions/renderer/canvas/ele-texture-cache.mjs
generated
vendored
Normal file
555
frontend/node_modules/cytoscape/src/extensions/renderer/canvas/ele-texture-cache.mjs
generated
vendored
Normal file
@@ -0,0 +1,555 @@
|
||||
import * as math from '../../../math.mjs';
|
||||
import { trueify, falsify, removeFromArray, clearArray, MAX_INT, assign, defaults } from '../../../util/index.mjs';
|
||||
import Heap from '../../../heap.mjs';
|
||||
import defs from './texture-cache-defs.mjs';
|
||||
import ElementTextureCacheLookup from './ele-texture-cache-lookup.mjs';
|
||||
|
||||
const minTxrH = 25; // the size of the texture cache for small height eles (special case)
|
||||
const txrStepH = 50; // the min size of the regular cache, and the size it increases with each step up
|
||||
const minLvl = -4; // when scaling smaller than that we don't need to re-render
|
||||
export const maxLvl = 3; // when larger than this scale just render directly (caching is not helpful)
|
||||
export const maxZoom = 7.99; // beyond this zoom level, layered textures are not used
|
||||
const eleTxrSpacing = 8; // spacing between elements on textures to avoid blitting overlaps
|
||||
const defTxrWidth = 1024; // default/minimum texture width
|
||||
const maxTxrW = 1024; // the maximum width of a texture
|
||||
const maxTxrH = 1024; // the maximum height of a texture
|
||||
const minUtility = 0.2; // if usage of texture is less than this, it is retired
|
||||
const maxFullness = 0.8; // fullness of texture after which queue removal is checked
|
||||
const maxFullnessChecks = 10; // dequeued after this many checks
|
||||
const deqCost = 0.15; // % of add'l rendering cost allowed for dequeuing ele caches each frame
|
||||
const deqAvgCost = 0.1; // % of add'l rendering cost compared to average overall redraw time
|
||||
const deqNoDrawCost = 0.9; // % of avg frame time that can be used for dequeueing when not drawing
|
||||
const deqFastCost = 0.9; // % of frame time to be used when >60fps
|
||||
const deqRedrawThreshold = 100; // time to batch redraws together from dequeueing to allow more dequeueing calcs to happen in the meanwhile
|
||||
const maxDeqSize = 1; // number of eles to dequeue and render at higher texture in each batch
|
||||
|
||||
const getTxrReasons = {
|
||||
dequeue: 'dequeue',
|
||||
downscale: 'downscale',
|
||||
highQuality: 'highQuality'
|
||||
};
|
||||
|
||||
const initDefaults = defaults({
|
||||
getKey: null,
|
||||
doesEleInvalidateKey: falsify,
|
||||
drawElement: null,
|
||||
getBoundingBox: null,
|
||||
getRotationPoint: null,
|
||||
getRotationOffset: null,
|
||||
isVisible: trueify,
|
||||
allowEdgeTxrCaching: true,
|
||||
allowParentTxrCaching: true
|
||||
});
|
||||
|
||||
const ElementTextureCache = function( renderer, initOptions ){
|
||||
let self = this;
|
||||
|
||||
self.renderer = renderer;
|
||||
self.onDequeues = [];
|
||||
|
||||
let opts = initDefaults(initOptions);
|
||||
|
||||
assign(self, opts);
|
||||
|
||||
self.lookup = new ElementTextureCacheLookup(opts.getKey, opts.doesEleInvalidateKey);
|
||||
|
||||
self.setupDequeueing();
|
||||
};
|
||||
|
||||
const ETCp = ElementTextureCache.prototype;
|
||||
|
||||
ETCp.reasons = getTxrReasons;
|
||||
|
||||
// the list of textures in which new subtextures for elements can be placed
|
||||
ETCp.getTextureQueue = function( txrH ){
|
||||
let self = this;
|
||||
self.eleImgCaches = self.eleImgCaches || {};
|
||||
|
||||
return ( self.eleImgCaches[ txrH ] = self.eleImgCaches[ txrH ] || [] );
|
||||
};
|
||||
|
||||
// the list of usused textures which can be recycled (in use in texture queue)
|
||||
ETCp.getRetiredTextureQueue = function( txrH ){
|
||||
let self = this;
|
||||
|
||||
let rtxtrQs = self.eleImgCaches.retired = self.eleImgCaches.retired || {};
|
||||
let rtxtrQ = rtxtrQs[ txrH ] = rtxtrQs[ txrH ] || [];
|
||||
|
||||
return rtxtrQ;
|
||||
};
|
||||
|
||||
// queue of element draw requests at different scale levels
|
||||
ETCp.getElementQueue = function(){
|
||||
let self = this;
|
||||
|
||||
let q = self.eleCacheQueue = self.eleCacheQueue || new Heap(function( a, b ){
|
||||
return b.reqs - a.reqs;
|
||||
});
|
||||
|
||||
return q;
|
||||
};
|
||||
|
||||
// queue of element draw requests at different scale levels (element id lookup)
|
||||
ETCp.getElementKeyToQueue = function(){
|
||||
let self = this;
|
||||
|
||||
let k2q = self.eleKeyToCacheQueue = self.eleKeyToCacheQueue || {};
|
||||
|
||||
return k2q;
|
||||
};
|
||||
|
||||
ETCp.getElement = function( ele, bb, pxRatio, lvl, reason ){
|
||||
let self = this;
|
||||
let r = this.renderer;
|
||||
let zoom = r.cy.zoom();
|
||||
let lookup = this.lookup;
|
||||
|
||||
if( !bb || bb.w === 0 || bb.h === 0 || isNaN(bb.w) || isNaN(bb.h) || !ele.visible() || ele.removed() ){ return null; }
|
||||
|
||||
if(
|
||||
( !self.allowEdgeTxrCaching && ele.isEdge() )
|
||||
|| ( !self.allowParentTxrCaching && ele.isParent() )
|
||||
){
|
||||
return null;
|
||||
}
|
||||
|
||||
if( lvl == null ){
|
||||
lvl = Math.ceil( math.log2( zoom * pxRatio ) );
|
||||
}
|
||||
|
||||
if( lvl < minLvl ){
|
||||
lvl = minLvl;
|
||||
} else if( zoom >= maxZoom || lvl > maxLvl ){
|
||||
return null;
|
||||
}
|
||||
|
||||
let scale = Math.pow( 2, lvl );
|
||||
let eleScaledH = bb.h * scale;
|
||||
let eleScaledW = bb.w * scale;
|
||||
let scaledLabelShown = r.eleTextBiggerThanMin( ele, scale );
|
||||
|
||||
if( !this.isVisible(ele, scaledLabelShown) ){ return null; }
|
||||
|
||||
let eleCache = lookup.get( ele, lvl );
|
||||
|
||||
// if this get was on an unused/invalidated cache, then restore the texture usage metric
|
||||
if( eleCache && eleCache.invalidated ){
|
||||
eleCache.invalidated = false;
|
||||
eleCache.texture.invalidatedWidth -= eleCache.width;
|
||||
}
|
||||
|
||||
if( eleCache ){
|
||||
return eleCache;
|
||||
}
|
||||
|
||||
let txrH; // which texture height this ele belongs to
|
||||
|
||||
if( eleScaledH <= minTxrH ){
|
||||
txrH = minTxrH;
|
||||
} else if( eleScaledH <= txrStepH ){
|
||||
txrH = txrStepH;
|
||||
} else {
|
||||
txrH = Math.ceil( eleScaledH / txrStepH ) * txrStepH;
|
||||
}
|
||||
|
||||
if( eleScaledH > maxTxrH || eleScaledW > maxTxrW ){
|
||||
return null; // caching large elements is not efficient
|
||||
}
|
||||
|
||||
let txrQ = self.getTextureQueue( txrH );
|
||||
|
||||
// first try the second last one in case it has space at the end
|
||||
let txr = txrQ[ txrQ.length - 2 ];
|
||||
|
||||
let addNewTxr = function(){
|
||||
return self.recycleTexture( txrH, eleScaledW ) || self.addTexture( txrH, eleScaledW );
|
||||
};
|
||||
|
||||
// try the last one if there is no second last one
|
||||
if( !txr ){
|
||||
txr = txrQ[ txrQ.length - 1 ];
|
||||
}
|
||||
|
||||
// if the last one doesn't exist, we need a first one
|
||||
if( !txr ){
|
||||
txr = addNewTxr();
|
||||
}
|
||||
|
||||
// if there's no room in the current texture, we need a new one
|
||||
if( txr.width - txr.usedWidth < eleScaledW ){
|
||||
txr = addNewTxr();
|
||||
}
|
||||
|
||||
let scalableFrom = function( otherCache ){
|
||||
return otherCache && otherCache.scaledLabelShown === scaledLabelShown;
|
||||
};
|
||||
|
||||
let deqing = reason && reason === getTxrReasons.dequeue;
|
||||
let highQualityReq = reason && reason === getTxrReasons.highQuality;
|
||||
let downscaleReq = reason && reason === getTxrReasons.downscale;
|
||||
|
||||
let higherCache; // the nearest cache with a higher level
|
||||
for( let l = lvl + 1; l <= maxLvl; l++ ){
|
||||
let c = lookup.get( ele, l );
|
||||
|
||||
if( c ){ higherCache = c; break; }
|
||||
}
|
||||
|
||||
let oneUpCache = higherCache && higherCache.level === lvl + 1 ? higherCache : null;
|
||||
|
||||
let downscale = function(){
|
||||
txr.context.drawImage(
|
||||
oneUpCache.texture.canvas,
|
||||
oneUpCache.x, 0,
|
||||
oneUpCache.width, oneUpCache.height,
|
||||
txr.usedWidth, 0,
|
||||
eleScaledW, eleScaledH
|
||||
);
|
||||
};
|
||||
|
||||
// reset ele area in texture
|
||||
txr.context.setTransform( 1, 0, 0, 1, 0, 0 );
|
||||
txr.context.clearRect( txr.usedWidth, 0, eleScaledW, txrH );
|
||||
|
||||
if( scalableFrom(oneUpCache) ){
|
||||
// then we can relatively cheaply rescale the existing image w/o rerendering
|
||||
downscale();
|
||||
|
||||
} else if( scalableFrom(higherCache) ){
|
||||
// then use the higher cache for now and queue the next level down
|
||||
// to cheaply scale towards the smaller level
|
||||
|
||||
if( highQualityReq ){
|
||||
for( let l = higherCache.level; l > lvl; l-- ){
|
||||
oneUpCache = self.getElement( ele, bb, pxRatio, l, getTxrReasons.downscale );
|
||||
}
|
||||
|
||||
downscale();
|
||||
|
||||
} else {
|
||||
self.queueElement( ele, higherCache.level - 1 );
|
||||
|
||||
return higherCache;
|
||||
}
|
||||
} else {
|
||||
|
||||
let lowerCache; // the nearest cache with a lower level
|
||||
if( !deqing && !highQualityReq && !downscaleReq ){
|
||||
for( let l = lvl - 1; l >= minLvl; l-- ){
|
||||
let c = lookup.get( ele, l );
|
||||
|
||||
if( c ){ lowerCache = c; break; }
|
||||
}
|
||||
}
|
||||
|
||||
if( scalableFrom(lowerCache) ){
|
||||
// then use the lower quality cache for now and queue the better one for later
|
||||
|
||||
self.queueElement( ele, lvl );
|
||||
|
||||
return lowerCache;
|
||||
}
|
||||
|
||||
txr.context.translate( txr.usedWidth, 0 );
|
||||
txr.context.scale( scale, scale );
|
||||
|
||||
this.drawElement( txr.context, ele, bb, scaledLabelShown, false );
|
||||
|
||||
txr.context.scale( 1/scale, 1/scale );
|
||||
txr.context.translate( -txr.usedWidth, 0 );
|
||||
}
|
||||
|
||||
eleCache = {
|
||||
x: txr.usedWidth,
|
||||
texture: txr,
|
||||
level: lvl,
|
||||
scale: scale,
|
||||
width: eleScaledW,
|
||||
height: eleScaledH,
|
||||
scaledLabelShown: scaledLabelShown
|
||||
};
|
||||
|
||||
txr.usedWidth += Math.ceil( eleScaledW + eleTxrSpacing );
|
||||
|
||||
txr.eleCaches.push( eleCache );
|
||||
|
||||
lookup.set( ele, lvl, eleCache );
|
||||
|
||||
self.checkTextureFullness( txr );
|
||||
|
||||
return eleCache;
|
||||
};
|
||||
|
||||
ETCp.invalidateElements = function( eles ){
|
||||
for( let i = 0; i < eles.length; i++ ){
|
||||
this.invalidateElement(eles[i]);
|
||||
}
|
||||
};
|
||||
|
||||
ETCp.invalidateElement = function( ele ){
|
||||
let self = this;
|
||||
let lookup = self.lookup;
|
||||
let caches = [];
|
||||
let invalid = lookup.isInvalid(ele);
|
||||
|
||||
if( !invalid ){
|
||||
return; // override the invalidation request if the element key has not changed
|
||||
}
|
||||
|
||||
for( let lvl = minLvl; lvl <= maxLvl; lvl++ ){
|
||||
let cache = lookup.getForCachedKey( ele, lvl );
|
||||
|
||||
if( cache ){
|
||||
caches.push( cache );
|
||||
}
|
||||
}
|
||||
|
||||
let noOtherElesUseCache = lookup.invalidate(ele);
|
||||
|
||||
if( noOtherElesUseCache ){
|
||||
for( let i = 0; i < caches.length; i++ ){
|
||||
let cache = caches[i];
|
||||
let txr = cache.texture;
|
||||
|
||||
// remove space from the texture it belongs to
|
||||
txr.invalidatedWidth += cache.width;
|
||||
|
||||
// mark the cache as invalidated
|
||||
cache.invalidated = true;
|
||||
|
||||
// retire the texture if its utility is low
|
||||
self.checkTextureUtility( txr );
|
||||
}
|
||||
}
|
||||
|
||||
// remove from queue since the old req was for the old state
|
||||
self.removeFromQueue( ele );
|
||||
};
|
||||
|
||||
ETCp.checkTextureUtility = function( txr ){
|
||||
// invalidate all entries in the cache if the cache size is small
|
||||
if( txr.invalidatedWidth >= minUtility * txr.width ){
|
||||
this.retireTexture( txr );
|
||||
}
|
||||
};
|
||||
|
||||
ETCp.checkTextureFullness = function( txr ){
|
||||
// if texture has been mostly filled and passed over several times, remove
|
||||
// it from the queue so we don't need to waste time looking at it to put new things
|
||||
|
||||
let self = this;
|
||||
let txrQ = self.getTextureQueue( txr.height );
|
||||
|
||||
if( txr.usedWidth / txr.width > maxFullness && txr.fullnessChecks >= maxFullnessChecks ){
|
||||
removeFromArray( txrQ, txr );
|
||||
} else {
|
||||
txr.fullnessChecks++;
|
||||
}
|
||||
};
|
||||
|
||||
ETCp.retireTexture = function( txr ){
|
||||
let self = this;
|
||||
let txrH = txr.height;
|
||||
let txrQ = self.getTextureQueue( txrH );
|
||||
let lookup = this.lookup;
|
||||
|
||||
// retire the texture from the active / searchable queue:
|
||||
|
||||
removeFromArray( txrQ, txr );
|
||||
|
||||
txr.retired = true;
|
||||
|
||||
// remove the refs from the eles to the caches:
|
||||
|
||||
let eleCaches = txr.eleCaches;
|
||||
|
||||
for( let i = 0; i < eleCaches.length; i++ ){
|
||||
let eleCache = eleCaches[i];
|
||||
|
||||
lookup.deleteCache( eleCache.key, eleCache.level );
|
||||
}
|
||||
|
||||
clearArray( eleCaches );
|
||||
|
||||
// add the texture to a retired queue so it can be recycled in future:
|
||||
|
||||
let rtxtrQ = self.getRetiredTextureQueue( txrH );
|
||||
|
||||
rtxtrQ.push( txr );
|
||||
};
|
||||
|
||||
ETCp.addTexture = function( txrH, minW ){
|
||||
let self = this;
|
||||
let txrQ = self.getTextureQueue( txrH );
|
||||
let txr = {};
|
||||
|
||||
txrQ.push( txr );
|
||||
|
||||
txr.eleCaches = [];
|
||||
|
||||
txr.height = txrH;
|
||||
txr.width = Math.max( defTxrWidth, minW );
|
||||
txr.usedWidth = 0;
|
||||
txr.invalidatedWidth = 0;
|
||||
txr.fullnessChecks = 0;
|
||||
|
||||
txr.canvas = self.renderer.makeOffscreenCanvas(txr.width, txr.height);
|
||||
|
||||
txr.context = txr.canvas.getContext('2d');
|
||||
|
||||
return txr;
|
||||
};
|
||||
|
||||
ETCp.recycleTexture = function( txrH, minW ){
|
||||
let self = this;
|
||||
let txrQ = self.getTextureQueue( txrH );
|
||||
let rtxtrQ = self.getRetiredTextureQueue( txrH );
|
||||
|
||||
for( let i = 0; i < rtxtrQ.length; i++ ){
|
||||
let txr = rtxtrQ[i];
|
||||
|
||||
if( txr.width >= minW ){
|
||||
txr.retired = false;
|
||||
|
||||
txr.usedWidth = 0;
|
||||
txr.invalidatedWidth = 0;
|
||||
txr.fullnessChecks = 0;
|
||||
|
||||
clearArray( txr.eleCaches );
|
||||
|
||||
txr.context.setTransform( 1, 0, 0, 1, 0, 0 );
|
||||
txr.context.clearRect( 0, 0, txr.width, txr.height );
|
||||
|
||||
removeFromArray( rtxtrQ, txr );
|
||||
txrQ.push( txr );
|
||||
|
||||
return txr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ETCp.queueElement = function( ele, lvl ){
|
||||
let self = this;
|
||||
let q = self.getElementQueue();
|
||||
let k2q = self.getElementKeyToQueue();
|
||||
let key = this.getKey(ele);
|
||||
let existingReq = k2q[key];
|
||||
|
||||
if( existingReq ){
|
||||
// use the max lvl b/c in between lvls are cheap to make
|
||||
existingReq.level = Math.max( existingReq.level, lvl );
|
||||
|
||||
existingReq.eles.merge(ele);
|
||||
|
||||
existingReq.reqs++;
|
||||
|
||||
q.updateItem( existingReq );
|
||||
} else {
|
||||
let req = {
|
||||
eles: ele.spawn().merge(ele),
|
||||
level: lvl,
|
||||
reqs: 1,
|
||||
key
|
||||
};
|
||||
|
||||
q.push( req );
|
||||
|
||||
k2q[key] = req;
|
||||
}
|
||||
};
|
||||
|
||||
ETCp.dequeue = function( pxRatio /*, extent*/ ){
|
||||
let self = this;
|
||||
let q = self.getElementQueue();
|
||||
let k2q = self.getElementKeyToQueue();
|
||||
let dequeued = [];
|
||||
let lookup = self.lookup;
|
||||
|
||||
for( let i = 0; i < maxDeqSize; i++ ){
|
||||
if( q.size() > 0 ){
|
||||
let req = q.pop();
|
||||
let key = req.key;
|
||||
let ele = req.eles[0]; // all eles have the same key
|
||||
let cacheExists = lookup.hasCache(ele, req.level);
|
||||
|
||||
// clear out the key to req lookup
|
||||
k2q[key] = null;
|
||||
|
||||
// dequeueing isn't necessary with an existing cache
|
||||
if( cacheExists ){ continue; }
|
||||
|
||||
dequeued.push( req );
|
||||
|
||||
let bb = self.getBoundingBox( ele );
|
||||
|
||||
self.getElement( ele, bb, pxRatio, req.level, getTxrReasons.dequeue );
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return dequeued;
|
||||
};
|
||||
|
||||
ETCp.removeFromQueue = function( ele ){
|
||||
let self = this;
|
||||
let q = self.getElementQueue();
|
||||
let k2q = self.getElementKeyToQueue();
|
||||
let key = this.getKey(ele);
|
||||
let req = k2q[key];
|
||||
|
||||
if( req != null ){
|
||||
if( req.eles.length === 1 ){ // remove if last ele in the req
|
||||
// bring to front of queue
|
||||
req.reqs = MAX_INT;
|
||||
q.updateItem(req);
|
||||
|
||||
q.pop(); // remove from queue
|
||||
|
||||
k2q[key] = null; // remove from lookup map
|
||||
} else { // otherwise just remove ele from req
|
||||
req.eles.unmerge(ele);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ETCp.onDequeue = function( fn ){ this.onDequeues.push( fn ); };
|
||||
ETCp.offDequeue = function( fn ){ removeFromArray( this.onDequeues, fn ); };
|
||||
|
||||
ETCp.setupDequeueing = defs.setupDequeueing({
|
||||
deqRedrawThreshold: deqRedrawThreshold,
|
||||
deqCost: deqCost,
|
||||
deqAvgCost: deqAvgCost,
|
||||
deqNoDrawCost: deqNoDrawCost,
|
||||
deqFastCost: deqFastCost,
|
||||
deq: function( self, pxRatio, extent ){
|
||||
return self.dequeue( pxRatio, extent );
|
||||
},
|
||||
onDeqd: function( self, deqd ){
|
||||
for( let i = 0; i < self.onDequeues.length; i++ ){
|
||||
let fn = self.onDequeues[i];
|
||||
|
||||
fn( deqd );
|
||||
}
|
||||
},
|
||||
shouldRedraw: function( self, deqd, pxRatio, extent ){
|
||||
for( let i = 0; i < deqd.length; i++ ){
|
||||
let eles = deqd[i].eles;
|
||||
|
||||
for( let j = 0; j < eles.length; j++ ){
|
||||
let bb = eles[j].boundingBox();
|
||||
|
||||
if( math.boundingBoxesIntersect( bb, extent ) ){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
priority: function( self ){
|
||||
return self.renderer.beforeRenderPriorities.eleTxrDeq;
|
||||
}
|
||||
});
|
||||
|
||||
export default ElementTextureCache;
|
||||
390
frontend/node_modules/cytoscape/src/extensions/renderer/canvas/index.mjs
generated
vendored
Normal file
390
frontend/node_modules/cytoscape/src/extensions/renderer/canvas/index.mjs
generated
vendored
Normal file
@@ -0,0 +1,390 @@
|
||||
/*
|
||||
The canvas renderer was written by Yue Dong.
|
||||
|
||||
Modifications tracked on Github.
|
||||
*/
|
||||
|
||||
/* global OffscreenCanvas */
|
||||
|
||||
import * as util from '../../../util/index.mjs';
|
||||
import * as is from '../../../is.mjs';
|
||||
import { makeBoundingBox } from '../../../math.mjs';
|
||||
import ElementTextureCache from './ele-texture-cache.mjs';
|
||||
import LayeredTextureCache from './layered-texture-cache.mjs';
|
||||
|
||||
import arrowShapes from './arrow-shapes.mjs';
|
||||
import drawingElements from './drawing-elements.mjs';
|
||||
import drawingEdges from './drawing-edges.mjs';
|
||||
import drawingImages from './drawing-images.mjs';
|
||||
import drawingLabelText from './drawing-label-text.mjs';
|
||||
import drawingNodes from './drawing-nodes.mjs';
|
||||
import drawingRedraw from './drawing-redraw.mjs';
|
||||
import drawingRedrawWebGL from './webgl/drawing-redraw-webgl.mjs';
|
||||
import drawingShapes from './drawing-shapes.mjs';
|
||||
import exportImage from './export-image.mjs';
|
||||
import nodeShapes from './node-shapes.mjs';
|
||||
|
||||
var CR = CanvasRenderer;
|
||||
var CRp = CanvasRenderer.prototype;
|
||||
|
||||
CRp.CANVAS_LAYERS = 3;
|
||||
//
|
||||
CRp.SELECT_BOX = 0;
|
||||
CRp.DRAG = 1;
|
||||
CRp.NODE = 2;
|
||||
CRp.WEBGL = 3;
|
||||
|
||||
CRp.CANVAS_TYPES = [ '2d', '2d', '2d', 'webgl2' ];
|
||||
|
||||
CRp.BUFFER_COUNT = 3;
|
||||
//
|
||||
CRp.TEXTURE_BUFFER = 0;
|
||||
CRp.MOTIONBLUR_BUFFER_NODE = 1;
|
||||
CRp.MOTIONBLUR_BUFFER_DRAG = 2;
|
||||
|
||||
function CanvasRenderer( options ){
|
||||
var r = this;
|
||||
|
||||
var containerWindow = r.cy.window();
|
||||
var document = containerWindow.document;
|
||||
|
||||
if( options.webgl ){
|
||||
CRp.CANVAS_LAYERS = r.CANVAS_LAYERS = 4;
|
||||
console.log('webgl rendering enabled');
|
||||
}
|
||||
|
||||
r.data = {
|
||||
canvases: new Array( CRp.CANVAS_LAYERS ),
|
||||
contexts: new Array( CRp.CANVAS_LAYERS ),
|
||||
canvasNeedsRedraw: new Array( CRp.CANVAS_LAYERS ),
|
||||
|
||||
bufferCanvases: new Array( CRp.BUFFER_COUNT ),
|
||||
bufferContexts: new Array( CRp.CANVAS_LAYERS ),
|
||||
};
|
||||
|
||||
var tapHlOffAttr = '-webkit-tap-highlight-color';
|
||||
var tapHlOffStyle = 'rgba(0,0,0,0)';
|
||||
r.data.canvasContainer = document.createElement( 'div' ); // eslint-disable-line no-undef
|
||||
var containerStyle = r.data.canvasContainer.style;
|
||||
r.data.canvasContainer.style[tapHlOffAttr] = tapHlOffStyle;
|
||||
containerStyle.position = 'relative';
|
||||
containerStyle.zIndex = '0';
|
||||
containerStyle.overflow = 'hidden';
|
||||
|
||||
var container = options.cy.container();
|
||||
container.appendChild( r.data.canvasContainer );
|
||||
container.style[tapHlOffAttr] = tapHlOffStyle;
|
||||
|
||||
var styleMap = {
|
||||
'-webkit-user-select': 'none',
|
||||
'-moz-user-select': '-moz-none',
|
||||
'user-select': 'none',
|
||||
'-webkit-tap-highlight-color': 'rgba(0,0,0,0)',
|
||||
'outline-style': 'none',
|
||||
};
|
||||
|
||||
if(is.ms()) {
|
||||
styleMap['-ms-touch-action'] = 'none';
|
||||
styleMap['touch-action'] = 'none';
|
||||
}
|
||||
|
||||
for( var i = 0; i < CRp.CANVAS_LAYERS; i++ ){
|
||||
var canvas = r.data.canvases[ i ] = document.createElement( 'canvas' ); // eslint-disable-line no-undef
|
||||
var type = CRp.CANVAS_TYPES[ i ];
|
||||
r.data.contexts[ i ] = canvas.getContext( type );
|
||||
if( !r.data.contexts[ i ] ) {
|
||||
util.error( 'Could not create canvas of type ' + type );
|
||||
}
|
||||
Object.keys(styleMap).forEach((k) => {
|
||||
canvas.style[k] = styleMap[k];
|
||||
});
|
||||
canvas.style.position = 'absolute';
|
||||
canvas.setAttribute( 'data-id', 'layer' + i );
|
||||
canvas.style.zIndex = String( CRp.CANVAS_LAYERS - i );
|
||||
r.data.canvasContainer.appendChild( canvas );
|
||||
|
||||
r.data.canvasNeedsRedraw[ i ] = false;
|
||||
}
|
||||
r.data.topCanvas = r.data.canvases[0];
|
||||
|
||||
r.data.canvases[ CRp.NODE ].setAttribute( 'data-id', 'layer' + CRp.NODE + '-node' );
|
||||
r.data.canvases[ CRp.SELECT_BOX ].setAttribute( 'data-id', 'layer' + CRp.SELECT_BOX + '-selectbox' );
|
||||
r.data.canvases[ CRp.DRAG ].setAttribute( 'data-id', 'layer' + CRp.DRAG + '-drag' );
|
||||
if( r.data.canvases[ CRp.WEBGL ] ) {
|
||||
r.data.canvases[ CRp.WEBGL ].setAttribute( 'data-id', 'layer' + CRp.WEBGL + '-webgl' );
|
||||
}
|
||||
|
||||
for( var i = 0; i < CRp.BUFFER_COUNT; i++ ){
|
||||
r.data.bufferCanvases[ i ] = document.createElement( 'canvas' ); // eslint-disable-line no-undef
|
||||
r.data.bufferContexts[ i ] = r.data.bufferCanvases[ i ].getContext( '2d' );
|
||||
r.data.bufferCanvases[ i ].style.position = 'absolute';
|
||||
r.data.bufferCanvases[ i ].setAttribute( 'data-id', 'buffer' + i );
|
||||
r.data.bufferCanvases[ i ].style.zIndex = String( -i - 1 );
|
||||
r.data.bufferCanvases[ i ].style.visibility = 'hidden';
|
||||
//r.data.canvasContainer.appendChild(r.data.bufferCanvases[i]);
|
||||
}
|
||||
|
||||
r.pathsEnabled = true;
|
||||
|
||||
let emptyBb = makeBoundingBox();
|
||||
|
||||
let getBoxCenter = bb => ({ x: (bb.x1 + bb.x2)/2, y: (bb.y1 + bb.y2)/2 });
|
||||
|
||||
let getCenterOffset = bb => ({ x: -bb.w/2, y: -bb.h/2 });
|
||||
|
||||
let backgroundTimestampHasChanged = ele => {
|
||||
let _p = ele[0]._private;
|
||||
let same = _p.oldBackgroundTimestamp === _p.backgroundTimestamp;
|
||||
|
||||
return !same;
|
||||
};
|
||||
|
||||
let getStyleKey = ele => ele[0]._private.nodeKey;
|
||||
let getLabelKey = ele => ele[0]._private.labelStyleKey;
|
||||
let getSourceLabelKey = ele => ele[0]._private.sourceLabelStyleKey;
|
||||
let getTargetLabelKey = ele => ele[0]._private.targetLabelStyleKey;
|
||||
|
||||
let drawElement = (context, ele, bb, scaledLabelShown, useEleOpacity) => r.drawElement( context, ele, bb, false, false, useEleOpacity );
|
||||
let drawLabel = (context, ele, bb, scaledLabelShown, useEleOpacity) => r.drawElementText( context, ele, bb, scaledLabelShown, 'main', useEleOpacity );
|
||||
let drawSourceLabel = (context, ele, bb, scaledLabelShown, useEleOpacity) => r.drawElementText( context, ele, bb, scaledLabelShown, 'source', useEleOpacity );
|
||||
let drawTargetLabel = (context, ele, bb, scaledLabelShown, useEleOpacity) => r.drawElementText( context, ele, bb, scaledLabelShown, 'target', useEleOpacity );
|
||||
|
||||
let getElementBox = ele => { ele.boundingBox(); return ele[0]._private.bodyBounds; };
|
||||
let getLabelBox = ele => { ele.boundingBox(); return ele[0]._private.labelBounds.main || emptyBb; };
|
||||
let getSourceLabelBox = ele => { ele.boundingBox(); return ele[0]._private.labelBounds.source || emptyBb; };
|
||||
let getTargetLabelBox = ele => { ele.boundingBox(); return ele[0]._private.labelBounds.target || emptyBb; };
|
||||
|
||||
let isLabelVisibleAtScale = (ele, scaledLabelShown) => scaledLabelShown;
|
||||
|
||||
let getElementRotationPoint = ele => getBoxCenter( getElementBox(ele) );
|
||||
|
||||
let addTextMargin = (prefix, pt, ele) => {
|
||||
let pre = prefix ? prefix + '-' : '';
|
||||
|
||||
return {
|
||||
x: pt.x + ele.pstyle(pre + 'text-margin-x').pfValue,
|
||||
y: pt.y + ele.pstyle(pre + 'text-margin-y').pfValue
|
||||
};
|
||||
};
|
||||
|
||||
let getRsPt = (ele, x, y) => {
|
||||
let rs = ele[0]._private.rscratch;
|
||||
|
||||
return { x: rs[x], y: rs[y] };
|
||||
};
|
||||
|
||||
let getLabelRotationPoint = ele => addTextMargin('', getRsPt(ele, 'labelX', 'labelY'), ele);
|
||||
let getSourceLabelRotationPoint = ele => addTextMargin('source', getRsPt(ele, 'sourceLabelX', 'sourceLabelY'), ele);
|
||||
let getTargetLabelRotationPoint = ele => addTextMargin('target', getRsPt(ele, 'targetLabelX', 'targetLabelY'), ele);
|
||||
|
||||
let getElementRotationOffset = ele => getCenterOffset( getElementBox(ele) );
|
||||
let getSourceLabelRotationOffset = ele => getCenterOffset( getSourceLabelBox(ele) );
|
||||
let getTargetLabelRotationOffset = ele => getCenterOffset( getTargetLabelBox(ele) );
|
||||
|
||||
let getLabelRotationOffset = ele => {
|
||||
let bb = getLabelBox(ele);
|
||||
let p = getCenterOffset( getLabelBox(ele) );
|
||||
|
||||
if( ele.isNode() ){
|
||||
switch( ele.pstyle('text-halign').value ){
|
||||
case 'left':
|
||||
p.x = -bb.w - (bb.leftPad || 0);
|
||||
break;
|
||||
case 'right':
|
||||
p.x = -(bb.rightPad || 0);
|
||||
break;
|
||||
}
|
||||
|
||||
switch( ele.pstyle('text-valign').value ){
|
||||
case 'top':
|
||||
p.y = -bb.h - (bb.topPad || 0);
|
||||
break;
|
||||
case 'bottom':
|
||||
p.y = -(bb.botPad || 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
};
|
||||
|
||||
let eleTxrCache = r.data.eleTxrCache = new ElementTextureCache( r, {
|
||||
getKey: getStyleKey,
|
||||
doesEleInvalidateKey: backgroundTimestampHasChanged,
|
||||
drawElement: drawElement,
|
||||
getBoundingBox: getElementBox,
|
||||
getRotationPoint: getElementRotationPoint,
|
||||
getRotationOffset: getElementRotationOffset,
|
||||
allowEdgeTxrCaching: false,
|
||||
allowParentTxrCaching: false
|
||||
} );
|
||||
|
||||
let lblTxrCache = r.data.lblTxrCache = new ElementTextureCache( r, {
|
||||
getKey: getLabelKey,
|
||||
drawElement: drawLabel,
|
||||
getBoundingBox: getLabelBox,
|
||||
getRotationPoint: getLabelRotationPoint,
|
||||
getRotationOffset: getLabelRotationOffset,
|
||||
isVisible: isLabelVisibleAtScale
|
||||
} );
|
||||
|
||||
let slbTxrCache = r.data.slbTxrCache = new ElementTextureCache( r, {
|
||||
getKey: getSourceLabelKey,
|
||||
drawElement: drawSourceLabel,
|
||||
getBoundingBox: getSourceLabelBox,
|
||||
getRotationPoint: getSourceLabelRotationPoint,
|
||||
getRotationOffset: getSourceLabelRotationOffset,
|
||||
isVisible: isLabelVisibleAtScale
|
||||
} );
|
||||
|
||||
let tlbTxrCache = r.data.tlbTxrCache = new ElementTextureCache( r, {
|
||||
getKey: getTargetLabelKey,
|
||||
drawElement: drawTargetLabel,
|
||||
getBoundingBox: getTargetLabelBox,
|
||||
getRotationPoint: getTargetLabelRotationPoint,
|
||||
getRotationOffset: getTargetLabelRotationOffset,
|
||||
isVisible: isLabelVisibleAtScale
|
||||
} );
|
||||
|
||||
let lyrTxrCache = r.data.lyrTxrCache = new LayeredTextureCache( r );
|
||||
|
||||
r.onUpdateEleCalcs(function invalidateTextureCaches( willDraw, eles ){
|
||||
// each cache should check for sub-key diff to see that the update affects that cache particularly
|
||||
eleTxrCache.invalidateElements( eles );
|
||||
lblTxrCache.invalidateElements( eles );
|
||||
slbTxrCache.invalidateElements( eles );
|
||||
tlbTxrCache.invalidateElements( eles );
|
||||
|
||||
// any change invalidates the layers
|
||||
lyrTxrCache.invalidateElements( eles );
|
||||
|
||||
// update the old bg timestamp so diffs can be done in the ele txr caches
|
||||
for( let i = 0; i < eles.length; i++ ){
|
||||
let _p = eles[i]._private;
|
||||
|
||||
_p.oldBackgroundTimestamp = _p.backgroundTimestamp;
|
||||
}
|
||||
});
|
||||
|
||||
let refineInLayers = reqs => {
|
||||
for( var i = 0; i < reqs.length; i++ ){
|
||||
lyrTxrCache.enqueueElementRefinement( reqs[i].ele );
|
||||
}
|
||||
};
|
||||
|
||||
eleTxrCache.onDequeue(refineInLayers);
|
||||
lblTxrCache.onDequeue(refineInLayers);
|
||||
slbTxrCache.onDequeue(refineInLayers);
|
||||
tlbTxrCache.onDequeue(refineInLayers);
|
||||
|
||||
if( options.webgl ) {
|
||||
r.initWebgl( options, {
|
||||
getStyleKey,
|
||||
getLabelKey,
|
||||
getSourceLabelKey,
|
||||
getTargetLabelKey,
|
||||
drawElement,
|
||||
drawLabel,
|
||||
drawSourceLabel,
|
||||
drawTargetLabel,
|
||||
getElementBox,
|
||||
getLabelBox,
|
||||
getSourceLabelBox,
|
||||
getTargetLabelBox,
|
||||
getElementRotationPoint,
|
||||
getElementRotationOffset,
|
||||
getLabelRotationPoint,
|
||||
getSourceLabelRotationPoint,
|
||||
getTargetLabelRotationPoint,
|
||||
getLabelRotationOffset,
|
||||
getSourceLabelRotationOffset,
|
||||
getTargetLabelRotationOffset
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
CRp.redrawHint = function( group, bool ){
|
||||
var r = this;
|
||||
|
||||
switch( group ){
|
||||
case 'eles':
|
||||
r.data.canvasNeedsRedraw[ CRp.NODE ] = bool;
|
||||
break;
|
||||
case 'drag':
|
||||
r.data.canvasNeedsRedraw[ CRp.DRAG ] = bool;
|
||||
break;
|
||||
case 'select':
|
||||
r.data.canvasNeedsRedraw[ CRp.SELECT_BOX ] = bool;
|
||||
break;
|
||||
case 'gc':
|
||||
r.data.gc = true;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// whether to use Path2D caching for drawing
|
||||
var pathsImpld = typeof Path2D !== 'undefined';
|
||||
|
||||
CRp.path2dEnabled = function( on ){
|
||||
if( on === undefined ){
|
||||
return this.pathsEnabled;
|
||||
}
|
||||
|
||||
this.pathsEnabled = on ? true : false;
|
||||
};
|
||||
|
||||
CRp.usePaths = function(){
|
||||
return pathsImpld && this.pathsEnabled;
|
||||
};
|
||||
|
||||
CRp.setImgSmoothing = function( context, bool ){
|
||||
if( context.imageSmoothingEnabled != null ){
|
||||
context.imageSmoothingEnabled = bool;
|
||||
} else {
|
||||
context.webkitImageSmoothingEnabled = bool;
|
||||
context.mozImageSmoothingEnabled = bool;
|
||||
context.msImageSmoothingEnabled = bool;
|
||||
}
|
||||
};
|
||||
|
||||
CRp.getImgSmoothing = function( context ){
|
||||
if( context.imageSmoothingEnabled != null ){
|
||||
return context.imageSmoothingEnabled;
|
||||
} else {
|
||||
return context.webkitImageSmoothingEnabled || context.mozImageSmoothingEnabled || context.msImageSmoothingEnabled;
|
||||
}
|
||||
};
|
||||
|
||||
CRp.makeOffscreenCanvas = function(width, height){
|
||||
let canvas;
|
||||
|
||||
if( typeof OffscreenCanvas !== typeof undefined ){
|
||||
canvas = new OffscreenCanvas(width, height);
|
||||
} else {
|
||||
var containerWindow = this.cy.window();
|
||||
var document = containerWindow.document;
|
||||
canvas = document.createElement('canvas'); // eslint-disable-line no-undef
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
}
|
||||
|
||||
return canvas;
|
||||
};
|
||||
|
||||
[
|
||||
arrowShapes,
|
||||
drawingElements,
|
||||
drawingEdges,
|
||||
drawingImages,
|
||||
drawingLabelText,
|
||||
drawingNodes,
|
||||
drawingRedraw,
|
||||
drawingRedrawWebGL,
|
||||
drawingShapes,
|
||||
exportImage,
|
||||
nodeShapes
|
||||
].forEach( function( props ){
|
||||
util.extend( CRp, props );
|
||||
} );
|
||||
|
||||
export default CR;
|
||||
1150
frontend/node_modules/cytoscape/src/extensions/renderer/canvas/webgl/drawing-elements-webgl.mjs
generated
vendored
Normal file
1150
frontend/node_modules/cytoscape/src/extensions/renderer/canvas/webgl/drawing-elements-webgl.mjs
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
208
frontend/node_modules/cytoscape/src/extensions/renderer/canvas/webgl/misc-upscaler.js
generated
vendored
Normal file
208
frontend/node_modules/cytoscape/src/extensions/renderer/canvas/webgl/misc-upscaler.js
generated
vendored
Normal file
@@ -0,0 +1,208 @@
|
||||
// Canvas Upscaling Plugin
|
||||
class MiscUpscaler {
|
||||
constructor(options = {}) {
|
||||
// Plugin Settings
|
||||
this.options = Object.assign({
|
||||
useEdgeDetection: true, // Edge Detection
|
||||
scaleFactor: 2.0, // Upscaling Value
|
||||
}, options);
|
||||
|
||||
this.gl = null;
|
||||
this.program = null;
|
||||
this.frameBuffer = null;
|
||||
this.texture = null;
|
||||
this.outputTexture = null;
|
||||
}
|
||||
|
||||
init(inputCanvas, outputCanvas) {
|
||||
this.inputCanvas = inputCanvas;
|
||||
this.outputCanvas = outputCanvas;
|
||||
|
||||
// Set output canvas size based on scale factor
|
||||
this.outputCanvas.width = this.inputCanvas.width * this.options.scaleFactor;
|
||||
this.outputCanvas.height = this.inputCanvas.height * this.options.scaleFactor;
|
||||
|
||||
// Initialize WebGL context
|
||||
this.gl = this.outputCanvas.getContext('webgl2');
|
||||
if (!this.gl) {
|
||||
console.error('WebGL not supported');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialize shaders and buffers
|
||||
this.initShaders();
|
||||
this.initBuffers();
|
||||
this.initTextures();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
initShaders() {
|
||||
// Vertex shader
|
||||
const vertexShaderSource = `
|
||||
attribute vec2 a_position;
|
||||
attribute vec2 a_texCoord;
|
||||
varying vec2 vUv;
|
||||
void main() {
|
||||
vUv = a_texCoord;
|
||||
gl_Position = vec4(a_position, 0.0, 1.0);
|
||||
}
|
||||
`;
|
||||
|
||||
// Fragment shader
|
||||
const fragmentShaderSource = `
|
||||
precision mediump float;
|
||||
uniform sampler2D tDiffuse;
|
||||
uniform vec2 resolution;
|
||||
varying vec2 vUv;
|
||||
|
||||
void main() {
|
||||
vec2 texelSize = 1.0 / resolution;
|
||||
|
||||
// Get Neighbor Pixels
|
||||
vec4 color = texture2D(tDiffuse, vUv);
|
||||
vec4 colorUp = texture2D(tDiffuse, vUv + vec2(0.0, texelSize.y));
|
||||
vec4 colorDown = texture2D(tDiffuse, vUv - vec2(0.0, texelSize.y));
|
||||
vec4 colorLeft = texture2D(tDiffuse, vUv - vec2(texelSize.x, 0.0));
|
||||
vec4 colorRight = texture2D(tDiffuse, vUv + vec2(texelSize.x, 0.0));
|
||||
|
||||
// Work with edges
|
||||
float edgeStrength = 1.0 - smoothstep(0.1, 0.3, length(color.rgb - colorUp.rgb));
|
||||
edgeStrength += 1.0 - smoothstep(0.1, 0.3, length(color.rgb - colorDown.rgb));
|
||||
edgeStrength += 1.0 - smoothstep(0.1, 0.3, length(color.rgb - colorLeft.rgb));
|
||||
edgeStrength += 1.0 - smoothstep(0.1, 0.3, length(color.rgb - colorRight.rgb));
|
||||
edgeStrength = clamp(edgeStrength, 0.0, 1.0);
|
||||
|
||||
// Applying edges incresing and filtering
|
||||
vec3 enhancedColor = mix(color.rgb, vec3(1.0) - (1.0 - color.rgb) * edgeStrength, 0.5);
|
||||
|
||||
gl_FragColor = vec4(enhancedColor, color.a);
|
||||
}
|
||||
`;
|
||||
|
||||
// Create shader program
|
||||
const vertexShader = this.createShader(this.gl.VERTEX_SHADER, vertexShaderSource);
|
||||
const fragmentShader = this.createShader(this.gl.FRAGMENT_SHADER, fragmentShaderSource);
|
||||
this.program = this.createProgram(vertexShader, fragmentShader);
|
||||
|
||||
// Get attribute and uniform locations
|
||||
this.positionLocation = this.gl.getAttribLocation(this.program, 'a_position');
|
||||
this.texCoordLocation = this.gl.getAttribLocation(this.program, 'a_texCoord');
|
||||
this.resolutionLocation = this.gl.getUniformLocation(this.program, 'resolution');
|
||||
this.textureLocation = this.gl.getUniformLocation(this.program, 'tDiffuse');
|
||||
}
|
||||
|
||||
createShader(type, source) {
|
||||
const shader = this.gl.createShader(type);
|
||||
this.gl.shaderSource(shader, source);
|
||||
this.gl.compileShader(shader);
|
||||
|
||||
if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
|
||||
console.error('Shader compile error:', this.gl.getShaderInfoLog(shader));
|
||||
this.gl.deleteShader(shader);
|
||||
return null;
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
createProgram(vertexShader, fragmentShader) {
|
||||
const program = this.gl.createProgram();
|
||||
this.gl.attachShader(program, vertexShader);
|
||||
this.gl.attachShader(program, fragmentShader);
|
||||
this.gl.linkProgram(program);
|
||||
|
||||
if (!this.gl.getProgramParameter(program, this.gl.LINK_STATUS)) {
|
||||
console.error('Program link error:', this.gl.getProgramInfoLog(program));
|
||||
return null;
|
||||
}
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
initBuffers() {
|
||||
// Create a buffer for positions
|
||||
this.positionBuffer = this.gl.createBuffer();
|
||||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer);
|
||||
|
||||
// Full screen quad (2 triangles)
|
||||
const positions = [
|
||||
-1, -1,
|
||||
1, -1,
|
||||
-1, 1,
|
||||
-1, 1,
|
||||
1, -1,
|
||||
1, 1
|
||||
];
|
||||
this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(positions), this.gl.STATIC_DRAW);
|
||||
|
||||
// Create a buffer for texture coordinates
|
||||
this.texCoordBuffer = this.gl.createBuffer();
|
||||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer);
|
||||
|
||||
// Texture coordinates for the quad with Y-flipped to match canvas coordinates
|
||||
const texCoords = [
|
||||
0, 1, // top-left (flipped from 0,0)
|
||||
1, 1, // top-right (flipped from 1,0)
|
||||
0, 0, // bottom-left (flipped from 0,1)
|
||||
0, 0, // bottom-left (flipped from 0,1)
|
||||
1, 1, // top-right (flipped from 1,0)
|
||||
1, 0 // bottom-right (flipped from 1,1)
|
||||
];
|
||||
this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(texCoords), this.gl.STATIC_DRAW);
|
||||
}
|
||||
|
||||
initTextures() {
|
||||
// Create texture for input canvas
|
||||
this.texture = this.gl.createTexture();
|
||||
this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);
|
||||
|
||||
// Set parameters for texture
|
||||
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
|
||||
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);
|
||||
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR);
|
||||
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.gl) return;
|
||||
|
||||
// Update texture from input canvas
|
||||
this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);
|
||||
this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, this.inputCanvas);
|
||||
|
||||
// Set viewport and clear
|
||||
this.gl.viewport(0, 0, this.outputCanvas.width, this.outputCanvas.height);
|
||||
this.gl.clearColor(0, 0, 0, 0);
|
||||
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
|
||||
|
||||
// Use shader program
|
||||
this.gl.useProgram(this.program);
|
||||
|
||||
// Set uniforms
|
||||
this.gl.uniform2f(this.resolutionLocation, this.inputCanvas.width, this.inputCanvas.height);
|
||||
this.gl.uniform1i(this.textureLocation, 0);
|
||||
|
||||
// Set position attribute
|
||||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer);
|
||||
this.gl.enableVertexAttribArray(this.positionLocation);
|
||||
this.gl.vertexAttribPointer(this.positionLocation, 2, this.gl.FLOAT, false, 0, 0);
|
||||
|
||||
// Set texture coordinate attribute
|
||||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer);
|
||||
this.gl.enableVertexAttribArray(this.texCoordLocation);
|
||||
this.gl.vertexAttribPointer(this.texCoordLocation, 2, this.gl.FLOAT, false, 0, 0);
|
||||
|
||||
// Draw
|
||||
this.gl.drawArrays(this.gl.TRIANGLES, 0, 6);
|
||||
}
|
||||
|
||||
resize() {
|
||||
if (this.inputCanvas && this.outputCanvas) {
|
||||
this.outputCanvas.width = this.inputCanvas.width * this.options.scaleFactor;
|
||||
this.outputCanvas.height = this.inputCanvas.height * this.options.scaleFactor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { MiscUpscaler as UpscalerPlugin };
|
||||
9
frontend/node_modules/cytoscape/src/extensions/renderer/index.mjs
generated
vendored
Normal file
9
frontend/node_modules/cytoscape/src/extensions/renderer/index.mjs
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import nullRenderer from './null/index.mjs';
|
||||
import baseRenderer from './base/index.mjs';
|
||||
import canvasRenderer from './canvas/index.mjs';
|
||||
|
||||
export default [
|
||||
{ name: 'null', impl: nullRenderer },
|
||||
{ name: 'base', impl: baseRenderer },
|
||||
{ name: 'canvas', impl: canvasRenderer },
|
||||
];
|
||||
Reference in New Issue
Block a user