├── .gitattributes
├── .gitignore
├── README.md
├── backend
├── README.md
├── requirements.txt
└── src
│ ├── graph.py
│ ├── main.py
│ ├── sampler.py
│ └── utils
│ └── sampling.py
└── frontend
├── .eslintrc.cjs
├── README.md
├── index.html
├── package.json
├── public
└── favicon.ico
├── src
├── App.vue
├── assets
│ ├── global.css
│ └── logo.svg
├── components
│ ├── DiyCard.vue
│ └── MetircDetail.vue
├── main.js
├── utils
│ ├── ColorStyle.js
│ ├── Constants.js
│ ├── DataProcess.js
│ └── Network.js
└── views
│ ├── CorrelationView.vue
│ ├── GeneralView.vue
│ ├── InfoGridView.vue
│ ├── MetricsView.vue
│ ├── RunsSimilarityView.vue
│ ├── SequentialView.vue
│ └── SolutionsView.vue
├── vite.config.js
└── yarn.lock
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.js text eol=lf
2 | *.vue text eol=lf
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 |
106 | # backend
107 | backend/data/*
108 | **/__pycache__
109 |
110 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # A Comparative Visual Analytics Framework for Evaluating Evolutionary Processes in Multi-objective Optimization
2 |
3 | This is the code repository for the IEEE VIS 2023 paper "A Comparative Visual Analytics Framework for Evaluating Evolutionary Processes in Multi-objective Optimization", an integrated visualization solution for analyzing evolutionary processes with comparative visual analytics methods.
4 |
5 | Paper link: [`Arxiv Link`](https://arxiv.org/abs/2308.05640)
6 |
7 | ## Timeline
8 |
9 | - **2023.10.10**: Code for the framework has been released.
10 | - **2023.07.15**: The paper was accepted by IEEE VIS 2023.
11 |
12 | ## Introduction
13 |
14 | Evolutionary multi-objective optimization (EMO) algorithms have been demonstrated to be effective in solving multi-criteria decision-making problems. In real-world applications, analysts often employ several algorithms concurrently and compare their solution sets to gain insight into the characteristics of different algorithms and explore a broader range of feasible solutions. However, EMO algorithms are typically treated as black boxes, leading to difficulties in performing detailed analysis and comparisons between the internal evolutionary processes. Inspired by the successful application of visual analytics tools in explainable AI, we argue that interactive visualization can significantly enhance the comparative analysis between multiple EMO algorithms. In this paper, we present a visual analytics framework that enables the exploration and comparison of evolutionary processes in EMO algorithms. Guided by a literature review and expert interviews, the proposed framework addresses various analytical tasks and establishes a multi-faceted visualization design to support the comparative analysis of intermediate generations in the evolution as well as solution sets. We demonstrate the effectiveness of our framework through case studies on benchmarking and real-world multi-objective optimization problems to elucidate how analysts can leverage our framework to inspect and compare diverse algorithms.
15 |
16 | ## Getting Started
17 |
18 | Please download the [`data.zip`](https://osf.io/agqvh/) and extract the files into the `./backend/data` directory.
19 |
20 | For deploying the frontend service, please refer to the [README file in `frontend`](./frontend).
21 |
22 | For deploying the backend server, please refer to the [README file in `backend`](./backend).
23 |
24 | ## Citation
25 |
26 | If you use or mention this work in your research, please consider the following BibTeX entry:
27 |
28 | ```BibTeX
29 | @article{Huang2023emo,
30 | author = {Huang, Yansong and Zhang, Zherui and Jiao, Ao and Ma, Yuxin and Cheng, Ran},
31 | journal = {IEEE Transactions on Visualization and Computer Graphics},
32 | title = {A Comparative Visual Analytics Framework for Evaluating Evolutionary Processes in Multi-Objective Optimization},
33 | url = {https://ieeexplore.ieee.org/abstract/document/10294213},
34 | year = {2024},
35 | volume = {30},
36 | number = {1},
37 | pages = {661-671},
38 | doi = {10.1109/TVCG.2023.3326921}
39 | }
40 | ```
41 |
--------------------------------------------------------------------------------
/backend/README.md:
--------------------------------------------------------------------------------
1 | # Backend
2 |
3 | The backend server for the visual analytics framework is developed with Python Flask.
4 |
5 | ## Environment Requirement
6 |
7 | - **Python** version `>= 3.10.0`
8 |
9 | ## Deploy and Run
10 |
11 | To start the backend, you need to perform the following steps:
12 |
13 | 1. Download the `data.zip` packet we have provided and extract the files into the `./data` directory.
14 | 2. Start a terminal and go to the `backend` folder directory.
15 | 3. Ensure that you have a **correct** Python environment on your computer, and then execute `pip install -r requirement.txt` in the terminal.
16 | 4. Execute `python ./src/main.py` in the terminal.
17 |
--------------------------------------------------------------------------------
/backend/requirements.txt:
--------------------------------------------------------------------------------
1 | Flask==2.3.2
2 | Flask_Cors==4.0.0
3 | hdbscan==0.8.30
4 | networkx==3.1
5 | numpy==1.25.1
6 | scikit_learn==1.3.0
7 |
--------------------------------------------------------------------------------
/backend/src/graph.py:
--------------------------------------------------------------------------------
1 | import networkx as nx
2 | import numpy as np
3 | import hdbscan
4 | from sklearn.neighbors import NearestNeighbors
5 |
6 |
7 | DataMatrix = list[list[dict]]
8 |
9 |
10 | def dict_to_matrix(data: dict) -> np.ndarray:
11 | len_data = len(data)
12 | res = np.zeros((len_data, len_data))
13 | for i, k in enumerate(data.values()):
14 | for j, v in enumerate(k.values()):
15 | res[i, j] = v
16 | return res
17 |
18 |
19 | def get_cluster(matrix) -> list[int]:
20 | clusterer = hdbscan.HDBSCAN(min_cluster_size=5, min_samples=1)
21 | clusterer.fit(matrix)
22 | return clusterer.labels_.tolist()
23 |
24 |
25 | def calc_graph(data: DataMatrix, name: list[str]) -> dict:
26 | mat_data = [[dict_to_matrix(j) for j in i] for i in data]
27 | mat_data = np.concatenate(tuple([np.concatenate(tuple(i), axis=1) for i in mat_data]), axis=0)
28 |
29 | nodes, edges = [], []
30 |
31 | for i in range(len(data)):
32 | for it in data[i][0].keys():
33 | nodes.append({ 'name': name[i], 'frame': it })
34 |
35 | old_edges = []
36 | nbrs = NearestNeighbors(n_neighbors=11).fit(mat_data)
37 | for k in range(2, 12):
38 | indices = nbrs.kneighbors(mat_data, n_neighbors=k, return_distance=False)
39 | for u in range(mat_data.shape[0]):
40 | for v in np.delete(indices[u], np.where(indices[u] == u)):
41 | v = int(v)
42 | if (v, u) not in old_edges:
43 | edges.append([v, u, k-1])
44 | old_edges.append((v, u))
45 |
46 | graph = nx.Graph()
47 | graph.add_nodes_from(np.arange(mat_data.shape[0]).tolist())
48 | graph.add_edges_from(old_edges)
49 | pos = nx.kamada_kawai_layout(graph)
50 | for i, p in enumerate(pos.values()):
51 | nodes[i]['pos'] = p.tolist()
52 |
53 | clusters = get_cluster(mat_data)
54 |
55 | return { 'nodes': nodes, 'edges': edges, 'clusters': clusters }
--------------------------------------------------------------------------------
/backend/src/main.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, escape, abort, request
2 | from flask_cors import CORS
3 | from urllib.parse import unquote
4 | import json, traceback
5 | import numpy as np
6 |
7 | from graph import calc_graph
8 | from sampler import sampler
9 |
10 | server = Flask(__name__)
11 | data_dir = './data'
12 | CORS(server, resources=r'/*')
13 |
14 | @server.route('/')
15 | def home():
16 | return '
EvoX VisSystem Data Server
'
17 |
18 | @server.route('/info/')
19 | def info(name: str):
20 | try:
21 | with open(f'{data_dir}/{escape(name)}/index.json') as fc:
22 | config = json.load(fc)
23 | res = {
24 | **config['info'],
25 | 'algorithms': len(config['algorithms'])
26 | }
27 | return res
28 | except Exception as e:
29 | print(e)
30 | abort(404)
31 |
32 | @server.route('/data/')
33 | def data(name: str):
34 | try:
35 | with open(f'{data_dir}/{escape(name)}/index.json') as fc:
36 | config = json.load(fc)
37 | res = { 'data' : {} }
38 | for alg, file in config['algorithms'].items():
39 | with open('%s/%s/%s/%s' % (data_dir, escape(name), config['display'], file)) as fd:
40 | res['data'][alg] = json.load(fd)
41 | with open('%s/%s/%s/%s' % (data_dir, escape(name), config['display'], config['reference'])) as fp:
42 | res['data']['PF'] = json.load(fp)
43 | with open(f'{data_dir}/{escape(name)}/runs_similarity.json') as fs:
44 | res['similarity'] = dict(json.load(fs))
45 | res['pfCluster'] = np.ones(len(res['data']['PF'])).tolist()
46 | return res
47 | except Exception as e:
48 | print(e)
49 | abort(404)
50 |
51 | @server.route('/obj_vec///')
52 | def obj_vec(name: str, algorithm: str, frame: str):
53 | try:
54 | with open(f'{data_dir}/{escape(name)}/index.json') as fc:
55 | config = json.load(fc)
56 | with open('%s/%s/%s/%s' % (data_dir, escape(name), config['origin'], config['algorithms'][escape(algorithm)])) as fd:
57 | res = json.load(fd)['result']['obj'][escape(frame)]
58 | return res
59 | except Exception as e:
60 | print(e)
61 | abort(404)
62 |
63 | @server.route('/attachment//')
64 | def attachment(name: str, algorithm: str):
65 | try:
66 | with open(f'{data_dir}/{escape(name)}/index.json') as fc:
67 | config = json.load(fc)
68 | file = config['attachments'][escape(algorithm)]
69 | with open('%s/%s/%s/%s' % (data_dir, escape(name), config['attach'], file)) as fd:
70 | res = json.load(fd)
71 | return res
72 | except Exception as e:
73 | print(e)
74 | abort(404)
75 |
76 | @server.route('/graph/', methods=['POST'])
77 | def graph(name: str):
78 | try:
79 | origin_algorithms = request.get_json()
80 | formater = lambda x: unquote(x).replace('/', '').replace('-', '')
81 | algorithms = list(map(formater, origin_algorithms))
82 | with open(f'{data_dir}/{escape(name)}/index.json') as fc:
83 | config = json.load(fc)
84 | src = []
85 | for algA in algorithms:
86 | t = []
87 | for algB in algorithms:
88 | with open('%s/%s/%s/%s' % (data_dir, escape(name), config['distance'], f'{algA}_{algB}_{escape(name)}_distance.json')) as fd:
89 | t.append(json.load(fd))
90 | src.append(t)
91 | res = calc_graph(src, origin_algorithms)
92 | return res
93 | except Exception as e:
94 | print(e)
95 | abort(404)
96 |
97 | @server.route('/sampling', methods=['POST'])
98 | def sampling():
99 | try:
100 | payload = request.get_json() #in the form of {algorithmName: iter}
101 | algoData = payload['data']
102 | problem = payload['problem']
103 | iteration = payload['iteration']
104 | sampling_rate = payload['sampling_rate']
105 | if not algoData:
106 | return {}
107 | with open(f'{data_dir}/{escape(problem)}/index.json') as fc:
108 | config = json.load(fc)
109 | originData = {}
110 | for algorithm, frame in iteration.items():
111 | with open('%s/%s/%s/%s' % (data_dir, escape(problem), config['origin'], config['algorithms'][escape(algorithm)])) as fd:
112 | originData[algorithm] = json.load(fd)['result']['obj'][escape(frame)]
113 | indices = sampler(algoData, originData, sampling_rate=sampling_rate)
114 | res = {}
115 | for algo, _ in algoData.items():
116 | res[algo] = indices[:len(algoData[algo])].tolist()
117 | indices = indices[len(algoData[algo]):]
118 | return res
119 | except Exception as e:
120 | print(e)
121 | abort(404)
122 |
123 | @server.errorhandler(404)
124 | def not_found(error):
125 | print(traceback.format_exc())
126 | return 'Required Resource not Found
', 404
127 |
128 |
129 | if __name__ == '__main__':
130 | server.run(port=5100)
131 |
--------------------------------------------------------------------------------
/backend/src/sampler.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from utils.sampling import *
3 | from sklearn.neighbors import LocalOutlierFactor
4 |
5 |
6 | def sampler(algoData, originData, sampling_rate):
7 | all_solutions_data = []
8 | all_solutions_class = []
9 | outlier = np.array([])
10 | for index, (_, data) in enumerate(originData.items()):
11 | outlier = np.concatenate(
12 | (outlier, LocalOutlierFactor().fit_predict(data)))
13 | for index, (_, data) in enumerate(algoData.items()):
14 | all_solutions_data += data
15 | all_solutions_class += [index] * len(data)
16 | outlier_indices = np.where(outlier == -1)[0]
17 | indices = np.zeros(len(all_solutions_data))
18 | if sampling_rate == 0:
19 | return indices
20 | if sampling_rate == 1:
21 | indices = np.ones(len(all_solutions_data))
22 | indices[outlier_indices] = -1
23 | return indices
24 | selected_indices = OutlierBiasedDensityBasedSampling(
25 | np.array(all_solutions_data),
26 | np.array(all_solutions_class),
27 | sampling_rate
28 | )
29 | indices[selected_indices] = 1
30 | indices[outlier_indices] = -1
31 | return indices
32 |
--------------------------------------------------------------------------------
/backend/src/utils/sampling.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from sklearn.neighbors import NearestNeighbors
3 |
4 |
5 | def OutlierBiasedDensityBasedSampling(data, category, sampling_rate):
6 | np.random.seed(0)
7 | alpha = 1
8 | beta = 1
9 | outlier_score = get_default_outlier_scores(data, category)
10 | X = np.array(data.tolist(), dtype=np.float64)
11 | n, d = X.shape
12 | m = round(n * sampling_rate)
13 | k = 50
14 | dist, _ = NearestNeighbors(n_neighbors=k + 2, p=2).fit(X).kneighbors(X)
15 | radius_of_k_neighbor = dist[:, -1]
16 | maxD = np.max(radius_of_k_neighbor)
17 | minD = np.min(radius_of_k_neighbor)
18 | for i in range(len(radius_of_k_neighbor)):
19 | radius_of_k_neighbor[i] = (
20 | (radius_of_k_neighbor[i] - minD) * 1.0 / (maxD - minD)) * 0.5 + 0.5
21 | prob = alpha * radius_of_k_neighbor + beta * outlier_score
22 | prob = prob / prob.sum()
23 | selected_indexes = np.random.choice(n, m, replace=False, p=prob)
24 | return selected_indexes
25 |
26 |
27 | def get_default_outlier_scores(data, category, k=50):
28 | X = np.array(data.tolist(), dtype=np.float64)
29 | n, d = X.shape
30 | if k + 1 > n:
31 | k = int((n - 1) / 2)
32 | _, neighbor = NearestNeighbors(n_neighbors=k + 2, p=2).fit(X).kneighbors(X)
33 | neighbor_labels = category[neighbor]
34 | outlier_score = [sum(neighbor_labels[i] != category[i])
35 | for i in range(data.shape[0])]
36 | outlier_score = np.array(outlier_score) / k
37 | return outlier_score
38 |
--------------------------------------------------------------------------------
/frontend/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | require('@rushstack/eslint-patch/modern-module-resolution')
3 |
4 | module.exports = {
5 | root: true,
6 | 'extends': [
7 | 'plugin:vue/vue3-essential',
8 | 'eslint:recommended',
9 | '@vue/eslint-config-prettier'
10 | ],
11 | parserOptions: {
12 | ecmaVersion: 'latest'
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | # Frontend
2 |
3 | The frontend service of the visual analytics framework is developed with Vue3 and d3.js in JavaScript.
4 |
5 | ## Environment Requirement
6 |
7 | - **Node.js** version `>= 18.16.0` (Maybe you can run the frontend on some Node.js versions that are out of date successfully, but we do not recommend for doing that since it may cause implicit problems.)
8 |
9 | ## Deploy and Run
10 |
11 | To start the frontend, you need to perform the following steps:
12 |
13 | 1. Start a terminal and go to the `frontend` folder directory.
14 | 2. Ensure that you have a **correct** Node.js environment on your computer, and then execute `npm install` or `yarn` (if you are using `yarn` as Node.js package manager)
15 | 3. Execute `npm run dev` or `yarn dev` in the terminal.
16 |
--------------------------------------------------------------------------------
/frontend/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | EvoX VisSystem - Interactive Visual Comparison for EMO
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "preview": "vite preview",
9 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
10 | },
11 | "dependencies": {
12 | "@fontsource/lato": "^4.5.10",
13 | "@vicons/fa": "^0.12.0",
14 | "d3": "^7.8.2",
15 | "html-to-image": "^1.11.11",
16 | "hull.js": "^1.0.3",
17 | "lodash": "^4.17.21",
18 | "vue": "^3.2.45"
19 | },
20 | "devDependencies": {
21 | "@rushstack/eslint-patch": "^1.1.4",
22 | "@types/d3": "^7.4.0",
23 | "@types/hull.js": "^1.0.2",
24 | "@vitejs/plugin-vue": "^4.0.0",
25 | "@vue/eslint-config-prettier": "^7.0.0",
26 | "eslint": "^8.22.0",
27 | "eslint-plugin-vue": "^9.3.0",
28 | "naive-ui": "^2.34.3",
29 | "prettier": "^2.7.1",
30 | "vite": "^4.0.0"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VIS-SUSTech/visual-analytics-for-emo-algorithm-comparison/84635b9d484f61e6b0eac5a390384719d28281ba/frontend/public/favicon.ico
--------------------------------------------------------------------------------
/frontend/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
11 | Statistical Overview
12 |
16 |
17 | Test Problem
18 |
19 |
20 |
28 |
29 | Change the Problem Set
30 |
31 |
32 |
33 |
34 |
35 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | Algorithm Similarity View
58 |
62 |
63 | Measure
64 |
65 |
66 |
75 |
76 | Change the Metric to Layout Algorithms
77 |
78 |
79 |
80 |
81 |
82 |
86 |
91 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | Quality Measure View
105 |
109 |
110 | Select Generation by Hovering
111 |
112 |
113 |
120 |
121 | {{ focusMode ? "Allow" : "Disallow" }} Focusing on Hovered Run
122 |
123 |
124 |
125 |
126 |
127 |
128 |
134 |
138 |
139 | {{ name }}
140 |
144 |
149 |
150 |
151 |
152 |
153 |
{{ full }}
154 |
155 | {{ info }}
156 |
157 |
158 |
159 |
160 |
161 |
165 |
166 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
183 |
184 |
185 |
186 | Generation Similarity View
187 |
191 |
192 | Curve
193 |
199 |
200 |
214 |
215 |
216 | Hid the Time-Curve of {{ name }}
217 |
218 | Show the Time-Curve of {{ name }}
219 |
220 |
221 | #Edge
222 |
223 |
224 |
225 | {{ knnOption.limit }}
226 |
227 |
228 |
229 |
230 | KNN Limit
231 |
238 |
239 |
240 | Neighbour Ring
241 |
248 |
249 |
250 |
251 |
252 | Size
253 |
254 |
255 |
263 |
264 | Select a Metric Mapping to Node Size
265 |
266 |
267 | Cluster
268 |
269 |
270 |
281 |
282 | {{ layoutMode === "cluster" ? "Enable" : "Disable" }}
283 | Cluster Display
284 |
285 |
286 |
287 |
288 |
289 |
293 |
294 |
303 |
304 |
305 |
306 |
307 |
308 |
309 | Solution Set View
310 |
314 |
315 | Scatter Sampling Rate
316 |
329 |
330 | {{ samplingRate }}
331 |
332 |
333 | Reference Set Style
334 |
340 |
341 |
349 | {{ _.capitalize(mode) }}
350 |
351 |
352 | Switch to Display in {{ _.capitalize(mode) }} Style
353 |
354 |
355 |
356 |
357 |
358 |
365 |
366 |
371 |
377 |
378 |
385 | {{ name }}
386 | Gen. Sample #{{
387 | _.indexOf(_.keys(algorithmsData[name].frames), iter) + 1
388 | }}
389 |
390 |
395 |
396 |
397 |
398 |
399 | Solutions Out of Default Viewport Detected
400 |
401 |
402 |
403 | cancelData(name)"
406 | :style="`
407 | --n-text-color: ${colors[color][1]};
408 | --n-text-color-hover: ${colors[color][0]};
409 | --n-text-color-pressed: ${colors[color][2]};
410 | --n-text-color-focus: ${colors[color][1]};
411 | `"
412 | text
413 | >
414 |
415 |
416 |
417 |
418 |
419 | Remove this Solution Set
420 |
421 |
422 |
423 |
![]()
424 |
425 |
426 |
427 |
428 |
429 |
433 | _.each(frames, (out, name) =>
434 | _.set(activeData, [name, 'out'], out)
435 | )
436 | "
437 | :data="activeData"
438 | :PF="activeProblem.PF"
439 | :reference-style="referenceStyle"
440 | :sampling-rate="samplingRate"
441 | :pf-cluster="pfCluster"
442 | :problem="selectedProblem"
443 | />
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 | Timeline View
452 |
453 |
457 |
458 | {{ METRIC_DIST.origin }} Distribution Scale
459 |
465 |
466 |
474 | {{ _.capitalize(mode) }}
475 |
476 |
477 | Use {{ _.capitalize(mode) }} Scaler for Axis X
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
828 |
829 |
906 |
--------------------------------------------------------------------------------
/frontend/src/assets/global.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: var(--font-family);
3 | font-size: var(--font-size-m);
4 | margin: 0;
5 | }
6 |
7 | h4 {
8 | font-size: var(--font-size-l);
9 | }
10 |
11 | :root {
12 | --shadow : 0 0 0.15vh rgba(0, 0, 0, 0.15), 0 0.15vh 0.30vh rgba(0, 0, 0, 0.30);
13 | --space : 0.60vh;
14 | --radius : 0.30vh;
15 | --frame-size : 14.0vh;
16 | --font-size-m : 1.05vh;
17 | --font-size-l : 1.25vh;
18 | --title-height : 3.00vh;
19 | --scrollbar-size : 0.30vh;
20 | --color-dark : #242424;
21 | --color-light : #FFFFFF;
22 | --color-gray : #D6D6D6;
23 | --color-light-gray: #F0F0F0;
24 | --font-family : "Lato", sans-serif;
25 | }
26 |
27 | ::-webkit-scrollbar {
28 | width : var(--scrollbar-size);
29 | height: var(--scrollbar-size);
30 | }
31 |
32 | ::-webkit-scrollbar-thumb {
33 | background : var(--color-gray);
34 | border-radius: calc(var(--scrollbar-size) / 2);
35 | }
36 |
37 | ::-webkit-scrollbar-thumb:hover {
38 | background: #707070;
39 | }
40 |
41 | ::-webkit-scrollbar-track {
42 | background-color: transparent;
43 | }
--------------------------------------------------------------------------------
/frontend/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/components/DiyCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
10 |
11 |
25 |
--------------------------------------------------------------------------------
/frontend/src/components/MetircDetail.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
{{ _.truncate(name, { length: 5, omission: "." }) }}
15 |
16 |
17 |
73 |
74 |
75 | Best
76 | {{ best }}
77 | (@
78 | #{{ bestSN }}
79 | ):
80 |
81 |
82 | Current
83 | {{ current }}
84 | (@
85 | #{{ currentSN }}
86 | ):
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
197 |
198 | {{ METRIC_DIST.origin }} Distribution
199 |
200 |
201 | Best Average
202 | {{ area(props.area.best)[1][0] }}
203 | (@
204 | #{{ props.area.bestSN }}
205 | ):
206 |
207 |
208 | Current Average
209 | {{ area(props.area.current)[1][0] }}
210 | (@
211 | #{{ props.area.currentSN }}
212 | ):
213 |
214 |
215 |
216 |
217 |
218 |
219 |
289 |
290 |
341 |
--------------------------------------------------------------------------------
/frontend/src/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "vue";
2 | import App from "./App.vue";
3 |
4 | import "@fontsource/lato";
5 | import "./assets/global.css";
6 |
7 | createApp(App).mount("#app");
8 |
--------------------------------------------------------------------------------
/frontend/src/utils/ColorStyle.js:
--------------------------------------------------------------------------------
1 | import _ from "lodash";
2 |
3 | export const colors = {
4 | // name: [light, normal, dark]
5 | green: ["#72CA9B", "#238551", "#165A36"],
6 | blue: ["#8ABBFF", "#2D72D2", "#184A90"],
7 | orange: ["#FBB360", "#C87619", "#77450D"],
8 | red: ["#FA999C", "#CD4246", "#8E292C"],
9 | indigo: ["#D6CCFF", "#9784D2", "#5642A6"],
10 | turquoise: ["#97F3EB", "#58B8AE", "#008075"],
11 | vermilion: ["#FFB7A5", "#D17257", "#9E2B0E"],
12 | rose: ["#FFB3D0", "#D56F90", "#A82255"],
13 | gold: ["#FFE4A0", "#D4AD5A", "#A67908"],
14 | violet: ["#E1BAE1", "#9D6D9C", "#5C255C"],
15 | forest: ["#B1ECB5", "#6AAE6A", "#1D7324"],
16 | cerulean: ["#B3CFFF", "#6F8ACB", "#1F4B99"],
17 | lime: ["#E8F9B6", "#ADC16D", "#728C23"],
18 | sepial: ["#E4CBB2", "#A38364", "#63411E"],
19 | // Inactived Color
20 | none: ["#d9d9d9", "#8c8c8c", "#434343"],
21 | };
22 |
23 | export const colorKeys = _.without(_.keys(colors), "none");
24 |
25 | export const initColorMap = () => _.fromPairs(_.map(colorKeys, (c) => [c, 0]));
26 |
--------------------------------------------------------------------------------
/frontend/src/utils/Constants.js:
--------------------------------------------------------------------------------
1 | import _ from "lodash";
2 |
3 | export const PROBLEMS = ["DDMOP2", "DTLZ2", "DTLZ3", "DTLZ7", "WFG2"];
4 | export const METRICS = {
5 | IGD: {
6 | full: "Inverted Generational Distance",
7 | info: "The IGD metric inverts the generational distance and measures the distance from any point in reference points set to the closest point in objective vector set.",
8 | optimal: _.min,
9 | },
10 | HV: {
11 | full: "Hypervolume",
12 | info: "The volume of the region in the objective space enclosed by the non-dominated solution set obtained by the algorithm and the reference point.",
13 | optimal: _.max,
14 | },
15 | MS: {
16 | full: "Maximum Spread",
17 | info: "Maximum spread (MS) measures the range of a solution set by considering the maximum extent on each objective.",
18 | optimal: _.max,
19 | },
20 | SP: {
21 | full: "Spacing",
22 | info: "Spacing (SP) reflects the variation of the distance between solutions in a set.",
23 | optimal: _.min,
24 | },
25 | };
26 |
27 | export const METRIC_ND = "VIS_ND_indices";
28 | export const METRIC_DIST = {
29 | origin: "IGD",
30 | target: "VIS_IGD_distr",
31 | bins: 50,
32 | };
33 |
34 | export const RUNS_SIM_DEFAULT = "IGD_dtw";
35 | export const RUNS_SIM_METRICS = [
36 | { label: "Euclidean (IGD)", value: "IGD_euclidean" },
37 | { label: "Euclidean (HV)", value: "HV_euclidean" },
38 | { label: "Best IGD", value: "IGD_best_frame_sinkhorn" },
39 | { label: "Best HV", value: "HV_best_frame_sinkhorn" },
40 | { label: "DTW (IGD)", value: "IGD_dtw" },
41 | { label: "DTW (HV)", value: "HV_dtw" },
42 | ];
43 |
--------------------------------------------------------------------------------
/frontend/src/utils/DataProcess.js:
--------------------------------------------------------------------------------
1 | import * as d3 from "d3";
2 | import _ from "lodash";
3 |
4 | export const getScalers = (
5 | domainX = [0, 0],
6 | domainY = [0, 0],
7 | margin = 0,
8 | viewSizeW = 0,
9 | viewSizeH
10 | ) => {
11 | if (_.isUndefined(viewSizeH)) {
12 | viewSizeH = viewSizeW;
13 | }
14 |
15 | const scaleX = d3.scaleLinear().domain(domainX).nice().range([0, 100]);
16 | const scaleY = d3.scaleLinear().domain(domainY).nice().range([0, 100]);
17 | const axisScaleX = d3
18 | .scaleLinear()
19 | .domain(scaleX.range())
20 | .range([margin, margin + viewSizeW]);
21 | const axisScaleY = d3
22 | .scaleLinear()
23 | .domain(scaleY.range())
24 | .range([margin + viewSizeH, margin]);
25 | const scaler = ([x, y]) => [scaleX(x), scaleY(y)];
26 |
27 | return [scaler, axisScaleX, axisScaleY];
28 | };
29 |
30 | export const getDirectScalers = (
31 | domainX = [0, 0],
32 | domainY = [0, 0],
33 | margin = 0,
34 | viewSizeW = 0,
35 | viewSizeH
36 | ) => {
37 | if (_.isUndefined(viewSizeH)) {
38 | viewSizeH = viewSizeW;
39 | }
40 |
41 | const scaleX = d3
42 | .scaleLinear()
43 | .domain(domainX)
44 | .range([margin, margin + viewSizeW]);
45 | const scaleY = d3
46 | .scaleLinear()
47 | .domain(domainY)
48 | .range([margin + viewSizeH, margin]);
49 |
50 | return [scaleX, scaleY];
51 | };
52 |
53 | export const getLimitedNumber = (value) => {
54 | const initStr = _.toString(value);
55 |
56 | if (_.size(initStr) <= 5) {
57 | return value;
58 | }
59 | return _.toNumber(value).toExponential(1);
60 | };
61 |
62 | export const getExponentialNumber = (value, digits) =>
63 | _.toNumber(value).toExponential(digits);
64 |
65 | export const getNaiveSelectItems = (options) =>
66 | _.map(options, (opt) => ({ label: opt, value: opt }));
67 |
68 | export const getIndexSeries = (frames) =>
69 | _.fromPairs(_.map(frames, (f, index) => [f, index + 1]));
70 |
--------------------------------------------------------------------------------
/frontend/src/utils/Network.js:
--------------------------------------------------------------------------------
1 | import _ from "lodash";
2 |
3 | const baseURL = `http://${window.location.hostname}:5100`;
4 | const encode = encodeURIComponent;
5 |
6 | export const getInfo = (name, callback, errorHandler) =>
7 | fetch(`${baseURL}/info/${encode(name)}`)
8 | .then((response) => response.json())
9 | .then((data) => {
10 | if (_.isFunction(callback)) {
11 | callback(data);
12 | }
13 | })
14 | .catch((error) => {
15 | if (_.isFunction(errorHandler)) {
16 | errorHandler(error);
17 | } else {
18 | console.error(error);
19 | }
20 | });
21 |
22 | export const getData = (name, callback, errorHandler) =>
23 | fetch(`${baseURL}/data/${encode(name)}`)
24 | .then((response) => response.json())
25 | .then((data) => {
26 | if (_.isFunction(callback)) {
27 | callback(data);
28 | }
29 | })
30 | .catch((error) => {
31 | if (_.isFunction(errorHandler)) {
32 | errorHandler(error);
33 | } else {
34 | console.error(error);
35 | }
36 | });
37 |
38 | export const getObjVector = (name, algorithm, frame, callback, errorHandler) =>
39 | fetch(
40 | `${baseURL}/obj_vec/${encode(name)}/${encode(algorithm)}/${encode(frame)}`
41 | )
42 | .then((response) => response.json())
43 | .then((data) => {
44 | if (_.isFunction(callback)) {
45 | callback(data);
46 | }
47 | })
48 | .catch((error) => {
49 | if (_.isFunction(errorHandler)) {
50 | errorHandler(error);
51 | } else {
52 | console.error(error);
53 | }
54 | });
55 |
56 | export const getAttachment = (name, algorithm, callback, errorHandler) =>
57 | fetch(`${baseURL}/attachment/${encode(name)}/${encode(algorithm)}`)
58 | .then((response) => response.json())
59 | .then((data) => {
60 | if (_.isFunction(callback)) {
61 | callback(data);
62 | }
63 | })
64 | .catch((error) => {
65 | if (_.isFunction(errorHandler)) {
66 | errorHandler(error);
67 | } else {
68 | console.error(error);
69 | }
70 | });
71 |
72 | export const getGraph = (name, payload, callback, errorHandler) =>
73 | fetch(`${baseURL}/graph/${encode(name)}`, {
74 | method: "POST",
75 | body: JSON.stringify(payload),
76 | headers: {
77 | "Content-Type": "application/json",
78 | },
79 | })
80 | .then((response) => response.json())
81 | .then((data) => {
82 | if (_.isFunction(callback)) {
83 | callback(data);
84 | }
85 | })
86 | .catch((error) => {
87 | if (_.isFunction(errorHandler)) {
88 | errorHandler(error);
89 | } else {
90 | console.error(error);
91 | }
92 | });
93 |
94 | export const getSampling = (payload, callback, errorHandler) =>
95 | fetch(`${baseURL}/sampling`, {
96 | method: "POST",
97 | body: JSON.stringify(payload),
98 | headers: {
99 | "Content-Type": "application/json",
100 | },
101 | })
102 | .then((response) => response.json())
103 | .then((data) => {
104 | if (_.isFunction(callback)) {
105 | callback(data);
106 | }
107 | })
108 | .catch((error) => {
109 | if (_.isFunction(errorHandler)) {
110 | errorHandler(error);
111 | } else {
112 | console.error(error);
113 | }
114 | });
115 |
--------------------------------------------------------------------------------
/frontend/src/views/CorrelationView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
18 |
19 | {{ props.data.nodes[hoverNodeIndex].name }}
20 |
21 | [
22 | Gen. Sample
23 |
24 | #{{
25 | SN[data.nodes[hoverNodeIndex].name][
26 | data.nodes[hoverNodeIndex].frame
27 | ]
28 | }}
29 |
30 | ]
31 |
32 |
33 | {{ props.metricName }}:
34 | {{ props.data.nodes[hoverNodeIndex].metricValue }}
35 |
36 |
37 |
38 | Cluster Index:
39 | {{ data.clusters[hoverNodeIndex] }}
40 |
41 |
42 | Outlier
43 |
44 |
45 |
46 |
47 |
48 | KNNs (K=
49 | {{ knnOption.limit }}
50 | ):
51 |
52 |
57 |
58 | {{ props.data.nodes[index].name }}
59 |
60 | [
61 | Gen. Sample
62 |
63 | #{{ SN[data.nodes[index].name][data.nodes[index].frame] }}
64 |
65 |
66 | ,
67 |
68 | Cluster Index
69 |
70 | {{ data.clusters[index] }}
71 |
72 |
73 |
74 | Outlier
75 |
76 |
77 | ]
78 |
79 |
80 |
81 |
91 |
92 |
93 | {{ hoverTimeCurve.name }}
94 |
95 | [
96 | Samples on Route:
97 |
98 | {{ _.size(hoverTimeCurve.nodes) }}
99 |
100 | ]
101 |
102 |
103 |
107 | From:
108 |
109 |
113 | To:
114 |
115 |
119 | ⭣
120 |
121 | Gen. Sample
122 | #{{ SN[hoverTimeCurve.name][frame] }}
123 | ,
124 |
125 | Cluster Index
126 |
127 | {{ data.clusters[index] }}
128 |
129 |
130 |
131 | Outlier
132 |
133 |
134 |
135 |
136 |
364 |
365 |
366 |
776 |
777 |
797 |
--------------------------------------------------------------------------------
/frontend/src/views/GeneralView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ problem }}
4 |
5 |
6 | {{ problemInfo.desc ?? "Loading..." }}
7 |
8 |
9 |
10 |
11 |
12 | Dimensions of Object
13 |
14 | {{ problemInfo.obj ?? "Loading..." }}
15 |
16 |
17 |
18 | Dimensions of Decision
19 |
20 | {{ problemInfo.dec ?? "Loading..." }}
21 |
22 |
23 |
24 | Available Algorithms
25 |
26 | {{ problemInfo.algorithms ?? "Loading..." }}
27 |
28 |
29 |
30 |
31 |
32 |
47 |
48 |
85 |
--------------------------------------------------------------------------------
/frontend/src/views/InfoGridView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
15 |
16 |
138 |
139 |
176 |
--------------------------------------------------------------------------------
/frontend/src/views/MetricsView.vue:
--------------------------------------------------------------------------------
1 |
2 |
90 |
91 |
92 |
304 |
305 |
306 |
--------------------------------------------------------------------------------
/frontend/src/views/RunsSimilarityView.vue:
--------------------------------------------------------------------------------
1 |
2 |
44 |
45 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/frontend/src/views/SequentialView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 | {{ name }}
12 |
13 |
18 |
19 |
20 | reorder(name, -1)"
29 | secondary
30 | >
31 |
32 |
33 |
34 |
35 |
36 | Move Up
37 |
38 |
39 |
40 | reorder(name, 1)"
49 | secondary
50 | >
51 |
52 |
53 |
54 |
55 |
56 | Move Down
57 |
58 |
59 |
60 |
68 |
69 |
70 | Cancel the selection of {{ name }} for comparison
71 |
72 |
73 | Select {{ name }} for calculate comparison graph
74 |
75 |
76 |
77 |
78 |
79 |
86 |
87 |
88 |
89 |
90 |
100 | onChange(name, iter, frame)"
104 | :class="['frame', { active: activeData[name]?.iter === iter }]"
105 | >
106 |
144 |
145 |
146 |
147 |
148 |
169 |
170 |
171 |
172 |
406 |
407 |
524 |
--------------------------------------------------------------------------------
/frontend/src/views/SolutionsView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
{{ hoverIndex.name }}
15 | [
16 |
19 | Inlier
20 |
21 | Outlier
22 |
23 | , Non-dominated
24 |
25 | ]
26 |
27 |
28 | Vector in Objective Space:
29 |
35 | Obj #{{ index }}
36 | : {{ value }}
37 |
38 |
39 |
40 |
221 |
222 |
223 |
548 |
549 |
564 |
--------------------------------------------------------------------------------
/frontend/vite.config.js:
--------------------------------------------------------------------------------
1 | import { fileURLToPath, URL } from "node:url";
2 |
3 | import { defineConfig } from "vite";
4 | import vue from "@vitejs/plugin-vue";
5 |
6 | // https://vitejs.dev/config/
7 | export default defineConfig({
8 | plugins: [vue()],
9 | resolve: {
10 | alias: {
11 | "@": fileURLToPath(new URL("./src", import.meta.url)),
12 | },
13 | },
14 | server: {
15 | port: 6100,
16 | },
17 | });
18 |
--------------------------------------------------------------------------------