user10014185
user10014185

Reputation:

d3.js horizontal Bar chart issue

I am new to d3 library and trying some charts using d3 library. I am able to made horizontal bar chart using d3.js library but I am stuck at some issues .

Currently I am having trouble as last value in x axis is getting cropped. I don't want to add right margin in my chart. And I want to show last label with tick on x axis.

I have highlighted the cropped value in the screenshot below:

Bar chart Screenshot

For debugging , refer below code :

const margin = {
    top: 30,
    right: 0,
    bottom: 10,
    left: 100
};
const width= 1180;
const data = [{
        name: 'A',
        value: 2
    },
    {
        name: 'B',
        value: 20
    },
    {
        name: 'C',
        value: 15
    },
    {
        name: 'D',
        value: 8
    },
    {
        name: 'E',
        value: 1
    }
];
const barHeight = 20;
const yMax = d3.max(data, d => d.value);

const height =
    Math.ceil((data.length + 0.1) * barHeight) + margin.top + margin.bottom;
const x = d3
    .scaleLinear()
    .domain([0, yMax < 10 ? 10 : yMax])
    .range([margin.left, width - margin.right])
    .nice();
const y = d3
    .scaleBand()
    .domain(d3.range(data.length))
    .range([margin.top, height - margin.bottom])
    .padding(0.1);

const format = x.tickFormat(10, data.format);
const svg = d3.select('body').append('svg').attr('viewBox', [0, 0, width, height]);
const xAxis = g =>
    g
    .attr('class', 'chartxAxisText')
    .attr('transform', `translate(0,${margin.top})`)
    .call(d3.axisTop(x))

    .call(g => g.select('.domain').remove());
const yAxis = g =>
    g
    .attr('class', 'chartyAxisText')
    .attr('transform', `translate(${margin.left},0)`)
    .call(
        d3
        .axisLeft(y)
        .tickFormat(i => data[i].name)
        .tickSizeOuter(0)
    );

svg
    .append('g')
    .selectAll('rect')
    .data(data)
    .enter().append('rect')
    .attr('x', x(0))
    .attr('y', (d, i) => y(i))
    .attr('width', d => x(d.value) - x(0))
    .attr('height', y.bandwidth())
    .attr('class', 'chartbar')

svg
    .append('g')
    .attr('class', 'chartbarTextWrap')
    .selectAll('text')
    .data(data)
    .enter().append('text')
    .attr('x', d => x(d.value) - 4)
    .attr('y', (d, i) => y(i) + y.bandwidth() / 2)
    .attr('dy', '0.35em')
    .text(d => format(d.value))
    .attr('class', 'chartbarText');

svg.append('g').call(xAxis);

svg.append('g').call(yAxis);
.chartbarTextWrap {
    font-size: 14px;
    font-weight: 700;
    letter-spacing: 1px;
    fill: #fff;
    text-anchor: end;
    pointer-events: none;
}
.chartbar {
    fill: red;
}
.chartxAxisText, .chartyAxisText {
    font-family: "Roboto", sans-serif;
    font-size: 16px;
    font-weight: 400;
    letter-spacing: 1px;
    color: #424242;
}
.chartxAxisText, .chartyAxisText {
   
    font-size: 16px;
    font-weight: 400;
    color: #424242;
}
svg {
  width:1180px
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

<html>
<body></body>
</html>

Upvotes: 1

Views: 406

Answers (1)

Mehdi
Mehdi

Reputation: 7403

Adding a small margin at the right of the chart would really be the most straightforward solution.

If you really do not want it, you may align the x axis text labels to the right:

.chartxAxisText {
    text-anchor: end;
}

This is demonstrated in the snipped below.

const margin = {
    top: 30,
    right: 0,
    bottom: 10,
    left: 100
};
const width= 1180;
const data = [{
        name: 'A',
        value: 2
    },
    {
        name: 'B',
        value: 20
    },
    {
        name: 'C',
        value: 15
    },
    {
        name: 'D',
        value: 8
    },
    {
        name: 'E',
        value: 1
    }
];
const barHeight = 20;
const yMax = d3.max(data, d => d.value);

const height =
    Math.ceil((data.length + 0.1) * barHeight) + margin.top + margin.bottom;
const x = d3
    .scaleLinear()
    .domain([0, yMax < 10 ? 10 : yMax])
    .range([margin.left, width - margin.right]);
const y = d3
    .scaleBand()
    .domain(d3.range(data.length))
    .range([margin.top, height - margin.bottom])
    .padding(0.1);

const format = x.tickFormat(10, data.format);
const svg = d3.select('body').append('svg').attr('viewBox', [0, 0, width, height]);
const xAxis = g =>
    g
    .attr('class', 'chartxAxisText')
    .attr('transform', `translate(0,${margin.top})`)
    .call(d3.axisTop(x))

    .call(g => g.select('.domain').remove());
const yAxis = g =>
    g
    .attr('class', 'chartyAxisText')
    .attr('transform', `translate(${margin.left},0)`)
    .call(
        d3
        .axisLeft(y)
        .tickFormat(i => data[i].name)
        .tickSizeOuter(0)
    );

svg
    .append('g')
    .selectAll('rect')
    .data(data)
    .enter().append('rect')
    .attr('x', x(0))
    .attr('y', (d, i) => y(i))
    .attr('width', d => x(d.value) - x(0))
    .attr('height', y.bandwidth())
    .attr('class', 'chartbar')

svg
    .append('g')
    .attr('class', 'chartbarTextWrap')
    .selectAll('text')
    .data(data)
    .enter().append('text')
    .attr('x', d => x(d.value) - 4)
    .attr('y', (d, i) => y(i) + y.bandwidth() / 2)
    .attr('dy', '0.35em')
    .text(d => format(d.value))
    .attr('class', 'chartbarText');

svg.append('g').call(xAxis);

svg.append('g').call(yAxis);
.chartbarTextWrap {
    font-size: 14px;
    font-weight: 700;
    letter-spacing: 1px;
    fill: #fff;
    text-anchor: end;
    pointer-events: none;
}
.chartbar {
    fill: red;
}
.chartxAxisText, .chartyAxisText {
    font-family: "Roboto", sans-serif;
    font-size: 16px;
    font-weight: 400;
    letter-spacing: 1px;
    color: #424242;
}
.chartxAxisText {
    text-anchor: end;
}
svg {
  width:1180px
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

<html>
<body></body>
</html>

Another option is to leave all the labels as they currently are, and only align the last one to the left. This can be done also in CSS, with the following rule:

.chartxAxisText text:last-child {
    text-anchor: end;
}

Upvotes: 1

Related Questions