mirror of
https://github.com/ae-utbm/sith.git
synced 2025-07-09 19:40:19 +00:00
Port galaxy to webpack
This commit is contained in:
File diff suppressed because one or more lines are too long
2
galaxy/static/galaxy/js/d3-force-3d.min.js
vendored
2
galaxy/static/galaxy/js/d3-force-3d.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
6
galaxy/static/galaxy/js/three.min.js
vendored
6
galaxy/static/galaxy/js/three.min.js
vendored
File diff suppressed because one or more lines are too long
138
galaxy/static/webpack/galaxy/galaxy-index.js
Normal file
138
galaxy/static/webpack/galaxy/galaxy-index.js
Normal file
@ -0,0 +1,138 @@
|
||||
import { default as ForceGraph3D } from "3d-force-graph";
|
||||
import { forceX, forceY, forceZ } from "d3-force-3d";
|
||||
// biome-ignore lint/style/noNamespaceImport: This is how it should be imported
|
||||
import * as Three from "three";
|
||||
import SpriteText from "three-spritetext";
|
||||
|
||||
/**
|
||||
* @typedef GalaxyConfig
|
||||
* @property {number} nodeId id of the current user node
|
||||
* @property {string} dataUrl url to fetch the galaxy data from
|
||||
**/
|
||||
|
||||
/**
|
||||
* Load the galaxy of an user
|
||||
* @param {GalaxyConfig} config
|
||||
**/
|
||||
window.loadGalaxy = (config) => {
|
||||
window.getNodeFromId = (id) => {
|
||||
return Graph.graphData().nodes.find((n) => n.id === id);
|
||||
};
|
||||
|
||||
window.getLinksFromNodeId = (id) => {
|
||||
return Graph.graphData().links.filter(
|
||||
(l) => l.source.id === id || l.target.id === id,
|
||||
);
|
||||
};
|
||||
|
||||
window.focusNode = (node) => {
|
||||
highlightNodes.clear();
|
||||
highlightLinks.clear();
|
||||
|
||||
hoverNode = node || null;
|
||||
if (node) {
|
||||
// collect neighbors and links for highlighting
|
||||
for (const link of window.getLinksFromNodeId(node.id)) {
|
||||
highlightLinks.add(link);
|
||||
highlightNodes.add(link.source);
|
||||
highlightNodes.add(link.target);
|
||||
}
|
||||
}
|
||||
|
||||
// refresh node and link display
|
||||
Graph.nodeThreeObject(Graph.nodeThreeObject())
|
||||
.linkWidth(Graph.linkWidth())
|
||||
.linkDirectionalParticles(Graph.linkDirectionalParticles());
|
||||
|
||||
// Aim at node from outside it
|
||||
const distance = 42;
|
||||
const distRatio = 1 + distance / Math.hypot(node.x, node.y, node.z);
|
||||
|
||||
const newPos =
|
||||
node.x || node.y || node.z
|
||||
? { x: node.x * distRatio, y: node.y * distRatio, z: node.z * distRatio }
|
||||
: { x: 0, y: 0, z: distance }; // special case if node is in (0,0,0)
|
||||
|
||||
Graph.cameraPosition(
|
||||
newPos, // new position
|
||||
node, // lookAt ({ x, y, z })
|
||||
3000, // ms transition duration
|
||||
);
|
||||
};
|
||||
|
||||
const highlightNodes = new Set();
|
||||
const highlightLinks = new Set();
|
||||
let hoverNode = null;
|
||||
|
||||
const grpahDiv = document.getElementById("3d-graph");
|
||||
const Graph = ForceGraph3D();
|
||||
Graph(grpahDiv);
|
||||
Graph.jsonUrl(config.dataUrl)
|
||||
.width(
|
||||
grpahDiv.parentElement.clientWidth > 1200
|
||||
? 1200
|
||||
: grpahDiv.parentElement.clientWidth,
|
||||
) // Not perfect at all. JS-fu master from the future, please fix this :-)
|
||||
.height(1000)
|
||||
.enableNodeDrag(false) // allow easier navigation
|
||||
.onNodeClick((node) => {
|
||||
const camera = Graph.cameraPosition();
|
||||
const distance = Math.sqrt(
|
||||
(node.x - camera.x) ** 2 + (node.y - camera.y) ** 2 + (node.z - camera.z) ** 2,
|
||||
);
|
||||
if (distance < 120 || highlightNodes.has(node)) {
|
||||
window.focusNode(node);
|
||||
}
|
||||
})
|
||||
.linkWidth((link) => (highlightLinks.has(link) ? 0.4 : 0.0))
|
||||
.linkColor((link) =>
|
||||
highlightLinks.has(link) ? "rgba(255,160,0,1)" : "rgba(128,255,255,0.6)",
|
||||
)
|
||||
.linkVisibility((link) => highlightLinks.has(link))
|
||||
.nodeVisibility((node) => highlightNodes.has(node) || node.mass > 4)
|
||||
// .linkDirectionalParticles(link => highlightLinks.has(link) ? 3 : 1) // kinda buggy for now, and slows this a bit, but would be great to help visualize lanes
|
||||
.linkDirectionalParticleWidth(0.2)
|
||||
.linkDirectionalParticleSpeed(-0.006)
|
||||
.nodeThreeObject((node) => {
|
||||
const sprite = new SpriteText(node.name);
|
||||
sprite.material.depthWrite = false; // make sprite background transparent
|
||||
sprite.color = highlightNodes.has(node)
|
||||
? node === hoverNode
|
||||
? "rgba(200,0,0,1)"
|
||||
: "rgba(255,160,0,0.8)"
|
||||
: "rgba(0,255,255,0.2)";
|
||||
sprite.textHeight = 2;
|
||||
sprite.center = new Three.Vector2(1.2, 0.5);
|
||||
return sprite;
|
||||
})
|
||||
.onEngineStop(() => {
|
||||
window.focusNode(window.getNodeFromId(config.nodeId));
|
||||
Graph.onEngineStop(() => {
|
||||
/* nope */
|
||||
}); // don't call ourselves in a loop while moving the focus
|
||||
});
|
||||
|
||||
// Set distance between stars
|
||||
Graph.d3Force("link").distance((link) => link.value);
|
||||
|
||||
// Set high masses nearer the center of the galaxy
|
||||
// TODO: quick and dirty strength computation, this will need tuning.
|
||||
Graph.d3Force(
|
||||
"positionX",
|
||||
forceX().strength((node) => {
|
||||
return 1 - 1 / node.mass;
|
||||
}),
|
||||
);
|
||||
Graph.d3Force(
|
||||
"positionY",
|
||||
forceY().strength((node) => {
|
||||
return 1 - 1 / node.mass;
|
||||
}),
|
||||
);
|
||||
Graph.d3Force(
|
||||
"positionZ",
|
||||
forceZ().strength((node) => {
|
||||
return 1 - 1 / node.mass;
|
||||
}),
|
||||
);
|
||||
};
|
@ -4,13 +4,18 @@
|
||||
{% trans user_name=object.get_display_name() %}{{ user_name }}'s Galaxy{% endtrans %}
|
||||
{% endblock %}
|
||||
|
||||
{% block additional_js %}
|
||||
<script src="{{ static('webpack/galaxy/galaxy-index.js') }}" defer></script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
{% if object.current_star %}
|
||||
<div style="display: flex; flex-wrap: wrap;">
|
||||
<div id="3d-graph"></div>
|
||||
|
||||
<div style="margin: 1em;">
|
||||
<p><a onclick="focus_node(get_node_from_id({{ object.id }}))">Reset on {{ object.get_display_name() }}</a></p>
|
||||
<p><a onclick="window.focusNode(window.getNodeFromId({{ object.id }}))">Reset on {{ object.get_display_name() }}</a></p>
|
||||
<p>Self score: {{ object.current_star.mass }}</p>
|
||||
<table style="width: initial;">
|
||||
<tr>
|
||||
@ -24,7 +29,7 @@
|
||||
</tr>
|
||||
{% for lane in lanes %}
|
||||
<tr>
|
||||
<td><a onclick="focus_node(get_node_from_id({{ lane.other_star_id }}))">Locate</a></td>
|
||||
<td><a onclick="window.focusNode(window.getNodeFromId({{ lane.other_star_id }}))">Locate</a></td>
|
||||
<td><a href="{{ url("galaxy:user", user_id=lane.other_star_id) }}">{{ lane.other_star_name }}</a></td>
|
||||
<td>{{ lane.other_star_mass }}</td>
|
||||
<td>{{ lane.distance }}</td>
|
||||
@ -45,106 +50,13 @@
|
||||
|
||||
{% block script %}
|
||||
{{ super() }}
|
||||
|
||||
<script src="{{ static('galaxy/js/three.min.js') }}" defer></script>
|
||||
<script src="{{ static('galaxy/js/three-spritetext.min.js') }}" defer></script>
|
||||
<script src="{{ static('galaxy/js/3d-force-graph.min.js') }}" defer></script>
|
||||
<script src="{{ static('galaxy/js/d3-force-3d.min.js') }}" defer></script>
|
||||
|
||||
<script>
|
||||
var Graph;
|
||||
|
||||
function get_node_from_id(id) {
|
||||
return Graph.graphData().nodes.find(n => n.id === id);
|
||||
}
|
||||
|
||||
function get_links_from_node_id(id) {
|
||||
return Graph.graphData().links.filter(l => l.source.id === id || l.target.id === id);
|
||||
}
|
||||
|
||||
function focus_node(node) {
|
||||
highlightNodes.clear();
|
||||
highlightLinks.clear();
|
||||
|
||||
hoverNode = node || null;
|
||||
if (node) { // collect neighbors and links for highlighting
|
||||
get_links_from_node_id(node.id).forEach(link => {
|
||||
highlightLinks.add(link);
|
||||
highlightNodes.add(link.source);
|
||||
highlightNodes.add(link.target);
|
||||
});
|
||||
}
|
||||
|
||||
// refresh node and link display
|
||||
Graph
|
||||
.nodeThreeObject(Graph.nodeThreeObject())
|
||||
.linkWidth(Graph.linkWidth())
|
||||
.linkDirectionalParticles(Graph.linkDirectionalParticles());
|
||||
|
||||
// Aim at node from outside it
|
||||
const distance = 42;
|
||||
const distRatio = 1 + distance/Math.hypot(node.x, node.y, node.z);
|
||||
|
||||
const newPos = node.x || node.y || node.z
|
||||
? { x: node.x * distRatio, y: node.y * distRatio, z: node.z * distRatio }
|
||||
: { x: 0, y: 0, z: distance }; // special case if node is in (0,0,0)
|
||||
|
||||
Graph.cameraPosition(
|
||||
newPos, // new position
|
||||
node, // lookAt ({ x, y, z })
|
||||
3000 // ms transition duration
|
||||
);
|
||||
}
|
||||
|
||||
const highlightNodes = new Set();
|
||||
const highlightLinks = new Set();
|
||||
let hoverNode = null;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
var graph_div = document.getElementById('3d-graph');
|
||||
Graph = ForceGraph3D();
|
||||
Graph(graph_div);
|
||||
Graph
|
||||
.jsonUrl('{{ url("galaxy:data") }}')
|
||||
.width(graph_div.parentElement.clientWidth > 1200 ? 1200 : graph_div.parentElement.clientWidth) // Not perfect at all. JS-fu master from the future, please fix this :-)
|
||||
.height(1000)
|
||||
.enableNodeDrag(false) // allow easier navigation
|
||||
.onNodeClick(node => {
|
||||
camera = Graph.cameraPosition();
|
||||
var distance = Math.sqrt(Math.pow(node.x - camera.x, 2) + Math.pow(node.y - camera.y, 2) + Math.pow(node.z - camera.z, 2))
|
||||
if (distance < 120 || highlightNodes.has(node)) {
|
||||
focus_node(node);
|
||||
}
|
||||
})
|
||||
.linkWidth(link => highlightLinks.has(link) ? 0.4 : 0.0)
|
||||
.linkColor(link => highlightLinks.has(link) ? 'rgba(255,160,0,1)' : 'rgba(128,255,255,0.6)')
|
||||
.linkVisibility(link => highlightLinks.has(link))
|
||||
.nodeVisibility(node => highlightNodes.has(node) || node.mass > 4)
|
||||
// .linkDirectionalParticles(link => highlightLinks.has(link) ? 3 : 1) // kinda buggy for now, and slows this a bit, but would be great to help visualize lanes
|
||||
.linkDirectionalParticleWidth(0.2)
|
||||
.linkDirectionalParticleSpeed(-0.006)
|
||||
.nodeThreeObject(node => {
|
||||
const sprite = new SpriteText(node.name);
|
||||
sprite.material.depthWrite = false; // make sprite background transparent
|
||||
sprite.color = highlightNodes.has(node) ? node === hoverNode ? 'rgba(200,0,0,1)' : 'rgba(255,160,0,0.8)' : 'rgba(0,255,255,0.2)';
|
||||
sprite.textHeight = 2;
|
||||
sprite.center = new THREE.Vector2(1.2, 0.5);
|
||||
return sprite;
|
||||
})
|
||||
.onEngineStop( () => {
|
||||
focus_node(get_node_from_id({{ object.id }}));
|
||||
Graph.onEngineStop(() => {}); // don't call ourselves in a loop while moving the focus
|
||||
});
|
||||
|
||||
// Set distance between stars
|
||||
Graph.d3Force('link').distance(link => link.value);
|
||||
|
||||
// Set high masses nearer the center of the galaxy
|
||||
// TODO: quick and dirty strength computation, this will need tuning.
|
||||
Graph.d3Force('positionX', d3.forceX().strength(node => { return 1 - (1 / node.mass); }));
|
||||
Graph.d3Force('positionY', d3.forceY().strength(node => { return 1 - (1 / node.mass); }));
|
||||
Graph.d3Force('positionZ', d3.forceZ().strength(node => { return 1 - (1 / node.mass); }));
|
||||
})
|
||||
window.loadGalaxy({
|
||||
nodeId: {{ object.id }},
|
||||
dataUrl: '{{ url("galaxy:data") }}',
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -160,7 +160,7 @@ class TestGalaxyView(TestCase):
|
||||
response = self.client.get(reverse("galaxy:user", args=[user.id]))
|
||||
self.assertContains(
|
||||
response,
|
||||
f'<a onclick="focus_node(get_node_from_id({user.id}))">Reset on {user}</a>',
|
||||
f'<a onclick="window.focusNode(window.getNodeFromId({user.id}))">Reset on {user}</a>',
|
||||
status_code=200,
|
||||
)
|
||||
|
||||
|
Reference in New Issue
Block a user