diff --git a/src/module/cytoscapeMap.js b/src/module/cytoscapeMap.js index b9a0760..6d3ded5 100644 --- a/src/module/cytoscapeMap.js +++ b/src/module/cytoscapeMap.js @@ -6,7 +6,6 @@ import cola from 'cytoscape-cola'; import tippy from 'tippy.js'; import 'tippy.js/dist/tippy.css'; import MapPathStore from '@/stores/mapPathStore'; -import Gradient from 'javascript-color-gradient'; // 多個色階產生器 import { getTimeLabel } from '@/module/timeLabel.js'; // 時間格式轉換器 import CytoscapeStore from '@/stores/cytoscapeStore'; import { SAVE_KEY_NAME } from '@/constants/constants.js'; @@ -15,7 +14,7 @@ const mapPathStore = MapPathStore(); const composeFreqTypeText = (baseText, dataLayerOption, optionValue) => { //sonar-qube let text = baseText; 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; return text; @@ -25,7 +24,7 @@ const composeFreqTypeText = (baseText, dataLayerOption, optionValue) => { //sona cytoscape.use(dagre); cytoscape.use(spread); cytoscape.use(fcose); -cytoscape.use(cola); +cytoscape.use(cola); /** * @param {object} mapData processMapData | bpmnData,可選以上任一。 @@ -60,10 +59,10 @@ export default function cytoscapeMap(mapData, dataLayerType, dataLayerOption, cu style: [ // 點擊 node 後改變的樣式 { - selector:'node:selected', - style:{ - 'border-color':'red', - 'border-width':'3', + selector: 'node:selected', + style: { + 'border-color': 'red', + 'border-width': '3', }, }, // node 節點的樣式 @@ -71,72 +70,72 @@ export default function cytoscapeMap(mapData, dataLayerType, dataLayerOption, cu selector: 'node', style: { 'label': - function(node) { // 節點要顯示的文字 - // node.data(this.dataLayerType+"."+this.dataLayerOption) 為原先陣列 node.data.key.value - let optionValue = node.data(`${dataLayerType}.${dataLayerOption}`); - let text = ''; - const STRING_LIMIT = 8; - if (node.data('label').length > STRING_LIMIT) { - // 若文字超過 STRING_LIMIT長度,則字尾巴要加「...」,style 要換兩行(\n 換行符號) - // 使用 data() 是因為在 cytoscape 中從陣列轉為 function - text = `${node.data('label').substr(0, STRING_LIMIT)}...\n\n`; - } else { // 補空白直到撐寬label的寬度,這是為了統一所有label的寬度 - 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; + function (node) { // 節點要顯示的文字 + // node.data(this.dataLayerType+"."+this.dataLayerOption) 為原先陣列 node.data.key.value + let optionValue = node.data(`${dataLayerType}.${dataLayerOption}`); + let text = ''; + const STRING_LIMIT = 8; + if (node.data('label').length > STRING_LIMIT) { + // 若文字超過 STRING_LIMIT長度,則字尾巴要加「...」,style 要換兩行(\n 換行符號) + // 使用 data() 是因為在 cytoscape 中從陣列轉為 function + text = `${node.data('label').substr(0, STRING_LIMIT)}...\n\n`; + } else { // 補空白直到撐寬label的寬度,這是為了統一所有label的寬度 + text = `${node.data('label').padEnd(STRING_LIMIT, ' ')}\n\n` } - } - return text; - }, - 'text-opacity':0.7, + // 在 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; + }, + 'text-opacity': 0.7, 'background-color': 'data(backgroundColor)', - 'border-color':'data(bordercolor)', + 'border-color': 'data(bordercolor)', 'border-width': - function(node) { - return node.data('type') === 'activity' ? '1' : '2'; - }, + function (node) { + return node.data('type') === 'activity' ? '1' : '2'; + }, 'background-image': 'data(nodeImageUrl)', 'background-opacity': 'data(backgroundOpacity)', // 透明背景 'border-opacity': 'data(borderOpacity)', // 透明邊框 - 'shape':'data(shape)', + 'shape': 'data(shape)', 'text-wrap': 'wrap', 'text-max-width': 'data(width)', // 在 div 內換行 'text-overflow-wrap': 'anywhere', // 在 div 內換行 - 'text-margin-x': function(node) { + 'text-margin-x': function (node) { return node.data('type') === 'activity' ? -5 : 0; }, - 'text-margin-y': function(node) { + 'text-margin-y': function (node) { return node.data('type') === 'activity' ? 2 : 0; }, - 'padding': function(node) { + 'padding': function (node) { return node.data('type') === 'activity' ? 0 : 0; - }, + }, 'text-justification': 'left', 'text-halign': 'center', 'text-valign': 'center', @@ -145,16 +144,16 @@ export default function cytoscapeMap(mapData, dataLayerType, dataLayerOption, cu 'color': 'data(textColor)', 'line-height': '0.7rem', 'font-size': - function(node) { - return node.data('type') === 'activity' ? 14 : 14; - }, + function (node) { + return node.data('type') === 'activity' ? 14 : 14; + }, }, }, // edge 關係線的樣式 { selector: 'edge', style: { - 'content': function(edge) { // 關係線顯示的文字 + 'content': function (edge) { // 關係線顯示的文字 let optionValue = edge.data(`${dataLayerType}.${dataLayerOption}`); let result = ''; let edgeInt; @@ -163,9 +162,9 @@ export default function cytoscapeMap(mapData, dataLayerType, dataLayerOption, cu let timeLabelInt; let timeLabelFloat; let edgeTimeLabel; - if(optionValue === '') return optionValue; + if (optionValue === '') return optionValue; - switch(dataLayerType) { + switch (dataLayerType) { case 'freq': edgeInt = dataLayerOption === 'rel_freq' ? optionValue * 100 + "%" : optionValue; 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', // 指向目標的箭頭形狀: 三角形 'color': 'gray', //#0066cc //'control-point-step-size':100, // 從點到點的垂直線,指定貝茲取線邊緣間的距離 - 'width':'data(lineWidth)', - 'line-style':'data(style)', + 'width': 'data(lineWidth)', + 'line-style': 'data(style)', "text-margin-y": "0.7rem", //"text-rotation": "autorotate", } - },{ + }, { selector: '.highlight-edge', style: { 'color': '#0099FF', @@ -206,14 +205,14 @@ export default function cytoscapeMap(mapData, dataLayerType, dataLayerOption, cu 'overlay-opacity': 0.2, 'overlay-padding': '5px', }, - },{ + }, { selector: '.highlight-node', style: { 'overlay-color': '#0099FF', 'overlay-opacity': 0.01, 'overlay-padding': '5px', }, - },{ + }, { selector: 'edge[source = target]', // 選擇 self-loop 的邊 style: { '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'); event.target.addClass('highlight-edge'); }); // 按下節點光暈效果與鄰邊光暈效果 - cy.on('tap, mousedown', 'node', function(event) { + cy.on('tap, mousedown', 'node', function (event) { mapPathStore.onNodeClickHighlightEdges(event.target); }); // 按下線段光暈效果與兩端點光暈效果 - cy.on('tap, mousedown', 'edge', function(event) { + cy.on('tap, mousedown', 'edge', function (event) { mapPathStore.onEdgeClickHighlightNodes(event.target); }); // creat tippy.js let tip; - cy.on('mouseover', 'node', function(event) { - const node = event.target; - let ref = node.popperRef() - let dummyDomEle = document.createElement('div'); - let content = document.createElement('div'); - content.innerHTML = node.data("label") - tip = new tippy(dummyDomEle, { // tippy props: - getReferenceClientRect: ref.getBoundingClientRect, - trigger: 'manual', - content:content - }); - if(node.data("label").length > 10) tip.show(); + cy.on('mouseover', 'node', function (event) { + const node = event.target; + let ref = node.popperRef() + let dummyDomEle = document.createElement('div'); + let content = document.createElement('div'); + content.innerHTML = node.data("label") + tip = new tippy(dummyDomEle, { // tippy props: + getReferenceClientRect: ref.getBoundingClientRect, + trigger: 'manual', + content: content + }); + if (node.data("label").length > 10) tip.show(); }); - cy.on('mouseout', 'node', function(event) { - tip.hide(); + cy.on('mouseout', 'node', function (event) { + tip.hide(); }); + // here we remember and recall positions const cytoscapeStore = CytoscapeStore(); cy.ready(() => { + if (rank === 'TB') { + return; // early return; only handle 'LR' (horizontal) case + } cytoscapeStore.loadPositionsFromStorage(); // 判斷localStorage是否儲存過拜訪資訊 // 若曾經儲存過拜訪後的座標位置,則restore位置來渲染出來 - if(localStorage.getItem(SAVE_KEY_NAME) && JSON.parse(localStorage.getItem(SAVE_KEY_NAME))) { - const allGraphsRemembered = JSON.parse(localStorage.getItem(SAVE_KEY_NAME)); - const currentGraphNodesRemembered = allGraphsRemembered[cytoscapeStore.currentGraphId]; // 可能是undefined - if(currentGraphNodesRemembered) { + if (localStorage.getItem(SAVE_KEY_NAME) && JSON.parse(localStorage.getItem(SAVE_KEY_NAME))) { + const allGraphsRemembered = JSON.parse(localStorage.getItem(SAVE_KEY_NAME)); + const currentGraphNodesRemembered = allGraphsRemembered[cytoscapeStore.currentGraphId]; // 可能是undefined + if (currentGraphNodesRemembered) { currentGraphNodesRemembered.forEach(nodeRemembered => { const nodeToDecide = cy.getElementById(nodeRemembered.id); if (nodeToDecide) { @@ -282,7 +280,7 @@ export default function cytoscapeMap(mapData, dataLayerType, dataLayerOption, cu } }); } - } + } //存下此刻剛進入畫面時當前所有節點的座標位置 const allNodes = cy.nodes(); allNodes.map(nodeFirstlySave => { @@ -292,7 +290,7 @@ export default function cytoscapeMap(mapData, dataLayerType, dataLayerOption, cu // 在改變節點位置後,盡可能地記錄節點線條的位置情報 cy.on('dragfree', 'node', (event) => { const nodeToSave = event.target; - cytoscapeStore.saveNodePosition(nodeToSave.id(), nodeToSave.position()); + cytoscapeStore.saveNodePosition(nodeToSave.id(), nodeToSave.position()); }); });