From b3f3eb208a09eaab8b67867d49fe6f0f524164dd Mon Sep 17 00:00:00 2001 From: Julian Rojas Date: Tue, 2 Feb 2021 23:40:09 +0100 Subject: [PATCH 01/10] Optimize route calculation to avoid consecutive internal node links --- src/algorithm/PathFinder.js | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/algorithm/PathFinder.js b/src/algorithm/PathFinder.js index cac70af..d8d1cd4 100644 --- a/src/algorithm/PathFinder.js +++ b/src/algorithm/PathFinder.js @@ -100,7 +100,6 @@ export class PathFinder extends EventEmitter { // Priority queue const queue = new TinyQueue([], (a, b) => { return a.cost - b.cost }); - // Add all starting NodePorts to the queue for (const f of from.ports) { queue.push({ @@ -108,16 +107,19 @@ export class PathFinder extends EventEmitter { cost: 0, lngLat: from.lngLat }); + queued.add(f); } // In this variable we will store the found arrival NodePort let dest = null; - // Step counter to increase the cost of routes with more routes + // Keep track of the current MicroNode + let currMN = null; + // Step counter to increase the cost of routes with more hops let step = 0; while (queue.length) { - step++; const here = queue.pop(); + // console.log('STEP: ', step); // console.log('HERE: ', here); // Arrived at destination if (toSet.has(here.from)) { @@ -131,9 +133,16 @@ export class PathFinder extends EventEmitter { // Add micro node to visited list explored.add(here.from); - // Skip If no there are no outgoing edges from this micro node, it means it is a dead end + const node = NG.nodes.get(here.from); // console.log('HERE\'s node: ', node); + + if(node.microNode !== currMN) { + // We jumped to another Micro Node + currMN = node.microNode; + step++; + } + // Skip If no there are no outgoing edges from this micro node, it means it is a dead end if (node.edges.size > 0) { for (const [i, e] of node.edges.entries()) { const edge = NG.edges.get(e); @@ -147,7 +156,7 @@ export class PathFinder extends EventEmitter { } let nextNode = NG.nodes.get(next.from); - // console.log('NEXT node: ', nextNode); + // console.log('NEXT node: ', nextNode, ' via edge: ', edge); // If undefined it means it was deliberately removed to find alternative paths, so skip it if (!nextNode) continue; @@ -173,9 +182,11 @@ export class PathFinder extends EventEmitter { if (pathMap.has(next.from)) { if (pathMap.get(next.from).cost < here.cost) { pathMap.set(next.from, { from: here.from, edge: e, cost: here.cost }); + // console.log('PathMap set: ', next.from, e, here.from, here.cost); } } else { pathMap.set(next.from, { from: here.from, edge: e, cost: here.cost }); + // console.log('PathMap set: ', next.from, e, here.from, here.cost); } // Add to the queue @@ -191,6 +202,7 @@ export class PathFinder extends EventEmitter { if (dest) { // Rebuild path + // console.log(pathMap); const path = { nodes: [dest], edges: [] }; let node = pathMap.get(dest); From b5a5b1251f789a351e4112dcbbfc7e893b62a240 Mon Sep 17 00:00:00 2001 From: Julian Rojas Date: Thu, 11 Feb 2021 13:24:59 +0100 Subject: [PATCH 02/10] Draft to visualize internal conectivity with beautiful-react-diagrams lib --- package-lock.json | 64 ++++-- package.json | 15 +- src/components/MainLayout.js | 45 ++++- src/components/OPInternalView.js | 241 +++++++++++++++++++++++ src/components/OperationalPointsLayer.js | 3 +- src/components/RoutesInfo.js | 32 +-- src/index.css | 31 +++ src/styles/Styles.js | 48 ++++- src/utils/NameSpaces.js | 1 + src/utils/Utils.js | 83 +++++++- 10 files changed, 509 insertions(+), 54 deletions(-) create mode 100644 src/components/OPInternalView.js diff --git a/package-lock.json b/package-lock.json index 930b875..29bd055 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { - "name": "era-ui", - "version": "1.1.2", + "name": "era-compatibility-check", + "version": "1.3.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2031,6 +2031,28 @@ "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", "dev": true }, + "beautiful-react-diagrams": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/beautiful-react-diagrams/-/beautiful-react-diagrams-0.5.1.tgz", + "integrity": "sha512-B5sw+L27acwcOv0BUqCOPSf29sFDLXm8Fn2Vl0s4IFn9lrwZU6uqKN6oVl1R+M/M6mvEP9ZxvuSyVPNS8mAEIg==", + "requires": { + "beautiful-react-hooks": "^0.31.0", + "classnames": "^2.2.6", + "lodash.findindex": "^4.6.0", + "lodash.isequal": "^4.5.0", + "lodash.throttle": "^4.1.1", + "prop-types": "^15.7.2" + } + }, + "beautiful-react-hooks": { + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/beautiful-react-hooks/-/beautiful-react-hooks-0.31.0.tgz", + "integrity": "sha512-LfMsMzF1rnaUlaEu7qKADUzi+FVDALN60/i0wuqBM4iSb9dYjcH9Scf8vDYZ690NuzfArIZO63IKpeZqODFl8A==", + "requires": { + "lodash.debounce": "^4.0.8", + "lodash.throttle": "^4.1.1" + } + }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -5401,15 +5423,35 @@ } }, "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, "lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" + }, + "lodash.findindex": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.findindex/-/lodash.findindex-4.6.0.tgz", + "integrity": "sha1-oyRd7mH7m24GJLU1ElYku2nBEQY=" + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + }, + "lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" + }, "loglevel": { "version": "1.6.8", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.8.tgz", @@ -6747,9 +6789,9 @@ } }, "react": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz", - "integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==", + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", + "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -6757,9 +6799,9 @@ } }, "react-dom": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz", - "integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==", + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", + "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", diff --git a/package.json b/package.json index 93adc82..ee4dab2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "era-compatibility-check", - "version": "1.3.0", + "version": "1.3.1", "description": "ERA's route compatibility check application", "scripts": { "start": "webpack-dev-server --open --mode development", @@ -12,18 +12,6 @@ "type": "git", "url": "git+https://github.com/julianrojas87/era-compatibility-check.git" }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, "devDependencies": { "@babel/core": "^7.10.3", "@babel/plugin-proposal-class-properties": "^7.10.1", @@ -49,6 +37,7 @@ "@rdfjs/data-model": "^1.1.2", "@turf/distance": "^6.0.1", "@turf/helpers": "^6.1.4", + "beautiful-react-diagrams": "^0.5.1", "jsonld-streaming-parser": "^2.0.2", "mapbox-gl": "^1.11.0", "n3": "^1.5.0", diff --git a/src/components/MainLayout.js b/src/components/MainLayout.js index 3715765..cd7927b 100644 --- a/src/components/MainLayout.js +++ b/src/components/MainLayout.js @@ -6,6 +6,7 @@ import { OperationalPointsLayer } from './OperationalPointsLayer'; import { TileFramesLayer } from './TileFramesLayer'; import { RoutesLayer } from './RoutesLayer'; import { RoutesInfo } from './RoutesInfo'; +import { OPInternalView } from './OPInternalView'; import { HelpPage } from './HelpPage'; import RDFetch from '../workers/RDFetch.worker'; import { TileFetcherWorkerPool } from '../workers/TileFetcherWorkerPool'; @@ -81,6 +82,7 @@ class MainLayout extends Component { popup: null, loading: false, showTileFrames: true, + internalView: false, helpPage: false, tileFrames: new Map(), compatibilityVehicleType: null, @@ -93,7 +95,10 @@ class MainLayout extends Component { routes: [], calculatingRoutes: false, loaderMessage: null, - routeFilter: new Set() + routeFilter: new Set(), + internalViewNode: null, + internalViewPath: null, + pathColor: null }; // Web Workers pool for fetching data tiles this.tileFetcherPool = new TileFetcherWorkerPool(); @@ -272,10 +277,11 @@ class MainLayout extends Component { microNode: quad.object.value }); this.graphStore.add(quad); - } else { - // Add the rest of the quads to the RDF graph store - this.graphStore.add(quad); } + + // Add the the quad to the RDF graph store + this.graphStore.add(quad); + } if (e.data.done) { @@ -554,10 +560,20 @@ class MainLayout extends Component { routeFilter: new Set(), tileFrames: new Map(), showTileFrames: false, - calculatingRoutes: false + calculatingRoutes: false, + loading: false }, () => { this.renderMicroNodes() }); } + toggleInternalView = (show, mn, route) => { + this.setState({ + internalView: show, + internalViewNode: mn, + internalViewPath: route? route.path : null, + pathColor: route ? route.style['line-color'] : null + }); + }; + toggleHelpPage = show => { this.setState({ helpPage: show }); } @@ -587,6 +603,10 @@ class MainLayout extends Component { loading, tileFrames, helpPage, + internalView, + internalViewNode, + internalViewPath, + pathColor, showTileFrames, vehicleTypes, vehicles, @@ -603,6 +623,14 @@ class MainLayout extends Component { return (
+ +
@@ -610,8 +638,8 @@ class MainLayout extends Component {
Route Compatibility Check - this.toggleHelpPage(true)}/> + this.toggleHelpPage(true)} />
@@ -668,7 +696,8 @@ class MainLayout extends Component { fetchAbstractionTile={this.fetchAbstractionTile} compatibilityVehicleType={compatibilityVehicleType} compatibilityVehicle={compatibilityVehicle} - checkCompatibility={this.checkCompatibility}> + checkCompatibility={this.checkCompatibility} + toggleInternalView={this.toggleInternalView}> {calculatingRoutes && ()} diff --git a/src/components/OPInternalView.js b/src/components/OPInternalView.js new file mode 100644 index 0000000..5860625 --- /dev/null +++ b/src/components/OPInternalView.js @@ -0,0 +1,241 @@ +import React, { useState, useEffect } from "react"; +import { Modal, Toggle } from "rsuite"; +import { RDFS, ERA } from "../utils/NameSpaces"; +import { FACETED_BASE_URI } from "../config/config"; +import Utils from "../utils/Utils"; +import Diagram, { useSchema } from 'beautiful-react-diagrams'; +import { + opBaseNodeDiagram, + nodeLabelStyle, + opNormalNodeDiagram, + opRouteNodeDiagram +} from '../styles/Styles'; +import 'beautiful-react-diagrams/styles.css'; + + +export const OPInternalView = ({ + internalViewNode, + internalViewPath, + pathColor, + show, + toggleInternalView, + graphStore +}) => { + const [op, setOp] = useState(null); + const [display, setDisplay] = useState(false); + const [canvasHeight, setCanvasHeight] = useState(0); + // create diagrams schema + const [schema, { onChange, addNode, removeNode }] = useSchema(); + + const opBaseNode = ({ data }) => { + return (
); + } + + const opNormalNode = ({ id, content, data }) => { + return ( +
+
+ data.onChange(checked, id)} /> +
+
+ {content} +
+
+ ); + } + + const opRouteNode = ({ id, content, data }) => { + return ( +
+
+ data.onChange(checked, id)} /> +
+
+ {content} +
+
+ ); + } + + const isRouteLink = inl => { + console.log(inl) + let from = null; + let to = null; + for (const n of schema.nodes) { + if (n.id === inl[ERA.startPort]) { + from = n; + if (to) break; + } + if (n.id === inl[ERA.endPort]) { + to = n; + if (from) break; + } + } + + console.log(from, to); + + + if (from.className === 'route-node' && to.className === 'route-node') { + return true; + } + + return false + } + + const onChangeNodePort = (checked, np) => { + if (checked) { + // Find and add all nodes starting from this Node Port + const inls = Utils.getAllInternalNodeLinksFromNodePort(np, graphStore); + for (const inl of inls) { + if (!isRouteLink(inl)) { + schema.links.push({ + input: np, + output: inl[ERA.endPort], + className: 'normal-link' + }); + } + } + } else { + schema.links = schema.links.filter(l => l.input !== np || l.className !== 'normal-link'); + } + + // Trigger change for re-render + onChange(schema); + }; + + const buildSchema = (ivn, ivp) => { + const inNPs = []; + const outNPs = []; + + // Retrieve all node ports for this OP + const nodePorts = Utils.getAllNodePorts(graphStore, ivn[ERA.hasAbstraction]); + let firstRouteNP = null; + + for (const np of nodePorts) { + // Check if this is a NodePort of the route + if (ivp.nodes.includes(np)) { + if (firstRouteNP) { + const inNode = { + render: opRouteNode, + data: { onChange: onChangeNodePort, switchAlign: 'right' }, + className: 'route-node' + } + const outNode = { + render: opRouteNode, + data: { onChange: onChangeNodePort, switchAlign: 'left' }, + className: 'route-node' + } + if (ivp.nodes.indexOf(np) > ivp.nodes.indexOf(firstRouteNP)) { + // np is the outgoing NodePort + outNode.id = np; + outNode.content = {np.substring(np.indexOf('#') + 1)}; + inNode.id = firstRouteNP; + inNode.content = {firstRouteNP.substring(firstRouteNP.indexOf('#') + 1)}; + } else { + // np in the incoming NodePort + inNode.id = np; + inNode.content = {np.substring(np.indexOf('#') + 1)}; + outNode.id = firstRouteNP; + outNode.content = {firstRouteNP.substring(firstRouteNP.indexOf('#') + 1)}; + } + + // Add route node ports to proper arrays + inNPs.push(inNode); + outNPs.push(outNode); + // Create special route link + schema.links.push({ + input: inNode.id, + output: outNode.id, + className: 'route-link' + }); + } else { + firstRouteNP = np; + } + } else { + const otherNode = { + id: np, + content: {np.substring(np.indexOf('#') + 1)}, + render: opNormalNode, + data: { onChange: onChangeNodePort } + } + // Check if NodePort is incoming or outgoing + if (Utils.isNodePortIncoming(np, graphStore)) { + otherNode.data.switchAlign = 'right'; + inNPs.push(otherNode); + } else { + otherNode.data.switchAlign = 'left'; + outNPs.push(otherNode); + } + } + } + + // Build base node according to the amount of node ports + const baseNodeHeight = Math.max(inNPs.length, outNPs.length); + const baseNode = { + id: 'op', + disableDrag: true, + coordinates: [Utils.vw(18.75), 40], + data: { size: baseNodeHeight }, + render: opBaseNode, + className: 'base-node' + }; + // Add the base node to the diagram + addNode(baseNode); + + // Set canvas size + setCanvasHeight((baseNodeHeight * 10) + 10); + + // Assign coordinates to input Node Ports and render + const baseNodeHeightPx = Utils.vh(baseNodeHeight * 10); + let inY = 55; + + inNPs.forEach(np => { + const label = np.id.substring(np.id.indexOf('#') + 1).length * 8.6; + const inX = Utils.vw(18.75) - (label > Utils.vw(20) ? Utils.vw(20) : label) + 80; + np.coordinates = [inX, inY]; + inY += baseNodeHeightPx / inNPs.length; + addNode(np); + }); + + // Assign coordinates to output Node Ports and render + inY = 55; + outNPs.forEach(np => { + const inX = Utils.vw(56.25) - 80; + np.coordinates = [inX, inY]; + inY += baseNodeHeightPx / outNPs.length; + addNode(np); + }); + } + + const cleanDiagram = () => { + schema.nodes.forEach(n => removeNode(n)); + schema.links = []; + } + + useEffect(() => { + setOp(internalViewNode); + setDisplay(show); + // New OP has been selected, proceed to build its diagram + if (internalViewNode) { + cleanDiagram(); + buildSchema(internalViewNode, internalViewPath); + } + }, [internalViewNode, show]); + + if (op) { + return
+ { toggleInternalView(false) }}> + + Internal connectivity of {op[RDFS.label]} ({op[ERA.uopid]}) + + +
+ +
+
+
+
+ } + + return null; +} \ No newline at end of file diff --git a/src/components/OperationalPointsLayer.js b/src/components/OperationalPointsLayer.js index dc784a0..3f991ae 100644 --- a/src/components/OperationalPointsLayer.js +++ b/src/components/OperationalPointsLayer.js @@ -1,7 +1,6 @@ -import React, { Component, Fragment } from "react"; +import React, { Component } from "react"; import { Layer, Feature } from "react-mapbox-gl"; import Utils from '../utils/Utils'; -import { ERA, OP_TYPES } from "../utils/NameSpaces"; import { mnIcon } from '../styles/Styles'; export class OperationalPointsLayer extends Component { diff --git a/src/components/RoutesInfo.js b/src/components/RoutesInfo.js index e08c842..d1e4fb6 100644 --- a/src/components/RoutesInfo.js +++ b/src/components/RoutesInfo.js @@ -1,5 +1,5 @@ import React, { Component, Fragment } from "react"; -import { Panel, Steps, Loader } from 'rsuite'; +import { Panel, Steps, Loader, Button } from 'rsuite'; import Utils from "../utils/Utils"; import { ERA, RDFS, SKOS, WGS84 } from '../utils/NameSpaces'; import { stepStyle, panelStyle, cellStyle } from '../styles/Styles'; @@ -37,13 +37,19 @@ export class RoutesInfo extends Component { } } - getOperationalPointTitle = op => { + getOperationalPointTitle = (op, internal, route) => { return ( - {`${op[RDFS.label]} (${op[ERA.opType][SKOS.prefLabel]})`} + + {`${op[RDFS.label]} (${op[ERA.opType][SKOS.prefLabel]})`} + {internal && ( + + )} + ); } @@ -93,11 +99,15 @@ export class RoutesInfo extends Component { getTrackDescription = (desc, reps) => { if (!reps) { - return (Track: {desc}); + return ( + + Track: + {this.getLabel(desc, RDFS.label)} + ); } else { return (
- Track: {desc}
+ Track: {this.getLabel(desc, RDFS.label)}
Vehicle Type: {this.getLabel(this.props.compatibilityVehicleType, ERA.typeVersionNumber)}
Vehicle: {this.getLabel(this.props.compatibilityVehicle, ERA.vehicleNumber)} @@ -118,7 +128,7 @@ export class RoutesInfo extends Component { {reps[rep].predicates.map((p, i) => { return ( - - {this.getLabel(p, RDFS.label)} + — {this.getLabel(p, RDFS.label)} ); })} @@ -207,7 +217,7 @@ export class RoutesInfo extends Component { 0 && i < Object.keys(steps).length - 1, r)} description={i < Object.keys(steps).length - 1 ? this.getTrackDescription(tracks[i], report[i]) : null}> ))} diff --git a/src/index.css b/src/index.css index 9ef05cf..96d38e3 100644 --- a/src/index.css +++ b/src/index.css @@ -119,4 +119,35 @@ code { max-width: 100%; box-sizing: initial; background-color: #fff; +} + +.rs-modal-lg { + width: 75vw; +} + +.normal-link path { + stroke-width: 0.15rem !important; + stroke: #aaaaaa !important; + animation: none !important; + marker-end: url(#triangle); +} + +.route-link path { + stroke-width: 0.3rem !important; + stroke: #0c3d78 !important; + stroke-dasharray: 10, 2; + animation: BiDashSegmentAnimation 1s linear infinite +} + +@keyframes BiDashSegmentAnimation { + 0% { + stroke-dashoffset: 24 + } + to { + stroke-dashoffset: 0 + } +} + +.base-node { + z-index: 0 !important; } \ No newline at end of file diff --git a/src/styles/Styles.js b/src/styles/Styles.js index d98af51..cc620c5 100644 --- a/src/styles/Styles.js +++ b/src/styles/Styles.js @@ -26,7 +26,7 @@ export const eraLogoWrapper = { } export const infoButton = { - "verticalAlign": 0, + "verticalAlign": 0, "float": "right", "cursor": "pointer" } @@ -108,31 +108,65 @@ export const input = { }; export const selectStyle = { - width: '100%', + width: '100%', marginTop: '10px' } export const stepStyle = { - width: '200px', - display: 'inline-table', + width: '200px', + display: 'inline-table', verticalAlign: 'top' } export const panelStyle = color => { return { - border: 'solid', + border: 'solid', borderColor: color, overflowX: 'auto', marginBottom: '10px' } -}; +} export const cellStyle = { borderLeft: '1px solid black', - borderRight: '1px solid black', + borderRight: '1px solid black', textAlign: 'center' } +export const opBaseNodeDiagram = size => { + return { + width: '37.5vw', + height: `${size * 10}vh`, + background: 'rgb(87 137 181)', + opacity: 0.3, + border: '0.07rem solid #8795a1', + borderRadius: '2rem', + borderShadow: '0 0.07rem 0.2rem 0 rgba(0, 0, 0, 0.1), 0 0.07rem 0.125rem 0 rgba(0, 0, 0, 0.06)' + } +} + +export const opNormalNodeDiagram = { + background: '#dae1e7', + border: '0.07rem solid #8795a1', + borderRadius: '0.25rem', + borderShadow: '0 0.07rem 0.2rem 0 rgba(0, 0, 0, 0.1), 0 0.07rem 0.125rem 0 rgba(0, 0, 0, 0.06)' +} + +export const opRouteNodeDiagram = color => { + return { + background: '#dae1e7', + border: `0.25rem solid ${color}`, + borderRadius: '0.25rem', + borderShadow: '0 0.07rem 0.2rem 0 rgba(0, 0, 0, 0.1), 0 0.07rem 0.125rem 0 rgba(0, 0, 0, 0.06)' + } +} + +export const nodeLabelStyle = { + padding: '0.3rem', + maxWidth: '20vw', + wordBreak: 'break-word' +} + function getRandomColor() { var letters = '0123456789ABCDEF'; var color = '#'; diff --git a/src/utils/NameSpaces.js b/src/utils/NameSpaces.js index a0c51d4..ed5de51 100644 --- a/src/utils/NameSpaces.js +++ b/src/utils/NameSpaces.js @@ -22,6 +22,7 @@ export const WGS84 = { } export const ERA = { + uopid: 'http://era.europa.eu/ns#uopid', opType: 'http://era.europa.eu/ns#opType', InternalNodeLink: 'http://era.europa.eu/ns#InternalNodeLink', belongsToNode: 'http://era.europa.eu/ns#belongsToNode', diff --git a/src/utils/Utils.js b/src/utils/Utils.js index 7d94600..f39492d 100644 --- a/src/utils/Utils.js +++ b/src/utils/Utils.js @@ -31,6 +31,18 @@ function isValidHttpUrl(string) { return url.protocol === "http:" || url.protocol === "https:"; } +// Returns the amount of pixels for a given relative viewport height +function vh(v) { + var h = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); + return (v * h) / 100; +} + +// Returns the amount of pixels for a given relative viewport width +function vw(v) { + var w = Math.max(document.documentElement.clientWidth, window.innerWidth || 0); + return (v * w) / 100; +} + function getTileFrame(coords, z, asXY) { let tile = null; if (asXY) { @@ -200,6 +212,40 @@ function getMicroNodePorts(store, mn) { } } +function getAllNodePorts(store, mn) { + // Query for all the NodePorts associated to this MicroNode + const queryNps = queryGraphStore({ + store: store, + p: ERA.belongsToNode, + o: mn + }); + + // There are disconnected MicroNodes + if (queryNps) { + return Object.keys(queryNps); + } +} + +function getAllInternalNodeLinksFromNodePort(np, store) { + const inls = []; + const links = queryGraphStore({ + store, + p: ERA.startPort, + o: np, + }); + + for (const inl of Object.keys(links)) { + if (isInternalNodeLink(inl, store)) { + inls.push({ + '@id': inl, + ...queryGraphStore({ store, s: inl })[inl] + }); + } + } + + return inls; +} + function getMicroNodeFromNodePort(np, store) { // Get the associated MicroNode const mn = queryGraphStore({ @@ -269,6 +315,17 @@ function isMicroLink(ml, store) { return q !== null; } +function isInternalNodeLink(inl, store) { + const q = queryGraphStore({ + store: store, + s: inl, + p: a, + o: ERA.InternalNodeLink + }); + + return q !== null; +} + function getTrackFromMicroLink(ml, store) { return queryGraphStore({ store: store, @@ -277,6 +334,22 @@ function getTrackFromMicroLink(ml, store) { })[ml][ERA.hasImplementation]; } +function isNodePortIncoming(np, store) { + const inLinks = queryGraphStore({ + store: store, + p: ERA.endPort, + o: np + }); + + for (const l of Object.keys(inLinks)) { + if (isMicroLink(l, store)) { + return true; + } + } + + return false; +} + function deepClone(obj) { let copy; @@ -321,7 +394,7 @@ function checkCompatibility(t, vehicle, store, includesVehicle) { const report = {}; let vehicleType = vehicle; - if(includesVehicle) { + if (includesVehicle) { vehicleType = vehicle[ERA.vehicleType]; } @@ -480,7 +553,7 @@ function checkCompatibility(t, vehicle, store, includesVehicle) { } // Noise restrictions - const nrs = track[ERA.isQuietRoute] === 'false' || (track[ERA.isQuietRoute] === 'true' + const nrs = track[ERA.isQuietRoute] === 'false' || (track[ERA.isQuietRoute] === 'true' && vehicle[ERA.operationalRestriction] !== 'http://era.europa.eu/concepts/restrictions#2.7.7'); report[ERA.operationalRestriction] = { predicates: [ERA.operationalRestriction, ERA.isQuietRoute], @@ -517,6 +590,8 @@ export default { lat2Tile, tile2long, tile2lat, + vh, + vw, isValidHttpUrl, getTileFrame, rebuildQuad, @@ -526,10 +601,14 @@ export default { getAllVehicles, getMicroNodeInfo, getMicroNodePorts, + getAllNodePorts, + getAllInternalNodeLinksFromNodePort, getMicroNodeFromNodePort, getOperationalPointFromMicroNode, getNodePortInfo, isMicroLink, + isInternalNodeLink, + isNodePortIncoming, getTrackFromMicroLink, checkCompatibility, deepClone From 120dd75bdac7526f6c7d56754ef2768ac78d04b1 Mon Sep 17 00:00:00 2001 From: Julian Rojas Date: Thu, 11 Feb 2021 15:57:16 +0100 Subject: [PATCH 03/10] WIP with graph d3 --- package-lock.json | 272 ++++++++++++++++++++++++++++++- package.json | 2 + src/components/OPInternalView.js | 213 +++++++++--------------- 3 files changed, 347 insertions(+), 140 deletions(-) diff --git a/package-lock.json b/package-lock.json index 29bd055..cab85bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2611,8 +2611,7 @@ "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, "commondir": { "version": "1.0.1", @@ -3238,6 +3237,270 @@ "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", "dev": true }, + "d3": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-5.16.0.tgz", + "integrity": "sha512-4PL5hHaHwX4m7Zr1UapXW23apo6pexCgdetdJ5kTmADpG/7T9Gkxw0M0tf/pjoB63ezCCm0u5UaFYy2aMt0Mcw==", + "requires": { + "d3-array": "1", + "d3-axis": "1", + "d3-brush": "1", + "d3-chord": "1", + "d3-collection": "1", + "d3-color": "1", + "d3-contour": "1", + "d3-dispatch": "1", + "d3-drag": "1", + "d3-dsv": "1", + "d3-ease": "1", + "d3-fetch": "1", + "d3-force": "1", + "d3-format": "1", + "d3-geo": "1", + "d3-hierarchy": "1", + "d3-interpolate": "1", + "d3-path": "1", + "d3-polygon": "1", + "d3-quadtree": "1", + "d3-random": "1", + "d3-scale": "2", + "d3-scale-chromatic": "1", + "d3-selection": "1", + "d3-shape": "1", + "d3-time": "1", + "d3-time-format": "2", + "d3-timer": "1", + "d3-transition": "1", + "d3-voronoi": "1", + "d3-zoom": "1" + } + }, + "d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" + }, + "d3-axis": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.12.tgz", + "integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ==" + }, + "d3-brush": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.6.tgz", + "integrity": "sha512-7RW+w7HfMCPyZLifTz/UnJmI5kdkXtpCbombUSs8xniAyo0vIbrDzDwUJB6eJOgl9u5DQOt2TQlYumxzD1SvYA==", + "requires": { + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" + } + }, + "d3-chord": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.6.tgz", + "integrity": "sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==", + "requires": { + "d3-array": "1", + "d3-path": "1" + } + }, + "d3-collection": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", + "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==" + }, + "d3-color": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz", + "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==" + }, + "d3-contour": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-1.3.2.tgz", + "integrity": "sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==", + "requires": { + "d3-array": "^1.1.1" + } + }, + "d3-dispatch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz", + "integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==" + }, + "d3-drag": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.5.tgz", + "integrity": "sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w==", + "requires": { + "d3-dispatch": "1", + "d3-selection": "1" + } + }, + "d3-dsv": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.2.0.tgz", + "integrity": "sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g==", + "requires": { + "commander": "2", + "iconv-lite": "0.4", + "rw": "1" + } + }, + "d3-ease": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.7.tgz", + "integrity": "sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ==" + }, + "d3-fetch": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.2.0.tgz", + "integrity": "sha512-yC78NBVcd2zFAyR/HnUiBS7Lf6inSCoWcSxFfw8FYL7ydiqe80SazNwoffcqOfs95XaLo7yebsmQqDKSsXUtvA==", + "requires": { + "d3-dsv": "1" + } + }, + "d3-force": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz", + "integrity": "sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==", + "requires": { + "d3-collection": "1", + "d3-dispatch": "1", + "d3-quadtree": "1", + "d3-timer": "1" + } + }, + "d3-format": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz", + "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==" + }, + "d3-geo": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.12.1.tgz", + "integrity": "sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg==", + "requires": { + "d3-array": "1" + } + }, + "d3-hierarchy": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz", + "integrity": "sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==" + }, + "d3-interpolate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz", + "integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==", + "requires": { + "d3-color": "1" + } + }, + "d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" + }, + "d3-polygon": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.6.tgz", + "integrity": "sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ==" + }, + "d3-quadtree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.7.tgz", + "integrity": "sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==" + }, + "d3-random": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.2.tgz", + "integrity": "sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ==" + }, + "d3-scale": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz", + "integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==", + "requires": { + "d3-array": "^1.2.0", + "d3-collection": "1", + "d3-format": "1", + "d3-interpolate": "1", + "d3-time": "1", + "d3-time-format": "2" + } + }, + "d3-scale-chromatic": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz", + "integrity": "sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg==", + "requires": { + "d3-color": "1", + "d3-interpolate": "1" + } + }, + "d3-selection": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", + "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" + }, + "d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "requires": { + "d3-path": "1" + } + }, + "d3-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz", + "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" + }, + "d3-time-format": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.3.0.tgz", + "integrity": "sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==", + "requires": { + "d3-time": "1" + } + }, + "d3-timer": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz", + "integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==" + }, + "d3-transition": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.3.2.tgz", + "integrity": "sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==", + "requires": { + "d3-color": "1", + "d3-dispatch": "1", + "d3-ease": "1", + "d3-interpolate": "1", + "d3-selection": "^1.1.0", + "d3-timer": "1" + } + }, + "d3-voronoi": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz", + "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==" + }, + "d3-zoom": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.8.3.tgz", + "integrity": "sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==", + "requires": { + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" + } + }, "date-fns": { "version": "1.30.1", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", @@ -6798,6 +7061,11 @@ "prop-types": "^15.6.2" } }, + "react-d3-graph": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-d3-graph/-/react-d3-graph-2.6.0.tgz", + "integrity": "sha512-U72didZuPuYEqAi1n2bJvnph+9MviIw2x9I0eoxb1IKk3cyEwsJV96n3RL72z/7HDsa1FOvDKuOJE7ujSNZB/Q==" + }, "react-dom": { "version": "16.14.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", diff --git a/package.json b/package.json index ee4dab2..c8218f9 100644 --- a/package.json +++ b/package.json @@ -38,11 +38,13 @@ "@turf/distance": "^6.0.1", "@turf/helpers": "^6.1.4", "beautiful-react-diagrams": "^0.5.1", + "d3": "^5.16.0", "jsonld-streaming-parser": "^2.0.2", "mapbox-gl": "^1.11.0", "n3": "^1.5.0", "rdf-string": "^1.4.2", "react": "^16.13.1", + "react-d3-graph": "^2.6.0", "react-dom": "^16.13.1", "react-mapbox-gl": "^4.8.6", "react-markdown": "^5.0.3", diff --git a/src/components/OPInternalView.js b/src/components/OPInternalView.js index 5860625..0aff240 100644 --- a/src/components/OPInternalView.js +++ b/src/components/OPInternalView.js @@ -1,16 +1,16 @@ import React, { useState, useEffect } from "react"; -import { Modal, Toggle } from "rsuite"; +import { Modal } from "rsuite"; import { RDFS, ERA } from "../utils/NameSpaces"; import { FACETED_BASE_URI } from "../config/config"; import Utils from "../utils/Utils"; -import Diagram, { useSchema } from 'beautiful-react-diagrams'; import { opBaseNodeDiagram, nodeLabelStyle, opNormalNodeDiagram, opRouteNodeDiagram } from '../styles/Styles'; -import 'beautiful-react-diagrams/styles.css'; + +import { Graph } from "react-d3-graph"; export const OPInternalView = ({ @@ -23,89 +23,30 @@ export const OPInternalView = ({ }) => { const [op, setOp] = useState(null); const [display, setDisplay] = useState(false); - const [canvasHeight, setCanvasHeight] = useState(0); - // create diagrams schema - const [schema, { onChange, addNode, removeNode }] = useSchema(); - - const opBaseNode = ({ data }) => { - return (
); - } - - const opNormalNode = ({ id, content, data }) => { - return ( -
-
- data.onChange(checked, id)} /> -
-
- {content} -
-
- ); - } - - const opRouteNode = ({ id, content, data }) => { - return ( -
-
- data.onChange(checked, id)} /> -
-
- {content} -
-
- ); - } - - const isRouteLink = inl => { - console.log(inl) - let from = null; - let to = null; - for (const n of schema.nodes) { - if (n.id === inl[ERA.startPort]) { - from = n; - if (to) break; - } - if (n.id === inl[ERA.endPort]) { - to = n; - if (from) break; - } - } - - console.log(from, to); - - - if (from.className === 'route-node' && to.className === 'route-node') { - return true; - } - - return false - } - - const onChangeNodePort = (checked, np) => { - if (checked) { - // Find and add all nodes starting from this Node Port - const inls = Utils.getAllInternalNodeLinksFromNodePort(np, graphStore); - for (const inl of inls) { - if (!isRouteLink(inl)) { - schema.links.push({ - input: np, - output: inl[ERA.endPort], - className: 'normal-link' - }); - } - } - } else { - schema.links = schema.links.filter(l => l.input !== np || l.className !== 'normal-link'); - } - - // Trigger change for re-render - onChange(schema); + const [schema, setSchema] = useState(null); + + // The graph global configuration + const schemaConfig = { + width: Utils.vw(70), + height: Utils.vh(70), + directed: true, + nodeHighlightBehavior: true, + staticGraph: true, + node: { + color: "lightgreen", + size: 500, + highlightStrokeColor: "blue", + labelProperty: "label" + }, + link: { + highlightColor: "#18ab10", + }, }; const buildSchema = (ivn, ivp) => { const inNPs = []; const outNPs = []; + const links = []; // Retrieve all node ports for this OP const nodePorts = Utils.getAllNodePorts(graphStore, ivn[ERA.hasAbstraction]); @@ -116,37 +57,34 @@ export const OPInternalView = ({ if (ivp.nodes.includes(np)) { if (firstRouteNP) { const inNode = { - render: opRouteNode, - data: { onChange: onChangeNodePort, switchAlign: 'right' }, - className: 'route-node' + color: pathColor } const outNode = { - render: opRouteNode, - data: { onChange: onChangeNodePort, switchAlign: 'left' }, - className: 'route-node' + color: pathColor } if (ivp.nodes.indexOf(np) > ivp.nodes.indexOf(firstRouteNP)) { // np is the outgoing NodePort outNode.id = np; - outNode.content = {np.substring(np.indexOf('#') + 1)}; + outNode.label = {np.substring(np.indexOf('#') + 1)}; inNode.id = firstRouteNP; - inNode.content = {firstRouteNP.substring(firstRouteNP.indexOf('#') + 1)}; + inNode.label = {firstRouteNP.substring(firstRouteNP.indexOf('#') + 1)}; } else { // np in the incoming NodePort inNode.id = np; - inNode.content = {np.substring(np.indexOf('#') + 1)}; + inNode.label = {np.substring(np.indexOf('#') + 1)}; outNode.id = firstRouteNP; - outNode.content = {firstRouteNP.substring(firstRouteNP.indexOf('#') + 1)}; + outNode.label = {firstRouteNP.substring(firstRouteNP.indexOf('#') + 1)}; } // Add route node ports to proper arrays inNPs.push(inNode); outNPs.push(outNode); // Create special route link - schema.links.push({ - input: inNode.id, - output: outNode.id, - className: 'route-link' + links.push({ + source: inNode.id, + target: outNode.id, + color: pathColor, + type: "CURVE_SMOOTH" }); } else { firstRouteNP = np; @@ -154,84 +92,83 @@ export const OPInternalView = ({ } else { const otherNode = { id: np, - content: {np.substring(np.indexOf('#') + 1)}, - render: opNormalNode, - data: { onChange: onChangeNodePort } + label: {np.substring(np.indexOf('#') + 1)}, + color: "#aaaaaa" } // Check if NodePort is incoming or outgoing if (Utils.isNodePortIncoming(np, graphStore)) { - otherNode.data.switchAlign = 'right'; inNPs.push(otherNode); } else { - otherNode.data.switchAlign = 'left'; outNPs.push(otherNode); } } } - // Build base node according to the amount of node ports - const baseNodeHeight = Math.max(inNPs.length, outNPs.length); - const baseNode = { - id: 'op', - disableDrag: true, - coordinates: [Utils.vw(18.75), 40], - data: { size: baseNodeHeight }, - render: opBaseNode, - className: 'base-node' - }; - // Add the base node to the diagram - addNode(baseNode); - - // Set canvas size - setCanvasHeight((baseNodeHeight * 10) + 10); - - // Assign coordinates to input Node Ports and render - const baseNodeHeightPx = Utils.vh(baseNodeHeight * 10); - let inY = 55; - + // Assign coordinates to Node Ports and create Internal Node Links + let Y = 100; inNPs.forEach(np => { - const label = np.id.substring(np.id.indexOf('#') + 1).length * 8.6; - const inX = Utils.vw(18.75) - (label > Utils.vw(20) ? Utils.vw(20) : label) + 80; - np.coordinates = [inX, inY]; - inY += baseNodeHeightPx / inNPs.length; - addNode(np); + np.labelPosition = "left"; + np.x = Utils.vw(25); + np.y = Y; + Y += 50; + + for (const inl of Utils.getAllInternalNodeLinksFromNodePort(np.id, graphStore)) { + links.push({ + source: np.id, + target: inl[ERA.endPort], + type: "CURVE_SMOOTH" + }); + } }); - // Assign coordinates to output Node Ports and render - inY = 55; + Y = 100; outNPs.forEach(np => { - const inX = Utils.vw(56.25) - 80; - np.coordinates = [inX, inY]; - inY += baseNodeHeightPx / outNPs.length; - addNode(np); + np.labelPosition = "right"; + np.x = Utils.vw(45); + np.y = Y; + Y += 50; }); + + setSchema({ nodes: [...inNPs, ...outNPs], links: [...links] }); } - const cleanDiagram = () => { + /*const cleanDiagram = () => { schema.nodes.forEach(n => removeNode(n)); schema.links = []; - } + }*/ + + const onClickNode = function (nodeId) { + window.alert(`Clicked node ${nodeId}`); + }; + + const onClickLink = function (source, target) { + window.alert(`Clicked link between ${source} and ${target}`); + }; useEffect(() => { setOp(internalViewNode); setDisplay(show); // New OP has been selected, proceed to build its diagram if (internalViewNode) { - cleanDiagram(); + //cleanDiagram(); buildSchema(internalViewNode, internalViewPath); } }, [internalViewNode, show]); - if (op) { + if (op && schema) { return
{ toggleInternalView(false) }}> Internal connectivity of {op[RDFS.label]} ({op[ERA.uopid]}) -
- -
+
From 8f8a83c6a9b050d572409883bb6f53609398cdb1 Mon Sep 17 00:00:00 2001 From: Julian Rojas Date: Mon, 15 Feb 2021 22:19:08 +0100 Subject: [PATCH 04/10] Visualize internal OP connectivity with react-d3-graph --- package-lock.json | 42 ------- package.json | 1 - src/components/MainLayout.js | 2 +- src/components/OPInternalView.js | 201 ++++++++++++++++++------------- src/index.css | 13 +- src/styles/Styles.js | 34 ------ src/utils/Utils.js | 43 +++++-- 7 files changed, 157 insertions(+), 179 deletions(-) diff --git a/package-lock.json b/package-lock.json index cab85bb..b3a22c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2031,28 +2031,6 @@ "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", "dev": true }, - "beautiful-react-diagrams": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/beautiful-react-diagrams/-/beautiful-react-diagrams-0.5.1.tgz", - "integrity": "sha512-B5sw+L27acwcOv0BUqCOPSf29sFDLXm8Fn2Vl0s4IFn9lrwZU6uqKN6oVl1R+M/M6mvEP9ZxvuSyVPNS8mAEIg==", - "requires": { - "beautiful-react-hooks": "^0.31.0", - "classnames": "^2.2.6", - "lodash.findindex": "^4.6.0", - "lodash.isequal": "^4.5.0", - "lodash.throttle": "^4.1.1", - "prop-types": "^15.7.2" - } - }, - "beautiful-react-hooks": { - "version": "0.31.0", - "resolved": "https://registry.npmjs.org/beautiful-react-hooks/-/beautiful-react-hooks-0.31.0.tgz", - "integrity": "sha512-LfMsMzF1rnaUlaEu7qKADUzi+FVDALN60/i0wuqBM4iSb9dYjcH9Scf8vDYZ690NuzfArIZO63IKpeZqODFl8A==", - "requires": { - "lodash.debounce": "^4.0.8", - "lodash.throttle": "^4.1.1" - } - }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -5695,26 +5673,6 @@ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" - }, - "lodash.findindex": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.findindex/-/lodash.findindex-4.6.0.tgz", - "integrity": "sha1-oyRd7mH7m24GJLU1ElYku2nBEQY=" - }, - "lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" - }, - "lodash.throttle": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" - }, "loglevel": { "version": "1.6.8", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.8.tgz", diff --git a/package.json b/package.json index c8218f9..13e37ad 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,6 @@ "@rdfjs/data-model": "^1.1.2", "@turf/distance": "^6.0.1", "@turf/helpers": "^6.1.4", - "beautiful-react-diagrams": "^0.5.1", "d3": "^5.16.0", "jsonld-streaming-parser": "^2.0.2", "mapbox-gl": "^1.11.0", diff --git a/src/components/MainLayout.js b/src/components/MainLayout.js index cd7927b..86eb640 100644 --- a/src/components/MainLayout.js +++ b/src/components/MainLayout.js @@ -619,7 +619,7 @@ class MainLayout extends Component { compatibilityVehicleType, compatibilityVehicle } = this.state; - + return (
diff --git a/src/components/OPInternalView.js b/src/components/OPInternalView.js index 0aff240..a720847 100644 --- a/src/components/OPInternalView.js +++ b/src/components/OPInternalView.js @@ -1,15 +1,8 @@ import React, { useState, useEffect } from "react"; -import { Modal } from "rsuite"; +import { Modal, Alert } from "rsuite"; import { RDFS, ERA } from "../utils/NameSpaces"; import { FACETED_BASE_URI } from "../config/config"; import Utils from "../utils/Utils"; -import { - opBaseNodeDiagram, - nodeLabelStyle, - opNormalNodeDiagram, - opRouteNodeDiagram -} from '../styles/Styles'; - import { Graph } from "react-d3-graph"; @@ -30,77 +23,82 @@ export const OPInternalView = ({ width: Utils.vw(70), height: Utils.vh(70), directed: true, - nodeHighlightBehavior: true, + nodeHighlightBehavior: false, staticGraph: true, node: { - color: "lightgreen", size: 500, - highlightStrokeColor: "blue", + color: "#aaaaaa", labelProperty: "label" }, link: { - highlightColor: "#18ab10", + renderLabel: true, + markerWidth: 4, + markerHeight: 4 }, }; + const isRouteLink = inl => { + if (inl[ERA.startPort]) { + if (internalViewPath.nodes.includes(inl[ERA.startPort]) && internalViewPath.nodes.includes(inl[ERA.endPort])) { + return true; + } else { + return false; + } + } else { + if (internalViewPath.nodes.includes(inl.source) && internalViewPath.nodes.includes(inl.target)) { + return true; + } else { + return false; + } + } + } + + const isNodeClicked = nodeId => { + for (const l of schema.links) { + if (l.source === nodeId && !isRouteLink(l)) return true; + } + + return false; + } + const buildSchema = (ivn, ivp) => { const inNPs = []; const outNPs = []; - const links = []; + const routeLinks = []; // Retrieve all node ports for this OP const nodePorts = Utils.getAllNodePorts(graphStore, ivn[ERA.hasAbstraction]); - let firstRouteNP = null; - for (const np of nodePorts) { + const node = { + id: np, + label: {np.substring(np.indexOf('#') + 1)} + }; + // Check if this is a NodePort of the route if (ivp.nodes.includes(np)) { - if (firstRouteNP) { - const inNode = { - color: pathColor - } - const outNode = { - color: pathColor - } - if (ivp.nodes.indexOf(np) > ivp.nodes.indexOf(firstRouteNP)) { - // np is the outgoing NodePort - outNode.id = np; - outNode.label = {np.substring(np.indexOf('#') + 1)}; - inNode.id = firstRouteNP; - inNode.label = {firstRouteNP.substring(firstRouteNP.indexOf('#') + 1)}; - } else { - // np in the incoming NodePort - inNode.id = np; - inNode.label = {np.substring(np.indexOf('#') + 1)}; - outNode.id = firstRouteNP; - outNode.label = {firstRouteNP.substring(firstRouteNP.indexOf('#') + 1)}; - } + node.color = pathColor; - // Add route node ports to proper arrays - inNPs.push(inNode); - outNPs.push(outNode); - // Create special route link - links.push({ - source: inNode.id, - target: outNode.id, + // Add route link to the diagram + const index = ivp.nodes.indexOf(np); + const inl = ivp.edges[index]; + + if (!Utils.isMicroLink(inl, graphStore)) { + routeLinks.push({ + source: ivp.nodes[index], + target: ivp.nodes[index + 1], + label: {inl.substring(inl.indexOf('#') + 1)}, color: pathColor, + strokeWidth: 2, type: "CURVE_SMOOTH" }); - } else { - firstRouteNP = np; } + } + + // Check if NodePort is incoming or outgoing + if (Utils.isNodePortIncoming(np, graphStore)) { + inNPs.push(node); } else { - const otherNode = { - id: np, - label: {np.substring(np.indexOf('#') + 1)}, - color: "#aaaaaa" - } - // Check if NodePort is incoming or outgoing - if (Utils.isNodePortIncoming(np, graphStore)) { - inNPs.push(otherNode); - } else { - outNPs.push(otherNode); - } + outNPs.push(node); } } @@ -110,15 +108,7 @@ export const OPInternalView = ({ np.labelPosition = "left"; np.x = Utils.vw(25); np.y = Y; - Y += 50; - - for (const inl of Utils.getAllInternalNodeLinksFromNodePort(np.id, graphStore)) { - links.push({ - source: np.id, - target: inl[ERA.endPort], - type: "CURVE_SMOOTH" - }); - } + Y += 70; }); Y = 100; @@ -126,31 +116,81 @@ export const OPInternalView = ({ np.labelPosition = "right"; np.x = Utils.vw(45); np.y = Y; - Y += 50; + Y += 70; }); - setSchema({ nodes: [...inNPs, ...outNPs], links: [...links] }); - } - - /*const cleanDiagram = () => { - schema.nodes.forEach(n => removeNode(n)); - schema.links = []; - }*/ + // Hack to fix the arrow head of the route link. + // We have to wrap the state setter in a promise to make sure the DOM is completely rendered + // and then proceed to directly manipulate it. + Promise.resolve() + .then(() => { setSchema({ nodes: [...inNPs, ...outNPs], links: [...routeLinks] }) }) + .then(() => { + // Add Operational Point frame + const svgns = "http://www.w3.org/2000/svg"; + const frame = document.createElementNS(svgns, "rect"); + frame.setAttribute("x", Utils.vw(25)); + frame.setAttribute("y", 70); + frame.setAttribute("height", Math.max(inNPs.length, outNPs.length) * 70); + frame.setAttribute("width", Utils.vw(45) - Utils.vw(25)); + frame.setAttribute("rx", 20); + frame.setAttribute("style", "stroke: #0c3d78; stroke-width: 4; fill: transparent;"); + const zoomable = document.getElementById("graph-id-graph-container-zoomable"); + zoomable.insertBefore(frame, zoomable.firstChild); + + // Fix arrow heads + routeLinks.forEach(routeLink => { + // Create a new with the same color of the route link + const marker = document.getElementById("marker-small").cloneNode(true); + marker.setAttribute("id", "marker-route"); + marker.setAttribute("fill", pathColor); + document.querySelector("#graph-id-graph-wrapper svg defs").appendChild(marker); + + // Change route link reference to the new arrow head () + const routePath = document.getElementById(`${routeLink.source},${routeLink.target}`); + routePath.setAttribute("marker-end", "url(#marker-route)"); + // Add CSS class for dashed animation + routePath.classList.add("route-link"); + }); + }); - const onClickNode = function (nodeId) { - window.alert(`Clicked node ${nodeId}`); - }; + } - const onClickLink = function (source, target) { - window.alert(`Clicked link between ${source} and ${target}`); - }; + const onClickNode = nodeId => { + if (isNodeClicked(nodeId)) { + // Remove all links starting from this node except for the route link + setSchema(prevSchema => { + return { nodes: prevSchema.nodes, links: prevSchema.links.filter(l => isRouteLink(l) || l.source !== nodeId) }; + }); + } else { + // Add all the routes starting from this node + const links = []; + const inls = Utils.getAllInternalNodeLinksFromNodePort(nodeId, graphStore); + + if (inls.length > 0) { + for (const inl of inls) { + if (!isRouteLink(inl)) { + links.push({ + source: nodeId, + target: inl[ERA.endPort], + label: {inl["@id"].substring(inl["@id"].indexOf('#') + 1)}, + type: "CURVE_SMOOTH" + }); + } + } + setSchema(prevSchema => { + return { nodes: prevSchema.nodes, links: [...prevSchema.links, ...links] }; + }); + } else { + Alert.warning(`There are no Internal Node Links starting from this Node Port`, 3000); + } + } + } useEffect(() => { setOp(internalViewNode); setDisplay(show); // New OP has been selected, proceed to build its diagram if (internalViewNode) { - //cleanDiagram(); buildSchema(internalViewNode, internalViewPath); } }, [internalViewNode, show]); @@ -163,11 +203,10 @@ export const OPInternalView = ({ diff --git a/src/index.css b/src/index.css index 96d38e3..d363fdd 100644 --- a/src/index.css +++ b/src/index.css @@ -125,17 +125,8 @@ code { width: 75vw; } -.normal-link path { - stroke-width: 0.15rem !important; - stroke: #aaaaaa !important; - animation: none !important; - marker-end: url(#triangle); -} - -.route-link path { - stroke-width: 0.3rem !important; - stroke: #0c3d78 !important; - stroke-dasharray: 10, 2; +.route-link { + stroke-dasharray: 10, 2 !important; animation: BiDashSegmentAnimation 1s linear infinite } diff --git a/src/styles/Styles.js b/src/styles/Styles.js index cc620c5..1736995 100644 --- a/src/styles/Styles.js +++ b/src/styles/Styles.js @@ -133,40 +133,6 @@ export const cellStyle = { textAlign: 'center' } -export const opBaseNodeDiagram = size => { - return { - width: '37.5vw', - height: `${size * 10}vh`, - background: 'rgb(87 137 181)', - opacity: 0.3, - border: '0.07rem solid #8795a1', - borderRadius: '2rem', - borderShadow: '0 0.07rem 0.2rem 0 rgba(0, 0, 0, 0.1), 0 0.07rem 0.125rem 0 rgba(0, 0, 0, 0.06)' - } -} - -export const opNormalNodeDiagram = { - background: '#dae1e7', - border: '0.07rem solid #8795a1', - borderRadius: '0.25rem', - borderShadow: '0 0.07rem 0.2rem 0 rgba(0, 0, 0, 0.1), 0 0.07rem 0.125rem 0 rgba(0, 0, 0, 0.06)' -} - -export const opRouteNodeDiagram = color => { - return { - background: '#dae1e7', - border: `0.25rem solid ${color}`, - borderRadius: '0.25rem', - borderShadow: '0 0.07rem 0.2rem 0 rgba(0, 0, 0, 0.1), 0 0.07rem 0.125rem 0 rgba(0, 0, 0, 0.06)' - } -} - -export const nodeLabelStyle = { - padding: '0.3rem', - maxWidth: '20vw', - wordBreak: 'break-word' -} - function getRandomColor() { var letters = '0123456789ABCDEF'; var color = '#'; diff --git a/src/utils/Utils.js b/src/utils/Utils.js index f39492d..f858533 100644 --- a/src/utils/Utils.js +++ b/src/utils/Utils.js @@ -234,13 +234,17 @@ function getAllInternalNodeLinksFromNodePort(np, store) { o: np, }); - for (const inl of Object.keys(links)) { - if (isInternalNodeLink(inl, store)) { - inls.push({ - '@id': inl, - ...queryGraphStore({ store, s: inl })[inl] - }); + if (links) { + for (const inl of Object.keys(links)) { + if (isInternalNodeLink(inl, store)) { + inls.push({ + '@id': inl, + ...queryGraphStore({ store, s: inl })[inl] + }); + } } + + } return inls; @@ -341,15 +345,35 @@ function isNodePortIncoming(np, store) { o: np }); - for (const l of Object.keys(inLinks)) { - if (isMicroLink(l, store)) { - return true; + if (inLinks) { + for (const l of Object.keys(inLinks)) { + if (isMicroLink(l, store)) { + return true; + } } } return false; } +function getMicroLinkFromNodePort(np, store) { + const links = queryGraphStore({ + store: store, + o: np + }); + + if (links) { + for (const l of Object.keys(links)) { + if (isMicroLink(l, store)) { + return queryGraphStore({ + store: store, + s: l + })[l] + } + } + } +} + function deepClone(obj) { let copy; @@ -610,6 +634,7 @@ export default { isInternalNodeLink, isNodePortIncoming, getTrackFromMicroLink, + getMicroLinkFromNodePort, checkCompatibility, deepClone }; \ No newline at end of file From 2746c51323e120a0c64ab2563ff27cb13d534f74 Mon Sep 17 00:00:00 2001 From: Julian Rojas Date: Mon, 15 Feb 2021 22:24:47 +0100 Subject: [PATCH 05/10] 1.4.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index b3a22c2..e31dafe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "era-compatibility-check", - "version": "1.3.1", + "version": "1.4.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 13e37ad..974b97e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "era-compatibility-check", - "version": "1.3.1", + "version": "1.4.0", "description": "ERA's route compatibility check application", "scripts": { "start": "webpack-dev-server --open --mode development", From 16485c5ff57ed5d410247b5c15f4ece1d1ad6639 Mon Sep 17 00:00:00 2001 From: Julian Rojas Date: Tue, 16 Feb 2021 17:21:35 +0100 Subject: [PATCH 06/10] Prevent disconnected alert during data fetching. Fixes #23 --- src/components/MainLayout.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/MainLayout.js b/src/components/MainLayout.js index 86eb640..3966269 100644 --- a/src/components/MainLayout.js +++ b/src/components/MainLayout.js @@ -412,6 +412,9 @@ class MainLayout extends Component { }; fromTo = async feature => { + // Data is already being fetched so do nothing in the meantime + if(this.state.loading) return false; + // Start building network graph await this.fetchAbstractionTile(feature.lngLat); if (!this.from.ports && this.state.to !== feature[RDFS.label]) { From 8d9a2df952bfa85201987e8932cd2b13c3511d5a Mon Sep 17 00:00:00 2001 From: Julian Rojas Date: Tue, 16 Feb 2021 19:17:56 +0100 Subject: [PATCH 07/10] Fix link visualization issue due to URI encoding --- src/components/OPInternalView.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/components/OPInternalView.js b/src/components/OPInternalView.js index a720847..1ae0a7b 100644 --- a/src/components/OPInternalView.js +++ b/src/components/OPInternalView.js @@ -45,7 +45,8 @@ export const OPInternalView = ({ return false; } } else { - if (internalViewPath.nodes.includes(inl.source) && internalViewPath.nodes.includes(inl.target)) { + if (internalViewPath.nodes.includes(new URL(inl.source).toString()) + && internalViewPath.nodes.includes(new URL(inl.target).toString())) { return true; } else { return false; @@ -70,8 +71,8 @@ export const OPInternalView = ({ const nodePorts = Utils.getAllNodePorts(graphStore, ivn[ERA.hasAbstraction]); for (const np of nodePorts) { const node = { - id: np, - label: {np.substring(np.indexOf('#') + 1)} + id: decodeURIComponent(np), + label: {np.substring(np.indexOf('#') + 1)} }; // Check if this is a NodePort of the route @@ -84,9 +85,9 @@ export const OPInternalView = ({ if (!Utils.isMicroLink(inl, graphStore)) { routeLinks.push({ - source: ivp.nodes[index], - target: ivp.nodes[index + 1], - label: {inl.substring(inl.indexOf('#') + 1)}, + source: decodeURIComponent(ivp.nodes[index]), + target: decodeURIComponent(ivp.nodes[index + 1]), + label: {inl.substring(inl.indexOf('#') + 1)}, color: pathColor, strokeWidth: 2, type: "CURVE_SMOOTH" @@ -119,7 +120,7 @@ export const OPInternalView = ({ Y += 70; }); - // Hack to fix the arrow head of the route link. + // Hack to manipulate the DOM directly. // We have to wrap the state setter in a promise to make sure the DOM is completely rendered // and then proceed to directly manipulate it. Promise.resolve() @@ -164,15 +165,15 @@ export const OPInternalView = ({ } else { // Add all the routes starting from this node const links = []; - const inls = Utils.getAllInternalNodeLinksFromNodePort(nodeId, graphStore); + const inls = Utils.getAllInternalNodeLinksFromNodePort(new URL(nodeId).toString(), graphStore); if (inls.length > 0) { for (const inl of inls) { if (!isRouteLink(inl)) { links.push({ - source: nodeId, - target: inl[ERA.endPort], - label: {inl["@id"].substring(inl["@id"].indexOf('#') + 1)}, + source: decodeURIComponent(nodeId), + target: decodeURIComponent(inl[ERA.endPort]), + label: {inl["@id"].substring(inl["@id"].indexOf('#') + 1)}, type: "CURVE_SMOOTH" }); } From 490f90165c76edb5d7779863426a23db42e2724b Mon Sep 17 00:00:00 2001 From: Julian Rojas Date: Wed, 17 Feb 2021 18:07:13 +0100 Subject: [PATCH 08/10] Parameters as object for abstraction tiles fetcher function --- src/algorithm/PathFinder.js | 2 +- src/components/MainLayout.js | 16 ++++++++-------- src/components/RoutesInfo.js | 10 +++++++--- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/algorithm/PathFinder.js b/src/algorithm/PathFinder.js index d8d1cd4..866bcad 100644 --- a/src/algorithm/PathFinder.js +++ b/src/algorithm/PathFinder.js @@ -242,7 +242,7 @@ export class PathFinder extends EventEmitter { if (npt) { const lat = parseFloat(npt[np][WGS84.latitude]); const long = parseFloat(npt[np][WGS84.longitude]); - await this.props.fetchAbstractionTile([long, lat], false, force); + await this.props.fetchAbstractionTile({ coords: [long, lat], force: force }); } } diff --git a/src/components/MainLayout.js b/src/components/MainLayout.js index 3966269..b6c34f8 100644 --- a/src/components/MainLayout.js +++ b/src/components/MainLayout.js @@ -235,9 +235,9 @@ class MainLayout extends Component { }); } - fetchAbstractionTile = (coords, asXY, force) => { + fetchAbstractionTile = ({ coords, asXY, zoom, force }) => { return new Promise(resolve => { - const tileFetcher = this.tileFetcherPool.runTask(ABSTRACTION_TILES, ABSTRACTION_ZOOM, coords, asXY, force); + const tileFetcher = this.tileFetcherPool.runTask(ABSTRACTION_TILES, zoom || ABSTRACTION_ZOOM, coords, asXY, force); // Draw tile frame on the map this.drawTileFrame(coords, ABSTRACTION_ZOOM, asXY); if (tileFetcher) { @@ -413,10 +413,10 @@ class MainLayout extends Component { fromTo = async feature => { // Data is already being fetched so do nothing in the meantime - if(this.state.loading) return false; - + if (this.state.loading) return false; + // Start building network graph - await this.fetchAbstractionTile(feature.lngLat); + await this.fetchAbstractionTile({ coords: feature.lngLat }); if (!this.from.ports && this.state.to !== feature[RDFS.label]) { // Get the NodePorts of this MicroNode const nodePorts = Utils.getMicroNodePorts(this.graphStore, feature['@id']); @@ -462,7 +462,7 @@ class MainLayout extends Component { const intersectedTiles = findIntersectedTiles(this.from.lngLat, this.to.lngLat); await Promise.all(intersectedTiles.map(async tile => { - return this.fetchAbstractionTile(tile, true); + return this.fetchAbstractionTile({ coords: tile, asXY: true }); })); this.pathFinder = new PathFinder({ @@ -572,7 +572,7 @@ class MainLayout extends Component { this.setState({ internalView: show, internalViewNode: mn, - internalViewPath: route? route.path : null, + internalViewPath: route ? route.path : null, pathColor: route ? route.style['line-color'] : null }); }; @@ -622,7 +622,7 @@ class MainLayout extends Component { compatibilityVehicleType, compatibilityVehicle } = this.state; - + return (
diff --git a/src/components/RoutesInfo.js b/src/components/RoutesInfo.js index d1e4fb6..465ace0 100644 --- a/src/components/RoutesInfo.js +++ b/src/components/RoutesInfo.js @@ -181,9 +181,13 @@ export class RoutesInfo extends Component { parseFloat(npDetails[WGS84.longitude]), parseFloat(npDetails[WGS84.latitude]) ], true), - this.props.fetchAbstractionTile([ - parseFloat(npDetails[WGS84.longitude]), - parseFloat(npDetails[WGS84.latitude])], false, true) + this.props.fetchAbstractionTile({ + coords: [ + parseFloat(npDetails[WGS84.longitude]), + parseFloat(npDetails[WGS84.latitude]) + ], + force: true + }) ]); op = Utils.getOperationalPointFromMicroNode(mn, this.props.graphStore); } From 25db1715c93c327502b8297198bef8cc31ed6b66 Mon Sep 17 00:00:00 2001 From: Julian Rojas Date: Mon, 1 Mar 2021 21:02:03 +0100 Subject: [PATCH 09/10] Remove unnecessary next tick --- src/components/MainLayout.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/components/MainLayout.js b/src/components/MainLayout.js index b6c34f8..b031e84 100644 --- a/src/components/MainLayout.js +++ b/src/components/MainLayout.js @@ -295,11 +295,9 @@ class MainLayout extends Component { this.toggleLoading(false); } - process.nextTick(() => { - tileFetcher.removeEventListener('data', ondata); - tileFetcher.removeEventListener('done', ondone); - resolve(); - }); + tileFetcher.removeEventListener('data', ondata); + tileFetcher.removeEventListener('done', ondone); + resolve(); } tileFetcher.addEventListener('data', ondata); From f6350d1101982f903428fa418f0638443ab8e5d4 Mon Sep 17 00:00:00 2001 From: Julian Rojas Date: Mon, 1 Mar 2021 21:02:38 +0100 Subject: [PATCH 10/10] Deal with n-triples instead of n-quads --- src/workers/RDFetch.worker.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/workers/RDFetch.worker.js b/src/workers/RDFetch.worker.js index f8972bd..7a03115 100644 --- a/src/workers/RDFetch.worker.js +++ b/src/workers/RDFetch.worker.js @@ -19,8 +19,8 @@ self.addEventListener('message', async e => { parser = new JsonLdParser(); // Hack to read HTTP response in a streaming way data = JSON.stringify(await res.json(), null, 3).split('\n'); - } else if (contentType.includes('application/n-quads')) { - parser = new StreamParser({ format: 'N-Quads' }); + } else if (contentType.includes('application/n-triples')) { + parser = new StreamParser({ format: 'N-Triples' }); data = await res.text(); } else if (contentType.includes('text/turtle')) { parser = new StreamParser({ format: 'Turtle' });