Upload files to ''
This commit is contained in:
parent
96a9be02cb
commit
30cb2832b5
291
app.js
Normal file
291
app.js
Normal file
@ -0,0 +1,291 @@
|
||||
// Copyright (c) 2023, Sam Hadow
|
||||
//
|
||||
//app.js
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
{
|
||||
// define svg in container
|
||||
const svg = d3.select('#container')
|
||||
.append('svg')
|
||||
.attr('width', '100%')
|
||||
.attr('height', '100%')
|
||||
.attr('preserveAspectRatio', 'xMinYMin meet')
|
||||
.classed('svg-content', true);
|
||||
|
||||
// svg should fill available space
|
||||
let width = window.innerWidth;
|
||||
let height = window.innerHeight;
|
||||
// svg viewbox
|
||||
svg.attr('viewBox', `0 0 ${window.innerWidth} ${window.innerHeight}`);
|
||||
|
||||
// reload page when window is resized
|
||||
// (resizes every elements and keeps svg consistent with background map)
|
||||
window.onresize = function(){ location.reload(); }
|
||||
|
||||
|
||||
// mouseover tooltip
|
||||
// (box showing node name)
|
||||
const tooltip = d3.select('#container')
|
||||
.append("div")
|
||||
.style("position", "absolute")
|
||||
.style("visibility", "hidden")
|
||||
.text("")
|
||||
.attr("class", "tooltip")
|
||||
.style("background-color", "white")
|
||||
.style("border", "solid")
|
||||
.style("border-width", "1px")
|
||||
.style("border-radius", "5px")
|
||||
.style("padding", "10px");
|
||||
|
||||
|
||||
// parsing data
|
||||
Promise.all([
|
||||
d3.csv("data_nodes.csv"), // nodes
|
||||
d3.csv("data_edges.csv") // edges
|
||||
]).then(
|
||||
|
||||
function (initialize) {
|
||||
|
||||
let nodes = initialize[0];
|
||||
let edges = initialize[1];
|
||||
|
||||
const nodes_array = []
|
||||
nodes.forEach(function(row){
|
||||
nodes_array.push(
|
||||
{
|
||||
'id':row.id,
|
||||
'y':row.latitude*(height/1747),
|
||||
'x':row.longitude*(width/2188),
|
||||
'name':row.name,
|
||||
'type':row.type
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const edges_array = []
|
||||
edges.forEach(function(row){
|
||||
edges_array.push(
|
||||
{
|
||||
'id':row.id,
|
||||
'source':row.origin,
|
||||
'target':row.destination,
|
||||
'name':row.name,
|
||||
'type':row.type,
|
||||
'weight':parseFloat(row.weight) //we need floats not strings for additions and comparison operators
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
// handling clicks on radio buttons
|
||||
let level = 0; // by default user is a beginner
|
||||
const buttons = d3.selectAll('input');
|
||||
buttons.on('change', function(d) {level = this.value;});
|
||||
|
||||
// handling clicks on nodes
|
||||
let selectedNode = null; //initializing 1st selected node
|
||||
|
||||
function onClick(node) {
|
||||
if (selectedNode === null) {
|
||||
// on first click remember the selected node (only its id is enough)
|
||||
selectedNode = d3.select(this).attr('id');
|
||||
} else {
|
||||
// second click should trigger a path finding function
|
||||
node2 = d3.select(this).attr('id')
|
||||
path_finding(selectedNode, node2);
|
||||
|
||||
// reset 1st selected node
|
||||
selectedNode = null;
|
||||
}
|
||||
}
|
||||
|
||||
function path_finding(node1, node2) {
|
||||
|
||||
// reset color and width of every edge
|
||||
svg.selectAll(".link")
|
||||
.attr("stroke", "black")
|
||||
.attr("stroke-width", 1.5);
|
||||
|
||||
if (node1 != node2) {
|
||||
// if we need to find a path
|
||||
if (level==0) {
|
||||
// beginner
|
||||
current_path = path_to_follow(next_b, node1, node2);
|
||||
estimated_time = adjacency_matrix_beginner[node1][node2];
|
||||
} else {
|
||||
// expert
|
||||
current_path = path_to_follow(next_e, node1, node2);
|
||||
estimated_time = adjacency_matrix_expert[node1][node2];
|
||||
}
|
||||
path_string = path_to_string(current_path, edges_array, nodes_array);
|
||||
// we round estimated_time to upper integer as we don't want too many digits, it's just an estimation
|
||||
path_string += 'Le trajet devrait vous prendre ' + Math.ceil(estimated_time) + 'minutes'
|
||||
|
||||
target_name = nodes_array.find(node => node.id === node2).name;
|
||||
// if target node has a name we append it to path_string
|
||||
if (target_name != '') {
|
||||
path_string += " jusqu'à " + target_name
|
||||
}
|
||||
|
||||
// then we fill corresponding html paragraph with generated string
|
||||
document.getElementById("instructions").innerHTML = path_string
|
||||
|
||||
// highlight edges in svg
|
||||
// get id of each edge in path
|
||||
var id_list = current_path.map(function(value) { return value[1];});
|
||||
svg.selectAll(".link")
|
||||
.filter(function() {
|
||||
return id_list.includes(d3.select(this).attr("id")); // filter links by id (only those in current path)
|
||||
})
|
||||
.attr("stroke", "red")
|
||||
.attr("stroke-width", 7); // make these links wider and red
|
||||
|
||||
} else {
|
||||
// names nodes, user is already here
|
||||
document.getElementById("instructions").innerHTML = "Vous vous trouvez déjà ici."
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// generating svg
|
||||
// edges
|
||||
// Draw the links
|
||||
const link = svg.selectAll(".link")
|
||||
.data(edges_array)
|
||||
.enter().append("path")
|
||||
.attr("class", "link")
|
||||
.attr("stroke", "black")
|
||||
.attr("stroke-width", 1.5)
|
||||
.attr("fill", "none")
|
||||
.attr("id", function(d) {
|
||||
return d.id
|
||||
})
|
||||
.attr("d", function(d) {
|
||||
const x1 = nodes_array.find(node => node.id === d.source).x;
|
||||
const y1 = nodes_array.find(node => node.id === d.source).y;
|
||||
const x2 = nodes_array.find(node => node.id === d.target).x;
|
||||
const y2 = nodes_array.find(node => node.id === d.target).y;
|
||||
return `M ${x1},${y1} L ${x2},${y2}`;
|
||||
});
|
||||
|
||||
// nodes
|
||||
for (let i = 0; i < nodes_array.length; i++) {
|
||||
if (nodes_array[i].type == 1) {
|
||||
svg
|
||||
.append('circle')
|
||||
.attr('cx', nodes_array[i].x)
|
||||
.attr('cy', nodes_array[i].y)
|
||||
.attr('r', 14)
|
||||
.attr('name',nodes_array[i].name)
|
||||
.attr('id', nodes_array[i].id)
|
||||
.style('fill', 'blue')
|
||||
|
||||
//.on("click", onClick)
|
||||
|
||||
.on("mouseover", function(){return tooltip.style("visibility", "visible");})
|
||||
.on("mousemove", function(){return tooltip.style("top", (event.pageY+30)+"px").style("left",(event.pageX)+"px")
|
||||
.text("station: "+d3.select(this).attr("name"));})
|
||||
.on("mouseout", function(){return tooltip.style("visibility", "hidden");});
|
||||
} else {
|
||||
svg
|
||||
.append('circle')
|
||||
.attr('cx', nodes_array[i].x)
|
||||
.attr('cy', nodes_array[i].y)
|
||||
.attr('r', 7)
|
||||
.attr('name',nodes_array[i].name)
|
||||
.attr('id', nodes_array[i].id)
|
||||
.style('fill', 'green')
|
||||
};
|
||||
};
|
||||
// handling click on nodes
|
||||
const my_nodes = d3.selectAll('circle');
|
||||
my_nodes.on('click', onClick);
|
||||
|
||||
|
||||
//adjacency matrix
|
||||
const size = nodes_array.length;
|
||||
let adjacency_matrix_beginner = Array(size).fill().map(()=>Array(size));
|
||||
let adjacency_matrix_expert = Array(size).fill().map(()=>Array(size));
|
||||
|
||||
let next_b = Array(size).fill().map(()=>Array(size).fill().map(()=>Array(2).fill()));
|
||||
let next_e = Array(size).fill().map(()=>Array(size).fill().map(()=>Array(2).fill()));
|
||||
|
||||
for (let j = 0; j < size; j++){
|
||||
adjacency_matrix_beginner[j][j]=0;
|
||||
next_b[j][j][0]=j;
|
||||
next_b[j][j][1]=-1;
|
||||
|
||||
adjacency_matrix_expert[j][j]=0;
|
||||
next_e[j][j][0]=j;
|
||||
next_e[j][j][1]=-1;
|
||||
}
|
||||
|
||||
for (let k = 0; k < edges_array.length; k++){
|
||||
|
||||
temp_weight = edges_array[k].weight;
|
||||
|
||||
// wait time (regardless of level)
|
||||
// for lifts we'll add these numbers to the weight (in minutes) regardless of base time
|
||||
// surface lift: 10
|
||||
// chairlift: 8
|
||||
// high speed chairlift: 5
|
||||
// gondola: 7
|
||||
// cable car: 7
|
||||
// free lift: 15
|
||||
if (edges_array[k].type == "surface lift") {
|
||||
temp_weight += 10
|
||||
} else if (edges_array[k].type == "gondola") {
|
||||
temp_weight += 7
|
||||
} else if (edges_array[k].type == "cable car") {
|
||||
temp_weight += 7
|
||||
} else if (edges_array[k].type == "chairlift") {
|
||||
temp_weight += 8
|
||||
} else if (edges_array[k].type == "high speed chairlift") {
|
||||
temp_weight += 5
|
||||
} else if (edges_array[k].type == "free lift") {
|
||||
temp_weight += 15
|
||||
}
|
||||
|
||||
//expert
|
||||
if (!(adjacency_matrix_expert[edges_array[k].source][edges_array[k].target] >= temp_weight ) ) {
|
||||
adjacency_matrix_expert[edges_array[k].source][edges_array[k].target] = temp_weight;
|
||||
next_b[edges_array[k].source][edges_array[k].target][0]=edges_array[k].target;
|
||||
next_b[edges_array[k].source][edges_array[k].target][1]=edges_array[k].id;
|
||||
}
|
||||
|
||||
// beginner
|
||||
// here we need to increase the weight according to difficulty if it's a slope
|
||||
// factors:
|
||||
// very easy (green): *1.2
|
||||
// easy (blue): *2
|
||||
// difficult (red): *3
|
||||
// very difficult (black): *4
|
||||
|
||||
if (edges_array[k].type == "very easy") {
|
||||
temp_weight *= 1.2;
|
||||
} else if (edges_array[k].type == "easy") {
|
||||
temp_weight *= 2;
|
||||
} else if (edges_array[k].type == "difficult") {
|
||||
temp_weight *= 3;
|
||||
} else if (edges_array[k].type == "very difficult") {
|
||||
temp_weight *= 4;
|
||||
}
|
||||
//
|
||||
if (!(adjacency_matrix_beginner[edges_array[k].source][edges_array[k].target] >= temp_weight ) ) {
|
||||
adjacency_matrix_beginner[edges_array[k].source][edges_array[k].target] = temp_weight;
|
||||
next_e[edges_array[k].source][edges_array[k].target][0]=edges_array[k].target;
|
||||
next_e[edges_array[k].source][edges_array[k].target][1]=edges_array[k].id;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// obtains final next and adjacency matrices using Floyd Warshall algorithm
|
||||
floyd_warshall(adjacency_matrix_beginner, next_b, size)
|
||||
floyd_warshall(adjacency_matrix_expert, next_e, size)
|
||||
|
||||
})}
|
30
floyd_warshall.js
Normal file
30
floyd_warshall.js
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright (c) 2023, Sam Hadow
|
||||
//
|
||||
//floyd_warshall.js
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
function floyd_warshall(matrix, next, size) {
|
||||
// Floyd Warshall algorithm
|
||||
for (let k = 0; k < size; k++){
|
||||
for (let i = 0; i < size; i++){
|
||||
for (let j = 0; j < size; j++){
|
||||
// we need to have matrix[i][k] and matrix[k][j] both defined
|
||||
if (!((matrix[i][k] == undefined) || (matrix[k][j] == undefined)) ){
|
||||
// here we use !(a<=b) instead of a>b to handle situations where a is undefined
|
||||
// (if a is undefined a <= any_value will be false)
|
||||
if (!(matrix[i][j] <= matrix[i][k] + matrix[k][j])) {
|
||||
matrix[i][j] = matrix[i][k] + matrix[k][j];
|
||||
next[i][j][0] = next[i][k][0]
|
||||
next[i][j][1] = next[i][k][1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
36
main.js
Normal file
36
main.js
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright (c) 2023, Sam Hadow
|
||||
//
|
||||
//main.js
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
|
||||
|
||||
let mainWindow = null;
|
||||
|
||||
app.whenReady().then(() => {
|
||||
// We cannot require the screen module until the app is ready.
|
||||
const { screen } = require('electron')
|
||||
|
||||
// Create a window that fills the screen's available work area.
|
||||
const primaryDisplay = screen.getPrimaryDisplay()
|
||||
const { width, height } = primaryDisplay.workAreaSize
|
||||
|
||||
|
||||
mainWindow = new BrowserWindow({
|
||||
width,
|
||||
height,
|
||||
autoHideMenuBar: true,
|
||||
resizable: true,
|
||||
//icon: 'logo.png',
|
||||
frame: true
|
||||
})
|
||||
|
||||
|
||||
mainWindow.loadFile('index.html')
|
||||
})
|
23
path.js
Normal file
23
path.js
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2023, Sam Hadow
|
||||
//
|
||||
//path.js
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
function path_to_follow(matrix,origin,target) {
|
||||
// calculate edges to follow to reach target from origin (origin and target are nodes id)
|
||||
// matrix should be next_b or next_e
|
||||
// return a list with the id of these edges, and estimated travel time as first element.
|
||||
let current_node = origin;
|
||||
let path = []
|
||||
while (current_node != target) {
|
||||
edge = matrix[current_node][target][1]
|
||||
current_node = matrix[current_node][target][0]
|
||||
path.push([current_node, edge]);
|
||||
}
|
||||
return path
|
||||
}
|
41
path_to_string.js
Normal file
41
path_to_string.js
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2023, Sam Hadow
|
||||
//
|
||||
//path_to_string.js
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
function path_to_string(path, edges, nodes) {
|
||||
// convert path (with only edges and nodes id) to a human-readable string
|
||||
let current = null;
|
||||
let string = ''
|
||||
for (let i = 0; i < path.length; i++){
|
||||
current = edges.find(edge => edge.id === path[i][1]);
|
||||
if (current.type == "very easy" || current.type == "easy" || current.type == "difficult" || current.type == "very difficult" ){
|
||||
string += i+1 +') Descendez la piste: ' + current.name + '<br/>';
|
||||
} else if (current.type == "surface lift") {
|
||||
string += i+1 +') Prenez le téléski: ' + current.name + '<br/>';
|
||||
} else if (current.type == "gondola") {
|
||||
string += i+1 +') Prenez la télécabine: ' + current.name + '<br/>';
|
||||
} else if (current.type == "cable car") {
|
||||
string += i+1 +') Prenez le téléphérique: ' + current.name + '<br/>';
|
||||
} else if (current.type == "chairlift") {
|
||||
string += i+1 +') Prenez le télésiège: ' + current.name + '<br/>';
|
||||
} else if (current.type == "high speed chairlift") {
|
||||
string += i+1 +') Prenez le télésiège express débrayable: ' + current.name + '<br/>';
|
||||
} else if (current.type == "path") {
|
||||
direction = nodes.find(node => node.id === current.target).name;
|
||||
if (direction != '') {
|
||||
string += i+1 +') Prenez le chemin en direction de: ' + direction + '<br/>';
|
||||
} else {
|
||||
string += i+1 +') Prenez un chemin <br/>';
|
||||
}
|
||||
} else if (current.type == "free lift") {
|
||||
string += i+1 +') Prenez la remontée mécanique gratuite: ' + current.name + '<br/>';
|
||||
}
|
||||
}
|
||||
return string
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user