d3.js + React, node.getBoundingClientRect is not a function - javascript

I'm making a line chart in React using D3, and I'm trying to show a tooltip when hovering inside the chart with data from the current data point. The chart renders correctly, but as soon as I'm hovering inside it, I get the error node.getBoundingClientRect is not a function.
I'm following this example, which doesn't use React, so could it have something to do with how react handle things?
This is what my component looks like:
class LineChart extends Component {
wrapper = React.createRef();
componentDidMount() {
const {data} = this.props
let svg = d3.select("body svg.mySvg"),
margin = {top: 20, right: 20, bottom: 30, left: 40},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
const parseTime = d3.timeParse("%Y")
const bisectDate = d3.bisector(function(d) { return d.year; }).left;
const x = d3.scaleTime().range([0, width]);
const y = d3.scaleLinear().range([height, 0]);
const line = d3.line()
.x(function(d) { return x(d.year); })
.y(function(d) { return y(d.value); });
let g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
data.forEach(function(d) {
d.year = parseTime(d.year);
d.value = +d.value;
});
x.domain(d3.extent(data, function(d) { return d.year; }));
y.domain([0, d3.max(data, function(d) { return d.value; })]);
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.style("color", "#fff")
.call(d3.axisBottom(x));
g.append("g")
.attr("class", "axis axis--y")
.style("color", "#fff")
.call(d3.axisLeft(y).ticks(7).tickFormat(function(d) { return d; }))
g.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
let 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);
focus.append("line")
.attr("class", "y-hover-line hover-line")
.attr("x1", width)
.attr("x2", width);
focus.append("circle")
.attr("r", 7.5);
focus.append("text")
.attr("x", 15)
.attr("dy", ".31em");
svg.append("rect")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", function() { focus.style("display", null); })
.on("mouseout", function() { focus.style("display", "none"); })
.on("mousemove", () => {
var x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.year > d1.year - x0 ? d1 : d0;
focus.attr("transform", "translate(" + x(d.year) + "," + y(d.value) + ")");
focus.select("text").text(function() { return d.value; });
focus.select(".x-hover-line").attr("y2", height - y(d.value));
focus.select(".y-hover-line").attr("x2", width + width);
});
}
render() {
const { width, height } = this.props
return (
<svg innerRef={ this.wrapper } height={ height } width={ width } className="mySvg"></svg>
)
}
}
I render the LineChart component inside a parent component like this:
<LineChart
width={ 1000 }
height={ 350 }
margin={ 50 }
data={[
{ year: 2011, value: 3 },
{ year: 2012, value: 20 },
{ year: 2013, value: 2 },
{ year: 2014, value: 12 },
{ year: 2015, value: 8 },
{ year: 2016, value: 14 },
{ year: 2017, value: 8 }
]}
/>
The error:

The problem is in the .on("mousemove", () => {}) line. You are using a fat arrow function, which does not have its own this, effectively using the upper scope this instead. When using "classic" function definition (as you do for "mouseover"), this keeps the correct value for this (the node which was the target of "mousemove").
svg.append("rect")
...
.on("mousemove", function () {
// this is the node which was target of "mouseover" event
})
I set up a demo.

Related

Show yearly X labels for June instead of January on d3.js chart

I have a line chart in d3.js where I have labels on the X-axis every year (showing 20 years of data). The labels are created with:
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickFormat(d3.timeFormat("%b/%d/%Y")).ticks(d3.timeYear))
.selectAll("text")
.style("text-anchor", "end")
.attr("dy", ".25em")
.attr("transform", "rotate(-45)");
The outcome looks like this:
Now, the thing is, I need the labels to not be placed on January 1st of each year - I need them on June 30th. How can I accomplish that?
See the Fiddle here to try for yourself.
One way of doing this is to specify explicitly each desired axis tick's value. The function axis.tickValues is designed for this.
The following function generates an array of dates, starting from the min date (june 28th, 2000 in your case), and adding a year until the max date (june 28th, 2020) is reached. It is necessary to go through this generation step because the dataset does not contain data for all years.
function generateTickvalues(min, max) {
let res = []
, currentExtent = new Date(min.valueOf())
while(currentExtent <= max) {
res.push(new Date(currentExtent.valueOf()))
currentExtent.setFullYear(currentExtent.getFullYear() + 1);
}
return res
}
Remark: new Date(date.valueOf()) is necessary in this function so that the date values from the original dataset are not overwritten.
The min and max dates from the dataset can conveniently be found using d3.extent. This array can also be used when calling x.domain.
let dateExtent = d3.extent(data, function(d) { return d.date})
let tickValues = generateTickvalues(dateExtent[0], dateExtent[1])
x.domain(dateExtent);
Then, when generating the axis, call the function axis.tickValues, passing the array of years starting from June just generated:
d3.axisBottom(x)
.tickFormat(d3.timeFormat("%b/%d/%Y"))
.ticks(d3.timeYear)
.tickValues(tickValues)
Demo in the snippet below:
const data = [
{ value: 46, date: '2000-06-28', formatted_date: '06/28/2000' },
{ value: 48, date: '2003-06-28', formatted_date: '06/28/2003' },
{ value: 26, date: '2004-06-28', formatted_date: '06/28/2004' },
{ value: 36, date: '2006-06-28', formatted_date: '06/28/2006' },
{ value: 40, date: '2010-06-28', formatted_date: '06/28/2010' },
{ value: 48, date: '2012-06-28', formatted_date: '06/28/2012' },
{ value: 34, date: '2018-06-28', formatted_date: '06/28/2018' },
{ value: 33, date: '2020-06-28', formatted_date: '06/28/2020' }
];
create_area_chart(data, 'history-chart-main');
function generateTickvalues(min, max) {
let res = []
, currentExtent = new Date(min.valueOf())
while(currentExtent <= max) {
res.push(new Date(currentExtent.valueOf()))
currentExtent.setFullYear(currentExtent.getFullYear() + 1);
}
return res
}
function create_area_chart(data, target){
document.getElementById(target).innerHTML = '';
var parentw = document.getElementById(target).offsetWidth;
var parenth = 0.6*parentw;
var svg = d3.select('#'+target).append("svg").attr("width", parentw).attr("height", parenth),
margin = {top: 20, right: 20, bottom: 40, left: 50},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var parseTime = d3.timeParse("%Y-%m-%d");
bisectDate = d3.bisector(function(d) { return d.date; }).left;
var x = d3.scaleTime()
.rangeRound([0, width]);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var area = d3.area()
.x(function (d) { return x(d.date); })
.y1(function (d) { return y(d.value); });
data.forEach(function (d) {
//only parse time if not already parsed (i.e. when using time period filters)
if(parseTime(d.date))
d.date = parseTime(d.date);
d.value = +d.value;
});
let dateExtent = d3.extent(data, function(d) { return d.date})
let tickValues = generateTickvalues(dateExtent[0], dateExtent[1])
x.domain(dateExtent);
y.domain([0, 1.05 * d3.max(data, function (d) { return d.value; })]);
area.y0(y(0));
g.append("rect")
.attr("transform", "translate(" + -margin.left + "," + -margin.top + ")")
.attr("width", svg.attr("width"))
.attr('class', 'overlay')
.attr("height", svg.attr("height"))
.on("mouseover", function () {
d3.selectAll(".eps-tooltip").remove();
d3.selectAll(".eps-remove-trigger").remove();
focus.style("display", "none");
});
g.append("path")
.datum(data)
.attr("fill", "#f6f6f6")
.attr("d", area);
//create line
var valueline = d3.line()
.x(function (d) { return x(d.date); })
.y(function (d) { return y(d.value); });
g.append("path")
.data([data])
.attr('fill', 'none')
.attr('stroke', '#068d46')
.attr("class", "line")
.attr("d", valueline);
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickFormat(d3.timeFormat("%b/%d/%Y")).ticks(d3.timeYear).tickValues(tickValues))
.selectAll("text")
.style("text-anchor", "end")
.attr("dy", ".25em")
.attr("transform", "rotate(-45)");
g.append("g")
.call(d3.axisLeft(y)
.tickFormat(function (d) { return "$" + d }))
.append("text")
.attr("fill", "#068d46")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end");
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);
focus.append("line")
.attr("class", "y-hover-line hover-line")
.attr("x1", width)
.attr("x2", width);
focus.append("circle")
.attr("fill", "#068d46")
.attr("r", 4);
focus.append("text")
.attr("class", "text-date focus-text")
.attr("x", 0)
.attr("y", -20)
.attr("dy", ".31em")
.style("text-anchor", "middle");
focus.append("text")
.attr("class", "text-val focus-text")
.attr("x", 0)
.attr("y", -30)
.attr("dy", ".31em")
.style("text-anchor", "middle");
g.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", function () { focus.style("display", null); })
.on("mousemove", function () {
var x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.year > d1.year - x0 ? d1 : d0;
focus.attr("transform", "translate(" + x(d.date) + "," + y(d.value) + ")");
focus.select(".text-date").text(function () { return d.formatted_date; });
focus.select(".text-val").text(function () { return '$' + d.value; });
focus.select(".x-hover-line").attr("y2", height - y(d.value));
focus.select(".y-hover-line").attr("x2", width + width);
});
}
.chart {
text-align: center;
padding: 10px 10px 25px 10px;
background: #f6f6f6;
}
.chart svg {
overflow: visible;
}
.chart .overlay {
fill: none;
pointer-events: all;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.8.0/d3.min.js"></script>
<div class="chart" id="history-chart-main"></div>
You can specify an interval with axis.ticks(), D3 provides a number of built in intervals which we can use and then filter for the appropriate day/month/time.
If we wanted June 1 every year we could use:
var axis = d3.axisBottom(x)
.tickFormat(d3.timeFormat("%b/%d/%Y"))
.ticks(d3.timeMonth.filter(function(d) { return d.getMonth() == 5; })))
If we want June 30 we can specify with a bit more specificity:
var axis = d3.axisBottom(x)
.tickFormat(d3.timeFormat("%b/%d/%Y"))
.ticks(d3.timeDay.filter(function(d) { return d.getMonth() == 5 && d.getDate() == 30 }))
The d3-time docs have some more description of d3 intervals and the d3-scale documentation time scale details have some example implementations of this method here.
Here's an updated fiddle

Use Date from JSON data in scalelog in d3v4

I am trying to use the date I get from my JSON data on the X-Axis using the ScaleLog. Like this one https://bl.ocks.org/andrewdblevins/f4ea65f70dbcb34abc1e4c3d75cfa142 only for time. I have a setting on my chart that is supposed to show a year's worth of data on the X-Axis.
My Code:
var svg = d3.select("#main-chart svg"),
margin = {top: 20, right: 20, bottom: 100, left: 75},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var tooltip = d3.select("body").append("div").attr("class", "toolTip");
var x = d3.scaleBand().rangeRound([0, width]).padding(0.1),
y = d3.scaleLinear().rangeRound([height, 0]);
var colours = d3.scaleOrdinal()
.range(["#d9534f", "#d9534f"]);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.json(url, function(error, data) {
if (error) throw error;
x.domain(data.map(function(d) {
let get_date = "";
if($('#day').hasClass("active")) {get_date = moment(d.date, 'YYYY-MM-DD').format('MMM DD, YYYY')}
else if($('#month').hasClass("active")) {get_date = moment(d.date, 'YYYY-MM-DD').format('MMM, YYYY')}
return get_date;
}));
y.domain([d3.min(data, function(d) {return d.total_valid_subscribers-1000000; }), d3.max(data, function(d) { return d.total_valid_subscribers; })]);
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).ticks(5))
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-65)" );
g.append("g").call(d3.axisLeft(y).ticks(10).tickFormat(function(d) {return parseInt(d); }).tickSizeInner([-width]));
g.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("x", function(d) {let get_date = "";
if($('#day').hasClass("active")) {get_date = moment(d.date, 'YYYY-MM-DD').format('MMM DD, YYYY')}
else if($('#month').hasClass("active")) {get_date = moment(d.date, 'YYYY-MM-DD').format('MMM, YYYY')}
return x(get_date);
})
.attr("y", function(d) {return y(d.total_valid_users); })
.attr("width", x.bandwidth())
.attr("height", function(d) { return height - y(d.total_valid_subscribers); })
.attr("fill", function(d) { return colours(d.date); })
.on("mousemove", function(d){
tooltip
.style("left", d3.event.pageX - 50 + "px")
.style("top", d3.event.pageY - 70 + "px")
.style("pointer-events", "none")
.style("display", "inline-block")
.html((d.date) + "<br>" + (d.total_valid_users));
})
.on("mouseout", function(d){ tooltip.style("display", "none");});
});
When I do "Last 7 Days". I get:
But when I do "This Year", all my data gets smushed together:
I was hoping to get something like this on both my scales, for X-Axis:
and for Y-Axis:
Is there anyways to do this?

Use legend in stacked bar Graph along with tooltip d3 js

I have done stacked bar graph with the help of this link https://bl.ocks.org/mbostock/1134768 but i want to customize my existing code of stacked graph in which i have to add d3.legend along with tooltip.
Right now i have completed till tooltip but there is label problem because i have to give value like this 'A:100' from my array data as shown below
var data = [
{month: "4/1854",total:"100" ,A: "45", B:"45", C:"10"},
{month: "5/1854",total:"200" ,A:"80", B:"70", C:"50"},
{month: "6/1854",total:"300" ,A:"0", B:"100", C:"200"},
{month: "7/1854",total:"400" ,A: "200", B:"100", C:"100"},
{month: "8/1854",total:"500" ,A:"100", B:"200", C:"200"},
{month: "9/1854",total:"600" ,A:"100", B:"200", C:"300"},
{month: "10/1854",total:"700" ,A: "400", B:"100", C:"200"},
{month: "11/1854",total:"800" ,A:"500", B:"200", C:"100"},
{month: "12/1854",total:"900" ,A:"100", B:"400", C:"500"},
{month: "13/1854",total:"1000" ,A:"500", B:"0", C:"500"}
];
And about d3.legend i don't find any suitable example that give me proper picture that how to use it in my existing code but i have to show like this
Here is my full working code
var xData = ["A", "B", "C"];
var parseDate = d3.time.format("%m/%Y").parse;
var margin = {top: 20, right: 50, bottom: 30, left: 50},
width = 500 - margin.left - margin.right,
height = 350 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .35);
var y = d3.scale.linear()
.rangeRound([height, 0]);
var color = d3.scale.category20();
//console.info(color(0));
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(d3.time.format("%b"));
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var svg = d3.select("#pie").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 + ")");
data.forEach(function(d) {
d.month = parseDate(d.month);
xData.forEach(function(c) {
d[c] = +d[c];
});
})
var dataIntermediate = xData.map(function (c) {
return data.map(function (d) {
return {x: d.month, y: d[c]};
});
});
var dataStackLayout = d3.layout.stack()(dataIntermediate);
x.domain(dataStackLayout[0].map(function (d) {
return d.x;
}));
y.domain([0,
d3.max(data, function(d) { return d.total; })
])
.nice();
var layer = svg.selectAll(".stack")
.data(dataStackLayout)
.enter().append("g")
.attr("class", "stack")
.style("fill", function (d, i) {
console.info(i, color(i));
return color(i);
});
layer.selectAll("rect")
.data(function (d) {
return d;
})
.enter().append("rect")
.attr("x", function (d) {
console.info("dx", d.x,x(d.x), x.rangeBand());
return x(d.x);
})
.attr("y", function (d) {
return y(d.y + d.y0);
})
.attr("height", function (d) {
// console.info(d.y0, d.y, y(d.y0), y(d.y))
return y(d.y0) - y(d.y + d.y0);
})
.attr("width", x.rangeBand() -1)
.on("mouseover", function(d){
var delta = d.y1 - d.y0;
var xPos = parseFloat(d3.select(this).attr("x"));
var yPos = parseFloat(d3.select(this).attr("y"));
var height = parseFloat(d3.select(this).attr("height"))
d3.select(this).attr("stroke","blue").attr("stroke-width",0.8);
svg.append("text")
.attr("x",xPos)
.attr("y",yPos +height/2)
.attr("class","tooltip")
.text(d +": "+ delta);
})
.on("mouseout",function(){
svg.select(".tooltip").remove();
d3.select(this).attr("stroke","pink").attr("stroke-width",0.2);
});
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "axis axis--y")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end");
Please provide me some useful suggestion about my graph any help should be appreciated.
Let's add a legend and a tooltip for blocks what you mentioned (I cannot use your code because of it is incomplete). Look at the demo in the hidden snippet below (I rewrite data loading to d3.tsv.parse to simplify the example).
First of all, let's increase bottom margin to the legend fits to svg:
var margin = {top: 20, right: 50, bottom: 130, left: 20};
Adds the legend this way (pay attention to the comments):
var legend = svg.append("g") // add g element it will be the container for our legend
.attr("transform", "translate(0," + (height + 25) + ")") // move it under the bar cart
.selectAll(".legend")
.data(causes.reverse()) // bind data
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; }); // put legend items one above the other
legend.append("rect") // append legend items rect
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d, i) { return z(causes.length - 1 - i);}); // set appropriate color with z scale
legend.append("text") // append legend items text
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; }); // set appropriate text
The fastest way to add tooltip use d3-tip library. Add it to your project with npm or script tag.
Define tooltip this way:
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(data, cause) {
return "<div class=\"tooltip\">" + cause + ":" + data.y + "</div>";
})
svg.call(tip);
Attach appropriate event handler functions for mouseover and mouseout event for rect elements:
layer.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.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() - 1)
.on('mouseover', function(d,i,j) { tip.show(d, causes[causes.length - 1 - j]); })
.on('mouseout', tip.hide);
Note: it works for d3v3 if you use d3v4 you will be forced to rewrite mouseover handler function a bit.
var causes = ["wounds", "other", "disease"];
var parseDate = d3.time.format("%m/%Y").parse;
var margin = {top: 20, right: 50, bottom: 130, left: 20},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width]);
var y = d3.scale.linear()
.rangeRound([height, 0]);
var z = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(d3.time.format("%b"));
var yAxis = d3.svg.axis()
.scale(y)
.orient("right");
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 dataAsString = `date total disease wounds other
4/1854 8571 1 0 5
5/1854 23333 12 0 9
6/1854 28333 11 0 6
7/1854 28772 359 0 23
8/1854 30246 828 1 30
9/1854 30290 788 81 70
10/1854 30643 503 132 128
11/1854 29736 844 287 106
12/1854 32779 1725 114 131
1/1855 32393 2761 83 324
2/1855 30919 2120 42 361
3/1855 30107 1205 32 172
4/1855 32252 477 48 57
5/1855 35473 508 49 37
6/1855 38863 802 209 31
7/1855 42647 382 134 33
8/1855 44614 483 164 25
9/1855 47751 189 276 20
10/1855 46852 128 53 18
11/1855 37853 178 33 32
12/1855 43217 91 18 28
1/1856 44212 42 2 48
2/1856 43485 24 0 19
3/1856 46140 15 0 35`;
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(data, cause) {
return "<div class=\"tooltip\">" + cause + ":" + data.y + "</div>";
})
svg.call(tip);
var crimea = d3.tsv.parse(dataAsString, function(item) {
return {
date: parseDate(item.date),
total: +item.total,
disease: +item.disease,
wounds: +item.wounds,
other: +item.other
};
});
var layers = d3.layout.stack()(causes.map(function(c) {
return crimea.map(function(d) {
return {x: d.date, y: d[c]};
});
}));
x.domain(layers[0].map(function(d) { return d.x; }));
y.domain([0, d3.max(layers[layers.length - 1], function(d) { return d.y0 + d.y; })]).nice();
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) { return z(i); });
layer.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.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() - 1)
.on('mouseover', function(d,i,j) { tip.show(d, causes[causes.length - 1 - j]); })
.on('mouseout', tip.hide);
svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "axis axis--y")
.attr("transform", "translate(" + width + ",0)")
.call(yAxis);
var legend = svg.append("g")
.attr("transform", "translate(0," + (height + 25) + ")")
.selectAll(".legend")
.data(causes.reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d, i) { return z(causes.length - 1 - i);});
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
function type(d) {
d.date = parseDate(d.date);
causes.forEach(function(c) { d[c] = +d[c]; });
return d;
}
.axis text {
font: 10px sans-serif;
}
.legend {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 60%;
}
.axis line,
.axis path {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.axis--x path {
display: none;
}
.tooltip {
background-color: lightblue;
border-radius: 5px;
padding: 6px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.7.1/d3-tip.js"></script>

X axis not displaying on Multi Series Line Chart using d3.js

I am implementing a multi series line chart in d3.js following the example on this block: http://bl.ocks.org/mbostock/3884955. However, the X axis is not displaying. The X axis should display the dates. Here is my code:
<svg width="960" height="500"></svg>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
margin = {top: 20, right: 80, bottom: 30, left: 50},
width = svg.attr("width") - margin.left - margin.right,
height = svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var parseTime = d3.timeParse("%Y%m%d");
var x = d3.scaleBand().rangeRound([0, width]),
// xAxis = d3.svg.axis().scale(x).orient("bottom"),
y = d3.scaleLinear().range([height, 0]),
z = d3.scaleOrdinal(d3.schemeCategory10);
var line = d3.line()
.curve(d3.curveBasis)
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.price); });
d3.tsv("data.tsv", type, function(error, data) {
if (error) throw error;
var products = data.columns.slice(1).map(function(id) {
return {
id: id,
values: data.map(function(d) {
return {date: d.date, price: d[id]};
})
};
});
x.domain(data.map( function(d) { return d.date; }));
y.domain([
d3.min(products, function(c) { return d3.min(c.values, function(d) { return d.price; }); }),
d3.max(products, function(c) { return d3.max(c.values, function(d) { return d.price; }); })
]);
z.domain(products.map(function(c) { return c.id; }));
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))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("fill", "#000")
.text("Price $");
var product = g.selectAll(".product")
.data(products)
.enter().append("g")
.attr("class", "product");
product.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return z(d.id); });
product.append("text")
.datum(function(d) { return {id: d.id, value: d.values[d.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.price) + ")"; })
.attr("x", 3)
.attr("dy", "0.35em")
.style("font", "10px sans-serif")
.text(function(d) { return d.id; });
});
function type(d, _, columns) {
d.date = parseTime(d.date);
for (var i = 1, n = columns.length, c; i < n; ++i) d[c = columns[i]] = +d[c];
return d;
}
Here is the resultant chart from my code:- http://bizvizmap.com/timeseries.html
You probably have some problem in your data, because this plunker has exactly your code, with some bogus data I created:
date,foo,bar,baz
20160101,11,23,45
20160102,21,22,35
20160103,44,76,54
20160104,31,23,45
20160105,41,63,15
20160106,12,22,25
Here is the plunker: https://plnkr.co/edit/xZY02zVTEh6o9RwNGJBw?p=preview
The ticks on the x axis are a mess because you're using the full JS date in an ordinal scale. Because of that, it makes more sense using a time scale, as in this other plunker: https://plnkr.co/edit/n5u8N9aK8YELtTCHexgR?p=preview

D3 V4: Zoom & drag multi-series line chart

I am drawing charts with d3 4.2.2 in my Angular2 project. I created a multi series line chart and added zoom and drag properties. Now the chart is zooming on mouse scroll event but it zoom only X-axis and Y-axis. And it can be dragged only X-axis & Y-axis but chart cannot be dragged. When I do zooming or dragging those events are applying only to the two axis es but not for the chart. Following is what I am doing with my code.
// set the dimensions and margins of the graph
var margin = {
top: 20,
right: 80,
bottom: 30,
left: 50
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var zoom = d3.zoom()
.scaleExtent([1, 5])
.translateExtent([[0, -100], [width + 90, height + 100]])
.on("zoom", zoomed);
var svg = d3.select(this.htmlElement).append("svg")
.attr("class", "line-graph")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("pointer-events", "all")
.call(zoom);
var view = svg.append("rect")
.attr("class", "view")
.attr("x", 0.5)
.attr("y", 0.5)
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.style("fill", "#EEEEEE")
.style("stroke", "#000")
.style("stroke-width", "0px");
// parse the date / time
var parseDate = d3.timeParse("%Y-%m-%d");
// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var z = d3.scaleOrdinal(d3.schemeCategory10);
// define the line
var line = d3.line()
.x( (d) => {
return x(d.date);
})
.y( (d) => {
return y(d.lookbookcount);
});
z.domain(d3.keys(data[0]).filter(function (key) {
return key !== "date";
}));
// format the data
data.forEach( (d)=> {
d.date = parseDate(d.date);
});
var lookBookData = z.domain().map(function (name) {
return {
name: name,
values: data.map( (d) => {
return {date: d.date, lookbookcount: d[name], name: name};
})
};
});
x.domain(d3.extent(data, (d) => {
return d.date;
}));
y.domain([
d3.min([0]),
d3.max(lookBookData, (c) => {
return d3.max(c.values,
(d) => {
return d.lookbookcount;
});
})
]);
z.domain(lookBookData.map( (c) => {
return c.name;
}));
var xAxis = d3.axisBottom(x)
.ticks(d3.timeDay.every(1))
.tickFormat(d3.timeFormat("%d/%m"));
var yAxis = d3.axisLeft(y)
.ticks(10);
// Add the X Axis
var gX = svg.append("g")
.style("font", "14px open-sans")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
var gY = svg.append("g")
.style("font", "14px open-sans")
.attr("class", "axis axis--x")
.call(yAxis)
.style("cursor", "ns-resize");
// Add Axis labels
svg.append("text")
.style("font", "14px open-sans")
.attr("text-anchor", "end")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.text("Sales / Searches");
svg.append("text")
.style("font", "14px open-sans")
.attr("text-anchor", "end")
.attr("dx", ".71em")
.attr("transform", "translate(" + width + "," + (height +
(margin.bottom)) + ")")
.text("Departure Date");
var chartdata = svg.selectAll(".chartdata")
.data(lookBookData)
.enter().append("g")
.attr("class", "chartdata");
chartdata.append("path")
.classed("line", true)
.attr("class", "line")
.attr("d", function (d) {
return line(d.values);
})
.style("fill", "none")
.style("stroke", function (d) {
return z(d.name);
})
.style("stroke-width", "2px");
chartdata.append("text")
.datum(function (d) {
return {
name: d.name, value: d.values[d.values.length - 1]
};
})
.attr("transform", function (d) {
return "translate(" +
x(d.value.date) + "," + y(d.value.lookbookcount) + ")";
})
.attr("x", 3)
.attr("dy", "0.35em")
.style("font", "14px open-sans")
.text(function (d) {
return d.name;
});
// add the dots with tooltips
chartdata.selectAll(".circle")
.data(function (d) {
return d.values;
})
.enter().append("circle")
.attr("class", "circle")
.attr("r", 4)
.attr("cx", function (d) {
console.log(d);
return x(d.date);
})
.attr("cy", function (d) {
return y(d.lookbookcount);
})
.style("fill", function (d) { // Add the colours dynamically
return z(d.name);
});
function zoomed() {
view.attr("transform", d3.event.transform);
gX.call(xAxis.scale(d3.event.transform.rescaleX(x)));
}
function resetted() {
svg.transition()
.duration(750)
.call(zoom.transform, d3.zoomIdentity);
}
Any suggestions would be highly appreciated.
Thank you

Categories

Resources