Responsive D3.js line chart - javascript

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();

Related

d3js beeswarm with force simulation

I try to do a beeswarm plot with different radius; inspired by this code
The issue I have, is that my point are offset regarding my x axis:
The point on the left should be at 31.7%. I don't understand why, so I would appreciate if you could guide me. This could be improved by changing the domain of x scale, but this can't match the exact value; same issue if I remove the d3.forceCollide()
Thank you,
Data are available here.
Here is my code:
$(document).ready(function () {
function tp(d) {
return d.properties.tp60;
}
function pop_mun(d) {
return d.properties.pop_mun;
}
var margin = {top: 20, right: 20, bottom: 20, left: 40},
width = 1280 - margin.right - margin.left,
height = 300 - margin.top - margin.bottom;
var svg = d3.select("body")
.append("svg")
.attr("viewBox", `0 0 ${width} ${height}`)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var z = d3.scaleThreshold()
.domain([.2, .3, .4, .5, .6, .7])
.range(["#35ff00", "#f1a340", "#fee0b6",
"#ff0000", "#998ec3", "#542788"]);
var loading = svg.append("text")
.attr("x", (width) / 2)
.attr("y", (height) / 2)
// .attr("dy", ".35em")
.style("text-anchor", "middle")
.text("Simulating. One moment please…");
var formatPercent = d3.format(".0%"),
formatNumber = d3.format(".0f");
d3.json('static/data/qp_full.json').then(function (data) {
features = data.features
//1 create scales
var x = d3.scaleLinear()
.domain([0, d3.max(features, tp)/100])
.range([0, width - margin.right])
var y = d3.scaleLinear().domain([0, 0.1]).range([margin.left, width - margin.right])
var r = d3.scaleSqrt().domain([0, d3.max(features, pop_mun)])
.range([0, 25]);
//2 create axis
var xAxis = d3.axisBottom(x).ticks(20)
.tickFormat(formatPercent);
svg.append("g")
.attr("class", "x axis")
.call(xAxis);
var nodes = features.map(function (node, index) {
return {
radius: r(node.properties.pop_mun),
color: '#ff7f0e',
x: x(node.properties.tp60 / 100),
y: height + Math.random(),
pop_mun: node.properties.pop_mun,
tp60: node.properties.tp60
};
});
function tick() {
for (i = 0; i < nodes.length; i++) {
var node = nodes[i];
node.cx = node.x;
node.cy = node.y;
}
}
setTimeout(renderGraph, 10);
function renderGraph() {
// Run the layout a fixed number of times.
// The ideal number of times scales with graph complexity.
// Of course, don't run too long—you'll hang the page!
const NUM_ITERATIONS = 1000;
var force = d3.forceSimulation(nodes)
.force('charge', d3.forceManyBody().strength(-3))
.force('center', d3.forceCenter(width / 2, height/2))
.force('x', d3.forceX(d => d.x))
.force('y', d3.forceY(d => d.y))
.force('collide', d3.forceCollide().radius(d => d.radius))
.on("tick", tick)
.stop();
force.tick(NUM_ITERATIONS);
force.stop();
svg.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("r", d => d.radius)
.style("fill", d => z(d.tp60/100))
.on("mouseover", function (d, i) {
d3.select(this).style('fill', "orange")
console.log(i.tp60,i)
svg.append("text")
.attr("id", "t")
.attr("x", function () {
return d.x - 50;
})
.attr("y", function () {
return d.y - 50;
})
.text(function () {
return [x.invert(i.x), i.tp60]; // Value of the text
})
})
.on("mouseout", function (d, i) {
d3.select("#t").remove(); // Remove text location
console.log(i)
d3.select(this).style('fill', z(i.tp60/100));
});
loading.remove();
}
})
})

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')...

d3.js line chat grid issue

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;
}

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.

How to scale y-Axis in d3.js according to data input correctly

I created a small Barchart. My problem is that the y-axis doesn't scale according to my dataset. Here is a screenshot:
So as you can see the 313 is a little bit above the 800 scale. I would like the 300 to be at the 300. I tweaked with the scalings but I just end up messing it up completely. I am very new to D3.js so I hope someone can help.
Here is my code:
var svgWidth = 1000, svgHeight = 800;
var barWidth = svgWidth / month_description.length;
var barPadding = 5;
var svg = d3.select('svg')
.attr("width", svgWidth)
.attr("height", svgHeight);
var barChart = svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("y", function(d) {
return svgHeight - d - 20
})
.attr("x", function(d, i) {
return i + 10;
})
.attr("height", function(d) {
return d;
})
.attr("width", barWidth - barPadding)
.attr("class", "bar")
.attr("transform", function (d, i) {
var translate = [barWidth * i, 0];
return "translate("+ translate +")";
});
var text = svg.selectAll("text")
.data(data)
.enter()
.append("text")
.text(function(d) {
return d;
})
.attr("y", function(d, i) {
return svgHeight - 20;
})
.attr("x", function(d, i) {
return barWidth * i + 35;
})
.attr("fill", "white");
var xScale = d3.scaleLinear()
.domain([0, d3.max(month_description)])
.range([0, svgWidth]);
var yScale = d3.scaleLinear()
.domain([0, d3.max(data)])
.range([svgHeight, 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 = svgHeight - 20;
svg.append("g")
.attr("transform", "translate(50, " + xAxisTranslate +")")
.call(x_axis);
Help is very much appreciated. Thanks very much in advance!!

Categories