When I try to brush & zoom a portion of the line chart, some parts of the selected area render outside the chart.
Code and behavior reproduction can be found at this jsbin.
Click & drag to select a portion and zoom in, double click to zoom out.
var svg = d3
.select('body')
.append('svg')
.attr('class', 'chart')
.attr('width', 960)
.attr('height', 500);
var margin = {
top: 40,
right: 40,
bottom: 40,
left: 40
};
var width = +svg.attr('width') - margin.left - margin.right;
var height = +svg.attr('height') - margin.top - margin.bottom;
var g = svg
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var timeParser = d3.timeParse('%Y-%m-%d');
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var brush = d3.brush().on('end', brushended);
var idleTimeout;
var idleDelay = 350;
var x0;
var y0;
var xAxis;
var yAxis;
var line = d3
.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.price);
})
.curve(d3.curveNatural);
var start = new Date();
var end = new Date(start.toDateString());
start.setFullYear(end.getFullYear() - 1);
var startStr = start.toISOString().slice(0, 10);
var endStr = end.toISOString().slice(0, 10);
var url = "https://api.coindesk.com/v1/bpi/historical/close.json?start=" + startStr + "&end=" + endStr;
d3.json(url, function(error, response) {
var data = Object.keys(response.bpi).map(function(date) {
return {
date: timeParser(date),
price: response.bpi[date]
};
});
x0 = d3.extent(data, function(d) {
return d.date;
});
y0 = d3.extent(data, function(d) {
return d.price;
});
x.domain(x0);
y.domain(y0);
xAxis = d3.axisBottom(x);
yAxis = d3.axisLeft(y);
g
.append('g')
.attr('class', 'axis axis--x')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
g
.append('g')
.attr('class', 'axis axis--y')
.call(yAxis);
g
.append('path')
.attr('class', 'line')
.datum(data)
.attr('fill', 'none')
.attr('stroke', 'steelblue')
.attr('d', line);
svg
.append('g')
.attr('class', 'brush')
.call(brush);
});
function brushended() {
var s = d3.event.selection;
if (!s) {
if (!idleTimeout) {
return (idleTimeout = setTimeout(idled, idleDelay));
}
x.domain(x0);
y.domain(y0);
} else {
x.domain([s[0][0] - 40, s[1][0] - 40].map(x.invert, x));
y.domain([s[1][1] - 40, s[0][1] - 40].map(y.invert, y));
svg.select('.brush').call(brush.move, null);
}
zoom();
}
function idled() {
idleTimeout = null;
}
function zoom() {
var t = svg.transition().duration(750);
svg
.select('.axis--x')
.transition(t)
.call(xAxis);
svg
.select('.axis--y')
.transition(t)
.call(yAxis);
svg
.select('.line')
.transition(t)
.attr('d', line);
}
.chart {
border: 1px solid #bdbdbd;
box-sizing: border-box;
}
<script src="https://unpkg.com/d3#4.12.2/build/d3.min.js"></script>
That's the expected behaviour. The most common way to deal with that is using a <clipPath>.
For instance, in your case:
var clipPath = g.append("defs")
.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
Then, in your path:
g.append('path')
//etc...
.attr("clip-path", "url(#clip)");
Here is the updated JSBin: https://jsbin.com/tatuhipevi/1/edit?js,output
And here the updated S.O. snippet:
var svg = d3
.select('body')
.append('svg')
.attr('class', 'chart')
.attr('width', 960)
.attr('height', 500);
var margin = {
top: 40,
right: 40,
bottom: 40,
left: 40
};
var width = +svg.attr('width') - margin.left - margin.right;
var height = +svg.attr('height') - margin.top - margin.bottom;
var g = svg
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var clipPath = g.append("defs")
.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var timeParser = d3.timeParse('%Y-%m-%d');
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var brush = d3.brush().on('end', brushended);
var idleTimeout;
var idleDelay = 350;
var x0;
var y0;
var xAxis;
var yAxis;
var line = d3
.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.price);
})
.curve(d3.curveNatural);
var start = new Date();
var end = new Date(start.toDateString());
start.setFullYear(end.getFullYear() - 1);
var startStr = start.toISOString().slice(0, 10);
var endStr = end.toISOString().slice(0, 10);
var url = "https://api.coindesk.com/v1/bpi/historical/close.json?start=" + startStr + "&end=" + endStr;
d3.json(url, function(error, response) {
var data = Object.keys(response.bpi).map(function(date) {
return {
date: timeParser(date),
price: response.bpi[date]
};
});
x0 = d3.extent(data, function(d) {
return d.date;
});
y0 = d3.extent(data, function(d) {
return d.price;
});
x.domain(x0);
y.domain(y0);
xAxis = d3.axisBottom(x);
yAxis = d3.axisLeft(y);
g
.append('g')
.attr('class', 'axis axis--x')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
g
.append('g')
.attr('class', 'axis axis--y')
.call(yAxis);
g
.append('path')
.attr('class', 'line')
.datum(data)
.attr('fill', 'none')
.attr('stroke', 'steelblue')
.attr('d', line)
.attr("clip-path", "url(#clip)");
svg
.append('g')
.attr('class', 'brush')
.call(brush);
});
function brushended() {
var s = d3.event.selection;
if (!s) {
if (!idleTimeout) {
return (idleTimeout = setTimeout(idled, idleDelay));
}
x.domain(x0);
y.domain(y0);
} else {
x.domain([s[0][0] - 40, s[1][0] - 40].map(x.invert, x));
y.domain([s[1][1] - 40, s[0][1] - 40].map(y.invert, y));
svg.select('.brush').call(brush.move, null);
}
zoom();
}
function idled() {
idleTimeout = null;
}
function zoom() {
var t = svg.transition().duration(750);
svg
.select('.axis--x')
.transition(t)
.call(xAxis);
svg
.select('.axis--y')
.transition(t)
.call(yAxis);
svg
.select('.line')
.transition(t)
.attr('d', line);
}
.chart {
border: 1px solid #bdbdbd;
box-sizing: border-box;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
Also, it's a good idea using a <clipPath> in the axes as well.
Related
Arrow head works fine on line draw but direction is wrong on axis!
live_update()
function live_update() {
var data1 = []
var n = 20
for (var i=0;i<=n;i++) {
data1.push({x:i,y:i*i})
}
var data2 = []
for (var i=0;i<=n;i++) {
data2.push({x:i,y:Math.sin(2*Math.PI/n*i)})
}
var toggle = 1
var div = d3.select('body').append('div')
div.append('button')
.text("update")
.on('click',(event,d) => {
console.log('click')
if (toggle == 0) {
update(data1)
toggle = 1
}else{
update(data2)
toggle = 0
}
})
var margin = {top: 10, right: 30, bottom: 30, left: 50},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
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 + ")")
.attr('class','box')
var aw = 6
var ah = 4
svg
.append("defs")
.append("marker")
.attr("id", "arrow")
.attr("refX", aw+1)
.attr("refY", ah/2+1)
.attr("markerWidth", aw+2)
.attr("markerHeight", ah+2)
.attr("orient", "auto")
.append("path")
.attr("d", ['M',1+aw/5,1+ah/2,'L',1,1,1+aw,1+ah/2,1,1+ah,'z'].join(' '));
svg.append('line').attr('class','avline')
var x = d3.scaleLinear().range([0,width]);
var xAxis = d3.axisBottom().scale(x);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.attr("class","myXaxis")
var y = d3.scaleLinear().range([height, 0]);
var yAxis = d3.axisLeft().scale(y);
svg.append("g")
.attr("class","myYaxis")
function update(data) {
var duration = 1000
var xmax = d3.max(data, function(d) { return d.x })
x.domain([d3.min(data,d => d.x), xmax]);
var xaxis = svg.selectAll(".myXaxis").transition()
.duration(duration)
.call(xAxis);
var ymin = d3.min(data,d => d.y)
y.domain([ymin, d3.max(data, function(d) { return d.y }) ]);
var yaxis = svg.selectAll(".myYaxis")
.transition()
.duration(duration)
.call(yAxis);
var frame = d3.select('.box')
frame.selectAll(".lineTest1")
.data([data])
.join("path")
.attr("class","lineTest1")
.transition()
.duration(duration)
.attr("d", d3.line()
.x(function(d) { return x(d.x); })
.y(function(d) { return y(d.y); }))
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 2.5)
.attr("marker-end", "url(#arrow)");
frame.selectAll(".lineTest2")
.data(data)
.join('circle')
.attr('class','lineTest2')
.attr('fill','red')
.transition()
.duration(duration)
.attr('cx',d => x(d.x))
.attr('r',3)
.attr('cy',d => y(d.y))
frame.selectAll(".lineTest3")
.data(data)
.join('path')
.attr('class','lineTest3')
.attr('stroke','gray')
.transition()
.duration(duration)
.attr('d',(d,i) => {
var ax = x(d.x)
var ay = y(d.y)
var bx = ax
var by = y(ymin)
var path = ['M',ax,ay,'L',bx,by]
return path.join(' ')
})
d3.select('.avline')
.attr('stroke','gray')
.transition()
.duration(duration)
.attr('x1',x(0))
.attr('y1',y(0))
.attr('x2',x(xmax))
.attr('y2',y(0))
xaxis.select("path")
.attr("marker-end", "url(#arrow)");
yaxis.select("path")
.attr("marker-end", "url(#arrow)");
}
update(data1)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>
I have implemented one scatter chart using d3.js. I want to convert this chart to line chart, but i am not able to do so. I have tried to follow ( http://embed.plnkr.co/wJDcZmkEzXaLVhuLZmcQ/ ) but it didn't helped me.
This is the code for scatter chart.
var data = [{"buildName":"otfa_R5-10_a1","build":"Build 1","value":"19628"},{"buildName":"otfa_R5-91_a1","build":"Build 2","value":"19628"},{"buildName":"otfa_R5-9_a1","build":"Build 3","value":"19628"}]
var yValues = [], responseData = [];
data.map(function(key) {
var test = [];
test[0] = key.build;
test[1] = key.value;
responseData.push(test);
yValues = key.value;
})
var margin = {
top: 20,
right: 15,
bottom: 60,
left: 60
},
width = 300 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.domain(responseData.map(function(d) {
return d[0];
}))
.rangePoints([0, width], 0.5)
var y = d3.scale.linear()
.domain([5000,20000])
.range([height, 0]);
var chart = d3.select(divId)
.append('svg:svg')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom)
.attr('class', 'chart')
var colors = d3.scale.linear()
.domain([5, 20])
.range(['#4577bc', '#4577bc'])
var main = chart.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.attr('width', width)
.attr('height', height)
.attr('class', 'main')
// draw the x axis
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom');
main.append('g')
.attr('transform', 'translate(0,' + height + ')')
.attr('class', 'main axis date')
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-45)" );
// draw the y axis
var yAxis = d3.svg.axis()
.scale(y)
.orient('left');
main.append('g')
.attr('transform', 'translate(0,0)')
.attr('class', 'main axis date')
.call(yAxis);
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var g = main.append("svg:g");
g.selectAll("scatter-dots")
.data(responseData)
.enter().append("svg:circle")
.attr("cx", function(d, i) {
return x(d[0]);
})
.attr("cy", function(d) {
return y(d[1]);
})
.attr("r", 6)
.style('stroke', function(d, i) {
return colors(i);
})
.style('fill', function(d, i) {
return colors(i);
})
.on("mouseover", function(d) {
d3.select(this).attr("r", 10).style("fill", "#fff8ee");
div.transition()
.duration(200)
.style("opacity", 2.9);
div .html((d[1]))
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 18) + "px");
})
.on("mouseout", function(d) {
d3.select(this).attr("r", 5.5).style("fill", "#4577bc");
div.transition()
.duration(500)
.style("opacity", 0);
});
How we can add a line connecting these points ?
Please help me !!
To add a line to your existing chart, just add it using path generators.
Line generator:
var line = d3.svg.line()
.x(function (d) { return x(d[0]); })
.y(function (d) { return y(d[1]); });
Append the line to the svg:
g.append('path').classed('line', true)
.style( { fill: 'none', 'stroke': 'steelblue'} )
.attr('d', line(responseData));
Snippet with the above code included and a few CSS styles to make it look better:
var data = [{"buildName":"otfa_R5-10_a1","build":"Build 1","value":"19628"},{"buildName":"otfa_R5-91_a1","build":"Build 2","value":"10628"},{"buildName":"otfa_R5-9_a1","build":"Build 3","value":"17628"}]
var yValues = [], responseData = [];
data.map(function(key) {
var test = [];
test[0] = key.build;
test[1] = key.value;
responseData.push(test);
yValues = key.value;
})
var margin = {
top: 20,
right: 15,
bottom: 60,
left: 60
},
width = 300 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.domain(responseData.map(function(d) {
return d[0];
}))
.rangePoints([0, width], 0.5)
var y = d3.scale.linear()
.domain([5000,20000])
.range([height, 0]);
var chart = d3.select('body')
.append('svg:svg')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom)
.attr('class', 'chart')
var colors = d3.scale.linear()
.domain([5, 20])
.range(['#4577bc', '#4577bc'])
var main = chart.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.attr('width', width)
.attr('height', height)
.attr('class', 'main')
// draw the x axis
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom');
main.append('g')
.attr('transform', 'translate(0,' + height + ')')
.attr('class', 'main axis date')
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-45)" );
// draw the y axis
var yAxis = d3.svg.axis()
.scale(y)
.orient('left');
main.append('g')
.attr('transform', 'translate(0,0)')
.attr('class', 'main axis date')
.call(yAxis);
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var g = main.append("svg:g");
g.selectAll("scatter-dots")
.data(responseData)
.enter().append("svg:circle")
.attr("cx", function(d, i) {
return x(d[0]);
})
.attr("cy", function(d) {
return y(d[1]);
})
.attr("r", 6)
.style('stroke', function(d, i) {
return colors(i);
})
.style('fill', function(d, i) {
return colors(i);
})
.on("mouseover", function(d) {
d3.select(this).attr("r", 10).style("fill", "#fff8ee");
div.transition()
.duration(200)
.style("opacity", 2.9);
div .html((d[1]))
.style("left", (d3.event.pageX+4) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
d3.select(this).attr("r", 5.5).style("fill", "#4577bc");
div.transition()
.duration(500)
.style("opacity", 0);
});
var line = d3.svg.line()
.x(function (d) { return x(d[0]); })
.y(function (d) { return y(d[1]); });
g.append('path').classed('line', true)
.style( { fill: 'none', 'stroke': 'steelblue'} )
.attr('d', line(responseData));
path.domain {
fill: none;
stroke: #000;
}
.axis text {
font-size: 12px;
}
div.tooltip {
position: absolute;
background: #FFF;
padding: 5px;
border: 1px solid #DDD;
pointer-events: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.0/d3.min.js"></script>
In the code below, a simple pie chart is created, but I am not able to move one slice towards the outer side of the chart when selected.
I want the individual (element) slice to be positioned outer the pie and the rest of the pie chart elements(slices) in its usual position, something like this:
Here is my code:
<!DOCTYPE html>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var data = [35, 20, 45];
var width = 300,
height = 300,
radius = 150;
var arc = d3.arc()
.outerRadius(130);
var arcLabel = d3.arc()
.outerRadius(radius - 30)
.innerRadius(radius - 20);
var pie = d3.pie()
.value(function(d) {
return d;
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var emptyPies = svg.selectAll(".arc")
.data(pie(data))
.enter()
.append("g")
.attr("class", "arc")
emptyPies.append("path")
.attr("d", arc)
.style("fill", function(d, i) {
return color[i];
})
emptyPies.append("text")
.attr("transform", function(d) {
return "translate(" + arcLabel.centroid(d) + ")";
})
.text(function(d) {
return d.data;
});
</script>
A simple solution is creating a different arc generator:
var arc2 = d3.arc()
.outerRadius(radius)
.innerRadius(60);
And, when setting the "d" attribute, choosing which arc generator to use. For instance, moving the red slice:
emptyPies.append("path")
.attr("d", function(d,i){
return i != 1 ? arc(d) : arc2(d);
})
Here is your code with that change:
<!DOCTYPE html>
<style>
.arc text {
text-anchor: middle;
}
.arc path {
stroke: white;
}
</style>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var data = [35, 20, 45];
var width = 300,
height = 300,
radius = Math.min(width, height) / 2;
var color = ["brown", "red", "blue"];
var arc = d3.arc()
.outerRadius(radius - 10)
.innerRadius(50);
var arc2 = d3.arc()
.outerRadius(radius)
.innerRadius(60);
var arcLabel = d3.arc()
.outerRadius(radius - 30)
.innerRadius(radius - 20);
var pie = d3.pie()
.value(function(d) {
return d;
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var emptyPies = svg.selectAll(".arc")
.data(pie(data))
.enter()
.append("g")
.attr("class", "arc")
emptyPies.append("path")
.attr("d", function(d,i){
return i != 1 ? arc(d) : arc2(d);})
.style("fill", function(d, i) {
return color[i];
})
emptyPies.append("text")
.attr("transform", function(d) {
return "translate(" + arcLabel.centroid(d) + ")";
})
.text(function(d) {
return d.data;
});
</script>
A simple solution is to use multiple arc() but to do slice we can use arc.centroid() of 2nd arc. The following code will work in v5.
function onDrawPieChart() {
var data = [35, 20, 45];
var color = d3.schemeCategory10;
var width = 600;
var height = 600;
var radius = 100;
var pie = d3.pie().value((d) => d);
var arc = d3.arc().innerRadius(0).outerRadius(130);
var arc2 = d3.arc().innerRadius(0).outerRadius(20);
var slicedIndex = 1;
var pieData = pie(data);
var centroid = arc2.centroid(pieData[slicedIndex]);
var svg = d3
.select("body")
.append("svg")
.attr("viewBox", [-width / 2, -height / 2, width, height].join(" "))
.attr("width", width)
.attr("height", height)
.append("g");
svg
.selectAll("path")
.data(pieData)
.join("path")
.attr("fill", (d, i) => color[i])
.attr("d", (d) => arc(d))
.attr("transform", (d, i) => {
if (i === slicedIndex) {
var [x, y] = centroid;
return "translate(" + x + ", " + y + ")";
}
});
}
Hi I am trying to adapt Matthew Izanuk's Unit Bar Chart with Brush and Zoom to a randomly generated bar chart I have already created.
I get an error with the zoom function, on line 261
line 261 is
x.domain(t.rescaleX(x2).domain());
Here is the code and jsFiddle
<!DOCTYPE html>
<body>
<style>
div {
display: inline-block;
vertical-align: top;
}
#bar_chart {
border: 2px solid lightgray;
border-radius: 15px;
}
#json {
max-height: 600px;
width: 200px;
overflow: scroll;
border: 2px solid gray;
border-radius: 15px;
}
.zoom {
cursor: move;
fill: none;
pointer-events: all;
}
</style>
<div id="bar_chart">
</div>
<div id="json"></div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
//************* generate data ************
var data = [];
var space = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var spaceLength = space.length;
makedata();
function makedata() {
var obj = {};
for (var i = 0; i < spaceLength; i++) {
obj = {};
value = Math.floor(Math.random() * 500);
rand = Math.floor(Math.random() * space.length)
name = space.charAt(rand);
obj["name"] = name;
obj["val"] = value;
data.push(obj);
space = space.slice(0, rand) + space.slice(rand + 1, space.length)
}
}
// To display json in html page
document.getElementById("json").innerHTML = "<pre>" + JSON.stringify(data, null, 4) + "</pre>";
var margin = {
top: 50,
right: 20,
bottom: 90,
left: 50
},
margin2 = {
top: 530,
right: 20,
bottom: 30,
left: 50
},
width = 1000 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom,
height2 = 600 - margin2.top - margin2.bottom;
var x = d3.scaleBand()
.domain(data.map(function(d) {
return d.name
}))
.range([0, width]);
var x2 = d3.scaleBand()
.domain(data.map(function(d) {
return d.name
}))
.range([0, width]);
var y = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return d.val
})])
.range([height, 0]);
var y2 = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return d.val
})])
.range([height2, 0]);
var brush = d3.brushX()
.extent([
[0, 0],
[width, height2]
])
.on("brush", brushed);
var zoom = d3.zoom()
.scaleExtent([1, Infinity])
.translateExtent([
[0, 0],
[width, height]
])
.extent([
[0, 0],
[width, height]
])
.on("zoom", zoomed);
var svg = d3.select("#bar_chart")
// .data(data)
.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("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var focus = svg.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var context = svg.append("g")
.attr("class", "context")
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");
var g0 = focus.append("g")
.attr("class", "focus")
.attr("transform", "translate(0,0)");
var xAxis = d3.axisBottom(x);
var xAxis2 = d3.axisBottom(x2);
focus.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
focus.append("g")
.attr("class", "axis")
.call(d3.axisLeft(y)
.ticks(7));
var tooltip = d3.select("#info")
.append("div")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden");
svg.append("rect")
.attr("class", "zoom")
.attr("width", width)
.attr("height", height)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom);
var focus_group = focus.append("g");
focus_group.attr("clip-path", "url(#clip)");
var rects = focus_group.selectAll('rect')
.data(data);
//********* Bar Chart 1 ****************
var newRects1 = rects.enter();
newRects1.append('rect')
.attr('class', 'bar mainBars')
.attr('x', function(d, i) {
return x(d.name);
})
.attr('y', function(d, i) {
return y(d.val);
})
.attr('height', function(d, i) {
return height - y(d.val)
})
.attr('opacity', 0.85)
.attr('width', 10)
.attr("transform", "translate(" + 4 + ",0)")
.style('fill', 'lightblue')
.style('stroke', 'gray');
var focus_group = context.append("g");
focus_group.attr("clip-path", "url(#clip)");
var brushRects = focus_group.selectAll('rect')
.data(data);
//********* Brush Bar Chart ****************
var brushRects1 = brushRects.enter();
brushRects1.append('rect')
.attr('class', 'bar mainBars')
.attr('x', function(d, i) {
return x2(d.name);
})
.attr('y', function(d, i) {
return y2(d.val);
})
.attr('height', function(d, i) {
return height2 - y2(d.val)
})
.attr('opacity', 0.85)
.attr('width', 10)
.attr("transform", "translate(" + 4 + ",0)")
.style('fill', 'lightblue')
.style('stroke', 'gray');
//append brush xAxis2
context.append("g")
.attr("class", "axis x-axis")
.attr("transform", "translate(0," + height2 + ")")
.call(xAxis2);
context.append("g")
.attr("class", "brush")
.call(brush)
.call(brush.move, x.range());
//create brush function redraw scatterplot with selection
function brushed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return; // ignore brush-by-zoom
var s = d3.event.selection || x2.range();
x.domain(s.map(x2.invert, x2));
focus.selectAll(".mainBars")
.attr("x", function(d) {
return x(d.name);
})
.attr("y", function(d) {
return y(d.val);
})
.attr('height', function(d, i) {
return height - y(d.val)
})
.attr('opacity', 0.85)
.attr('width', 10);
focus.select(".x-axis").call(xAxis);
svg.select(".zoom").call(zoom.transform, d3.zoomIdentity
.scale(width / (s[1] - s[0]))
.translate(-s[0], 0));
}
function zoomed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") return; // ignore zoom-by-brush
var t = d3.event.transform;
x.domain(t.rescaleX(x2).domain());
focus.selectAll(".mainBars")
.attr("x", function(d) {
return x(d.name);
})
.attr("y", function(d) {
return y(d.val);
})
.attr('height', function(d, i) {
return height - y(d.val)
})
.attr('opacity', 0.85)
.attr('width', 10);
focus.select(".x-axis").call(xAxis);
context.select(".brush").call(brush.move, x.range().map(t.invertX, t));
}
</script>
Any help would be greatly appreciated,
thanks
As mentioned in the comments, invert only exists for continuous ranges. Your scaleBand is a ordinal scale composed of discreet values. One way I've worked around this is to simply figure which values in my domain fall in the selection:
function brushed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return;
// ignore brush-by-zoom
// get bounds of selection
var s = d3.event.selection,
nD = [];
// for each "tick" in our domain is it in selection
x2.domain().forEach((d)=>{
var pos = x2(d) + x2.bandwidth()/2;
if (pos > s[0] && pos < s[1]){
nD.push(d);
}
});
// set new domain
x.domain(nD);
// redraw
focus.selectAll(".mainBars")
// hide bars not in domain
.style("opacity", function(d){
return x.domain().indexOf(d.name) === -1 ? 0 : 100;
})
.attr("x", function(d) {
return x(d.name)+ x.bandwidth()/2 - 5;
})
.attr("y", function(d) {
return y(d.val);
})
.attr('height', function(d, i) {
return height - y(d.val)
})
.attr('opacity', 0.85)
.attr('width', 10);
focus.select(".x.axis").call(xAxis);
svg.select(".zoom").call(zoom.transform, d3.zoomIdentity
.scale(width / (s[1] - s[0]))
.translate(-s[0], 0));
}
Running code:
<!DOCTYPE html>
<head>
<style>
div {
display: inline-block;
vertical-align: top;
}
#bar_chart {
border: 2px solid lightgray;
border-radius: 15px;
}
#json {
max-height: 600px;
width: 200px;
overflow: scroll;
border: 2px solid gray;
border-radius: 15px;
}
.zoom {
cursor: move;
fill: none;
pointer-events: all;
}
</style>
</head>
<body>
<div id="bar_chart">
</div>
<div id="json"></div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
//************* generate data ************
var data = [];
var space = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var spaceLength = space.length;
makedata();
function makedata() {
var obj = {};
for (var i = 0; i < spaceLength; i++) {
obj = {};
value = Math.floor(Math.random() * 500);
rand = Math.floor(Math.random() * space.length)
name = space.charAt(rand);
obj["name"] = name;
obj["val"] = value;
data.push(obj);
space = space.slice(0, rand) + space.slice(rand + 1, space.length)
}
}
// To display json in html page
document.getElementById("json").innerHTML = "<pre>" + JSON.stringify(data, null, 4) + "</pre>";
var margin = {
top: 50,
right: 20,
bottom: 90,
left: 50
},
margin2 = {
top: 530,
right: 20,
bottom: 30,
left: 50
},
width = 1000 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom,
height2 = 600 - margin2.top - margin2.bottom;
var x = d3.scaleBand()
.domain(data.map(function(d) {
return d.name
}))
.range([0, width]);
var x2 = d3.scaleBand()
.domain(data.map(function(d) {
return d.name
}))
.range([0, width]);
var y = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return d.val
})])
.range([height, 0]);
var y2 = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return d.val
})])
.range([height2, 0]);
var brush = d3.brushX()
.extent([
[0, 0],
[width, height2]
])
.on("brush", brushed);
var zoom = d3.zoom()
.scaleExtent([1, Infinity])
.translateExtent([
[0, 0],
[width, height]
])
.extent([
[0, 0],
[width, height]
])
.on("zoom", zoomed);
var svg = d3.select("#bar_chart")
// .data(data)
.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("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var focus = svg.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var context = svg.append("g")
.attr("class", "context")
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");
var xAxis = d3.axisBottom(x);
var xAxis2 = d3.axisBottom(x2);
focus.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
focus.append("g")
.attr("class", "axis")
.call(d3.axisLeft(y)
.ticks(7));
var tooltip = d3.select("#info")
.append("div")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden");
svg.append("rect")
.attr("class", "zoom")
.attr("width", width)
.attr("height", height)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom);
var focus_group = focus.append("g");
focus_group.attr("clip-path", "url(#clip)");
var rects = focus_group.selectAll('rect')
.data(data);
//********* Bar Chart 1 ****************
var newRects1 = rects.enter();
newRects1.append('rect')
.attr('class', 'bar mainBars')
.attr('x', function(d, i) {
return x(d.name) + x.bandwidth()/2;
})
.attr('y', function(d, i) {
return y(d.val);
})
.attr('height', function(d, i) {
return height - y(d.val)
})
.attr('opacity', 0.85)
.attr('width', 10)
.style('fill', 'lightblue')
.style('stroke', 'gray');
var focus_group = context.append("g");
focus_group.attr("clip-path", "url(#clip)");
var brushRects = focus_group.selectAll('rect')
.data(data);
//********* Brush Bar Chart ****************
var brushRects1 = brushRects.enter();
brushRects1.append('rect')
.attr('class', 'bar mainBars')
.attr('x', function(d, i) {
return x2(d.name);
})
.attr('y', function(d, i) {
return y2(d.val);
})
.attr('height', function(d, i) {
return height2 - y2(d.val)
})
.attr('opacity', 0.85)
.attr('width', 10)
.attr("transform", "translate(" + 4 + ",0)")
.style('fill', 'lightblue')
.style('stroke', 'gray');
//append brush xAxis2
context.append("g")
.attr("class", "axis x-axis")
.attr("transform", "translate(0," + height2 + ")")
.call(xAxis2);
context.append("g")
.attr("class", "brush")
.call(brush)
.call(brush.move, x.range());
//create brush function redraw scatterplot with selection
function brushed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return; // ignore brush-by-zoom
// get bounds of selection
var s = d3.event.selection,
nD = [];
x2.domain().forEach((d)=>{
var pos = x2(d) + x2.bandwidth()/2;
if (pos > s[0] && pos < s[1]){
nD.push(d);
}
});
x.domain(nD);
focus.selectAll(".mainBars")
.style("opacity", function(d){
return x.domain().indexOf(d.name) === -1 ? 0 : 100;
})
.attr("x", function(d) {
return x(d.name)+ x.bandwidth()/2 - 5;
})
.attr("y", function(d) {
return y(d.val);
})
.attr('height', function(d, i) {
return height - y(d.val)
})
.attr('opacity', 0.85)
.attr('width', 10);
focus.select(".x.axis").call(xAxis);
svg.select(".zoom").call(zoom.transform, d3.zoomIdentity
.scale(width / (s[1] - s[0]))
.translate(-s[0], 0));
}
function zoomed() {
/*
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") return; // ignore zoom-by-brush
var t = d3.event.transform;
x.domain(t.rescaleX(x2).domain());
focus.selectAll(".mainBars")
.attr("x", function(d) {
return x(d.name) + x.bandwidth()/2;
})
.attr("y", function(d) {
return y(d.val);
})
.attr('height', function(d, i) {
return height - y(d.val)
})
.attr('opacity', 0.85)
.attr('width', 10);
focus.select(".x-axis").call(xAxis);
context.select(".brush").call(brush.move, x.range().map(t.invertX, t));
*/
}
</script>
I have the following d3 code:
var json = [
{
date: "05/17",
numTags: 23
}
];
d3.select('summary-graph').selectAll('*').remove();
var svg = d3.select("summary-graph"),
margin = {
top: 20,
right: 30,
bottom: 30,
left: 40
},
width = svg.attr("width") - margin.left - margin.right,
height = svg.attr("height") - margin.top - margin.bottom;
var parseTime = d3.timeParse("%m/%y");
var svg = d3.select("summary-graph").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 + ")");
// Get the data
var data = json;
// format the data
data.forEach(function (d) {
console.log(d);
d.date = parseTime(d.date);
d.numTags = +d.numTags;
});
// set the ranges
var xScale = d3.scaleTime()
.range([0, width])
.domain(d3.extent(data, function (d) {
return d.date;
}))
.nice();
var yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max(data, function (d) {
return Math.max(d.numTags);
})])
.nice();
// define the 1st line
var tagLine = d3.line()
.x(function (d) {
return xScale(d.date);
})
.y(function (d) {
return yScale(d.numTags);
});
// Axes
var xAxis = d3.axisBottom()
.scale(xScale)
.ticks(json.length)
.tickSizeOuter(0)
.tickFormat(d3.timeFormat('%B %Y'));
var yAxis = d3.axisLeft().scale(yScale);
svg.append("path")
.data([data])
.attr("class", "line")
.style("stroke", "blue")
.attr("d", tagLine);
var points = svg.selectAll(".point")
.data(data)
.enter().append("svg:circle")
.attr("stroke", "green")
.attr("fill", function(d, i) { return "blue" })
.attr("cx", function(d, i) { return xScale(d.date) })
.attr("cy", function(d, i) { return yScale(d.numTags) })
.attr("r", function(d, i) { return 10 });
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.style("font-size","14px");;
// Add the Y Axis
svg.append("g")
.call(yAxis);
Which produces the following visualization:
I'm trying to figure out how to center the tick mark and show the x-axis label when there is only 1 data point like this. At 2 data points, I don't love how it still sets the tick marks at the extreme ends of the x-axis. For 3 data points and above, it looks nice (thanks to .nice() it seems).
Any help?
Based on Gerado's response, I was able to get close. The last sticking point is that the left side of the X-axis now has the month (March) despite no data for that day.
Fixed:
changed .ticks(json.length) to .ticks(d3.timeMonth.every(1))
Since you are using only a single data point, your date scale has a domain in which the lower and upper values are the same:
[
Mon May 01 2017 00: 00: 00 GMT + 1000,
Mon May 01 2017 00: 00: 00 GMT + 1000
]
For putting that circle in the middle of the x axis you have to set different values for the scale's domain.
There are several ways for doing that. My proposed solution here involves verifying if the domain's values are the same...
if (xScale.domain()[0].getTime() == xScale.domain()[1].getTime()) {
... and, if they are, changing them. In this case, I'm subtracting one day from the lower limit and adding one day to the upper limit:
if (xScale.domain()[0].getTime() == xScale.domain()[1].getTime()) {
var dateLess = d3.timeDay.offset(xScale.domain()[0], -1);
var dateMore = d3.timeDay.offset(xScale.domain()[0], 1);
xScale.domain([dateLess, dateMore])
}
Check the result:
var json = [{
date: "05/17",
numTags: 23
}];
var margin = {
top: 20,
right: 30,
bottom: 30,
left: 40
},
width = 400,
height = 200;
var parseTime = d3.timeParse("%m/%y");
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 + ")");
// Get the data
var data = json;
// format the data
data.forEach(function(d) {
d.date = parseTime(d.date);
d.numTags = +d.numTags;
});
// set the ranges
var xScale = d3.scaleTime()
.range([0, width])
.domain(d3.extent(data, function(d) {
return d.date;
}))
.nice();
if (xScale.domain()[0].getTime() == xScale.domain()[1].getTime()) {
var dateLess = d3.timeDay.offset(xScale.domain()[0], -1);
var dateMore = d3.timeDay.offset(xScale.domain()[0], 1);
xScale.domain([dateLess, dateMore])
}
var yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max(data, function(d) {
return Math.max(d.numTags);
})])
.nice();
// define the 1st line
var tagLine = d3.line()
.x(function(d) {
return xScale(d.date);
})
.y(function(d) {
return yScale(d.numTags);
});
// Axes
var xAxis = d3.axisBottom()
.scale(xScale)
.ticks(json.length)
.tickSizeOuter(0)
.tickFormat(d3.timeFormat('%B %Y'));
var yAxis = d3.axisLeft().scale(yScale);
svg.append("path")
.data([data])
.attr("class", "line")
.style("stroke", "blue")
.attr("d", tagLine);
var points = svg.selectAll(".point")
.data(data)
.enter().append("svg:circle")
.attr("stroke", "green")
.attr("fill", function(d, i) {
return "blue"
})
.attr("cx", function(d, i) {
return xScale(d.date)
})
.attr("cy", function(d, i) {
return yScale(d.numTags)
})
.attr("r", function(d, i) {
return 10
});
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.style("font-size", "14px");;
// Add the Y Axis
svg.append("g")
.call(yAxis);
<script src="https://d3js.org/d3.v4.js"></script>
EDIT: As you asked in your edit, when you have two data values my solution will create additional ticks on the limits, which is the expected behaviour:
var json = [{
date: "05/17",
numTags: 23
}, {
date: "05/17",
numTags: 17
}];
var margin = {
top: 20,
right: 30,
bottom: 30,
left: 40
},
width = 400,
height = 200;
var parseTime = d3.timeParse("%m/%y");
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 + ")");
// Get the data
var data = json;
// format the data
data.forEach(function(d) {
d.date = parseTime(d.date);
d.numTags = +d.numTags;
});
// set the ranges
var xScale = d3.scaleTime()
.range([0, width])
.domain(d3.extent(data, function(d) {
return d.date;
}))
.nice();
if (xScale.domain()[0].getTime() == xScale.domain()[1].getTime()) {
var dateLess = d3.timeDay.offset(xScale.domain()[0], -1);
var dateMore = d3.timeDay.offset(xScale.domain()[0], 1);
xScale.domain([dateLess, dateMore])
}
var yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max(data, function(d) {
return Math.max(d.numTags);
})])
.nice();
// define the 1st line
var tagLine = d3.line()
.x(function(d) {
return xScale(d.date);
})
.y(function(d) {
return yScale(d.numTags);
});
// Axes
var xAxis = d3.axisBottom()
.scale(xScale)
.ticks(json.length)
.tickSizeOuter(0)
.tickFormat(d3.timeFormat('%B %Y'));
var yAxis = d3.axisLeft().scale(yScale);
svg.append("path")
.data([data])
.attr("class", "line")
.style("stroke", "blue")
.attr("d", tagLine);
var points = svg.selectAll(".point")
.data(data)
.enter().append("svg:circle")
.attr("stroke", "green")
.attr("fill", function(d, i) {
return "blue"
})
.attr("cx", function(d, i) {
return xScale(d.date)
})
.attr("cy", function(d, i) {
return yScale(d.numTags)
})
.attr("r", function(d, i) {
return 10
});
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.style("font-size", "14px");;
// Add the Y Axis
svg.append("g")
.call(yAxis);
<script src="https://d3js.org/d3.v4.js"></script>
There are several ways for removing those ticks. One of them is using tickValues:
.tickValues(data.map(function(d){ return d.date}))
Here is the demo:
var json = [{
date: "05/17",
numTags: 23
}, {
date: "05/17",
numTags: 17
}];
var margin = {
top: 20,
right: 30,
bottom: 30,
left: 40
},
width = 400,
height = 200;
var parseTime = d3.timeParse("%m/%y");
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 + ")");
// Get the data
var data = json;
// format the data
data.forEach(function(d) {
d.date = parseTime(d.date);
d.numTags = +d.numTags;
});
// set the ranges
var xScale = d3.scaleTime()
.range([0, width])
.domain(d3.extent(data, function(d) {
return d.date;
}))
.nice();
if (xScale.domain()[0].getTime() == xScale.domain()[1].getTime()) {
var dateLess = d3.timeDay.offset(xScale.domain()[0], -1);
var dateMore = d3.timeDay.offset(xScale.domain()[0], 1);
xScale.domain([dateLess, dateMore])
}
var yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max(data, function(d) {
return Math.max(d.numTags);
})])
.nice();
// define the 1st line
var tagLine = d3.line()
.x(function(d) {
return xScale(d.date);
})
.y(function(d) {
return yScale(d.numTags);
});
// Axes
var xAxis = d3.axisBottom()
.scale(xScale)
.ticks(json.length)
.tickSizeOuter(0)
.tickValues(data.map(function(d){ return d.date}))
.tickFormat(d3.timeFormat('%B %Y'));
var yAxis = d3.axisLeft().scale(yScale);
svg.append("path")
.data([data])
.attr("class", "line")
.style("stroke", "blue")
.attr("d", tagLine);
var points = svg.selectAll(".point")
.data(data)
.enter().append("svg:circle")
.attr("stroke", "green")
.attr("fill", function(d, i) {
return "blue"
})
.attr("cx", function(d, i) {
return xScale(d.date)
})
.attr("cy", function(d, i) {
return yScale(d.numTags)
})
.attr("r", function(d, i) {
return 10
});
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.style("font-size", "14px");;
// Add the Y Axis
svg.append("g")
.call(yAxis);
<script src="https://d3js.org/d3.v4.js"></script>