I am new with D3 and I am trying to build a grouped bar chart using D3. I need my grid lines on the X-axis to be placed when the one plot ends. Currently it's placed at the middle. Also, currently the grid lines are appearing on top of the plotted graph, how to move the grid lines to behind the graph. In the Y-axis, is it possible to have my ticks start at 1000, 2000, 3000, 4000 instead of the current, which show .5k, 1k etc
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<div id="grouped-chart"></div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
const data = [
{
category: {
model: 'A',
},
type1: '1000',
type2: '2000',
},
{
category: {
model: 'B',
},
type1: '2000',
type2: '3000',
},
{
category: {
model: 'C',
},
type1: '1500',
type2: '4000',
},
];
const margin = { top: 20, right: 30, bottom: 30, left: 60 };
const width = 400 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;
const svg = d3
.select('#grouped-chart')
.append('svg')
.attr('width', width margin.left margin.right)
.attr('height', height margin.top margin.bottom)
.style('background', '#fff');
const g = svg
.append('g')
.attr('transform', 'translate(' margin.left ',' margin.top ')');
const x0 = d3.scaleBand().rangeRound([0, width]).paddingInner(0.1);
const x1 = d3.scaleBand().padding(0.05);
const y = d3.scaleLinear().rangeRound([height, 0]);
const z = d3.scaleOrdinal().range(['#004c6d', '#255e7e', '#3d708f']);
const keys = Object.keys(data[0]).slice(1);
x0.domain(data.map((d) => d.category.model));
x1.domain(keys).rangeRound([0, x0.bandwidth()]);
y.domain([0, 4000]);
const gX = g
.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(0,' height ')')
.call(d3.axisBottom(x0));
const gY = g
.append('g')
.attr('class', 'axis')
.call(d3.axisLeft(y).ticks(null, 's'));
g.append('g')
.attr('class', 'grid')
.attr('transform', `translate(0, ${height})`)
.call(d3.axisBottom().scale(x0).tickSize(-height, 0, 0).tickFormat(''));
g.append('g')
.attr('class', 'grid')
.call(d3.axisLeft().scale(y).tickSize(-width, 0, 0).tickFormat(''));
g.append('g')
.selectAll('g')
.data(data)
.enter()
.append('g')
.attr('transform', (d) => 'translate(' x0(d.category.model) ',0)')
.selectAll('rect')
.data((d) =>
keys.map((key) => {
return { key: key, value: d[key] };
})
)
.enter()
.append('rect')
.attr('x', (d) => x1(d.key))
.attr('y', (d) => y(d.value))
.attr('width', x1.bandwidth())
.attr('height', (d) => height - y(d.value))
.attr('fill', (d) => z(d.key));
</script>
</body>
</html>
CodePudding user response:
For the y-axis tick marks, you can set the number of ticks to 4. Then they get drawn at the thousands: d3.axisLeft(y).ticks(4, 's')
. You could also explicitly set the tickValues.
For the grid lines, you can copy the tick marks created by the axis and adjust their coordinates to go across the whole chart. For the vertical grid lines, you can offset their x coordinates by x0.step() / 2
in order to put them in between the groups (step() docs).
Also, I would recommend gray tick lines instead of black so that they don't draw so much attention. Lastly, the two colors you chose for the bars are a bit hard to distinguish from each other. Tools like Colorgorical and ColorBrewer can be useful for picking colors. Many of the ColorBrewer schemes are provided in d3-scale-chromatic.
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<div id="grouped-chart"></div>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script>
// data
const data = [
{
category: {
model: 'A',
},
type1: '1000',
type2: '2000',
},
{
category: {
model: 'B',
},
type1: '2000',
type2: '3000',
},
{
category: {
model: 'C',
},
type1: '1500',
type2: '4000',
},
];
// set up
const margin = { top: 20, right: 30, bottom: 30, left: 60 };
const width = 400 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;
const svg = d3.select('#grouped-chart')
.append('svg')
.attr('width', width margin.left margin.right)
.attr('height', height margin.top margin.bottom)
.style('background', '#fff');
const g = svg
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
// scales
const keys = Object.keys(data[0]).slice(1);
const x0 = d3.scaleBand()
.domain(data.map((d) => d.category.model))
.rangeRound([0, width])
.paddingInner(0.1);
const x1 = d3.scaleBand()
.domain(keys)
.rangeRound([0, x0.bandwidth()])
.padding(0.05);
const y = d3.scaleLinear()
.domain([0, d3.max(data, d => Math.max( d.type1, d.type2))])
.rangeRound([height, 0]);
const z = d3.scaleOrdinal()
.domain(keys)
// colors picked with colorgorical
.range(["rgb(88,181,225)", "rgb(26,101,135)"]);
// axes
g.append('g')
.attr('class', 'axis')
// move group to bottom of chart
.attr('transform', `translate(0,${height})`)
// add axis
.call(d3.axisBottom(x0).tickSizeOuter(0))
// copy tick marks to create grid lines
.call(g => g.selectAll('.tick > line')
// skip last tick since there's no group after it
.filter((d, i, nodes) => i < nodes.length - 1)
.clone()
.attr('stroke', '#cccccc')
// move to in between the groups
.attr('x1', x0.step() / 2)
.attr('x2', x0.step() / 2)
// make them go from top to bottom of chart
.attr('y1', -height)
.attr('y2', 0));
g.append('g')
.attr('class', 'axis')
// add axis
.call(d3.axisLeft(y).ticks(4, 's'))
// copy tick marks to create grid lines
.call(g => g.selectAll('.tick > line')
// skip first tick since there's already a baseline from the x axis
.filter((d, i) => i > 0)
.clone()
.attr('stroke', '#cccccc')
.attr('x1', 0)
.attr('x2', width));
// bars
g.append('g')
.selectAll('g')
.data(data)
.join('g')
.attr('transform', d => `translate(${x0(d.category.model)},0)`)
.selectAll('rect')
.data(d => keys.map((key) => ({ key: key, value: d[key] })))
.join('rect')
.attr('x', d => x1(d.key))
.attr('y', d => y(d.value))
.attr('width', x1.bandwidth())
.attr('height', d => height - y(d.value))
.attr('fill', d => z(d.key));
</script>
</body>
</html>