Related
I'm new to d3 (and javascript) and this is my first time posting a question on Stackoverflow. Please let me know if my question is unclear or inappropriate. I would be very appreciated if you give any advice or help.
I'am having a difficulty with filtering nodes in force directed graph. I have searched and used the code posted on other question (D3 force-directed graph - filter nodes and associated links) to hide nodes and its associated links. However, I wasn't able to hide nodes whose links are all invisible.
Here's what I'm trying to do:
If all the links with that node are invisible, the node should also be invisible. Otherwise, if any link related to that node is visible, the node should be visible. I have found this code from jsfiddle.net (zhanghuancs/cuYu8/) but I was not able to use this code on mine.(I cound't figure out how to link jsfiddle code here.)
Could anyone help me?
Here's my code:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>Force Layout</title>
<script type="text/javascript" src="http://code.jquery.com/jquery-2.1.0.js"></script>
<script type="text/javascript" src="http://code.jquery.com/ui/1.11.0/jquery-ui.min.js"></script>
<link rel="stylesheet" type="text/css" href="http://code.jquery.com/ui/1.11.0/themes/smoothness/jquery-ui.css">
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" src="https://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js"></script>
<style id="compiled-css" type="text/css">
.node {
stroke: #fff;
stroke-width: 0.5px;
}
.link {
stroke: #999;
stroke-opacity: .1;
}
d3-tip {
line-height: 1;
color: black;
}
div {
display: inline-block;
}
form {
display: inline-block;
}
svg {
border: solid 1px blue;
}
body,
.container {
background-color: white;
margin: 5px;
}
.graphContainer {
text-shadow: -1px -1px 0 white, 1px -1px 0 white, -1px 1px 0 white, 1px 1px 0 white;
}
#sidebar{
position: absolute;
z-index: 2;
background-color: #FFF;
padding: 10px;
margin: 5px;
border: 1px solid #6895b4;
min-height: 3px;
width: 100px;
top:45px;
right:220px;
}
.item-group {
margin-bottom: 5px;
}
.item-group .item-label {
width: 90px;
text-align: right;
font-family: Arial,sans-serif;
font-size: 14px;
font-weight: bold;
position: relative;
min-height: 1px;
margin-top: 5px;
display: inline;
padding-right: 5px;
font-size: .90em;
}
.checkbox-interaction-group {
margin-left: 10px;
margin-top: 5px;
clear: both;
}
.checkbox-container {
display: block;
min-height: 30px;
vertical-align: middle;
margin-left: 10px;
}
.checkbox-container label{
display:inline;
margin-bottom: 0px;
}
</style>
</head>
<body>
<div id="container" class="container">
<div id="sidebar" style="display: none;">
<div class="item-group">
<label class="item-label">Filter</label>
<div id="filterContainer" class="filterContainer checkbox-interaction-group"></div>
</div>
</div>
<div id="graphContainer" class="graphContainer">
<script type="application/json" id="patent">
{
"nodes":[
{"label": "label1","title":"label1","group": "group1", "type": "label1", "s":1},
{"label": "label2","title":"label2","group": "group1", "type": "label2","s":1},
{"label": "label3","title":"label3","group": "group1", "type": "label3","s":1},
{"id":"5712454", "title": "title1", "group": "group2", "s":0},
{"id":"5497941", "title": "title2", "group": "group2", "s":0},
{"id":"5517952", "title": "title3", "group": "group2", "s":0},
{"id":"4854277", "title": "title4", "group": "group2", "s":0},
{"id":"9556782", "title": "title5", "group": "group2", "s":0}
],
"links":[
{"source": 3, "target": 0, "value": 1},
{"source": 3, "target": 1, "value": 1},
{"source": 3, "target": 2, "value": 1},
{"source": 4, "target": 0, "value": 1},
{"source": 5, "target": 2, "value": 1},
{"source": 6, "target": 1, "value": 1},
{"source": 7, "target": 2, "value": 1},
{"source": 7, "target": 0, "value": 1},
{"source": 6, "target": 2, "value": 1},
{"source": 5, "target": 1, "value": 1}
]
}
</script>
<!-- TODO: Missing CoffeeScript 2 -->
<script type="text/javascript">
//Constants for the SVG
var width = window.innerWidth,
height = window.innerHeight-47;
//Set up the colour scale
var color = d3.scale.category10();
//Set up the force layout
var force = d3.layout.force()
.charge(-200)
.linkDistance(20)
.size([width, height]);
// Set up zoom behavior
var zoom = d3.behavior.zoom().scaleExtent([0.1,5]).on("zoom",redraw);
//Append a SVG to the body of the html page. Assign this SVG as an object to svg
var svg = d3.select("body")
.append("svg")
.attr("width", width-240)
.attr("height", height)
.call(zoom)
.on("dblclick.zoom",null)
.append('g');
var svg2 = d3.select("body")
.append("svg")
.attr("width", 200)
.attr("height", height);
//Set up tooltip
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function (d) {
return d.title + "</span>";
})
svg.call(tip);
//Read the data from the mis element
var patent = document.getElementById('patent').innerHTML;
graph = JSON.parse(patent);
//Creates the graph data structure out of the json data
force.nodes(graph.nodes)
.links(graph.links)
.start()
.friction(0.5);
//Create all the line svgs but without locations yet
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function (d) {return Math.sqrt(d.value);
});
//Do the same with the circles for the nodes - no
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("path")
.attr("class", "node")
.attr("d", d3.svg.symbol()
.type(function(d) { return d3.svg.symbolTypes[d.s]; }))
.style("fill", function (d) {return color(d.group);})
.call(force.drag)
.on('dblclick', connectedNodes)
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
.on("click", function(d){
if (d3.event.shiftKey) {
var url = "https://patents.google.com/patent/US"+ d.id
alert("Redirecting you to " + url)
window.open(url,"", "width=800,height=800");
}
});
var label = svg.selectAll(".mytext")
.data(graph.nodes)
.enter()
.append("text")
.text(function (d) { return d.label; })
.style("text-anchor", "middle")
.style("fill","gray")
.style("font-family", "Arial")
.style("font-size", 8);
//Zoom and Pan function
function redraw() {
svg.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}
var drag = force.drag()
.on("dragstart", function(d) {
d3.event.sourceEvent.stopPropagation();
});
//Now we are giving the SVGs co-ordinates - the force layout is generating the co-ordinates which this code is using to update the attributes of the SVG elements
force.on("tick", function () {
link.attr("x1", function (d) {return d.source.x;})
.attr("y1", function (d) {return d.source.y;})
.attr("x2", function (d) {return d.target.x;})
.attr("y2", function (d) {return d.target.y;});
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
label.attr("x", function(d){ return d.x; })
.attr("y", function (d) {return d.y - 10; });
});
//Highlight function
//Toggle stores whether the highlighting is on
var toggle = 0;
//Create an array logging what is connected to what
var linkedByIndex = {};
for (i = 0; i < graph.nodes.length; i++) {
linkedByIndex[i + "," + i] = 1;
};
graph.links.forEach(function (d) {
linkedByIndex[d.source.index + "," + d.target.index] = 1;
});
//This function looks up whether a pair are neighbours
function neighboring(a, b) {
return linkedByIndex[a.index + "," + b.index];
}
function connectedNodes() {
if (toggle == 0) {
//Reduce the opacity of all but the neighbouring nodes
d = d3.select(this).node().__data__;
node.style("opacity", function (o) {
return neighboring(d, o) | neighboring(o, d) ? 1 : 0.1;
});
label.style("opacity", function (o) {
return neighboring(d, o) | neighboring(o, d) ? 1 : 0.1;
});
link.style("opacity", function (o) {
return d.index==o.source.index | d.index==o.target.index ? 1 : 0.1;
});
//Reduce the op
toggle = 1;
} else {
//Put them back to opacity=1
node.style("opacity", 1);
link.style("opacity", 1);
label.style("opacity",1);
toggle = 0;
}
}
//Search function
var optArray = [];
for (var i = 0; i < graph.nodes.length; i++) {
optArray.push(graph.nodes[i].title);
}
optArray = optArray.sort();
$(function () {
$("#search").autocomplete({
source: optArray
});
});
window.searchNode = searchNode;
function searchNode() {
//find the node
var selectedVal = document.getElementById('search').value;
if (selectedVal == "none") {
node.style("stroke", "white").style("stroke-width", "1");
} else {
var selected = node.filter(function (d, i) {
return d.title != selectedVal;
});
selected.style("opacity", "0");
link.style("opacity", "0");
label.style("opacity", "0");
d3.selectAll(".node, .link").transition()
.duration(2000)
.style("opacity", 1);
label.transition()
.duration(2000)
.style("opacity",1);
var selectedNode = node
.filter(function (d, i) { return d.title == selectedVal; })
.datum();
var scale = zoom.scale();
var desiredPosition = { x: (width-240)/2, y: height/2}; // constants, set to svg center point
zoom.translate([desiredPosition.x - selectedNode.x*scale, desiredPosition.y - selectedNode.y*scale]);
zoom.event(svg);
}
}
//Legend
var legend = svg2.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", 5)
.attr("y",5)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", 30)
.attr("y", 13)
.attr("dy", ".35em")
.style("text-anchor", "start")
.text(function(d) { return d });
// call method to create filter
createFilter();
// method to create filter
function createFilter(){
d3.select(".filterContainer").selectAll("div")
.data(["label1","label2","label3"])
.enter()
.append("div")
.attr("class", "checkbox-container")
.append("label")
.each(function(d) {
// create checkbox for each data
d3.select(this).append("input")
.attr("type", "checkbox")
.attr("id", function(d) {return "chk_" + d;})
.attr("checked", true)
.on("click", function(d, i) {
// register on click event
var lVisibility = this.checked? "visible":"hidden";
filterGraph(d, lVisibility);
})
d3.select(this).append("span")
.text(function(d){return d;});
});
$("#sidebar").show(); // show sidebar
}
var hidden_nodes =[];
// Method to filter graph
function filterGraph(aType, aVisibility){
// change the visibility of the node
// if all the links with that node are invisibile, the node should also be invisible
// otherwise if any link related to that node is visibile, the node should be visible
// change the visibility of the connection link
node.style("visibility", function(o) {
var lOriginalVisibility = $(this).css("visibility");
if (o.type == aType) {
if (aVisibility == "hidden")
{
hidden_nodes.push(o.title);
}
else
{
index = hidden_nodes.indexOf(o.title);
if (index > -1)
{
hidden_nodes.splice(index, 1);
}
}
}
return o.type === aType ? aVisibility : lOriginalVisibility;
});
label.style("visibility", function(o) {
var lOriginalVisibility = $(this).css("visibility");
if (o.type == aType) {
if (aVisibility == "hidden")
{
hidden_nodes.push(o.title);
}
else
{
index = hidden_nodes.indexOf(o.title);
if (index > -1)
{
hidden_nodes.splice(index, 1);
}
}
}
return o.type === aType ? aVisibility : lOriginalVisibility;
});
link.attr("display", function (o) {
////Here the structure of the the link can vary, sometimes it is o["source"]["name"], sometimes it is o["source"]["name"], check it out before you fill in.
var source_name = o["source"]["title"];
var target_name = o["target"]["title"];
var result = hidden_nodes.indexOf(source_name) != -1 || hidden_nodes.indexOf(target_name) != -1 ? "none" : "auto"
return result;
})
;
}
</script>
</div>
</body>
</html>
In my project, I am trying to display India map using d3 and GeoJSON. It works properly, but I am finding difficulties to display each state name on top of the respective state. How to find the centroid of each state.
Please help me to find out, Thanks in advance...,
In the below image, it is displaying at top left corner.
Index.html
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<style>
.state {
fill: none;
stroke: #a9a9a9;
stroke-width: 1;
}
.state:hover {
fill-opacity: 0.5;
}
#tooltip {
position: absolute;
text-align: center;
padding: 20px;
margin: 10px;
font: 12px sans-serif;
background: lightsteelblue;
border: 1px;
border-radius: 2px;
pointer-events: none;
}
#tooltip h4 {
margin: 0;
font-size: 14px;
}
#tooltip {
background: rgba(0, 0, 0, 0.9);
border: 1px solid grey;
border-radius: 5px;
font-size: 12px;
width: auto;
padding: 4px;
color: white;
opacity: 0;
}
#tooltip table {
table-layout: fixed;
}
#tooltip tr td {
padding: 0;
margin: 0;
}
#tooltip tr td:nth-child(1) {
width: 50px;
}
#tooltip tr td:nth-child(2) {
text-align: center;
}
</style>
<body>
<div id="tooltip"></div>
<!-- div to hold tooltip. -->
<div style="height: 600px;" id="statesvg"></div>
<!-- svg to hold the map. -->
<!-- <script src="indiaState.js"></script> -->
<!-- creates india State. -->
<script src="d3.v3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
function tooltipHtml(n, id, d) { /* function to create html content string in tooltip div. */
return "<h4>" + id + "</h4>" +
"<h4>" + n + "</h4>";
}
function getRandomColor() {
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
var sampleData = {}; /* Sample random data. */
["AP", "AR", "AS", "BR", "CT", "DL", "GA", "GJ", "HR", "HP", "JK", "JH", "KA", "KL", "MP", "MH", "MN", "ML", "MZ", "NL", "OR", "PB", "RJ", "SK", "TN", "TR", "UP", "UT", "WB"]
.forEach(function(d) {
var low = Math.round(100 * Math.random());
sampleData[d] = { color: getRandomColor()};
});
/* draw states on id #statesvg */
//iStates.draw("#statesvg", sampleData, tooltipHtml);
d3.select(self.frameElement).style("height", "600px");
d3.json("county.json", function(json) {
console.log(json)
var projection = d3.geo.mercator()
.scale(1)
.translate([0, 0]);
var path = d3.geo.path()
.projection(projection);
function mouseOver(d) {
d3.select("#tooltip").transition().duration(200).style("opacity", .9);
d3.select("#tooltip").html(tooltipHtml(d.n, d.id, sampleData[d.id]))
.style("left", (d3.event.layerX) + "px")
.style("top", (d3.event.layerY) + "px");
}
function mouseOut() {
d3.select("#tooltip").transition().duration(500).style("opacity", 0);
}
function Click(d) {
delete d.d
console.log(d)
}
var svg = d3.select("#statesvg")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.append("g");
svg.selectAll(".state")
.data(json)
.enter()
.append("path")
.attr("class", "state")
.attr("d", function(d) {
return d.d;
})
.style("fill", function(d) {
return sampleData[d.id].color;
})
.on("mousemove", mouseOver).on("mouseout", mouseOut).on("click", Click);
svg.selectAll("text")
.data(json)
.enter()
.append("text")
.attr("fill", "black")
.attr("x", function(d) {
return path.centroid(d.d)[0];
})
.attr("y", function(d) {
return path.centroid(d.d)[1];
})
.attr("text-anchor", "middle")
.attr("dy", ".35em")
.text(function(d) {
return d.id;
});
});
</script>
</body>
</html>
I tried using below code, but its giving both cordinates as NaN, how to solve this...
var projection = d3.geo.mercator()
.scale(1)
.translate([0, 0]);
var path = d3.geo.path()
.projection(projection);
Since your json is returning the actual path d element and not topojson, I'd just use getBBox on the path directly. I also simplified your selections to group the path and the text:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script data-require="d3#3.5.17" data-semver="3.5.17" src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.js"></script>
<style>
.state {
fill: none;
stroke: #a9a9a9;
stroke-width: 1;
}
.state:hover {
fill-opacity: 0.5;
}
#tooltip {
position: absolute;
text-align: center;
padding: 20px;
margin: 10px;
font: 12px sans-serif;
background: lightsteelblue;
border: 1px;
border-radius: 2px;
pointer-events: none;
}
#tooltip h4 {
margin: 0;
font-size: 14px;
}
#tooltip {
background: rgba(0, 0, 0, 0.9);
border: 1px solid grey;
border-radius: 5px;
font-size: 12px;
width: auto;
padding: 4px;
color: white;
opacity: 0;
}
#tooltip table {
table-layout: fixed;
}
#tooltip tr td {
padding: 0;
margin: 0;
}
#tooltip tr td:nth-child(1) {
width: 50px;
}
#tooltip tr td:nth-child(2) {
text-align: center;
}
</style>
</head>
<body>
<div id="tooltip"></div>
<!-- div to hold tooltip. -->
<div style="height: 600px;" id="statesvg"></div>
<!-- svg to hold the map. -->
<!-- <script src="indiaState.js"></script> -->
<!-- creates india State. -->
<!--<script src="d3.v3.min.js"></script>-->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
function tooltipHtml(n, id, d) { /* function to create html content string in tooltip div. */
return "<h4>" + id + "</h4>" +
"<h4>" + n + "</h4>";
}
function getRandomColor() {
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
var sampleData = {}; /* Sample random data. */
["AP", "AR", "AS", "BR", "CT", "DL", "GA", "GJ", "HR", "HP", "JK", "JH", "KA", "KL", "MP", "MH", "MN", "ML", "MZ", "NL", "OR", "PB", "RJ", "SK", "TN", "TR", "UP", "UT", "WB"]
.forEach(function(d) {
var low = Math.round(100 * Math.random());
sampleData[d] = { color: getRandomColor()};
});
/* draw states on id #statesvg */
//iStates.draw("#statesvg", sampleData, tooltipHtml);
d3.select(self.frameElement).style("height", "600px");
d3.json("https://api.myjson.com/bins/l36bq", function(json) {
//console.log(json)
var projection = d3.geo.mercator()
.scale(1)
.translate([0, 0]);
var path = d3.geo.path()
.projection(projection);
function mouseOver(d) {
d3.select("#tooltip").transition().duration(200).style("opacity", .9);
d3.select("#tooltip").html(tooltipHtml(d.n, d.id, sampleData[d.id]))
.style("left", (d3.event.layerX) + "px")
.style("top", (d3.event.layerY) + "px");
}
function mouseOut() {
d3.select("#tooltip").transition().duration(500).style("opacity", 0);
}
function Click(d) {
delete d.d
console.log(d)
}
var svg = d3.select("#statesvg")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.append("g");
var eS = svg.selectAll(".state")
.data(json)
.enter()
.append("g");
eS.append("path")
.attr("class", "state")
.attr("d", function(d) {
return d.d;
})
.style("fill", function(d) {
return sampleData[d.id].color;
})
.on("mousemove", mouseOver).on("mouseout", mouseOut).on("click", Click)
eS.append("text")
.attr("fill", "black")
.attr("transform", function(d) {
var bbox = this.previousSibling.getBBox();
return "translate(" + (bbox.x + bbox.width/2) + "," + (bbox.y + bbox.height/2) + ")";
})
.attr("text-anchor", "middle")
.attr("dy", ".35em")
.text(function(d) {
return d.id;
});
});
</script>
</body>
</html>
i am a beginner in the JS, Vue and D3 programming but already managed to understand the basics, i think :).
So currently i am trying to implement a program which renders a phylogenetical tree using Vue.js and D3.js. Using this example as a start up:
https://bl.ocks.org/lorenzopub/02ccce43d708919ca7c0b242fe1c93f2
So far i managed to get my program to open a tree file and to render it properly. Also i can select certain nodes and change their color accordingly. But my problem now is how to select a subtree and change the color of the children nodes.
Any help and or hints will be appreciated!
Thank you!
Here is my code so far:
<!DOCTYPE html>
<head>
<script type="text/javascript" src="VUE/vue.min.js"></script>
<script type="text/javascript" src="D3/d3.js"></script>
<script>function parseNewick(s) {
var ancestors = [];
var tree = {};
var tokens = s.split(/\s*(;|\(|\)|,|:)\s*/);
for (var i=0; i<tokens.length; i++) {
var token = tokens[i];
switch (token) {
case '(': // new branchset
var subtree = {};
tree.branchset = [subtree];
ancestors.push(tree);
tree = subtree;
break;
case ',': // another branch
var subtree = {};
ancestors[ancestors.length-1].branchset.push(subtree);
tree = subtree;
break;
case ')': // optional name next
tree = ancestors.pop();
break;
case ':': // optional length next
break;
default:
var x = tokens[i-1];
if (x == ')' || x == '(' || x == ',') {
tree.name = token;
} else if (x == ':') {
tree.length = parseFloat(token);
}
}
}
return tree;
};
</script>
<style>
.node {
opacity: 1;
}
.node circle {
fill: #999;
cursor: pointer;
}
.node text {
font: 12px sans-serif;
cursor: pointer;
}
.node--internal circle {
fill: #555;
}
.node--leaf circle {
fill: blue;
}
.node--selected circle {
fill: blue
}
.node--internal text {
text-shadow: 0 1px 0 #fff, 0 -1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff;
}
.link {
fill: none;
stroke: #555;
stroke-opacity: 0.4;
stroke-width: 1.5px;
stroke-dasharray: 1000;
}
.node:hover {
pointer-events: all;
stroke: #ff0000;
}
.node.highlight {
fill: red;
}
.controls {
position: fixed;
top: 16px;
left: 16px;
background: #f8f8f8;
padding: 0.5rem;
display: flex;
flex-direction: column;
}
.controls > * + * {
margin-top: 1rem;
}
label {
display: block;
}
.list-enter-active, .list-leave-active {
transition: all 1s;
}
.list-enter, .list-leave-to /* .list-leave-active for <2.1.8 */ {
opacity: 0;
transform: translateY(30px);
}
.line-enter-active, .line-leave-active {
transition: all 2s;
stroke-dashoffset: 0;
}
.line-enter, .line-leave-to /* .list-leave-active for <2.1.8 */ {
stroke-dashoffset: 1000;
}
</style>
</head>
<body>
<div id="app">
<div class='controls'>
<input type="file" #change="onFileChange">
<label>Search...</label>
<input type="text" v-model="search" />
</div>
<svg v-bind:width="settings.width" v-bind:height="settings.height">
<!-- LINKS BETWEEN NODES -->
<transition-group tag="g" name="line">
<g v-for="link in links" v-bind:key="link.id">
<path class="link" v-bind:style="link.style" v-bind:d="link.d"></path>
</g>
</transition-group>
<!-- LINKS BETWEEN NODES -->
<transition-group tag="g" name="list">
<g class="node" v-on:dblclick="selectSubTree(node,index)" v-on:mouseover="mouseOver(node,index)" v-on:click="select(node,index)" v-for="(node, index) in nodes" v-bind:key="node.id" v-bind:style="node.style" v-bind:class="[node.className, {'highlight': node.highlight}]">
<!-- Circles for each node -->
<circle v-bind:r="node.r" v-bind:style="node.selCol"></circle>
<!-- Finally, text labels -->
<text v-bind:dx="node.textpos.x" v-bind:dy="node.textpos.y" v-bind:style="node.textStyle">{{ node.text }}</text>
</g>
</transition-group>
</svg>
</div>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: function (){
return {
treeFile: '',
search: '',
selected: [],
settings: {
strokeColor: "#19B5FF",
width: 1000,
height: 1000
}
}
},
computed: {
root: function(){
var vm = this;
if(vm.treeFile){
return vm.tree(
d3.hierarchy(parseNewick(vm.treeFile),function(d) { return d.branchset; })
.sum(function(d) { return d.branchset ? 0 : 1; })
.sort(function(a, b) { return (a.value - b.value) || d3.ascending(a.data.length, b.data.length); }));
}
},
tree: function(){
return d3.tree().size([this.settings.height, this.settings.width - 160]);
},
nodes: function(){
var vm = this;
if(this.root){
return vm.root.descendants().map(function(d,i) {
vm.selected[i]=0;
return{
id: d.data.name,
index: i,
r: 7,
className: "node" + (d.children ? " node--internal" : " node--leaf"),
text: d.data.name.substring(d.data.name.lastIndexOf(".") +1),
highlight: d.data.name.toLowerCase().indexOf(vm.search.toLowerCase()) != -1 && vm.search != "",
style: {
transform: "translate(" + d.y + "px," + d.x + "px)",
},
textpos: {
x: d.children ? -10 : 10,
y: 3
},
textStyle: {
textAnchor: d.children ? "end" : "start"
},
children: d.children,
selCol: {fill: '#bfbfbf'}
};
});
}
},
links: function() {
var vm = this;
if (vm.root) {
// here we'll calculate the "d" attribute for each path that is then used in the template where we use "v-for" to loop through all of the links to create <path> elements
return vm.root.descendants().slice(1).map(function(d) {
return {
id: d.data.name,
d: "M" + d.y + "," + d.x + "C" + (d.parent.y + 100) + "," + d.x + " " + (d.parent.y + 100) + "," + d.parent.x + " " + d.parent.y + "," + d.parent.x,
// here we could of course calculate colors depending on data but for now all links share the same color from the settings object that we can manipulate using UI controls and v-model
style: {
stroke: vm.settings.strokeColor
}
};
});
}
}
},
methods: {
onFileChange(e) {
var file = e.target.files[0];
var reader = new FileReader();
var vm = this;
reader.onloadend = function(e) {
vm.treeFile = e.target.result;
};
reader.readAsText(file);
},
select: function(node, index) {
var vm = this;
if(this.selected[index] == 0){
this.selected[index]=1;
vm.nodes[index].selCol='fill: green';
}else{
this.selected[index]=0;
vm.nodes[index].selCol='fill: #bfbfbf';
}
vm.$forceUpdate();
},
mouseOver: function (node,index){
// console.log(node.id + " " + node.isSelected);
},
selectSubTree: function(node,index){
var vm=this;
if(this.selected[index] == 0){
this.selected[index]=1;
vm.nodes[index].selCol='fill: green';
}else{
this.selected[index]=0;
vm.nodes[index].selCol='fill: #bfbfbf';
}
if(node.children){
// console.log(node.id + " HAVE CHILDREN");
// console.log("NC: " + node.children);
node.children.forEach(function (d,i){
// this.selected=i;
vm.selected[i]=1;
vm.nodes[index].selCol='fill: green';
console.log("SELECTING: " + d.data.name + " " + i);
console.log(vm.$refs.A);
// if(d.isSelected){
// this.selected=i;
// console.log("CS :" + this.selected);
// }
});
}else{
// console.log(node.id + " DONT HAVE CHILDREN");
}
vm.$forceUpdate();
}
}
})
</script>
</body>
I cant find a straight forward answer anywhere. How can I make a drop down menu appear on click of an svg circle? I dont want it on display until the shape has been clicked. Can someone help me please?
Download this and include in your html : https://github.com/patorjk/d3-context-menu
And to use on click of a circle :
circle.on('contextmenu', d3.contextMenu(menu)); // attach menu to element
Thats if you want plugins :)
EDIT
NO PLUGINS
If you don't want plugins just create a list of things you want to show, prevent the default behaviour and on right click show the created list and click anywhere else hide it.
Here is a fiddle (not mine) : http://jsfiddle.net/thatoneguy/u2kJq/727/
List he creates :
<ul class='custom-menu'>
<li data-action = "first">First thing</li>
<li data-action = "second">Second thing</li>
<li data-action = "third">Third thing</li>
</ul>
This will show on right click. Now to implement this with D3 you can use event.preventDefault(); like the fiddle above, you have to use d3.event.preventDefault();. Also you have to position the menu, so change the css top and left to position on mouse position :
css({
top: d3.event.pageY + "px",
left: d3.event.pageX + "px"
});
Here is a simple implementation of what I have described :
var w = 500;
var h = 50;
var dataset = [5, 10, 15, 20, 25];
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var circles = svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle");
circles.attr("cx", function(d, i) {
return (i * 50) + 25;
})
.attr("cy", h / 2)
.attr("r", function(d) {
return d;
})
.on("contextmenu", function(event) {
// Avoid the real one
d3.event.preventDefault();
// Show contextmenu
$(".custom-menu").finish().toggle(100).
// In the right position (the mouse)
css({
top: d3.event.pageY + "px",
left: d3.event.pageX + "px"
});
});;
//$(document).bind
// If the document is clicked somewhere
$(document).bind("mousedown", function(e) {
// If the clicked element is not the menu
if (!$(e.target).parents(".custom-menu").length > 0) {
// Hide it
$(".custom-menu").hide(100);
}
});
// If the menu element is clicked
$(".custom-menu li").click(function() {
// This is the triggered action name
switch ($(this).attr("data-action")) {
// A case for each action. Your actions here
case "first":
alert("first");
break;
case "second":
alert("second");
break;
case "third":
alert("third");
break;
}
// Hide it AFTER the action was triggered
$(".custom-menu").hide(100);
});
.custom-menu {
display: none;
z-index: 1000;
position: absolute;
overflow: hidden;
border: 1px solid #CCC;
white-space: nowrap;
font-family: sans-serif;
background: #FFF;
color: #333;
border-radius: 5px;
}
.custom-menu li {
padding: 8px 12px;
cursor: pointer;
}
.custom-menu li:hover {
background-color: #DEF;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<body>
<ul class='custom-menu'>
<li data-action = "first">First thing</li>
<li data-action = "second">Second thing</li>
<li data-action = "third">Third thing</li>
</ul>
</body>
If you want to do something when clicking one of the menu items, you can use onclick in the html like so :
<li onclick='doSomething()' data-action = "third">Third thing</li>
Or you can just give the individual items an id like so :
<li id='listItemOne' data-action = "third">Third thing</li>
And then add event listener :
document.getElementById('listItemOne').addEventListener('click', doSomething)
#thisOneGuy gave a great answer, but since I've already coded it, here's a solution that builds the context menu with svg:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.4.6" data-semver="3.4.6" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.4.6/d3.min.js"></script>
<style>
svg {
font: 12px sans-serif;
}
</style>
</head>
<body>
<script>
var menu = [{
title: 'A really, really long item',
action: function(elem, d, i) {
console.log('Item #1 clicked!');
console.log('The data for this circle is: ' + d);
}
}, {
title: 'Item #2',
action: function(elem, d, i) {
console.log('You have clicked the second item!');
console.log('The data for this circle is: ' + d);
}
}]
var data = [1, 2, 3];
var g = d3.select('body').append('svg')
.on('click', function(){
m.style('display', 'none');
})
.attr('width', 500)
.attr('height', 400)
.append('g');
g.selectAll('circles')
.data(data)
.enter()
.append('circle')
.attr('r', 30)
.attr('fill', 'steelblue')
.attr('cx', function(d) {
return 100;
})
.attr('cy', function(d) {
return d * 100;
})
.on('contextmenu', function(d) {
var coors = d3.mouse(this);
m.attr('transform', 'translate(' + coors[0] + ',' + coors[1] + ')');
m.style('display', 'block');
m.datum(d);
d3.event.preventDefault();
});
/* build context menu */
var m = g.append("g")
m.style('display', 'none');
var r = m.append('rect')
.attr('height', menu.length * 25)
.style('fill', "#eee");
var t = m.selectAll('menu_item')
.data(menu)
.enter()
.append('g')
.attr('transform', function(d, i) {
return 'translate(' + 10 + ',' + ((i + 1) * 20) + ')';
})
.on('mouseover', function(d){
d3.select(this).style('fill', 'steelblue');
})
.on('mouseout', function(d){
d3.select(this).style('fill', 'black');
})
.on('click', function(d,i){
d.action(d, d3.select(this.parentNode).datum(), i);
})
.append('text')
.text(function(d) {
return d.title;
});
var w = 0;
t.each(function(d){
var l = this.getComputedTextLength();
if (l > w) w = l;
})
r.attr('width', w + 20);
</script>
</body>
</html>
I am trying to get some data in my map, however I have the following error:
Uncaught TypeError: Cannot read property 'length' of undefined.
This is my code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>D3 Test</title>
<script type="text/javascript" src="http://localhost/webserver/d3/d3.js"></script>
<script type="text/javascript" src="http://localhost/webserver/topojson/topojson.js"></script>
<style type="text/css">
div.bar {
display: inline-block;
width: 20px;
height: 75px;
background-color: teal;
margin-right: 2px;
}
.pumpkin {
fill: rgba(128, 0, 128, 0.75);
stroke: yellow;
stroke-width: 5;
}
.apple {
fill: rgba(0, 255, 0, 0.55);
stroke: green;
stroke-width: 15;
}
.orange {
fill: rgba(255, 255, 0, 0.55);
stroke: orange;
stroke-width: 10;
}
.subunit { fill: #cdc; }
.subunit-label {
fill: #777;
fill-opacity: .25;
font-size: 30px;
font-weight: 300;
text-anchor: middle;}
.provincie {fill: none; }
.Utrecht {fill: #ddd; }
.Zuid-Holland {fill: #dde; }
.Noord-Holland {fill: #dee; }
.Drenthe {fill: #aae; }
.Gelderland {fill: #eee; }
.Friesland {fill: #ddc; }
.Groningen {fill: #dcc; }
.Limburg {fill: #ccc; }
.Noord-Brabant {fill: #ddb; }
.Overijssel {fill: #dbb; }
.Zeeland {fill: #bbb; }
</style>
</head>
<body>
<script type="text/javascript">
var width = 960, height = 860;
var projection = d3.geo.albers()
.center([6, 49.40])
.rotate([0, -1.9])
.parallels([50, 60])
.scale(11000)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
//Define quantize scale to sort data values into buckets of color
var color = d3.scale.quantize()
.range(["rgb(237,248,233)","rgb(186,228,179)","rgb(116,196,118)","rgb(49,163,84)","rgb(0,109,44)"]);
//Load in data
d3.csv("http://localhost/webserver/data/beroepsbevolking.csv", function(data) {
//Set input domain for color scale
color.domain([
d3.min(data, function(d) { return d.value; }),
d3.max(data, function(d) { return d.value; })
]);
d3.json("http://localhost/webserver/data/nl2.json", function(error, nl) {
svg.selectAll(".subunit")
.data(topojson.object(nl, nl.objects.subunits).geometries)
.enter().append("path")
.attr("class", function(d) { return "subunit " + d.id; })
.attr("d", path);
svg.selectAll(".subunit-label")
.data(topojson.object(nl, nl.objects.subunits).geometries)
//svg.selectAll(".provincie")
.data(topojson.object(nl, nl.objects.provincies).geometries)
.enter().append("path")
// .attr("class", function(d) { return "provincie " + d.properties.name; })
.attr("d", path);
//Merge the ag. data and GeoJSON
//Loop through once for each ag. data value
d3.json("http://localhost/webserver/data/nl2.json" ,function(json) {
for (var i = 0; i < data.length; i++) {
//Grab provincie name
var dataProvincie = data[i].provincie;
//Grab data value, and convert from string to float
var dataValue = parseFloat(data[i].value);
//Find the corresponding provincie inside the GeoJSON
for (var j = 0; j < json.features.length; j++) {
var jsonProvincie = json.features[j].properties.name;
if (dataProvincie == jsonProvincie) {
//Copy the data value into the JSON
json.features[j].properties.value = dataValue;
//Stop looking through the JSON
break;
}
}
}
//Bind data and create one path per GeoJSON feature
svg.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d", path)
.style("fill", function(d) {
//Get data value
var value = d.properties.value;
if (value) {
//If value exists…
return color(value);
} else {
//If value is undefined…
return "#ccc";
}
});
});
});
});
</script>
</body>
</html>
It goes wrong at line 115:
for (var j = 0; j < json.features.length; j++) {
Why?
Your JSON doesn't have a features field. In addition, you've missed an argument in the d3.json function -- in your second call, json will actually be bound to the error. Change
d3.json("http://localhost/webserver/data/nl2.json" ,function(json) {
to
d3.json("http://localhost/webserver/data/nl2.json" ,function(error, json) {
The second call to d3.json seems unnecessary -- at that point, you have the data in nl2.json already in the nl variable.
The name of the parameter is "json", not "data".
Make them the same name and it should work.
Also; get a hint from the error message; "data" is undefined.