I am attempting to animate 3 circles along 3 paths, these paths are randomly generated.Only two paths are generating and only one circle is animating along its allocated path. I have tried pairing the dataset and circle, this has no effect.
One of the paths seems to be blending two dataset's to generate a monster path. How do i stop this? How do i get each circle to its allocated path?
There is probably a more elegant way to do this.
var w = 2000, h = 2000;
var dataset1 = [] ;
for (var i = 0; i < 5; i++) {
var x = Math.floor((Math.random()*900)+1);
var y = Math.floor((Math.random()*900)+1);
dataset1.push({"x":x, "y":y});
};
var dataset2 = [] ;
for (var i = 0; i < 4; i++) {
var x = Math.floor((Math.random()*700)+1);
var y = Math.floor((Math.random()*600)+1);
dataset2.push({"x":x, "y":y});
};
var dataset3 = [] ;
for (var i = 0; i < 3; i++) {
var x = Math.floor((Math.random()*800)+1);
var y = Math.floor((Math.random()*400)+1);
dataset2.push({"x":x, "y":y});
};
var lineFunction = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate ("cardinal-closed")
.tension(0)
;
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h)
;
var path1 = svg.append("path")
.datum( dataset1 )
.attr("d", lineFunction)
.attr("stroke", "black")
.attr("stroke-width", 3)
.attr("fill", "none")
;
var circle1 = svg.append("circle")
.attr("r", 130)
.attr("transform", "translate(" + [0] + ")")
;
var path2 = svg.append("path")
.datum( dataset2 )
.attr("d", lineFunction)
.attr("stroke", "black")
.attr("stroke-width", 3)
.attr("fill", "none")
;
var circle2 = svg.append("circle")
.attr("r", 30)
.attr("transform", "translate(" + [0] + ")")
;
var path3 = svg.append("path")
.datum( dataset2 )
.attr("d", lineFunction)
.attr("stroke", "black")
.attr("stroke-width", 3)
.attr("fill", "none")
;
var circle3 = svg.append("circle")
.attr("r", 10)
.attr("transform", "translate(" + [0] + ")")
;
transition();
function transition() {
circle1.transition()
.duration(10000)
.attrTween("transform", translateAlong(path1.node()))
.each("end", transition);
}
transition();
function transition() {
circle2.transition()
.duration(10000)
.attrTween("transform", translateAlong(path2.node()))
}
transition();
function transition() {
circle3.transition()
.duration(10000)
.attrTween("transform", translateAlong(path3.node()))
}
function translateAlong(path) {
var l = path.getTotalLength();
return function(d, i, a) {
return function(t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";
};
};
}
function translateAlong(path) {
var l = path.getTotalLength();
return function(d, i, a) {
return function(t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";
};
};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<style>
path {
fill: none;
stroke: red;
stroke-width: 3px;
}
circle {
fill: red;
stroke: #fff;
stroke-width: 3px;
opacity: 0.7;
}
</style>
<script src="https://d3js.org/d3.v3.min.js"></script>
You had 3 problems with your code:
You were pushing dataset3 values into dataset2 array.
You were using dataset2 array for painting path3 element.
This is the most important problem: you had 3 functions with the same name. The last one simply overwrites the other ones. They should have different names. Alternatively, for DRY, write a general function and pass both the circle and the path as arguments.
Here is your code with those changes.
var w = 2000,
h = 2000;
var dataset1 = [];
for (var i = 0; i < 5; i++) {
var x = Math.floor((Math.random() * 900) + 1);
var y = Math.floor((Math.random() * 900) + 1);
dataset1.push({
"x": x,
"y": y
});
};
var dataset2 = [];
for (var i = 0; i < 4; i++) {
var x = Math.floor((Math.random() * 700) + 1);
var y = Math.floor((Math.random() * 600) + 1);
dataset2.push({
"x": x,
"y": y
});
};
var dataset3 = [];
for (var i = 0; i < 3; i++) {
var x = Math.floor((Math.random() * 800) + 1);
var y = Math.floor((Math.random() * 400) + 1);
dataset3.push({
"x": x,
"y": y
});
};
var lineFunction = d3.svg.line()
.x(function(d) {
return d.x;
})
.y(function(d) {
return d.y;
})
.interpolate("cardinal-closed")
.tension(0);
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h);
var path1 = svg.append("path")
.datum(dataset1)
.attr("d", lineFunction)
.attr("stroke", "black")
.attr("stroke-width", 3)
.attr("fill", "none");
var circle1 = svg.append("circle")
.attr("r", 130)
.attr("transform", "translate(" + [0] + ")");
var path2 = svg.append("path")
.datum(dataset2)
.attr("d", lineFunction)
.attr("stroke", "black")
.attr("stroke-width", 3)
.attr("fill", "none");
var circle2 = svg.append("circle")
.attr("r", 30)
.attr("transform", "translate(" + [0] + ")");
var path3 = svg.append("path")
.datum(dataset3)
.attr("d", lineFunction)
.attr("stroke", "black")
.attr("stroke-width", 3)
.attr("fill", "none");
var circle3 = svg.append("circle")
.attr("r", 10)
.attr("fill", "blue")
.attr("transform", "translate(" + [0] + ")");
transition();
function transition() {
circle1.transition()
.duration(10000)
.attrTween("transform", translateAlong(path1.node()))
.each("end", transition);
}
transition2();
function transition2() {
circle2.transition()
.duration(10000)
.attrTween("transform", translateAlong(path2.node()))
.each("end", transition2);
}
transition3();
function transition3() {
circle3.transition()
.duration(10000)
.attrTween("transform", translateAlong(path3.node()))
.each("end", transition3);
}
function translateAlong(path) {
var l = path.getTotalLength();
return function(d, i, a) {
return function(t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";
};
};
}
function translateAlong(path) {
var l = path.getTotalLength();
return function(d, i, a) {
return function(t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";
};
};
}
path {
fill: none;
stroke: red;
stroke-width: 3px;
}
circle {
fill: red;
stroke: #fff;
stroke-width: 3px;
opacity: 0.7;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
First and foremost, you are likely having a problem with the code because you are redefining the same function. You have 3 different definitions for the transition() function (one for each of the circles). Once that code is fixed, then we can move on to the next problem.
Related
I have made a grouped bar chart, but data isnt updating above when the below mover is moved, i dont know how to change the transform of .bar , how to use exit and remove here, as it is a grouped bar chart.
the new rects are going inside the groups and its getting messy.
This is my grouped bar chart
var counter = 0;
var dataset = [];
var found = 0;
var moverToLast = 0;
var moverToFirst = 0;
var of;
var displayFlag=0;
var selectBar;
var firstSubBarLength;
function buildBar(selectBar){
displayFlag = 1;
$('#barChart').text('');
dataset = [{"user_id":"AAAA0001","my_count":1,"sale_count":1,"lead_count":0}
,{"user_id":"AAAA0002","my_count":6,"sale_count":5,"lead_count":2}
,{"user_id":"AAAA0003","my_count":13,"sale_count":6,"lead_count":3}
,{"user_id":"AAAA0004","my_count":11,"sale_count":12,"lead_count":4}
,{"user_id":"AAAA0005","my_count":5,"sale_count":6,"lead_count":5}
,{"user_id":"AAAA0006","my_count":6,"sale_count":10,"lead_count":6}
,{"user_id":"AAAA0007","my_count":13,"sale_count":2,"lead_count":7}
,{"user_id":"AAAA0008","my_count":11,"sale_count":0,"lead_count":8}
,{"user_id":"AAAA0009","my_count":7,"sale_count":11,"lead_count":9}
,{"user_id":"AAAA0010","my_count":1,"sale_count":7,"lead_count":10}
,{"user_id":"AAAA0011","my_count":4,"sale_count":4,"lead_count":11}
,{"user_id":"AAAA0012","my_count":15,"sale_count":8,"lead_count":12}
,{"user_id":"AAAA0013","my_count":1,"sale_count":15,"lead_count":13}
,{"user_id":"AAAA0014","my_count":8,"sale_count":1,"lead_count":14}
,{"user_id":"AAAA0015","my_count":9,"sale_count":8,"lead_count":15}
,{"user_id":"AAAA0016","my_count":4,"sale_count":11,"lead_count":16}
,{"user_id":"AAAA0017","my_count":18,"sale_count":4,"lead_count":17}
,{"user_id":"AAAA1234","my_count":6,"sale_count":12,"lead_count":1}
,{"user_id":"ABHI1245","my_count":10,"sale_count":7,"lead_count":1}
,{"user_id":"ABHI2188","my_count":1,"sale_count":4,"lead_count":1}
,{"user_id":"BBBB1234","my_count":1,"sale_count":3,"lead_count":1}
,{"user_id":"DDDD1235","my_count":7,"sale_count":10,"lead_count":1}
,{"user_id":"DEEP1234","my_count":1,"sale_count":6,"lead_count":1}
,{"user_id":"EEEE2188","my_count":1,"sale_count":5,"lead_count":1}
,{"user_id":"QWER1234","my_count":10,"sale_count":5,"lead_count":1}
,{"user_id":"ROHI1234","my_count":6,"sale_count":12,"lead_count":1}];
var barTooltip = d3.select("#barChart").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var formatRatio = d3.format("%");
var margin = {top: 20, right: 10, bottom: 20, left: 40};
var marginOverview = {top: 30, right: 10, bottom: 20, left: 40};
var selectorHeight = 40;
var width = 500 - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom - selectorHeight;
var heightOverview = 80 - marginOverview.top - marginOverview.bottom;
var maxLength = d3.max(dataset.map(function(d){ return d.user_id.length}))
var barWidth = maxLength * 7;
console.log("barWidth "+barWidth);
var numBars = Math.round(width/barWidth);
console.log("numBars "+numBars);
console.log("width"+width);
var isScrollDisplayed = barWidth * dataset.length > width;
var user_id = function(d) { return d.user_id; };
var my_count = function(d) { return d.my_count; };
var sale_count = function(d) { return d.sale_count; };
var lead_count = function(d) { return d.lead_count; };
var valueObject = function(d) { return [{name:"my_count", value:my_count(d)},{name:"sale_count", value: sale_count(d)}, {name: "lead_count", value: lead_count(d)}]; };
var val = $('#userId').val();
var colorIndex = 0;
for(var i = 0;i<dataset.length;i++){
console.log(dataset[i].user_id);
if(dataset[i].user_id==val){
found = i-(numBars-5);
break;
}
}
colorIndex = found;
var dint = dataset.length - (numBars/2);
console.log(dint + " dataaa");
if(found<0){
found=0;
}else if(found>(dint-numBars)){
found = dataset.length-8;
console.log("in found")
}
if(selectBar==0){
found=0;
}
console.log("found"+found);
console.log(isScrollDisplayed)
newData = dataset.slice(found);
console.log(newData);
var xscale = d3.scale.ordinal()
.domain(newData.slice(0,numBars).map(function (d) { return d.user_id; }))
.rangeRoundBands([0, width], .1);
var x1 = d3.scale.ordinal()
.domain(["my_count","sale_count","lead_count"])
.rangeRoundBands([0, xscale.rangeBand()]);
var yscale = d3.scale.linear()
.domain([0, d3.max(dataset.map(my_count))])
.nice()
.range([height, 0]);
var xAxis = d3.svg.axis().scale(xscale).orient("bottom");
var yAxis = d3.svg.axis().scale(yscale).orient("left").tickSize(-width, 0, 0).tickFormat(d3.format("s"))
.ticks(10);
var color = d3.scale.ordinal()
.domain(["my_count","sale_count","lead_count"])
.range(["#31a354","#3182bd","#e6550d"]);
// var minProfit = d3.min(data, function(d){ return d.lead_count; });
// var maxProfit = d3.max(data, function(d){ return d.lead_count; });
// var color = d3.scale.quantize()
// .domain([minProfit, maxProfit])
// .range(['rgb(165,15,21)','rgb(222,45,38)','rgb(251,106,74)','rgb(252,146,114)','rgb(252,187,161)','rgb(254,229,217)','rgb(150, 106, 106)','rgb(64,64,64)']);
var svg = d3.select("#barChart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom + selectorHeight);
var diagram = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
diagram.append("g")
.attr("class", "x axis x-axis")
.attr("transform", "translate(0, " + height + ")")
.call(xAxis);
diagram.append("g")
.attr("class", "y axis y-axis")
.call(yAxis);
var bars = diagram.selectAll(".bar")
.data(newData.slice(0,numBars))
.enter()
.append("g")
.attr("class", "bar")
.attr("transform", function(d) { return "translate(" + xscale(user_id(d)) + ",0)"; });
;
bars.selectAll("rect")
.data(valueObject)
.enter().append("rect")
.attr("x", function(d) { return x1(d.name); })
.attr("width", x1.rangeBand())
.attr("y", function (d) { return yscale(0); })
.attr("height", 0)
.transition().duration(2000)
.ease("cubic")
.attr("y", function (d) { return yscale(d.value) })
.attr("height", function (d) { return height - yscale(d.value); })
.attr("fill", function(d) { return color(d.name); })
;
// d3.selectAll("rect").style("fill", function(d) { return color(d.lead_count); }).on("mouseover", function(d) {
// barTooltip.transition()
// .duration(500)
// .style("opacity", .9);
// var tip = "<strong>UserID:</strong> " + d.user_id + "<br/>";
// var tip = tip+"<strong>Leads:</strong> " + d.lead_count+ "<br/>";
// lead_count = d.lead_count;
// barTooltip.html(tip)
// .style("left", (d3.event.pageX) + "px")
// .style("top", (d3.event.pageY - 28) + "px");
// })
// .on("mouseout", function(d) {
// barTooltip.transition()
// .duration(500)
// .style("opacity", 0);
// });
// if(selectBar==1){
// d3.selectAll("rect").classed("disable",function(d){if(d.user_id!=val){return true}else{return false}}).style("fill", function(d) { return color(d.lead_count);
// d3.selectAll(".mover").classed("disable",true)
// });}
if (isScrollDisplayed)
{
var xOverview = d3.scale.ordinal()
.domain(dataset.map(user_id))
.rangeBands([0, width], .2);
var xOverview1 = d3.scale.ordinal()
.domain(["my_count","sale_count","lead_count"])
.rangeRoundBands([0, xOverview.rangeBand()]);
yOverview = d3.scale.linear().range([heightOverview, 0]);
yOverview.domain(yscale.domain());
var subBars = diagram.selectAll('.sub')
.data(dataset)
.enter()
.append("g")
.attr("class","sub")
.attr("transform", function(d) { return "translate(" + xOverview(user_id(d)) + ",0)"; })
.classed("selectedSubBar",function(d,i){if(d.user_id==val && i>8 && i<dataset.length-8){return true}else{return false;}})
.classed("lastSubBar",function(d,i){if(i==dataset.length-1){return true}else{return false;}})
.classed("firstSubBar",function(d,i){if(i==0){return true}else{return false;}})
;
subBars.selectAll("rect")
.data(valueObject)
.enter().append("rect")
.classed('subBar', true)
.attr("x", function(d) {return xOverview1(d.name);})
.attr("width", function(d) {return xOverview1.rangeBand()})
.attr("y", function (d) { return height + heightOverview + yOverview(0); })
.attr("height", 0)
.transition().duration(1500)
.attr("y", function(d) {
return height + heightOverview + yOverview(d.value)
})
.attr("height", function(d) {
return heightOverview - yOverview(d.value);
})
var displayed = d3.scale.quantize()
.domain([0, width])
.range(d3.range(dataset.length));
diagram.append("rect")
.attr("transform", "translate(0, " + (height + margin.bottom) + ")")
.attr("class", "mover")
.attr("x", 0)
.attr("y", 0)
.attr("height", selectorHeight)
.attr("width", Math.round(parseFloat(numBars * width)/dataset.length))
.attr("pointer-events", "all")
.attr("cursor", "ew-resize")
.call(d3.behavior.drag().on("drag", display));
console.log("float " + Math.round(parseFloat(numBars * width)/dataset.length)/2);
var rectWidth = $('.sub').width();
for(var checker = 0; checker < dataset.length; checker++){
if(dataset[checker].user_id==val){
if(checker>dataset.length-9){
moverToLast = 1;
moverToFirst = 0;
}else if(checker<8){
moverToLast = 0;
moverToFirst = 1;
}else{
moverToFirst = 0;
moverToLast = 0;
}
}
}
console.log("moverToLast",moverToLast);
firstSubBarLength = $('.firstSubBar').offset().left - 8;
console.log("moverToFirst "+moverToFirst);
if(moverToLast == 1){
console.log(rectWidth + " rectWidth");
console.log($('.lastSubBar').offset().left + " $('.lastSubBar').offset().left");
of = Math.round($('.lastSubBar').offset().left + rectWidth - (parseFloat(numBars * width)/dataset.length)) - firstSubBarLength;
}else if(moverToFirst == 1){
of = 0;
}else{
of = Math.round($('.selectedSubBar').offset().left - firstSubBarLength - (parseFloat(numBars * width)/dataset.length/2)) + rectWidth;
}
console.log("of ",of) ;
if(selectBar==1){
d3.selectAll('.mover').attr("x",of);
}else{
d3.selectAll(".sub").classed("selectedSubBar",false);
}
}
function display () {
d3.selectAll(".sub").classed("selectedSubBar",false);
selectBar=0;
if(counter==0){
buildBar(0);
counter = 1;
}
$("#userId").val($("#userId option:first").val());
console.log("this is lead count "+lead_count);
d3.selectAll(".bar").classed("disable",false);
var x = parseInt(d3.select(this).attr("x")),
nx = x + d3.event.dx,
w = parseInt(d3.select(this).attr("width")),
f, nf, new_data, rects,rects1;
console.log("display x :" + x);
console.log("this is nx "+nx);
if ( nx < 0 || nx + w > width ) return;
d3.select(this).attr("x", nx);
f = displayed(x);
nf = displayed(nx+1);
if ( f === nf ) return;
new_data = dataset.slice(nf, nf + numBars);
for(i=0;i<new_data.length;i++){
console.log("new data " + new_data[i].user_id);
}
xscale.domain(new_data.map(user_id));
diagram.select(".x.axis").call(xAxis);
rects = diagram.selectAll(".bar")
.data(new_data);
rects
.enter()
.append("g")
.attr("class", "bar")
.attr("transform", function(d) { return "translate(" + xscale(user_id(d)) + ",0)"; });
;
var rects1 = rects.selectAll("rect")
.data(valueObject);
rects1
.enter().append("rect")
.attr("x", function(d) { return x1(d.name); })
.attr("width", x1.rangeBand())
.attr("y", function (d) { return yscale(0); })
.attr("height", 0)
.transition().duration(2000)
.ease("cubic")
.attr("y", function (d) { return yscale(d.value) })
.attr("height", function (d) { return height - yscale(d.value); })
.attr("fill", function(d) { return color(d.name); })
;
rects.exit().remove();
rects1.exit().remove();
};
}
.subBar {
fill: gray;
opacity: 0.5;
}
.axis path,
.y-axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.brush .extent {
stroke: #fff;
fill: steelblue;
fill-opacity: .25;
shape-rendering: crispEdges;
}
rect.mover {
stroke: red;
stroke-opacity: .1;
fill: lightSteelBlue ;
fill-opacity: .5;
}
.disable{
fill: #c0c0c0 !important;
}
.y-axis line{
stroke: lightgray;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<div>
<select name = "userId" id="userId" href = "javascript:void(0);" onchange="buildBar(1)">
<option selected disabled>Select User ID</option>
<option value="AAAA0009">AAAA0009</option>
<option value="AAAA0010">AAAA0010</option>
<option value="AAAA0011">AAAA0011</option>
<option value="AAAA0003">AAAA0003</option>
<option value="AAAA0002">AAAA0002</option>
<option value="AAAA0004">AAAA0004</option>
<option value="AAAA0006">AAAA0006</option>
<option value="AAAA0007">AAAA0007</option>
<option value="AAAA0008">AAAA0008</option>
<option value="AAAA0005">AAAA0005</option>
<option value="AAAA0001">AAAA0001</option>
<option value="AAAA0000">AAAA0000</option>
<option value="clay">clay</option>
<option value="rohi1234">rohi1234</option>
<option value="rohi1236">rohi1236</option>
<option value="AAAA0012">AAAA0012</option>
<option value="AAAA0013">AAAA0013</option>
<option value="AAAA0014">AAAA0014</option>
<option value="AAAA0015">AAAA0015</option>
<option value="AAAA0016">AAAA0016</option>
<option value="AAAA0017">AAAA0017</option>
<option value="daca2111">daca2111</option>
</select>
</div>
<div id="topChart" style=" width: 800px;">
<!-- <div id="barChartLegend"></div> -->
<svg id="barChartLegend"></svg>
<div id="barChart" href = "javascript:void(0);" onclick="buildBar(0)"></div>
</div>
I started with a simple bar chart, it worked fine.
I tried to migrate D3js V2 to V4 of below example:
https://jasonneylon.wordpress.com/2013/09/05/two-sided-horizontal-barchart-using-d3-js/
But getting error while migrating:
Error: attribute y: Expected length, "NaN".
at line no 201:
.attr("y", function(d, z){ return y(z) + y.bandwidth()/2; } )
and
line no 223:
.attr("y", function(d){ return y(d) + y.bandwidth()/2; }
Please advice.
<!DOCTYPE html>
<html>
<head>
<title>Bar Chart</title>
<script src="http://d3js.org/d3.v4.js"></script>
<style type="text/css">
.chart {
background: #00ccff;
margin: 10px;
padding-top: 10px;
}
.chart .right {
stroke: white;
fill: indianred;
}
.chart .left {
stroke: white;
fill: steelblue;
}
.chart rect:hover {
fill: #64707d;
}
.chart text {
fill: white;
}
.chart text.name {
fill: black;
}
</style>
</head>
<body>
<h1>Two sided horiztontal bar chart</h1>
<script type="text/javascript">
var randomNumbers = function() {
var numbers = [];
for (var i = 0; i < 20; i++) {
numbers.push(parseInt(Math.random() * 19) + 1);
}
return numbers;
};
var randomNames = function() {
var names = [];
for (var i = 0; i < 20; i++) {
names.push(String.fromCharCode(65 + Math.random() * 25) + String.fromCharCode(65 + Math.random() * 25) + String.fromCharCode(65 + Math.random() * 25));
}
return names;
};
var names = randomNames();
var leftData = randomNumbers();
var rightData = randomNumbers();
for (var i= 0; i< names.length; i++) {
console.log(names[i] + " from: " + leftData[i] + " to: " + rightData[i]);
}
var labelArea = 160;
var chart,
width = 400,
bar_height = 20,
height = bar_height * (names.length);
var rightOffset = width + labelArea;
var chart = d3.select("body")
.append('svg')
.attr('class', 'chart')
.attr('width', labelArea + width + width)
.attr('height', height);
var xFrom = d3.scaleLinear()
.domain([0, d3.max(leftData)])
.range([0, width]);
var y = d3.scaleBand()
.domain(names)
.rangeRound([10, height]);
console.log('Y Range: '+y.range());
console.log('y.bandwidth(): '+y.bandwidth()); // 33
var yPosByIndex = function(d, index){ return y(index); }
chart.selectAll("rect.left")
.data(leftData)
.enter().append("rect")
.attr("x", function(pos) { return width - xFrom(pos); })
.attr("y", yPosByIndex)
.attr("class", "left")
.attr("width", xFrom)
.attr("height", y.bandwidth());
chart.selectAll("text.leftscore")
.data(leftData)
.enter().append("text")
.attr("x", function(d) { return width - xFrom(d); })
.attr("y", function(d, z){ return y(z) + y.bandwidth()/2; } )
.attr("dx", "20")
.attr("dy", ".36em")
.attr("text-anchor", "end")
.attr('class', 'leftscore')
.text(String);
chart.selectAll("text.name")
.data(names)
.enter().append("text")
.attr("x", (labelArea / 2) + width)
.attr("y", function(d){ return y(d) + y.bandwidth()/2; } )
.attr("dy", ".20em")
.attr("text-anchor", "middle")
.attr('class', 'name')
.text(String);
var xTo = d3.scaleLinear()
.domain([0, d3.max(rightData)])
.range([0, width]);
chart.selectAll("rect.right")
.data(rightData)
.enter().append("rect")
.attr("x", rightOffset)
.attr("y", yPosByIndex)
.attr("class", "right")
.attr("width", xTo)
.attr("height", y.bandwidth());
chart.selectAll("text.score")
.data(rightData)
.enter().append("text")
.attr("x", function(d) { return xTo(d) + rightOffset; })
.attr("y", function(d,z){ console.log(y(z)); return y(z) + y.bandwidth()/2; } )
.attr("dx", -5)
.attr("dy", ".36em")
.attr("text-anchor", "end")
.attr('class', 'score')
.text(String);
</script>
</body>
</html>
You call console.log(y(z)); and you get 40 undefined. And you don't investigate why?
What is the domain of y? Strings
So if you give it a number it most likely gives you a wrong answer.
The same reason why your function
var yPosByIndex = function(d, index){ return y(index); }
is wrong.
The main reason you have all these problems is that you have multiple arrays of information that are related based on the index. Create 1 array with objects that contain all the related data.
var data = d3.range(20).map(i => { return {name: randomName(), left:randomNumber(), right:randomNumber()}; } );
Now adjust your program to use d.name, d.left, d.right.
Don't use parseInt if you want to calculate the integer part of a number, it is slow and unclear what you want, use Math.floor()
Better to use the same xScale for the left and right bars. Why should a bar with value 10 be smaller on one of the sides?
I would like to be able to click on a circle (coordinate points); bring the marker to the position of the circle and pause at the position of the circle and then resume again along the path.
In addition I would like to activate a circle when marker is paused on them - they are clicked (or their Voronoi cell is clicked). My intention is to have an on click function to an href for the circle coordinates eventually.
I think I need to pass the index of the path coordinates into the translateAlong function instead of the time variables but can't work out how to do this.
I’m not sure if the Voronoi cells are necessary - I tried to add this thinking I could pause my transition and activate my circles with the Voronoi cells. In any case I can’t activate the circle with the Voronoi cell.
I was helped considerably recently on Stackoverflow d3 on click on circle pause and resume transition of marker along line
and I am hoping for assistance again
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>basic_animateBetweenCircles</title>
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
path {
stroke: #848484;
fill: none;
}
circle {
fill: steelblue;
stroke: steelblue;
stroke-width: 3px;
}
.line {
fill: none;
stroke: #FE642E;
stroke-width: 4;
stroke-dasharray: 4px, 8px;
}
.point{
fill:#DF013A;
}
</style>
</head>
<body>
<script>
var width = 960,
height = 500;
var data = [
[480, 200],
[580, 400],
[680, 100],
[780, 300],
[180, 300],
[280, 100],
[380, 400]
];
//check index of path data
for (var i = 0; i < data.length; i++) {
var coordindex = i + " " + data[i];
console.log("Coordindex: " + coordindex);
//return coordindex;
};
var duration = 20000;
var line = d3.line()
.x(function(d) {return (d)[0];})
.y(function(d) {return (d)[1];});
var voronoi = d3.voronoi()
.extent([[0, 0], [width, height]]);
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
//path to animate - marker transitions along this path
var path = svg.append("path")
.data([data])
.attr("d", line)
.attr('class', 'line')
.attr("d", function(d) {
return line(d)
});
//voronoi
var voronoiPath = svg.append("g")
.selectAll("path")
.data(voronoi.polygons(data))
.enter().append("path")
.attr("d", polygon)
.on("touchmove mousemove", function() {
d3.select(this)
.style("fill", "purple");
});
//Want to activate circles when marker paused on them / in voronoi cell - intention is to have on click to href
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("class", "point")
.attr("r", 10)
.attr("transform", function(d) { return "translate(" + d + ")"; })
.on('click', function(d, i) {
d3.select(this)
.style("fill", "green");
if (d3.active(this)) {
marker.transition();
setTimeout(function() {
pauseValues.lastTime = pauseValues.currentTime;
//console.log(pauseValues);
}, 100);
} else {
transition();
}
});
var pauseValues = {
lastTime: 0,
currentTime: 0
};
//marker to transition along path
var marker = svg.append("circle")
.attr("r", 19)
.attr("transform", "translate(" + (data[0]) + ")")
.on('click', function(d, i) {
if (d3.active(this)) {
marker.transition();
setTimeout(function() {
pauseValues.lastTime = pauseValues.currentTime;
//console.log(pauseValues);
}, 100);
} else {
transition();
}
});
function transition() {
marker.transition()
.duration(duration - (duration * pauseValues.lastTime))
.attrTween("transform", translateAlong(path.node()))
.on("end", function() {
pauseValues = {
lastTime: 0,
currentTime: 0
};
transition()
});
}
function translateAlong(path) {
var l = path.getTotalLength();
return function(d, i, a) {
return function(t) {
t += pauseValues.lastTime;
var p = path.getPointAtLength(t * l);
pauseValues.currentTime = t;
return "translate(" + p.x + "," + p.y + ")";
};
};
}
function polygon(d) {
return "M" + d.join("L") + "Z";
}
</script>
</body>
If you want to pause at points, I would not run one transition across the entire path. Instead, I would break it up into N transitions, moving from point to point. Before starting the circle on it's next leg, you can pause it for a time. To do this, I would just transition along each line segment with a little algebra:
// copy our data
transData = data.slice();
function transition() {
marker.transition()
.ease(d3.easeLinear)
.duration(duration)
.attrTween("transform", function(){
// get our two points
// slope between them
// and intercetp
var p0 = transData.shift(),
p1 = transData[0];
m = (p0[1] - p1[1]) / (p0[0] - p1[0]),
b = p0[1] - (m * p0[0]),
i = d3.interpolateNumber(p0[0], p1[0]);
// move the point along the line
return function(t){
var x = i(t),
y = m*x + b;
return "translate(" + x + "," + y + ")";
}
})
// one line segment is complete
.on("end", function(){
// if no more movements, stop
if (transData.length <= 1) return;
iter++;
// determine if this is a "pause"
setTimeout(transition, pausePoints.indexOf(iter) !== -1 ? pauseTime : 0);
});
Running code, click a dot to start you can pause a multiple points:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>basic_animateBetweenCircles</title>
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
path {
stroke: #848484;
fill: none;
}
circle {
fill: steelblue;
stroke: steelblue;
stroke-width: 3px;
}
.line {
fill: none;
stroke: #FE642E;
stroke-width: 4;
stroke-dasharray: 4px, 8px;
}
.point {
fill: #DF013A;
}
</style>
</head>
<body>
<script>
var width = 960,
height = 500;
var data = [
[480, 200],
[580, 400],
[680, 100],
[780, 300],
[180, 300],
[280, 100],
[380, 400]
];
var duration = 20000/data.length,
pauseTime = 2000;
var line = d3.line()
.x(function(d) {
return (d)[0];
})
.y(function(d) {
return (d)[1];
});
var voronoi = d3.voronoi()
.extent([
[0, 0],
[width, height]
]);
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
//path to animate - marker transitions along this path
var path = svg.append("path")
.data([data])
.attr("d", line)
.attr('class', 'line')
.attr("d", function(d) {
return line(d)
});
//voronoi
var voronoiPath = svg.append("g")
.selectAll("path")
.data(voronoi.polygons(data))
.enter().append("path")
.attr("d", polygon);
//Want to activate circles when marker paused on them / in voronoi cell - intention is to have on click to href
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("class", "point")
.attr("r", 10)
.attr("transform", function(d) {
return "translate(" + d + ")";
})
.on('click', function(d, i) {
d3.select(this)
.style("fill", "green");
pausePoints.push(i);
if (pausePoints.length === 1)
transition();
});
//marker to transition along path
var marker = svg.append("circle")
.attr("r", 19)
.attr("transform", "translate(" + (data[0]) + ")");
var pausePoints = [],
iter = 0,
transData = data.slice();
function transition() {
marker.transition()
.ease(d3.easeLinear)
.duration(duration)
.attrTween("transform", function(){
var p0 = transData.shift(),
p1 = transData[0];
m = (p0[1] - p1[1]) / (p0[0] - p1[0]),
b = p0[1] - (m * p0[0]),
i = d3.interpolateNumber(p0[0], p1[0]);
return function(t){
var x = i(t),
y = m*x + b;
return "translate(" + x + "," + y + ")";
}
})
.on("end", function(){
if (transData.length <= 1) return;
iter++;
setTimeout(transition, pausePoints.indexOf(iter) !== -1 ? pauseTime : 0);
});
}
function polygon(d) {
return "M" + d.join("L") + "Z";
}
</script>
</body>
I've been looking at this example of a beeswarm plot in d3.js and I'm trying to figure out how to change the size of the dots and without getting the circles to overlap. It seems if the radius of the dots change, it doesn't take this into account when running the calculations of where to place the dots.
This is a cool visualization.
I've made a plunk of it here: https://plnkr.co/edit/VwyXfbc94oXp6kXQ7JFx?p=preview and modified it to work a bit more like you're looking for (I think). The real key is changing the call to handle collision to vary based on the radius of the circles (in the original post it's hard coded to 4, which works well when r === 3 but fails as r grows). The changes:
Make the circle radius into a variable (line 7 of script.js, var r = 3;)
Change the d3.forceCollide call to use that radius and a multiplier - line 110 (.force("collide", d3.forceCollide(r * 1.333)))
Change the .enter() call to use that radius as well (line 130: .attr("r", r))
This works reasonably well for reasonable values of r - but you'll need to adjust the height, and it might even be nice to just change the whole thing so that r is based on height (e.g. var r = height * .01). You'll notice that as is now, the circles go off the bottom and top of the graph area.
This post might be of interest as well: Conflict between d3.forceCollide() and d3.forceX/Y() with high strength() value
Here's the whole of script.js for posterity:
var w = 1000, h = 280;
var padding = [0, 40, 34, 40];
var r = 5;
var xScale = d3.scaleLinear()
.range([ padding[3], w - padding[1] ]);
var xAxis = d3.axisBottom(xScale)
.ticks(10, ".0s")
.tickSizeOuter(0);
var colors = d3.scaleOrdinal()
.domain(["asia", "africa", "northAmerica", "europe", "southAmerica", "oceania"])
.range(['#e41a1c','#377eb8','#4daf4a','#984ea3','#ff7f00','#ffff33']);
d3.select("#africaColor").style("color", colors("africa"));
d3.select("#namericaColor").style("color", colors("northAmerica"));
d3.select("#samericaColor").style("color", colors("southAmerica"));
d3.select("#asiaColor").style("color", colors("asia"));
d3.select("#europeColor").style("color", colors("europe"));
d3.select("#oceaniaColor").style("color", colors("oceania"));
var formatNumber = d3.format(",");
var tt = d3.select("#svganchor").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var svg = d3.select("#svganchor")
.append("svg")
.attr("width", w)
.attr("height", h);
var xline = svg.append("line")
.attr("stroke", "gray")
.attr("stroke-dasharray", "1,2");
var chartState = {};
chartState.variable = "totalEmission";
chartState.scale = "scaleLinear";
chartState.legend = "Total emissions, in kilotonnes";
d3.csv("co2bee.csv", function(error, data) {
if (error) throw error;
var dataSet = data;
xScale.domain(d3.extent(data, function(d) { return +d.totalEmission; }));
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (h - padding[2]) + ")")
.call(xAxis);
var legend = svg.append("text")
.attr("text-anchor", "middle")
.attr("x", w / 2)
.attr("y", h - 4)
.attr("font-family", "PT Sans")
.attr("font-size", 12)
.attr("fill", "darkslategray")
.attr("fill-opacity", 1)
.attr("class", "legend");
redraw(chartState.variable);
d3.selectAll(".button1").on("click", function(){
var thisClicked = this.value;
chartState.variable = thisClicked;
if (thisClicked == "totalEmission"){
chartState.legend = "Total emissions, in kilotonnes";
}
if (thisClicked == "emissionPerCap"){
chartState.legend = "Per Capita emissions, in metric tons";
}
redraw(chartState.variable);
});
d3.selectAll(".button2").on("click", function(){
var thisClicked = this.value;
chartState.scale = thisClicked;
redraw(chartState.variable);
});
d3.selectAll("input").on("change", filter);
function redraw(variable){
if (chartState.scale == "scaleLinear"){ xScale = d3.scaleLinear().range([ padding[3], w - padding[1] ]);}
if (chartState.scale == "scaleLog"){ xScale = d3.scaleLog().range([ padding[3], w - padding[1] ]);}
xScale.domain(d3.extent(dataSet, function(d) { return +d[variable]; }));
var xAxis = d3.axisBottom(xScale)
.ticks(10, ".0s")
.tickSizeOuter(0);
d3.transition(svg).select(".x.axis").transition().duration(1000)
.call(xAxis);
var simulation = d3.forceSimulation(dataSet)
.force("x", d3.forceX(function(d) { return xScale(+d[variable]); }).strength(2))
.force("y", d3.forceY((h / 2)-padding[2]/2))
.force("collide", d3.forceCollide(r * 1.333))
.stop();
for (var i = 0; i < dataSet.length; ++i) simulation.tick();
var countriesCircles = svg.selectAll(".countries")
.data(dataSet, function(d) { return d.countryCode});
countriesCircles.exit()
.transition()
.duration(1000)
.attr("cx", 0)
.attr("cy", (h / 2)-padding[2]/2)
.remove();
countriesCircles.enter()
.append("circle")
.attr("class", "countries")
.attr("cx", 0)
.attr("cy", (h / 2)-padding[2]/2)
.attr("r", r)
.attr("fill", function(d){ return colors(d.continent)})
.merge(countriesCircles)
.transition()
.duration(2000)
.attr("cx", function(d) { console.log(d); return d.x; })
.attr("cy", function(d) { return d.y; });
legend.text(chartState.legend);
d3.selectAll(".countries").on("mousemove", function(d) {
tt.html("Country: <strong>" + d.countryName + "</strong><br>"
+ chartState.legend.slice(0, chartState.legend.indexOf(",")) + ": <strong>" + formatNumber(d[variable]) + "</strong>" + chartState.legend.slice(chartState.legend.lastIndexOf(" ")))
.style('top', d3.event.pageY - 12 + 'px')
.style('left', d3.event.pageX + 25 + 'px')
.style("opacity", 0.9);
xline.attr("x1", d3.select(this).attr("cx"))
.attr("y1", d3.select(this).attr("cy"))
.attr("y2", (h - padding[2]))
.attr("x2", d3.select(this).attr("cx"))
.attr("opacity", 1);
}).on("mouseout", function(d) {
tt.style("opacity", 0);
xline.attr("opacity", 0);
});
d3.selectAll(".x.axis, .legend").on("mousemove", function(){
tt.html("This axis uses SI prefixes:<br>m: 10<sup>-3</sup><br>k: 10<sup>3</sup><br>M: 10<sup>6</sup>")
.style('top', d3.event.pageY - 12 + 'px')
.style('left', d3.event.pageX + 25 + 'px')
.style("opacity", 0.9);
}).on("mouseout", function(d) {
tt.style("opacity", 0);
});
//end of redraw
}
function filter(){
function getCheckedBoxes(chkboxName) {
var checkboxes = document.getElementsByName(chkboxName);
var checkboxesChecked = [];
for (var i=0; i<checkboxes.length; i++) {
if (checkboxes[i].checked) {
checkboxesChecked.push(checkboxes[i].defaultValue);
}
}
return checkboxesChecked.length > 0 ? checkboxesChecked : null;
}
var checkedBoxes = getCheckedBoxes("continent");
var newData = [];
if (checkedBoxes == null){
dataSet = newData;
redraw();
return;
};
for (var i = 0; i < checkedBoxes.length; i++){
var newArray = data.filter(function(d){
return d.continent == checkedBoxes[i];
});
Array.prototype.push.apply(newData, newArray);
}
dataSet = newData;
redraw(chartState.variable);
//end of filter
}
//end of d3.csv
});
I'm trying to append text on my multi-line donut charts. But it returns invalid value error on "translate" line.
gs
.append("text")
.attr("class", "pieNum")
.attr("transform", function(d, i, j){
console.log(d, i, j);
return "translate(" + arc.centroid(d[i]) + ")"
})
.text(function(d, i){
console.log(d);
return d.value
})
I found that it happens because on function(d, i, j), d gets array data, not a string or float.
because my data looks like
nextYear thisYear preYear
11118 10683 10892
28201 27358 28537
1473 1398 1399
0 0 1
so data like ["11118", "28201", "1473", "0"] are in d, and I'm trying to use numbers in the array, but keep failing.
Here's my code:
JS
d3.csv(filename, function(error,data){
var dataOuter = [], dataMid = [], dataInner = [];
var dataset = {
dataOuter,
dataMid,
dataInner,
};
var svgEle = document.getElementById("chart")
var width = window.getComputedStyle(svgEle, null).getPropertyValue("width")
height = window.getComputedStyle(svgEle, null).getPropertyValue("height")
cwidth = 50;
width = parseFloat(width) // remove px
height = parseFloat(height) // remove px
for (var i = 0; i < data.length; i++){
dataset.dataOuter.push(data[i].nextYear)
dataset.dataMid.push(data[i].thisYear)
dataset.dataInner.push(data[i].preYear)
}
var color = d3.scale.category10();
var pie = d3.layout.pie()
.sort(null);
var arc = d3.svg.arc();
var svg = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var gs = svg.selectAll("g").data(d3.values(dataset)).enter().append("g");
var path = gs.selectAll("path")
.data(function (d) { return pie(d); })
.enter().append("path")
.attr("fill", function (d, i) { return color(i); })
.attr("d", function (d, i, j) { return arc.innerRadius(10 + cwidth * j).outerRadius(cwidth * (j + 1))(d); });
gs
.append("text")
.attr("class", "pieNum")
.attr("transform", function(d, i, j){
console.log(d, i, j);
return "translate(" + arc.centroid(d[i]) + ")"
})
.text(function(d, i){
console.log(d);
return d.value
})
})
From the code it looks like you are nesting 3 pie charts inside of each other. For each, you need to keep a reference to arc function it is using. Here's how I'd restructure your 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>
</head>
<body>
<div id="chart" style="width: 600px; height: 600px"></div>
<script>
// input data
var data = [{"nextYear":"11118","thisYear":"10683","preYear":"10892"},{"nextYear":"28201","thisYear":"27358","preYear":"28537"},{"nextYear":"1473","thisYear":"1398","preYear":"1399"},{"nextYear":"0","thisYear":"0","preYear":"1"}];
var dataOuter = [],
dataMid = [],
dataInner = [];
// make this array of arrays
var dataset = [
dataOuter,
dataMid,
dataInner,
];
var svgEle = document.getElementById("chart")
var width = window.getComputedStyle(svgEle, null).getPropertyValue("width")
height = window.getComputedStyle(svgEle, null).getPropertyValue("height")
cwidth = 50;
width = parseFloat(width) // remove px
height = parseFloat(height) // remove px
// again, array of arrays for data-binding
for (var i = 0; i < data.length; i++) {
dataset[0].push(+data[i].nextYear);
dataset[1].push(+data[i].thisYear);
dataset[2].push(+data[i].preYear);
}
var color = d3.scale.category10();
var pie = d3.layout.pie()
.sort(null);
// pre-make all the arcs you'll need
var arcs = [
d3.svg.arc()
.innerRadius(0)
.outerRadius(50),
d3.svg.arc()
.innerRadius(60)
.outerRadius(110),
d3.svg.arc()
.innerRadius(120)
.outerRadius(170)];
var svg = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
// g for each pie
var pies = svg.selectAll(".pie")
.data(dataset)
.enter().append("g")
.attr("class", "pie");
// pie up the data, add a g for each slice (arc + text)
var slices = pies.selectAll(".slice")
.data(function(d) {
return pie(d);
})
.enter()
.append("g")
.attr("class","slice");
// add our paths with our arcs
slices
.append("path")
.attr("fill", function(d, i, j) {
return color(i);
})
.attr("d", function(d, i, j) {
return arcs[j](d);
});
// add our text
slices
.append("text")
.attr("class", "pieNum")
.attr("transform", function(d, i, j) {
return "translate(" + arcs[j].centroid(d) + ")"
})
.text(function(d, i) {
return d.value
})
// })
</script>
</body>
</html>