d3.js line chat grid issue - javascript

I am new to d3 library and I have made a line chart containing grid. I am having trouble in reducing grid lines length so that they can match the lengths with x axis and y axis lines.
Code link : https://codepen.io/liot/pen/yLYyRZj
Line chart Image highlighting extra length of grid lines:
Below is my code
const data = [
{ date: "01/01/2016", value: 10000 },
{ date: "01/02/2016", value: 20000 },
{ date: "01/03/2016", value: 40000 },
{ date: "01/04/2016", value: 30000 },
{ date: "01/05/2016", value: 30000 }
];
const width = 600;
const height = 400;
const margin = { top: 10, right: 40, bottom: 40, left: 70 };
const parseDate = d3.timeParse("%m/%d/%Y");
const xFormat = "%d-%b";
const yMax = d3.max(data, (d) => d.value);
const x = d3
.scaleTime()
.domain(d3.extent(data, (d) => parseDate(d.date)))
.rangeRound([margin.left, width - margin.right])
.nice(data.length);
const y = d3
.scaleLinear()
.domain([0, yMax < 10 ? 10 : yMax])
.range([height - margin.bottom, margin.top])
.nice();
const xAxis = (g) =>
g
.style("font-size", "15px")
.style("color", "#424242")
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x).tickFormat(d3.timeFormat(xFormat)).ticks(d3.timeDay))
.call((g) => g.select(".domain"))
.call((g) =>
g
.selectAll(".tick line")
.clone()
.attr("y2", -height)
.attr("stroke-opacity", 0.2)
);
const yAxis = (g) =>
g
.style("font-size", "15px")
.style("color", "#424242")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y))
.call((g) => g.select(".domain"))
.call((g) =>
g
.selectAll(".tick line")
.clone()
.attr("x2", width)
.attr("stroke-opacity", 0.2)
);
const line = d3
.line()
// eslint-disable-next-line no-restricted-globals
.defined((d) => !isNaN(d.value))
.x((d) => x(parseDate(d.date)))
.y((d) => y(d.value));
const svg = d3
.select("body")
.append("svg")
.attr("viewBox", [0, 0, width, height]);
svg.append("g").call(xAxis);
svg.append("g").call(yAxis);
svg
.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "#007bff")
.attr("stroke-width", 3)
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("d", line.curve(d3.curveBasis));
CSS code is below :
body {
width:600px;
height:400px;
}

These two lines below which control the lengths of x2 and y2 need to be adjusted:
.attr("y2", - height + 50)
.attr("x2", width - 110)
Complete Code:
const data = [
{ date: "01/01/2016", value: 10000 },
{ date: "01/02/2016", value: 20000 },
{ date: "01/03/2016", value: 40000 },
{ date: "01/04/2016", value: 30000 },
{ date: "01/05/2016", value: 30000 }
];
const width = 600;
const height = 400;
const margin = { top: 10, right: 40, bottom: 40, left: 70 };
const parseDate = d3.timeParse("%m/%d/%Y");
const xFormat = "%d-%b";
const yMax = d3.max(data, (d) => d.value);
const x = d3
.scaleTime()
.domain(d3.extent(data, (d) => parseDate(d.date)))
.rangeRound([margin.left, width - margin.right])
.nice(data.length);
const y = d3
.scaleLinear()
.domain([0, yMax < 10 ? 10 : yMax])
.range([height - margin.bottom, margin.top])
.nice();
const xAxis = (g) =>
g
.style("font-size", "15px")
.style("color", "#424242")
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x).tickFormat(d3.timeFormat(xFormat)).ticks(d3.timeDay))
.call((g) => g.select(".domain"))
.call((g) =>
g
.selectAll(".tick line")
.clone()
.attr("y2", - height+50)
.attr("stroke-opacity", 0.2)
);
const yAxis = (g) =>
g
.style("font-size", "15px")
.style("color", "#424242")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y))
.call((g) => g.select(".domain"))
.call((g) =>
g
.selectAll(".tick line")
.clone()
.attr("x2", width -110)
.attr("stroke-opacity", 0.2)
);
const line = d3
.line()
// eslint-disable-next-line no-restricted-globals
.defined((d) => !isNaN(d.value))
.x((d) => x(parseDate(d.date)))
.y((d) => y(d.value));
const svg = d3
.select("body")
.append("svg")
.attr("viewBox", [0, 0, width, height]);
svg.append("g").call(xAxis);
svg.append("g").call(yAxis);
svg
.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "#007bff")
.attr("stroke-width", 3)
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("d", line.curve(d3.curveBasis));
CSS Code:
body {
width:600px;
height:400px;
}

Related

Responsive D3.js line chart

I am new to using D3 charts and am having an issue making my multi line chart responsive. I have reviewed some examples on how to make D3 responsive, but I can not make it work on my graph. If someone could assist me in making the code I currently have responsive I would really appreciate it.
Here is the code:
const svg = d3.select("#lineChart").attr("height", 400).attr("width", 850);
const width = +svg.attr("width");
const height = +svg.attr("height");
let hasMoreThan0 = false;
const render = (data) => {
const xValue = (d) => d.dateTest;
const yValue = (d) => d.total;
const margin = { top: 60, right: 40, bottom: 88, left: 105 };
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
const xScale = d3
.scaleTime()
.domain(d3.extent(data, xValue))
.range([0, innerWidth])
.nice();
let yScale;
let totalArray = [];
data.forEach((element) => {
totalArray.push(element.total);
});
const reducer = (accumulator, currentValue) => accumulator + currentValue;
let tc = totalArray.reduce(reducer);
data.forEach((d) => {
if (tc == 0) {
yScale = d3
.scaleLinear()
.domain([0, 51])
.range([innerHeight, 0])
.nice();
} else {
yScale = d3
.scaleLinear()
.domain(d3.extent(data, yValue))
.range([innerHeight, 0])
.nice();
}
});
const g = svg
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
const xAxis = d3
.axisBottom(xScale)
.tickPadding(15)
.ticks(d3.timeDay)
.tickFormat(d3.timeFormat("%a %d %B %y"));
const yAxis = d3.axisLeft(yScale).tickSize(-innerWidth).tickPadding(10);
const yAxisG = g.append("g").call(yAxis);
yAxisG.selectAll(".domain").remove();
yAxisG
.append("text")
.attr("class", "axis-label")
.attr("y", -60)
.attr("x", -innerHeight / 2)
.attr("fill", "black")
.attr("transform", `rotate(-90)`)
.attr("text-anchor", "middle");
const xAxisG = g
.append("g")
.call(xAxis)
.attr("transform", `translate(0,${innerHeight})`);
xAxisG.select(".domain").remove();
xAxisG
.append("text")
.attr("class", "axis-label")
.attr("y", 80)
.attr("x", innerWidth / 2)
.attr("fill", "black");
const areaGenerator = d3
.area()
.x((d) => xScale(xValue(d)))
.y0(innerHeight)
.y1((d) => yScale(yValue(d)))
.curve(d3.curveMonotoneX);
g.append("path")
.attr("class", "area-path")
.attr("d", areaGenerator(data));
const lineGenerator = d3
.line()
.x((d) => xScale(xValue(d)))
.y((d) => yScale(yValue(d)))
.curve(d3.curveMonotoneX);
g.append("path")
.attr("class", "line-path")
.attr("d", lineGenerator(data));
// g.append("text").attr("class", "title").attr("y", -10).text(title);
svg.append("line").attr("class", "line-dashet");
svg.append("line").attr("class", "line-dashet");
svg
.selectAll("circle")
.data(data)
.join("circle")
.attr("cx", function (d) {
return xScale(xValue(d)) + 105;
})
.attr("cy", function (d) {
return yScale(yValue(d)) + 60;
})
.attr("r", 4)
.on("mouseout", function (e) {
let toolTip = document.getElementsByClassName("tooltip")[0];
toolTip.classList.remove("tooltip-open");
})
.on("mouseover", function (e) {
let selectPoint = d3.select(this);
let xLineAnim = selectPoint._groups[0][0].cx.animVal.value;
let yLineAnim = selectPoint._groups[0][0].cy.animVal.value;
let selectPointData = selectPoint._groups[0][0].__data__;
let dateFormated = formatDate(selectPointData.dateTest, a, "-");
let toolTip = document.getElementsByClassName("tooltip")[0];
toolTip.style.top = yLineAnim - 55 + "px";
toolTip.style.left = xLineAnim + 5 + "px";
toolTip.innerHTML =
"<div><span>Day: </span>" +
dateFormated +
"</div><div><span>Total: </span>" +
selectPointData.total +
"</div>";
toolTip.classList.add("tooltip-open");
svg
.select(".line-dashet")
.attr("x1", xLineAnim)
.attr("y1", margin.top)
.attr("x2", xLineAnim)
.attr("y2", innerHeight + margin.top);
});
};
getTimeData();

Image is not showing in appended d3 gantt chart

I am trying to append some image in Gantt chart horizontal bar but it's not showing. But tags getting appended inside rect tag.
here is the code
const start_date = "2020-01-15";
const end_date = "2020-05-05";
const MARGIN = {
left: 50,
right: 50,
top: 50,
bottom: 50
}
const svg = d3.select('.graph').append('svg')
// console.log(svg)
const width = 800
const height = 600
const ticks = ['task1', 'task2', 'task3', 'task4', 'task5', 'task6'];
const x = d3.scaleTime().domain([new Date(start_date), new Date(end_date)]).range([0, (width - (MARGIN.left + MARGIN.right))])
const y = d3.scaleBand()
.domain(ticks)
.range([0, 400])
// .range([0, 50,100, 150, 200, 250, 300, 350,height])
const X = v => {
const minDate = d3.min(v, d => new Date(d.initDate))
const maxDate = d3.max(v, d => new Date(d.endDate))
return d3.scaleUtc().domain([minDate, maxDate]).range([MARGIN.right, width - MARGIN.left]);
}
const x_axis = d3.axisTop(x).ticks(6);
const y_axis = d3
.axisLeft(y)
.ticks(6)
.tickFormat(function(d, i){return ticks[i]})
.tickSize(15);
svg.attr('width', width).attr('height', height).attr('viewBox', `0 0 700 800`)
let chart = svg.append("g").attr('class', 'chart-holder').attr('transform', `translate(50,50)`);
chart
.append("g")
.attr('class', 'x axis')
.attr("transform", "translate(0,50)")
.call(x_axis);
chart
.append("g")
.attr('class', 'y axis')
.attr("transform", "translate(0, 50)")
.call(y_axis);
chart
.selectAll('rect')
.data([...this.state.data])
.enter()
.append('rect')
.attr('height', 40)
.attr('x', function(d){
return x(new Date(d.initDate))
})
.attr('y', function(d, i){
return y(d.name);
})
.attr('width', function(d, i){
return x(new Date(d.endDate)) - x(new Date(d.initDate))
})
.attr('rx', 17)
.attr('ry', 17)
.attr('fill', "blue")
.attr('transform', `translate(50, 60)`)
.append('image')
.attr("src", "https://miro.medium.com/max/1000/1*tv9pIQPhwumDnYBfCoapYg.jpeg")
.attr("width", 30)
.attr("height", 30)
and things I have already tried
xlink:href instead of href
svg:image instead of image
You cannot append under . Replace with , then append and under
const items =
chart.selectAll('g.item').data(...).enter().append('g').classed('item', true);
items.append('rect')...
items.append('image')...

Why the graph lines are off axis Y and X?

I am having trouble creating a chart with a simple line.
I'll put here my code and an image of how the line is getting off axis Y and X. I really have no idea why this is happening.
Chart:
HTML:
<div id="myChart"></div>
JavaScript:
Function to adjust my json only with the data I need:
var reduceVendedores = vendedores.reduce(function (allSales, sales) {
if (allSales.some(function (e) {
return e.vendnm === sales.vendnm;
})) {
allSales.filter(function (e) {
return e.vendnm === sales.vendnm
})[0].Vendas_Ano += sales.Vendas_Ano;
allSales.filter(function (e) {
return e.vendnm === sales.vendnm
})[0].Vendas_Ant += sales.Vendas_Ant
} else {
allSales.push({
vendnm: sales.vendnm,
Vendas_Ano: sales.Vendas_Ano,
Vendas_Ant: sales.Vendas_Ant
})
}
return allSales;
}, []);
Defining X and Y Axes of the Chart:
var margin = { top: 30, right: 30, bottom: 70, left: 60 },
width = 600 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
function getMax() {
return reduceVendedores.map(d => d.Vendas_Ano)
}
var svg = d3.select("#myChart")
.append("svg")
.attr("width", width)
.attr("height", height);
var xscale = d3.scaleBand()
.range([0, width - 100])
.domain(reduceVendedores.map(function (d) { return d.vendnm; }))
var yscale = d3.scaleLinear()
.domain([0, d3.max(getMax()) + 30000])
.range([height / 2, 0]);
var x_axis = d3.axisBottom().scale(xscale);
var y_axis = d3.axisLeft().scale(yscale);
svg.append("g")
.attr("transform", "translate(50, 10)")
.call(y_axis);
var xAxisTranslate = height / 2 + 10;
svg.append("g")
.attr("transform", "translate(50, " + xAxisTranslate + ")")
.call(d3.axisBottom(xscale))
.selectAll("text")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end");
Defining the chart line:
svg.append("path")
.datum(reduceVendedores)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(function (d) { return xscale(d.vendnm); })
.y(function (d) { return yscale(d.Vendas_Ano) })
)
You're not adjusting for the margins you gave to your axis with:
.attr("transform", "translate(50, 10)")
Try:
svg.append('g')
.attr('transform','translate(50,0)')
.append("path")
.datum(reduceVendedores)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(function (d) { return xscale(d.vendnm); })
.y(function (d) { return yscale(d.Vendas_Ano) })
)
Typically you'd set your margin in svg, like:
var svg = d3.select("#myChart")
.append("svg")
.attr("width", width)
.attr("height", height)
.append('g')
.attr('transform','translate(' + margin.left + ',' + margin.top + ')')
But doing so know would ruin the alignment of your axis.

Arranging graphs on one screen

I wan to four graphs side-by-side linearly but they keep overlapping. I am using d3.js and I would like to draw these four graphs side by side.
I tried drawing each graph in its own svg tag and then combine them but it doesn't work.
<script>
const svg = d3.select("svg");
const width = +svg.attr("width");
const height = +svg.attr("height");
var TelescopeData = [
{ Average: 2000, TelescopeName: "1 meter" },
{ Average: 3000, TelescopeName: "1.9 meter" },
{ Average: 4000, TelescopeName: "Lesedi" }
];
var padding = { top: 40, right: 20, bottom: 30, left: 75 };
const innerWidth = width - padding.left - padding.right;
const innerHeight = height - padding.top - padding.bottom;
var colors = ["red", "black", "green"];
var yScale = d3
.scaleLinear()
.domain([0, d3.max(TelescopeData, d => d.Average)])
.range([innerHeight, 0])
.nice();
var xScale = d3
.scaleBand()
.domain(
TelescopeData.map(d => {
return d.TelescopeName;
})
)
.range([0, innerWidth])
.padding(0.4);
//xAxis
const xAxis = svg
.append("g")
.classed("xAxis", true)
.attr(
"transform",
`translate(${padding.left},${innerHeight + padding.top})`
)
.call(d3.axisBottom(xScale));
//yAxis
const yAxis = svg
.append("g")
.classed("yAxis", true)
.attr("transform", `translate(${padding.left},${padding.top})`)
.call(d3.axisLeft(yScale));
// now adding the data
const rectGrp = svg
.append("g")
.attr("transform", `translate(${padding.left},${padding.top})`);
rectGrp
.selectAll("rect")
.data(TelescopeData)
.enter()
.append("rect")
.attr("width", xScale.bandwidth())
.attr("height", (d, i) => {
return innerHeight - yScale(d.Average);
})
.attr("x", d => {
return xScale(d.TelescopeName);
})
.attr("y", function(d) {
return yScale(d.Average);
})
.attr("fill", (d, i) => {
return colors[i];
});
rectGrp
.append("text")
.attr("y", -20)
.attr("x", 50)
.text("Quarterly Average");
</script>
I expect to see the code attached here to be used for drawing 3 other graphs side-side with the first one
Are you wanting to have four graphs shown side by side on the screen next to each other?
That is what your question seems to imply, without having an example of what you want the output to look like. The other option would be, how to merge four graphs into one graph, which is an entirely different question.
Here is a very simple HTML document that will put the four graphs side by side on the screen. I think your problem was probably that you were selecting the element to draw to using the SVG selector instead of assigning an id to each different element and then selecting each element id.
<html>
<head>
<title>Example</title>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
function drawGraph(elementId) {
const svg = d3.select(elementId);
const width = +svg.attr("width");
const height = +svg.attr("height");
let TelescopeData = [
{ Average: 2000, TelescopeName: "1 meter" },
{ Average: 3000, TelescopeName: "1.9 meter" },
{ Average: 4000, TelescopeName: "Lesedi" }
];
let padding = { top: 40, right: 20, bottom: 30, left: 75 };
const innerWidth = width - padding.left - padding.right;
const innerHeight = height - padding.top - padding.bottom;
let colors = ["red", "black", "green"];
let yScale = d3
.scaleLinear()
.domain([0, d3.max(TelescopeData, d => d.Average)])
.range([innerHeight, 0])
.nice();
let xScale = d3
.scaleBand()
.domain(
TelescopeData.map(d => {
return d.TelescopeName;
})
)
.range([0, innerWidth])
.padding(0.4);
//xAxis
const xAxis = svg
.append("g")
.classed("xAxis", true)
.attr("transform",`translate(${padding.left},${innerHeight + padding.top})`)
.call(d3.axisBottom(xScale));
//yAxis
const yAxis = svg
.append("g")
.classed("yAxis", true)
.attr("transform", `translate(${padding.left},${padding.top})`)
.call(d3.axisLeft(yScale));
// now adding the data
const rectGrp = svg
.append("g")
.attr("transform", `translate(${padding.left},${padding.top})`);
rectGrp
.selectAll("rect")
.data(TelescopeData)
.enter()
.append("rect")
.attr("width", xScale.bandwidth())
.attr("height", (d, i) => {
return innerHeight - yScale(d.Average);
})
.attr("x", d => {
return xScale(d.TelescopeName);
})
.attr("y", function(d) {
return yScale(d.Average);
})
.attr("fill", (d, i) => {
return colors[i];
});
rectGrp
.append("text")
.attr("y", -20)
.attr("x", 50)
.text("Quarterly Average");
}
drawGraph("#testsvg1");
drawGraph("#testsvg2");
drawGraph("#testsvg3");
drawGraph("#testsvg4");
</script>
</head>
<body>
<svg id="#testsvg1" width="200" height="200"></svg>
<svg id="#testsvg2" width="200" height="200"></svg>
<svg id="#testsvg3" width="200" height="200"></svg>
<svg id="#testsvg4" width="200" height="200"></svg>
</body>
</html>

Creating an area chart: path with NaN

I am starting with D3 and trying to recreate example from the url
I am getting this error
Error: <path> attribute d: Expected number, "MNaN,NaNLNaN,NaNL…"
Attribute d is referred to each and every data. But why is it coming as MNaN?
I would be thankful, if the solution provider explain how the debug is done.
data = [
{
"date": "2007-04-23",
"close": 93.24
},
{
"date": "2007-04-24",
"close": 95.35
}];
//Update 1
data = data.map((item) => ({date:item.date, value:item.close}))
data.y = "$ Close";
//Update 1
height = 500;
width = 500;
margin = ({ top: 20, right: 20, bottom: 30, left: 30 });
x = d3.scaleTime()
.domain(d3.extent(data, d => d.date))
.range([margin.left, width - margin.right])
y = d3.scaleLinear()
.domain([0, d3.max(data, d => d.value)]).nice()
.range([height - margin.bottom, margin.top])
xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x).ticks(width / 80).tickSizeOuter(0));
yAxis = g => g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y))
.call(g => g.select(".domain").remove())
.call(g => g.select(".tick:last-of-type text").clone()
.attr("x", 3)
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text(data.y))
area = d3.area()
.x(d => x(d.date))
.y0(y(0))
.y1(d => y(d.value))
const svg = d3.select('svg')
.attr('width', width)
.attr('height', height)
svg.append("path")
.datum(data)
.attr("fill", "steelblue")
.attr("d", area);
svg.append("g")
.call(xAxis);
svg.append("g")
.call(yAxis);
Here is the JSFIDDLE
Update 1
I need have the data in date and value and y..... but i had date and close..... updated the code in jsfiddle too.
Now i am getting the x and y axis but the error message remains the same.
Update 1 Fiddle
Understanding the error in the path's d attribute
When you get a NaN in a path creates by a line (or an area for that matter), you have to check where is the NaN. For instance:
MNaN,NaNL...
↑ ↑
x y
This shows that the problem is in both x and y methods. On the other hand, when you get:
MNaN,42L...
↑ ↑
x y
The problem is in the x method, while...
M42,NaNL...
↑ ↑
x y
... shows that the problem is in the y method. According to your updated JSFiddle, the problem comes from the x method.
Your problem
The problem is that you're passing things like this to the time scale:
"2007-04-24"
This is not a date, this is just a string. You have to parse the dates. For instance, given your string format:
data.forEach(function(d){
d.date = d3.timeParse("%Y-%m-%d")(d.date);
});
Here is the code with that change:
data = [{
"date": "2007-04-23",
"close": 93.24
},
{
"date": "2007-04-24",
"close": 95.35
}
];
data.forEach(function(d) {
d.date = d3.timeParse("%Y-%m-%d")(d.date);
d.value = d.close;
})
height = 500;
width = 500;
margin = ({
top: 20,
right: 20,
bottom: 30,
left: 30
});
x = d3.scaleTime()
.domain(d3.extent(data, d => d.date))
.range([margin.left, width - margin.right])
y = d3.scaleLinear()
.domain([0, d3.max(data, d => d.value)]).nice()
.range([height - margin.bottom, margin.top])
xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x).ticks(width / 80).tickSizeOuter(0));
yAxis = g => g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y))
.call(g => g.select(".domain").remove())
.call(g => g.select(".tick:last-of-type text").clone()
.attr("x", 3)
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text(data.y))
area = d3.area()
.x(d => x(d.date))
.y0(y(0))
.y1(d => y(d.value))
const svg = d3.select('svg')
.attr('width', width)
.attr('height', height)
svg.append("path")
.datum(data)
.attr("fill", "steelblue")
.attr("d", area);
svg.append("g")
.call(xAxis);
svg.append("g")
.call(yAxis);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg></svg>

Categories