From 88aaf85922a8381f5fb48e72783e86cd34d00d1b Mon Sep 17 00:00:00 2001 From: chiayin Date: Fri, 24 Feb 2023 18:52:31 +0800 Subject: [PATCH] Discover: node and edge color set done --- package-lock.json | 11 ++ package.json | 1 + src/views/Discover/index.vue | 309 +++++++++++++++++++++++++++++++++-- 3 files changed, 307 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9c417a9..e8aaa8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "axios": "^1.2.2", "cytoscape": "^3.23.0", "cytoscape-klay": "^3.1.4", + "javascript-color-gradient": "^2.4.4", "mitt": "^3.0.0", "moment": "^2.29.4", "pinia": "^2.0.28", @@ -3674,6 +3675,11 @@ "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", "dev": true }, + "node_modules/javascript-color-gradient": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/javascript-color-gradient/-/javascript-color-gradient-2.4.4.tgz", + "integrity": "sha512-kbt3Y1eltW4X789P1eQzCUEB5X7hFmLNpQT4bgDFMtj/cW2v+j8ms/rLSR7jndZUAP6yya4vyeZK6bRqh6yClA==" + }, "node_modules/jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", @@ -8932,6 +8938,11 @@ "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", "dev": true }, + "javascript-color-gradient": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/javascript-color-gradient/-/javascript-color-gradient-2.4.4.tgz", + "integrity": "sha512-kbt3Y1eltW4X789P1eQzCUEB5X7hFmLNpQT4bgDFMtj/cW2v+j8ms/rLSR7jndZUAP6yya4vyeZK6bRqh6yClA==" + }, "jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", diff --git a/package.json b/package.json index 56dd1d0..b2ea4a5 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "axios": "^1.2.2", "cytoscape": "^3.23.0", "cytoscape-klay": "^3.1.4", + "javascript-color-gradient": "^2.4.4", "mitt": "^3.0.0", "moment": "^2.29.4", "pinia": "^2.0.28", diff --git a/src/views/Discover/index.vue b/src/views/Discover/index.vue index b5c4436..fe43d46 100644 --- a/src/views/Discover/index.vue +++ b/src/views/Discover/index.vue @@ -1,7 +1,48 @@ @@ -9,6 +50,7 @@ import { storeToRefs } from 'pinia'; import LoadingStore from '@/stores/loading.js'; import AllMapDataStore from '@/stores/allMapData.js'; +import Gradient from 'javascript-color-gradient'; // 多個色階產生器 export default { setup() { @@ -19,7 +61,9 @@ export default { return { isLoading, - processMap, bpmn, allMapDataStore, + processMap, + bpmn, + allMapDataStore, } }, data() { @@ -30,22 +74,97 @@ export default { nodes: [], edges: [], }, + bpmnData: { + startId: 0, + endId: 1, + nodes: [], + edges: [], + }, + selectFrequency: [ + { value:"total", label:"Total", disabled:false, }, + { value:"rel_freq", label:"Relative", disabled:false, }, + { value:"average", label:"Average", disabled:false, }, + { value:"median", label:"Median", disabled:false, }, + { value:"max", label:"Max", disabled:false, }, + { value:"min", label:"Min", disabled:false, }, + { value:"cases", label:"Number of cases", disabled:false, }, + ], + selectDuration:[ + { value:"total", label:"Total", disabled:false, }, + { value:"rel_duration", label:"Relative", disabled:false, }, + { value:"average", label:"Average", disabled:false, }, + { value:"median", label:"Median", disabled:false, }, + { value:"max", label:"Max", disabled:false, }, + { value:"min", label:"Min", disabled:false, }, + ], curveStyle:'unbundled-bezier', // 貝茲曲線 bezier | 直角 unbundled-bezier + mapType: 'processMap', // processMap | bpmn + dataLayerType: 'freq', // freq | duration + dataLayerOption: 'total' } }, methods: { + switchDataLayerType(e){ + if(e.target.type === 'radio') this.dataLayerType = e.target.value; + else if(e.target.type === 'select-one') this.dataLayerOption = e.target.value; + }, /** - * Switch curve-style is 'bezier' or 'unbundled-bezier'. - * @param {string} style 貝茲曲線 bezier | 直角 unbundled-bezier + * 設定每個 node, edges */ - switchCurveStyle(style){ - this.curveStyle = style; + createColorGradient() { + let gradientArray = []; + let activityArray = []; + let edgeArray = []; + let nodeOption = []; + let edgeOption = []; + let nodes = this.processMapData.nodes; + let edges = this.processMapData.edges; + + // 設定除了 start, end 的 node 顏色 + // 找出 type activity's node + activityArray = nodes.filter(i => i.data.type === 'activity'); + + // 找出除了 start, end 以外所有的 node 的 option value + activityArray.map(node => nodeOption.push(node.data[this.dataLayerType][this.dataLayerOption])); + + // 刪掉重複的元素,小到大排序(映對色階淺到深) + nodeOption = [...new Set(nodeOption)].sort((a, b) => a - b); + + // 產生 node 色階 + gradientArray = new Gradient() + .setColorGradient("#CCE5FF", "#66b2ff") + .setMidpoint(nodeOption.length) + .getColors(); + + // 設定每個 node 的背景色 + activityArray.forEach(node => { + // 透過 index 找對應位置 + let gradientIndex = nodeOption.indexOf(node.data[this.dataLayerType][this.dataLayerOption]); + node.data.backgroundColor = gradientArray[gradientIndex]; + }) + + // 設定除了 start, end 的 edges 粗細 + // 找出除了 start, end 以外所有的 edge + edgeArray = edges.filter(i => i.data.source !== this.processMapData.startId && i.data.target !== this.processMapData.endId); + + // 找出所有 edge 的 option value + edgeArray.map(edge => edgeOption.push(edge.data[this.dataLayerType][this.dataLayerOption])); + + // 刪掉重複的元素,小到大排序(映對色階淺到深) + edgeOption = [...new Set(edgeOption)].sort((a, b) => a - b); + + // 設定每個 edge 的粗細 + edgeArray.forEach(edge => { + let edgeIndex = edgeOption.indexOf(edge.data[this.dataLayerType][this.dataLayerOption]) + edge.data.lineWidth = (parseInt(edgeIndex) + 1) * 0.15 + edge.data.style = 'solid' + }) }, /** * 將 element nodes 資料彙整 * @param {string} type ProcessMap | BPMN */ - setNodesData(type){ + setNodesData(type) { const logFreq = { "total": "", "rel_freq": "", @@ -73,6 +192,23 @@ export default { // 將 api call 回來的資料帶進 node this.processMap.vertices.forEach(node => { switch (node.type) { + // add type of 'bpmn gateway' node + case 'gateway': + this.processMapData.nodes.push({ + data:{ + id:node.id, + type:node.type, + label:gateway[node.gateway_type], + height:60, + width:60, + backgroundColor:'#FFF', + bordercolor:'#003366', + shape:"diamond", + freq:logFreq, + duration:logDuration, + } + }) + break; // add type of 'event' node case 'event': if(node.event_type === 'start') this.processMapData.startId = node.id; @@ -131,8 +267,8 @@ export default { this.processMap.edges.forEach(edge => { this.processMapData.edges.push({ data: { - target:edge.head, source:edge.tail, + target:edge.head, freq:edge.freq, duration:edge.duration === null ? logDuration : edge.duration, style:'dotted', @@ -141,6 +277,19 @@ export default { }); }); }, + /** + * 繪圖 + */ + // drawMap(){ + // if(this.mapType === 'processMap' && this.processMap.vertices.length !== 0){ + // let graphId = this.mapType; + // ProcessMap(this.ProcessMap.nodes,this.ProcessMap.edges,ProcessMapID,this.Optval.value,this.ProcessMap.start_end,this.Optval.key,this.Curvetype) + // } + // else if(this.mapType === 'bpmn' && this.bpmn.vertices.length !== 0){ + // let graphId = this.mapType; + // ProcessMap(this.BPMN.nodes,this.BPMN.edges,BPMNID,this.Optval.value,this.BPMN.start_end,this.Optval.key,this.Curvetype) + // } + // }, /** * create and setting cytoscape */ @@ -151,18 +300,149 @@ export default { let cy = this.$cytoscape({ container: graphId, - // elements: { - // nodes: nodes, // 節點的資料 - // edges: edges, // 關係線的資料 - // }, + elements: { + nodes: this.processMapData.nodes, //nodes, // 節點的資料 + edges: this.processMapData.edges, //edges, // 關係線的資料 + }, layout: { name: 'klay', rankDir: 'LR' // 直向 TB | 橫向 LR }, style: [ + // 點擊 node 後改變的樣式 + { + selector:'node:selected', + style:{ + 'border-color':'red', + 'border-width':'3', + }, + }, + // node 節點的樣式 { selector: 'node', - } + style: { + 'label': // 節點要顯示的文字 + function(node) { + let text = ''; + + // 文字超過 10 字尾巴要加「...」,style 要空一行(\n 換行符號) + text = node.data.label.length > 10 ? `${node.data.label.substr(0,10)}...\n\n` : `${node.data("label")}`; + + switch (node.data.type) { + case 'activity': + + break; + + default: + break; + } + if(node.data.type === 'activity'){ + if(key == "duration"){ //Duration + if(value == "rel_duration"){ // % + text = text + (parseFloat(node.data(key+"."+value))*100).toFixed(2) + "%" + } + else{ //Timelabel + if(parseInt(node.data(key+"."+value))==node.data(key+"."+value)){ //Int + text = text + TimeLabel(node.data(key+"."+value)) + } + else{//Float + text = text + TimeLabel(node.data(key+"."+value).toFixed(2)) + } + } + } + else{ // Freq + if(parseInt(node.data(key+"."+value))==node.data(key+"."+value)){ //Int + if(value == "rel_freq"){ + text = text + node.data(key+"."+value)*100 + "%" + } + else{ + text = text + node.data(key+"."+value) + } + } + else{ //Float + if(value == "rel_freq"){ + text = text + (parseFloat(node.data(key+"."+value))*100).toFixed(2) + "%" + } + else{ + text = text + parseFloat(node.data(key+"."+value)).toFixed(2) + } + } + } + } + return text + }, + 'text-opacity':0.7, + 'background-color': 'data(backgroundColor)', + 'border-color':'data(bordercolor)', + 'border-width': function(node) { + if(node.data("type") == "activity"){ + return "1" + } + return "2" + }, + //'border-radius': '5', + 'shape':'data(shape)', + 'text-wrap': 'wrap', + 'text-max-width':75, + 'text-halign': 'center', + 'text-valign': 'center', + 'height': 'data(height)', + 'width': 'data(width)', + 'color': '#001933', + 'font-size':function(node) { + if(node.data("type") == "activity"){ + return 14 + } + return 22 + }, + }, + }, + // edge 關係線的樣式 + { + selector: 'edge', + style: { + 'content': function(edge) { + if(edge.data(key+"."+value) == ""){ + return edge.data(key+"."+value) + } + if(key == "duration"){ //Duration + if(value == "rel_duration"){ // % + return (parseFloat(edge.data(key+"."+value))*100).toFixed(2) + "%" + } + else{ //Timelabel + if(parseInt(edge.data(key+"."+value))==edge.data(key+"."+value)){ //Int + return TimeLabel(edge.data(key+"."+value)) + } + else{//Float + return TimeLabel(edge.data(key+"."+value).toFixed(2)) + } + } + } + else{ // Freq + if(parseInt(edge.data(key+"."+value))==edge.data(key+"."+value)){ + if(value == "rel_freq"){ + return edge.data(key+"."+value)*100 + "%" + } + return edge.data(key+"."+value) + } + else{ + if(value == "rel_freq"){ + return (parseFloat(edge.data(key+"."+value))*100).toFixed(2) + "%" + } + return parseFloat(edge.data(key+"."+value)).toFixed(2) + } + } + }, + 'curve-style': 'unbundled-bezier', // 貝茲曲線 bezier | 直角 unbundled-bezier + 'target-arrow-shape': 'triangle', // 指向目標的箭頭形狀: 三角形 + 'color': 'gray', //#0066cc + //'control-point-step-size':100, // 從點到點的垂直線,指定貝茲取線邊緣間的距離 + 'width':'data(lineWidth)', + 'line-style':'data(style)', + "text-margin-y": "15rem", + //"text-rotation": "autorotate", + } + }, ], }) @@ -172,8 +452,9 @@ export default { await this.allMapDataStore.getAllMapData(); this.setNodesData(); this.setEdgesData(); + // this.drawMap(); + this.createColorGradient(); } - }, created() { this.allMapDataStore.logId = this.$route.params.logId;