these either
This commit is contained in:
parent
c1a7c441d2
commit
89dd7c1f28
32
api/endpoints/person_routes.py
Normal file
32
api/endpoints/person_routes.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
from flask import Blueprint, render_template, request
|
||||||
|
import json
|
||||||
|
|
||||||
|
bp = Blueprint('person', __name__)
|
||||||
|
|
||||||
|
# Load person data from JSON file
|
||||||
|
with open('../person_data.json') as f:
|
||||||
|
person_data = json.load(f)
|
||||||
|
|
||||||
|
@bp.route('/', methods=['GET', 'POST'])
|
||||||
|
def index():
|
||||||
|
persons = get_persons()
|
||||||
|
relationships = ['SPONSORED', 'COSPONSORED']
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
relationship_type = request.form.get('relationship_type')
|
||||||
|
|
||||||
|
# Fetch related nodes based on selected relationship
|
||||||
|
with driver.session() as session:
|
||||||
|
if relationship_type == 'SPONSORED':
|
||||||
|
result = session.run("MATCH (p:Person)-[:SPONSORED]->(l:Legislation) RETURN p.name AS person")
|
||||||
|
elif relationship_type == 'COSPONSORED':
|
||||||
|
result = session.run("MATCH (p:Person)-[:COSPONSORED]->(l:Legislation) RETURN p.name AS person")
|
||||||
|
else:
|
||||||
|
# Fetch all unique persons
|
||||||
|
result_persons = session.run("MATCH (p:Person) RETURN DISTINCT p.name AS name")
|
||||||
|
persons = [record['name'] for record in result_persons]
|
||||||
|
|
||||||
|
if relationship_type:
|
||||||
|
persons = list(set(record['person'] for record in result))
|
||||||
|
|
||||||
|
return render_template('graph.html', persons=persons, relationships=relationships)
|
26
api/endpoints/show_cache.py
Normal file
26
api/endpoints/show_cache.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
from flask import Blueprint, render_template
|
||||||
|
import json
|
||||||
|
from cachetools import cached, TTLCache
|
||||||
|
|
||||||
|
# Create a Blueprint instance
|
||||||
|
cache_bp = Blueprint('cache', __name__, template_folder='templates')
|
||||||
|
|
||||||
|
# Configure cache with a time-to-live (TTL) of 3600 seconds (1 hour)
|
||||||
|
cache = TTLCache(maxsize=100, ttl=3600)
|
||||||
|
|
||||||
|
def load_cache():
|
||||||
|
"""Load cache from cache.json"""
|
||||||
|
try:
|
||||||
|
with open('cache.json', 'r') as file:
|
||||||
|
data = json.load(file)
|
||||||
|
for key, value in data.items():
|
||||||
|
cache[key] = value
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("cache.json not found")
|
||||||
|
|
||||||
|
@cache_bp.route('/show_cache')
|
||||||
|
@cached(cache)
|
||||||
|
def index():
|
||||||
|
"""Render the HTML page with cached items"""
|
||||||
|
load_cache()
|
||||||
|
return render_template('show_cache.html', bioguideId=cache.get('bioguideId', {}), legislation=cache.get('legislation', {}))
|
29
api/templates/graph.html
Normal file
29
api/templates/graph.html
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Dropdown Example</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Select a Relationship and Person</h1>
|
||||||
|
<form action="/" method="post">
|
||||||
|
<label for="relationship_type">Relationship Type:</label>
|
||||||
|
<select name="relationship_type" id="relationship_type">
|
||||||
|
<option value="">Any</option>
|
||||||
|
{% for relationship in relationships %}
|
||||||
|
<option value="{{ relationship }}" {% if request.form.get('relationship_type') == relationship %}selected{% endif %}>{{ relationship }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<label for="person">Person:</label>
|
||||||
|
<select name="person" id="person">
|
||||||
|
<option value="">Any</option>
|
||||||
|
{% for person in persons %}
|
||||||
|
<option value="{{ person }}" {% if request.form.get('person') == person %}selected{% endif %}>{{ person }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<button type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
33
api/templates/show_cache.html
Normal file
33
api/templates/show_cache.html
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Cache Items</title>
|
||||||
|
<style>
|
||||||
|
.nested-list {
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Cached Items</h1>
|
||||||
|
|
||||||
|
{% macro render_nested_dict(d) -%}
|
||||||
|
<ul class="nested-list">
|
||||||
|
{%- for key, value in d.items() %}
|
||||||
|
{%- if isinstance(value, dict) %}
|
||||||
|
<li>{{ key }}: {{ render_nested_dict(value) }}</li>
|
||||||
|
{%- else %}
|
||||||
|
<li>{{ key }}: {{ value }}</li>
|
||||||
|
{%- endif %}
|
||||||
|
{%- endfor %}
|
||||||
|
</ul>
|
||||||
|
{%- endmacro %}
|
||||||
|
|
||||||
|
<h2>Bioguide ID Cache</h2>
|
||||||
|
{{- render_nested_dict(bioguideId) -}}
|
||||||
|
|
||||||
|
<h2>Legislation Cache</h2>
|
||||||
|
{{- render_nested_dict(legislation) -}}
|
||||||
|
</body>
|
||||||
|
</html>
|
65
render.py
Normal file
65
render.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
from flask import Flask, render_template_string
|
||||||
|
from neo4j import GraphDatabase
|
||||||
|
from pyvis.network import Network
|
||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
# Load environment variables from .env file
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
# Configure your Neo4j connection using environment variables
|
||||||
|
uri = os.getenv("NEO4J_URI")
|
||||||
|
user = os.getenv("NEO4J_USER")
|
||||||
|
password = os.getenv("NEO4J_PASSWORD")
|
||||||
|
|
||||||
|
driver = GraphDatabase.driver(uri, auth=(user, password))
|
||||||
|
|
||||||
|
def fetch_data():
|
||||||
|
with driver.session() as session:
|
||||||
|
result = session.run("MATCH (n:Person)-[r]->(m:Legislation) RETURN n.name AS node1, type(r) AS relationship, m.number AS node2")
|
||||||
|
nodes = []
|
||||||
|
edges = []
|
||||||
|
|
||||||
|
for record in result:
|
||||||
|
node1 = record["node1"]
|
||||||
|
node2 = record["node2"]
|
||||||
|
|
||||||
|
# Debugging: Check the types of node IDs
|
||||||
|
print(f"Node 1 Type: {type(node1)}, Value: {node1}")
|
||||||
|
print(f"Node 2 Type: {type(node2)}, Value: {node2}")
|
||||||
|
|
||||||
|
assert isinstance(node1, (str, int)), f"Unexpected type for node ID: {type(node1)}"
|
||||||
|
assert isinstance(node2, (str, int)), f"Unexpected type for node ID: {type(node2)}"
|
||||||
|
|
||||||
|
nodes.append(node1)
|
||||||
|
nodes.append(node2)
|
||||||
|
edges.append((node1, node2, {"title": record["relationship"]}))
|
||||||
|
|
||||||
|
return list(set(nodes)), edges
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
try:
|
||||||
|
nodes, edges = fetch_data()
|
||||||
|
|
||||||
|
# Create a Network object
|
||||||
|
net = Network(notebook=False, height='600px', width='100%', directed=True)
|
||||||
|
|
||||||
|
# Add nodes and edges to the graph
|
||||||
|
for node in nodes:
|
||||||
|
net.add_node(node)
|
||||||
|
|
||||||
|
for edge in edges:
|
||||||
|
net.add_edge(edge[0], edge[1], title=edge[2]['title'])
|
||||||
|
|
||||||
|
# Generate HTML for embedding in Flask app
|
||||||
|
html = net.generate_html("3d-force-directed-graph.html")
|
||||||
|
return render_template_string(html)
|
||||||
|
except AssertionError as e:
|
||||||
|
return f"Assertion Error: {e}", 500
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
port = os.getenv("FLASK_PORT", 5000) # Default to 5000 if not specified in .env
|
||||||
|
app.run(debug=True, port=port)
|
109
templates/index.html
Normal file
109
templates/index.html
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>3D Force-Directed Graph</title>
|
||||||
|
<script src="https://d3js.org/d3.v7.min.js"></script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
#graph-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="graph-container"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Fetch the graph data from the Flask server
|
||||||
|
fetch('/data')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
const nodes = data.nodes.map(node => ({
|
||||||
|
...node,
|
||||||
|
x: Math.random() * 1000 - 500,
|
||||||
|
y: Math.random() * 1000 - 500,
|
||||||
|
z: Math.random() * 1000 - 500
|
||||||
|
}));
|
||||||
|
const links = data.edges;
|
||||||
|
|
||||||
|
// Create the force-directed graph using D3.js
|
||||||
|
const container = document.getElementById('graph-container');
|
||||||
|
const width = container.clientWidth;
|
||||||
|
const height = container.clientHeight;
|
||||||
|
|
||||||
|
const simulation = d3.forceSimulation(nodes)
|
||||||
|
.force("link", d3.forceLink(links).id(d => d.id))
|
||||||
|
.force("charge", d3.forceManyBody().strength(-50))
|
||||||
|
.force("x", d3.forceX(width / 2))
|
||||||
|
.force("y", d3.forceY(height / 2));
|
||||||
|
|
||||||
|
const svg = d3.select(container)
|
||||||
|
.append('svg')
|
||||||
|
.attr('width', width)
|
||||||
|
.attr('height', height);
|
||||||
|
|
||||||
|
const link = svg.append('g')
|
||||||
|
.attr('class', 'links')
|
||||||
|
.selectAll('line')
|
||||||
|
.data(links)
|
||||||
|
.enter().append('line')
|
||||||
|
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
|
||||||
|
|
||||||
|
const node = svg.append('g')
|
||||||
|
.attr('class', 'nodes')
|
||||||
|
.selectAll('circle')
|
||||||
|
.data(nodes)
|
||||||
|
.enter().append('circle')
|
||||||
|
.attr('r', 5)
|
||||||
|
.attr('fill', '#69b3a2')
|
||||||
|
.call(d3.drag()
|
||||||
|
.on("start", dragstarted)
|
||||||
|
.on("drag", dragged)
|
||||||
|
.on("end", dragended));
|
||||||
|
|
||||||
|
node.append('title')
|
||||||
|
.text(d => d.id);
|
||||||
|
|
||||||
|
simulation.on("tick", () => {
|
||||||
|
link
|
||||||
|
.attr("x1", d => d.source.x)
|
||||||
|
.attr("y1", d => d.source.y)
|
||||||
|
.attr("z1", d => d.source.z)
|
||||||
|
.attr("x2", d => d.target.x)
|
||||||
|
.attr("y2", d => d.target.y)
|
||||||
|
.attr("z2", d => d.target.z);
|
||||||
|
|
||||||
|
node
|
||||||
|
.attr("cx", d => d.x)
|
||||||
|
.attr("cy", d => d.y)
|
||||||
|
.attr("cz", d => d.z);
|
||||||
|
});
|
||||||
|
|
||||||
|
function dragstarted(event, d) {
|
||||||
|
if (!event.active) simulation.alphaTarget(0.3).restart();
|
||||||
|
d.fx = d.x;
|
||||||
|
d.fy = d.y;
|
||||||
|
d.fz = d.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragged(event, d) {
|
||||||
|
d.fx = event.x;
|
||||||
|
d.fy = event.y;
|
||||||
|
d.fz = event.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragended(event, d) {
|
||||||
|
if (!event.active) simulation.alphaTarget(0);
|
||||||
|
d.fx = null;
|
||||||
|
d.fy = null;
|
||||||
|
d.fz = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user