Skip to content

Commit

Permalink
261 cosmograph data format (#262)
Browse files Browse the repository at this point in the history
* serializes some network data; dynamic network-view

* visualize filtered edges
  • Loading branch information
csae8092 authored Nov 26, 2024
1 parent ec9dc70 commit d585570
Show file tree
Hide file tree
Showing 8 changed files with 1,038 additions and 1,028 deletions.
3 changes: 0 additions & 3 deletions network/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ def nodes_icontains_filter(self, queryset, name, value):

def nodes_id_filter(self, queryset, name, value):
sane_values = [safe_int_conversion(x) for x in value]
print(value)
print(sane_values)
return queryset.filter(
Q(source_id__in=sane_values) | Q(target_id__in=sane_values)
)
Expand All @@ -56,7 +54,6 @@ def __init__(self, *args, **kwargs):
self.filters[field_name].help_text = model_field.help_text
except FieldDoesNotExist:
continue
print(field_name, type(model_field))
if isinstance(model_field, CharField) and not field_name == "edge_label":
if (
model_field.choices
Expand Down
18 changes: 13 additions & 5 deletions network/templates/network/list_view.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,19 @@
<h1 class="display-1 text-center">
<i class="bi bi-share"> Beziehungen</i>
</h1>
<div class="text-center">
<h2>Download</h2>
<div class="btn-group" role="group" class="text-end">
<a type="button" class="btn btn-outline-primary" href="{% url 'network:data' %}{% querystring %}&format=csv">CSV</a>
<a type="button" class="btn btn-outline-primary" href="{% url 'network:data' %}{% querystring %}&format=json">JSON</a>
<div class="row">
<div class="col-md-6 text-center">
<h2>Download</h2>
<div class="btn-group" role="group" class="text-end">
<a type="button" class="btn btn-outline-primary" href="{% url 'network:data' %}{% querystring %}&format=csv">CSV</a>
<a type="button" class="btn btn-outline-primary" href="{% url 'network:data' %}{% querystring %}&format=json">JSON</a>
</div>
</div>
<div class="col-md-6 text-center">
<h2>Visualize</h2>
<div class="btn-group" role="group" class="text-end">
<a type="button" class="btn btn-outline-primary" href="{% url 'dumper:network' %}{% querystring %}&format=cosmograph">Als Nezwerk</a>
</div>
</div>
</div>

Expand Down
48 changes: 45 additions & 3 deletions network/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,62 @@ def network_data(request):
content_type="text/csv",
)
format = request.GET.get("format", "csv")
if format not in ["csv", "json"]:
if format not in ["csv", "json", "cosmograph"]:
format = "csv"
if format == "csv":
response = HttpResponse(
content_type="text/csv",
headers={"Content-Disposition": 'attachment; filename="relations.csv"'},
)
df.to_csv(response, index=False)
else:
return response
elif format == "json":
df = df.set_index("edge_id")
df["start_date"] = pd.to_datetime(df["start_date"], errors="coerce")
df["end_date"] = pd.to_datetime(df["end_date"], errors="coerce")
df["start_date"] = df["start_date"].dt.strftime("%Y-%m-%d").fillna("")
df["end_date"] = df["end_date"].dt.strftime("%Y-%m-%d").fillna("")
out = df.to_json(orient="index", force_ascii=False)
response = JsonResponse(json.loads(out))
return response
elif format == "cosmograph":
data = {}
edge_data = df.apply(
lambda row: {
"id": row["edge_id"],
"s": row["source_id"],
"t": row["target_id"],
"start": str(row["start_date"]),
"end": str(row["end_date"]),
},
axis=1,
).tolist()
data["edges"] = edge_data
source_nodes = df[["source_label", "source_kind", "source_id"]].rename(
columns={
"source_label": "node_label",
"source_kind": "node_kind",
"source_id": "node_id",
}
)
target_nodes = df[["target_label", "target_kind", "target_id"]].rename(
columns={
"target_label": "node_label",
"target_kind": "node_kind",
"target_id": "node_id",
}
)
nodes = (
pd.concat([source_nodes, target_nodes])
.drop_duplicates()
.reset_index(drop=True)
)
data["nodes"] = nodes.apply(
lambda row: {
"id": row["node_id"],
"k": row["node_kind"],
"l": row["node_label"],
},
axis=1,
).tolist()
response = JsonResponse(data)
return response
137 changes: 76 additions & 61 deletions static/src/js/main.js
Original file line number Diff line number Diff line change
@@ -1,61 +1,76 @@
import {
Cosmograph,
CosmographSearch,
CosmographTimeline,
} from "@cosmograph/cosmograph";
import * as d3 from "d3";

const edge_csv = "/media/edges.csv";
const node_csv = "/media/nodes.csv";

const edge_promis = d3.csv(edge_csv);
const node_promis = d3.csv(node_csv);
const alertNode = document.getElementById("alert")
const spinnerNode = document.getElementById("spinner");
const legendNode = document.getElementById("legend");
const canvas = document.createElement("div");

Promise.all([edge_promis, node_promis]).then(([edge_data, node_data]) => {
const links = edge_data.map((d) => ({
source: parseInt(d.source),
target: parseInt(d.target),
date: Date.parse(d.date),
}));

const nodes = node_data.map((d) => ({
id: parseInt(d.id),
label: d.label,
color: d.color,
}));
const appNode = document.getElementById("app");

// remove spinner
spinnerNode.classList.add("visually-hidden");
legendNode.style.visibility = "visible";
alertNode.classList.add("visually-hidden");
appNode.appendChild(canvas);
const searchContainer = document.createElement("div");
appNode.appendChild(searchContainer);
const config = {
nodeColor: (d) => d.color,
nodeLabelAccessor: (d) => d.label,
showTopLabels: true,
showDynamicLabels: false,
linkGreyoutOpacity: 0,
nodeLabelColor: "white",
hoveredNodeLabelColor: "white",
linkWidth: 1,
linkArrows: false,
onClick: (data) => alert(data.label)
};
const cosmograph = new Cosmograph(canvas, config);

const timelineContainer = document.createElement("div");
const timeline = new CosmographTimeline(cosmograph, timelineContainer);


const search = new CosmographSearch(cosmograph, searchContainer);
cosmograph.setData(nodes, links);
appNode.appendChild(timelineContainer);

});
import { Cosmograph, CosmographTimeline } from "@cosmograph/cosmograph";

async function init() {
const alertNode = document.getElementById("alert");
const spinnerNode = document.getElementById("spinner");
const legendNode = document.getElementById("legend");
const canvas = document.createElement("div");

const queryString = window.location.search;
const url = `/network/csv/${queryString}`;

try {
const res = await fetch(url);
const data = await res.json();

const colors = {
person: "#720e07",
place: "#5bc0eb",
work: "#ff8600",
event: "#9bc53d",
institution: "#ffdd1b",
};

const links = data["edges"].map((d) => ({
source: parseInt(d.s),
target: parseInt(d.t),
date: Date.parse(d.start),
}));

const nodes = data["nodes"].map((d) => ({
id: parseInt(d.id),
label: d.l,
color: colors[d["k"]],
}));

const appNode = document.getElementById("app");

// Remove spinner
spinnerNode.classList.add("visually-hidden");
legendNode.style.visibility = "visible";
alertNode.classList.add("visually-hidden");

appNode.appendChild(canvas);
const searchContainer = document.createElement("div");
appNode.appendChild(searchContainer);

const config = {
nodeColor: (d) => d.color,
nodeLabelAccessor: (d) => d.label,
showTopLabels: true,
showDynamicLabels: false,
linkGreyoutOpacity: 0,
nodeLabelColor: "white",
hoveredNodeLabelColor: "white",
linkWidth: 1,
linkArrows: false,
onClick: (data) => alert(data.label),
simulationRepulsion: 1,
linkDistance: 5,
gravity: 0.5
};

const cosmograph = new Cosmograph(canvas, config);

const timelineContainer = document.createElement("div");
const timeline = new CosmographTimeline(cosmograph, timelineContainer);
cosmograph.setData(nodes, links);
appNode.appendChild(timelineContainer);
} catch (error) {
console.error("Failed to fetch data:", error);
alertNode.textContent = "Failed to load data. Please try again later.";
alertNode.style.visibility = "visible";
}
}

init();

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

951 changes: 0 additions & 951 deletions static/vite/main-CJBCyQtR.js

This file was deleted.

899 changes: 899 additions & 0 deletions static/vite/main-Dnusw_Zv.js

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions static/vite/manifest.info
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
{
"_browser-CBdNgIts.js": {
"file": "browser-CBdNgIts.js",
"_browser-lg2ZDdSy.js": {
"file": "browser-lg2ZDdSy.js",
"name": "browser",
"isDynamicEntry": true,
"imports": [
"js/main.js"
]
},
"js/main.js": {
"file": "main-CJBCyQtR.js",
"file": "main-Dnusw_Zv.js",
"name": "main",
"src": "js/main.js",
"isEntry": true,
"dynamicImports": [
"_browser-CBdNgIts.js"
"_browser-lg2ZDdSy.js"
]
}
}

0 comments on commit d585570

Please sign in to comment.