gpml2pvjson
Version:
convert GPML (XML) to pvjson (json)
467 lines • 32.5 kB
JavaScript
import { isFinite, map, omit, toPairs } from "lodash/fp";
import { isAttachablePoint, isDefinedCXML, isGPMLAnchor, isPvjsonBurr, isPvjsonSingleFreeNode, isPvjsonGroup, unionLSV } from "../gpml-utilities";
import { SmartPoint } from "../geom-utils";
import { calculateAllPoints } from "./calculateAllPoints";
import * as VError from "verror";
import * as MarkerMappings from "./MarkerMappings.json";
// a stub is a short path segment that is used for the first and/or last segment(s) of a path
export const DEFAULT_STUB_LENGTH = 20;
/**
* getOffsetAndOrientationScalarsAlongAxis
*
* @param relValue {number}
* @param axis {string}
* @param referencedEntity
* @return {OffsetOrientationAndPositionScalarsAlongAxis}
*/
function getOffsetAndOrientationScalarsAlongAxis(positionScalar, relativeOffsetScalar, axis,
// TODO are we correctly handling the case of a group as the referenced
// entity? Do we have the group width and height yet to properly calculate
// this?
referencedEntity) {
let offsetScalar = relativeOffsetScalar *
(axis === "x" ? referencedEntity.width : referencedEntity.height);
// TODO WP536 has a referenced entity that lacks width/height. Why?
// The referenced entity was a group.
// Is the problem that the group was nested?
// Or is it a problem with the order of evaluation of entities (trying to
// parse a dependent entity before its dependencies were parsed)?
if (!isFinite(offsetScalar)) {
throw new Error(`
Got non-finite value ${offsetScalar} for offsetScalar
along ${axis} axis for
getOffsetAndOrientationScalarsAlongAxis(
positionScalar=${positionScalar},
relativeOffsetScalar=${relativeOffsetScalar},
referencedEntity=
${JSON.stringify(referencedEntity, null, " ")}
)
`);
}
// orientationScalar here refers to the initial direction the edge takes as
// it moves away from the entity to which it is attached.
let orientationScalar;
if (positionScalar === 0) {
orientationScalar = -1;
}
else if (positionScalar === 1) {
orientationScalar = 1;
}
else {
orientationScalar = 0;
}
return { offsetScalar, orientationScalar, positionScalar };
}
/**
* preprocessGPML
*
* @param edge {GPMLEdge}
* @return {GPMLEdge}
*/
export function preprocessGPML(Edge) {
const isAttachedToOrVia = Edge.Graphics.Point.filter(p => p.GraphRef && isDefinedCXML(p.GraphRef)).map(p => p.GraphRef);
if (isAttachedToOrVia.length > 0) {
// In pvjson, an edge attaches directly to another entity (Node, Edge, Group),
// not to an anchor.
// If the edge attaches to another edge, it does so VIA an anchor.
Edge["isAttachedToOrVia"] = isAttachedToOrVia;
}
return Edge;
}
/**
* postprocessPVJSON
*
* @param referencedEntities
* @param pvjsonEdge {pvjsonEdge}
* @return {pvjsonEdge}
*/
export function postprocessPVJSON(referencedEntities, pvjsonEdge) {
const { points, drawAs } = pvjsonEdge;
const pointCount = points.length;
let index = 0;
const pvjsonEdgeIsAttachedTo = [];
const providedPvjsonPoints = map(function (point) {
const { marker, x, y } = point;
if (!!marker) {
// NOTE: side effects below
if (index === 0) {
pvjsonEdge.markerStart = marker;
}
else if (index === pointCount - 1) {
pvjsonEdge.markerEnd = marker;
}
if (MarkerMappings.hasOwnProperty(marker)) {
pvjsonEdge.type = toPairs(MarkerMappings[marker]).reduce(function (acc, [namespace, moreTypes]) {
return unionLSV(acc, moreTypes);
}, pvjsonEdge.type);
}
}
if (isAttachablePoint(point)) {
// NOTE: pvjson allows for expressing one edge attached to another edge.
// When we do this, we say that the POINT attaches to an ANCHOR on the other edge,
// but the EDGE attaches to the other EDGE, never the anchor.
const { isAttachedTo, attachmentDisplay } = point;
if (!attachmentDisplay.offset) {
throw new Error(`attachmentDisplay for a Point has no offset property.
postprocessPVJSON(
referencedEntities=${JSON.stringify(referencedEntities, null, " ")},
pvjsonEdge=${JSON.stringify(pvjsonEdge, null, " ")}
)`);
}
// entityReferencedByPoint can be a regular node (DataNode, Shape, Label)
// or an Anchor. If connected to an Anchor, the biological meaning is
// that the edge is connected to another edge, but in this code, we
// implement this by treating the Anchor as a node, as if it were
// a "burr" that is always stuck (isAttachedTo) the other edge.
const entityReferencedByPoint = referencedEntities &&
!!isAttachedTo &&
referencedEntities[isAttachedTo];
const entityIdReferencedByEdge = isGPMLAnchor(entityReferencedByPoint)
? entityReferencedByPoint.isAttachedTo
: entityReferencedByPoint.id;
// WARNING: side effect
pvjsonEdgeIsAttachedTo.push(entityIdReferencedByEdge);
const entityReferencedByEdge = referencedEntities[entityIdReferencedByEdge];
const orientation = (point.orientation = (point.orientation ||
[]));
// attachmentDisplay: { position: [x: number, y: number], offset: [xOffset: number, yOffset: number], orientation: [dx: number, dy: number] }
//
// x = xDistance / width (relative: [0,1])
// y = yDistance / height (relative: [0,1])
// xOffset = distance offset in x direction (absolute)
// yOffset = distance offset in y direction (absolute)
// dx = x component of edge emanation angle (unit: [0,1])
// dy = y component of edge emanation angle (unit: [0,1])
//
// 0 ----------------- x ------------------->
// | ========================================
// | || ||
// | || ||
// | || ||
// y || ||
// | || ||
// | || ||
// | || ||
// | || ||
// v ===================*====================
// |
// yOffset |
// | |
// v |
// ----------*
// xOffset> \
// \
// \ dx>
// dy \
// | \
// v \
// \
//
// example above is an attachmentDisplay specifying an edge that emanates down and to the right
// at a 45 deg. angle (1, 1), offset right 5 x units and down 11 y units from the center (0.5)
// of the bottom side (1) of the node: {position: [0.75, 1], offset: [5, 11], orientation: [1, 1]}
//
//
// where x is distance from left side along width axis as a percentage of the total width
// y is distance from top side along height axis as a percentage of the total height
// offsetX, offsetY are obvious from the name. Notice they are absolute, unlike x,y.
// dx, dy are unit vector coordinates of a point that specifies how the edge emanates from the node
if (isPvjsonSingleFreeNode(entityReferencedByEdge) ||
isPvjsonGroup(entityReferencedByEdge) ||
isPvjsonBurr(entityReferencedByEdge)) {
const { position, relativeOffset } = attachmentDisplay;
// edge connected to a SingleFreeNode, a Group or a Burr, but NOT another edge or an anchor
try {
const { offsetScalar: offsetScalarX, orientationScalar: orientationScalarX } = getOffsetAndOrientationScalarsAlongAxis(position[0], relativeOffset[0], "x", entityReferencedByEdge);
const { offsetScalar: offsetScalarY, orientationScalar: orientationScalarY } = getOffsetAndOrientationScalarsAlongAxis(position[1], relativeOffset[1], "y", entityReferencedByEdge);
if (index === 0) {
orientation[0] = orientationScalarX;
orientation[1] = orientationScalarY;
}
else {
orientation[0] = -1 * orientationScalarX;
orientation[1] = -1 * orientationScalarY;
}
// TODO is there a case where we would ever use offset for edges?
attachmentDisplay.offset[0] = offsetScalarX;
attachmentDisplay.offset[1] = offsetScalarY;
point.attachmentDisplay = omit(["relativeOffset"], attachmentDisplay);
}
catch (err) {
throw new VError(err, `
Error for:
postprocessPVJSON(
referencedEntities=${JSON.stringify(referencedEntities, null, " ")},
pvjsonEdge=${JSON.stringify(pvjsonEdge, null, " ")}
)
`);
/* TODO should we use this?
console.warn(`Setting offsetScalar equal to 0.`);
offsetScalar = 0;
//*/
}
}
else if (isGPMLAnchor(entityReferencedByPoint)) {
// edge is connected to another edge via an anchor
point.attachmentDisplay.position =
entityReferencedByPoint.attachmentDisplay.position;
}
else {
throw new Error(`
Edge or Point attached to unexpected entity.
Point is attached to:
${JSON.stringify(entityReferencedByPoint, null, " ")}
Point is attached to:
${JSON.stringify(entityReferencedByEdge, null, " ")}
for:
postprocessPVJSON(
referencedEntities=${JSON.stringify(referencedEntities, null, " ")},
pvjsonEdge=${JSON.stringify(pvjsonEdge, null, " ")}
)
`);
}
}
// NOTE: side effect
index += 1;
return omit(["marker"], point);
}, points);
const pvjsonEdgeAttachedToCount = pvjsonEdgeIsAttachedTo.length;
if (pvjsonEdgeAttachedToCount > 0) {
pvjsonEdge.isAttachedTo = pvjsonEdgeIsAttachedTo;
}
let allPvjsonPoints;
if (["StraightLine", "SegmentedLine"].indexOf(drawAs) > -1) {
allPvjsonPoints = providedPvjsonPoints;
}
else if (["ElbowLine", "CurvedLine"].indexOf(drawAs) > -1) {
// pvjsonEdge.isAttachedTo refers to what the EDGE is fundamentally attached to.
// pvjsonEdge.points[0].isAttachedTo refers to what the POINT is attached to.
//
// From the perspective of the biological meaning, the edge is always attached to
// a regular node like a DataNode or Shape (maybe Label?) but never to an Anchor.
//
// From the perspective of the implementation of the graphics, we say the edge
// has points, one or more of which can be connected to an Anchor.
let sourceEntity;
let targetEntity;
if (pvjsonEdgeAttachedToCount === 2) {
sourceEntity = referencedEntities[pvjsonEdgeIsAttachedTo[0]];
targetEntity = referencedEntities[pvjsonEdgeIsAttachedTo[1]];
}
else if (pvjsonEdgeAttachedToCount === 1) {
const firstPoint = providedPvjsonPoints[0];
const lastPoint = providedPvjsonPoints[providedPvjsonPoints.length - 1];
if (firstPoint.hasOwnProperty("isAttachedTo")) {
sourceEntity = referencedEntities[pvjsonEdgeIsAttachedTo[0]];
}
else if (lastPoint.hasOwnProperty("isAttachedTo")) {
targetEntity = referencedEntities[pvjsonEdgeIsAttachedTo[0]];
}
else {
throw new Error(`edge "${pvjsonEdge.id}" is said to be attached to "${pvjsonEdge.isAttachedTo.join()}",
but neither first nor last points have "isAttachedTo" property`);
}
}
allPvjsonPoints = calculateAllPoints(providedPvjsonPoints.map(point => new SmartPoint(point)), sourceEntity, targetEntity);
}
else {
throw new Error(`
Unknown edge drawer "${drawAs}" for:
postprocessPVJSON(
referencedEntities=${JSON.stringify(referencedEntities, null, " ")},
pvjsonEdge=${JSON.stringify(pvjsonEdge, null, " ")}
)
`);
// TODO should we use this?
// allPvjsonPoints = providedPvjsonPoints;
}
// TODO how do we distinguish between intermediate (not first or last) points that a user
// has explicitly specified vs. intermediate points that are only implied?
// Do we need to? I think once a user specifies any implicit points, they may all be
// made explicit.
// GPML currently does not specify implicit intermediate points, but
// pvjson does.
pvjsonEdge.points = allPvjsonPoints;
// TODO can I get rid of isAttachedToOrVia earlier?
return omit(["isAttachedToOrVia"], pvjsonEdge);
}
//function recursivelyGetReferencedElements(acc, gpmlElement: GPMLElement) {
// const { Graphics } = gpmlElement;
// const graphRefIds: string[] = !!Graphics.Point &&
// Graphics.Point[0]._exists !== false
// ? Graphics.Point.filter(P => isString(P.GraphRef)).map(P => P.GraphRef)
// : gpmlElement.hasOwnProperty("GraphRef")
// ? arrayify(gpmlElement.GraphRef)
// : [];
//
// const referencedElementIds = arrayify(graphRefIds);
// //const referencedElementIds = unionLSV(graphRefIds, gpmlElement.GroupRef);
// return referencedElementIds.length === 0
// ? acc
// : hl([
// acc,
// hl(referencedElementIds)
// .flatMap(referencedElementId =>
// hl(getGPMLElementByGraphId(referencedElementId))
// )
// .flatMap(function(referencedElement: GPMLElement) {
// return recursivelyGetReferencedElements(
// hl([referencedElement]),
// referencedElement
// );
// })
// ]).merge();
//}
//
//export function postprocessPVJSON(
// pvjsonEdge: PvjsonEdge
//): Highland.Stream<PvjsonEdge> {
// return hl([
// hl([pvjsonEdge])
// .reduce(hl([]), recursivelyGetReferencedElements)
// .merge()
// .flatMap(function(referencedGPMLElement: GPMLElement) {
// return hl(
// getPvjsonEntityLatestByGraphId(
// referencedGPMLElement.GraphId
// )
// );
// })
// ])
// .merge()
// .reduce({}, function(
// acc: {
// [key: string]: (PvjsonNode | PvjsonEdge);
// },
// referencedEntity: (PvjsonNode | PvjsonEdge)
// ) {
// acc[referencedEntity.id] = referencedEntity;
// return acc;
// })
// .map(function(referencedEntities) {
// return process(pvjsonEdge, referencedEntities);
// });
// .merge();
//}
//
//export function createEdgeTransformStream(
// processor,
// edgeType: "Interaction" | "GraphicalLine"
//): (
// s: Highland.Stream<GPML2013a.InteractionType | GPML2013a.GraphicalLineType>
//) => Highland.Stream<(PvjsonNode | PvjsonEdge)> {
// const {
// fillInGPMLPropertiesFromParent,
// getGPMLElementByGraphId,
// getPvjsonEntityLatestByGraphId,
// ensureGraphIdExists,
// preprocessGPMLElement,
// processPropertiesAndType
// } = processor;
//
// function recursivelyGetReferencedElements(acc, gpmlElement: GPMLElement) {
// const { Graphics } = gpmlElement;
// const graphRefIds: string[] = !!Graphics.Point &&
// Graphics.Point[0]._exists !== false
// ? Graphics.Point.filter(P => isString(P.GraphRef)).map(P => P.GraphRef)
// : gpmlElement.hasOwnProperty("GraphRef")
// ? arrayify(gpmlElement.GraphRef)
// : [];
//
// const referencedElementIds = arrayify(graphRefIds);
// //const referencedElementIds = unionLSV(graphRefIds, gpmlElement.GroupRef);
// return referencedElementIds.length === 0
// ? acc
// : hl([
// acc,
// hl(referencedElementIds)
// .flatMap(referencedElementId =>
// hl(getGPMLElementByGraphId(referencedElementId))
// )
// .flatMap(function(referencedElement: GPMLElement) {
// return recursivelyGetReferencedElements(
// hl([referencedElement]),
// referencedElement
// );
// })
// ]).merge();
// }
//
// return function(s) {
// return s
// .map(preprocessGPMLElement)
// .flatMap(function(
// gpmlEdge: GPMLElement
// ): Highland.Stream<Highland.Stream<PvjsonNode | PvjsonEdge>> {
// const { Graphics } = gpmlEdge;
//
// const gpmlAnchors = Graphics.hasOwnProperty("Anchor") &&
// Graphics.Anchor &&
// Graphics.Anchor[0] &&
// Graphics.Anchor[0]._exists !== false
// ? Graphics.Anchor.filter(a => a.hasOwnProperty("GraphId"))
// : [];
//
// const fillInGPMLPropertiesFromEdge = fillInGPMLPropertiesFromParent(
// gpmlEdge
// );
//
// return hl([
// hl([gpmlEdge])
// .map(processPropertiesAndType(edgeType))
// .flatMap(function(pvjsonEdge: PvjsonEdge) {
// return hl([
// hl([gpmlEdge])
// .reduce(hl([]), recursivelyGetReferencedElements)
// .merge()
// .flatMap(function(referencedGPMLElement: GPMLElement) {
// return hl(
// getPvjsonEntityLatestByGraphId(
// referencedGPMLElement.GraphId
// )
// );
// })
// ])
// .merge()
// .reduce({}, function(
// acc: {
// [key: string]: (PvjsonNode | PvjsonEdge);
// },
// referencedEntity: (PvjsonNode | PvjsonEdge)
// ) {
// acc[referencedEntity.id] = referencedEntity;
// return acc;
// })
// .map(function(referencedEntities) {
// return process(pvjsonEdge, referencedEntities);
// });
// }),
// hl(gpmlAnchors)
// .map(preprocessGPMLElement)
// .map(function(gpmlAnchor: GPMLElement) {
// const filledInAnchor = fillInGPMLPropertiesFromEdge(gpmlAnchor);
// filledInAnchor.GraphRef = gpmlEdge.GraphId;
// return filledInAnchor;
// })
// .map(processPropertiesAndType("Anchor"))
// .map(function(pvjsonAnchor: PvjsonNode): PvjsonNode {
// const drawAnchorAs = pvjsonAnchor.drawAs;
// if (drawAnchorAs === "None") {
// defaultsDeep(pvjsonAnchor, {
// Height: 4,
// Width: 4
// });
// } else if (drawAnchorAs === "Circle") {
// defaultsDeep(pvjsonAnchor, {
// Height: 8,
// Width: 8
// });
// }
// return pvjsonAnchor;
// })
// ]);
// })
// .merge();
// };
//}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZWRnZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9lZGdlL2VkZ2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFFBQVEsRUFBUyxHQUFHLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSxNQUFNLFdBQVcsQ0FBQztBQUNoRSxPQUFPLEVBQ0wsaUJBQWlCLEVBQ2pCLGFBQWEsRUFDYixZQUFZLEVBQ1osWUFBWSxFQUNaLHNCQUFzQixFQUN0QixhQUFhLEVBQ2IsUUFBUSxFQUNULE1BQU0sbUJBQW1CLENBQUM7QUFDM0IsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQWMzQyxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUMxRCxPQUFPLEtBQUssTUFBTSxNQUFNLFFBQVEsQ0FBQztBQUNqQyxPQUFPLEtBQUssY0FBYyxNQUFNLHVCQUF1QixDQUFDO0FBRXhELDZGQUE2RjtBQUM3RixNQUFNLENBQUMsTUFBTSxtQkFBbUIsR0FBRyxFQUFFLENBQUM7QUFFdEM7Ozs7Ozs7R0FPRztBQUNILFNBQVMsdUNBQXVDLENBQzlDLGNBQXNCLEVBQ3RCLG9CQUE0QixFQUM1QixJQUFlO0FBQ2YsdUVBQXVFO0FBQ3ZFLDBFQUEwRTtBQUMxRSxRQUFRO0FBQ1IsZ0JBQTRCO0lBRTVCLElBQUksWUFBWSxHQUNkLG9CQUFvQjtRQUNwQixDQUFDLElBQUksS0FBSyxHQUFHLENBQUMsQ0FBQyxDQUFDLGdCQUFnQixDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsZ0JBQWdCLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDcEUsbUVBQW1FO0lBQ25FLHFDQUFxQztJQUNyQyw0Q0FBNEM7SUFDNUMseUVBQXlFO0lBQ3pFLG1FQUFtRTtJQUNuRSxJQUFJLENBQUMsUUFBUSxDQUFDLFlBQVksQ0FBQyxFQUFFO1FBQzNCLE1BQU0sSUFBSSxLQUFLLENBQ2I7MEJBQ29CLFlBQVk7V0FDM0IsSUFB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