1 | import { assign, curry, fromPairs, isArray, isPlainObject, isFinite, toPairs } from "lodash/fp";
|
2 | import { isPvjsonEdge, isPvjsonSingleFreeNode, unionLSV } from "./gpml-utilities";
|
3 | import * as GPML2013aGroupMappingsByStyle from "./2013a/GroupMappingsByStyle.json";
|
4 | export function getGroupDimensions(padding, strokeWidth, containedEntities) {
|
5 | if (containedEntities.length === 0) {
|
6 | console.warn(`Warning: Empty group observed.`);
|
7 | return {
|
8 | x: 0,
|
9 | y: 0,
|
10 | width: 0,
|
11 | height: 0,
|
12 | zIndex: 0
|
13 | };
|
14 | }
|
15 | else if (!isFinite(padding)) {
|
16 | throw new Error("Invalid padding value: ${padding}");
|
17 | }
|
18 | else if (!isFinite(strokeWidth)) {
|
19 | throw new Error("Invalid strokeWidth value: ${strokeWidth}");
|
20 | }
|
21 | const dimensions = containedEntities
|
22 | .filter(entity => isPvjsonSingleFreeNode(entity) || isPvjsonEdge(entity))
|
23 | .reduce(function ({ runningDimensions, topLeftCorner, bottomRightCorner }, entity) {
|
24 | const { zIndex } = entity;
|
25 | const zIndexIsFinite = isFinite(zIndex);
|
26 | const runningDimensionsZIndexIsFinite = isFinite(runningDimensions.zIndex);
|
27 | if (zIndexIsFinite && runningDimensionsZIndexIsFinite) {
|
28 | runningDimensions.zIndex = Math.min(zIndex, runningDimensions.zIndex);
|
29 | }
|
30 | else if (zIndexIsFinite) {
|
31 | runningDimensions.zIndex = zIndex;
|
32 | }
|
33 | if (isPvjsonEdge(entity)) {
|
34 | const points = entity.points;
|
35 | // If entity is an edge
|
36 | const firstPoint = points[0];
|
37 | const firstPointX = firstPoint.x;
|
38 | const firstPointY = firstPoint.y;
|
39 | const lastPoint = points[points.length - 1];
|
40 | const lastPointX = lastPoint.x;
|
41 | const lastPointY = lastPoint.y;
|
42 | topLeftCorner.x = Math.min(topLeftCorner.x, firstPointX, lastPointX);
|
43 | topLeftCorner.y = Math.min(topLeftCorner.y, firstPointY, lastPointY);
|
44 | bottomRightCorner.x = Math.max(bottomRightCorner.x, firstPointX, lastPointX);
|
45 | bottomRightCorner.y = Math.max(bottomRightCorner.y, firstPointY, lastPointY);
|
46 | }
|
47 | else {
|
48 | // If entity is a node
|
49 | topLeftCorner.x = Math.min(topLeftCorner.x, entity.x);
|
50 | topLeftCorner.y = Math.min(topLeftCorner.y, entity.y);
|
51 | bottomRightCorner.x = Math.max(bottomRightCorner.x, entity.x + entity.width);
|
52 | bottomRightCorner.y = Math.max(bottomRightCorner.y, entity.y + entity.height);
|
53 | }
|
54 | runningDimensions.x = topLeftCorner.x - padding - strokeWidth;
|
55 | runningDimensions.y = topLeftCorner.y - padding - strokeWidth;
|
56 | runningDimensions.width =
|
57 | bottomRightCorner.x - topLeftCorner.x + 2 * (padding + strokeWidth);
|
58 | runningDimensions.height =
|
59 | bottomRightCorner.y - topLeftCorner.y + 2 * (padding + strokeWidth);
|
60 | return { runningDimensions, topLeftCorner, bottomRightCorner };
|
61 | }, {
|
62 | topLeftCorner: {
|
63 | x: Infinity,
|
64 | y: Infinity
|
65 | },
|
66 | bottomRightCorner: {
|
67 | x: 0,
|
68 | y: 0
|
69 | },
|
70 | runningDimensions: {
|
71 | zIndex: Infinity
|
72 | }
|
73 | }).runningDimensions;
|
74 | const propertiesToCheck = ["x", "y", "width", "height", "zIndex"];
|
75 | const nonFinites = propertiesToCheck
|
76 | .map(function (key) {
|
77 | return [[key], dimensions[key]];
|
78 | })
|
79 | .filter(dims => !isFinite(dims[1]));
|
80 | if (nonFinites.length > 0) {
|
81 | throw new Error(`Got a non-finite value(s):
|
82 | ${JSON.stringify(fromPairs(nonFinites), null, " ")}
|
83 | when calling
|
84 | getGroupDimensions(
|
85 | padding: ${padding},
|
86 | strokeWidth: ${strokeWidth},
|
87 | containedEntities: ${JSON.stringify(containedEntities, null, " ")}
|
88 | )
|
89 |
|
90 | `);
|
91 | }
|
92 | return dimensions;
|
93 | }
|
94 | // NOTE: side effects
|
95 | export const preprocessGPML = curry(function (processor, Group) {
|
96 | // NOTE: The class "Group" is the only class that uses the "Style" property.
|
97 | // There are defaults for each Style, so we apply them here.
|
98 | toPairs(GPML2013aGroupMappingsByStyle[Group.Style]).forEach(function ([mappingKey, mappingValue]) {
|
99 | const oldValue = Group[mappingKey];
|
100 | let newValue;
|
101 | if (isPlainObject(mappingValue)) {
|
102 | newValue = assign(oldValue || {}, mappingValue);
|
103 | }
|
104 | else if (Group.hasOwnProperty(mappingKey)) {
|
105 | if (isArray(mappingValue)) {
|
106 | newValue = unionLSV(mappingValue, oldValue);
|
107 | }
|
108 | else {
|
109 | newValue = oldValue;
|
110 | }
|
111 | }
|
112 | else {
|
113 | // override prototype with default by style
|
114 | newValue = mappingValue;
|
115 | }
|
116 | Group[mappingKey] = newValue;
|
117 | });
|
118 | Group.Contains = processor.containedGraphIdsByGroupGroupId[Group.GroupId];
|
119 | return Group;
|
120 | });
|
121 | export function postprocessPVJSON(containedEntities, group) {
|
122 | return assign(group, getGroupDimensions(group.padding, group.strokeWidth, containedEntities));
|
123 | }
|
124 | //// TODO do we need any of this old code that was in post-process.ts?
|
125 | // // Kludge to get the zIndex for Groups
|
126 | // const zIndexForGroups =
|
127 | // -1 +
|
128 | // EDGES.concat(["DataNode", "Label"])
|
129 | // .reduce(function(acc, gpmlElementName) {
|
130 | // data[gpmlElementName].forEach(function(el) {
|
131 | // acc.push(el);
|
132 | // });
|
133 | // return acc;
|
134 | // }, [])
|
135 | // .reduce(getFromElementMapByIdIfExists, [])
|
136 | // .map(element => element.zIndex)
|
137 | // .reduce(function(acc, zIndex) {
|
138 | // return Math.min(acc, zIndex);
|
139 | // }, Infinity);
|
140 | //
|
141 | // // specify contained elements in groups
|
142 | // data.Group
|
143 | // .reduce(getFromElementMapByIdIfExists, [])
|
144 | // .map(function(element) {
|
145 | // element.zIndex = zIndexForGroups;
|
146 | //
|
147 | // // NOTE: pvjson doesn't use GroupId. It just uses GraphId as the id for an element.
|
148 | // // That means:
|
149 | // // GPML GroupId is replaced in pvjson by just id (from GraphId), and
|
150 | // // GPML GroupRef is replaced in pvjson by element.isPartOf and group.contains (from GraphRef)
|
151 | // // We need to map from GroupId/GroupRef to id/contains/isPartOf here.
|
152 | // // element.id refers to the value of the GraphId of the Group
|
153 | // const groupGraphId = element.id;
|
154 | // const containedIds = (element.contains =
|
155 | // data.containedIdsByGroupId[data.GraphIdToGroupId[groupGraphId]] || []);
|
156 | //
|
157 | // if (containedIds.length > 0) {
|
158 | // // NOTE side effects
|
159 | // containedIds
|
160 | // .reduce(getFromElementMapByIdIfExists, [])
|
161 | // .map(function(contained) {
|
162 | // contained.isPartOf = groupGraphId;
|
163 | // return contained;
|
164 | // })
|
165 | // .forEach(upsertDataMapEntry.bind(undefined, elementMap));
|
166 | // } else {
|
167 | // // NOTE: side effect
|
168 | // delete elementMap[groupGraphId];
|
169 | // }
|
170 | //
|
171 | // return element;
|
172 | // })
|
173 | // .forEach(upsertDataMapEntry.bind(undefined, elementMap));
|
174 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ3JvdXAuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvZ3JvdXAudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUNMLE1BQU0sRUFDTixLQUFLLEVBQ0wsU0FBUyxFQUNULE9BQU8sRUFDUCxhQUFhLEVBQ2IsUUFBUSxFQUNSLE9BQU8sRUFDUixNQUFNLFdBQVcsQ0FBQztBQUNuQixPQUFPLEVBQ0wsWUFBWSxFQUNaLHNCQUFzQixFQUN0QixRQUFRLEVBQ1QsTUFBTSxrQkFBa0IsQ0FBQztBQUMxQixPQUFPLEtBQUssNkJBQTZCLE1BQU0sbUNBQW1DLENBQUM7QUFhbkYsTUFBTSw2QkFDSixPQUFlLEVBQ2YsV0FBbUIsRUFDbkIsaUJBQWlDO0lBRWpDLEVBQUUsQ0FBQyxDQUFDLGlCQUFpQixDQUFDLE1BQU0sS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ25DLE9BQU8sQ0FBQyxJQUFJLENBQUMsZ0NBQWdDLENBQUMsQ0FBQztRQUMvQyxNQUFNLENBQUM7WUFDTCxDQUFDLEVBQUUsQ0FBQztZQUNKLENBQUMsRUFBRSxDQUFDO1lBQ0osS0FBSyxFQUFFLENBQUM7WUFDUixNQUFNLEVBQUUsQ0FBQztZQUNULE1BQU0sRUFBRSxDQUFDO1NBQ1YsQ0FBQztJQUNKLENBQUM7SUFBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQzlCLE1BQU0sSUFBSSxLQUFLLENBQUMsbUNBQW1DLENBQUMsQ0FBQztJQUN2RCxDQUFDO0lBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUNsQyxNQUFNLElBQUksS0FBSyxDQUFDLDJDQUEyQyxDQUFDLENBQUM7SUFDL0QsQ0FBQztJQUNELE1BQU0sVUFBVSxHQUFHLGlCQUFpQjtTQUNqQyxNQUFNLENBQUMsTUFBTSxDQUFDLEVBQUUs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 |
\ | No newline at end of file |