D3 v6: How to zoom stacked bar by selected segment to stretch only selected segment and shrink rest segments (and keep initial chart width)?

Viewed 112

I have a problem with my horizontal stacked bar.

The problem: sometimes I got really small values, so one of bands' segment (sub-band ?) has very small width. (Please look below on the picture, rect of each color I call segment):

enter image description here

In some cases I even can't see this segment on the chart. In future I want to show text on each segment (percentage values). But since width of segment can be too small, I need a solution to show text.

Possible solutions: First I thought to set minimal segment width. But it seems the chart will not look OK after this. Also I tried to play around xScale:

const maxX = 1.4; // this value selected experimentally
const xScale = d3.scaleLinear().range([ 0, width ]).domain([ 0, maxX ]);

But for some cases segment still too small (please look at third segment, I marked it with blue color).

enter image description here

So for now I choose solution to zoom stacked bar by selected segment. For example, I want to zoom red segment, all red segments on the chart should be stretched, rest of segments should be shrinked. And total width of bands should be the same (as initial). So width of the chart should be same.

This is my code example: https://jsfiddle.net/8d1te7cb/2/

The problem here that I can't correctly zoom only selected segment. I tried to detect currect group of segments:

const currentNode = d3.select(event.sourceEvent.target).node();
const currentGroup = d3.select(currentNode.parentNode).node();

and then I tried to rescale only segments related to currentGroup:

group.selectAll("rect.segment")
                // .attr("transform", event.transform.toString())
                .attr("x", (d, i, n) => {
                    if (n[i].parentNode === currentGroup) {
                        return xScale(d[0]) + PADDING_TO_SHOW_TEXT;
                    }
                    return xScale2(d[0]) + PADDING_TO_SHOW_TEXT;
                })
                .attr("width", (d, i, n) => {
                    if (n[i].parentNode === currentGroup) {
                        return xScale(d[1]) - xScale(d[0])
                    }

                    return xScale2(d[1]) - xScale2(d[0])
                });

But actually all segments not from currentGroup keep their width and selected group stretched too much, so it moved outside of the axis (and actually after that I got width of the chart changed).

The question: how to fix zoom to allow only selected group stretch and rest of group shrink (and keep initial width of the chart) ?

Extra question: does it exist any another way to show segments proportionally even if for some of them value is too small?

UPDATED: my initial project on typescript, so I forgot to remove some ts hints, this is a bit updated example: https://jsfiddle.net/8d1te7cb/3/ (removed ts from commented code)

1 Answers

Well, finally I implemented what I want.

Result: https://jsfiddle.net/2yqbvrwp/

Details: First of all, I decided to remove second scale, bc I need it only in runtime. So, my zoom function is changed to:

d3.zoom().scaleExtent([ 1, 10 ])
        // I am not sure if I need the line below since code works same without it
        // I think below is default value

        //.translateExtent([ [ 0, 0 ], [ width, height ] ])
        .on("zoom", (event) => {

            const transform = event.transform;

            // the new scale I use for runtime
            // the important part here is clamp method. It prevents from moving
            // segments outside of axis
            const newScaleX = transform.rescaleX(xScale).clamp(true);

            // so I just applied new scale to current axis
            xAxis.scale(newScaleX)
            svg.select("g.axis-x").call(xAxis);

            svg.selectAll("rect.segment")
                .attr("x", (d) => newScaleX(d[0]))
                .attr("width", (d) => newScaleX(d[1]) - newScaleX(d[0]));
        })

    svg.call(zoom);

Also I removed rect transform from css and added margin left in code. But I think I will return it back since native css is faster.

Related