I'm drawing a simple bar chart with d3, and I have an x axis setup and have the brush setup so that I can brush to "select" a group of the bars. I would like to then drill down and scale the chart to contain only these bars. The following code works and the colors of the bars inside and touched by the brush turn the right color, but I can't make the thing zoom.
I have looked at this: http://bl.ocks.org/mbostock/1667367 and a bunch of other stuff and just cannot figure it out.
Here is a fiddle, can someone show me how to simply zoom the darn thing?
var brush;
function go3()
{
var dataset = [];
var m = 40;
var count = 500;
dataset.push(m);
for (var i = 0; i < 150; i++) { //Loop 25 times
var newNumber = Math.random() * m; //New random number (0-30)
dataset.push(newNumber); //Add new number to array
}
margin = {top: 10, right: 10, bottom: 30, left: 10},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom
w = width;
h = height;
yScale = d3.scale.linear().domain([0, d3.max(dataset)]).range([0, h * .95]);
xScale = d3.scale.linear()
.range([0, w])
.domain([0, w]);
var xAxis = d3.svg.axis().scale(xScale).orient("bottom");
console.log("Max: " + d3.max(dataset));
svg = d3.select("body")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
;
svg.append("rect")
.attr("stroke", "grey")
.attr("stroke-width", 1)
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.attr("fill", "#FFFFFF")
.classed("main-container", true);
svg.append("rect")
.attr("width", w)
.attr("height", h)
.attr("stroke", "grey")
.attr("stroke-width", 0)
.attr("fill", "#EEFFEE")
.attr("x", margin.left)
.attr("y", margin.top)
.classed("brushable-container", true)
;
xAxisGroup = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + (h + margin.top) + ")")
.call(xAxis)
padding=2;
rects = svg.append("g").selectAll(".brushable")
.data(dataset)
.enter()
.append("rect")
.classed("brushable", true);
brush = d3.svg.brush()
.x(xScale)
.on("brush", brushmove)
.on("brushend", brushend);
context = svg.append("g")
.attr("class", "context")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("class", "brush")
.call(brush)
.selectAll('rect')
.attr('height', h);
barWidth = w / dataset.length;
console.log("Width: " + barWidth);
rects
.attr("width", 4)
.attr("height", function(d, i){ return yScale(d)})
.attr("stroke", "yellow")
.attr("stroke-width", .3)
.attr("x", function(d, i){ return (i * barWidth + margin.left) })
.attr("y", function(d, i){ return h - yScale(d) + margin.top} )
;
}
function brushend(){
var extent = brush.extent();
var min = extent[0] >= extent[1] ? extent[1] : extent[0];
var max = extent[0] >= extent[1] ? extent[0] : extent[1];
var lolobb = d3.selectAll("rect.brushable");
var lob = lolobb[0];
console.log(min + " - " + max);
var i = 0;
while( i < lob.length ){
var bbb = lob[i];
try {
var p = parseFloat(bbb.attributes.x.value);
if(min <= p && max >= p) {
d3.select(bbb).attr("fill", "#00FF00");
} else {
d3.select(bbb).attr("fill", "#000000");
}
i++;
} catch(r) {
console.log("BBB");
console.log(bbb);
console.log("Error with " + i);
console.log(typeof(bbb));
console.log(r);
}
}
console.log(min + " - " + max);
console.log(lolobb);
}
function brushmove() {
var extent = brush.extent();
}
You can do this by adding calling a zoom function on "body", you can do so by doing:
svg = d3.select("body")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.call(d3.behavior.zoom().scaleExtent([1, 8]).on("zoom", zoom))
;
The zooming part is:
.append("g")
.call(d3.behavior.zoom().scaleExtent([1, 8]).on("zoom", zoom))
Then add the zoom() function:
function zoom() {
svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
Here is a Fiddle Example. Use the scroll wheel to zoom.
Related
I am creating a visualisation using d3.js and svg images in PowerBi (this uses version 3 of d3).
I have got my visual working, however my legend is not rendering. I tested this in a browser, and the legend items appear in the elements of the page, but just aren't showing up.
My code for the legend items are
var pbi = {
width:1108,
height:636,
colors:[
"#A70240",
"#4A2366",
"#009A44",
"#A0D081",
"#01B5BB",
"#137B88",
"#5D6771",
"#CDC8C1"
]
var margin = {top: 20, right: 30, bottom: 30, left: 140},
width = pbi.width - margin.left - margin.right,
height = pbi.height - margin.top - margin.bottom,
legendleft = pbi.width - margin.right;
var ly = d3.scale.ordinal() // For legend
.rangeRoundBands([0, height], barPad, barOuterPad);
var svg = d3.select("#chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
----
ly.domain(rData.map(function(d) { return d.milestone; })); // Legend
var milestoneMap = {}; // Maps years to colours
var legendArray = []; // For legend
rData.forEach(function (d) {
var entry = d.year;
var rowEntry = {
entry: entry, // Axis label
milestone: d.milestone, // For colour lookup
date: d.date, // For X position of points
y: y(entry)
}
if (!(d.milestone in milestoneMap)) {
// First occurrence of each year saved to legend
legendArray.push({milestone: d.milestone});
rowArray.push(rowEntry);
var legend = svg.append("g").attr("id", "legend")
.attr("transform", "translate(" + legendleft + "," + margin.top + ")").selectAll(null)
.data(legendArray)
.enter();
// Legend agency labels
legend.append("text")
.attr("class", "milestoneLabel")
.attr("x", 25)
.attr("y", function(d) { return ly(d.milestone)+5; })
.style("stroke", "black")
.style("stroke-width", .3)
.text(function(d) { return d.milestone; });
legend.append("circle")
.attr("class", "legend")
.attr("cx", 12)
.attr("cy", function(d) { return ly(d.milestone); })
.attr("r", 8)
.style("stroke", "black")
.style("stroke-width", 1)
.style("fill", function(d, i) { return milestoneMap[d.milestone]; });
This is omitting code that calculates all other elements.
Why is it that the legend circle and label is appearing as an element on the page but isn't rendering anything?
Thanks
Turns out, I just needed to add my legend left to my svg.
From
var svg = d3.select("#chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
to
var svg = d3.select("#chart")
.attr("width", width + margin.left + margin.right + legendleft)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
I want to add a legend to my D3js donut chart, like this post, its supposed to be kind of simple but I can't get it and I don't know what I'm doing wrong, also the console is not throwing any errors, anyone can see the error?
my data comes from a csv and looks like this:
data = [{
value: 30,
key: "Alta"
}, {
value: 37,
key: "Media"
}, {
value: 15,
key: "Moderada"
}, {
value: 8,
key: "Baja"
},
{
value: 13,
key: "Muy baja"
},
]
and this is the part that adds the data to the chart:
var margin = {top: 20, right: 20, bottom: 20, left: 20},
width = 500 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom,
radius = width/2;
var color = d3.scaleOrdinal()
.range(["#B4DC70", "#FEFE2B", "#FE8E2B", "#FE2B2B", "#2B5EFE"]);
// arc generator
var arc = d3.arc()
.outerRadius(radius - 10)
.innerRadius(radius - 70);
// generate pie chart and donut chart
var pie = d3.pie()
.sort(null)
.value(function(d) { return d.value; });
// define the svg for pie chart
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
d3.csv("alertas.csv", function(error, data) {
if (error) throw error;
var amenazasCount = d3.nest()
.key(function(d) { return d.TEXTO_AMENAZA; })
.rollup(function(v) { return v.length; })
.entries(data);
amenazasCount.forEach(function(d) {
d.value = +d.value;
});
var g = svg.selectAll(".arc")
.data(pie(amenazasCount))
.enter().append("g")
.attr("class", "arc");
// append path
g.append("path")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.key); });
var legendG = svg.selectAll(".legend")
.data(pie(amenazasCount))
.enter().append("g")
.attr("transform", function(d,i){
return "translate(" + (width - 110) + "," + (i * 15 + 20) + ")";
})
.attr("class", "legend");
legendG.append("rect")
.attr("width", 10)
.attr("height", 10)
.attr("fill", function(d) { return color(d.data.key); });
legendG.append("text")
.text(function(d){
return d.value + " " + d.data.key;
})
.style("font-size", 12)
.attr("y", 10)
.attr("x", 11);
});
The SVG and G elements are not sized correctly or consistently with the margins you had defined, so that legend was positioned too far to the right, and outside of the SVG view.
If you set up your SVG and g elements like this then it will work:
// set a width and height inclusive of the margins
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
// create a parent g element for everything to be included within
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// position the pie half of the width and height
var pieG = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("transform", "translate(" + width/2 + "," + height/2 + ")")
.attr("class", "arc");
And then append the legendG to the "g" element:
var legendG = g.selectAll(".legend")
.data(legendData)
.enter()
.append("g")
.attr("transform", function(d,i){
return "translate(" + (width - 60) + "," + (i * 15 + 20) + ")";
})
.attr("class", "legend");
I have groups(svg groups) of different height, I want these groups of svg not to overlap each other and have uniform spacing between them.
Following is a link to my jsfiddle.
https://jsfiddle.net/3jxqgjcL/1/
var margin = { top: 10, right: 10, bottom: 30, left: 10 },
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom
distance = 0, barHeight = 75, i = 0;
function yAxis() {
if (i == 0) {
i++;
return 2;
} else {
distance = parseInt(barHeight) * i;
i++;
return distance;
}
}
var rectangles = d3.range(5).map(function() {
return {
x: 5,
y: Math.round(yAxis())
};
});
var color = d3.scaleOrdinal(d3.schemeCategory20);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
var group = svg.selectAll('g')
.data(rectangles)
.enter().append("g")
.attr("class", "groups")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"
})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
group.append("rect")
.attr("height", function(d,i){return Math.round(Math.random() * 100)})
.attr("width", 360)
.style("fill", function(d, i) {
return color(i);
});
var getheight = d3.select('g').node();
console.log("getheight"+ getheight.getBoundingClientRect().height);
group.append("text")
.attr("text-anchor", "start")
.style("fill", "steelblue")
.text("Close");
function dragstarted(d) {
d3.select(this).raise().classed("active", true);
}
function dragged(d) {
d3.select(this).attr("transform", "translate(" + margin.left / 2 + "," + (d.y = d3.event.y) + ")");
}
function dragended(d) {
d3.select(this).classed("active", false);
var theseGroups = svg.selectAll(".groups").sort(function(a, b) {
return d3.ascending(a.y, b.y);
});
theseGroups.attr("transform", function(d, i) {
return "translate(" + margin.left / 2 + "," + (d.y = barHeight * i) + ")";
})
}
I've created a basic bar chart in d3 from an array of integers.
let maxWidth = $('body').width();
let chartWidth = maxWidth * 0.75; // also arbitrary
let chartHeight = chartWidth / _GoldenRatio;
let barWidth = (chartWidth / dataArray.length) - horizontalSpacing;
let barHeightUnit = chartHeight / d3.max(dataArray);
let mainChart = d3.select(containerSelector)
.append('svg')
.attr('width', chartWidth)
.attr('height', chartHeight);
mainChart.selectAll("rect")
.data(dataArray)
.enter()
.append("rect")
.attr("class", "bar")
.attr("height", function(d, i) { return (d * barHeightUnit) })
.attr("width",barWidth)
.attr("x", function(d, i) { return i * (barWidth + horizontalSpacing) + horizontalSpacing })
.attr("y", function(d, i) { return chartHeight - (d * barHeightUnit) });
I do this a few times with a few different arrays. Each list of integers corresponds with a list of strings that I'd like to display along the x-axis. Each string will lie below one bar to label it.
I can't figure out a way to do this easily with d3 or d3-axis. What is an easy way to construct an axis from this list of strings?
Hope this snippet using d3.v4 helps.
var margin = {
top: 100,
right: 100,
bottom: 100,
left: 100
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scaleBand()
.range([0, width])
.domain(["apple", "orange", "banana", "grapefruit"]);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
<script src="https://d3js.org/d3.v4.min.js"></script>
I got this piece of code in which the timeline updates his value by sliding(manually). Problem is That I got an error saying that projection is not a code. Can someone help me please.
Setting the size of the slider.
var margin = {
top: 50,
right: 90,
bottom: 50,
left: 50
},
width = 1000 - margin.left - margin.right,
height = 300 - margin.bottom - margin.top;
// scale function
var timeScale = d3.time.scale()
.domain([new Date('2016-01-01'), new Date('2016-12-31')])
.range([0, width])
.clamp(true);
// initial value
var startValue = timeScale(new Date('2012-03-20'));
startingValue = new Date('2016-08-12');
//////////
defines brush
var brush = d3.svg.brush()
.x(timeScale)
.extent([startingValue, startingValue])
.on("brush", brushed);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
// classic transform to position g
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "x axis")
// put in middle of screen
.attr("transform", "translate(0," + height / 2 + ")")
// inroduce axis
.call(d3.svg.axis()
.scale(timeScale)
.orient("bottom")
.tickFormat(function(d) {
return formatDate(d);
})
.tickSize(0)
.tickPadding(12)
.tickValues([timeScale.domain()[0], timeScale.domain()[1]]))
.select(".domain")
.select(function() {
console.log(this);
return this.parentNode.appendChild(this.cloneNode(true));
})
.attr("class", "halo");
var slider = svg.append("g")
.attr("class", "slider")
.call(brush);
slider.selectAll(".extent,.resize")
.remove();
slider.select(".background")
.attr("height", height);
var handle = slider.append("g")
.attr("class", "handle")
handle.append("path")
.attr("transform", "translate(0," + height / 2 + ")")
.attr("d", "M 0 -20 V 20")
handle.append('text')
.text(startingValue)
.attr("transform", "translate(" + (-18) + " ," + (height / 2 - 25) + ")");
slider
.call(brush.event)
Sets the canvas with the projection.
var width = 1280;
var height = 900;
var projection = d3.geo.mercator()
.scale(200000)
.center([4.9, 52.36])
.translate([width / 2, height / 2]);
var canvas = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.call(d3.behavior.zoom().on("zoom", function () {
canvas.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")")
}))
.append("g")
Logs the events of the day and the coordinates to the console.
function brushed() {
var value = brush.extent()[0];
console.log()
if (d3.event.sourceEvent) { // not a programmatic event
value = timeScale.invert(d3.mouse(this)[0]);
brush.extent([value, value]);
}
handle.attr("transform", "translate(" + timeScale(value) + ",0)");
handle.select('text').text(formatDate(value));
slidedate = (formatDate(value));
console.log(slidedate)
console.log("dit is datum" + slidedate)
dataCsv.forEach(function(d) {
//console.log("datum is" + slidedate)
if (slidedate == d.Datepattern_startdate) {
console.log(slidedate)
console.log(d.Title)
coords = projection([+d["Longitude"],+d["Latitude"]]);
console.log(coords);
var circle = canvas.append("circle")
.attr("cx", coords[0] )
.attr("cy", coords[1] )
.attr("r", "6px")
.attr("fill", "orange")
.style("opacity", .6)
.append("title")
.text(function(dataCsv) {return d.Title})
} else {
//console.log(" Werkt niet")
}
});
Error = mapm.html:272 Uncaught TypeError: projection is not a function.