You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
297 lines
13 KiB
297 lines
13 KiB
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.Fisheye = void 0;
|
|
const util_1 = require("@antv/util");
|
|
const constants_1 = require("../../constants");
|
|
const elements_1 = require("../../elements");
|
|
const diff_1 = require("../../utils/diff");
|
|
const id_1 = require("../../utils/id");
|
|
const point_1 = require("../../utils/point");
|
|
const position_1 = require("../../utils/position");
|
|
const vector_1 = require("../../utils/vector");
|
|
const base_plugin_1 = require("../base-plugin");
|
|
const defaultLensStyle = {
|
|
fill: '#ccc',
|
|
fillOpacity: 0.1,
|
|
lineWidth: 2,
|
|
stroke: '#000',
|
|
strokeOpacity: 0.8,
|
|
labelFontSize: 12,
|
|
};
|
|
const R_DELTA = 0.05;
|
|
const D_DELTA = 0.1;
|
|
/**
|
|
* <zh/> 鱼眼放大镜
|
|
*
|
|
* <en/> Fisheye Distortion
|
|
* @remarks
|
|
* <zh/> Fisheye 鱼眼放大镜是为 focus+context 的探索场景设计的,它能够保证在放大关注区域的同时,保证上下文以及上下文与关注中心的关系不丢失。
|
|
*
|
|
* <en/> Fisheye is designed for focus+context exploration, it keeps the context and the relationships between context and the focus while magnifying the focus area.
|
|
*/
|
|
class Fisheye extends base_plugin_1.BasePlugin {
|
|
constructor(context, options) {
|
|
super(context, Object.assign({}, Fisheye.defaultOptions, options));
|
|
this.r = this.options.r;
|
|
this.d = this.options.d;
|
|
this.onCreateFisheye = (event) => {
|
|
if (this.options.trigger === 'drag' && this.isLensOn)
|
|
return;
|
|
const origin = (0, point_1.parsePoint)(event.canvas);
|
|
this.onMagnify(origin);
|
|
};
|
|
this.onMagnify = (origin) => {
|
|
if (origin.some(isNaN))
|
|
return;
|
|
this.renderLens(origin);
|
|
this.renderFocusElements();
|
|
};
|
|
this.renderLens = (origin) => {
|
|
const style = Object.assign({}, defaultLensStyle, this.options.style);
|
|
if (!this.isLensOn) {
|
|
this.lens = new elements_1.Circle({ style });
|
|
this.canvas.appendChild(this.lens);
|
|
}
|
|
Object.assign(style, (0, point_1.toPointObject)(origin), {
|
|
size: this.r * 2,
|
|
label: this.options.showDPercent,
|
|
labelText: this.getDPercent(),
|
|
});
|
|
this.lens.update(style);
|
|
};
|
|
this.getDPercent = () => {
|
|
const { minD, maxD } = this.options;
|
|
const percent = Math.round(((this.d - minD) / (maxD - minD)) * 100);
|
|
return `${percent}%`;
|
|
};
|
|
this.prevMagnifiedStyleMap = new Map();
|
|
this.prevOriginStyleMap = new Map();
|
|
this.renderFocusElements = () => {
|
|
if (!this.isLensOn)
|
|
return;
|
|
const { graph } = this.context;
|
|
const origin = this.lens.getCenter();
|
|
const molecularParam = (this.d + 1) * this.r;
|
|
const magnifiedStyleMap = new Map();
|
|
const originStyleMap = new Map();
|
|
const nodeData = graph.getNodeData();
|
|
nodeData.forEach((datum) => {
|
|
const position = (0, position_1.positionOf)(datum);
|
|
const distanceToOrigin = (0, vector_1.distance)(position, origin);
|
|
if (distanceToOrigin > this.r)
|
|
return;
|
|
const magnifiedDistance = (molecularParam * distanceToOrigin) / (this.d * distanceToOrigin + this.r);
|
|
const [nodeX, nodeY] = position;
|
|
const [originX, originY] = origin;
|
|
const cos = (nodeX - originX) / distanceToOrigin;
|
|
const sin = (nodeY - originY) / distanceToOrigin;
|
|
const newPoint = [originX + magnifiedDistance * cos, originY + magnifiedDistance * sin];
|
|
const nodeId = (0, id_1.idOf)(datum);
|
|
const style = this.getNodeStyle(datum);
|
|
const originStyle = (0, util_1.pick)(graph.getElementRenderStyle(nodeId), Object.keys(style));
|
|
magnifiedStyleMap.set(nodeId, Object.assign(Object.assign({}, (0, point_1.toPointObject)(newPoint)), style));
|
|
originStyleMap.set(nodeId, Object.assign(Object.assign({}, (0, point_1.toPointObject)(position)), originStyle));
|
|
});
|
|
this.updateStyle(magnifiedStyleMap, originStyleMap);
|
|
};
|
|
this.getNodeStyle = (datum) => {
|
|
const { nodeStyle } = this.options;
|
|
return typeof nodeStyle === 'function' ? nodeStyle(datum) : nodeStyle;
|
|
};
|
|
this.updateStyle = (magnifiedStyleMap, originStyleMap) => {
|
|
const { graph, element } = this.context;
|
|
const { enter, exit, keep } = (0, diff_1.arrayDiff)(Array.from(this.prevMagnifiedStyleMap.keys()), Array.from(magnifiedStyleMap.keys()), (d) => d);
|
|
const relatedEdges = new Set();
|
|
const update = (nodeId, style) => {
|
|
const node = element.getElement(nodeId);
|
|
node === null || node === void 0 ? void 0 : node.update(style);
|
|
graph.getRelatedEdgesData(nodeId).forEach((datum) => {
|
|
relatedEdges.add((0, id_1.idOf)(datum));
|
|
});
|
|
};
|
|
[...enter, ...keep].forEach((nodeId) => {
|
|
update(nodeId, magnifiedStyleMap.get(nodeId));
|
|
});
|
|
exit.forEach((nodeId) => {
|
|
update(nodeId, this.prevOriginStyleMap.get(nodeId));
|
|
this.prevOriginStyleMap.delete(nodeId);
|
|
});
|
|
relatedEdges.forEach((edgeId) => {
|
|
const edge = element.getElement(edgeId);
|
|
edge === null || edge === void 0 ? void 0 : edge.update({});
|
|
});
|
|
this.prevMagnifiedStyleMap = magnifiedStyleMap;
|
|
originStyleMap.forEach((style, nodeId) => {
|
|
if (!this.prevOriginStyleMap.has(nodeId)) {
|
|
this.prevOriginStyleMap.set(nodeId, style);
|
|
}
|
|
});
|
|
};
|
|
this.isWheelValid = (event) => {
|
|
if (this.options.preventDefault)
|
|
event.preventDefault();
|
|
if (!this.isLensOn)
|
|
return false;
|
|
const { clientX, clientY } = event;
|
|
const scaleOrigin = this.context.graph.getCanvasByClient([clientX, clientY]);
|
|
const origin = this.lens.getCenter();
|
|
if ((0, vector_1.distance)(scaleOrigin, origin) > this.r)
|
|
return false;
|
|
return true;
|
|
};
|
|
this.scaleR = (positive) => {
|
|
const { maxR, minR } = this.options;
|
|
const ratio = positive ? 1 / (1 - R_DELTA) : 1 - R_DELTA;
|
|
const canvasR = Math.min(...this.context.canvas.getSize()) / 2;
|
|
this.r = Math.max(minR || 0, Math.min(maxR || canvasR, this.r * ratio));
|
|
};
|
|
this.scaleD = (positive) => {
|
|
const { maxD, minD } = this.options;
|
|
const newD = positive ? this.d + D_DELTA : this.d - D_DELTA;
|
|
this.d = Math.max(minD, Math.min(maxD, newD));
|
|
};
|
|
this.scaleRByWheel = (event) => {
|
|
if (!this.isWheelValid(event))
|
|
return;
|
|
const { deltaX, deltaY } = event;
|
|
this.scaleR(deltaX + deltaY > 0);
|
|
const origin = this.lens.getCenter();
|
|
this.onMagnify(origin);
|
|
};
|
|
this.scaleDByWheel = (event) => {
|
|
if (!this.isWheelValid(event))
|
|
return;
|
|
const { deltaX, deltaY } = event;
|
|
this.scaleD(deltaX + deltaY > 0);
|
|
const origin = this.lens.getCenter();
|
|
this.onMagnify(origin);
|
|
};
|
|
this.isDragValid = (event) => {
|
|
if (this.options.preventDefault)
|
|
event.preventDefault();
|
|
if (!this.isLensOn)
|
|
return false;
|
|
const dragOrigin = (0, point_1.parsePoint)(event.canvas);
|
|
const origin = this.lens.getCenter();
|
|
if ((0, vector_1.distance)(dragOrigin, origin) > this.r)
|
|
return false;
|
|
return true;
|
|
};
|
|
this.isLensDragging = false;
|
|
this.onDragStart = (event) => {
|
|
if (!this.isDragValid(event))
|
|
return;
|
|
this.isLensDragging = true;
|
|
};
|
|
this.onDrag = (event) => {
|
|
if (!this.isLensDragging)
|
|
return;
|
|
const dragOrigin = (0, point_1.parsePoint)(event.canvas);
|
|
this.onMagnify(dragOrigin);
|
|
};
|
|
this.onDragEnd = () => {
|
|
this.isLensDragging = false;
|
|
};
|
|
this.scaleRByDrag = (event) => {
|
|
if (!this.isLensDragging)
|
|
return;
|
|
const { dx, dy } = event;
|
|
this.scaleR(dx - dy > 0);
|
|
const origin = this.lens.getCenter();
|
|
this.onMagnify(origin);
|
|
};
|
|
this.scaleDByDrag = (event) => {
|
|
if (!this.isLensDragging)
|
|
return;
|
|
const { dx, dy } = event;
|
|
this.scaleD(dx - dy > 0);
|
|
const origin = this.lens.getCenter();
|
|
this.onMagnify(origin);
|
|
};
|
|
this.bindEvents();
|
|
}
|
|
get canvas() {
|
|
return this.context.canvas.getLayer('transient');
|
|
}
|
|
get isLensOn() {
|
|
return this.lens && !this.lens.destroyed;
|
|
}
|
|
get graphDom() {
|
|
return this.context.graph.getCanvas().getContextService().getDomElement();
|
|
}
|
|
bindEvents() {
|
|
var _a;
|
|
const { graph } = this.context;
|
|
const { trigger, scaleRBy, scaleDBy } = this.options;
|
|
const canvas = graph.getCanvas().getLayer();
|
|
if (['click', 'drag'].includes(trigger)) {
|
|
canvas.addEventListener(constants_1.CommonEvent.CLICK, this.onCreateFisheye);
|
|
}
|
|
if (trigger === 'pointermove') {
|
|
canvas.addEventListener(constants_1.CommonEvent.POINTER_MOVE, this.onCreateFisheye);
|
|
}
|
|
if (trigger === 'drag' || scaleRBy === 'drag' || scaleDBy === 'drag') {
|
|
canvas.addEventListener(constants_1.CommonEvent.DRAG_START, this.onDragStart);
|
|
canvas.addEventListener(constants_1.CommonEvent.DRAG_END, this.onDragEnd);
|
|
const dragFunc = trigger === 'drag' ? this.onDrag : scaleRBy === 'drag' ? this.scaleRByDrag : this.scaleDByDrag;
|
|
canvas.addEventListener(constants_1.CommonEvent.DRAG, dragFunc);
|
|
}
|
|
if (scaleRBy === 'wheel' || scaleDBy === 'wheel') {
|
|
const wheelFunc = scaleRBy === 'wheel' ? this.scaleRByWheel : this.scaleDByWheel;
|
|
(_a = this.graphDom) === null || _a === void 0 ? void 0 : _a.addEventListener(constants_1.CommonEvent.WHEEL, wheelFunc, { passive: false });
|
|
}
|
|
}
|
|
unbindEvents() {
|
|
var _a;
|
|
const { graph } = this.context;
|
|
const { trigger, scaleRBy, scaleDBy } = this.options;
|
|
const canvas = graph.getCanvas().getLayer();
|
|
if (['click', 'drag'].includes(trigger)) {
|
|
canvas.removeEventListener(constants_1.CommonEvent.CLICK, this.onCreateFisheye);
|
|
}
|
|
if (trigger === 'pointermove') {
|
|
canvas.removeEventListener(constants_1.CommonEvent.POINTER_MOVE, this.onCreateFisheye);
|
|
}
|
|
if (trigger === 'drag' || scaleRBy === 'drag' || scaleDBy === 'drag') {
|
|
canvas.removeEventListener(constants_1.CommonEvent.DRAG_START, this.onDragStart);
|
|
canvas.removeEventListener(constants_1.CommonEvent.DRAG_END, this.onDragEnd);
|
|
const dragFunc = trigger === 'drag' ? this.onDrag : scaleRBy === 'drag' ? this.scaleRByDrag : this.scaleDByDrag;
|
|
canvas.removeEventListener(constants_1.CommonEvent.DRAG, dragFunc);
|
|
}
|
|
if (scaleRBy === 'wheel' || scaleDBy === 'wheel') {
|
|
const wheelFunc = scaleRBy === 'wheel' ? this.scaleRByWheel : this.scaleDByWheel;
|
|
(_a = this.graphDom) === null || _a === void 0 ? void 0 : _a.removeEventListener(constants_1.CommonEvent.WHEEL, wheelFunc);
|
|
}
|
|
}
|
|
update(options) {
|
|
var _a, _b;
|
|
this.unbindEvents();
|
|
super.update(options);
|
|
this.r = (_a = options.r) !== null && _a !== void 0 ? _a : this.r;
|
|
this.d = (_b = options.d) !== null && _b !== void 0 ? _b : this.d;
|
|
this.bindEvents();
|
|
}
|
|
destroy() {
|
|
var _a;
|
|
this.unbindEvents();
|
|
if (this.isLensOn) {
|
|
(_a = this.lens) === null || _a === void 0 ? void 0 : _a.destroy();
|
|
}
|
|
this.prevMagnifiedStyleMap.clear();
|
|
this.prevOriginStyleMap.clear();
|
|
super.destroy();
|
|
}
|
|
}
|
|
exports.Fisheye = Fisheye;
|
|
Fisheye.defaultOptions = {
|
|
trigger: 'pointermove',
|
|
r: 120,
|
|
d: 1.5,
|
|
maxD: 5,
|
|
minD: 0,
|
|
showDPercent: true,
|
|
style: {},
|
|
nodeStyle: { label: true },
|
|
preventDefault: true,
|
|
};
|
|
//# sourceMappingURL=index.js.map
|