I am new to d3 and trying to make a grouped bar chart following this and this websites.
The following is my code for the scales
x0 = d3.scaleTime()
.domain([
d3.min(dataset, function(d) {return d.date;}),
d3.max(dataset, function(d) {return d.date;})
])
.range([0,w]);
x1 = d3.scaleOrdinal()
.domain([dataset.WorldPopulation, dataset.InternetUser]);
// .rangeRound([0, x0.bandwidth()]);
y = d3.scaleLinear()
.domain([0, d3.max(dataset, function(d) {return d.WorldPopulation})])
.range([h,0]);
In the website above they use scaleOrdinal but I used scaleTime as x0. Therefore I'm not too sure if that works.
This is my code for the append(rect) for the bar chart
var date = svg.selectAll(".date")
.data(dataset)
.enter()
.append("g")
.attr("class", "g")
.attr("transform", function(d) {return "translate(" + x0(d.date) + ",0)";});
/* Add field1 bars */
date.selectAll(".bar.field1")
.data(d => [d])
.enter()
.append("rect")
.attr("class", "bar field1")
.style("fill","blue")
.attr("x", d => x0(d.WorldPopulation))
.attr("y", d => y(d.WorldPopulation))
.attr("width", x1.bandwidth())
.attr("height", d => {
return height - margin.top - margin.bottom - y(d.WorldPopulation)
});
/* Add field2 bars */
date.selectAll(".bar.field2")
.data(d => [d])
.enter()
.append("rect")
.attr("class", "bar field2")
.style("fill","red")
.attr("x", d => x0(d.InternetUser))
.attr("y", d => y(d.InternetUser))
.attr("width", x1.bandwidth())
.attr("height", d => {
return height - margin.top - margin.bottom - y(d.InternetUser)
});
And this is my csv file. Not too sure how to upload excel so I took a screenshot.
Any help provided will be appreciated. Feel free to request for any extra snippets of my code for clarification.
edit: After tweaking the code, no errors are shown but the svg is not produced either.
Using scaleBand for both x0 and x1, the following code works:
<script src="js/d3.v4.js"></script>
<script>
var margin = { top: 20, right: 20, bottom: 30, left: 40 },
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x0 = d3.scaleBand()
.rangeRound([0, width]).padding(.1);
var x1 = d3.scaleBand();
var y = d3.scaleLinear()
.range([height, 0]);
var xAxis = d3.axisBottom()
.scale(x0)
.tickSize(0);
var yAxis = d3.axisLeft()
.scale(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 + ")");
d3.csv("InternetUserStats.csv", function (error, data) {
var dates = data.map(function (d) { return d.date; });
var ymax = d3.max(data, function (d) { return Math.max(d.WorldPopulation, d.InternetUser); });
x0.domain(dates);
x1.domain(['WorldPopulations', 'InternetUsers']).rangeRound([0, x0.bandwidth()]);
y.domain([0, ymax]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
var date = svg.selectAll(".g")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function (d) { return "translate(" + x0(d.date) + ",0)"; });
/* Add field1 bars */
date/*.selectAll(".bar.field1")
.data(data)
.enter()*/
.append("rect")
.attr("class", "bar field1")
.style("fill", "blue")
.attr("x", function (d) { return x1('WorldPopulations'); })
.attr("y", function (d) { return y(+d.WorldPopulation); })
.attr("width", x0.bandwidth() / 2)
.attr("height", function (d) {
return height /*- margin.top - margin.bottom*/ - y(d.WorldPopulation)
});
/* Add field2 bars */
date/*.selectAll(".bar.field1")
.data(data)
.enter()*/
.append("rect")
.attr("class", "bar field2")
.style("fill", "red")
.attr("x", function (d) { return x1('InternetUsers'); })
.attr("y", function (d) { return y(d.InternetUser); })
.attr("width", x0.bandwidth() / 2)
.attr("height", function (d) {
return height /*- margin.top - margin.bottom*/ - y(d.InternetUser)
});
});
</script>
EDIT: the following code adds a legend that is based on your comment and your second link:
//This code goes after field2 bars are added.
/* Legend */
var legend = svg.selectAll(".legend")
.data(['WorldPopulations', 'InternetUsers']) //Bind an array of the legend entries
.enter().append("g")
.attr("class", "legend")
.attr("font-family", "sans-serif")
.attr("font-size", 13)
.style("text-anchor", "end")
.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) {
// Return "blue" for "WorldPopulations" and "red" for "InternetUsers":
return ((d === "WorldPopulations") ? "blue" : "red");
});
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
//.style("text-anchor", "end") //Already applied to .legend
.text(function (d) { return d; });
Related
I am newbie in d3js, I do not know why all labels in the bar are wrong.
My code and captures are shown as below, then you can see that all labels are different from my data.
Anyone know what's going on in my text label section?
// set the dimensions and margins of the graph
var margin = { top: 10, right: 30, bottom: 40, left: 50 },
width = 700 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
const dataUrl = "https://raw.githubusercontent.com/yushinglui/IV/main/time_distance_status_v2.csv"
//fetch the data
d3.csv(dataUrl)
.then((data) => {
// append the svg object to the body of the page
var svg = d3.select("#graph-2")
.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 + ")")
// List of subgroups = header of the csv files = soil condition here
var subgroups = data.columns.slice(1)
// List of groups = species here = value of the first column called group -> I show them on the X axis
var groups = d3.map(data, function (d) { return (d.startTime) })
// Add X axis
var x = d3.scaleBand()
.domain(groups)
.range([0, width])
.padding([0.2])
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickSize(0));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 20])
.range([height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// Another scale for subgroup position?
var xSubgroup = d3.scaleBand()
.domain(subgroups)
.range([0, x.bandwidth()])
.padding([0.05])
// color palette = one color per subgroup
var color = d3.scaleOrdinal()
.domain(subgroups)
.range(['#98abc5', '#8a89a6'])
// Show the bars
svg.append("g")
.selectAll("g")
// Enter in data = loop group per group
.data(data)
.enter()
.append("g")
.attr("transform", function (d) { return "translate(" + x(d.startTime) + ",0)"; })
.selectAll("rect")
.data(function (d) { return subgroups.map(function (key) { return { key: key, value: d[key] }; }); })
.enter()
.append("rect")
.attr("x", function (d) { return xSubgroup(d.key); })
.attr("y", function (d) { return y(d.value); })
.attr("width", xSubgroup.bandwidth())
.attr("height", function (d) { return height - y(d.value); })
.attr("fill", function (d) { return color(d.key); })
// mouseover and mouseout animation
.on("mouseover", function (d) {
d3.select(this).style("fill", d3.rgb(color(d.key)).darker(2))
})
.on("mouseout", function (d) {
d3.select(this).style("fill", function (d) { return color(d.key); })
})
//axis labels
svg.append('text')
.attr('x', - (height / 2))
.attr('y', width - 650)
.attr('transform', 'rotate(-90)')
.attr('text-anchor', 'middle')
.style("font-size", "17px")
.text('Average Distance');
svg.append('text')
.attr('x', 300)
.attr('y', width - 240)
.attr('transform', 'rotate()')
.attr('text-anchor', 'middle')
.style("font-size", "17px")
.text('Start Time');
// legend
svg.append("circle").attr("cx", 200).attr("cy", 20).attr("r", 6).style("fill", "#98abc5")
svg.append("circle").attr("cx", 300).attr("cy", 20).attr("r", 6).style("fill", "#8a89a6")
svg.append("text").attr("x", 220).attr("y", 20).text("Present").style("font-size", "15px").attr("alignment-baseline", "middle")
svg.append("text").attr("x", 320).attr("y", 20).text("Absent").style("font-size", "15px").attr("alignment-baseline", "middle")
//text labels on bars -- all labels wrong!!
svg.append("g")
.selectAll("g")
// Enter in data = loop group per group
.data(data)
.enter()
.append("g")
.attr("transform", function (d) { return "translate(" + x(d.startTime) + ",0)"; })
.selectAll("text")
.data(function (d) { return subgroups.map(function (key) { return { key: key, value: d[key] }; }); })
.enter()
.append("text")
.text(function (d) { return y(d.value); })
.attr("font-family", "sans-serif")
.attr("font-size", "12px")
.attr("fill", "black")
.attr("text-anchor", "middle")
.attr("x", function (d) { return xSubgroup(d.key); })
.attr("y", function (d) { return y(d.value) + 10; })
});
My reference website:
http://plnkr.co/edit/9lAiAXwet1bCOYL58lWN?p=preview&preview
https://bl.ocks.org/bricedev/0d95074b6d83a77dc3ad
Your issue is that when you're appending the text, you inadvertently called the y function, which is used to get the y-location on where to insert the text. The numbers you're getting are actually y-location values, which seems completely random.
.text(function (d) { return y(d.value); }) // here is the issue
Change it to
.text(function (d) { return d.value; })
and it should work!
So I am trying to update a grouped bar chart with new data. The user has a drop-down to select a branch and see the statistic on the number of calls and type.
Right now when I select one of the options the chart is drawn but the following error is thrown in the log. When I then select another option the new data is drawn on top of the already drawn graph and the error is thrown again
Uncaught Error: invalid merge
at Selection$1.selection_merge [as merge] (d3.v6.js:1690)
at Selection$1.selection_join [as join] (d3.v6.js:1686)
at draw (Charts:236)
at Object.success (Charts:126)
at c (jquery.min.js:2)
at Object.fireWith [as resolveWith] (jquery.min.js:2)
at l (jquery.min.js:2)
at XMLHttpRequest.<anonymous> (jquery.min.js:2)
draw (Charts:236) is the join statment in my code
Here is an example of the input data I have, but the user will of course have the option to choose the dates they want the information from.
var A = [{"Date":"2021-02-01","CallTypeOne":69,"CallTypeTwo":67,"CallTypeThree":2,"CallTypeFour":0},{"Date":"2021-02-02","CallTypeOne":69,"CallTypeTwo":69,"CallTypeThree":0,"CallTypeFour":0}]
var B = [{"Date":"2021-02-01","CallTypeOne":116,"CallTypeTwo":114,"CallTypeThree":2,"CallTypeFour":0},{"Date":"2021-02-02","CallTypeOne":43,"CallTypeTwo":40,"CallTypeThree":1,"CallTypeFour":2},{"Date":"2021-02-03","CallTypeOne":270,"CallTypeTwo":221,"CallTypeThree":26,"CallTypeFour":23}]
And here is my code for creating the charts
var margin = { top: 20, right: 20, bottom: 60, left: 40 },
width = 1060 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x0 = d3.scaleBand()
.rangeRound([0, width])
.padding(0.2)
var x1 = d3.scaleBand();
var y = d3.scaleLinear()
.range([height, 0]);
var color = d3.scaleOrdinal()
.range(["#4472c4", "#ed7d31", "#a5a5a5", "#ffc000", "#a05d56", "#d0743c", "#ff8c00"]);
var xAxis = d3.axisBottom(x0);
var yAxis = d3.axisLeft(y)
.tickFormat(d3.format(".2s"));
var svg = d3.select("#ChartInfo").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 + ")");
function draw(dataInput) {
console.log(dataInput);
var data = JSON.parse(dataInput)
console.log(data);
var keys = Object.keys(data[0]).slice(1);
x0.domain(data.map(function (d) { return d.Date; }));
x1.domain(keys).rangeRound([0, x0.bandwidth()]);
y.domain([0, d3.max(data, function (d) { return d3.max(keys, function (key) { return d[key]; }); })]).nice();
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "start")
.attr("transform", "rotate(45)");
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Population");
var state = svg.selectAll(".state")
.data(data)
.enter().append("g")
.attr("class", "state")
.attr("transform", function (d) { return "translate(" + x0(d.Date) + ",0)"; });
state.selectAll("rect")
.data(function (d) {
return keys.map(function (key) {
return { key: key, value: d[key] };
});
})
.join(
function (enter) {
return enter
.append("rect")
.attr("width", x1.bandwidth())
.attr("x", function (d) { return x1(d.key); })
.attr("y", function (d) { return y(d.value); })
.attr("height", function (d) { return height - y(d.value); })
.style("fill", function (d) { return color(d.key); })
},
function (update) {
return update
.transition()
.duration(750)
.attr("width", x1.bandwidth())
.attr("x", function (d) { return x1(d.key); })
.attr("y", function (d) { return y(d.value); })
.attr("height", function (d) { return height - y(d.value); })
.style("fill", function (d) { return color(d.key); })
},
function (exit) {
return exit
.remove()
});
var legend = svg.selectAll(".legend")
.data(keys)
.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", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function (d) { return d; });
}
Here is a picture of what the chart looks like after I have selected both options
EDIT:
I am running it in Razor Pages but as far as I can see it's a problem with my ability to do the whole join, merge, exit correctly :)
Link to fiddle https://jsfiddle.net/r40vLscd/
I have found multiple errors in your code:
New X Axis and Y Axis are created each time you draw another chart. The old ones should be removed before
Merge does not work properly. Instead, I redraw rectangles for each group.
See the fixed chart in the snippet:
const margin = { top: 20, right: 20, bottom: 60, left: 40 };
const width = 1060 - margin.left - margin.right;
const height = 500 - margin.top - margin.bottom;
const x0 = d3.scaleBand()
.rangeRound([0, width])
.padding(0.2)
const x1 = d3.scaleBand();
const y = d3.scaleLinear()
.range([height, 0]);
const color = d3.scaleOrdinal()
.range(["#4472c4", "#ed7d31", "#a5a5a5", "#ffc000", "#a05d56", "#d0743c", "#ff8c00"]);
const xAxis = d3.axisBottom(x0);
const yAxis = d3.axisLeft(y)
.tickFormat(d3.format(".2s"));
const svg = d3.select("#ChartInfo")
.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 + ")");
function draw(data) {
const keys = Object.keys(data[0]).slice(1);
x0.domain(data.map(function (d) { return d.Date; }));
x1.domain(keys).rangeRound([0, x0.bandwidth()]);
y.domain([0, d3.max(data, function (d) {
return d3.max(keys, function (key) {
return d[key];
});
})])
.nice();
svg.selectAll(".x_axis,.y_axis").remove();
svg.append("g")
.attr("class", "x_axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "start")
.attr("transform", "rotate(45)");
svg.append("g")
.attr("class", "y_axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Population");
const state = svg.selectAll(".state")
.data(data, d => d.Date);
const newState = state.enter()
.append("g")
.attr("class", "state");
newState.merge(state)
.attr("transform", function (d) { return "translate(" + x0(d.Date) + ",0)"; })
.each(function(d) {
const g = d3.select(this);
g.selectAll('rect').remove();
Object.keys(d)
.filter(key => key !== 'Date')
.forEach(key => {
g.append("rect")
.attr("width", x1.bandwidth())
.attr("x", x1(key))
.attr("y", y(d[key]))
.attr("height", height - y(d[key]))
.style("fill", color(key))
});
});
state.exit().remove();
var legend = svg.selectAll(".legend")
.data(keys)
.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", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function (d) { return d; });
};
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Page Title</title>
</head>
<body>
<script src="https://d3js.org/d3.v6.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.11.4/jquery-ui.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<div class="row">
<div class="col-xl-2">
<div class="container-fluid p-3 my-3">
<h1>Select Queue</h1>
<button onclick="ChartOne()" id="myButton">ChartOne</button>
<button onclick="ChartTwo()" id="myButtonTwo">ChartTwo</button>
</div>
</div>
<div class="col">
<div class="container-fluid p-3 my-3">
<h1>Charts</h1>
<p>Page to test charts.</p>
<h2>Chart</h2>
<div id="ChartInfo">
</div>
</div>
</div>
</div>
<script type="text/javascript">
function ChartOne() {
testAjax(function (output) {
});
}
</script>
<script type="text/javascript">
function testAjax() {
var A = [{"Date":"2021-02-01","CallTypeOne":116,"CallTypeTwo":114,"CallTypeThree":2,"CallTypeFour":0},{"Date":"2021-02-02","CallTypeOne":43,"CallTypeTwo":40,"CallTypeThree":1,"CallTypeFour":2},{"Date":"2021-02-03","CallTypeOne":270,"CallTypeTwo":221,"CallTypeThree":26,"CallTypeFour":23}];
draw(A);
}
</script>
<script type="text/javascript">
function ChartTwo() {
testTwoAjax(function (output) {
});
}
</script>
<script type="text/javascript">
function testTwoAjax() {
var A =[{"Date":"2021-02-01","CallTypeOne":69,"CallTypeTwo":67,"CallTypeThree":2,"CallTypeFour":0},{"Date":"2021-02-02","CallTypeOne":69,"CallTypeTwo":69,"CallTypeThree":0,"CallTypeFour":0}];
draw(A);
}
</script>
</body>
</html>
https://i.stack.imgur.com/90isf.png
When visualizing the data, I got some troubles of X axis of line chart.
As picture shows, line of X axis disappeared, in fact, it only shows the scale of X axis. I wonder why this condition happens. Was it due to the illegal coding or because of the size of SVG was not big enough to contain the whole picture?
var margin = { top: 30, right: 120, bottom: 30, left: 50 },
width = 960 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom,
tooltip = { width: 100, height: 100, x: 10, y: -30 };
var parseDate = d3.timeParse("%m-%d-%Y"),
bisectDate = d3.bisector(function(d) { return d.date; }).left,
formatValue = d3.format(","),
dateFormatter = d3.timeParse("%m-%d-%Y");
var x = d3.scaleTime()
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var xAxis = d3.axisBottom()
var yAxis = d3.axisLeft()
.scale(y);
var line = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.likes); });
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 + ")");
d3.csv("output4.csv",
function(d){
return { date : d.framenum, likes : d.nSNR }
},
function(data) {
x.domain([0, d3.max(data, function(d) { return +d.date; })]);
y.domain([-d3.max(data, function(d) { return +d.likes; }), d3.max(data, function(d) { return +d.likes; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.style("font-size","17px")
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Number of Likes");
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
var focus = svg.append("g")
.attr("class", "focus")
.style("display", "none");
focus.append("circle")
.attr("r", 5);
focus.append("rect")
.attr("class", "tooltip")
.attr("width", 100)
.attr("height", 50)
.attr("x", 10)
.attr("y", -22)
.attr("rx", 4)
.attr("ry", 4);
focus.append("text")
.attr("class", "tooltip-date")
.attr("x", 18)
.attr("y", -2);
focus.append("text")
.attr("x", 18)
.attr("y", 18)
.text("Likes:");
focus.append("text")
.attr("class", "tooltip-likes")
.attr("x", 60)
.attr("y", 18);
svg.append("rect")
.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", mousemove);
function mousemove() {
var x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.date > d1.date - x0 ? d1 : d0;
focus.attr("transform", "translate(" + x(d.date) + "," + y(d.likes) + ")");
focus.select(".tooltip-date").text(dateFormatter(d.date));
focus.select(".tooltip-likes").text(formatValue(d.likes));
}
});
The problem:
The number of groups is dynamic, and vertical line (separator) needs dynamic padding. Group width im getting with x0.rangeBand(). Is there any way to get width of space beetween two groups dynamically?
Peace of code:
.....
var slice = svg.selectAll(".chart")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function (d) {
return "translate(" + x0(d.category) + ",0)";
});
// Create rectangles of the correct width
slice.selectAll("rect")
.data(function (d) {
return d.values;
})
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function (d) {
return x1(d.rate);
})
.style("fill", function (d) {
return color(d.rate)
})
.attr("y", function (d) {
return y(0);
})
.attr("height", function (d) {
return height - y(0);
})
.on("mouseover", function (d) {
d3.select(this).style("fill", d3.rgb(color(d.rate)).darker(2));
tip.show(d);
})
.on("mouseout", function (d) {
tip.hide
d3.select(this).style("fill", color(d.rate));
})
slice.append("line")
.attr("class", "blabla")
.attr("x1", x0.rangeBand()+20)
.attr("x2", x0.rangeBand()+20)
.attr("y1", 0)
.attr("y2", height + margin.top + margin.bottom)
.style("stroke-width", 1)
.style("stroke", "#000");
.....
This is how it looks with few groups
This is how it looks with many groups
Because I see no reason why to stick to d3v3 and I can't find the d3v3 documentation easily for ordinal scales here is a d3v5 version of the code with correct placement of the vertical bars. You have to use the bandwidth and the step to calculate the position.
It is an adaptation of the example in your other question : https://bl.ocks.org/bricedev/0d95074b6d83a77dc3ad
I doubled the number of groups and it looks nice.
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x0 = d3.scaleBand()
.rangeRound([0, width])
.paddingInner(0.1);
var x1 = d3.scaleBand();
var y = d3.scaleLinear()
.range([height, 0]);
var xAxis = d3.axisBottom()
.scale(x0)
.tickSize(0);
var yAxis = d3.axisLeft()
.scale(y);
var color = d3.scaleOrdinal()
.range(["#ca0020","#f4a582","#d5d5d5","#92c5de","#0571b0"]);
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 + ")");
d3.json("barchart.json", {credentials: 'same-origin'}).then(function(data) {
var categoriesNames = data.map(function(d) { return d.categorie; });
var rateNames = data[0].values.map(function(d) { return d.rate; });
x0.domain(categoriesNames);
x1.domain(rateNames).range([0, x0.bandwidth()]);
y.domain([0, d3.max(data, function(categorie) { return d3.max(categorie.values, function(d) { return d.value; }); })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.style('opacity','0')
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.style('font-weight','bold')
.text("Value");
svg.select('.y').transition().duration(500).delay(1300).style('opacity','1');
var slice = svg.selectAll(".slice")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform",function(d) { return "translate(" + x0(d.categorie) + ",0)"; });
slice.selectAll("rect")
.data(function(d) { return d.values; })
.enter().append("rect")
.attr("width", x1.bandwidth())
.attr("x", function(d) { return x1(d.rate); })
.style("fill", function(d) { return color(d.rate) })
.attr("y", function(d) { return y(0); })
.attr("height", function(d) { return height - y(0); })
.on("mouseover", function(d) {
d3.select(this).style("fill", d3.rgb(color(d.rate)).darker(2));
})
.on("mouseout", function(d) {
d3.select(this).style("fill", color(d.rate));
});
slice.selectAll("rect")
.transition()
.delay(function (d) {return Math.random()*1000;})
.duration(1000)
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); });
slice.append("line")
.attr("class", "blabla")
.attr("x1", (x0.step() - x0.bandwidth())*0.5 + x0.bandwidth())
.attr("x2", (x0.step() - x0.bandwidth())*0.5 + x0.bandwidth())
.attr("y1", 0)
.attr("y2", height + margin.top + margin.bottom)
.style("stroke-width", 1)
.style("stroke", "#000");
//Legend
var legend = svg.selectAll(".legend")
.data(data[0].values.map(function(d) { return d.rate; }).reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d,i) { return "translate(0," + i * 20 + ")"; })
.style("opacity","0");
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d) { return color(d); });
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) {return d; });
legend.transition().duration(500).delay(function(d,i){ return 1300 + 100 * i; }).style("opacity","1");
});
So the trick is to recalculate padding self. V3 does not have band.step() function so I added this method to get the middle of two items:
function getMiddle(x0){
var rng = x0.range();
var band = x0.rangeBand();
var padding = 0;
if(rng.length>1){
padding = (rng[1]-rng[0] - band) *0.5;
}
return band + padding;
}
And usage:
slice.append("line")
.attr("x1", getMiddle(x0))
.attr("x2", getMiddle(x0))
.attr("y1", 0)
.attr("y2", height + margin.top + margin.bottom)
.style("stroke-width", 1)
.style("stroke", "#000");
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