I'm having trouble with a d3.js bar chart. The full code and working demo are here: https://jsfiddle.net/monotasker/yurqkm3n/. The issue is that when I use the brush on the lower (context) chart, the upper (focus) chart overflows its margins.
Here is the function that the brush activates:
function brushed() {
time.domain(brush.empty() ? navTime.domain() : brush.extent())
.range([0, (width)]);
x.domain(max_extent_in_days(time))
.rangeBands([margin.left, (width)], 0.1, 0);
focus.selectAll('.bar.stack')
.attr('transform', function(d) {return "translate(" + time(d.date) + ",0)"; })
.attr('width', x.rangeBand());
focus.selectAll('.rect')
.attr('width', x.rangeBand());
focus.selectAll('.line').attr('d', line);
focus.select(".x.axis").call(x_axis);
};
One oddity about the chart is that there are two scales that together control the x axis: "time" and "x". The "time" scale is used for positioning the content on the x axis, but the "x" scale (which converts "time" to an ordinal scale) is used for calculating bar widths. I don't know whether this approach is somehow the source of the problem.
Any help is greatly appreciated.
As in this example where Mr. Bostock applies a clip path to the area path, you need to apply one to the bars. The easiest way is to put them in their own g then apply the clip-path to that:
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
...
// Plot the stacked bars
var focus_bar = focus
.append("g") //<-- g just for the bars to be clipped
.attr("class","barClipper")
.style('clip-path', 'url(#clip)') //<-- apply clipping
.selectAll('.g')
.data(data.answer_counts)
.enter().append('g')
.attr('class', 'g bar stack')
.attr('transform', function(d) {
return "translate(" + x(d.date) + ",0)"
});
Full code:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
<style>
#milestones_attempts_combo .context {
/*fill: #efefef; */
}
#milestones_attempts_combo .focus .right,
#milestones_attempts_combo .context .right {
fill: #0064cd;
}
#milestones_attempts_combo .focus .wrong,
#milestones_attempts_combo .context .wrong {
fill: #cd001d;
}
#milestones_attempts_combo .y-axis-label,
#milestones_attempts_combo .y2-axis-label {
fill: #aaa;
font-size: 120%;
}
#milestones_attempts_combo .focus-line,
#milestones_attempts_combo .context-line {
stroke: orange;
stroke-width: 2;
fill-opacity: 0;
}
.brush .extent {
stroke: #efefef;
fill: #666;
fill-opacity: .125;
shape-rendering: crispEdges;
}
.stats-container {
width: 700px;
margin: 0 auto;
}
.chart .axis line,
.chart .axis path,
.chart .tick line {
fill: none;
stroke: #aaa;
shape-rendering: crispEdges;
}
</style>
</head>
<body>
<div id="milestones_attempts_combo"></div>
<script>
(function() {
var myjsondata = {
"answer_counts": [{
"date": "2012-09-13",
"ys": [{
"y1": 9,
"y0": 0,
"class": "right"
}, {
"y1": 12,
"y0": 9,
"class": "wrong"
}],
"total": 12
}, {
"date": "2012-09-16",
"ys": [{
"y1": 16,
"y0": 0,
"class": "right"
}, {
"y1": 17,
"y0": 16,
"class": "wrong"
}],
"total": 17
}, {
"date": "2012-09-17",
"ys": [{
"y1": 12,
"y0": 0,
"class": "right"
}, {
"y1": 14,
"y0": 12,
"class": "wrong"
}],
"total": 14
}, {
"date": "2012-09-19",
"ys": [{
"y1": 2,
"y0": 0,
"class": "right"
}, {
"y1": 2,
"y0": 2,
"class": "wrong"
}],
"total": 2
}, {
"date": "2012-09-20",
"ys": [{
"y1": 12,
"y0": 0,
"class": "right"
}, {
"y1": 16,
"y0": 12,
"class": "wrong"
}],
"total": 16
}, {
"date": "2012-09-21",
"ys": [{
"y1": 20,
"y0": 0,
"class": "right"
}, {
"y1": 22,
"y0": 20,
"class": "wrong"
}],
"total": 22
}, {
"date": "2012-09-22",
"ys": [{
"y1": 1,
"y0": 0,
"class": "right"
}, {
"y1": 1,
"y0": 1,
"class": "wrong"
}],
"total": 1
}, {
"date": "2012-09-23",
"ys": [{
"y1": 10,
"y0": 0,
"class": "right"
}, {
"y1": 12,
"y0": 10,
"class": "wrong"
}],
"total": 12
}, {
"date": "2012-09-24",
"ys": [{
"y1": 9,
"y0": 0,
"class": "right"
}, {
"y1": 9,
"y0": 9,
"class": "wrong"
}],
"total": 9
}, {
"date": "2012-09-25",
"ys": [{
"y1": 2,
"y0": 0,
"class": "right"
}, {
"y1": 4,
"y0": 2,
"class": "wrong"
}],
"total": 4
}, {
"date": "2012-09-29",
"ys": [{
"y1": 26,
"y0": 0,
"class": "right"
}, {
"y1": 37,
"y0": 26,
"class": "wrong"
}],
"total": 37
}, {
"date": "2012-10-01",
"ys": [{
"y1": 44,
"y0": 0,
"class": "right"
}, {
"y1": 44,
"y0": 44,
"class": "wrong"
}],
"total": 44
}, {
"date": "2012-10-02",
"ys": [{
"y1": 2,
"y0": 0,
"class": "right"
}, {
"y1": 2,
"y0": 2,
"class": "wrong"
}],
"total": 2
}, {
"date": "2012-10-03",
"ys": [{
"y1": 13,
"y0": 0,
"class": "right"
}, {
"y1": 13,
"y0": 13,
"class": "wrong"
}],
"total": 13
}, {
"date": "2012-10-05",
"ys": [{
"y1": 47,
"y0": 0,
"class": "right"
}, {
"y1": 47,
"y0": 47,
"class": "wrong"
}],
"total": 47
}, {
"date": "2012-10-08",
"ys": [{
"y1": 17,
"y0": 0,
"class": "right"
}, {
"y1": 17,
"y0": 17,
"class": "wrong"
}],
"total": 17
}, {
"date": "2012-10-09",
"ys": [{
"y1": 19,
"y0": 0,
"class": "right"
}, {
"y1": 20,
"y0": 19,
"class": "wrong"
}],
"total": 20
}, {
"date": "2012-10-10",
"ys": [{
"y1": 31,
"y0": 0,
"class": "right"
}, {
"y1": 31,
"y0": 31,
"class": "wrong"
}],
"total": 31
}, {
"date": "2012-10-11",
"ys": [{
"y1": 6,
"y0": 0,
"class": "right"
}, {
"y1": 6,
"y0": 6,
"class": "wrong"
}],
"total": 6
}, {
"date": "2012-10-14",
"ys": [{
"y1": 6,
"y0": 0,
"class": "right"
}, {
"y1": 6,
"y0": 6,
"class": "wrong"
}],
"total": 6
}, {
"date": "2012-10-19",
"ys": [{
"y1": 30,
"y0": 0,
"class": "right"
}, {
"y1": 32,
"y0": 30,
"class": "wrong"
}],
"total": 32
}, {
"date": "2012-10-20",
"ys": [{
"y1": 20,
"y0": 0,
"class": "right"
}, {
"y1": 22,
"y0": 20,
"class": "wrong"
}],
"total": 22
}, {
"date": "2012-10-23",
"ys": [{
"y1": 20,
"y0": 0,
"class": "right"
}, {
"y1": 20,
"y0": 20,
"class": "wrong"
}],
"total": 20
}, {
"date": "2012-10-24",
"ys": [{
"y1": 20,
"y0": 0,
"class": "right"
}, {
"y1": 21,
"y0": 20,
"class": "wrong"
}],
"total": 21
}, {
"date": "2012-10-29",
"ys": [{
"y1": 22,
"y0": 0,
"class": "right"
}, {
"y1": 22,
"y0": 22,
"class": "wrong"
}],
"total": 22
}, {
"date": "2012-11-01",
"ys": [{
"y1": 1,
"y0": 0,
"class": "right"
}, {
"y1": 1,
"y0": 1,
"class": "wrong"
}],
"total": 1
}, {
"date": "2012-11-02",
"ys": [{
"y1": 26,
"y0": 0,
"class": "right"
}, {
"y1": 29,
"y0": 26,
"class": "wrong"
}],
"total": 29
}, {
"date": "2012-11-05",
"ys": [{
"y1": 23,
"y0": 0,
"class": "right"
}, {
"y1": 27,
"y0": 23,
"class": "wrong"
}],
"total": 27
}, {
"date": "2012-11-06",
"ys": [{
"y1": 10,
"y0": 0,
"class": "right"
}, {
"y1": 11,
"y0": 10,
"class": "wrong"
}],
"total": 11
}, {
"date": "2012-11-07",
"ys": [{
"y1": 15,
"y0": 0,
"class": "right"
}, {
"y1": 18,
"y0": 15,
"class": "wrong"
}],
"total": 18
}, {
"date": "2012-11-09",
"ys": [{
"y1": 26,
"y0": 0,
"class": "right"
}, {
"y1": 31,
"y0": 26,
"class": "wrong"
}],
"total": 31
}, {
"date": "2012-11-10",
"ys": [{
"y1": 2,
"y0": 0,
"class": "right"
}, {
"y1": 2,
"y0": 2,
"class": "wrong"
}],
"total": 2
}, {
"date": "2012-11-14",
"ys": [{
"y1": 15,
"y0": 0,
"class": "right"
}, {
"y1": 17,
"y0": 15,
"class": "wrong"
}],
"total": 17
}, {
"date": "2012-11-16",
"ys": [{
"y1": 20,
"y0": 0,
"class": "right"
}, {
"y1": 23,
"y0": 20,
"class": "wrong"
}],
"total": 23
}, {
"date": "2012-11-18",
"ys": [{
"y1": 2,
"y0": 0,
"class": "right"
}, {
"y1": 2,
"y0": 2,
"class": "wrong"
}],
"total": 2
}, {
"date": "2012-11-19",
"ys": [{
"y1": 23,
"y0": 0,
"class": "right"
}, {
"y1": 26,
"y0": 23,
"class": "wrong"
}],
"total": 26
}, {
"date": "2012-11-21",
"ys": [{
"y1": 2,
"y0": 0,
"class": "right"
}, {
"y1": 3,
"y0": 2,
"class": "wrong"
}],
"total": 3
}, {
"date": "2012-11-23",
"ys": [{
"y1": 9,
"y0": 0,
"class": "right"
}, {
"y1": 10,
"y0": 9,
"class": "wrong"
}],
"total": 10
}, {
"date": "2012-11-26",
"ys": [{
"y1": 2,
"y0": 0,
"class": "right"
}, {
"y1": 2,
"y0": 2,
"class": "wrong"
}],
"total": 2
}, {
"date": "2012-11-27",
"ys": [{
"y1": 25,
"y0": 0,
"class": "right"
}, {
"y1": 26,
"y0": 25,
"class": "wrong"
}],
"total": 26
}, {
"date": "2012-11-28",
"ys": [{
"y1": 73,
"y0": 0,
"class": "right"
}, {
"y1": 75,
"y0": 73,
"class": "wrong"
}],
"total": 75
}, {
"date": "2012-12-02",
"ys": [{
"y1": 19,
"y0": 0,
"class": "right"
}, {
"y1": 20,
"y0": 19,
"class": "wrong"
}],
"total": 20
}, {
"date": "2012-12-05",
"ys": [{
"y1": 1,
"y0": 0,
"class": "right"
}, {
"y1": 1,
"y0": 1,
"class": "wrong"
}],
"total": 1
}, {
"date": "2013-01-11",
"ys": [{
"y1": 2,
"y0": 0,
"class": "right"
}, {
"y1": 3,
"y0": 2,
"class": "wrong"
}],
"total": 3
}, {
"date": "2013-01-16",
"ys": [{
"y1": 8,
"y0": 0,
"class": "right"
}, {
"y1": 8,
"y0": 8,
"class": "wrong"
}],
"total": 8
}],
"badge_set_reached": [{
"date": "2014-05-24",
"set": 3
}, {
"date": "2014-09-29",
"set": 5
}, {
"date": "2014-11-10",
"set": 6
}, {
"date": "2014-08-29",
"set": 7
}, {
"date": "2015-08-12",
"set": 9
}, {
"date": "2016-01-09",
"set": 9
}]
}
var data = myjsondata;
// preprocess data to get usable date objects
var parse_date = d3.time.format('%Y-%m-%d').parse
for (var i in data) {
var myObj = data[i]
for (var j in myObj) {
myObj[j].date = parse_date(myObj[j].date);
}
}
console.log(data);
// set variables
var margin = {
left: 60,
right: 60,
top: 10,
bottom: 140
},
navMargin = {
top: 300,
right: 60,
bottom: 40,
left: 60
},
height = 400 - margin.top - margin.bottom,
width = 800 - margin.left - margin.right,
navWidth = width, // for context band
navHeight = 400 - navMargin.top - navMargin.bottom,
max_total_counts = d3.max(data.answer_counts, function(d) {
return d.total;
}),
max_extent_dates = d3.extent(data.answer_counts, function(d) {
return d.date;
});
max_extent_in_days = function(timescale) {
return d3.time.days(timescale.domain()[0], d3.time.day.offset(timescale.domain()[1], 1))
};
// scales
var y = d3.scale.linear().domain([0, max_total_counts]).rangeRound([height, 0]),
navY = d3.scale.linear().domain([0, max_total_counts]).rangeRound([navHeight, 0]),
time = d3.time.scale().domain(max_extent_dates).range([0, width]),
navTime = d3.time.scale().domain(max_extent_dates).range([0, width]),
x = d3.scale.ordinal().domain(max_extent_in_days(time)).rangeBands([0, width], 0.1, 0), // used to calculate bar widths
navX = d3.scale.ordinal().domain(max_extent_in_days(navTime)).rangeBands([0, width], 0.1, 0),
y2 = d3.scale.linear().domain([0, d3.max(data.badge_set_reached, function(d) {
return d.set
})]).rangeRound([height, 0]);
navY2 = d3.scale.linear().domain([0, d3.max(data.badge_set_reached, function(d) {
return d.set
})]).rangeRound([navHeight, 0]);
// axes
var x_axis = d3.svg.axis().scale(time).orient('bottom') // .tickFormat(d3.time.format('%Y-%m-%d'))
.outerTickSize(0), // at start and end of axis line
nav_x_axis = d3.svg.axis().scale(navTime).orient('bottom') // .tickFormat(d3.time.format('%Y-%m-%d'))
.outerTickSize(0), // at start and end of axis line
y_axis = d3.svg.axis().scale(y).orient('left').tickFormat(d3.format('d'));
y2_axis = d3.svg.axis().scale(y2).orient('right').tickFormat(d3.format('d'));
// add brush
var brush = d3.svg.brush()
.x(navTime)
.on("brush", brushed);
// svg context
var svg = d3.select("#milestones_attempts_combo")
.append('svg')
.attr('class', 'chart')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var focus = svg.append('g')
.attr('class', 'focus')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var context = svg.append('g')
.attr('class', 'context')
.attr('transform', 'translate(' + navMargin.left + ',' + navMargin.top + ')');
// Plot the stacked bars
var focus_bar = focus
.append("g")
.attr("class","barClipper")
.style('clip-path', 'url(#clip)')
.selectAll('.g')
.data(data.answer_counts)
.enter().append('g')
.attr('class', 'g bar stack')
.attr('transform', function(d) {
return "translate(" + x(d.date) + ",0)"
});
var focus_rects = focus_bar.selectAll('rect')
.data(function(d) {
return d.ys;
})
.enter().append('rect')
.attr('width', x.rangeBand())
.attr('height', function(d) {
return y(d.y0) - y(d.y1);
})
.attr('y', function(d) {
return y(d.y1);
})
.attr('class', function(d) {
return 'rect ' + d['class'];
});
var context_bar = context.selectAll('.g')
.data(data.answer_counts)
.enter().append('g')
.attr('class', 'g')
.attr('transform', function(d) {
return "translate(" + navTime(d.date) + ",0)"
});
var context_rects = context_bar.selectAll('rect')
.data(function(d) {
return d.ys;
})
.enter().append('rect')
.attr('width', navX.rangeBand())
.attr('height', function(d) {
return navY(d.y0) - navY(d.y1);
})
.attr('y', function(d) {
return navY(d.y1);
})
.attr('class', function(d) {
return 'rect ' + d['class'];
});
// Plot lines
var line = d3.svg.line()
.x(function(d) {
return time(d.date)
})
.y(function(d) {
return y2(d.set)
})
.interpolate('step-after');
var focus_line = focus.append('path')
.datum(data.badge_set_reached)
.attr('class', 'line focus-line')
.attr('d', line);
var line2 = d3.svg.line()
.x(function(d) {
return time(d.date)
})
.y(function(d) {
return navY2(d.set)
})
.interpolate('step-after');
var context_line = context.append('path')
.datum(data.badge_set_reached)
.attr('class', 'line context-line')
.attr('d', line2);
// Plot axes
focus.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0, ' + height + ')')
.call(x_axis)
.selectAll('text')
.style('text-anchor', 'end')
// .attr('transform', 'rotate(-45)')
// .attr('dx', '-.5em')
// .attr('dy', '.5em');
focus.append('g')
.attr('class', 'y axis')
.attr('transform', 'translate(0, 0)')
.call(y_axis);
focus.append('g')
.attr('class', 'y2 axis')
.attr('transform', 'translate(' + width + ', 0)')
.call(y2_axis);
context.append('g')
.attr('class', 'navX axis')
.attr('transform', 'translate(0, ' + navHeight + ')')
.call(nav_x_axis);
// Label axes
svg.append('text')
.attr('class', 'label y-axis-label')
.attr('transform', 'rotate(-90)')
.attr('x', 0 - ((height + margin.top) / 2))
.attr('y', 0)
.attr('dy', 20)
.style('text-anchor', 'middle')
.text('Paths Attempted');
svg.append('text')
.attr('class', 'label y2-axis-label')
.attr('transform', 'rotate(+90)')
.attr('y', 0 - (width + margin.left + margin.right))
.attr('x', 0 + ((height + margin.top) / 2))
.attr('dy', '2em')
.style('text-anchor', 'middle')
.text('Badge Set Reached');
// Add brush to svg
context.append('g')
.attr('class', 'x brush')
.call(brush)
.selectAll("rect")
.attr("y", -6)
.attr("height", navHeight + 7);
function brushed() {
time.domain(brush.empty() ? navTime.domain() : brush.extent())
.range([0, (width)]);
x.domain(max_extent_in_days(time))
.rangeBands([margin.left, (width)], 0.1, 0);
focus.selectAll('.bar.stack')
.attr('transform', function(d) {
return "translate(" + time(d.date) + ",0)";
})
.attr('width', x.rangeBand());
focus.selectAll('.rect')
.attr('width', x.rangeBand());
focus.selectAll('.line').attr('d', line);
focus.select(".x.axis").call(x_axis);
};
})();
</script>
</body>
</html>
Related
I am trying to implement paging in d3 tree. Can anyone please help me with the same.
Please find below my code with screenshots of current and expected output.
The data_tree is the input data variable. This gets read by script below and produces hierarchy tree as showed in screenshot
data_tree = {
"Type": "Root",
"id": 0,
"name": "ERM",
"ParentDocType": "EnterpriseWide",
"children": [{
"Type": "Stem",
"id": 4,
"name": "RG",
"ParentDocType": "EnterpriseWide",
"children": [{
"Type": "Stem",
"id": 5,
"name": "WCR F",
"ParentDocType": "WholesaleCreditRisk",
"children": [{
"Type": "Stem",
"id": 50,
"name": "WCR P",
"ParentDocType": "WholesaleCreditRisk",
"children": [{
"Type": "Leaf",
"id": 8,
"name": "IBQA SD",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 9,
"name": "WCR EMS",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 28,
"name": "WCR DD & RGEC",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 29,
"name": "PMM",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 30,
"name": "PRRM",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 31,
"name": "WCR Rep",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 32,
"name": "RR",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 33,
"name": "CO",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 34,
"name": "LD",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 35,
"name": "ESRM",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 36,
"name": "TPCR",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 37,
"name": "InterA",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 38,
"name": "DR",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 39,
"name": "MAR",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 40,
"name": "BSLM",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 51,
"name": "CCR Meas",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 52,
"name": "WLP",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 53,
"name": "TTS Pro",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 54,
"name": "CI",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 55,
"name": "LL",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 56,
"name": "SP",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 57,
"name": "CC",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 58,
"name": "CRE",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 59,
"name": "Sec",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 60,
"name": "SS",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
}
]
}]
},
{
"Type": "Stem",
"id": 10,
"name": "GCMP",
"ParentDocType": "EnterpriseWide",
"children": [{
"Type": "Leaf",
"id": 11,
"name": "WCR CMS",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
}]
},
{
"Type": "Stem",
"id": 12,
"name": "REAVP",
"ParentDocType": "EnterpriseWide",
"children": [{
"Type": "Stem",
"id": 13,
"name": "CREAVS",
"ParentDocType": "WholesaleCreditRisk",
"children": [{
"Type": "Leaf",
"id": 14,
"name": "CREAVP",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
}]
}]
}
]
}]
};
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 960 - margin.right - margin.left,
height = 800 - margin.top - margin.bottom;
var root =JSON.parse(data_tree);
var i = 0,
duration = 750,
rectW = 150,
rectH = 50;
//var tree = d3.layout.tree().nodeSize([70, 40]);
var nodeWidth = 150;
var nodeHeight = 50;
var horizontalSeparationBetweenNodes = 16;
var verticalSeparationBetweenNodes = 128;
var tree = d3.layout.tree()
.nodeSize([nodeWidth + horizontalSeparationBetweenNodes, nodeHeight + verticalSeparationBetweenNodes])
.separation(function(a, b) {
return a.parent == b.parent ? 1 : 1.25;
});
/*var diagonal = d3.svg.diagonal()
.projection(function (d) {
return [d.x + rectW / 2, d.y + rectH / 6];
});*/
var diagonal = d3.svg.diagonal()
.target(function(d) {
var o = d.target;
o.y = o.y - 50
return o;
})
.projection(function(d) {
return [d.x + rectW / 2, d.y + rectH];
});
var svg = d3.select("#body").append("svg").attr("width", document.getElementById("body").style.width).attr("height", document.getElementById("body").style.height)
.call(zm = d3.behavior.zoom().scaleExtent([0.5, 3]).on("zoom", redraw)).append("g")
.attr("transform", "translate(" + 650 + "," + 20 + ")scale(0.7)");
//necessary so that zoom knows where to zoom and unzoom from
zm.translate([650, 20]);
root.x0 = 0;
root.y0 = height / 2;
d3.select("#myCheckbox").on("change", enableLink);
enableLink(root);
d3.select("#body").style("height", "800px");
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
x = text.attr("x"),
y = text.attr("y"),
dy = 0, //parseFloat(text.attr("dy")),
tspan = text.text(null)
.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
function enableLink(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 125;
});
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + source.x0 + "," + source.y0 + ")";
}).on("click", click);
nodeEnter.append("rect")
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "blue")
.attr("rx", 4)
.attr("ry", 4)
.attr("stroke-width", 2)
.style("fill", function(d) {
if (d.ParentDocType == "EnterpriseWide") return "darkblue";
if (d.ParentDocType == "WholesaleCreditRisk") return "blue";
return d._children ? "lightsteelblue" : "#fff";
});
nodeEnter.append("text")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) {
return d.name;
}).call(wrap, rectW - 10);
nodeEnter
.append("a")
.attr("xlink:href", function(d) {
return d.url;
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
nodeUpdate.select("rect")
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "blue")
.attr("stroke-width", 2)
.style("fill", function(d) {
if (d.ParentDocType == "EnterpriseWide") return "darkblue";
if (d.ParentDocType == "WholesaleCreditRisk") return "blue";
return d._children ? "lightsteelblue" : "#fff";
});
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.x + "," + source.y + ")";
})
.remove();
nodeExit.select("rect")
.attr("width", rectW)
.attr("height", rectH)
//.attr("width", bbox.getBBox().width)""
//.attr("height", bbox.getBBox().height)
.attr("stroke", "blue")
.attr("stroke-width", 2);
nodeExit.select("text");
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) {
d.y = d.y + 100;
return d.target.id;
});
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0 + 100
};
return diagonal({
source: o,
target: o
});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y + 100;
});
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
enableLink(d);
}
//Redraw for zoom
function redraw() {
//console.log("here", d3.event.translate, d3.event.scale);
svg.attr("transform",
"translate(" + d3.event.translate + ")" +
" scale(" + d3.event.scale + ")");
}
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
fill: white;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
body {
overflow: hidden;
}
.switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
}
input:checked+.slider {
background-color: #2196F3;
}
input:focus+.slider {
box-shadow: 0 0 1px #2196F3;
}
input:checked+.slider:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
/* Rounded sliders */
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}
.box {
float: left;
height: 20px;
width: 20px;
margin-bottom: 15px;
border: 1px solid black;
}
.darkblue {
background-color: darkblue;
}
.blue {
background-color: blue;
}
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.6.0/d3.min.js"></script>
</head>
<body>
<div id="body" style="border: 1px black solid; width:100%; height:600px;"></div>
</body>
Current output
Expected output
var root = {
"Type": "Root",
"id": 0,
"name": "ERM",
"ParentDocType": "EnterpriseWide",
"children": [{
"Type": "Stem",
"id": 4,
"name": "RG",
"ParentDocType": "EnterpriseWide",
"children": [{
"Type": "Stem",
"id": 5,
"name": "WCR F",
"ParentDocType": "WholesaleCreditRisk",
"children": [{
"Type": "Stem",
"id": 50,
"name": "WCR P",
"ParentDocType": "WholesaleCreditRisk",
"children": [{
"Type": "Leaf",
"id": 8,
"name": "IBQA SD",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 9,
"name": "WCR EMS",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 28,
"name": "WCR DD & RGEC",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 29,
"name": "PMM",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 30,
"name": "PRRM",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 31,
"name": "WCR Rep",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 32,
"name": "RR",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 33,
"name": "CO",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 34,
"name": "LD",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 35,
"name": "ESRM",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 36,
"name": "TPCR",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 37,
"name": "InterA",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 38,
"name": "DR",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 39,
"name": "MAR",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 40,
"name": "BSLM",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 51,
"name": "CCR Meas",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 52,
"name": "WLP",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 53,
"name": "TTS Pro",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 54,
"name": "CI",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 55,
"name": "LL",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 56,
"name": "SP",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 57,
"name": "CC",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 58,
"name": "CRE",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 59,
"name": "Sec",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 60,
"name": "SS",
"ParentDocType": "WholesaleCreditRisk",
"children": []
}
]
}]
},
{
"Type": "Stem",
"id": 10,
"name": "GCMP",
"ParentDocType": "EnterpriseWide",
"children": [{
"Type": "Leaf",
"id": 11,
"name": "WCR CMS",
"ParentDocType": "WholesaleCreditRisk",
"children": []
}]
},
{
"Type": "Stem",
"id": 12,
"name": "REAVP",
"ParentDocType": "EnterpriseWide",
"children": [{
"Type": "Stem",
"id": 13,
"name": "CREAVS",
"ParentDocType": "WholesaleCreditRisk",
"children": [{
"Type": "Leaf",
"id": 14,
"name": "CREAVP",
"ParentDocType": "WholesaleCreditRisk",
"children": []
}]
}]
}
]
}]
};
function pageNodes(d) {
if (d.children) {
d.children.forEach(c => pageNodes(c));
if (d.children.length > pageNodes.maxNode) {
d.pages = {}
const count = pageNodes.maxNode - 1;
const l = Math.ceil(d.children.length / count);
for (let i = 0; i < l; i++) {
let startRange = i * count;
let endRange = i * count + count;
d.pages[i] = d.children.slice(startRange, endRange);
pageNodes.addNode(d.pages[i], "More...", {
__next: i != (l - 1) ? i + 1 : 0,
});
}
d.children = d.pages[0];
}
}
}
pageNodes.maxNode = 5;
pageNodes.addNode = function(children, name, more) {
let node = Object.assign({
Type: "Leaf",
id: children[children.length - 1].id + 10000,
ParentDocType: children[children.length - 1].ParentDocType,
name: name,
}, more);
children.push(node);
};
root.children.forEach(c => pageNodes(c));
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = window.innerWidth - margin.right - margin.left,
height = window.innerHeight - margin.top - margin.bottom;
var i = 0,
duration = 750,
rectW = 150,
rectH = 50;
// var tree = d3.layout.tree().nodeSize([70, 40]);
var nodeWidth = 150;
var nodeHeight = 50;
var horizontalSeparationBetweenNodes = 16;
var verticalSeparationBetweenNodes = 128;
var tree = d3.layout.tree()
.nodeSize([nodeWidth + horizontalSeparationBetweenNodes, nodeHeight + verticalSeparationBetweenNodes])
.separation(function(a, b) {
return a.parent == b.parent ? 1 : 1.25;
});
/*var diagonal = d3.svg.diagonal()
.projection(function (d) {
return [d.x + rectW / 2, d.y + rectH / 6];
});*/
var diagonal = d3.svg.diagonal()
.target(function(d) {
var o = d.target;
o.y = o.y - 50
return o;
})
.projection(function(d) {
return [d.x + rectW / 2, d.y + rectH];
});
var svg = d3.select("#body").append("svg").attr("width", document.getElementById("body").style.width).attr("height", document.getElementById("body").style.height)
.call(zm = d3.behavior.zoom().scaleExtent([0.5, 3]).on("zoom", redraw)).append("g")
.attr("transform", "translate(" + 650 + "," + 20 + ")scale(0.7)");
//necessary so that zoom knows where to zoom and unzoom from
zm.translate([650, 20]);
root.x0 = 0;
root.y0 = height / 2;
d3.select("#myCheckbox").on("change", enableLink);
enableLink(root);
d3.select("#body").style("height", "800px");
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
x = text.attr("x"),
y = text.attr("y"),
dy = 0, //parseFloat(text.attr("dy")),
tspan = text.text(null)
.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
function enableLink(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 125;
});
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + source.x0 + "," + source.y0 + ")";
}).on("click", click);
nodeEnter.append("rect")
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "blue")
.attr("rx", 4)
.attr("ry", 4)
.attr("stroke-width", 2)
.style("fill", function(d) {
if (d.ParentDocType == "EnterpriseWide") return "darkblue";
if (d.ParentDocType == "WholesaleCreditRisk") return "blue";
return d._children ? "lightsteelblue" : "#fff";
});
nodeEnter.append("text")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) {
return d.name;
}).call(wrap, rectW - 10);
nodeEnter
.append("a")
.attr("xlink:href", function(d) {
return d.url;
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
nodeUpdate.select("rect")
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "blue")
.attr("stroke-width", 2)
.style("fill", function(d) {
if (d.ParentDocType == "EnterpriseWide") return "darkblue";
if (d.ParentDocType == "WholesaleCreditRisk") return "blue";
return d._children ? "lightsteelblue" : "#fff";
});
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.x + "," + source.y + ")";
})
.remove();
nodeExit.select("rect")
.attr("width", rectW)
.attr("height", rectH)
//.attr("width", bbox.getBBox().width)""
//.attr("height", bbox.getBBox().height)
.attr("stroke", "blue")
.attr("stroke-width", 2);
nodeExit.select("text");
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) {
d.y = d.y + 100;
return d.target.id;
});
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0 + 100
};
return diagonal({
source: o,
target: o
});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y + 100;
});
}
// Toggle children on click.
function click(d) {
if (d.hasOwnProperty('__next')) {
d.parent.children = d.parent.pages[d.__next];
} else if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
enableLink(d);
}
//Redraw for zoom
function redraw() {
//console.log("here", d3.event.translate, d3.event.scale);
svg.attr("transform",
"translate(" + d3.event.translate + ")" +
" scale(" + d3.event.scale + ")");
}
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
fill: white;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
body {
overflow: hidden;
}
.switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
}
input:checked+.slider {
background-color: #2196F3;
}
input:focus+.slider {
box-shadow: 0 0 1px #2196F3;
}
input:checked+.slider:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
/* Rounded sliders */
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}
.box {
float: left;
height: 20px;
width: 20px;
margin-bottom: 15px;
border: 1px solid black;
}
.darkblue {
background-color: darkblue;
}
.blue {
background-color: blue;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<div id="body" style="border: 1px black solid; width:100%; height:600px;"></div>
I have a requirement where I need to use customlegends in XY Amcharts.
I have implemented them, but When I have added event listeners to those legends, the function wasnt triggered. I dont know the reason, can anyone correct me what I have made a mistake.
You can check the following link for the output:
jsfiddle.net/u371jyjs/3/
clickMarker is a legend-specific event, not a top-level chart event. Put the listener for it inside the legend object:
legend: {
// ...
listeners: [{
"event": "clickMarker",
"method": function(event) {
// toggle the marker state
event.dataItem.hidden = !event.dataItem.hidden;
event.chart.validateNow();
}
}]
}
Demo:
var chart = AmCharts.makeChart("chartdiv", {
"type": "xy",
"path": "https://www.amcharts.com/lib/3/",
"theme": "light",
"dataProvider": [{
"y": 10,
"x": 2,
"value": 59,
"y2": 5,
"x2": 3,
"value2": 44,
"label": "Hello",
"category": "0",
"column-1": 32
}, {
"y": 5,
"x": 8,
"value": 50,
"y2": 15,
"x2": 8,
"value2": 12,
"label": "Hi",
"category": "1000",
"column-1": 14
}, {
"y": 10,
"x": 8,
"value": 19,
"y2": 4,
"x2": 6,
"value2": 35,
"label": "Yo"
}, {
"y": 6,
"x": 5,
"value": 65,
"y2": 5,
"x2": 6,
"value2": 168,
"label": "Howdy"
}, {
"y": 15,
"x": 4,
"value": 92,
"y2": 13,
"x2": 8,
"value2": 102,
"label": "Hi there"
}, {
"y": 13,
"x": 1,
"value": 8,
"y2": 2,
"x2": 0,
"value2": 41,
"label": "Morning"
}, {
"y": 1,
"x": 6,
"value": 35,
"y2": 0,
"x2": 3,
"value2": 16,
"label": "Afternoon"
}],
"valueAxes": [{
"position": "bottom",
"axisAlpha": 0,
"integersOnly": true,
//"labelRotation": 45,
"labelFunction": function(value) {
// define categories
var cats = [
"Nick",
"Sarah",
"Kevin",
"Dominick",
"Christy",
"Kate",
"Julian",
"Anita",
"Mike",
"Kyle",
"Tyrese"
];
return cats[value];
}
}, {
"axisAlpha": 0,
"position": "left"
}],
"startDuration": 1.5,
"graphs": [{
"balloonText": "[[label]]",
"bullet": "circle",
"bulletBorderAlpha": 0.2,
"bulletAlpha": 0.8,
"lineAlpha": 0,
"fillAlphas": 0,
"valueField": "value",
"xField": "x",
"yField": "y",
"maxBulletSize": 100
}, {
"balloonText": "[[label]]",
"bullet": "diamond",
"bulletBorderAlpha": 0.2,
"bulletAlpha": 0.8,
"lineAlpha": 0,
"fillAlphas": 0,
"valueField": "value2",
"xField": "x2",
"yField": "y2",
"maxBulletSize": 100
}],
"legend": {
"switchable": true,
"textClickEnabled": true,
"data": [{
title: "One",
color: "#3366CC",
hidden: true
}, {
title: "Two",
color: "#FFCC33"
}],
"listeners": [{
"event": "clickMarker",
"method": function(event) {
event.dataItem.hidden = !event.dataItem.hidden;
event.chart.validateNow()
}
}]
}
});
html, body {
width: 100%;
height: 100%;
margin: 0;
}
#chartdiv {
width: 100%;
height: 100%;
}
<script src="https://www.amcharts.com/lib/3/amcharts.js"></script>
<script src="https://www.amcharts.com/lib/3/xy.js"></script>
<script src="https://www.amcharts.com/lib/3/themes/light.js"></script>
<div id="chartdiv"></div>
<div id="clicked">
</div>
https://jsfiddle.net/q5vwgxgz/3/
var xAxis = d3.axisBottom(x)
.ticks(15)
.tickSizeInner(25)
So the issue is that labels of the X axis overlap, Mon 31 and Novemeber.
From what I can see the space between axes for those two dates is smaller and in result the labels overlap.
Can I solve this somehow or it's a library issue?
The project where that chart is used in has dateselectors for start and end date and also a dropdown for date granularity(daily, monthly, yearly) which means that I can't use a fixed number of ticks and specific format.
.ticks(15)
is used here just for the demo, in the project it's like this
Math.min(data.length, Math.floor(width / 100)) // 100 being the 'min' tick width
One solution (out of many) is specifically setting the tick format...
.tickFormat(function(d) {
return d3.timeFormat("%b %d")(d)
})
And spreading the ticks, for instance using timeDay:
.ticks(d3.timeDay.every(4))
Here is your code with those changes:
var data = [{
"Date": "2016-09-30",
"Jobs": 0,
}, {
"Date": "2016-10-01",
"Jobs": 0,
}, {
"Date": "2016-10-02",
"Jobs": 1,
}, {
"Date": "2016-10-03",
"Jobs": 0,
}, {
"Date": "2016-10-04",
"Jobs": 3,
}, {
"Date": "2016-10-05",
"Jobs": 0,
}, {
"Date": "2016-10-06",
"Jobs": 2,
}, {
"Date": "2016-10-07",
"Jobs": 0,
}, {
"Date": "2016-10-08",
"Jobs": 0,
}, {
"Date": "2016-10-09",
"Jobs": 4,
}, {
"Date": "2016-10-10",
"Jobs": 6,
}, {
"Date": "2016-10-11",
"Jobs": 1,
}, {
"Date": "2016-10-12",
"Jobs": 2,
}, {
"Date": "2016-10-13",
"Jobs": 0,
}, {
"Date": "2016-10-14",
"Jobs": 0,
}, {
"Date": "2016-10-15",
"Jobs": 0,
}, {
"Date": "2016-10-16",
"Jobs": 3,
}, {
"Date": "2016-10-17",
"Jobs": 2,
}, {
"Date": "2016-10-18",
"Jobs": 3,
}, {
"Date": "2016-10-19",
"Jobs": 3,
}, {
"Date": "2016-10-20",
"Jobs": 1,
}, {
"Date": "2016-10-21",
"Jobs": 0,
}, {
"Date": "2016-10-22",
"Jobs": 0,
}, {
"Date": "2016-10-23",
"Jobs": 4,
}, {
"Date": "2016-10-24",
"Jobs": 2,
}, {
"Date": "2016-10-25",
"Jobs": 6,
}, {
"Date": "2016-10-26",
"Jobs": 1,
}, {
"Date": "2016-10-27",
"Jobs": 1,
}, {
"Date": "2016-10-28",
"Jobs": 0,
}, {
"Date": "2016-10-29",
"Jobs": 3,
}, {
"Date": "2016-10-30",
"Jobs": 5,
}, {
"Date": "2016-10-31",
"Jobs": 0,
}, {
"Date": "2016-11-01",
"Jobs": 3,
}, {
"Date": "2016-11-02",
"Jobs": 1,
}, {
"Date": "2016-11-03",
"Jobs": 0,
}, {
"Date": "2016-11-04",
"Jobs": 0,
}, {
"Date": "2016-11-05",
"Jobs": 1,
}, {
"Date": "2016-11-06",
"Jobs": 4,
}, {
"Date": "2016-11-07",
"Jobs": 5,
}, {
"Date": "2016-11-08",
"Jobs": 6,
}, {
"Date": "2016-11-09",
"Jobs": 2,
}, {
"Date": "2016-11-10",
"Jobs": 1,
}, {
"Date": "2016-11-11",
"Jobs": 0,
}, {
"Date": "2016-11-12",
"Jobs": 2,
}, {
"Date": "2016-11-13",
"Jobs": 2,
}, {
"Date": "2016-11-14",
"Jobs": 1,
}, {
"Date": "2016-11-15",
"Jobs": 3,
}, {
"Date": "2016-11-16",
"Jobs": 0,
}, {
"Date": "2016-11-17",
"Jobs": 1,
}, {
"Date": "2016-11-18",
"Jobs": 0,
}, {
"Date": "2016-11-19",
"Jobs": 0,
}, {
"Date": "2016-11-20",
"Jobs": 1,
}, {
"Date": "2016-11-21",
"Jobs": 2,
}, {
"Date": "2016-11-22",
"Jobs": 2,
}, {
"Date": "2016-11-23",
"Jobs": 4,
}, {
"Date": "2016-11-24",
"Jobs": 0,
}, {
"Date": "2016-11-25",
"Jobs": 0,
}]
var parseTime = d3.timeParse("%Y-%m-%d");
data.forEach(function(d) {
d.Date = parseTime(d.Date);
});
var svg = d3.select("svg"),
margin = {
top: 20,
right: 20,
bottom: 50,
left: 50
},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleTime()
.rangeRound([0, width]);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var line = d3.line()
.x(function(d) {
return x(d.Date);
})
.y(function(d) {
return y(d.Jobs);
});
x.domain(d3.extent(data, function(d) {
return d.Date;
}));
y.domain(d3.extent(data, function(d) {
return d.Jobs;
}));
var xAxis = d3.axisBottom(x)
.ticks(d3.timeDay.every(4))
.tickFormat(function(d) {
return d3.timeFormat("%b %d")(d)
})
.tickSizeInner(25)
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.select(".domain")
.remove();
g.append("g")
.call(d3.axisLeft(y))
.append("text")
.attr("fill", "#000")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.text("Jobs");
g.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 1.5)
.attr("d", line);
<svg width="1200" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
However, have in mind that in D3 time scales were not created having customizations in mind. Their behaviour is dynamic. Therefore, You'll have to fix each specific problem using ad hoc solutions.
Or you may rotate the label for ticks by 90 degrees:
d3.selectAll(".x-axis .tick text")
.attr("transform", "translate(-30, 50)rotate(-90)")
working code here
I'm working with something like this:
Fiddle
I'm loading data with jQuery and running the whole update function with it. What I want to do is have all the circles run closer to the main one before the update is triggered. Would I be able to do something like that? I've tried rewriting the tick function to do same as it does now, only to use d.x-50, but that didn't work.
There are several ways to do what you want, but this is my solution: restart the force with a smaller linkDistance:
var inter = setInterval(function() {
updateData();
setTimeout(function() {
force.linkDistance(20);
force.start().alpha(0.01);
}, 2000);
}, 5000);
Here is the demo:
var data = {
nodes: [
{"x": 250, "y": 250, "color": "green", "name":"TEST", "r":"28", "fixed":true},
{"x": 120, "y": 150, "name":"forums.macrumors", "score": -12.2, "icon": ""},
{"x": 140, "y": 150, "name":"delhidailynews", "score": -0.08, "icon": ""},
{"x": 280, "y": 150, "name":"4-traders", "score": -0.055, "icon": ""},
{"x": 300, "y": 150, "name":"phonearena", "score": 0.45, "icon": ""},
{"x": 40, "y": 200, "name":"inga3.wordpress", "score": -0.27, "icon": ""},
{"x": 70, "y": 200, "name":"kahinaweb.wordpress", "score": -0.28, "icon": ""},
{"x": 100, "y": 200, "name":"bilqueessite.wordpress", "score": -0.3, "icon": ""},
{"x": 130, "y": 200, "name":"beforeitsnews", "score": -0.72, "icon": ""},
{"x": 380, "y": 200, "name":"yahoo", "score": -0.66, "icon": ""}
],
links: [
{"source": 0, "target": 1, "distance": 180, "label": ""},
{"source": 0, "target": 2, "distance": 180, "label": ""},
{"source": 0, "target": 3, "distance": 180, "label": ""},
{"source": 0, "target": 4, "distance": 180, "label": ""},
{"source": 0, "target": 5, "distance": 180, "label": ""},
{"source": 0, "target": 6, "distance": 180, "label": ""},
{"source": 0, "target": 7, "distance": 180, "label": ""},
{"source": 0, "target": 8, "distance": 180, "label": ""},
{"source": 0, "target": 9, "distance": 180, "label": ""}
]
};
var width = 500,
height = 500;
var force = d3.layout.force()
.size([width, height])
.charge(function(d){
var charge = -500;
if (d.index === 0) charge = 10 * charge;
return charge;
})
.linkDistance(d => d.distance)
.on("tick", tick);
var svg = d3.select("#orb")
.append("svg")
.attr("width", width)
.attr("height", height)
.attr("class", "mainsvg");
var link = svg.selectAll(".link"),
node = svg.selectAll(".node"),
path = svg.selectAll(".path");
force.nodes(data.nodes)
.links(data.links)
.start();
var edges = link.data(data.links)
.enter()
.append("line")
.attr("class", "link")
.style("stroke", "grey")
.style("pointer-events", "none");
node = node.data(data.nodes)
.enter()
.append("g");
node.append("circle")
.attr("class", "circle")
.attr("r", function(d) { if(d.r){ return d.r; } else { return "18";} })
.attr("fill", function(d) { if(d.color) { return d.color; } else { return "orange";} })
.attr("stroke", function(d) { if(d.color) { return d.color; } else { return "orange";} });
var linkwrap = node.append("a")
.attr("href", "3");
linkwrap.append("image")
.attr("class", "srcico")
.attr("height", "16px")
.attr("width", "16px")
.attr("xlink:href", function(d) { return d.icon; });
linkwrap.append("text")
.attr("fill", "white")
.attr("stroke", "none")
.attr("x", "232")
.attr("y", "255")
.text(function(d) { return d.ticker; });
function tick() {
var link = svg.selectAll("line");
var edgepaths = svg.selectAll(".edgepath");
var edgelabels = svg.selectAll(".edgelabel");
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; });
svg.selectAll(".circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
svg.selectAll(".srcico")
.attr("x", function(d) { return d.x-5; })
.attr("y", function(d) { return d.y-8; });
svg.selectAll(".notecap")
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
}
var inter = setInterval(function() {
updateData();
setTimeout(function(){
force.linkDistance(20);
force.start().alpha(0.01);
},2000);
}, 5000);
function updateData() {
var data = {
nodes: [
{"x": 250, "y": 250, "color": "grey", "name":"TEST", "r":"28", "fixed":true},
{"x": 120, "y": 210, "name":"", "score": -12.2, "icon": ""},
{"x": 140, "y": 210, "name":"", "score": -0.08, "icon": ""},
{"x": 280, "y": 210, "name":"", "score": -0.055, "icon": ""},
{"x": 300, "y": 210, "name":"", "score": 0.45, "icon": ""},
{"x": 40, "y": 200, "name":"", "score": -0.27, "icon": ""},
{"x": 70, "y": 200, "name":"", "score": -0.28, "icon": ""},
{"x": 100, "y": 200, "name":"", "score": -0.3, "icon": ""},
{"x": 130, "y": 200, "name":"", "score": -0.72, "icon": ""},
{"x": 380, "y": 200, "name":"", "score": -0.66, "icon": ""},
{"x": 160, "y": 200, "name":"", "score": -0.317, "icon": ""},
{"x": 280, "y": 200, "name":"", "score": -0.37, "icon": ""},
{"x": 270, "y": 200, "name":"", "score": -0.49, "icon": ""},
{"x": 340, "y": 200, "name":"", "score": -0.62, "icon": ""},
{"x": 100, "y": 300, "name":"", "score": -0.31, "icon": ""},
{"x": 140, "y": 300, "name":"", "score": -0.457, "icon": ""},
{"x": 180, "y": 300, "name":"", "score": -0.472, "icon": ""},
{"x": 280, "y": 300, "name":"", "score": -0.66, "icon": ""},
{"x": 320, "y": 300, "name":"", "score": -0.68, "icon": ""},
{"x": 410, "y": 300, "name":"", "score": -0.8, "icon": ""},
{"x": 260, "y": 300, "name":"", "score": -0.86, "icon": ""}
],
links: [
{"source": 0, "target": 1, "distance": 180, "label": ""},
{"source": 0, "target": 2, "distance": 180, "label": ""},
{"source": 0, "target": 3, "distance": 180, "label": ""},
{"source": 0, "target": 4, "distance": 180, "label": ""},
{"source": 0, "target": 5, "distance": 180, "label": ""},
{"source": 0, "target": 6, "distance": 180, "label": ""},
{"source": 0, "target": 7, "distance": 180, "label": ""},
{"source": 0, "target": 8, "distance": 180, "label": ""},
{"source": 0, "target": 9, "distance": 180, "label": ""},
{"source": 0, "target": 10, "distance": 180, "label": ""},
{"source": 0, "target": 11, "distance": 180, "label": ""},
{"source": 0, "target": 12, "distance": 180, "label": ""},
{"source": 0, "target": 13, "distance": 180, "label": ""},
{"source": 0, "target": 14, "distance": 180, "label": ""},
{"source": 0, "target": 15, "distance": 180, "label": ""},
{"source": 0, "target": 16, "distance": 180, "label": ""},
{"source": 0, "target": 17, "distance": 180, "label": ""},
{"source": 0, "target": 18, "distance": 180, "label": ""},
{"source": 0, "target": 19, "distance": 180, "label": ""},
{"source": 0, "target": 20, "distance": 180, "label": ""}
]
};
d3.selectAll(".mainsvg > *").remove();
var link = svg.selectAll(".link"),
node = svg.selectAll(".node"),
path = svg.selectAll(".path");
force.linkDistance(d=>d.distance);
force.nodes(data.nodes)
.links(data.links)
.start();
var edges = link.data(data.links)
.enter()
.append("line")
.attr("class", "link")
.style("stroke", "grey")
.style("pointer-events", "none");
node = node.data(data.nodes)
.enter()
.append("g");
node.append("circle")
.attr("class", "circle")
.attr("r", function(d) { if(d.r){ return d.r; } else { return "18";} })
.attr("fill", function(d) { if(d.color) { return d.color; } else { return "orange";} })
.attr("stroke", function(d) { if(d.color) { return d.color; } else { return "orange";} });
var linkwrap = node.append("a")
.attr("href", "3");
linkwrap.append("image")
.attr("class", "srcico")
.attr("height", "16px")
.attr("width", "16px")
.attr("xlink:href", function(d) { return d.icon; });
linkwrap.append("text")
.attr("fill", "white")
.attr("stroke", "none")
.attr("x", "232")
.attr("y", "255")
.text(function(d) { return d.ticker; });
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="orb">
</div>
I am trying to expand this force layout example by changing a nodes' shape from circle to rectangle when it is clicked. So I don't want to change any data but just want to replace the corresponding SVG element.
One of my approaches looked like this:
node.on("click", function() {
this.remove();
svg.selectAll(".node").data(graph.nodes).enter().append("rect")
.attr("class", "node")
.attr("width", 5).attr("height", 5)
.style("fill", function(d) { return color(d.group); });
});
So I removed the SVG element from the DOM and rebound the data, adding a rectangle for the now missing node.
Unfortunately this does not work (the force layout does not set any properties on the new element) and I have no idea if this a reasonable approach at all.
Any ideas how to do this properly?
Try this way.
node.on("click", function(d) {
var size = d.weight * 2 + 12;
d3.select(this).select("circle").remove();
d3.select(this).append("rect")
.attr("x", -(size / 2))
.attr("y", -(size / 2))
.attr("height", size)
.attr("width", size)
.style("fill", function(d) {
return color(1 / d.rating);
});
});
Working code snippet -
var graph = {
"nodes": [{
"name": "1",
"rating": 90,
"id": 2951
}, {
"name": "2",
"rating": 80,
"id": 654654
}, {
"name": "3",
"rating": 80,
"id": 6546544
}, {
"name": "4",
"rating": 1,
"id": 68987978
}, {
"name": "5",
"rating": 1,
"id": 9878933
}, {
"name": "6",
"rating": 1,
"id": 6161
}, {
"name": "7",
"rating": 1,
"id": 64654
}, {
"name": "8",
"rating": 20,
"id": 354654
}, {
"name": "9",
"rating": 50,
"id": 8494
}, {
"name": "10",
"rating": 1,
"id": 6846874
}, {
"name": "11",
"rating": 1,
"id": 5487
}, {
"name": "12",
"rating": 80,
"id": "parfum_kenzo"
}, {
"name": "13",
"rating": 1,
"id": 65465465
}, {
"name": "14",
"rating": 90,
"id": "jungle_de_kenzo"
}, {
"name": "15",
"rating": 20,
"id": 313514
}, {
"name": "16",
"rating": 40,
"id": 36543614
}, {
"name": "17",
"rating": 100,
"id": "Yann_YA645"
}, {
"name": "18",
"rating": 1,
"id": 97413
}, {
"name": "19",
"rating": 1,
"id": 97414
}, {
"name": "20",
"rating": 100,
"id": 976431231
}, {
"name": "21",
"rating": 1,
"id": 9416
}, {
"name": "22",
"rating": 1,
"id": 998949
}, {
"name": "23",
"rating": 100,
"id": 984941
}, {
"name": "24",
"rating": 100,
"id": "99843"
}, {
"name": "25",
"rating": 1,
"id": 94915
}, {
"name": "26",
"rating": 1,
"id": 913134
}, {
"name": "27",
"rating": 1,
"id": 9134371
}],
"links": [{
"source": 6,
"target": 5,
"value": 6,
"label": "publishedOn"
}, {
"source": 8,
"target": 5,
"value": 6,
"label": "publishedOn"
}, {
"source": 7,
"target": 1,
"value": 4,
"label": "containsKeyword"
}, {
"source": 8,
"target": 10,
"value": 3,
"label": "containsKeyword"
}, {
"source": 7,
"target": 14,
"value": 4,
"label": "publishedBy"
}, {
"source": 8,
"target": 15,
"value": 6,
"label": "publishedBy"
}, {
"source": 9,
"target": 1,
"value": 6,
"label": "depicts"
}, {
"source": 10,
"target": 1,
"value": 6,
"label": "depicts"
}, {
"source": 16,
"target": 1,
"value": 6,
"label": "manageWebsite"
}, {
"source": 16,
"target": 2,
"value": 5,
"label": "manageWebsite"
}, {
"source": 16,
"target": 3,
"value": 6,
"label": "manageWebsite"
}, {
"source": 16,
"target": 4,
"value": 6,
"label": "manageWebsite"
}, {
"source": 19,
"target": 18,
"value": 2,
"label": "postedOn"
}, {
"source": 18,
"target": 1,
"value": 6,
"label": "childOf"
}, {
"source": 17,
"target": 19,
"value": 8,
"label": "describes"
}, {
"source": 18,
"target": 11,
"value": 6,
"label": "containsKeyword"
}, {
"source": 17,
"target": 13,
"value": 3,
"label": "containsKeyword"
}, {
"source": 20,
"target": 13,
"value": 3,
"label": "containsKeyword"
}, {
"source": 20,
"target": 21,
"value": 3,
"label": "postedOn"
}, {
"source": 22,
"target": 20,
"value": 3,
"label": "postedOn"
}, {
"source": 23,
"target": 21,
"value": 3,
"label": "manageWebsite"
}, {
"source": 23,
"target": 24,
"value": 3,
"label": "manageWebsite"
}, {
"source": 23,
"target": 25,
"value": 3,
"label": "manageWebsite"
}, {
"source": 23,
"target": 26,
"value": 3,
"label": "manageWebsite"
}]
}
var margin = {
top: -5,
right: -5,
bottom: -5,
left: -5
};
var width = 500 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-200)
.linkDistance(50)
.size([width + margin.left + margin.right, height + margin.top + margin.bottom]);
var zoom = d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", zoomed);
var drag = d3.behavior.drag()
.origin(function(d) {
return d;
})
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
var svg = d3.select("#map").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.right + ")")
.call(zoom);
var rect = svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all");
var container = svg.append("g");
//d3.json('http://blt909.free.fr/wd/map2.json', function(error, graph) {
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = container.append("g")
.attr("class", "links")
.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) {
return Math.sqrt(d.value);
});
var node = container.append("g")
.attr("class", "nodes")
.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.call(drag);
node.append("circle")
.attr("r", function(d) {
return d.weight * 2 + 12;
})
.style("fill", function(d) {
return color(1 / d.rating);
});
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 + ")";
});
});
var linkedByIndex = {};
graph.links.forEach(function(d) {
linkedByIndex[d.source.index + "," + d.target.index] = 1;
});
function isConnected(a, b) {
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index];
}
node.on("click", function(d) {
var size = d.weight * 2 + 12;
d3.select(this).select("circle").remove();
d3.select(this).append("rect")
.attr("x", -(size / 2))
.attr("y", -(size / 2))
.attr("height", size)
.attr("width", size)
.style("fill", function(d) {
return color(1 / d.rating);
});
});
function dottype(d) {
d.x = +d.x;
d.y = +d.y;
return d;
}
function zoomed() {
container.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
force.start();
}
function dragged(d) {
d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}
function dragended(d) {
d3.select(this).classed("dragging", false);
}
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.node-active {
stroke: #555;
stroke-width: 1.5px;
}
.link {
stroke: #555;
stroke-opacity: .3;
}
.link-active {
stroke-opacity: 1;
}
.overlay {
fill: none;
pointer-events: all;
}
#map {
border: 2px #555 dashed;
width: 500px;
height: 400px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<body>
<div id="map"></div>
</body>
You've updated the dom elements, but not the array used by the force layout algorithm, so do this as the last line in the on click function:
graph.nodes = svg.selectAll(".node");
(You might also find without a data key function that random nodes get changed to rectangles rather than the one you clicked)