Upload files to ''

This commit is contained in:
sam.hadow 2023-05-23 03:04:36 +02:00
parent 96a9be02cb
commit 30cb2832b5
5 changed files with 421 additions and 0 deletions

291
app.js Normal file
View 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
View 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
View 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
View 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
View 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
}