Discover: node and edge color set done

This commit is contained in:
chiayin
2023-02-24 18:52:31 +08:00
parent e7711d39b8
commit 88aaf85922
3 changed files with 307 additions and 14 deletions

11
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View File

@@ -1,7 +1,48 @@
<template>
<h1>H1底家啦~~這裡是 Discovery 動物頻道耶~</h1>
<div>
<!-- 選擇 bpmn / processmap button -->
<ul class="m-3 flex">
<li class="rounded-xl p-2 mr-3 cursor-pointer border border-[#F0EFF4] hover:bg-primary" @click="mapType = 'processMap'">Process map</li>
<li class="rounded-xl p-2 cursor-pointer border border-[#F0EFF4] hover:bg-primary" @click="mapType = 'bpmn'">BPMN model</li>
<li class="p-2">map type: {{ mapType }}</li>
</ul>
<!-- 選擇繪畫樣式 bezier / unbundled-bezier button-->
<ul class="m-3 flex">
<li class="rounded-xl p-2 mr-3 cursor-pointer border border-[#F0EFF4] hover:bg-primary" @click="curveStyle = 'bezier'">bezier</li>
<li class="rounded-xl p-2 cursor-pointer border border-[#F0EFF4] hover:bg-primary" @click="curveStyle = 'unbundled-bezier'">unbundled-bezier</li>
<li class="p-2">curve style: {{ curveStyle }}</li>
</ul>
<!-- Data Layer type -->
<ul class="m-3" @change="switchDataLayerType($event)">
<li class="flex mb-3">
<div class="mr-5">
<input type="radio" id="freq" value="freq" name="dataLayer" />
<label for="freq">Frequency</label>
</div>
<div>
<select class="border-b">
<option v-for="(freq, index) in selectFrequency" :key="index" :value="freq.value" :disabled="freq.disabled">{{ freq.label }}</option>
</select>
</div>
</li>
<li class="flex mb-3">
<div class="mr-5">
<input type="radio" id="duration" value="duration" name="dataLayer" />
<label for="duration">Duration</label>
</div>
<div>
<select class="border-b">
<option v-for="(duration, index) in selectDuration" :key="index" :value="duration.value" :disabled="duration.disabled">{{ duration.label }}</option>
</select>
</div>
</li>
<li class="mb-3">Data Layer Type: {{ dataLayerType }}</li>
<li class="mb-3">Data Layer Option: {{ dataLayerOption }}</li>
</ul>
</div>
<div class="flex min-w-full min-h-screen">
<div class="cyto" id="cyto"></div>
<div :id="mapType" v-show="mapType"></div>
</div>
</template>
@@ -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;