UNPKG

gpml2pvjson

Version:
356 lines (349 loc) 26.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RelX = exports.Position = exports.ConnectorType = exports.LineThickness = exports.FillColor = exports.Color = exports.gpmlColorToCssColor = exports.Href = exports.Valign = exports.FontWeight = exports.FontStyle = exports.FontStrikethru = exports.FontDecoration = exports.Align = exports.getTextDecorationFromGPMLElement = exports.TextLabel = exports.Name = exports.Maintainer = exports.Email = exports.DataSource = exports.Author = exports.LineStyle = exports.Rotation = exports.CenterY = exports.CenterX = exports.Width = exports.Height = exports.BiopaxRef = exports.AUTHORS = exports.YEAR = exports.SOURCE = exports.TITLE = exports.DB = exports.$ID = exports.ID = void 0; var fp_1 = require("lodash/fp"); var RGBColor = require("rgbcolor"); var VError = require("verror"); var he_1 = require("he"); var gpml_utilities_1 = require("../gpml-utilities"); var Angle_1 = require("../spinoffs/Angle"); var gpml_utilities_2 = require("../gpml-utilities"); // TODO are these ever used? PathVisio-Java // does not accept them as inputs in the // Rotation input field in the UI. // TODO if they are used, are the notes in // the XSD correct, or would "Right" actually // be 0 radians? var GPML_ROTATION_SIDE_TO_RAD = { Top: 0, Right: 0.5 * Math.PI, Bottom: Math.PI, Left: (3 / 2) * Math.PI }; function decodeIfNotEmpty(input) { return fp_1.isEmpty(input) ? input : he_1.decode(input); } function parseAsNonNaNNumber(i) { var parsed = Number(i); if (fp_1.isNaN(parsed)) { throw new Error('Cannot parse "' + String(i) + '" as non-NaN number'); } return parsed; } //***************** // Value Converters //***************** // NOTE: we use He.decode for many of these // because at some point some GPML files were // processed w/out using UTF-8, leaving some // strings garbled, such as author names. // TODO backpageHead could be further processed to yield displayName and standardName function ID(gpmlElement) { if (gpmlElement.hasOwnProperty("ID")) { var ID_1 = gpmlElement.ID; return fp_1.isString(ID_1) ? ID_1 : ID_1.content; } else { return gpmlElement.Xref.ID; } } exports.ID = ID; // GPML2013-ish incorrectly used "rdf:id" where it was intented // to use "rdf:ID". We corrected that error before processing, // but CXML turns "rdf:ID" into "ID", and since we already have // a property "ID" on the element, CXML uses "$ID". exports.$ID = fp_1.flow(fp_1.get("$ID"), gpml_utilities_1.generatePublicationXrefId); exports.DB = fp_1.flow(fp_1.get("DB.content"), decodeIfNotEmpty); exports.TITLE = fp_1.flow(fp_1.get("TITLE.content"), decodeIfNotEmpty); exports.SOURCE = fp_1.flow(fp_1.get("SOURCE.content"), decodeIfNotEmpty); // TODO: why doesn't TypeScript like the following? //export const YEAR = get("YEAR.content"); exports.YEAR = function (x) { return x.YEAR.content; //return get("YEAR.content"); }; exports.AUTHORS = fp_1.flow(fp_1.get("AUTHORS"), fp_1.map(fp_1.flow(fp_1.get("content"), decodeIfNotEmpty))); exports.BiopaxRef = fp_1.flow(fp_1.get("BiopaxRef"), fp_1.map(gpml_utilities_1.generatePublicationXrefId)); /* Meanings of Width ----------------- In PathVisio-Java, GPML Width/Height for GPML Shapes is inconsistent when zoomed in vs. when at default zoom level. When zoomed in, GPML Width/Height refers to the distance from center of stroke (border) one one edge to center of stroke (border) on the opposite edge, meaning that shapes that run up to the edge are cropped. When at default zoom level, GPML Width/Height refers to the distance from outer edge of stroke (border) to outer edge of stroke (border) with no cropping. Because of this, LineThickness is also inconsistent. When zoomed in: approx. one half of specified LineThickness. When at default zoom level: approx. full specified LineThickness. For double lines, LineThickness refers to the the stroke (border) width of each line and the space between each line, meaning the stroke (border) width for the double line as a whole will be three times the listed LineThickness. For pvjs, we define GPML Width/Height to be from outer edge of stroke (border) on one side to outer edge of stroke (border) on the opposite site, meaning visible width/height may not exactly match between pvjs and PathVisio. See issue https://github.com/PathVisio/pathvisio/issues/59 * DOM box model - box-sizing: border-box visible width = width (width means border + padding + width of the content) (see https://css-tricks.com/international-box-sizing-awareness-day/) - box-sizing: content-box visible width = width + border + padding (width means width of the content) * PathVisio-Java - Zoomed in - LineStyle NOT Double visible width ≈ GPMLWidth visible height ≈ GPMLHeight (matches box-sizing: border-box) - LineStyle Double visible width ≈ Width + 1.5 * LineThickness visible height ≈ Height + 1.5 * LineThickness - Zoomed out - LineStyle NOT Double visible width ≈ GPMLWidth + LineThickness visible height ≈ GPMLHeight + LineThickness (matches box-sizing: border-box) (one half LineThickness on either side yields a full LineThickness to add to width/height). - LineStyle Double visible width = Width + 3 * LineThickness visible height = Height + 3 * LineThickness * SVG: visible width = width + stroke-width * kaavio/pvjs: same as DOM box model with box-sizing: border-box //*/ var getDimension = fp_1.curry(function (dimensionName, gpmlElement) { var dimension = gpmlElement.Graphics[dimensionName]; if (fp_1.findIndex(function (_a) { var Key = _a.Key, Value = _a.Value; return Key === "org.pathvisio.DoubleLineProperty"; }, gpmlElement.Attribute) > -1) { return dimension + LineThickness(gpmlElement); } else { return dimension; } }); exports.Height = getDimension("Height"); exports.Width = getDimension("Width"); function CenterX(gpmlElement) { var CenterX = gpmlElement.Graphics.CenterX; return CenterX - exports.Width(gpmlElement) / 2; } exports.CenterX = CenterX; function CenterY(gpmlElement) { var CenterY = gpmlElement.Graphics.CenterY; return CenterY - exports.Height(gpmlElement) / 2; } exports.CenterY = CenterY; function Rotation(gpmlElement) { // NOTE: the rotation input field in the PathVisio-Java UI expects degrees, // but GPML expresses rotation in radians. The XSD indicates GPML can also // use directional strings, although I haven't seen one used in actual GPML. // For both the PathVisio-Java UI and GPML, a positive value means clockwise // rotation. // NOTE: GPML can hold a rotation value for State elements in an element // named "Attribute" like this: // Key="org.pathvisio.core.StateRotation" // From discussion with AP and KH, we've decided to ignore this value, // because we don't actually want States to be rotated. var Graphics = gpmlElement.Graphics; var Rotation = !gpml_utilities_2.isDefinedCXML(Graphics.Rotation) ? 0 : Graphics.Rotation; // NOTE: Output is in degrees, because that's what the SVG transform // attribute accepts. Don't get confused, because we use radians in // the edge processing. // // NOTE: to make it as simple as possible for users to work with pvjson, // we're normalizing these rotation values so they are always positive values // between 0 and 2 * Math.PI, e.g., // (3/2) * Math.PI, not -1 * Math.PI/2 or (7/3) * Math.PI return Angle_1.radiansToDegrees(Angle_1.normalize(GPML_ROTATION_SIDE_TO_RAD.hasOwnProperty(Rotation) ? GPML_ROTATION_SIDE_TO_RAD[Rotation] : parseAsNonNaNNumber(Rotation))); } exports.Rotation = Rotation; function LineStyle(gpmlElement) { var LineStyle = gpmlElement.Graphics.LineStyle; // TODO hard-coding this here is not the most maintainable if (LineStyle === "Solid") { // this gets converted to strokeDasharray, // and we don't need this value when it's // solid, so we return undefined, because // then this won't be included. return; } else if (LineStyle === "Broken") { return "5,3"; } else { throw new Error("Unrecognized LineStyle: " + LineStyle); } } exports.LineStyle = LineStyle; exports.Author = fp_1.flow(fp_1.get("Author"), decodeIfNotEmpty); exports.DataSource = fp_1.flow(fp_1.get("Data-Source"), decodeIfNotEmpty); exports.Email = fp_1.flow(fp_1.get("Email"), decodeIfNotEmpty); exports.Maintainer = fp_1.flow(fp_1.get("Maintainer"), decodeIfNotEmpty); exports.Name = fp_1.flow(fp_1.get("Name"), decodeIfNotEmpty); exports.TextLabel = fp_1.flow(fp_1.get("TextLabel"), decodeIfNotEmpty); // TODO is this ever used? // The only way I see to create underlined text in PathVisio-Java // is to create a Label and fill in the Link field. // But the resulting GPML does not have a FontDecoration attribute. function getTextDecorationFromGPMLElement(gpmlElement) { var _a = gpmlElement.Graphics, FontDecoration = _a.FontDecoration, FontStrikethru = _a.FontStrikethru; var outputChunks = []; var fontDecorationDefined = gpml_utilities_2.isDefinedCXML(FontDecoration) && FontDecoration === "Underline"; var fontStrikethruDefined = gpml_utilities_2.isDefinedCXML(FontStrikethru) && FontStrikethru === "Strikethru"; if (fontDecorationDefined || fontStrikethruDefined) { if (fontDecorationDefined) { outputChunks.push("underline"); } if (fontStrikethruDefined) { outputChunks.push("line-through"); } } else { outputChunks.push("none"); } return outputChunks.join(" "); } exports.getTextDecorationFromGPMLElement = getTextDecorationFromGPMLElement; exports.Align = fp_1.flow(fp_1.get("Graphics.Align"), fp_1.kebabCase); exports.FontDecoration = getTextDecorationFromGPMLElement; exports.FontStrikethru = getTextDecorationFromGPMLElement; exports.FontStyle = fp_1.flow(fp_1.get("Graphics.FontStyle"), fp_1.kebabCase); exports.FontWeight = fp_1.flow(fp_1.get("Graphics.FontWeight"), fp_1.kebabCase); exports.Valign = fp_1.flow(fp_1.get("Graphics.Valign"), fp_1.kebabCase); exports.Href = fp_1.flow(fp_1.get("Href"), decodeIfNotEmpty, encodeURI); function gpmlColorToCssColor(colorValue) { var colorValueLowerCased = colorValue.toLowerCase(); if (["transparent", "none"].indexOf(colorValueLowerCased) > -1) { return colorValueLowerCased; } else { var color = new RGBColor(colorValue); if (!color.ok) { throw new VError("\n\t\t\t\tFailed to get a valid CSS color for gpmlColorToCssColor(" + colorValue + ")\n\t\t\t\tIs there an invalid Color or FillColor in the GPML?\n\t\t\t\t"); // TODO should we use this? // return "#c0c0c0"; } return color.toHex(); } } exports.gpmlColorToCssColor = gpmlColorToCssColor; exports.Color = fp_1.flow(fp_1.get("Graphics.Color"), gpmlColorToCssColor); function FillColor(gpmlElement) { var _a = gpmlElement.Graphics, FillColor = _a.FillColor, ShapeType = _a.ShapeType; // If it's a GPML Group, DataNode, Shape, Label or State, it needs a // ShapeType in order for it to have a FillColor, but a // GPML Interaction or GraphicalLine can have a FillColor // without having a ShapeType. return (!!ShapeType && ShapeType.toLowerCase() !== "none") || gpmlElement.Graphics.hasOwnProperty("Point") ? gpmlColorToCssColor(FillColor) : "transparent"; } exports.FillColor = FillColor; function LineThickness(gpmlElement) { var _a = gpmlElement.Graphics, LineThickness = _a.LineThickness, ShapeType = _a.ShapeType; // See note near Height converter regarding LineThickness. // If it's a GPML Group, DataNode, Shape, Label or State, it needs a // ShapeType in order for it to have a LineThickness > 0, but a // GPML Interaction or GraphicalLine can have a LineThickness > 0 // without having a ShapeType. if (!gpml_utilities_2.isDefinedCXML(LineThickness)) { return 0; } else if (gpml_utilities_2.isDefinedCXML(ShapeType) && ShapeType.toLowerCase() !== "none") { /* return findIndex(function({ Key, Value }) { return Key === "org.pathvisio.DoubleLineProperty"; }, gpmlElement.Attribute) > -1 ? LineThickness * 3 : LineThickness; //*/ /* return findIndex(function({ Key, Value }) { return Key === "org.pathvisio.DoubleLineProperty"; }, gpmlElement.Attribute) > -1 ? LineThickness : LineThickness * 2; //*/ //return LineThickness * 2; return LineThickness; } else if (gpmlElement.Graphics.hasOwnProperty("Point")) { return LineThickness; } else { return 0; } } exports.LineThickness = LineThickness; function ConnectorType(gpmlElement) { var ConnectorType = gpmlElement.Graphics.ConnectorType; return ConnectorType + "Line"; } exports.ConnectorType = ConnectorType; // We return a partial attachmentDisplay, because it's // merged with the other items as we come across them. function Position(gpmlElement) { var Position = gpmlElement.Position; return { position: [Position, 0], // a GPML Anchor never has an offset offset: [0, 0] }; } exports.Position = Position; /** * getPositionAndRelativeOffsetScalarsAlongAxis * * @param relValue {number} * @return {OffsetOrientationAndPositionScalarsAlongAxis} */ function getPositionAndRelativeOffsetScalarsAlongAxis(relValue) { var relativeOffsetScalar; var positionScalar; var relativeToUpperLeftCorner = (relValue + 1) / 2; if (relativeToUpperLeftCorner < 0 || relativeToUpperLeftCorner > 1) { if (relativeToUpperLeftCorner < 0) { positionScalar = 0; relativeOffsetScalar = relativeToUpperLeftCorner; } else { positionScalar = 1; relativeOffsetScalar = relativeToUpperLeftCorner - 1; } } else { positionScalar = relativeToUpperLeftCorner; relativeOffsetScalar = 0; } if (!isFinite(positionScalar) || !isFinite(relativeOffsetScalar)) { throw new Error("Expected finite values for positionScalar " + positionScalar + " and relativeOffsetScalar " + relativeOffsetScalar); } return { relativeOffsetScalar: relativeOffsetScalar, positionScalar: positionScalar }; } // We actually handle both RelX and RelY together // when we hit RelX and then ignoring when we // hit RelY. // We return a partial attachmentDisplay, because it's // merged with the other items as we come across them. function RelX(gpmlElement) { // first is for a State (?), second is for a Point var RelXRelYContainer = gpml_utilities_2.isDefinedCXML(gpmlElement.Graphics) ? gpmlElement.Graphics : gpmlElement; var RelX = RelXRelYContainer.RelX, RelY = RelXRelYContainer.RelY; var _a = getPositionAndRelativeOffsetScalarsAlongAxis(RelX), relativeOffsetScalarX = _a.relativeOffsetScalar, positionScalarX = _a.positionScalar; var _b = getPositionAndRelativeOffsetScalarsAlongAxis(RelY), relativeOffsetScalarY = _b.relativeOffsetScalar, positionScalarY = _b.positionScalar; return { position: [positionScalarX, positionScalarY], // we can't calculate absolute offset until we get the // referenced element width/height offset: [], relativeOffset: [relativeOffsetScalarX, relativeOffsetScalarY] }; } exports.RelX = RelX; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiVmFsdWVDb252ZXJ0ZXJzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjLzIwMTNhL1ZhbHVlQ29udmVydGVycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxnQ0FVbUI7QUFDbkIsSUFBTSxRQUFRLEdBQUcsT0FBTyxDQUFDLFVBQVUsQ0FBQyxDQUFDO0FBQ3JDLCtCQUFpQztBQUNqQyx5QkFBNEI7QUFFNUIsb0RBSTJCO0FBQzNCLDJDQUFnRTtBQUNoRSxvREFBa0Q7QUFHbEQsMkNBQTJDO0FBQzNDLHdDQUF3QztBQUN4QyxrQ0FBa0M7QUFDbEMsMENBQTBDO0FBQzFDLDZDQUE2QztBQUM3QyxnQkFBZ0I7QUFDaEIsSUFBTSx5QkFBeUIsR0FBRztJQUNoQyxHQUFHLEVBQUUsQ0FBQztJQUNOLEtBQUssRUFBRSxHQUFHLEdBQUcsSUFBSSxDQUFDLEVBQUU7SUFDcEIsTUFBTSxFQUFFLElBQUksQ0FBQyxFQUFFO0lBQ2YsSUFBSSxFQUFFLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxHQUFHLElBQUksQ0FBQyxFQUFFO0NBQ3hCLENBQUM7QUFFRixTQUFTLGdCQUFnQixDQUFDLEtBQUs7SUFDN0IsT0FBTyxZQUFPLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsV0FBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO0FBQ2hELENBQUM7QUFFRCxTQUFTLG1CQUFtQixDQUFDLENBQWtCO0lBQzdDLElBQU0sTUFBTSxHQUFHLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUN6QixJQUFJLFVBQUssQ0FBQyxNQUFNLENBQUMsRUFBRTtRQUNqQixNQUFNLElBQUksS0FBSyxDQUFDLGdCQUFnQixHQUFHLE1BQU0sQ0FBQyxDQUFDLENBQUMsR0FBRyxxQkFBcUIsQ0FBQyxDQUFDO0tBQ3ZFO0lBQ0QsT0FBTyxNQUFNLENBQUM7QUFDaEIsQ0FBQztBQUVELG1CQUFtQjtBQUNuQixtQkFBbUI7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