UNPKG

gpml2pvjson

Version:
467 lines 32.5 kB
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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