UNPKG

33.4 kBJavaScriptView Raw
1import { isFinite, map, omit, toPairs } from "lodash/fp";
2import { isAttachablePoint, isDefinedCXML, isGPMLAnchor, isPvjsonBurr, isPvjsonSingleFreeNode, isPvjsonGroup, unionLSV } from "../gpml-utilities";
3import { SmartPoint } from "../geom-utils";
4import { calculateAllPoints } from "./calculateAllPoints";
5import * as VError from "verror";
6import * as MarkerMappings from "./MarkerMappings.json";
7// a stub is a short path segment that is used for the first and/or last segment(s) of a path
8export const DEFAULT_STUB_LENGTH = 20;
9/**
10 * getOffsetAndOrientationScalarsAlongAxis
11 *
12 * @param relValue {number}
13 * @param axis {string}
14 * @param referencedEntity
15 * @return {OffsetOrientationAndPositionScalarsAlongAxis}
16 */
17function getOffsetAndOrientationScalarsAlongAxis(positionScalar, relativeOffsetScalar, axis,
18// TODO are we correctly handling the case of a group as the referenced
19// entity? Do we have the group width and height yet to properly calculate
20// this?
21referencedEntity) {
22 let offsetScalar = relativeOffsetScalar *
23 (axis === "x" ? referencedEntity.width : referencedEntity.height);
24 // TODO WP536 has a referenced entity that lacks width/height. Why?
25 // The referenced entity was a group.
26 // Is the problem that the group was nested?
27 // Or is it a problem with the order of evaluation of entities (trying to
28 // parse a dependent entity before its dependencies were parsed)?
29 if (!isFinite(offsetScalar)) {
30 throw new Error(`
31 Got non-finite value ${offsetScalar} for offsetScalar
32 along ${axis} axis for
33 getOffsetAndOrientationScalarsAlongAxis(
34 positionScalar=${positionScalar},
35 relativeOffsetScalar=${relativeOffsetScalar},
36 referencedEntity=
37 ${JSON.stringify(referencedEntity, null, " ")}
38 )
39 `);
40 }
41 // orientationScalar here refers to the initial direction the edge takes as
42 // it moves away from the entity to which it is attached.
43 let orientationScalar;
44 if (positionScalar === 0) {
45 orientationScalar = -1;
46 }
47 else if (positionScalar === 1) {
48 orientationScalar = 1;
49 }
50 else {
51 orientationScalar = 0;
52 }
53 return { offsetScalar, orientationScalar, positionScalar };
54}
55/**
56 * preprocessGPML
57 *
58 * @param edge {GPMLEdge}
59 * @return {GPMLEdge}
60 */
61export function preprocessGPML(Edge) {
62 const isAttachedToOrVia = Edge.Graphics.Point
63 .filter(p => p.GraphRef && isDefinedCXML(p.GraphRef))
64 .map(p => p.GraphRef);
65 if (isAttachedToOrVia.length > 0) {
66 // In pvjson, an edge attaches directly to another entity (Node, Edge, Group),
67 // not to an anchor.
68 // If the edge attaches to another edge, it does so VIA an anchor.
69 Edge["isAttachedToOrVia"] = isAttachedToOrVia;
70 }
71 return Edge;
72}
73/**
74 * postprocessPVJSON
75 *
76 * @param referencedEntities
77 * @param pvjsonEdge {pvjsonEdge}
78 * @return {pvjsonEdge}
79 */
80export function postprocessPVJSON(referencedEntities, pvjsonEdge) {
81 const { points, drawAs } = pvjsonEdge;
82 const pointCount = points.length;
83 let index = 0;
84 const pvjsonEdgeIsAttachedTo = [];
85 const providedPvjsonPoints = map(function (point) {
86 const { marker, x, y } = point;
87 if (!!marker) {
88 // NOTE: side effects below
89 if (index === 0) {
90 pvjsonEdge.markerStart = marker;
91 }
92 else if (index === pointCount - 1) {
93 pvjsonEdge.markerEnd = marker;
94 }
95 if (MarkerMappings.hasOwnProperty(marker)) {
96 pvjsonEdge.type = toPairs(MarkerMappings[marker]).reduce(function (acc, [namespace, moreTypes]) {
97 return unionLSV(acc, moreTypes);
98 }, pvjsonEdge.type);
99 }
100 }
101 if (isAttachablePoint(point)) {
102 // NOTE: pvjson allows for expressing one edge attached to another edge.
103 // When we do this, we say that the POINT attaches to an ANCHOR on the other edge,
104 // but the EDGE attaches to the other EDGE, never the anchor.
105 const { isAttachedTo, attachmentDisplay } = point;
106 if (!attachmentDisplay.offset) {
107 throw new Error(`attachmentDisplay for a Point has no offset property.
108 postprocessPVJSON(
109 referencedEntities=${JSON.stringify(referencedEntities, null, " ")},
110 pvjsonEdge=${JSON.stringify(pvjsonEdge, null, " ")}
111 )`);
112 }
113 // entityReferencedByPoint can be a regular node (DataNode, Shape, Label)
114 // or an Anchor. If connected to an Anchor, the biological meaning is
115 // that the edge is connected to another edge, but in this code, we
116 // implement this by treating the Anchor as a node, as if it were
117 // a "burr" that is always stuck (isAttachedTo) the other edge.
118 const entityReferencedByPoint = referencedEntities &&
119 !!isAttachedTo &&
120 referencedEntities[isAttachedTo];
121 const entityIdReferencedByEdge = isGPMLAnchor(entityReferencedByPoint)
122 ? entityReferencedByPoint.isAttachedTo
123 : entityReferencedByPoint.id;
124 // WARNING: side effect
125 pvjsonEdgeIsAttachedTo.push(entityIdReferencedByEdge);
126 const entityReferencedByEdge = referencedEntities[entityIdReferencedByEdge];
127 const orientation = (point.orientation =
128 point.orientation || []);
129 // attachmentDisplay: { position: [x: number, y: number], offset: [xOffset: number, yOffset: number], orientation: [dx: number, dy: number] }
130 //
131 // x = xDistance / width (relative: [0,1])
132 // y = yDistance / height (relative: [0,1])
133 // xOffset = distance offset in x direction (absolute)
134 // yOffset = distance offset in y direction (absolute)
135 // dx = x component of edge emanation angle (unit: [0,1])
136 // dy = y component of edge emanation angle (unit: [0,1])
137 //
138 // 0 ----------------- x ------------------->
139 // | ========================================
140 // | || ||
141 // | || ||
142 // | || ||
143 // y || ||
144 // | || ||
145 // | || ||
146 // | || ||
147 // | || ||
148 // v ===================*====================
149 // |
150 // yOffset |
151 // | |
152 // v |
153 // ----------*
154 // xOffset> \
155 // \
156 // \ dx>
157 // dy \
158 // | \
159 // v \
160 // \
161 //
162 // example above is an attachmentDisplay specifying an edge that emanates down and to the right
163 // at a 45 deg. angle (1, 1), offset right 5 x units and down 11 y units from the center (0.5)
164 // of the bottom side (1) of the node: {position: [0.75, 1], offset: [5, 11], orientation: [1, 1]}
165 //
166 //
167 // where x is distance from left side along width axis as a percentage of the total width
168 // y is distance from top side along height axis as a percentage of the total height
169 // offsetX, offsetY are obvious from the name. Notice they are absolute, unlike x,y.
170 // dx, dy are unit vector coordinates of a point that specifies how the edge emanates from the node
171 if (isPvjsonSingleFreeNode(entityReferencedByEdge) ||
172 isPvjsonGroup(entityReferencedByEdge) ||
173 isPvjsonBurr(entityReferencedByEdge)) {
174 const { position, relativeOffset } = attachmentDisplay;
175 // edge connected to a SingleFreeNode, a Group or a Burr, but NOT another edge or an anchor
176 try {
177 const { offsetScalar: offsetScalarX, orientationScalar: orientationScalarX } = getOffsetAndOrientationScalarsAlongAxis(position[0], relativeOffset[0], "x", entityReferencedByEdge);
178 const { offsetScalar: offsetScalarY, orientationScalar: orientationScalarY } = getOffsetAndOrientationScalarsAlongAxis(position[1], relativeOffset[1], "y", entityReferencedByEdge);
179 if (index === 0) {
180 orientation[0] = orientationScalarX;
181 orientation[1] = orientationScalarY;
182 }
183 else {
184 orientation[0] = -1 * orientationScalarX;
185 orientation[1] = -1 * orientationScalarY;
186 }
187 // TODO is there a case where we would ever use offset for edges?
188 attachmentDisplay.offset[0] = offsetScalarX;
189 attachmentDisplay.offset[1] = offsetScalarY;
190 point.attachmentDisplay = omit(["relativeOffset"], attachmentDisplay);
191 }
192 catch (err) {
193 throw new VError(err, `
194 Error for:
195 postprocessPVJSON(
196 referencedEntities=${JSON.stringify(referencedEntities, null, " ")},
197 pvjsonEdge=${JSON.stringify(pvjsonEdge, null, " ")}
198 )
199 `);
200 /* TODO should we use this?
201 console.warn(`Setting offsetScalar equal to 0.`);
202 offsetScalar = 0;
203 //*/
204 }
205 }
206 else if (isGPMLAnchor(entityReferencedByPoint)) {
207 // edge is connected to another edge via an anchor
208 point.attachmentDisplay.position =
209 entityReferencedByPoint.attachmentDisplay.position;
210 }
211 else {
212 throw new Error(`
213 Edge or Point attached to unexpected entity.
214 Point is attached to:
215 ${JSON.stringify(entityReferencedByPoint, null, " ")}
216 Point is attached to:
217 ${JSON.stringify(entityReferencedByEdge, null, " ")}
218 for:
219 postprocessPVJSON(
220 referencedEntities=${JSON.stringify(referencedEntities, null, " ")},
221 pvjsonEdge=${JSON.stringify(pvjsonEdge, null, " ")}
222 )
223 `);
224 }
225 }
226 // NOTE: side effect
227 index += 1;
228 return omit(["marker"], point);
229 }, points);
230 const pvjsonEdgeAttachedToCount = pvjsonEdgeIsAttachedTo.length;
231 if (pvjsonEdgeAttachedToCount > 0) {
232 pvjsonEdge.isAttachedTo = pvjsonEdgeIsAttachedTo;
233 }
234 let allPvjsonPoints;
235 if (["StraightLine", "SegmentedLine"].indexOf(drawAs) > -1) {
236 allPvjsonPoints = providedPvjsonPoints;
237 }
238 else if (["ElbowLine", "CurvedLine"].indexOf(drawAs) > -1) {
239 // pvjsonEdge.isAttachedTo refers to what the EDGE is fundamentally attached to.
240 // pvjsonEdge.points[0].isAttachedTo refers to what the POINT is attached to.
241 //
242 // From the perspective of the biological meaning, the edge is always attached to
243 // a regular node like a DataNode or Shape (maybe Label?) but never to an Anchor.
244 //
245 // From the perspective of the implementation of the graphics, we say the edge
246 // has points, one or more of which can be connected to an Anchor.
247 let sourceEntity;
248 let targetEntity;
249 if (pvjsonEdgeAttachedToCount === 2) {
250 sourceEntity = referencedEntities[pvjsonEdgeIsAttachedTo[0]];
251 targetEntity = referencedEntities[pvjsonEdgeIsAttachedTo[1]];
252 }
253 else if (pvjsonEdgeAttachedToCount === 1) {
254 const firstPoint = providedPvjsonPoints[0];
255 const lastPoint = providedPvjsonPoints[providedPvjsonPoints.length - 1];
256 if (firstPoint.hasOwnProperty("isAttachedTo")) {
257 sourceEntity = referencedEntities[pvjsonEdgeIsAttachedTo[0]];
258 }
259 else if (lastPoint.hasOwnProperty("isAttachedTo")) {
260 targetEntity = referencedEntities[pvjsonEdgeIsAttachedTo[0]];
261 }
262 else {
263 throw new Error(`edge "${pvjsonEdge.id}" is said to be attached to "${pvjsonEdge.isAttachedTo.join()}",
264 but neither first nor last points have "isAttachedTo" property`);
265 }
266 }
267 allPvjsonPoints = calculateAllPoints(providedPvjsonPoints.map(point => new SmartPoint(point)), sourceEntity, targetEntity);
268 }
269 else {
270 throw new Error(`
271 Unknown edge drawer "${drawAs}" for:
272 postprocessPVJSON(
273 referencedEntities=${JSON.stringify(referencedEntities, null, " ")},
274 pvjsonEdge=${JSON.stringify(pvjsonEdge, null, " ")}
275 )
276 `);
277 // TODO should we use this?
278 // allPvjsonPoints = providedPvjsonPoints;
279 }
280 // TODO how do we distinguish between intermediate (not first or last) points that a user
281 // has explicitly specified vs. intermediate points that are only implied?
282 // Do we need to? I think once a user specifies any implicit points, they may all be
283 // made explicit.
284 // GPML currently does not specify implicit intermediate points, but
285 // pvjson does.
286 pvjsonEdge.points = allPvjsonPoints;
287 // TODO can I get rid of isAttachedToOrVia earlier?
288 return omit(["isAttachedToOrVia"], pvjsonEdge);
289}
290//function recursivelyGetReferencedElements(acc, gpmlElement: GPMLElement) {
291// const { Graphics } = gpmlElement;
292// const graphRefIds: string[] = !!Graphics.Point &&
293// Graphics.Point[0]._exists !== false
294// ? Graphics.Point.filter(P => isString(P.GraphRef)).map(P => P.GraphRef)
295// : gpmlElement.hasOwnProperty("GraphRef")
296// ? arrayify(gpmlElement.GraphRef)
297// : [];
298//
299// const referencedElementIds = arrayify(graphRefIds);
300// //const referencedElementIds = unionLSV(graphRefIds, gpmlElement.GroupRef);
301// return referencedElementIds.length === 0
302// ? acc
303// : hl([
304// acc,
305// hl(referencedElementIds)
306// .flatMap(referencedElementId =>
307// hl(getGPMLElementByGraphId(referencedElementId))
308// )
309// .flatMap(function(referencedElement: GPMLElement) {
310// return recursivelyGetReferencedElements(
311// hl([referencedElement]),
312// referencedElement
313// );
314// })
315// ]).merge();
316//}
317//
318//export function postprocessPVJSON(
319// pvjsonEdge: PvjsonEdge
320//): Highland.Stream<PvjsonEdge> {
321// return hl([
322// hl([pvjsonEdge])
323// .reduce(hl([]), recursivelyGetReferencedElements)
324// .merge()
325// .flatMap(function(referencedGPMLElement: GPMLElement) {
326// return hl(
327// getPvjsonEntityLatestByGraphId(
328// referencedGPMLElement.GraphId
329// )
330// );
331// })
332// ])
333// .merge()
334// .reduce({}, function(
335// acc: {
336// [key: string]: (PvjsonNode | PvjsonEdge);
337// },
338// referencedEntity: (PvjsonNode | PvjsonEdge)
339// ) {
340// acc[referencedEntity.id] = referencedEntity;
341// return acc;
342// })
343// .map(function(referencedEntities) {
344// return process(pvjsonEdge, referencedEntities);
345// });
346// .merge();
347//}
348//
349//export function createEdgeTransformStream(
350// processor,
351// edgeType: "Interaction" | "GraphicalLine"
352//): (
353// s: Highland.Stream<GPML2013a.InteractionType | GPML2013a.GraphicalLineType>
354//) => Highland.Stream<(PvjsonNode | PvjsonEdge)> {
355// const {
356// fillInGPMLPropertiesFromParent,
357// getGPMLElementByGraphId,
358// getPvjsonEntityLatestByGraphId,
359// ensureGraphIdExists,
360// preprocessGPMLElement,
361// processPropertiesAndType
362// } = processor;
363//
364// function recursivelyGetReferencedElements(acc, gpmlElement: GPMLElement) {
365// const { Graphics } = gpmlElement;
366// const graphRefIds: string[] = !!Graphics.Point &&
367// Graphics.Point[0]._exists !== false
368// ? Graphics.Point.filter(P => isString(P.GraphRef)).map(P => P.GraphRef)
369// : gpmlElement.hasOwnProperty("GraphRef")
370// ? arrayify(gpmlElement.GraphRef)
371// : [];
372//
373// const referencedElementIds = arrayify(graphRefIds);
374// //const referencedElementIds = unionLSV(graphRefIds, gpmlElement.GroupRef);
375// return referencedElementIds.length === 0
376// ? acc
377// : hl([
378// acc,
379// hl(referencedElementIds)
380// .flatMap(referencedElementId =>
381// hl(getGPMLElementByGraphId(referencedElementId))
382// )
383// .flatMap(function(referencedElement: GPMLElement) {
384// return recursivelyGetReferencedElements(
385// hl([referencedElement]),
386// referencedElement
387// );
388// })
389// ]).merge();
390// }
391//
392// return function(s) {
393// return s
394// .map(preprocessGPMLElement)
395// .flatMap(function(
396// gpmlEdge: GPMLElement
397// ): Highland.Stream<Highland.Stream<PvjsonNode | PvjsonEdge>> {
398// const { Graphics } = gpmlEdge;
399//
400// const gpmlAnchors = Graphics.hasOwnProperty("Anchor") &&
401// Graphics.Anchor &&
402// Graphics.Anchor[0] &&
403// Graphics.Anchor[0]._exists !== false
404// ? Graphics.Anchor.filter(a => a.hasOwnProperty("GraphId"))
405// : [];
406//
407// const fillInGPMLPropertiesFromEdge = fillInGPMLPropertiesFromParent(
408// gpmlEdge
409// );
410//
411// return hl([
412// hl([gpmlEdge])
413// .map(processPropertiesAndType(edgeType))
414// .flatMap(function(pvjsonEdge: PvjsonEdge) {
415// return hl([
416// hl([gpmlEdge])
417// .reduce(hl([]), recursivelyGetReferencedElements)
418// .merge()
419// .flatMap(function(referencedGPMLElement: GPMLElement) {
420// return hl(
421// getPvjsonEntityLatestByGraphId(
422// referencedGPMLElement.GraphId
423// )
424// );
425// })
426// ])
427// .merge()
428// .reduce({}, function(
429// acc: {
430// [key: string]: (PvjsonNode | PvjsonEdge);
431// },
432// referencedEntity: (PvjsonNode | PvjsonEdge)
433// ) {
434// acc[referencedEntity.id] = referencedEntity;
435// return acc;
436// })
437// .map(function(referencedEntities) {
438// return process(pvjsonEdge, referencedEntities);
439// });
440// }),
441// hl(gpmlAnchors)
442// .map(preprocessGPMLElement)
443// .map(function(gpmlAnchor: GPMLElement) {
444// const filledInAnchor = fillInGPMLPropertiesFromEdge(gpmlAnchor);
445// filledInAnchor.GraphRef = gpmlEdge.GraphId;
446// return filledInAnchor;
447// })
448// .map(processPropertiesAndType("Anchor"))
449// .map(function(pvjsonAnchor: PvjsonNode): PvjsonNode {
450// const drawAnchorAs = pvjsonAnchor.drawAs;
451// if (drawAnchorAs === "None") {
452// defaultsDeep(pvjsonAnchor, {
453// Height: 4,
454// Width: 4
455// });
456// } else if (drawAnchorAs === "Circle") {
457// defaultsDeep(pvjsonAnchor, {
458// Height: 8,
459// Width: 8
460// });
461// }
462// return pvjsonAnchor;
463// })
464// ]);
465// })
466// .merge();
467// };
468//}
469//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZWRnZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9lZGdlL2VkZ2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFFBQVEsRUFBUyxHQUFHLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSxNQUFNLFdBQVcsQ0FBQztBQUNoRSxPQUFPLEVBQ0wsaUJBQWlCLEVBQ2pCLGFBQWEsRUFDYixZQUFZLEVBQ1osWUFBWSxFQUNaLHNCQUFzQixFQUN0QixhQUFhLEVBQ2IsUUFBUSxFQUNULE1BQU0sbUJBQW1CLENBQUM7QUFDM0IsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQWMzQyxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUMxRCxPQUFPLEtBQUssTUFBTSxNQUFNLFFBQVEsQ0FBQztBQUNqQyxPQUFPLEtBQUssY0FBYyxNQUFNLHVCQUF1QixDQUFDO0FBRXhELDZGQUE2RjtBQUM3RixNQUFNLENBQUMsTUFBTSxtQkFBbUIsR0FBRyxFQUFFLENBQUM7QUFFdEM7Ozs7Ozs7R0FPRztBQUNILGlEQUNFLGNBQXNCLEVBQ3RCLG9CQUE0QixFQUM1QixJQUFlO0FBQ2YsdUVBQXVFO0FBQ3ZFLDBFQUEwRTtBQUMxRSxRQUFRO0FBQ1IsZ0JBQTRCO0lBRTVCLElBQUksWUFBWSxHQUNkLG9CQUFvQjtRQUNwQixDQUFDLElBQUksS0FBSyxHQUFHLENBQUMsQ0FBQyxDQUFDLGdCQUFnQixDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsZ0JBQWdCLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDcEUsbUVBQW1FO0lBQ25FLHFDQUFxQztJQUNyQyw0Q0FBNEM7SUFDNUMseUVBQXlFO0lBQ3pFLG1FQUFtRTtJQUNuRSxFQUFFLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxZQUFZLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDNUIsTUFBTSxJQUFJLEtBQUssQ0FDYjswQkFDb0IsWUFBWTtX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
\No newline at end of file