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