fix: dotted solid edge style bug

This commit is contained in:
Cindy Chang
2024-09-05 17:49:01 +08:00
parent 8635c8d3e2
commit 5b3c0050b9
3 changed files with 370 additions and 353 deletions

View File

@@ -192,7 +192,7 @@ export default function cytoscapeMap(mapData, dataLayerType, dataLayerOption, cu
'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(edgeStyle)',
"text-margin-y": "0.7rem", "text-margin-y": "0.7rem",
//"text-rotation": "autorotate", //"text-rotation": "autorotate",
} }

View File

@@ -21,23 +21,23 @@ export default defineStore('useMapPathStore', {
cytoscape: { cytoscape: {
process: process:
{ {
curved:{ curved: {
horizontal:null, horizontal: null,
vertical:null, vertical: null,
}, },
elbow:{ elbow: {
horizontal:null, horizontal: null,
vertical:null, vertical: null,
} }
}, },
bpmn: { bpmn: {
curved:{ curved: {
horizontal:null, horizontal: null,
vertical:null, vertical: null,
}, },
elbow:{ elbow: {
horizontal:null, horizontal: null,
vertical:null, vertical: null,
} }
} }
}, },
@@ -60,36 +60,36 @@ export default defineStore('useMapPathStore', {
this.directionType = directionType; this.directionType = directionType;
this.cytoscape[processOrBPMN][curveType][directionType] = cytoscape; this.cytoscape[processOrBPMN][curveType][directionType] = cytoscape;
await this.createInsightWithPath(); await this.createInsightWithPath();
if(processOrBPMN === 'process') { if (processOrBPMN === 'process') {
await this.highlightMostFrequentPath(); await this.highlightMostFrequentPath();
} }
}, },
async createInsightWithPath() { async createInsightWithPath() {
const { insights } = AllMapData(); const { insights } = AllMapData();
this.insights = {...insights}; this.insights = { ...insights };
this.startNode = this.cytoscape[this.processOrBPMN][this.curveType][this.directionType]?.nodes() this.startNode = this.cytoscape[this.processOrBPMN][this.curveType][this.directionType]?.nodes()
.filter(function(elem) { .filter(function (elem) {
return elem.data('label').toLocaleLowerCase() === 'start'; return elem.data('label').toLocaleLowerCase() === 'start';
}); });
for(let i = 0; i < INSIGHTS_FIELDS_AND_LABELS.length; i++) { for (let i = 0; i < INSIGHTS_FIELDS_AND_LABELS.length; i++) {
const curButton = this.insights[INSIGHTS_FIELDS_AND_LABELS[i][0]]; const curButton = this.insights[INSIGHTS_FIELDS_AND_LABELS[i][0]];
this.insightWithPath[INSIGHTS_FIELDS_AND_LABELS[i][0]] = {}; // first layer index this.insightWithPath[INSIGHTS_FIELDS_AND_LABELS[i][0]] = {}; // first layer index
for(let listIndex = 0; listIndex < curButton.length; listIndex++) { for (let listIndex = 0; listIndex < curButton.length; listIndex++) {
this.insightWithPath[INSIGHTS_FIELDS_AND_LABELS[i][0]][listIndex] = { this.insightWithPath[INSIGHTS_FIELDS_AND_LABELS[i][0]][listIndex] = {
edges: [], edges: [],
nodes: [], nodes: [],
}; // second layer index }; // second layer index
let curGraphNode, prevGraphNode, curEdge; // 配對 curGraphNode 與 nodeIndex 指向的 node let curGraphNode, prevGraphNode, curEdge; // 配對 curGraphNode 與 nodeIndex 指向的 node
for(let nodeIndex = 0; nodeIndex < curButton[listIndex].length; nodeIndex++){ for (let nodeIndex = 0; nodeIndex < curButton[listIndex].length; nodeIndex++) {
if(nodeIndex === 0) { // special case, initialize curGraphNode if (nodeIndex === 0) { // special case, initialize curGraphNode
curGraphNode = this.startNode.outgoers('node').filter(neighborOfStart => curGraphNode = this.startNode.outgoers('node').filter(neighborOfStart =>
neighborOfStart.data('label') === curButton[listIndex][nodeIndex] neighborOfStart.data('label') === curButton[listIndex][nodeIndex]
); );
curEdge = this.startNode.edgesTo(curGraphNode); curEdge = this.startNode.edgesTo(curGraphNode);
} else { } else {
if(prevGraphNode){ if (prevGraphNode) {
curGraphNode = prevGraphNode.outgoers('node').filter(neighbor => curGraphNode = prevGraphNode.outgoers('node').filter(neighbor =>
neighbor.data('label') === curButton[listIndex][nodeIndex] neighbor.data('label') === curButton[listIndex][nodeIndex]
); );
@@ -99,7 +99,7 @@ export default defineStore('useMapPathStore', {
this.insightWithPath[INSIGHTS_FIELDS_AND_LABELS[i][0]][listIndex].nodes.push(curGraphNode); this.insightWithPath[INSIGHTS_FIELDS_AND_LABELS[i][0]][listIndex].nodes.push(curGraphNode);
this.insightWithPath[INSIGHTS_FIELDS_AND_LABELS[i][0]][listIndex].edges.push(curEdge); this.insightWithPath[INSIGHTS_FIELDS_AND_LABELS[i][0]][listIndex].edges.push(curEdge);
// 特殊狀況在for迴圈之外額外插入最後一條線段 // 特殊狀況在for迴圈之外額外插入最後一條線段
if(nodeIndex === curButton[listIndex].length - 1){ if (nodeIndex === curButton[listIndex].length - 1) {
const endNode = curGraphNode.outgoers('node').filter(neighbor => const endNode = curGraphNode.outgoers('node').filter(neighbor =>
neighbor.data('label').toLocaleLowerCase() === 'end' neighbor.data('label').toLocaleLowerCase() === 'end'
); );
@@ -113,19 +113,19 @@ export default defineStore('useMapPathStore', {
}, },
async createPaths() { async createPaths() {
this.startNode = this.cytoscape[this.processOrBPMN][this.curveType][this.directionType]?.nodes() this.startNode = this.cytoscape[this.processOrBPMN][this.curveType][this.directionType]?.nodes()
.filter(function(elem) { .filter(function (elem) {
return elem.data('label').toLocaleLowerCase() === 'start'; return elem.data('label').toLocaleLowerCase() === 'start';
}); });
// Depth First Search from the starting node // Depth First Search from the starting node
await this.depthFirstSearchCreatePath(this.startNode, [this.startNode], []); await this.depthFirstSearchCreatePath(this.startNode, [this.startNode], []);
const { insights } = AllMapData(); const { insights } = AllMapData();
this.insights = {...insights}; this.insights = { ...insights };
await this.matchGraphPathWithInsightsPath(); await this.matchGraphPathWithInsightsPath();
}, },
/** 從start節點開始建立所有的path /** 從start節點開始建立所有的path
* 於第二個參數逐漸推入節點,於第三個參數逐漸推入線段 * 於第二個參數逐漸推入節點,於第三個參數逐漸推入線段
*/ */
depthFirstSearchCreatePath(node, currentPathByNode, curPathByEdge){ depthFirstSearchCreatePath(node, currentPathByNode, curPathByEdge) {
const outgoingEdges = node.outgoers('edge'); const outgoingEdges = node.outgoers('edge');
if (outgoingEdges.length === 0) { if (outgoingEdges.length === 0) {
// 表示已經遇到尾聲 // 表示已經遇到尾聲
@@ -153,21 +153,21 @@ export default defineStore('useMapPathStore', {
* 而curButton[listIndex][nodeIndex]是用來確認是否跟depthFirstSearchCreatePath內的 * 而curButton[listIndex][nodeIndex]是用來確認是否跟depthFirstSearchCreatePath內的
* node.data('label')字串完全相等,也就是 activity 節點的文字 * node.data('label')字串完全相等,也就是 activity 節點的文字
*/ */
matchGraphPathWithInsightsPath(){ matchGraphPathWithInsightsPath() {
for(let whichPath = 0; whichPath < this.allPaths.length; whichPath++) { for (let whichPath = 0; whichPath < this.allPaths.length; whichPath++) {
const curPath = this.allPaths[whichPath]; const curPath = this.allPaths[whichPath];
const curPathByEdge = this.allPathsByEdge[whichPath]; const curPathByEdge = this.allPathsByEdge[whichPath];
// 針對這個path的第一個節點找到它在insights中是對應到哪一個起點 // 針對這個path的第一個節點找到它在insights中是對應到哪一個起點
for(let i = 0; i < INSIGHTS_FIELDS_AND_LABELS.length; i++) { for (let i = 0; i < INSIGHTS_FIELDS_AND_LABELS.length; i++) {
const curButton = this.insights[INSIGHTS_FIELDS_AND_LABELS[i][0]]; const curButton = this.insights[INSIGHTS_FIELDS_AND_LABELS[i][0]];
for(let listIndex = 0; listIndex < curButton.length; listIndex++) { for (let listIndex = 0; listIndex < curButton.length; listIndex++) {
for(let nodeIndex = 0; nodeIndex < curButton[listIndex].length; nodeIndex++){ for (let nodeIndex = 0; nodeIndex < curButton[listIndex].length; nodeIndex++) {
if(curPath[1].data('label') === curButton[listIndex][nodeIndex]){ if (curPath[1].data('label') === curButton[listIndex][nodeIndex]) {
// 從 1 開始而不是從 0 開始是因為 0 的label是start字串 // 從 1 開始而不是從 0 開始是因為 0 的label是start字串
const matchResult = this.depthFirstSearchMatchTwoPaths(curPath, 1, curButton, listIndex, nodeIndex) const matchResult = this.depthFirstSearchMatchTwoPaths(curPath, 1, curButton, listIndex, nodeIndex)
if(matchResult){ if (matchResult) {
this.mapGraphPathToInsight[i] = { this.mapGraphPathToInsight[i] = {
[listIndex] : { [listIndex]: {
pathByNode: [...curPath], pathByNode: [...curPath],
pathByEdge: [...curPathByEdge], pathByEdge: [...curPathByEdge],
pathType: INSIGHTS_FIELDS_AND_LABELS[i][0], pathType: INSIGHTS_FIELDS_AND_LABELS[i][0],
@@ -180,11 +180,11 @@ export default defineStore('useMapPathStore', {
} // end second for } // end second for
} // end first for } // end first for
}, },
depthFirstSearchMatchTwoPaths(curPath, curPathIndex, curButton, listIndex, nodeIndex){ depthFirstSearchMatchTwoPaths(curPath, curPathIndex, curButton, listIndex, nodeIndex) {
if(listIndex >= curButton.length) { // 邊界條件檢查,防止超出範圍 if (listIndex >= curButton.length) { // 邊界條件檢查,防止超出範圍
return; // nodeIndex表示是當選訂了五顆按鈕之一之後清單上的第幾個path return; // nodeIndex表示是當選訂了五顆按鈕之一之後清單上的第幾個path
} }
if(nodeIndex >= curButton[listIndex]) { // 邊界條件檢查,防止超出範圍 if (nodeIndex >= curButton[listIndex]) { // 邊界條件檢查,防止超出範圍
return; // 表示清單上這個path上的第幾個節點 return; // 表示清單上這個path上的第幾個節點
} }
// 如果 `curPath` 和 `curButton[listIndex]` 完全匹配 // 如果 `curPath` 和 `curButton[listIndex]` 完全匹配
@@ -200,22 +200,22 @@ export default defineStore('useMapPathStore', {
const nodeLabel = curPath[curPathIndex].data('label'); const nodeLabel = curPath[curPathIndex].data('label');
// 如果當前節點匹配 // 如果當前節點匹配
if (nodeLabel === curButton[listIndex][nodeIndex]) { if (nodeLabel === curButton[listIndex][nodeIndex]) {
if(nodeIndex === curButton[listIndex].length - 1) { if (nodeIndex === curButton[listIndex].length - 1) {
return true; // Reach return true; // Reach
} }
//從以下兩個選項選出答案可能是true的。但也可能答案都是false //從以下兩個選項選出答案可能是true的。但也可能答案都是false
// 選項一是遞增insights的第一層的指標。這裡必須遞增path的指標 // 選項一是遞增insights的第一層的指標。這裡必須遞增path的指標
if(this.depthFirstSearchMatchTwoPaths(curPath, curPathIndex + 1, curButton, listIndex + 1, nodeIndex)) { if (this.depthFirstSearchMatchTwoPaths(curPath, curPathIndex + 1, curButton, listIndex + 1, nodeIndex)) {
return true; return true;
} }
// 選項二是遞增insights的第一層的指標。這裡必須遞增path的指標 // 選項二是遞增insights的第一層的指標。這裡必須遞增path的指標
if(this.depthFirstSearchMatchTwoPaths(curPath, curPathIndex + 1, curButton, listIndex, nodeIndex + 1)) { if (this.depthFirstSearchMatchTwoPaths(curPath, curPathIndex + 1, curButton, listIndex, nodeIndex + 1)) {
return true; return true;
} }
} }
return false; // 當前節點不匹配時返回 false return false; // 當前節點不匹配時返回 false
}, },
highlightClickedPath(clickedActiveTraceIndex: number, clickedPathListIndex: number){ highlightClickedPath(clickedActiveTraceIndex: number, clickedPathListIndex: number) {
this.insightWithPath[INSIGHTS_FIELDS_AND_LABELS[clickedActiveTraceIndex][0]][clickedPathListIndex].edges.forEach(edgeToHighlight => { this.insightWithPath[INSIGHTS_FIELDS_AND_LABELS[clickedActiveTraceIndex][0]][clickedPathListIndex].edges.forEach(edgeToHighlight => {
edgeToHighlight.addClass('highlight-edge'); edgeToHighlight.addClass('highlight-edge');
}); });
@@ -265,7 +265,6 @@ export default defineStore('useMapPathStore', {
clickedEdge.addClass('highlight-edge'); clickedEdge.addClass('highlight-edge');
}, },
async highlightMostFrequentPath() { async highlightMostFrequentPath() {
console.log('highlightMostFrequentPath', this.insightWithPath['most_freq_traces'][0]);
const LIST_INDEX = 0; const LIST_INDEX = 0;
this.insightWithPath['most_freq_traces'][LIST_INDEX].nodes.map(nodeToHighlight => { this.insightWithPath['most_freq_traces'][LIST_INDEX].nodes.map(nodeToHighlight => {
nodeToHighlight.data('nodeImageUrl', ImgCapsulesGlow[nodeToHighlight.data('level')]); nodeToHighlight.data('nodeImageUrl', ImgCapsulesGlow[nodeToHighlight.data('level')]);
@@ -274,17 +273,17 @@ export default defineStore('useMapPathStore', {
edgeToHighlight.addClass('highlight-edge')); edgeToHighlight.addClass('highlight-edge'));
}, },
async highlightMostFrequentPathUnused() { async highlightMostFrequentPathUnused() {
for(let buttonIter = 0; buttonIter < INSIGHTS_FIELDS_AND_LABELS.length; buttonIter++) { for (let buttonIter = 0; buttonIter < INSIGHTS_FIELDS_AND_LABELS.length; buttonIter++) {
// 有可能遇到兩個以上的most frequent paths然而我們只取一個path點亮 // 有可能遇到兩個以上的most frequent paths然而我們只取一個path點亮
if (this.mapGraphPathToInsight[buttonIter]) { if (this.mapGraphPathToInsight[buttonIter]) {
const keyLength = Object.keys(this.mapGraphPathToInsight[buttonIter]).length; const keyLength = Object.keys(this.mapGraphPathToInsight[buttonIter]).length;
for(let i = 0; i < keyLength; i++) { for (let i = 0; i < keyLength; i++) {
if(this.mapGraphPathToInsight[buttonIter][i]?.pathType === 'most_freq_traces'){ if (this.mapGraphPathToInsight[buttonIter][i]?.pathType === 'most_freq_traces') {
await this.mapGraphPathToInsight[buttonIter][i].pathByNode.map(async(nodeToHighlight) => { await this.mapGraphPathToInsight[buttonIter][i].pathByNode.map(async (nodeToHighlight) => {
await nodeToHighlight.data('nodeImageUrl', ImgCapsulesGlow[nodeToHighlight.data('level')]); await nodeToHighlight.data('nodeImageUrl', ImgCapsulesGlow[nodeToHighlight.data('level')]);
}); });
await this.mapGraphPathToInsight[buttonIter][i].pathByEdge.map( await this.mapGraphPathToInsight[buttonIter][i].pathByEdge.map(
async(edgeToHighlight) => { async (edgeToHighlight) => {
await edgeToHighlight.addClass('highlight-edge'); await edgeToHighlight.addClass('highlight-edge');
}); });
return; // 之所以要此時就立刻return是因為要避免第二個以上的most freq path也被點亮 return; // 之所以要此時就立刻return是因為要避免第二個以上的most freq path也被點亮

View File

@@ -1,22 +1,29 @@
<template> <template>
<!-- Sidebar: Switch data type --> <!-- Sidebar: Switch data type -->
<div class="flex flex-col justify-between py-4 w-14 h-screen-main absolute bottom-0 left-0 z-10" :class="sidebarLeftValue? 'bg-neutral-50':''"> <div class="flex flex-col justify-between py-4 w-14 h-screen-main absolute bottom-0 left-0 z-10"
:class="sidebarLeftValue ? 'bg-neutral-50' : ''">
<ul class="space-y-4 flex flex-col justify-center items-center"> <ul class="space-y-4 flex flex-col justify-center items-center">
<li class="inline-flex items-center justify-center border border-neutral-500 rounded-full w-9 h-9 cursor-pointer bg-neutral-50 drop-shadow <li class="inline-flex items-center justify-center border border-neutral-500 rounded-full w-9 h-9 cursor-pointer bg-neutral-50 drop-shadow
hover:border-primary" @click="sidebarView = !sidebarView" :class="{'border-primary': sidebarView}" v-tooltip="tooltip.sidebarView"> hover:border-primary" @click="sidebarView = !sidebarView" :class="{ 'border-primary': sidebarView }"
<span class="material-symbols-outlined !text-2xl hover:text-primary p-1.5" :class="[sidebarView ? 'text-primary' : 'text-neutral-500']"> v-tooltip="tooltip.sidebarView">
<span class="material-symbols-outlined !text-2xl hover:text-primary p-1.5"
:class="[sidebarView ? 'text-primary' : 'text-neutral-500']">
track_changes track_changes
</span> </span>
</li> </li>
<li class="inline-flex items-center justify-center border border-neutral-500 rounded-full w-9 h-9 cursor-pointer bg-neutral-50 drop-shadow <li class="inline-flex items-center justify-center border border-neutral-500 rounded-full w-9 h-9 cursor-pointer bg-neutral-50 drop-shadow
hover:border-primary" @click="sidebarFilter = !sidebarFilter" :class="{'border-primary': sidebarFilter}" v-tooltip="tooltip.sidebarFilter"> hover:border-primary" @click="sidebarFilter = !sidebarFilter" :class="{ 'border-primary': sidebarFilter }"
<span class="material-symbols-outlined !text-2xl hover:text-primary p-1.5" :class="[sidebarFilter ? 'text-primary' : 'text-neutral-500']" id="iconFilter"> v-tooltip="tooltip.sidebarFilter">
<span class="material-symbols-outlined !text-2xl hover:text-primary p-1.5"
:class="[sidebarFilter ? 'text-primary' : 'text-neutral-500']" id="iconFilter">
tornado tornado
</span> </span>
</li> </li>
<li class="inline-flex items-center justify-center border border-neutral-500 rounded-full w-9 h-9 cursor-pointer bg-neutral-50 <li class="inline-flex items-center justify-center border border-neutral-500 rounded-full w-9 h-9 cursor-pointer bg-neutral-50
drop-shadow hover:border-primary" @click="sidebarTraces = !sidebarTraces" :class="{'border-primary': sidebarTraces}" v-tooltip="tooltip.sidebarTraces"> drop-shadow hover:border-primary" @click="sidebarTraces = !sidebarTraces"
<span class="material-symbols-outlined !text-2xl hover:text-primary p-1.5" :class="[sidebarTraces ? 'text-primary' : 'text-neutral-500']"> :class="{ 'border-primary': sidebarTraces }" v-tooltip="tooltip.sidebarTraces">
<span class="material-symbols-outlined !text-2xl hover:text-primary p-1.5"
:class="[sidebarTraces ? 'text-primary' : 'text-neutral-500']">
rebase rebase
</span> </span>
</li> </li>
@@ -33,7 +40,7 @@
<ul class="flex flex-col justify-center items-center"> <ul class="flex flex-col justify-center items-center">
<li class="inline-flex items-center justify-center border border-neutral-500 rounded-full w-9 h-9 cursor-pointer <li class="inline-flex items-center justify-center border border-neutral-500 rounded-full w-9 h-9 cursor-pointer
bg-neutral-50 drop-shadow hover:border-primary" @click="sidebarState = !sidebarState" bg-neutral-50 drop-shadow hover:border-primary" @click="sidebarState = !sidebarState"
:class="{'border-primary': sidebarState}" id="iconState" v-tooltip.left="tooltip.sidebarState"> :class="{ 'border-primary': sidebarState }" id="iconState" v-tooltip.left="tooltip.sidebarState">
<span class="material-symbols-outlined !text-2xl text-neutral-500 hover:text-primary p-1.5" <span class="material-symbols-outlined !text-2xl text-neutral-500 hover:text-primary p-1.5"
:class="[sidebarState ? 'text-primary' : 'text-neutral-500']"> :class="[sidebarState ? 'text-primary' : 'text-neutral-500']">
info info
@@ -43,10 +50,11 @@
</div> </div>
<!-- Sidebar Model --> <!-- Sidebar Model -->
<SidebarView v-model:visible="sidebarView" @switch-map-type="switchMapType" @switch-curve-styles="switchCurveStyles" @switch-rank="switchRank" <SidebarView v-model:visible="sidebarView" @switch-map-type="switchMapType" @switch-curve-styles="switchCurveStyles"
@switch-data-layer-type="switchDataLayerType" ></SidebarView> @switch-rank="switchRank" @switch-data-layer-type="switchDataLayerType"></SidebarView>
<SidebarState v-model:visible="sidebarState" :insights="insights" :stats="stats"></SidebarState> <SidebarState v-model:visible="sidebarState" :insights="insights" :stats="stats"></SidebarState>
<SidebarTraces v-model:visible="sidebarTraces" :cases="cases" @switch-Trace-Id="switchTraceId" ref="tracesView"></SidebarTraces> <SidebarTraces v-model:visible="sidebarTraces" :cases="cases" @switch-Trace-Id="switchTraceId" ref="tracesView">
</SidebarTraces>
<SidebarFilter v-model:visible="sidebarFilter" :filterTasks="filterTasks" :filterStartToEnd="filterStartToEnd" <SidebarFilter v-model:visible="sidebarFilter" :filterTasks="filterTasks" :filterStartToEnd="filterStartToEnd"
:filterEndToStart="filterEndToStart" :filterTimeframe="filterTimeframe" :filterTrace="filterTrace" :filterEndToStart="filterEndToStart" :filterTimeframe="filterTimeframe" :filterTrace="filterTrace"
@submit-all="createCy(mapType)" @switch-Trace-Id="switchTraceId" ref="sidevarFilterRef"></SidebarFilter> @submit-all="createCy(mapType)" @switch-Trace-Id="switchTraceId" ref="sidevarFilterRef"></SidebarFilter>
@@ -113,14 +121,15 @@ export default {
}); });
return { isLoading, processMap, bpmn, stats, insights, traceId, traces, baseTraces, return {
isLoading, processMap, bpmn, stats, insights, traceId, traces, baseTraces,
baseTraceId, filterTasks, filterStartToEnd, filterEndToStart, filterTimeframe, baseTraceId, filterTasks, filterStartToEnd, filterEndToStart, filterTimeframe,
filterTrace, logId, baseLogId, createFilterId, temporaryData, isRuleData, filterTrace, logId, baseLogId, createFilterId, temporaryData, isRuleData,
ruleData, allMapDataStore, cases, postRuleData, ruleData, allMapDataStore, cases, postRuleData,
setCurrentGraphId, setCurrentGraphId,
}; };
}, },
props:['type', 'checkType', 'checkId', 'checkFileId'], // 來自 router 的 props props: ['type', 'checkType', 'checkId', 'checkFileId'], // 來自 router 的 props
components: { components: {
SidebarView, SidebarView,
SidebarState, SidebarState,
@@ -142,7 +151,7 @@ export default {
edges: [], edges: [],
}, },
cytoscapeGraph: null, cytoscapeGraph: null,
curveStyle:'unbundled-bezier', // unbundled-bezier | taxi curveStyle: 'unbundled-bezier', // unbundled-bezier | taxi
mapType: 'processMap', // processMap | bpmn mapType: 'processMap', // processMap | bpmn
mapPathStore: MapPathStore(), mapPathStore: MapPathStore(),
dataLayerType: 'freq', // freq | duration dataLayerType: 'freq', // freq | duration
@@ -154,6 +163,8 @@ export default {
sidebarTraces: false, // SideBar: Traces sidebarTraces: false, // SideBar: Traces
sidebarFilter: false, // SideBar: Filter sidebarFilter: false, // SideBar: Filter
infiniteFirstCases: null, infiniteFirstCases: null,
startNodeId: -1,
endNodeId: -1,
tooltip: { tooltip: {
sidebarView: { sidebarView: {
value: 'Visualization Setting', value: 'Visualization Setting',
@@ -186,37 +197,37 @@ export default {
} }
} }
}, },
computed:{ computed: {
sidebarLeftValue: function() { sidebarLeftValue: function () {
let result = this.sidebarView === true || this.sidebarTraces === true || this.sidebarFilter === true; let result = this.sidebarView === true || this.sidebarTraces === true || this.sidebarFilter === true;
return result; return result;
} }
}, },
watch: { watch: {
sidebarView: function(newValue) { sidebarView: function (newValue) {
if(newValue) { if (newValue) {
this.sidebarFilter = false; this.sidebarFilter = false;
this.sidebarTraces = false; this.sidebarTraces = false;
} }
}, },
sidebarFilter: function(newValue) { sidebarFilter: function (newValue) {
if(newValue) { if (newValue) {
this.sidebarView = false; this.sidebarView = false;
this.sidebarState = false; this.sidebarState = false;
this.sidebarTraces = false; this.sidebarTraces = false;
this.sidebarState = false; this.sidebarState = false;
} }
}, },
sidebarTraces: function(newValue) { sidebarTraces: function (newValue) {
if(newValue) { if (newValue) {
this.sidebarView = false; this.sidebarView = false;
this.sidebarState = false; this.sidebarState = false;
this.sidebarFilter = false; this.sidebarFilter = false;
this.sidebarState = false; this.sidebarState = false;
} }
}, },
sidebarState: function(newValue) { sidebarState: function (newValue) {
if(newValue) { if (newValue) {
this.sidebarFilter = false; this.sidebarFilter = false;
this.sidebarTraces = false; this.sidebarTraces = false;
} }
@@ -252,7 +263,7 @@ export default {
* @param {string} type freq | duration * @param {string} type freq | duration
* @param {string} option 下拉選單中的選項 * @param {string} option 下拉選單中的選項
*/ */
async switchDataLayerType(type, option){ async switchDataLayerType(type, option) {
this.dataLayerType = type; this.dataLayerType = type;
this.dataLayerOption = option; this.dataLayerOption = option;
this.createCy(this.mapType); this.createCy(this.mapType);
@@ -262,7 +273,7 @@ export default {
* @param {event} e input 傳入的事件 * @param {event} e input 傳入的事件
*/ */
async switchTraceId(e) { async switchTraceId(e) {
if(e.id == this.traceId) return; if (e.id == this.traceId) return;
// 超過 1000 筆要 loading 畫面 // 超過 1000 筆要 loading 畫面
this.isLoading = true; // 都要 loading 畫面 this.isLoading = true; // 都要 loading 畫面
this.traceId = e.id; this.traceId = e.id;
@@ -308,56 +319,62 @@ export default {
// add type of 'bpmn gateway' node // add type of 'bpmn gateway' node
case 'gateway': case 'gateway':
mapData.nodes.push({ mapData.nodes.push({
data:{ data: {
id:node.id, id: node.id,
type:node.type, type: node.type,
label:gateway[node.gateway_type], label: gateway[node.gateway_type],
height:60, height: 60,
width:60, width: 60,
backgroundColor:'#FFF', backgroundColor: '#FFF',
bordercolor:'#003366', bordercolor: '#003366',
shape:"diamond", shape: "diamond",
freq:logFreq, freq: logFreq,
duration:logDuration, duration: logDuration,
} }
}) })
break; break;
// add type of 'event' node // add type of 'event' node
case 'event': case 'event':
if(node.event_type === 'start') mapData.startId = node.id; if (node.event_type === 'start') {
else if(node.event_type === 'end') mapData.endId = node.id; mapData.startId = node.id;
this.startNodeId = node.id;
}
else if (node.event_type === 'end') {
mapData.endId = node.id;
this.endNodeId = node.id;
}
mapData.nodes.push({ mapData.nodes.push({
data:{ data: {
id:node.id, id: node.id,
type:node.type, type: node.type,
label:node.event_type, label: node.event_type,
height: 48, height: 48,
width: 48, width: 48,
backgroundColor:'#FFFFFF', backgroundColor: '#FFFFFF',
bordercolor:'#0F172A', bordercolor: '#0F172A',
textColor: '#FF3366', textColor: '#FF3366',
shape:"ellipse", shape: "ellipse",
freq:logFreq, freq: logFreq,
duration:logDuration, duration: logDuration,
} }
}); });
break; break;
// add type of 'activity' node // add type of 'activity' node
default: default:
mapData.nodes.push({ mapData.nodes.push({
data:{ data: {
id:node.id, id: node.id,
type:node.type, type: node.type,
label:node.label, label: node.label,
height: 48, height: 48,
width: 216, width: 216,
textColor: '#0F172A', textColor: '#0F172A',
backgroundColor:'rgba(0, 0, 0, 0)', backgroundColor: 'rgba(0, 0, 0, 0)',
borderradius: 999, borderradius: 999,
shape:"round-rectangle", shape: "round-rectangle",
freq:node.freq, freq: node.freq,
duration:node.duration, duration: node.duration,
backgroundOpacity: 0, backgroundOpacity: 0,
borderOpacity: 0, borderOpacity: 0,
} }
@@ -387,12 +404,13 @@ export default {
this[mapType].edges.map(edge => { this[mapType].edges.map(edge => {
mapData.edges.push({ mapData.edges.push({
data: { data: {
source:edge.tail, source: edge.tail,
target:edge.head, target: edge.head,
freq:edge.freq, freq: edge.freq,
duration:edge.duration === null ? logDuration : edge.duration, duration: edge.duration === null ? logDuration : edge.duration,
style:'dotted', // Don't know why but tail is related to start and head is related to end
lineWidth:1, edgeStyle: edge.tail === this.startNodeId || edge.head === this.endNodeId ? 'dotted' : 'solid',
lineWidth: 1,
}, },
}); });
}); });
@@ -403,9 +421,9 @@ export default {
*/ */
async createCy(type) { async createCy(type) {
let graphId = document.getElementById('cy'); let graphId = document.getElementById('cy');
let mapData = type === 'processMap'? this.processMapData: this.bpmnData; let mapData = type === 'processMap' ? this.processMapData : this.bpmnData;
if(this[type].vertices.length !== 0){ if (this[type].vertices.length !== 0) {
this.setNodesData(mapData); this.setNodesData(mapData);
this.setEdgesData(mapData); this.setEdgesData(mapData);
this.setActivityBgImage(mapData); this.setActivityBgImage(mapData);
@@ -429,12 +447,12 @@ export default {
activityNodeArray.map(node => nodeOptionArr.push(node.data[this.dataLayerType][this.dataLayerOption])); activityNodeArray.map(node => nodeOptionArr.push(node.data[this.dataLayerType][this.dataLayerOption]));
// 將node的option值從小到大排序(映對色階淺到深) // 將node的option值從小到大排序(映對色階淺到深)
nodeOptionArr = nodeOptionArr.sort((a, b) => a - b); nodeOptionArr = nodeOptionArr.sort((a, b) => a - b);
for(let i = 0; i < ImgCapsules.length; i++) { for (let i = 0; i < ImgCapsules.length; i++) {
const startIdx = i * groupSize; const startIdx = i * groupSize;
const endIdx = (i === ImgCapsules.length - 1) ? activityNodeArray.length : startIdx + groupSize; const endIdx = (i === ImgCapsules.length - 1) ? activityNodeArray.length : startIdx + groupSize;
leveledGroups.push(nodeOptionArr.slice(startIdx, endIdx)); leveledGroups.push(nodeOptionArr.slice(startIdx, endIdx));
} }
for(let level = 0; level < leveledGroups.length; level++) { for (let level = 0; level < leveledGroups.length; level++) {
leveledGroups[level].map(option => { leveledGroups[level].map(option => {
// 考慮可能有名次一樣的情形 // 考慮可能有名次一樣的情形
const curNodes = activityNodeArray.filter(activityNode => activityNode.data[this.dataLayerType][this.dataLayerOption] === option); const curNodes = activityNodeArray.filter(activityNode => activityNode.data[this.dataLayerType][this.dataLayerOption] === option);
@@ -459,7 +477,7 @@ export default {
// Log 檔前往 Map Log 頁, Filter 檔前往 Map Filter 頁 // Log 檔前往 Map Log 頁, Filter 檔前往 Map Filter 頁
switch (routeParams.type) { switch (routeParams.type) {
case 'log': case 'log':
if(!isCheckPage) { if (!isCheckPage) {
this.logId = await routeParams.fileId; this.logId = await routeParams.fileId;
this.baseLogId = await routeParams.fileId; this.baseLogId = await routeParams.fileId;
} else { } else {
@@ -468,7 +486,7 @@ export default {
} }
break; break;
case 'filter': case 'filter':
if(!isCheckPage) { if (!isCheckPage) {
this.createFilterId = await routeParams.fileId; this.createFilterId = await routeParams.fileId;
} else { } else {
this.createFilterId = await file.parent.id; this.createFilterId = await file.parent.id;