|
|
|
from flask import Flask, jsonify, request, render_template
|
|
|
|
import numpy as np
|
|
|
|
|
|
|
|
app = Flask(__name__)
|
|
|
|
app.template_folder = "frontend/"
|
|
|
|
app.static_folder = "frontend/assets/"
|
|
|
|
|
|
|
|
|
|
|
|
class Graph(object):
|
|
|
|
nodes = set() # set of strings
|
|
|
|
edges = dict() # (string,string) -> float
|
|
|
|
|
|
|
|
|
|
|
|
def bellmann_ford(graph, node_a, node_b):
|
|
|
|
"""Calculate distance from node_a to node_b assuming no negative cycles."""
|
|
|
|
# weights
|
|
|
|
from_a_to = {**[(node, np.inf) for node in graph.nodes]}
|
|
|
|
from_a_to[node_a] = 0
|
|
|
|
# save path so we dont have to backtrace later
|
|
|
|
path_from_a_to = {**[(node, []) for node in graph.nodes]}
|
|
|
|
path_from_a_to[node_a] = [node_a]
|
|
|
|
|
|
|
|
for i in range(len(graph.nodes)-1):
|
|
|
|
for e in graph.edges:
|
|
|
|
# because edges are not directed
|
|
|
|
for (x,y) in [e,e[::-1]]:
|
|
|
|
if from_a_to[x] + graph.edges[(x,y)] < from_a_to[y]:
|
|
|
|
path_from_a_to[y] = path_from_a_to + [y]
|
|
|
|
from_a_to[y] = from_a_to[x] + graph.edges[(x,y)]
|
|
|
|
|
|
|
|
return path_from_a_to[node_b]
|
|
|
|
|
|
|
|
|
|
|
|
graph = Graph()
|
|
|
|
|
|
|
|
|
|
|
|
def error(message):
|
|
|
|
return jsonify({"error": message})
|
|
|
|
|
|
|
|
|
|
|
|
def edges_to_json(edges):
|
|
|
|
return_list = []
|
|
|
|
for e in edges:
|
|
|
|
return_list.append({"start":e[0],"end":e[1],"weight":edges[e]})
|
|
|
|
return return_list
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/api/nodes', methods=['GET'])
|
|
|
|
def get_nodes():
|
|
|
|
return jsonify({'nodes': graph.nodes})
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/api/edges', methods=['GET'])
|
|
|
|
def get_edges():
|
|
|
|
return jsonify({'edges': edges_to_json(graph.edges)})
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/api/nodes', methods=['POST'])
|
|
|
|
def create_node():
|
|
|
|
if not request.json:
|
|
|
|
return error("Request needs to be JSON."), 400
|
|
|
|
if 'name' not in request.json:
|
|
|
|
return error("Name must be set."), 400
|
|
|
|
|
|
|
|
name = request.json['name']
|
|
|
|
if name == "":
|
|
|
|
return error("Name cannot be empty."), 400
|
|
|
|
if name in graph.nodes:
|
|
|
|
return error(f'Node with name "{name}" already exist.'), 400
|
|
|
|
|
|
|
|
graph.nodes.add(name)
|
|
|
|
return jsonify(name), 201
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/api/edges', methods=['POST'])
|
|
|
|
def create_edge():
|
|
|
|
if not request.json:
|
|
|
|
return error("Request needs to be JSON."), 400
|
|
|
|
if 'start' not in request.json:
|
|
|
|
return error("Start node must be set."), 400
|
|
|
|
if 'end' not in request.json:
|
|
|
|
return error("End node must be set."), 400
|
|
|
|
if 'weight' not in request.json:
|
|
|
|
return error("Edge weight must be set."), 400
|
|
|
|
|
|
|
|
start = request.json['start']
|
|
|
|
end = request.json['end']
|
|
|
|
weight = request.json['weight']
|
|
|
|
try:
|
|
|
|
weight = float(weight)
|
|
|
|
except ValueError:
|
|
|
|
return error("Weight must be a number."), 400
|
|
|
|
if weight < 0:
|
|
|
|
return error("Weight must be positive."), 400
|
|
|
|
if start == end:
|
|
|
|
return error("Start and end node must be different."), 400
|
|
|
|
if start not in graph.nodes:
|
|
|
|
return error("Start node does not exist."), 400
|
|
|
|
if end not in graph.nodes:
|
|
|
|
return error("End node does not exist."), 400
|
|
|
|
if (start,end) in graph.edges or (end,start) in graph.edges:
|
|
|
|
return error("Edge already exists."), 400
|
|
|
|
|
|
|
|
edge = {"start":start,"end":end, "weight": weight}
|
|
|
|
graph.edges[(start,end)] = weight
|
|
|
|
|
|
|
|
return jsonify(edge), 201
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/")
|
|
|
|
def index():
|
|
|
|
return render_template("index.html", nodes=graph.nodes, edges=edges_to_json(graph.edges))
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
app.run(host="127.0.0.1", debug=True, port=5000)
|