blob: 05a10d00ba7cce96f2112343a4ad1c77aced945a [file] [log] [blame]
charts_linked = (obj, nodes, links, options) ->
llcolors = genColors(nodes.length+1, 0.55, 0.475, true)
licolors = genColors(nodes.length+1, 0.375, 0.35, true)
lla = 0
obj.className = "chartChart linkedChart"
svg = d3.select(obj).append("svg")
.attr("width", "100%")#llwidth)
.attr("height", "600")# llheight)
g = svg.append("g")
bb = obj.getBoundingClientRect()
llwidth = bb.width
llheight = Math.max(600, bb.height)
tooltip = d3.select("body").append("div")
.attr("class", "link_tooltip")
.style("opacity", 0);
avg = links.length / nodes.length
force = d3.layout.force()
.gravity(0.015)
.distance(llheight/8)
.charge(-200/Math.log10(nodes.length))
.linkStrength(0.2/avg)
.size([llwidth, llheight])
edges = []
links.forEach((e) ->
sourceNode = nodes.filter((n) => n.id == e.source)[0]
targetNode = nodes.filter((n) => n.id == e.target)[0]
edges.push({source: sourceNode, target: targetNode, s: e.source, value: e.value, name: e.name, tooltip: e.tooltip});
)
force
.nodes(nodes)
.links(edges)
.start()
lcolors = {}
nodes.forEach((e) ->
lcolors[e.id] = licolors[lla++]
)
lla = 0
link = g.selectAll(".link")
.data(edges)
.enter().append("path")
.attr("class", "link_link")
.attr("id", (d) => d.name)
.attr("data-source", (d) => d.source.id)
.attr("data-target", (d) => d.target.id)
.attr("style", (d) =>
"stroke-width: #{d.value}; stroke: #{lcolors[d.s]};"
).on("mouseover", (d) ->
if d.tooltip
tooltip.transition()
.duration(100)
.style("opacity", .9);
tooltip.html("<b>#{d.name}:</b><br/>" + d.tooltip.replace("\n", "<br/>"))
.style("left", (d3.event.pageX + 20) + "px")
.style("top", (d3.event.pageY - 28) + "px");
)
.on("mouseout", (d) ->
d3.select(this).style("stroke-opacity", "0.375")
tooltip.transition()
.duration(200)
.style("opacity", 0);
)
defs = svg.append("defs")
nodes.forEach( (n) ->
if n.gravatar
defs.append("pattern")
.attr("id", "gravatar-" + n.id)
.attr("patternUnits", "userSpaceOnUse")
.attr("width", n.size*2)
.attr("height", n.size*2)
.attr("x", n.size)
.attr("y", n.size)
.append("image")
.attr("width", n.size*2)
.attr("height", n.size*2)
.attr("x", "0")
.attr("y", "0")
.attr("xlink:href", "https://secure.gravatar.com/avatar/#{n.gravatar}.png?d=identicon")
else
n.gravatar = false
)
node = g.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "link_node")
.attr("data-source", (d) => d.id)
.call(force.drag);
lTargets = []
gatherTargets = (d, e) ->
if e.source == d or e.target == d
lTargets.push(e.source.id)
lTargets.push(e.target.id)
return true
return false
uptop = svg.append("g")
x = null
node.append("circle")
.attr("class", "link_node")
.attr("data-source", (d) => d.id)
.attr("data-color", (d) =>
lla++
return llcolors[lla-1]
)
.style("fill", (d) ->
if d.gravatar
return "url(#gravatar-#{d.id})"
else
return "#{d3.select(this).attr('data-color')}"
)
.style("stroke", "black")
.attr("r", (d) => d.size)
.on("mouseover", (d) ->
lTargets.push(d.id)
d3.selectAll("path").style("stroke-opacity", "0.075")
d3.selectAll("path").filter((e) => gatherTargets(d,e) ).style("stroke-opacity", "1").style("z-index", "20")
d3.selectAll("path").filter((e) => e.source == d or e.target).each((o) =>
x = d3.select(this).insert("g", ":first-child").style("stroke", "red !important")
x.append("use").attr("xlink:href", "#" + o.name)
)
d3.selectAll("circle").filter((e) => e.id not in lTargets ).style("opacity", "0.2")
d3.selectAll("text").filter((e) => e.id not in lTargets ).style("opacity", "0.2")
)
.on("mouseout", (d) ->
lTargets = []
if x
x.selectAll("*").remove()
d3.selectAll("circle").style("opacity", null)
d3.selectAll("text").style("opacity", null)
d3.selectAll("path").style("stroke-opacity", null)
)
node.append("a")
.attr("href", (d) => if not d.gravatar then "#" else "contributors.html?page=biography&email=#{d.id}")
.append("text")
.attr("dx", 13)
.attr("dy", ".35em")
.text((d) => d.name)
.on("mouseover", (d) ->
if d.tooltip
tooltip.transition()
.duration(100)
.style("opacity", .9);
tooltip.html("<b>#{d.name}:</b><br/>" + d.tooltip.replace("\n", "<br/>"))
.style("left", (d3.event.pageX + 20) + "px")
.style("top", (d3.event.pageY - 28) + "px");
)
.on("mouseout", (d) ->
#d3.selectAll(".link").filter( (e) => e.source == this.id ).style("stroke-opacity", "0.375")
tooltip.transition()
.duration(200)
.style("opacity", 0);
)
force.on("tick", () ->
link.attr("d", (d) ->
dx = d.target.x - d.source.x
dy = d.target.y - d.source.y
dr = Math.sqrt(dx * dx + dy * dy)
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y
)
node.attr("cx",(d) =>
d.x = Math.max(d.size, Math.min(llwidth - d.size, d.x))
)
.attr("cy", (d) =>
d.y = Math.max(d.size, Math.min(llheight - d.size, d.y))
)
node.attr("transform", (d) => ("translate(" + d.x + "," + d.y + ")"))
)
linked_zoom = () ->
isShift = not not window.event.shiftKey
if isShift
g.attr("transform", "translate("+d3.event.translate + ")scale(" + d3.event.scale + ")");
else
g.attr("transform", "scale(" + d3.event.scale + ")");
svg
.call( d3.behavior.zoom().center([llwidth / 2, llheight / 2]).scaleExtent([0.333, 4]).on("zoom", linked_zoom) )
return [
{
svg: svg,
parent: obj,
force: force,
config: {},
resize: (conf) ->
ns = ""
if conf.height
ns += "height: #{conf.height}px; "
if conf.width
ns += "width: #{conf.width}px; "
svg.attr("style", ns)
llwidth = parseInt(svg.style("width"))
llheight = parseInt(svg.style("height"))
force.size([llwidth, llheight]).distance(llheight/4)
force
.nodes(nodes)
.links(edges)
.start()
},
{linked: true}]