fix vertical path bug by not handling TB case
This commit is contained in:
@@ -6,7 +6,6 @@ import cola from 'cytoscape-cola';
|
|||||||
import tippy from 'tippy.js';
|
import tippy from 'tippy.js';
|
||||||
import 'tippy.js/dist/tippy.css';
|
import 'tippy.js/dist/tippy.css';
|
||||||
import MapPathStore from '@/stores/mapPathStore';
|
import MapPathStore from '@/stores/mapPathStore';
|
||||||
import Gradient from 'javascript-color-gradient'; // 多個色階產生器
|
|
||||||
import { getTimeLabel } from '@/module/timeLabel.js'; // 時間格式轉換器
|
import { getTimeLabel } from '@/module/timeLabel.js'; // 時間格式轉換器
|
||||||
import CytoscapeStore from '@/stores/cytoscapeStore';
|
import CytoscapeStore from '@/stores/cytoscapeStore';
|
||||||
import { SAVE_KEY_NAME } from '@/constants/constants.js';
|
import { SAVE_KEY_NAME } from '@/constants/constants.js';
|
||||||
@@ -15,7 +14,7 @@ const mapPathStore = MapPathStore();
|
|||||||
const composeFreqTypeText = (baseText, dataLayerOption, optionValue) => { //sonar-qube
|
const composeFreqTypeText = (baseText, dataLayerOption, optionValue) => { //sonar-qube
|
||||||
let text = baseText;
|
let text = baseText;
|
||||||
const textInt = dataLayerOption === 'rel_freq' ? baseText + optionValue * 100 + "%" : baseText + optionValue;
|
const textInt = dataLayerOption === 'rel_freq' ? baseText + optionValue * 100 + "%" : baseText + optionValue;
|
||||||
const textFloat = dataLayerOption === 'rel_freq'? baseText + (optionValue * 100).toFixed(2) + "%" : baseText + optionValue.toFixed(2);
|
const textFloat = dataLayerOption === 'rel_freq' ? baseText + (optionValue * 100).toFixed(2) + "%" : baseText + optionValue.toFixed(2);
|
||||||
// 判斷是否為整數,若非整數要取小數點後面兩個值。
|
// 判斷是否為整數,若非整數要取小數點後面兩個值。
|
||||||
text = Math.trunc(optionValue) === optionValue ? textInt : textFloat;
|
text = Math.trunc(optionValue) === optionValue ? textInt : textFloat;
|
||||||
return text;
|
return text;
|
||||||
@@ -60,10 +59,10 @@ export default function cytoscapeMap(mapData, dataLayerType, dataLayerOption, cu
|
|||||||
style: [
|
style: [
|
||||||
// 點擊 node 後改變的樣式
|
// 點擊 node 後改變的樣式
|
||||||
{
|
{
|
||||||
selector:'node:selected',
|
selector: 'node:selected',
|
||||||
style:{
|
style: {
|
||||||
'border-color':'red',
|
'border-color': 'red',
|
||||||
'border-width':'3',
|
'border-width': '3',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// node 節點的樣式
|
// node 節點的樣式
|
||||||
@@ -71,70 +70,70 @@ export default function cytoscapeMap(mapData, dataLayerType, dataLayerOption, cu
|
|||||||
selector: 'node',
|
selector: 'node',
|
||||||
style: {
|
style: {
|
||||||
'label':
|
'label':
|
||||||
function(node) { // 節點要顯示的文字
|
function (node) { // 節點要顯示的文字
|
||||||
// node.data(this.dataLayerType+"."+this.dataLayerOption) 為原先陣列 node.data.key.value
|
// node.data(this.dataLayerType+"."+this.dataLayerOption) 為原先陣列 node.data.key.value
|
||||||
let optionValue = node.data(`${dataLayerType}.${dataLayerOption}`);
|
let optionValue = node.data(`${dataLayerType}.${dataLayerOption}`);
|
||||||
let text = '';
|
let text = '';
|
||||||
const STRING_LIMIT = 8;
|
const STRING_LIMIT = 8;
|
||||||
if (node.data('label').length > STRING_LIMIT) {
|
if (node.data('label').length > STRING_LIMIT) {
|
||||||
// 若文字超過 STRING_LIMIT長度,則字尾巴要加「...」,style 要換兩行(\n 換行符號)
|
// 若文字超過 STRING_LIMIT長度,則字尾巴要加「...」,style 要換兩行(\n 換行符號)
|
||||||
// 使用 data() 是因為在 cytoscape 中從陣列轉為 function
|
// 使用 data() 是因為在 cytoscape 中從陣列轉為 function
|
||||||
text = `${node.data('label').substr(0, STRING_LIMIT)}...\n\n`;
|
text = `${node.data('label').substr(0, STRING_LIMIT)}...\n\n`;
|
||||||
} else { // 補空白直到撐寬label的寬度,這是為了統一所有label的寬度
|
} else { // 補空白直到撐寬label的寬度,這是為了統一所有label的寬度
|
||||||
text = `${node.data('label').padEnd(STRING_LIMIT, ' ')}\n\n`
|
text = `${node.data('label').padEnd(STRING_LIMIT, ' ')}\n\n`
|
||||||
}
|
|
||||||
|
|
||||||
// 在 element 中 activity 歸類在 default,所以要先判斷 node 是否為 activity 才裝入文字。
|
|
||||||
// 可使用 parseInt(整數) parseFloat(浮點數) 將字串轉為數字
|
|
||||||
// Relative 要轉為百分比 %
|
|
||||||
if(node.data('type') === 'activity') {
|
|
||||||
let textDurRel;
|
|
||||||
let timeLabelInt;
|
|
||||||
let timeLabelFloat;
|
|
||||||
let textTimeLabel;
|
|
||||||
|
|
||||||
switch(dataLayerType) {
|
|
||||||
case 'freq': // Frequency
|
|
||||||
text = composeFreqTypeText(text, dataLayerOption, optionValue);
|
|
||||||
break;
|
|
||||||
case 'duration': // Duration 除了 Relative 為百分比 % ,其他要轉變時間單位。
|
|
||||||
// Relative %
|
|
||||||
textDurRel = text + (optionValue * 100).toFixed(2) + "%";
|
|
||||||
// Timelabel
|
|
||||||
timeLabelInt = text + getTimeLabel(optionValue);
|
|
||||||
timeLabelFloat = text + getTimeLabel(optionValue.toFixed(2));
|
|
||||||
|
|
||||||
// 判斷是否為整數,若非整數要取小數點後面兩個值。
|
|
||||||
textTimeLabel = Math.trunc(optionValue) === optionValue ? timeLabelInt : timeLabelFloat;
|
|
||||||
|
|
||||||
text = dataLayerOption === 'rel_duration' ? textDurRel : textTimeLabel;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return text;
|
// 在 element 中 activity 歸類在 default,所以要先判斷 node 是否為 activity 才裝入文字。
|
||||||
},
|
// 可使用 parseInt(整數) parseFloat(浮點數) 將字串轉為數字
|
||||||
'text-opacity':0.7,
|
// Relative 要轉為百分比 %
|
||||||
|
if (node.data('type') === 'activity') {
|
||||||
|
let textDurRel;
|
||||||
|
let timeLabelInt;
|
||||||
|
let timeLabelFloat;
|
||||||
|
let textTimeLabel;
|
||||||
|
|
||||||
|
switch (dataLayerType) {
|
||||||
|
case 'freq': // Frequency
|
||||||
|
text = composeFreqTypeText(text, dataLayerOption, optionValue);
|
||||||
|
break;
|
||||||
|
case 'duration': // Duration 除了 Relative 為百分比 % ,其他要轉變時間單位。
|
||||||
|
// Relative %
|
||||||
|
textDurRel = text + (optionValue * 100).toFixed(2) + "%";
|
||||||
|
// Timelabel
|
||||||
|
timeLabelInt = text + getTimeLabel(optionValue);
|
||||||
|
timeLabelFloat = text + getTimeLabel(optionValue.toFixed(2));
|
||||||
|
|
||||||
|
// 判斷是否為整數,若非整數要取小數點後面兩個值。
|
||||||
|
textTimeLabel = Math.trunc(optionValue) === optionValue ? timeLabelInt : timeLabelFloat;
|
||||||
|
|
||||||
|
text = dataLayerOption === 'rel_duration' ? textDurRel : textTimeLabel;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
},
|
||||||
|
'text-opacity': 0.7,
|
||||||
'background-color': 'data(backgroundColor)',
|
'background-color': 'data(backgroundColor)',
|
||||||
'border-color':'data(bordercolor)',
|
'border-color': 'data(bordercolor)',
|
||||||
'border-width':
|
'border-width':
|
||||||
function(node) {
|
function (node) {
|
||||||
return node.data('type') === 'activity' ? '1' : '2';
|
return node.data('type') === 'activity' ? '1' : '2';
|
||||||
},
|
},
|
||||||
'background-image': 'data(nodeImageUrl)',
|
'background-image': 'data(nodeImageUrl)',
|
||||||
'background-opacity': 'data(backgroundOpacity)', // 透明背景
|
'background-opacity': 'data(backgroundOpacity)', // 透明背景
|
||||||
'border-opacity': 'data(borderOpacity)', // 透明邊框
|
'border-opacity': 'data(borderOpacity)', // 透明邊框
|
||||||
'shape':'data(shape)',
|
'shape': 'data(shape)',
|
||||||
'text-wrap': 'wrap',
|
'text-wrap': 'wrap',
|
||||||
'text-max-width': 'data(width)', // 在 div 內換行
|
'text-max-width': 'data(width)', // 在 div 內換行
|
||||||
'text-overflow-wrap': 'anywhere', // 在 div 內換行
|
'text-overflow-wrap': 'anywhere', // 在 div 內換行
|
||||||
'text-margin-x': function(node) {
|
'text-margin-x': function (node) {
|
||||||
return node.data('type') === 'activity' ? -5 : 0;
|
return node.data('type') === 'activity' ? -5 : 0;
|
||||||
},
|
},
|
||||||
'text-margin-y': function(node) {
|
'text-margin-y': function (node) {
|
||||||
return node.data('type') === 'activity' ? 2 : 0;
|
return node.data('type') === 'activity' ? 2 : 0;
|
||||||
},
|
},
|
||||||
'padding': function(node) {
|
'padding': function (node) {
|
||||||
return node.data('type') === 'activity' ? 0 : 0;
|
return node.data('type') === 'activity' ? 0 : 0;
|
||||||
},
|
},
|
||||||
'text-justification': 'left',
|
'text-justification': 'left',
|
||||||
@@ -145,16 +144,16 @@ export default function cytoscapeMap(mapData, dataLayerType, dataLayerOption, cu
|
|||||||
'color': 'data(textColor)',
|
'color': 'data(textColor)',
|
||||||
'line-height': '0.7rem',
|
'line-height': '0.7rem',
|
||||||
'font-size':
|
'font-size':
|
||||||
function(node) {
|
function (node) {
|
||||||
return node.data('type') === 'activity' ? 14 : 14;
|
return node.data('type') === 'activity' ? 14 : 14;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// edge 關係線的樣式
|
// edge 關係線的樣式
|
||||||
{
|
{
|
||||||
selector: 'edge',
|
selector: 'edge',
|
||||||
style: {
|
style: {
|
||||||
'content': function(edge) { // 關係線顯示的文字
|
'content': function (edge) { // 關係線顯示的文字
|
||||||
let optionValue = edge.data(`${dataLayerType}.${dataLayerOption}`);
|
let optionValue = edge.data(`${dataLayerType}.${dataLayerOption}`);
|
||||||
let result = '';
|
let result = '';
|
||||||
let edgeInt;
|
let edgeInt;
|
||||||
@@ -163,9 +162,9 @@ export default function cytoscapeMap(mapData, dataLayerType, dataLayerOption, cu
|
|||||||
let timeLabelInt;
|
let timeLabelInt;
|
||||||
let timeLabelFloat;
|
let timeLabelFloat;
|
||||||
let edgeTimeLabel;
|
let edgeTimeLabel;
|
||||||
if(optionValue === '') return optionValue;
|
if (optionValue === '') return optionValue;
|
||||||
|
|
||||||
switch(dataLayerType) {
|
switch (dataLayerType) {
|
||||||
case 'freq':
|
case 'freq':
|
||||||
edgeInt = dataLayerOption === 'rel_freq' ? optionValue * 100 + "%" : optionValue;
|
edgeInt = dataLayerOption === 'rel_freq' ? optionValue * 100 + "%" : optionValue;
|
||||||
edgeFloat = dataLayerOption === 'rel_freq' ? (optionValue * 100).toFixed(2) + "%" : optionValue.toFixed(2);
|
edgeFloat = dataLayerOption === 'rel_freq' ? (optionValue * 100).toFixed(2) + "%" : optionValue.toFixed(2);
|
||||||
@@ -192,12 +191,12 @@ export default function cytoscapeMap(mapData, dataLayerType, dataLayerOption, cu
|
|||||||
'target-arrow-shape': 'triangle', // 指向目標的箭頭形狀: 三角形
|
'target-arrow-shape': 'triangle', // 指向目標的箭頭形狀: 三角形
|
||||||
'color': 'gray', //#0066cc
|
'color': 'gray', //#0066cc
|
||||||
//'control-point-step-size':100, // 從點到點的垂直線,指定貝茲取線邊緣間的距離
|
//'control-point-step-size':100, // 從點到點的垂直線,指定貝茲取線邊緣間的距離
|
||||||
'width':'data(lineWidth)',
|
'width': 'data(lineWidth)',
|
||||||
'line-style':'data(style)',
|
'line-style': 'data(style)',
|
||||||
"text-margin-y": "0.7rem",
|
"text-margin-y": "0.7rem",
|
||||||
//"text-rotation": "autorotate",
|
//"text-rotation": "autorotate",
|
||||||
}
|
}
|
||||||
},{
|
}, {
|
||||||
selector: '.highlight-edge',
|
selector: '.highlight-edge',
|
||||||
style: {
|
style: {
|
||||||
'color': '#0099FF',
|
'color': '#0099FF',
|
||||||
@@ -206,14 +205,14 @@ export default function cytoscapeMap(mapData, dataLayerType, dataLayerOption, cu
|
|||||||
'overlay-opacity': 0.2,
|
'overlay-opacity': 0.2,
|
||||||
'overlay-padding': '5px',
|
'overlay-padding': '5px',
|
||||||
},
|
},
|
||||||
},{
|
}, {
|
||||||
selector: '.highlight-node',
|
selector: '.highlight-node',
|
||||||
style: {
|
style: {
|
||||||
'overlay-color': '#0099FF',
|
'overlay-color': '#0099FF',
|
||||||
'overlay-opacity': 0.01,
|
'overlay-opacity': 0.01,
|
||||||
'overlay-padding': '5px',
|
'overlay-padding': '5px',
|
||||||
},
|
},
|
||||||
},{
|
}, {
|
||||||
selector: 'edge[source = target]', // 選擇 self-loop 的邊
|
selector: 'edge[source = target]', // 選擇 self-loop 的邊
|
||||||
style: {
|
style: {
|
||||||
'loop-direction': '0deg', // 控制 loop 的方向
|
'loop-direction': '0deg', // 控制 loop 的方向
|
||||||
@@ -224,57 +223,56 @@ export default function cytoscapeMap(mapData, dataLayerType, dataLayerOption, cu
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
// 讓節點不要左右重疊的演算法
|
|
||||||
cy.layout({
|
|
||||||
name: 'spread',
|
|
||||||
minDist: 60, // 設置最小節點間距
|
|
||||||
}).run();
|
|
||||||
|
|
||||||
// 按下線條,線條及線條上數字有光暈效果
|
// 按下線條,線條及線條上數字有光暈效果
|
||||||
cy.on('tap', 'edge', function(event) {
|
cy.on('tap', 'edge', function (event) {
|
||||||
cy.edges().removeClass('highlight-edge');
|
cy.edges().removeClass('highlight-edge');
|
||||||
event.target.addClass('highlight-edge');
|
event.target.addClass('highlight-edge');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// 按下節點光暈效果與鄰邊光暈效果
|
// 按下節點光暈效果與鄰邊光暈效果
|
||||||
cy.on('tap, mousedown', 'node', function(event) {
|
cy.on('tap, mousedown', 'node', function (event) {
|
||||||
mapPathStore.onNodeClickHighlightEdges(event.target);
|
mapPathStore.onNodeClickHighlightEdges(event.target);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 按下線段光暈效果與兩端點光暈效果
|
// 按下線段光暈效果與兩端點光暈效果
|
||||||
cy.on('tap, mousedown', 'edge', function(event) {
|
cy.on('tap, mousedown', 'edge', function (event) {
|
||||||
mapPathStore.onEdgeClickHighlightNodes(event.target);
|
mapPathStore.onEdgeClickHighlightNodes(event.target);
|
||||||
});
|
});
|
||||||
|
|
||||||
// creat tippy.js
|
// creat tippy.js
|
||||||
let tip;
|
let tip;
|
||||||
cy.on('mouseover', 'node', function(event) {
|
cy.on('mouseover', 'node', function (event) {
|
||||||
const node = event.target;
|
const node = event.target;
|
||||||
let ref = node.popperRef()
|
let ref = node.popperRef()
|
||||||
let dummyDomEle = document.createElement('div');
|
let dummyDomEle = document.createElement('div');
|
||||||
let content = document.createElement('div');
|
let content = document.createElement('div');
|
||||||
content.innerHTML = node.data("label")
|
content.innerHTML = node.data("label")
|
||||||
tip = new tippy(dummyDomEle, { // tippy props:
|
tip = new tippy(dummyDomEle, { // tippy props:
|
||||||
getReferenceClientRect: ref.getBoundingClientRect,
|
getReferenceClientRect: ref.getBoundingClientRect,
|
||||||
trigger: 'manual',
|
trigger: 'manual',
|
||||||
content:content
|
content: content
|
||||||
});
|
});
|
||||||
if(node.data("label").length > 10) tip.show();
|
if (node.data("label").length > 10) tip.show();
|
||||||
});
|
});
|
||||||
cy.on('mouseout', 'node', function(event) {
|
cy.on('mouseout', 'node', function (event) {
|
||||||
tip.hide();
|
tip.hide();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// here we remember and recall positions
|
||||||
const cytoscapeStore = CytoscapeStore();
|
const cytoscapeStore = CytoscapeStore();
|
||||||
cy.ready(() => {
|
cy.ready(() => {
|
||||||
|
if (rank === 'TB') {
|
||||||
|
return; // early return; only handle 'LR' (horizontal) case
|
||||||
|
}
|
||||||
cytoscapeStore.loadPositionsFromStorage();
|
cytoscapeStore.loadPositionsFromStorage();
|
||||||
// 判斷localStorage是否儲存過拜訪資訊
|
// 判斷localStorage是否儲存過拜訪資訊
|
||||||
// 若曾經儲存過拜訪後的座標位置,則restore位置來渲染出來
|
// 若曾經儲存過拜訪後的座標位置,則restore位置來渲染出來
|
||||||
if(localStorage.getItem(SAVE_KEY_NAME) && JSON.parse(localStorage.getItem(SAVE_KEY_NAME))) {
|
if (localStorage.getItem(SAVE_KEY_NAME) && JSON.parse(localStorage.getItem(SAVE_KEY_NAME))) {
|
||||||
const allGraphsRemembered = JSON.parse(localStorage.getItem(SAVE_KEY_NAME));
|
const allGraphsRemembered = JSON.parse(localStorage.getItem(SAVE_KEY_NAME));
|
||||||
const currentGraphNodesRemembered = allGraphsRemembered[cytoscapeStore.currentGraphId]; // 可能是undefined
|
const currentGraphNodesRemembered = allGraphsRemembered[cytoscapeStore.currentGraphId]; // 可能是undefined
|
||||||
if(currentGraphNodesRemembered) {
|
if (currentGraphNodesRemembered) {
|
||||||
currentGraphNodesRemembered.forEach(nodeRemembered => {
|
currentGraphNodesRemembered.forEach(nodeRemembered => {
|
||||||
const nodeToDecide = cy.getElementById(nodeRemembered.id);
|
const nodeToDecide = cy.getElementById(nodeRemembered.id);
|
||||||
if (nodeToDecide) {
|
if (nodeToDecide) {
|
||||||
@@ -292,7 +290,7 @@ export default function cytoscapeMap(mapData, dataLayerType, dataLayerOption, cu
|
|||||||
// 在改變節點位置後,盡可能地記錄節點線條的位置情報
|
// 在改變節點位置後,盡可能地記錄節點線條的位置情報
|
||||||
cy.on('dragfree', 'node', (event) => {
|
cy.on('dragfree', 'node', (event) => {
|
||||||
const nodeToSave = event.target;
|
const nodeToSave = event.target;
|
||||||
cytoscapeStore.saveNodePosition(nodeToSave.id(), nodeToSave.position());
|
cytoscapeStore.saveNodePosition(nodeToSave.id(), nodeToSave.position());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user