Related
I am trying to create two charts, a stacked bar and a bar, each displaying a different dataset. But I would like to transition from the stacked bar chart to the bar chart when I click on a button and vice versa. The code I put together works only the first time, then after that when I want to transition back to the stacked bar chart from the bar chart all the bars stack on top of each other in the form of a single bar. Can someone point me in the right direction of how to transition back from bar to stacked bar? Any help would be appreciated. (I haven't really messed with the axes yet so it is normal that they are not changing).
Here is a link to how it currently looks:https://jhjanicki.github.io/stackbartobar/
Below is my code:
var value = 'stack';
var data = [{
name: "Shihuahuaco",
value: 1067,
china: 772
}, {
name: "Cachimbo",
value: 283,
china: 1
}, {
name: "Estoraque",
value: 204,
china: 150
}, {
name: "Cumala",
value: 154,
china: 0
}, {
name: "Ishpingo",
value: 108,
china: 3
}, {
name: "Huayruro",
value: 108,
china: 1
}, {
name: "Tornillo",
value: 61,
china: 4
}, {
name: "Congona",
value: 54,
china: 0
}, {
name: "Capirona",
value: 37,
china: 5
}, {
name: "Tahuari",
value: 33,
china: 14
}, {
name: "Marupa",
value: 33,
china: 1
}, {
name: "Quinilla",
value: 28,
china: 4
}, {
name: "Azucar huayo",
value: 22,
china: 15
}, {
name: "Protium sp.",
value: 19,
china: 0
}, {
name: "Nogal",
value: 15,
china: 6
}, {
name: "Ana Caspi",
value: 14,
china: 2
}, {
name: "Cedro",
value: 14,
china: 0
}, {
name: "Carapa guianensis",
value: 12,
china: 0
}, {
name: "Leche caspi",
value: 12,
china: 0
}, {
name: "Andiroba",
value: 11,
china: 0
}, {
name: "Copaiba",
value: 7,
china: 4
}, {
name: "Palo baston",
value: 6,
china: 0
}, {
name: "Moena",
value: 5,
china: 0
}, {
name: "Almendro",
value: 5,
china: 0
}, {
name: "Chancaquero",
value: 4,
china: 0
}, {
name: "Caimitillo",
value: 3,
china: 1
}, {
name: "Nogal amarillo",
value: 3,
china: 0
}, {
name: "Couma macrocarpa",
value: 3,
china: 0
}, {
name: "Tulpay",
value: 3,
china: 0
}, {
name: "Carapa",
value: 3,
china: 0
}, {
name: "Dacryodes olivifera",
value: 2,
china: 0
}, {
name: "Capinuri",
value: 2,
china: 2
}, {
name: "Brosimum alicastrum",
value: 2,
china: 0
}, {
name: "Paramachaerium ormosioide",
value: 2,
china: 0
}, {
name: "Brosimum sp.",
value: 2,
china: 0
}, {
name: "Manchinga",
value: 2,
china: 0
}];
// data for stacked bar
var points = [{
'lon': 105.3,
'lat': 33.5,
'name': 'China',
'GTF': 1024,
"ID": "CHN"
},
{
'lon': -70.9,
'lat': 18.8,
'name': 'Dominican Republic',
'GTF': 470,
"ID": "DOM"
},
{
'lon': -101,
'lat': 38,
'name': 'USA',
'GTF': 248,
"ID": "USA"
},
{
'lon': -102.5,
'lat': 22.7,
'name': 'Mexico',
'GTF': 220,
"ID": "MEX"
},
{
'lon': 2.98,
'lat': 46,
'name': 'France',
'GTF': 85,
"ID": "FRA"
}
];
//data for bar
var margin = {
top: 20,
right: 30,
bottom: 150,
left: 60
},
widthB = 700 - margin.left - margin.right,
heightB = 500 - margin.top - margin.bottom;
var dataIntermediate = ['value', 'china'].map(function(key, i) {
return data.map(function(d, j) {
return {
x: d['name'],
y: d[key]
};
})
})
var dataStackLayout = d3.layout.stack()(dataIntermediate);
var svg = d3.select("#chart").append("svg")
.attr("width", widthB + margin.left + margin.right)
.attr("height", heightB + margin.top + margin.bottom)
var gBar = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr('class', 'gBar');
var x = d3.scale.ordinal()
.rangeRoundBands([0, widthB], .2);
var y = d3.scale.linear()
.range([heightB, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(8)
.tickFormat(function(d) {
return y.tickFormat(4, d3.format(",d"))(d)
});
data.forEach(function(d) {
d.value = +d.value; // coerce to number
d.china = +d.china;
});
x.domain(dataStackLayout[0].map(function(d) {
return d.x;
}));
y.domain([0, d3.max(dataStackLayout[dataStackLayout.length - 1],
function(d) {
return d.y0 + d.y;
})]).nice();
var layer;
var bars;
//axes
gBar.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (heightB + 10) + ")")
.call(xAxis)
.selectAll("text")
.style('font-size', '14px')
.style('font-family', 'Alegreya')
.style("text-anchor", "end")
.attr("dx", "-0.40em")
.attr("dy", ".10em")
.attr("transform", function(d) {
return "rotate(-65)"
});
gBar.append("g")
.attr("class", "y axis")
.call(yAxis)
.selectAll("text")
.style('font-size', '16px')
.style('font-family', 'Alegreya');
function draw() {
if (value == 'stack') {
layer = gBar.selectAll(".stack")
.data(dataStackLayout);
layer.exit()
.transition()
.delay(function(d, i) {
return 30 * i;
})
.duration(1500)
.style("fill", "none")
.remove();
layer.enter().append("g")
.attr("class", "stack")
.style("fill", function(d, i) {
return i == 0 ? '#b4d5c3' : '#ecaeb3';
});
bars = layer.selectAll("rect")
.data(function(d) {
return d;
});
// the "EXIT" set:
bars.exit()
.transition()
.delay(function(d, i) {
return 30 * i;
})
.duration(1500)
.attr("y", y(0))
.attr("height", heightB - y(0))
.style('fill-opacity', 1e-6)
.remove();
// the "ENTER" set:
bars.enter().append("rect")
.transition()
.delay(function(d, i) {
return 30 * i;
})
.duration(3000)
.attr("x", function(d) {
return x(d.x);
})
.attr("y", function(d) {
return y(d.y + d.y0);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y + d.y0);
})
.attr("width", x.rangeBand());
// the "UPDATE" set:
bars.transition().delay(function(d, i) {
return 30 * i;
}).duration(1500).attr("x", function(d) {
return x(d.x);
})
.attr("width", x.rangeBand()) // constant, so no callback function(d) here
.attr("y", function(d) {
return y(d.y + d.y0);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y + d.y0);
});
} else { // draw bar
x.domain(points.map(function(d) {
return d.name;
}));
y.domain([0, 1024]).nice();
bars = layer.selectAll("rect")
.data(points);
// the "EXIT" set:
bars.exit()
.transition()
.delay(function(d, i) {
return 30 * i;
})
.duration(1500)
.attr("y", y(0))
.attr("height", heightB - y(0))
.style('fill-opacity', 1e-6)
.remove();
// the "ENTER" set:
bars.enter().append("rect")
.transition()
.delay(function(d, i) {
return 30 * i;
})
.duration(3000)
.attr("x", function(d) {
return x(d.name);
})
.attr("y", function(d) {
return y(d.GTF);
})
.attr("height", function(d) {
return heightB - y(d.GTF);;
})
.attr("width", x.rangeBand());
// the "UPDATE" set:
bars.transition().delay(function(d, i) {
return 30 * i;
}).duration(1500).attr("x", function(d) {
return x(d.name);
})
.attr("width", x.rangeBand()) // constant, so no callback function(d) here
.attr("y", function(d) {
return y(d.GTF);
})
.attr("height", function(d) {
return heightB - y(d.GTF);
});
}
}
window.onload = draw();
$("#click").on('click', function() {
if (value == 'stack') {
value = 'bar';
} else {
value = 'stack';
}
draw();
});
body {
font-family: 'Alegreya', serif;
}
.axis text {
font: 10px sans-serif;
}
.axis path {
fill: none;
stroke: #000;
stroke-width: 0px;
shape-rendering: crispEdges;
}
.axis line {
fill: none;
stroke: #000;
stroke-width: 0.5px;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://d3js.org/d3.v3.js"></script>
<div id="chart"></div>
<p id="click"> click here to change </p>
The problem in your code is that you're changing the scales' domain for the bar chart, but you're not changing them back for the stacked bar chart.
Therefore, you should put this in the draw() section (conditional statement) for the stacked bar:
x.domain(dataStackLayout[0].map(function(d) {
return d.x;
}));
y.domain([0, d3.max(dataStackLayout[dataStackLayout.length - 1],
function(d) {
return d.y0 + d.y;
})]).nice();
Here is your code with that change (I also put a call for the x axis):
var value = 'stack';
var data = [{
name: "Shihuahuaco",
value: 1067,
china: 772
}, {
name: "Cachimbo",
value: 283,
china: 1
}, {
name: "Estoraque",
value: 204,
china: 150
}, {
name: "Cumala",
value: 154,
china: 0
}, {
name: "Ishpingo",
value: 108,
china: 3
}, {
name: "Huayruro",
value: 108,
china: 1
}, {
name: "Tornillo",
value: 61,
china: 4
}, {
name: "Congona",
value: 54,
china: 0
}, {
name: "Capirona",
value: 37,
china: 5
}, {
name: "Tahuari",
value: 33,
china: 14
}, {
name: "Marupa",
value: 33,
china: 1
}, {
name: "Quinilla",
value: 28,
china: 4
}, {
name: "Azucar huayo",
value: 22,
china: 15
}, {
name: "Protium sp.",
value: 19,
china: 0
}, {
name: "Nogal",
value: 15,
china: 6
}, {
name: "Ana Caspi",
value: 14,
china: 2
}, {
name: "Cedro",
value: 14,
china: 0
}, {
name: "Carapa guianensis",
value: 12,
china: 0
}, {
name: "Leche caspi",
value: 12,
china: 0
}, {
name: "Andiroba",
value: 11,
china: 0
}, {
name: "Copaiba",
value: 7,
china: 4
}, {
name: "Palo baston",
value: 6,
china: 0
}, {
name: "Moena",
value: 5,
china: 0
}, {
name: "Almendro",
value: 5,
china: 0
}, {
name: "Chancaquero",
value: 4,
china: 0
}, {
name: "Caimitillo",
value: 3,
china: 1
}, {
name: "Nogal amarillo",
value: 3,
china: 0
}, {
name: "Couma macrocarpa",
value: 3,
china: 0
}, {
name: "Tulpay",
value: 3,
china: 0
}, {
name: "Carapa",
value: 3,
china: 0
}, {
name: "Dacryodes olivifera",
value: 2,
china: 0
}, {
name: "Capinuri",
value: 2,
china: 2
}, {
name: "Brosimum alicastrum",
value: 2,
china: 0
}, {
name: "Paramachaerium ormosioide",
value: 2,
china: 0
}, {
name: "Brosimum sp.",
value: 2,
china: 0
}, {
name: "Manchinga",
value: 2,
china: 0
}];
var points = [{
'lon': 105.3,
'lat': 33.5,
'name': 'China',
'GTF': 1024,
"ID": "CHN"
}, {
'lon': -70.9,
'lat': 18.8,
'name': 'Dominican Republic',
'GTF': 470,
"ID": "DOM"
}, {
'lon': -101,
'lat': 38,
'name': 'USA',
'GTF': 248,
"ID": "USA"
}, {
'lon': -102.5,
'lat': 22.7,
'name': 'Mexico',
'GTF': 220,
"ID": "MEX"
}, {
'lon': 2.98,
'lat': 46,
'name': 'France',
'GTF': 85,
"ID": "FRA"
}];
var margin = {
top: 20,
right: 30,
bottom: 150,
left: 60
},
widthB = 700 - margin.left - margin.right,
heightB = 500 - margin.top - margin.bottom;
var dataIntermediate = ['value', 'china'].map(function(key, i) {
return data.map(function(d, j) {
return {
x: d['name'],
y: d[key]
};
})
})
var dataStackLayout = d3.layout.stack()(dataIntermediate);
var svgBar = d3.select("#chart").append("svg")
.attr("width", widthB + margin.left + margin.right)
.attr("height", heightB + margin.top + margin.bottom)
var gBar = svgBar.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr('class', 'gBar');
var x = d3.scale.ordinal()
.rangeRoundBands([0, widthB], .2);
var y = d3.scale.linear()
.range([heightB, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(8)
.tickFormat(function(d) {
return y.tickFormat(4, d3.format(",d"))(d)
});
data.forEach(function(d) {
d.value = +d.value; // coerce to number
d.china = +d.china;
});
x.domain(dataStackLayout[0].map(function(d) {
return d.x;
}));
y.domain([0, d3.max(dataStackLayout[dataStackLayout.length - 1],
function(d) {
return d.y0 + d.y;
})]).nice();
var layer;
// this part
var bars;
var gX = gBar.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (heightB + 10) + ")");
gBar.append("g")
.attr("class", "y axis")
.call(yAxis)
.selectAll("text")
.style('font-size', '16px')
.style('font-family', 'Alegreya');
function draw() {
if (value == 'stack') {
x.domain(dataStackLayout[0].map(function(d) {
return d.x;
}));
y.domain([0, d3.max(dataStackLayout[dataStackLayout.length - 1],
function(d) {
return d.y0 + d.y;
})]).nice();
layer = gBar.selectAll(".stack")
.data(dataStackLayout);
layer.exit()
.transition()
.delay(function(d, i) {
return 30 * i;
})
.duration(1500)
.style("fill", "none")
.remove();
layer.enter().append("g")
.attr("class", "stack")
.style("fill", function(d, i) {
return i == 0 ? '#b4d5c3' : '#ecaeb3';
});
bars = layer.selectAll("rect")
.data(function(d) {
return d;
});
bars.exit()
.transition()
.delay(function(d, i) {
return 30 * i;
})
.duration(1500)
.attr("y", y(0))
.attr("height", heightB - y(0))
.style('fill-opacity', 1e-6)
.remove();
bars.enter().append("rect")
.transition()
.delay(function(d, i) {
return 30 * i;
})
.duration(3000)
.attr("x", function(d) {
return x(d.x);
})
.attr("y", function(d) {
return y(d.y + d.y0);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y + d.y0);
})
.attr("width", x.rangeBand());
// the "UPDATE" set:
bars.transition().delay(function(d, i) {
return 30 * i;
}).duration(1500).attr("x", function(d) {
return x(d.x);
}) // (d) is one item from the data array, x is the scale object from above
.attr("width", x.rangeBand()) // constant, so no callback function(d) here
.attr("y", function(d) {
return y(d.y + d.y0);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y + d.y0);
})
.style("fill-opacity", 1);
gX.call(xAxis)
.selectAll("text")
.style('font-size', '14px')
.style('font-family', 'Alegreya')
.style("text-anchor", "end")
.attr("dx", "-0.40em")
.attr("dy", ".10em")
.attr("transform", function(d) {
return "rotate(-65)"
});
} else {
x.domain(points.map(function(d) {
return d.name;
}));
y.domain([0, 1024]).nice();
// this part
bars = layer.selectAll("rect")
.data(points);
bars.exit()
.transition()
.delay(function(d, i) {
return 30 * i;
})
.duration(1500)
.attr("y", y(0))
.attr("height", heightB - y(0))
.style('fill-opacity', 1e-6)
.remove();
bars.enter().append("rect")
.transition()
.delay(function(d, i) {
return 30 * i;
})
.duration(3000)
.attr("x", function(d) {
return x(d.name);
})
.attr("y", function(d) {
return y(d.GTF);
})
.attr("height", function(d) {
return heightB - y(d.GTF);;
})
.attr("width", x.rangeBand());
// the "UPDATE" set:
bars.transition().delay(function(d, i) {
return 30 * i;
}).duration(1500).attr("x", function(d) {
return x(d.name);
}) // (d) is one item from the data array, x is the scale object from above
.attr("width", x.rangeBand()) // constant, so no callback function(d) here
.attr("y", function(d) {
return y(d.GTF);
})
.attr("height", function(d) {
return heightB - y(d.GTF);
});
gX.call(xAxis);
}
}
window.onload = draw();
$("#click").on('click', function() {
if (value == 'stack') {
value = 'bar';
} else {
value = 'stack';
}
draw();
});
body {
font-family: 'Alegreya', serif;
}
.axis text {
font: 10px sans-serif;
}
.axis path {
fill: none;
stroke: #000;
stroke-width: 0px;
shape-rendering: crispEdges;
}
.axis line {
fill: none;
stroke: #000;
stroke-width: 0.5px;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://d3js.org/d3.v3.js"></script>
<div id="chart"></div>
<button id="click"> click here to change </button>
PS: Besides that, there is a lot of other minor changes you should do in your code, both for performance and design. As this is (now) a running code, I suggest you post further questions about how to improve it on Code Review, using the d3.js tag.
I drew a multi series line using d3 and added a vertical line when mouse hover over points. And I want to hide the vertical line and the text in 2018 point which has no data. I don't know how to select the line at the specific point which is translate(415,0), so I am not able to change the style to display:none;
Here is the code:
var data =[
{
'timescale': '2015',
'Not': 31,
'Nearly': 59,
'Standard': 81,
'Exceed':100
},
{
'timescale': '2016',
'Not': 28,
'Nearly': 55,
'Standard': 78,
'Exceed':100
},
{
'timescale': '2017',
'Not': 25,
'Nearly': 51,
'Standard': 75,
'Exceed':100
},
{
'timescale': '2018',
'Not': "null",
'Nearly': "null",
'Standard': "null",
'Exceed':"null"
},
{
'timescale': '2019',
'Not': 41,
'Nearly': 67,
'Standard': 90,
'Exceed':100
},
{
'timescale': '2020',
'Not': 36,
'Nearly': 61,
'Standard': 86,
'Exceed':100
},
{
'timescale': '2021',
'Not': 31,
'Nearly': 55,
'Standard': 82,
'Exceed':100
}
];
//d3.csv("test.csv", function(error,data){
console.log(data);
// set the dimensions and margins of the graph
var margin = { top: 20, right: 80, bottom: 30, left: 50 },
svg = d3.select('svg'),
width = +svg.attr('width') - margin.left - margin.right,
height = +svg.attr('height') - margin.top - margin.bottom;
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// set the ranges
var x = d3.scaleBand().rangeRound([0, width]).padding(1),
y = d3.scaleLinear().rangeRound([height, 0]),
z = d3.scaleOrdinal(["#BBB84B","#789952","#50DB51","#2D602A"]);
// define the line
var line = d3.line()
.defined(function (d) {
return !isNaN(d.total);
})
.x(function(d) { return x(d.timescale); })
.y(function(d) { return y(d.total); });
// scale the range of the data
z.domain(d3.keys(data[0]).filter(function(key) {
return key !== "timescale";
}));
var trends = z.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {
timescale: d.timescale,
total: +d[name]
};
})
};
});
x.domain(data.map(function(d) { return d.timescale; }));
y.domain([0, d3.max(trends, function(c) {
return d3.max(c.values, function(v) {
return v.total;
});
})]);
// Draw the line
var trend = g.selectAll(".trend")
.data(trends)
.enter()
.append("g")
.attr("class", "trend");
trend.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return z(d.name); });
// Draw the empty value for every point
var points = g.selectAll('.points')
.data(trends)
.enter()
.append('g')
.attr('class', 'points')
.append('text');
// Draw the circle
trend
.style("fill", "#FFF")
.style("stroke", function(d) { return z(d.name); })
.selectAll("circle.line")
.data(function(d){return d.values} )
.enter()
.append("circle")
.filter(function(d) { return d.timescale !== "2018" })
.attr("r", 5)
.style("stroke-width", 3)
.attr("cx", function(d) { return x(d.timescale); })
.attr("cy", function(d) { return y(d.total); });
// Draw the axis
g.append("g")
.attr("class", "axis axis-x")
.attr("transform", "translate(0, " + height + ")")
.call(d3.axisBottom(x));
g.append("g")
.attr("class", "axis axis-y")
.call(d3.axisLeft(y).ticks(6));
var focus = g.append('g')
.attr('class','focus')
.style('display', 'none');
focus.append('line')
.attr('class', 'x-hover-line hover-line')
.attr('y1' , 0)
.attr('y2', height)
.style('stroke',"black");
svg.append('rect')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("mousemove", mousemove);
// var timeScales = data.map(function(name) { return x(name.timescale); });
// console.log(timeScales);
var timeScales = [106,209,312,415,518,621,724];
// d3.select('.focus')
// .attr("x1",415)
// .attr("y1",0)
// .attr("x2",415)
// .attr("y2",height)
// .style("display","none");
function mouseover() {
focus.style("display", null);
d3.selectAll('.points text').style("display", null);
}
function mouseout() {
focus.style("display", "none");
d3.selectAll('.points text').style("display", "none");
}
function mousemove() {
var i = d3.bisect(timeScales, d3.mouse(this)[0], 1);
var di = data[i-1];
focus.attr("transform", "translate(" + x(di.timescale) + ",0)");
d3.selectAll('.points text')
.attr('x', function(d) { return x(di.timescale) + 5; })
.attr('y', function(d) { return y(d.values[i-1].total)-5; })
.text(function(d) { return d.values[i-1].total; })
.style('fill', function(d) { return z(d.name); });
}
body {
font-family: 'Proxima Nova', Georgia, sans-serif;
}
.line {
fill: none;
stroke-width: 3px;
}
.overlay {
fill: none;
pointer-events: all;
}
.hover-line {
stroke-width: 2px;
stroke-dasharray: 3,3;
}
<svg width="960" height="500"></svg>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js'></script>
In the mousemove function only perform the transformation if timescale is not 2018:
function mousemove() {
var i = d3.bisect(timeScales, d3.mouse(this)[0], 1);
var di = data[i-1];
if (di.timescale !== '2018') {
focus.attr("transform", "translate(" + x(di.timescale) + ",0)");
d3.selectAll('.points text')
.attr('x', function(d) { return x(di.timescale) + 5; })
.attr('y', function(d) { return y(d.values[i-1].total)-5; })
.text(function(d) { return d.values[i-1].total; })
.style('fill', function(d) { return z(d.name); });
}
}
you have using bisection as a tip
so it will draw only when it selected, lets change that so it will not draw the 2018 when mouseover it
var data =[
{
'timescale': '2015',
'Not': 31,
'Nearly': 59,
'Standard': 81,
'Exceed':100
},
{
'timescale': '2016',
'Not': 28,
'Nearly': 55,
'Standard': 78,
'Exceed':100
},
{
'timescale': '2017',
'Not': 25,
'Nearly': 51,
'Standard': 75,
'Exceed':100
},
{
'timescale': '2018',
'Not': "null",
'Nearly': "null",
'Standard': "null",
'Exceed':"null"
},
{
'timescale': '2019',
'Not': 41,
'Nearly': 67,
'Standard': 90,
'Exceed':100
},
{
'timescale': '2020',
'Not': 36,
'Nearly': 61,
'Standard': 86,
'Exceed':100
},
{
'timescale': '2021',
'Not': 31,
'Nearly': 55,
'Standard': 82,
'Exceed':100
}
];
//d3.csv("test.csv", function(error,data){
// console.log(data);
// set the dimensions and margins of the graph
var margin = { top: 20, right: 80, bottom: 30, left: 50 },
svg = d3.select('svg'),
width = +svg.attr('width') - margin.left - margin.right,
height = +svg.attr('height') - margin.top - margin.bottom;
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// set the ranges
var x = d3.scaleBand().rangeRound([0, width]).padding(1),
y = d3.scaleLinear().rangeRound([height, 0]),
z = d3.scaleOrdinal(["#BBB84B","#789952","#50DB51","#2D602A"]);
// define the line
var line = d3.line()
.defined(function (d) {
return !isNaN(d.total);
})
.x(function(d) { return x(d.timescale); })
.y(function(d) { return y(d.total); });
// scale the range of the data
z.domain(d3.keys(data[0]).filter(function(key) {
return key !== "timescale";
}));
var trends = z.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {
timescale: d.timescale,
total: +d[name]
};
})
};
});
x.domain(data.map(function(d) { return d.timescale; }));
y.domain([0, d3.max(trends, function(c) {
return d3.max(c.values, function(v) {
return v.total;
});
})]);
// Draw the line
var trend = g.selectAll(".trend")
.data(trends)
.enter()
.append("g")
.attr("class", "trend");
trend.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return z(d.name); });
// Draw the empty value for every point
var points = g.selectAll('.points')
.data(trends)
.enter()
.append('g')
.attr('class', 'points')
.append('text');
// Draw the circle
trend
.style("fill", "#FFF")
.style("stroke", function(d) { return z(d.name); })
.selectAll("circle.line")
.data(function(d){return d.values} )
.enter()
.append("circle")
.filter(function(d) { return d.timescale !== "2018" })
.attr("r", 5)
.style("stroke-width", 3)
.attr("cx", function(d) { return x(d.timescale); })
.attr("cy", function(d) { return y(d.total); });
// Draw the axis
g.append("g")
.attr("class", "axis axis-x")
.attr("transform", "translate(0, " + height + ")")
.call(d3.axisBottom(x));
g.append("g")
.attr("class", "axis axis-y")
.call(d3.axisLeft(y).ticks(6));
var focus = g.append('g')
.attr('class','focus')
.style('display', 'none');
focus.append('line')
.attr('class', 'x-hover-line hover-line')
.attr('y1' , 0)
.attr('y2', height)
.style('stroke',"black");
svg.append('rect')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("mousemove", mousemove);
// var timeScales = data.map(function(name) { return x(name.timescale); });
// console.log(timeScales);
var timeScales = [106,209,312,415,518,621,724];
// d3.select('.focus')
// .attr("x1",415)
// .attr("y1",0)
// .attr("x2",415)
// .attr("y2",height)
// .style("display","none");
function mouseover() {
focus.style("display", null);
d3.selectAll('.points text').style("display", null);
}
function mouseout() {
focus.style("display", "none");
d3.selectAll('.points text').style("display", "none");
}
function mousemove() {
var i = d3.bisect(timeScales, d3.mouse(this)[0], 1);
var di = data[i-1];
console.log(di)
if (di.timescale =="2018"){
focus.style('display','none')
d3.selectAll('.points text').style("display", "none");
}else{
focus.style('display','block')
d3.selectAll('.points text').style("display", "block");
focus.attr("transform", "translate(" + x(di.timescale) + ",0)");}
d3.selectAll('.points text')
.attr('x', function(d) { return x(di.timescale) + 5; })
.attr('y', function(d) { return y(d.values[i-1].total)-5; })
.text(function(d) { return d.values[i-1].total; })
.style('fill', function(d) { return z(d.name); });
}
body {
font-family: 'Proxima Nova', Georgia, sans-serif;
}
.line {
fill: none;
stroke-width: 3px;
}
.overlay {
fill: none;
pointer-events: all;
}
.hover-line {
stroke-width: 2px;
stroke-dasharray: 3,3;
}
<svg width="960" height="500"></svg>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js'></script>
how can I add text into circle from var data? I mean from label?
My code:
<!Doctype html>
<html>
<head>
<title>Dobble</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
</head>
<body>
<div id="canvas"></div>
<script type="text/javascript">
var w = 640,
h = 480;
var data = {
name : "root",
children : [
{ name: '1', size: 100, label: 'someText' },
{ name: '2', size: 85, label: 'someText' },
{ name: '3', size: 70, label: 'someText' },
{ name: '4', size: 55, label: 'someText' },
{ name: '5', size: 40, label: 'someText' },
{ name: '6', size: 25, label: 'someText' },
{ name: '7', size: 10, label: 'someText' },
]
}
var canvas = d3.select("#canvas")
.append("svg:svg")
.attr('width', w)
.attr('height', h);
var nodes = d3.layout.pack()
.value(function(d) { return d.size; })
.size([w, h])
.nodes(data);
nodes.shift();
canvas.selectAll('circles')
.data(nodes)
.enter().append('svg:circle')
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('r', function(d) { return d.r; })
.attr('fill', 'white')
.attr('stroke', 'grey');
</script>
</body>
</html>
The idea is creating an g element (see here for more detail), it contains a circle and text.
Let's see my example:
EDIT:
Rotate the text inside circle:
To rotate the text but keep position, you can use transform then inside it we calculate the position and radians to rotate
.attr('transform', function(d, i){
var distance = d.r - 15;
// Converts from degrees to radians.
Math.radians = function(degrees) {
return degrees * Math.PI / 180;
};
var degrees =-90;
var xText = d.x - Math.cos(Math.radians(degrees))*distance;
var yText = d.y - Math.sin(Math.radians(degrees))*distance;
return "translate(" +xText + "," + yText + ") rotate(" + degrees+ ")";
});
var w = 640,
h = 480;
var data = {
name : "root",
children : [
{ name: '1', size: 100, label: 'someText' },
{ name: '2', size: 85, label: 'someText' },
{ name: '3', size: 70, label: 'someText' },
{ name: '4', size: 55, label: 'someText' },
{ name: '5', size: 40, label: 'someText' },
{ name: '6', size: 25, label: 'someText' },
{ name: '7', size: 10, label: 'someText' },
]
}
var canvas = d3.select("#canvas")
.append("svg:svg")
.attr('width', w)
.attr('height', h);
var nodes = d3.layout.pack()
.value(function(d) { return d.size; })
.size([w, h])
.nodes(data);
nodes.shift();
var enterEl = canvas.selectAll('circles')
.data(nodes)
.enter().append("g");
enterEl.append('svg:circle')
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('r', function(d) { return d.r; })
.attr('fill', 'white')
.attr('stroke', 'grey');
enterEl.append('text')
.text(function(d){ return d.label })
.style("font-size", function(d) { return Math.min(2 * d.r, (2 * d.r - 8) / this.getComputedTextLength() * 13) + "px"; })
.attr("dy", ".35em").attr('transform', function(d, i){
var distance = d.r - 15;
// Converts from degrees to radians.
Math.radians = function(degrees) {
return degrees * Math.PI / 180;
};
var degrees =-90*i;
var xText = d.x - Math.cos(Math.radians(degrees))*distance;
var yText = d.y - Math.sin(Math.radians(degrees))*distance;
return "translate(" +xText + "," + yText + ") rotate(" + degrees+ ")";
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id='canvas'></div>
I'm having issues with my update() function. Here, within svg.append('rect') I have .on('click') where I simply change the data, then run update().
Why doesn't this work? How do I make it work?
var width = 640,
height = 480;
var graphNodes = [
{ id: 0, x: 39, y: 343, r: 15 },
{ id: 1, x: 425, y: 38, r: 15 },
{ id: 2, x: 183, y: 417, r: 15 },
{ id: 3, x: 564, y: 31, r: 15 },
{ id: 4, x: 553, y: 351, r: 15 },
{ id: 5, x: 454, y: 298, r: 15 },
{ id: 6, x: 493, y: 123, r: 15 },
{ id: 7, x: 471, y: 427, r: 15 },
{ id: 8, x: 142, y: 154, r: 15 }
];
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height);
svg.append('rect')
.attr('class', 'graph')
.attr('width', width)
.attr('height', height)
.attr('fill', 'lightblue')
.attr('opacity', 0.3)
.on('click', function(){
graphNodes[8].id = 'hey there'; // <----- Why doesn't this happen?
update();
});
var nodeGroup = svg.selectAll('.nodes')
.data(graphNodes, function(d){ return d.id; })
.enter().append('g')
.attr('class', 'node');
nodeGroup.append('circle')
.attr('cx', function(d) { return d.x })
.attr('cy', function(d) { return d.y })
.attr("r", function(d){ return d.r; })
.attr("fill", "gray");
nodeGroup.append('text')
.attr("dx", function(d){ return d.x + 20; })
.attr("dy", function(d){ return d.y + 5; })
.text(function(d) { return d.id });
function update() {
if(nodeGroup){
// Update nodes
var node = nodeGroup.data(graphNodes, function(d){ return d.id; }),
nodeEnter = node.enter().append('g')
.attr('class', 'node');
nodeEnter.append('circle')
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('r', function(d){ return d.r; })
.attr('fill', 'gray');
nodeEnter.append('text')
.attr("dx", function(d){ return d.x + 20; })
.attr("dy", function(d){ return d.y + 5; })
.text(function(d) { return d.id });
nodeGroup = nodeEnter.merge(node);
node.exit().remove();
}
}
Here's a fiddle
Typed this as a comment in the message you left me, but here it is as an answer.
You need to separate out the things you do on enter, on update and on exit. On enter you want to just append and set any attributes that never change. On update you want to add/change the text and add/change the radius. On exit you remove. Here I've properly handled the enter, update, exit paradigm:
// bind the data
var node = nodeGroup.data(graphNodes, function(d){ return d.id; }),
// this is the enter selection
nodeEnter = node.enter().append('g')
.attr('class', 'node');
// append to enter selection
// append and set color, we never change color
nodeEnter.append('circle')
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('fill', 'gray');
// again entering, append text and set position
nodeEnter.append('text')
.attr("dx", function(d){ return d.x + 20; })
.attr("dy", function(d){ return d.y + 5; });
// nodeGroup is the enter + update selection
nodeGroup = nodeEnter.merge(node);
// change the things we want to change on every update
nodeGroup.select("text")
.text(function(d) { return d.text ? d.text : d.id });
nodeGroup.select("circle")
.attr('r', function(d){ return d.r; })
// exit, just remove
node.exit().remove();
Running code:
var width = 640,
height = 480;
var graphNodes = [
{ id: 0, x: 39, y: 343, r: 15 },
{ id: 1, x: 425, y: 38, r: 15 },
{ id: 2, x: 183, y: 417, r: 15 },
{ id: 3, x: 564, y: 31, r: 15 },
{ id: 4, x: 553, y: 351, r: 15 },
{ id: 5, x: 454, y: 298, r: 15 },
{ id: 6, x: 493, y: 123, r: 15 },
{ id: 7, x: 471, y: 427, r: 15 },
{ id: 8, x: 142, y: 154, r: 15 }
];
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height);
svg.append('rect')
.attr('class', 'graph')
.attr('width', width)
.attr('height', height)
.attr('fill', 'lightblue')
.attr('opacity', 0.3)
.on('click', function(){
/*graphNodes.push({
x: d3.mouse(this)[0],
y: d3.mouse(this)[1],
id: graphNodes.length,
r: 15
});*/
graphNodes.splice(2, 1);
graphNodes[Math.floor(Math.random() * graphNodes.length)].text = "Tomato!";
graphNodes[Math.floor(Math.random() * graphNodes.length)].r = Math.random() * 30;
update();
});
var nodeGroup = svg.selectAll('.nodes')
.data(graphNodes, function(d){ return d.id; })
.enter().append('g')
.attr('class', 'node');
nodeGroup.append('circle')
.attr('cx', function(d) { return d.x })
.attr('cy', function(d) { return d.y })
.attr("r", function(d){ return d.r; })
.attr("fill", "gray");
nodeGroup.append('text')
.attr("dx", function(d){ return d.x + 20; })
.attr("dy", function(d){ return d.y + 5; })
.text(function(d) { return d.id });
function update() {
if(nodeGroup){
// Update nodes
var node = nodeGroup.data(graphNodes, function(d){ return d.id; }),
nodeEnter = node.enter().append('g')
.attr('class', 'node');
// this is the enter selection
// append and set color, we never change color
nodeEnter.append('circle')
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('fill', 'gray');
nodeEnter.append('text')
.attr("dx", function(d){ return d.x + 20; })
.attr("dy", function(d){ return d.y + 5; });
// nodeGroup is the enter + update selection
nodeGroup = nodeEnter.merge(node);
// change the things we want to change on every update
nodeGroup.select("text")
.text(function(d) { return d.text ? d.text : d.id });
nodeGroup.select("circle")
.attr('r', function(d){ return d.r; })
// exit, just remove
node.exit().remove();
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
I'm having a lot of trouble getting a circle to delete. In this example I want to delete the circle labeled "2" but it just deletes the last circle in the array. If you uncomment the commented part and comment the graphNodes.splice(2, 1), it works as it should adding a new circle. But the delete won't work.
What am I missing here?
var width = 640,
height = 480;
var graphNodes = [
{ id: 0, x: 39, y: 343, r: 15 },
{ id: 1, x: 425, y: 38, r: 15 },
{ id: 2, x: 183, y: 417, r: 15 },
{ id: 3, x: 564, y: 31, r: 15 },
{ id: 4, x: 553, y: 351, r: 15 },
{ id: 5, x: 454, y: 298, r: 15 },
{ id: 6, x: 493, y: 123, r: 15 },
{ id: 7, x: 471, y: 427, r: 15 },
{ id: 8, x: 142, y: 154, r: 15 }
];
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height);
svg.append('rect')
.attr('class', 'graph')
.attr('width', width)
.attr('height', height)
.attr('fill', 'lightblue')
.attr('opacity', 0.3)
.on('click', function(){
/*graphNodes.push({
x: d3.mouse(this)[0],
y: d3.mouse(this)[1],
id: graphNodes.length,
r: 15
});*/
graphNodes.splice(2, 1);
update();
});
var nodeGroup = svg.selectAll('.nodes')
.data(graphNodes, function(d){ return d.id; })
.enter().append('g')
.attr('class', 'node');
nodeGroup.append('circle')
.attr('cx', function(d) { return d.x })
.attr('cy', function(d) { return d.y })
.attr("r", function(d){ return d.r; })
.attr("fill", "gray");
nodeGroup.append('text')
.attr("dx", function(d){ return d.x + 20; })
.attr("dy", function(d){ return d.y + 5; })
.text(function(d) { return d.id });
function update() {
if(nodeGroup){
// Update nodes
var node = nodeGroup.data(graphNodes),
nodeEnter = node.enter().append('g')
.attr('class', 'node');
nodeEnter.append('circle')
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('r', function(d){ return d.r; })
.attr('fill', 'gray');
nodeEnter.append('text')
.attr("dx", function(d){ return d.x + 20; })
.attr("dy", function(d){ return d.y + 5; })
.text(function(d) { return d.id });
nodeGroup = nodeEnter.merge(node);
node.exit().remove();
}
}
Fiddle
When you re-bind your data in update function, you forgot your key function:
var node = nodeGroup.data(graphNodes, function(d){ return d.id; }),
Update fiddle.