├── .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 | 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 | 6 | 7 | 10 | 11 | 25 | -------------------------------------------------------------------------------- /frontend/src/components/MetircDetail.vue: -------------------------------------------------------------------------------- 1 | 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 | 365 | 366 | 776 | 777 | 797 | -------------------------------------------------------------------------------- /frontend/src/views/GeneralView.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 47 | 48 | 85 | -------------------------------------------------------------------------------- /frontend/src/views/InfoGridView.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 138 | 139 | 176 | -------------------------------------------------------------------------------- /frontend/src/views/MetricsView.vue: -------------------------------------------------------------------------------- 1 | 91 | 92 | 304 | 305 | 306 | -------------------------------------------------------------------------------- /frontend/src/views/RunsSimilarityView.vue: -------------------------------------------------------------------------------- 1 | 45 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /frontend/src/views/SequentialView.vue: -------------------------------------------------------------------------------- 1 | 171 | 172 | 406 | 407 | 524 | -------------------------------------------------------------------------------- /frontend/src/views/SolutionsView.vue: -------------------------------------------------------------------------------- 1 | 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 | --------------------------------------------------------------------------------