Skip to content
This repository was archived by the owner on Mar 14, 2026. It is now read-only.

Commit 3ba91ba

Browse files
committed
hotfix(vehicle-routing): fix webui
1 parent 04aaac6 commit 3ba91ba

File tree

2 files changed

+238
-0
lines changed

2 files changed

+238
-0
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
function analyzeScore(solution, endpointPath) {
2+
new bootstrap.Modal("#scoreAnalysisModal").show()
3+
const scoreAnalysisModalContent = $("#scoreAnalysisModalContent");
4+
scoreAnalysisModalContent.children().remove();
5+
scoreAnalysisModalContent.text("");
6+
7+
if (solution.score == null) {
8+
scoreAnalysisModalContent.text("Score not ready for analysis, try to run the solver first or wait until it advances.");
9+
} else {
10+
visualizeScoreAnalysis(scoreAnalysisModalContent, solution, endpointPath)
11+
}
12+
}
13+
14+
function visualizeScoreAnalysis(scoreAnalysisModalContent, solution, endpointPath) {
15+
$('#scoreAnalysisScoreLabel').text(`(${solution.score})`);
16+
$.put(endpointPath, JSON.stringify(solution), function (scoreAnalysis) {
17+
let constraints = scoreAnalysis.constraints;
18+
constraints.sort(compareConstraintsBySeverity);
19+
constraints.map(addDerivedScoreAttributes);
20+
scoreAnalysis.constraints = constraints;
21+
22+
const analysisTable = $(`<table class="table"/>`).css({textAlign: 'center'});
23+
const analysisTHead = $(`<thead/>`).append($(`<tr/>`)
24+
.append($(`<th></th>`))
25+
.append($(`<th>Constraint</th>`).css({textAlign: 'left'}))
26+
.append($(`<th>Type</th>`))
27+
.append($(`<th># Matches</th>`))
28+
.append($(`<th>Weight</th>`))
29+
.append($(`<th>Score</th>`))
30+
.append($(`<th></th>`)));
31+
analysisTable.append(analysisTHead);
32+
const analysisTBody = $(`<tbody/>`)
33+
$.each(scoreAnalysis.constraints, function (index, constraintAnalysis) {
34+
visualizeConstraintAnalysis(analysisTBody, index, constraintAnalysis)
35+
});
36+
analysisTable.append(analysisTBody);
37+
scoreAnalysisModalContent.append(analysisTable);
38+
}).fail(function (xhr, ajaxOptions, thrownError) {
39+
showError("Score analysis failed.", xhr);
40+
},
41+
"text");
42+
}
43+
44+
function compareConstraintsBySeverity(a, b) {
45+
let aComponents = getScoreComponents(a.score), bComponents = getScoreComponents(b.score);
46+
if (aComponents.hard < 0 && bComponents.hard > 0) return -1;
47+
if (aComponents.hard > 0 && bComponents.soft < 0) return 1;
48+
if (Math.abs(aComponents.hard) > Math.abs(bComponents.hard)) {
49+
return -1;
50+
} else {
51+
if (aComponents.medium < 0 && bComponents.medium > 0) return -1;
52+
if (aComponents.medium > 0 && bComponents.medium < 0) return 1;
53+
if (Math.abs(aComponents.medium) > Math.abs(bComponents.medium)) {
54+
return -1;
55+
} else {
56+
if (aComponents.soft < 0 && bComponents.soft > 0) return -1;
57+
if (aComponents.soft > 0 && bComponents.soft < 0) return 1;
58+
59+
return Math.abs(bComponents.soft) - Math.abs(aComponents.soft);
60+
}
61+
}
62+
}
63+
64+
function addDerivedScoreAttributes(constraint) {
65+
let components = getScoreComponents(constraint.weight);
66+
constraint.type = components.hard != 0 ? 'hard' : (components.medium != 0 ? 'medium' : 'soft');
67+
constraint.weight = components[constraint.type];
68+
let scores = getScoreComponents(constraint.score);
69+
constraint.implicitScore = scores.hard != 0 ? scores.hard : (scores.medium != 0 ? scores.medium : scores.soft);
70+
}
71+
72+
function getScoreComponents(score) {
73+
let components = {hard: 0, medium: 0, soft: 0};
74+
75+
$.each([...score.matchAll(/(-?[0-9]+)(hard|medium|soft)/g)], function (i, parts) {
76+
components[parts[2]] = parseInt(parts[1], 10);
77+
});
78+
79+
return components;
80+
}
81+
82+
function visualizeConstraintAnalysis(analysisTBody, constraintIndex, constraintAnalysis, recommendation = false, recommendationIndex = null) {
83+
let icon = constraintAnalysis.type == "hard" && constraintAnalysis.implicitScore < 0 ? '<span class="fas fa-exclamation-triangle" style="color: red"></span>' : '';
84+
if (!icon) icon = constraintAnalysis.weight < 0 && constraintAnalysis.matches.length == 0 ? '<span class="fas fa-check-circle" style="color: green"></span>' : '';
85+
86+
let row = $(`<tr/>`);
87+
row.append($(`<td/>`).html(icon))
88+
.append($(`<td/>`).text(constraintAnalysis.name).css({textAlign: 'left'}))
89+
.append($(`<td/>`).text(constraintAnalysis.type))
90+
.append($(`<td/>`).html(`<b>${constraintAnalysis.matches.length}</b>`))
91+
.append($(`<td/>`).text(constraintAnalysis.weight))
92+
.append($(`<td/>`).text(recommendation ? constraintAnalysis.score : constraintAnalysis.implicitScore));
93+
94+
analysisTBody.append(row);
95+
row.append($(`<td/>`));
96+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
function replaceSolverForgeAutoHeaderFooter() {
2+
const solverforgeHeader = $("header#solverforge-auto-header");
3+
if (solverforgeHeader != null) {
4+
solverforgeHeader.addClass("bg-black")
5+
solverforgeHeader.append(
6+
$(`<div class="container-fluid">
7+
<nav class="navbar sticky-top navbar-expand-lg navbar-dark shadow mb-3">
8+
<a class="navbar-brand" href="https://www.solverforge.org">
9+
<img src="/solverforge/img/solverforge-horizontal-white.svg" alt="SolverForge logo" width="200">
10+
</a>
11+
</nav>
12+
</div>`));
13+
}
14+
const solverforgeFooter = $("footer#solverforge-auto-footer");
15+
if (solverforgeFooter != null) {
16+
solverforgeFooter.append(
17+
$(`<footer class="bg-black text-white-50">
18+
<div class="container">
19+
<div class="hstack gap-3 p-4">
20+
<div class="ms-auto"><a class="text-white" href="https://www.solverforge.org">SolverForge</a></div>
21+
<div class="vr"></div>
22+
<div><a class="text-white" href="https://www.solverforge.org/docs">Documentation</a></div>
23+
<div class="vr"></div>
24+
<div><a class="text-white" href="https://github.com/SolverForge/solverforge-legacy">Code</a></div>
25+
<div class="vr"></div>
26+
<div class="me-auto"><a class="text-white" href="mailto:info@solverforge.org">Support</a></div>
27+
</div>
28+
</div>
29+
<div id="applicationInfo" class="container text-center"></div>
30+
</footer>`));
31+
32+
applicationInfo();
33+
}
34+
35+
}
36+
37+
function showSimpleError(title) {
38+
const notification = $(`<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" style="min-width: 50rem"/>`)
39+
.append($(`<div class="toast-header bg-danger">
40+
<strong class="me-auto text-dark">Error</strong>
41+
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
42+
</div>`))
43+
.append($(`<div class="toast-body"/>`)
44+
.append($(`<p/>`).text(title))
45+
);
46+
$("#notificationPanel").append(notification);
47+
notification.toast({delay: 30000});
48+
notification.toast('show');
49+
}
50+
51+
function showError(title, xhr) {
52+
var serverErrorMessage = !xhr.responseJSON ? `${xhr.status}: ${xhr.statusText}` : xhr.responseJSON.message;
53+
var serverErrorCode = !xhr.responseJSON ? `unknown` : xhr.responseJSON.code;
54+
var serverErrorId = !xhr.responseJSON ? `----` : xhr.responseJSON.id;
55+
var serverErrorDetails = !xhr.responseJSON ? `no details provided` : xhr.responseJSON.details;
56+
57+
if (xhr.responseJSON && !serverErrorMessage) {
58+
serverErrorMessage = JSON.stringify(xhr.responseJSON);
59+
serverErrorCode = xhr.statusText + '(' + xhr.status + ')';
60+
serverErrorId = `----`;
61+
}
62+
63+
console.error(title + "\n" + serverErrorMessage + " : " + serverErrorDetails);
64+
const notification = $(`<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" style="min-width: 50rem"/>`)
65+
.append($(`<div class="toast-header bg-danger">
66+
<strong class="me-auto text-dark">Error</strong>
67+
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
68+
</div>`))
69+
.append($(`<div class="toast-body"/>`)
70+
.append($(`<p/>`).text(title))
71+
.append($(`<pre/>`)
72+
.append($(`<code/>`).text(serverErrorMessage + "\n\nCode: " + serverErrorCode + "\nError id: " + serverErrorId))
73+
)
74+
);
75+
$("#notificationPanel").append(notification);
76+
notification.toast({delay: 30000});
77+
notification.toast('show');
78+
}
79+
80+
// ****************************************************************************
81+
// Application info
82+
// ****************************************************************************
83+
84+
function applicationInfo() {
85+
$.getJSON("info", function (info) {
86+
$("#applicationInfo").append("<small>" + info.application + " (version: " + info.version + ", built at: " + info.built + ")</small>");
87+
}).fail(function (xhr, ajaxOptions, thrownError) {
88+
console.warn("Unable to collect application information");
89+
});
90+
}
91+
92+
// ****************************************************************************
93+
// TangoColorFactory
94+
// ****************************************************************************
95+
96+
const SEQUENCE_1 = [0x8AE234, 0xFCE94F, 0x729FCF, 0xE9B96E, 0xAD7FA8];
97+
const SEQUENCE_2 = [0x73D216, 0xEDD400, 0x3465A4, 0xC17D11, 0x75507B];
98+
99+
var colorMap = new Map;
100+
var nextColorCount = 0;
101+
102+
function pickColor(object) {
103+
let color = colorMap[object];
104+
if (color !== undefined) {
105+
return color;
106+
}
107+
color = nextColor();
108+
colorMap[object] = color;
109+
return color;
110+
}
111+
112+
function nextColor() {
113+
let color;
114+
let colorIndex = nextColorCount % SEQUENCE_1.length;
115+
let shadeIndex = Math.floor(nextColorCount / SEQUENCE_1.length);
116+
if (shadeIndex === 0) {
117+
color = SEQUENCE_1[colorIndex];
118+
} else if (shadeIndex === 1) {
119+
color = SEQUENCE_2[colorIndex];
120+
} else {
121+
shadeIndex -= 3;
122+
let floorColor = SEQUENCE_2[colorIndex];
123+
let ceilColor = SEQUENCE_1[colorIndex];
124+
let base = Math.floor((shadeIndex / 2) + 1);
125+
let divisor = 2;
126+
while (base >= divisor) {
127+
divisor *= 2;
128+
}
129+
base = (base * 2) - divisor + 1;
130+
let shadePercentage = base / divisor;
131+
color = buildPercentageColor(floorColor, ceilColor, shadePercentage);
132+
}
133+
nextColorCount++;
134+
return "#" + color.toString(16);
135+
}
136+
137+
function buildPercentageColor(floorColor, ceilColor, shadePercentage) {
138+
let red = (floorColor & 0xFF0000) + Math.floor(shadePercentage * ((ceilColor & 0xFF0000) - (floorColor & 0xFF0000))) & 0xFF0000;
139+
let green = (floorColor & 0x00FF00) + Math.floor(shadePercentage * ((ceilColor & 0x00FF00) - (floorColor & 0x00FF00))) & 0x00FF00;
140+
let blue = (floorColor & 0x0000FF) + Math.floor(shadePercentage * ((ceilColor & 0x0000FF) - (floorColor & 0x0000FF))) & 0x0000FF;
141+
return red | green | blue;
142+
}

0 commit comments

Comments
 (0)