Skip to content

Instantly share code, notes, and snippets.

@sj26
Last active May 18, 2018 14:50
Show Gist options
  • Save sj26/7079674 to your computer and use it in GitHub Desktop.
Save sj26/7079674 to your computer and use it in GitHub Desktop.
Realtime editable force directed graph
<!DOCTYPE html>
<meta charset="utf-8">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.0.0/js/bootstrap.min.css">
<style>
.well {
box-sizing: border-box
width: 100%
height: 40em
}
.node {
stroke: #fff
stroke-width: 4px
}
.label {
fill: #fff
font-size: 20px
text-anchor: middle
cursor: default
user-select: none
-webkit-user-select: none
-moz-user-select: none
-ms-user-select: none
}
.link {
stroke: #999
stroke-width: 4px
}
</style>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.0.0/js/bootstrap.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.3.3/d3.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/coffee-script/1.6.3/coffee-script.min.js"></script>
<body>
<div class="form-group"><textarea class="form-control"></textarea></div>
<div class="well"></div>
<script type="text/coffeescript">
input = $("textarea")
preview = $(".well")
width = preview.width()
height = preview.height()
game = JSON.parse(input.val())
svg = d3.select(preview.empty()[0]).append("svg")
.attr("viewBox", "0 0 #{width} #{height}")
.attr("preserveAspectRatio", "xMidYMid meet")
$(window).on "resize", ->
svg.attr("viewBox", "0 0 #{preview.width()} #{preview.height()}")
force = d3.layout.force()
.size([width, height])
.charge(-800)
.linkDistance(100)
.on "tick", ->
svg.selectAll(".link")
.attr("x1", (d) -> d.source.x)
.attr("y1", (d) -> d.source.y)
.attr("x2", (d) -> d.target.x)
.attr("y2", (d) -> d.target.y)
svg.selectAll(".node")
.attr("cx", (d) -> d.x)
.attr("cy", (d) -> d.y)
svg.selectAll(".label")
.attr("x", (d) -> d.x)
.attr("y", (d) -> d.y + 8)
unless input.is(":focus")
updateInput()
preview.on "resize", ->
force.size([preview.width(), preview.height()]).start()
update = ->
try
game = JSON.parse input.val()
catch e
console?.log e
if game
## Links
link = svg.selectAll(".link").data(game.links)
# Update
link
.attr("x1", (d) -> d.source.x)
.attr("y1", (d) -> d.source.y)
.attr("x2", (d) -> d.target.x)
.attr("y2", (d) -> d.target.y)
# Insert
link.enter()
.insert("line")
.attr("class", "link")
.attr("x1", (d) -> d.source.x)
.attr("y1", (d) -> d.source.y)
.attr("x2", (d) -> d.target.x)
.attr("y2", (d) -> d.target.y)
# Remove
link.exit().remove()
## Nodes
nodeGroups = svg.selectAll(".node-group").data(game.nodes)
# Update
nodeGroups.selectAll(".circle")
.attr("cx", (d) -> d.x)
.attr("cy", (d) -> d.y)
nodeGroups.selectAll(".label")
.attr("x", (d) -> d.x)
.attr("y", (d) -> d.y + 8)
.text((d) -> d.round)
# Insert
newNodeGroups = nodeGroups.enter()
.append("g")
.attr("class", "node-group")
console.log newNodeGroups
console.log newNodeGroups.append("circle")
.attr("class", "node")
.attr("r", 30)
.attr("cx", (d) -> d.x)
.attr("cy", (d) -> d.y)
console.log newNodeGroups.append("text")
.attr("class", "label")
.attr("x", (d) -> d.x)
.attr("y", (d) -> d.y + 8)
.text((d) -> d.round)
newNodeGroups.call(force.drag)
# Remove
nodeGroups.exit().remove()
force.nodes(game.nodes)
.links(game.links)
.start()
input.on input: update
update()
serialize = (game) ->
nodes: for node in game.nodes
round: node.round
x: Math.round(node.x)
y: Math.round(node.y)
links: for link in game.links
source: link.source.index
target: link.target.index
updateInput = ->
input.val(JSON.stringify(serialize(game)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment