Merge pull request #14 from bitnine-oss/@feature/edge-thickness
@feature/edge thickness
diff --git a/frontend/src/components/contents/presentations/Editor.jsx b/frontend/src/components/contents/presentations/Editor.jsx
index 4ed1912..e86eb79 100644
--- a/frontend/src/components/contents/presentations/Editor.jsx
+++ b/frontend/src/components/contents/presentations/Editor.jsx
@@ -1,200 +1,200 @@
-/*
- * Copyright 2020 Bitnine Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import React, { useEffect, useState } from 'react';
-import { useDispatch } from 'react-redux';
-import uuid from 'react-uuid';
-import PropTypes from 'prop-types';
-import AlertContainers from '../../alert/containers/AlertContainers';
-import CodeMirror from '../../editor/containers/CodeMirrorWapperContainer';
-import SideBarToggle from '../../editor/containers/SideBarMenuToggleContainer';
-
-const Editor = ({
- setCommand,
- command,
- addFrame,
- trimFrame,
- addAlert,
- alertList,
- isActive,
- database,
- executeCypherQuery,
- addCommandHistory,
- toggleMenu,
- // addCommandFavorites,
-}) => {
- const dispatch = useDispatch();
- const [alerts, setAlerts] = useState([]);
-
- // const favoritesCommand = () => {
- // dispatch(() => addCommandFavorites(command));
- // };
-
- const clearCommand = () => {
- setCommand('');
- };
-
- const onClick = () => {
- const refKey = uuid();
- if (command.toUpperCase().startsWith(':PLAY')) {
- dispatch(() => addFrame(command, 'Contents', refKey));
- } else if (command.toUpperCase() === ':SERVER STATUS') {
- dispatch(() => trimFrame('ServerStatus'));
- dispatch(() => addFrame(command, 'ServerStatus', refKey));
- } else if (database.status === 'disconnected' && command.toUpperCase() === ':SERVER DISCONNECT') {
- dispatch(() => trimFrame('ServerDisconnect'));
- dispatch(() => trimFrame('ServerConnect'));
- dispatch(() => addAlert('ErrorNoDatabaseConnected'));
- dispatch(() => addFrame(command, 'ServerDisconnect', refKey));
- } else if (database.status === 'disconnected' && command.toUpperCase() === ':SERVER CONNECT') {
- dispatch(() => trimFrame('ServerConnect'));
- dispatch(() => addFrame(':server connect', 'ServerConnect'));
- } else if (database.status === 'disconnected' && command.toUpperCase().match('(MATCH|CREATE).*')) {
- dispatch(() => trimFrame('ServerConnect'));
- dispatch(() => addAlert('ErrorNoDatabaseConnected'));
- dispatch(() => addFrame(command, 'ServerConnect', refKey));
- } else if (database.status === 'connected' && command.toUpperCase() === ':SERVER DISCONNECT') {
- dispatch(() => trimFrame('ServerDisconnect'));
- dispatch(() => addAlert('NoticeServerDisconnected'));
- dispatch(() => addFrame(command, 'ServerDisconnect', refKey));
- } else if (database.status === 'connected' && command.toUpperCase() === ':SERVER CONNECT') {
- dispatch(() => trimFrame('ServerStatus'));
- dispatch(() => addAlert('NoticeAlreadyConnected'));
- dispatch(() => addFrame(command, 'ServerStatus', refKey));
- } else if (database.status === 'connected') {
- const reqStringValue = command;
- dispatch(() => executeCypherQuery([refKey, reqStringValue]).then((response) => {
- if (response.type === 'cypher/executeCypherQuery/fulfilled') {
- addFrame(reqStringValue, 'CypherResultFrame', refKey);
- } else if (response.type === 'cypher/executeCypherQuery/rejected') {
- addFrame(reqStringValue, 'CypherResultFrame', refKey);
- dispatch(() => addAlert('ErrorCypherQuery'));
- }
- }));
- }
- dispatch(() => addCommandHistory(command));
- clearCommand();
- };
-
- useEffect(() => {
- setAlerts(
- alertList.map((alert) => (
- <AlertContainers
- key={alert.alertProps.key}
- alertKey={alert.alertProps.key}
- alertName={alert.alertName}
- errorMessage={alert.alertProps.errorMessage}
- />
- )),
- );
- }, [alertList]);
-
- return (
- <div className="container-fluid">
- <div className="editor">
- <div className="container-fluid editor-area card-header">
- <div className="input-group input-style">
- <div style={{
- height: '60px',
- width: '60px',
- color: '#ffffff',
- textAlign: 'left',
- lineHeight: '30px',
- }}
- >
- <spna>
- Query
- <br />
- Editor
- </spna>
- </div>
- <div className="form-control col-11 editor-code-wrapper">
- <CodeMirror
- onClick={onClick}
- value={command}
- onChange={setCommand}
- />
- </div>
- <div className="input-group-append ml-auto editor-button-wrapper" id="editor-buttons">
- {/* <button className="frame-head-button btn btn-link"
- type="button" onClick={() => favoritesCommand()}>
- <FontAwesomeIcon
- icon={faStar}
- size="lg"
- />
- </button> */}
- <button className={command ? 'btn show-eraser' : 'btn hide-eraser'} type="button" id="eraser" onClick={() => clearCommand()}>
- <i className="icon-eraser" />
- </button>
- <button
- className="frame-head-button btn btn-link"
- type="button"
- onClick={() => onClick()}
- title="Run Query"
- >
- <i className="icon-play" />
- </button>
- <button
- className="frame-head-button btn btn-link"
- type="button"
- onClick={() => {
- toggleMenu('home');
- if (!isActive) {
- document.getElementById('wrapper').classList.remove('wrapper');
- document.getElementById('wrapper').classList.add('wrapper-extension-padding');
- } else {
- document.getElementById('wrapper').classList.remove('wrapper-extension-padding');
- document.getElementById('wrapper').classList.add('wrapper');
- }
- }}
- title={(isActive) ? 'Hide' : 'Show'}
- >
- <SideBarToggle isActive={isActive} />
- </button>
- </div>
- </div>
- </div>
- </div>
- {alerts}
- </div>
- );
-};
-
-Editor.propTypes = {
- setCommand: PropTypes.func.isRequired,
- command: PropTypes.string.isRequired,
- addFrame: PropTypes.func.isRequired,
- trimFrame: PropTypes.func.isRequired,
- addAlert: PropTypes.func.isRequired,
- alertList: PropTypes.arrayOf(PropTypes.shape({
- alertName: PropTypes.string.isRequired,
- alertProps: PropTypes.shape({
- key: PropTypes.string.isRequired,
- alertType: PropTypes.string.isRequired,
- errorMessage: PropTypes.string.isRequired,
- }),
- })).isRequired,
- isActive: PropTypes.bool.isRequired,
- database: PropTypes.shape({
- status: PropTypes.string.isRequired,
- }).isRequired,
- executeCypherQuery: PropTypes.func.isRequired,
- addCommandHistory: PropTypes.func.isRequired,
- toggleMenu: PropTypes.func.isRequired,
- // addCommandFavorites: PropTypes.func.isRequired,
-};
-
-export default Editor;
+/*
+ * Copyright 2020 Bitnine Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useEffect, useState } from 'react';
+import { useDispatch } from 'react-redux';
+import uuid from 'react-uuid';
+import PropTypes from 'prop-types';
+import AlertContainers from '../../alert/containers/AlertContainers';
+import CodeMirror from '../../editor/containers/CodeMirrorWapperContainer';
+import SideBarToggle from '../../editor/containers/SideBarMenuToggleContainer';
+
+const Editor = ({
+ setCommand,
+ command,
+ addFrame,
+ trimFrame,
+ addAlert,
+ alertList,
+ isActive,
+ database,
+ executeCypherQuery,
+ addCommandHistory,
+ toggleMenu,
+ // addCommandFavorites,
+}) => {
+ const dispatch = useDispatch();
+ const [alerts, setAlerts] = useState([]);
+
+ // const favoritesCommand = () => {
+ // dispatch(() => addCommandFavorites(command));
+ // };
+
+ const clearCommand = () => {
+ setCommand('');
+ };
+
+ const onClick = () => {
+ const refKey = uuid();
+ if (command.toUpperCase().startsWith(':PLAY')) {
+ dispatch(() => addFrame(command, 'Contents', refKey));
+ } else if (command.toUpperCase() === ':SERVER STATUS') {
+ dispatch(() => trimFrame('ServerStatus'));
+ dispatch(() => addFrame(command, 'ServerStatus', refKey));
+ } else if (database.status === 'disconnected' && command.toUpperCase() === ':SERVER DISCONNECT') {
+ dispatch(() => trimFrame('ServerDisconnect'));
+ dispatch(() => trimFrame('ServerConnect'));
+ dispatch(() => addAlert('ErrorNoDatabaseConnected'));
+ dispatch(() => addFrame(command, 'ServerDisconnect', refKey));
+ } else if (database.status === 'disconnected' && command.toUpperCase() === ':SERVER CONNECT') {
+ dispatch(() => trimFrame('ServerConnect'));
+ dispatch(() => addFrame(':server connect', 'ServerConnect'));
+ } else if (database.status === 'disconnected' && command.toUpperCase().match('(MATCH|CREATE).*')) {
+ dispatch(() => trimFrame('ServerConnect'));
+ dispatch(() => addAlert('ErrorNoDatabaseConnected'));
+ dispatch(() => addFrame(command, 'ServerConnect', refKey));
+ } else if (database.status === 'connected' && command.toUpperCase() === ':SERVER DISCONNECT') {
+ dispatch(() => trimFrame('ServerDisconnect'));
+ dispatch(() => addAlert('NoticeServerDisconnected'));
+ dispatch(() => addFrame(command, 'ServerDisconnect', refKey));
+ } else if (database.status === 'connected' && command.toUpperCase() === ':SERVER CONNECT') {
+ dispatch(() => trimFrame('ServerStatus'));
+ dispatch(() => addAlert('NoticeAlreadyConnected'));
+ dispatch(() => addFrame(command, 'ServerStatus', refKey));
+ } else if (database.status === 'connected') {
+ const reqStringValue = command;
+ dispatch(() => executeCypherQuery([refKey, reqStringValue]).then((response) => {
+ if (response.type === 'cypher/executeCypherQuery/fulfilled') {
+ addFrame(reqStringValue, 'CypherResultFrame', refKey);
+ } else if (response.type === 'cypher/executeCypherQuery/rejected') {
+ addFrame(reqStringValue, 'CypherResultFrame', refKey);
+ dispatch(() => addAlert('ErrorCypherQuery'));
+ }
+ }));
+ }
+ dispatch(() => addCommandHistory(command));
+ clearCommand();
+ };
+
+ useEffect(() => {
+ setAlerts(
+ alertList.map((alert) => (
+ <AlertContainers
+ key={alert.alertProps.key}
+ alertKey={alert.alertProps.key}
+ alertName={alert.alertName}
+ errorMessage={alert.alertProps.errorMessage}
+ />
+ )),
+ );
+ }, [alertList]);
+
+ return (
+ <div className="container-fluid">
+ <div className="editor">
+ <div className="container-fluid editor-area card-header">
+ <div className="input-group input-style">
+ <div style={{
+ height: '60px',
+ width: '60px',
+ color: '#ffffff',
+ textAlign: 'left',
+ lineHeight: '30px',
+ }}
+ >
+ <span>
+ Query
+ <br />
+ Editor
+ </span>
+ </div>
+ <div className="form-control col-11 editor-code-wrapper">
+ <CodeMirror
+ onClick={onClick}
+ value={command}
+ onChange={setCommand}
+ />
+ </div>
+ <div className="input-group-append ml-auto editor-button-wrapper" id="editor-buttons">
+ {/* <button className="frame-head-button btn btn-link"
+ type="button" onClick={() => favoritesCommand()}>
+ <FontAwesomeIcon
+ icon={faStar}
+ size="lg"
+ />
+ </button> */}
+ <button className={command ? 'btn show-eraser' : 'btn hide-eraser'} type="button" id="eraser" onClick={() => clearCommand()}>
+ <i className="icon-eraser" />
+ </button>
+ <button
+ className="frame-head-button btn btn-link"
+ type="button"
+ onClick={() => onClick()}
+ title="Run Query"
+ >
+ <i className="icon-play" />
+ </button>
+ <button
+ className="frame-head-button btn btn-link"
+ type="button"
+ onClick={() => {
+ toggleMenu('home');
+ if (!isActive) {
+ document.getElementById('wrapper').classList.remove('wrapper');
+ document.getElementById('wrapper').classList.add('wrapper-extension-padding');
+ } else {
+ document.getElementById('wrapper').classList.remove('wrapper-extension-padding');
+ document.getElementById('wrapper').classList.add('wrapper');
+ }
+ }}
+ title={(isActive) ? 'Hide' : 'Show'}
+ >
+ <SideBarToggle isActive={isActive} />
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+ {alerts}
+ </div>
+ );
+};
+
+Editor.propTypes = {
+ setCommand: PropTypes.func.isRequired,
+ command: PropTypes.string.isRequired,
+ addFrame: PropTypes.func.isRequired,
+ trimFrame: PropTypes.func.isRequired,
+ addAlert: PropTypes.func.isRequired,
+ alertList: PropTypes.arrayOf(PropTypes.shape({
+ alertName: PropTypes.string.isRequired,
+ alertProps: PropTypes.shape({
+ key: PropTypes.string.isRequired,
+ alertType: PropTypes.string.isRequired,
+ errorMessage: PropTypes.string.isRequired,
+ }),
+ })).isRequired,
+ isActive: PropTypes.bool.isRequired,
+ database: PropTypes.shape({
+ status: PropTypes.string.isRequired,
+ }).isRequired,
+ executeCypherQuery: PropTypes.func.isRequired,
+ addCommandHistory: PropTypes.func.isRequired,
+ toggleMenu: PropTypes.func.isRequired,
+ // addCommandFavorites: PropTypes.func.isRequired,
+};
+
+export default Editor;
diff --git a/frontend/src/components/cypherresult/components/EdgeThicknessMenu.jsx b/frontend/src/components/cypherresult/components/EdgeThicknessMenu.jsx
new file mode 100644
index 0000000..40839ba
--- /dev/null
+++ b/frontend/src/components/cypherresult/components/EdgeThicknessMenu.jsx
@@ -0,0 +1,115 @@
+/* eslint-disable react/prop-types */
+/* eslint-disable no-unused-vars */
+import React, { useState, useEffect } from 'react';
+import {
+ Modal, Select, Input, Button,
+} from 'antd';
+import style from './popover.module.scss';
+
+const EdgeThicknessSettingModal = ({
+ onSubmit,
+ properties,
+}) => {
+ const [standardEdge, setStdEdge] = useState('');
+ const [standardProperty, setStdProperty] = useState('');
+ const [MinValue, setMinValue] = useState('');
+ const [MaxValue, setMaxValue] = useState('');
+
+ useEffect(() => {
+ if (standardEdge === '') setStdProperty('');
+ }, [standardEdge]);
+
+ const selectionEdge = () => {
+ const edgeList = new Set(properties.map((p) => p.edge));
+ return Array.from(edgeList).map((edge) => (
+ <>
+ <option className={style.option} value={edge}>
+ {edge}
+ </option>
+ </>
+ ));
+ };
+
+ const selectionPropertie = () => {
+ const propertyList = new Set(
+ properties.map((p) => (p.edge === standardEdge ? p.property : undefined)),
+ );
+ return Array.from(propertyList).map((property) => (
+ property
+ ? (
+ <>
+ <option className={style.option} value={property}>
+ {property}
+ </option>
+ </>
+ )
+ : <></>
+ ));
+ };
+
+ const apply = () => {
+ const thickness = {
+ edge: standardEdge,
+ property: standardProperty,
+ min: MinValue,
+ max: MaxValue,
+ };
+ onSubmit(thickness);
+ };
+
+ const reset = () => {
+ onSubmit(null);
+ setStdEdge('');
+ setStdProperty('');
+ setMinValue('');
+ setMaxValue('');
+ };
+
+ return (
+ <div style={{ width: '370px' }}>
+ <p className={style.title}>Apply Edge Weight</p>
+ <select
+ className={`${standardEdge === '' ? style.default : style.select}`}
+ defaultValue={null}
+ value={standardEdge}
+ onChange={(e) => setStdEdge(e.target.value)}
+ style={{ width: '95%' }}
+ >
+ <option className={`${style.option}`} value="">Select Edge</option>
+ {selectionEdge()}
+ </select>
+ <select
+ className={`${standardProperty === '' ? style.default : style.select}`}
+ defaultValue={null}
+ value={standardProperty}
+ onChange={(e) => setStdProperty(e.target.value)}
+ style={{ width: '95%' }}
+ >
+ <option className={`${style.option}`} value="">Select Node</option>
+ {selectionPropertie()}
+ </select>
+ <input
+ className={style.input}
+ value={MinValue}
+ onChange={(e) => {
+ if (Number(e.target.value) || e.target.value === '' || e.target.value === '0') setMinValue(Number(e.target.value));
+ }}
+ placeholder="Min Value"
+ />
+ <input
+ className={style.input}
+ value={MaxValue}
+ onChange={(e) => {
+ if (Number(e.target.value) || e.target.value === '' || e.target.value === '0') setMaxValue(Number(e.target.value));
+ }}
+ placeholder="Max Value"
+ />
+ <div className={style.buttons}>
+ <button className={style.btn} type="button" onClick={() => reset()}>Reset</button>
+ <button className={style.btn} type="button" onClick={() => apply()}>Apply</button>
+ </div>
+ </div>
+ );
+};
+
+export default EdgeThicknessSettingModal;
diff --git a/frontend/src/components/cypherresult/components/GraphFilterModal.jsx b/frontend/src/components/cypherresult/components/GraphFilterModal.jsx
index e6615f8..5e0e834 100644
--- a/frontend/src/components/cypherresult/components/GraphFilterModal.jsx
+++ b/frontend/src/components/cypherresult/components/GraphFilterModal.jsx
@@ -107,7 +107,7 @@
onChange={(value) => {
filterList[index].property = value;
}}
- style={{ minWidth: 120 }}
+ style={{ minWidth: 300 }}
>
<Select.Option value={null} disabled>Select</Select.Option>
{propertyElements}
@@ -135,7 +135,7 @@
);
}, [propertyElements, filterList]);
return (
- <Modal title="Filter on Graph" visible={visible} onOk={onOk} onCancel={() => setVisible(false)}>
+ <Modal title="Filter on Graph" visible={visible} onOk={onOk} onCancel={() => setVisible(false)} width={800}>
{
filterElements
}
diff --git a/frontend/src/components/cypherresult/components/popover.module.scss b/frontend/src/components/cypherresult/components/popover.module.scss
new file mode 100644
index 0000000..458d44d
--- /dev/null
+++ b/frontend/src/components/cypherresult/components/popover.module.scss
@@ -0,0 +1,72 @@
+.title {
+ font-size: 18px;
+ font-weight: bold;
+}
+.input {
+ width: 100%;
+ height: 48px;
+ background: #F8F9FA 0% 0% no-repeat padding-box !important;
+ border-radius: 5px !important;
+ opacity: 1 !important;
+ border: none;
+ margin: 5px 0px 5px 0px;
+ color: #2756FF;
+}
+.input:hover {
+ border: 1px solid #2756FF;
+}
+.input:focus {
+ border: 1px solid #2756FF;
+}
+.select {
+ width: 100%;
+ height: 48px;
+ background: #F8F9FA 0% 0% no-repeat padding-box !important;
+ border-radius: 5px !important;
+ opacity: 1 !important;
+ border: none;
+ margin: 5px 0px 5px 0px;
+ color: #2756FF;
+}
+.default{
+ width: 100%;
+ height: 48px;
+ background: #F8F9FA 0% 0% no-repeat padding-box !important;
+ border-radius: 5px !important;
+ opacity: 1 !important;
+ border: none;
+ margin: 5px 0px 5px 0px;
+ color:#808080;
+}
+.select select-item {
+ width: 100%;
+ height: 48px;
+ background: #F8F9FA 0% 0% no-repeat padding-box !important;
+ border-radius: 5px !important;
+ opacity: 1 !important;
+ border: none;
+ color:#808080;
+}
+.buttons {
+ margin: 32px 0 32px 0;
+ text-align: right;
+}
+
+.btn {
+ width: 100px;
+ height: 45px;
+ font-size: 14px;
+ font-weight: bold;
+ border: 1px solid #2756FF;
+ border-radius: 10px;
+ opacity: 1;
+ margin-left: 10px;
+ margin-right: 10px;
+ color: #2756FF;
+ background-color: #F8F9FA;
+}
+
+.btn:hover {
+ color: #F8F9FA;
+ background-color: #2756FF;
+}
\ No newline at end of file
diff --git a/frontend/src/components/cypherresult/presentations/CypherResultCytoscape.jsx b/frontend/src/components/cypherresult/presentations/CypherResultCytoscape.jsx
index a934ab6..b4e5edd 100644
--- a/frontend/src/components/cypherresult/presentations/CypherResultCytoscape.jsx
+++ b/frontend/src/components/cypherresult/presentations/CypherResultCytoscape.jsx
@@ -173,26 +173,66 @@
};
const changeCaptionOnCytoscapeElements = (elementType, label, caption) => {
- cytoscapeObject.elements(`${elementType}[label = "${label}"]`).style('label', (ele) => {
- let displayValue = '< NULL >';
- if (caption === 'gid') {
- const idValue = ele.data('id');
- if (idValue !== null && idValue !== undefined) {
- displayValue = `[ ${idValue} ]`;
+ if (caption === null) {
+ cytoscapeObject.elements(`${elementType}[label = "${label}"]`).style('label', '');
+ } else {
+ cytoscapeObject.elements(`${elementType}[label = "${label}"]`).style('label', (ele) => {
+ let displayValue = '< NULL >';
+ if (caption === 'gid') {
+ const idValue = ele.data('id');
+ if (idValue !== null && idValue !== undefined) {
+ displayValue = `[ ${idValue} ]`;
+ }
+ } else if (caption === 'label') {
+ const labelValue = ele.data('label');
+ if (labelValue !== null && labelValue !== undefined) {
+ displayValue = `[ :${labelValue} ]`;
+ }
+ } else if (ele !== null && ele !== undefined) {
+ const anonValue = ele.data('properties')[caption];
+ if (anonValue !== null && anonValue !== undefined) {
+ displayValue = anonValue;
+ }
}
- } else if (caption === 'label') {
- const labelValue = ele.data('label');
- if (labelValue !== null && labelValue !== undefined) {
- displayValue = `[ :${labelValue} ]`;
- }
- } else if (ele !== null && ele !== undefined) {
- const anonValue = ele.data('properties')[caption];
- if (anonValue !== null && anonValue !== undefined) {
- displayValue = anonValue;
- }
+ return displayValue;
+ });
+ }
+ };
+
+ const applyEdgeThicknessCytoscapeElements = (thickness) => {
+ const edgeSizes = [1, 6, 11, 16, 21];
+ if (thickness !== null) {
+ const range = thickness.max - thickness.min;
+ const edgeSizeByRate = (rate) => {
+ let size = edgeSizes[0];
+ size = (rate >= 0) ? edgeSizes[0] : size;
+ size = (rate >= 20) ? edgeSizes[1] : size;
+ size = (rate >= 40) ? edgeSizes[2] : size;
+ size = (rate >= 60) ? edgeSizes[3] : size;
+ size = (rate >= 80) ? edgeSizes[4] : size;
+ size = (rate >= 100) ? edgeSizes[4] : size;
+ return size;
+ };
+ if (cytoscapeObject) {
+ cytoscapeObject.elements().forEach((e) => {
+ const ele = e;
+ if (ele.group() === 'edges') {
+ if (ele.data().label === thickness.edge && ele.data().properties[thickness.property]) {
+ const propertyValue = ele.data().properties[thickness.property];
+ const propertyRate = (propertyValue / range) * 100;
+ ele.style('width', edgeSizeByRate(propertyRate).toString());
+ }
+ }
+ });
}
- return displayValue;
- });
+ } else if (cytoscapeObject) {
+ cytoscapeObject.elements().forEach((e) => {
+ const ele = e;
+ if (ele.group() === 'edges') {
+ ele.style('width', '');
+ }
+ });
+ }
};
const applyFilterOnCytoscapeElements = (filters) => {
@@ -242,16 +282,26 @@
}).addClass(gFilteredClassName);
// Step2. Edge Highlight from not filtered nodes.
+ const targetAndSourceNodeList = [];
for (let nodeIndex = 0; nodeIndex < notFilteredNodeLength; nodeIndex += 1) {
const currentNode = notFilteredNodes[nodeIndex];
const edges = currentNode.connectedEdges();
const edgesSize = edges.length;
for (let edgeIndex = 0; edgeIndex < edgesSize; edgeIndex += 1) {
const currentEdge = edges[edgeIndex];
+ const edgeTargetNode = currentEdge.target();
+ const edgeSourceNode = currentEdge.source();
const connectedWithHighlightNode = currentEdge.connectedNodes().not(`.${gFilteredClassName}`).filter((ele) => ele !== currentNode);
- if (connectedWithHighlightNode.length === 0) currentEdge.addClass(gFilteredClassName);
+ if (connectedWithHighlightNode.length === 0) {
+ currentEdge.addClass(gFilteredClassName);
+ } else {
+ targetAndSourceNodeList.push(edgeTargetNode);
+ targetAndSourceNodeList.push(edgeSourceNode);
+ }
}
}
+ // Step3 . Edge Highlighting target And source filtered remove
+ targetAndSourceNodeList.forEach((node) => { node.removeClass(gFilteredClassName); });
cytoscapeObject.elements(`.${gFilteredClassName}`).style('opacity', '0.1');
};
@@ -290,9 +340,13 @@
getLabels() {
return Object.keys(props.data.legend.nodeLegend);
},
+ getEdges() {
+ return Object.keys(props.data.legend.edgeLegend);
+ },
getCaptionsFromCytoscapeObject,
applyFilterOnCytoscapeElements,
resetFilterOnCytoscapeElements,
+ applyEdgeThicknessCytoscapeElements,
}));
return (
diff --git a/frontend/src/components/cytoscape/CypherResultCytoscapeFooter.jsx b/frontend/src/components/cytoscape/CypherResultCytoscapeFooter.jsx
index 7260f82..73f84a7 100644
--- a/frontend/src/components/cytoscape/CypherResultCytoscapeFooter.jsx
+++ b/frontend/src/components/cytoscape/CypherResultCytoscapeFooter.jsx
@@ -251,7 +251,19 @@
</strong>
</button>
))}
-
+ <button
+ onClick={() => [
+ updateLabelCaption(footerData.data.type, footerData.data.label, null),
+ captionChange(footerData.data.type, footerData.data.label, null)]}
+ key={uuid()}
+ type="button"
+ className={`btn captionSelector ${selectedCaption === null ? ' btn-secondary ' : ' btn-outline-dark '}`}
+ >
+ <strong>
+ <
+ >
+ </strong>
+ </button>
</span>
</div>
<button
diff --git a/frontend/src/components/cytoscape/CytoscapeStyleSheet.js b/frontend/src/components/cytoscape/CytoscapeStyleSheet.js
index e2db89e..0559b50 100644
--- a/frontend/src/components/cytoscape/CytoscapeStyleSheet.js
+++ b/frontend/src/components/cytoscape/CytoscapeStyleSheet.js
@@ -21,14 +21,8 @@
}
const props = ele.data('properties');
if (props[captionProp] === undefined) {
- if (ele.isNode()) {
- selectedLabel.node[ele.data('label')] = 'gid';
- } else {
- selectedLabel.edge[ele.data('label')] = 'gid';
- }
- return `[ ${ele.data('id')} ]`;
+ return '';
}
-
if (ele.isNode()) {
selectedLabel.node[ele.data('label')] = captionProp;
} else {
diff --git a/frontend/src/components/frame/Frame.jsx b/frontend/src/components/frame/Frame.jsx
index f565fb5..74d89f6 100644
--- a/frontend/src/components/frame/Frame.jsx
+++ b/frontend/src/components/frame/Frame.jsx
@@ -9,8 +9,9 @@
// faPaperclip,
faSync, faTimes,
} from '@fortawesome/free-solid-svg-icons';
-
-import { Button, Dropdown, Menu } from 'antd';
+import {
+ Button, Dropdown, Menu, Popover,
+} from 'antd';
import PropTypes from 'prop-types';
import styles from './Frame.module.scss';
@@ -18,7 +19,7 @@
reqString, content,
// isPinned, pinFrame,
refKey, removeFrame,
- onSearch, onSearchCancel, onDownload, onRefresh,
+ onSearch, onThick, thicnessMenu, onSearchCancel, onDownload, onRefresh,
bodyNoPadding,
}) => {
const [isFullScreen, setFullScreen] = useState(false);
@@ -48,6 +49,19 @@
</strong>
</div>
<div className={styles.ButtonArea}>
+ {onThick ? (
+ <Popover placement="bottomLeft" content={thicnessMenu} trigger="click">
+ <Button
+ size="large"
+ type="link"
+ className={styles.FrameButton}
+ title="Edge Weight"
+ onClick={() => onThick()}
+ >
+ <i className="icon-edge-weight" />
+ </Button>
+ </Popover>
+ ) : null }
{onSearchCancel ? (
<Button
size="large"
@@ -164,7 +178,9 @@
Frame.defaultProps = {
onSearch: null,
+ onThick: null,
onSearchCancel: null,
+ thicnessMenu: null,
onDownload: null,
onRefresh: null,
bodyNoPadding: false,
@@ -178,6 +194,8 @@
refKey: PropTypes.string.isRequired,
removeFrame: PropTypes.func.isRequired,
onSearch: PropTypes.func,
+ onThick: PropTypes.func,
+ thicnessMenu: PropTypes.func,
onSearchCancel: PropTypes.func,
onDownload: PropTypes.func,
onRefresh: PropTypes.func,
diff --git a/frontend/src/components/frame/presentations/CypherGraphResultFrame.jsx b/frontend/src/components/frame/presentations/CypherGraphResultFrame.jsx
index 82fb8a0..76e7a51 100644
--- a/frontend/src/components/frame/presentations/CypherGraphResultFrame.jsx
+++ b/frontend/src/components/frame/presentations/CypherGraphResultFrame.jsx
@@ -25,6 +25,7 @@
from '../../cypherresult/containers/CypherResultCytoscapeContainer';
import CypherResultTableContainer from '../../cypherresult/containers/CypherResultTableContainer';
import GraphFilterModal from '../../cypherresult/components/GraphFilterModal';
+import EdgeThicknessMenu from '../../cypherresult/components/EdgeThicknessMenu';
import Frame from '../Frame';
const CypherResultFrame = ({
@@ -38,9 +39,13 @@
const [cytoscapeContainerKey, setCytoscapeContainerKey] = useState(uuid());
const [filterModalVisible, setFilterModalVisible] = useState(false);
+ const [thicknessModalVisible, setThicknessModalVisible] = useState(false);
const [filterProperties, setFilterProperties] = useState([]);
+ const [edgeProperties, setEdgeProperties] = useState([]);
const [globalFilter, setGlobalFilter] = useState(null);
+ const [globalThickness, setGlobalThickness] = useState(null);
+
useEffect(() => {
if (chartAreaRef.current && filterModalVisible) {
const labels = chartAreaRef.current.getLabels()
@@ -55,7 +60,18 @@
).flat();
setFilterProperties(labels);
}
- }, [filterModalVisible]);
+ if (chartAreaRef.current && thicknessModalVisible) {
+ const edges = chartAreaRef.current.getEdges()
+ .map((edge) => {
+ const propertiesIter = Array.from(chartAreaRef.current.getCaptionsFromCytoscapeObject('edge', edge));
+ return propertiesIter.map((value) => ({
+ edge,
+ property: value,
+ }));
+ }).flat();
+ setEdgeProperties(edges);
+ }
+ }, [filterModalVisible, thicknessModalVisible]);
useEffect(() => {
if (globalFilter) {
@@ -65,6 +81,10 @@
}
}, [globalFilter]);
+ useEffect(() => {
+ chartAreaRef.current.applyEdgeThicknessCytoscapeElements(globalThickness);
+ }, [globalThickness]);
+
const refreshFrame = () => {
setCytoscapeContainerKey(uuid());
};
@@ -128,6 +148,17 @@
<Frame
bodyNoPadding
onSearch={() => setFilterModalVisible(true)}
+ onThick={() => setThicknessModalVisible(true)}
+ thicnessMenu={
+ (
+ <EdgeThicknessMenu
+ onSubmit={(thicness) => {
+ setGlobalThickness(thicness);
+ }}
+ properties={edgeProperties}
+ />
+ )
+ }
onSearchCancel={() => setGlobalFilter(null)}
onRefresh={refreshFrame}
onDownload={(type) => {
diff --git a/frontend/src/static/icons/css/fontello.css b/frontend/src/static/icons/css/fontello.css
index 617825c..41d441b 100644
--- a/frontend/src/static/icons/css/fontello.css
+++ b/frontend/src/static/icons/css/fontello.css
@@ -61,4 +61,5 @@
.icon-play:before { content: '\e803'; font-size: 30px; }
.icon-close-session:before { content: '\e804'; font-size: 30px; color: #142B80; }
.icon-refresh:before { content: '\e805'; font-size: 30px; color: #18CCC5; }
-.icon-search-cancel:before { content: '\e806'; font-size: 24px; }
\ No newline at end of file
+.icon-search-cancel:before { content: '\e806'; font-size: 24px; }
+.icon-edge-weight:before { content: '\e807'; font-size: 24px;}
\ No newline at end of file
diff --git a/frontend/src/static/icons/font/fontello.eot b/frontend/src/static/icons/font/fontello.eot
index 7371ed1..76dc8f9 100644
--- a/frontend/src/static/icons/font/fontello.eot
+++ b/frontend/src/static/icons/font/fontello.eot
Binary files differ
diff --git a/frontend/src/static/icons/font/fontello.svg b/frontend/src/static/icons/font/fontello.svg
index 86d9e18..67f3e31 100644
--- a/frontend/src/static/icons/font/fontello.svg
+++ b/frontend/src/static/icons/font/fontello.svg
@@ -19,6 +19,8 @@
<glyph glyph-name="refresh" unicode="" d="M864 850h-728a136 136 0 0 1-136-136v-728a136 136 0 0 1 136-136h728a136 136 0 0 1 136 136v728a136 136 0 0 1-136 136z m-154-706a268 268 0 0 0-182-99 270 270 0 0 0-297 226l0 0a49 49 0 0 0 96 15 172 172 0 1 1 187 199v-56l0-1a19 19 0 0 0-29-13l-1 1-142 104a18 18 0 0 0-3 4 18 18 0 0 0 3 25l143 104a19 19 0 0 0 29-12l0-1v-57a270 270 0 0 0 196-439z" horiz-adv-x="1000" />
<glyph glyph-name="search-cancel" unicode="" d="M752 850a105 105 0 0 1-103-123l-72-34a105 105 0 0 1-78 36h0a105 105 0 0 1-78-36l-71 34a105 105 0 1 1-104-86h0a105 105 0 0 1 65 22l84-40a105 105 0 0 1 104-104h0a105 105 0 0 1 104 104l84 40a105 105 0 0 1 65-22h0a105 105 0 0 1 0 209z m-506-135a30 30 0 1 0 31 30 30 30 0 0 0-31-30z m253-121a30 30 0 1 0 30 30v0a30 30 0 0 0-30-30z m253 121a30 30 0 1 0 31 30v0a30 30 0 0 0-31-30z m76-420a234 234 0 1 1 133-211 234 234 0 0 1-133 211z m49-319l-51-51-99 99-99-99-51 51 99 99-99 99 51 51 99-99 99 99 51-51-99-99z m-454 108a305 305 0 0 0 13 89q-123 136-246 272a2971 2971 0 0 1 618 0l-27-30c-9-10-17-19-26-28a301 301 0 0 0 85-21l106 116a46 46 0 0 1 11 17 46 46 0 0 1-22 59 110 110 0 0 1-35 13c-44 8-91 16-138 23-20 2-40 3-59 4-9 1-17 1-26 2l-27 2v-78l24 0q40-2 80-6c-34-3-69-7-104-8a2660 2660 0 0 0-415 7q45 6 91 7l25 1-3 78-27-2c-14-2-28-3-42-4-31-2-63-5-94-9a696 696 0 0 1-107-24 50 50 0 0 1-37-34 53 53 0 0 1 16-51q163-180 325-360a11 11 0 0 0 2-7c1-56 1-107 1-153a65 65 0 0 1 34-65 522 522 0 0 1 80-33 224 224 0 0 1 27-5 303 303 0 0 0-103 228z" horiz-adv-x="1000" />
+
+<glyph glyph-name="edge-weight" unicode="" d="M0 360v-85a32 32 0 0 1 35-28h929a32 32 0 0 1 35 28v85a32 32 0 0 1-35 29h-929a32 32 0 0 1-35-29z m1 262v-44a29 29 0 0 1 31-26h937a29 29 0 0 1 31 26v44a29 29 0 0 1-31 25h-937a29 29 0 0 1-31-25z m-1-598v-142a36 36 0 0 1 39-32h921a36 36 0 0 1 39 32v142a36 36 0 0 1-39 32h-921a36 36 0 0 1-39-32z m0 802v-10a27 27 0 0 1 29-24h941a27 27 0 0 1 29 24v10a27 27 0 0 1-29 24h-941a27 27 0 0 1-29-24z" horiz-adv-x="1000" />
</font>
</defs>
</svg>
\ No newline at end of file
diff --git a/frontend/src/static/icons/font/fontello.ttf b/frontend/src/static/icons/font/fontello.ttf
index 07f5d0f..c74fc93 100644
--- a/frontend/src/static/icons/font/fontello.ttf
+++ b/frontend/src/static/icons/font/fontello.ttf
Binary files differ
diff --git a/frontend/src/static/icons/font/fontello.woff b/frontend/src/static/icons/font/fontello.woff
index cc1defd..85262f2 100644
--- a/frontend/src/static/icons/font/fontello.woff
+++ b/frontend/src/static/icons/font/fontello.woff
Binary files differ
diff --git a/frontend/src/static/icons/font/fontello.woff2 b/frontend/src/static/icons/font/fontello.woff2
index 55d527d..c5dad89 100644
--- a/frontend/src/static/icons/font/fontello.woff2
+++ b/frontend/src/static/icons/font/fontello.woff2
Binary files differ