0

I'm making a website where people can browse data by region. Basically, it shows a bar chart, and there's a dropdown menu at the top that updates the data based on the selection.

One detail I'm having trouble with: I'd like the area of the chart to be identical regardless of the data shown. However, depending on the magnitude of the data, the area beside the y-axis will be wider or narrower. You can see what I mean in the simplified example below. The second image has more space to the left of the chart to accommodate the larger numbers, which slightly squishes the area of the actual chart. I would like the chart area to be identical between charts.

Chart with narrow border

Chart with wider border

I've looked into the "padding" option, but it just seems to add white space around the chart, which isn't quite what I want. Here's some sample code. Anyone have any ideas?

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
        <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    </head>

    <body>

        <select name="nation_select" id="nation_dropdown" onchange=update_plot()>
            <option value="nation_1">Nation 1</option>
            <option value="nation_2">Nation 2</option>
        </select>
        <div><canvas id="canvas_national_ts" width="1000" height="480"></canvas></div>

        <script>

            // Make plot
            function update_plot() {
                nation_selected = document.getElementById('nation_dropdown').value;

                // Set the data
                var time = [1,2,3,4,5];
                if (nation_selected == 'nation_1') {var data = [1,2,1,3,2];}
                if (nation_selected == 'nation_2') {var data = [1000000,1040000,970000,1200000,900000];}

                // Make a plot
                const ctx_national_ts = document.getElementById('canvas_national_ts');
                if (Chart.getChart(ctx_national_ts)) {
                    Chart.getChart(ctx_national_ts).destroy();
                };
                var chart_national_ts = new Chart(ctx_national_ts, {
                    type: 'bar',
                    data: {
                        labels: time,
                        datasets: [{
                            data: data,
                        }]
                    }
                })
            }

            // Initial plot
            update_plot();

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

2 Answers 2

1

You could use the the ticks.callback method in the axis configuration to fill the tick marks with leading spaces when the numbers are low. For example:

options: {
  scales: {
    y: {
      ticks: {
        callback: function(value, index, ticks) {
            wdth = 12;
            lft = value == 0 ? wdth - 1 : wdth - Math.ceil(Math.log10(Math.abs(value)));
            spacer = '\u2007';
            return spacer.repeat(lft) + value;
        }
      }
    }
  }
}

In this example i use Math.ceil(Math.log10(Math.abs(value))) to compute the number of digits of the value. I specify the default width (in this example wdth = 12). Now I can add the required spaces (I use the figure space \u2007) in front of the value to get a fixed width.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks, this is just what I need. I adjusted it very slightly, to account for two possibilities: First, when the number is a factor of 10, the digits calculation is 1 too small (e.g., the log10 of 1000 is 3), so I updated it based on this answer: stackoverflow.com/questions/14879691/…. Secondly, when my values were fairly small, sometimes the axis would have decimals (e.g., "1.5"), which throws the spacing off. As a somewhat hacky solution, I just didn't add spacing in that situation. Thanks again! This was very helpful.
1

What you can do over here is get the readings of the y-axis labels, divide them iteratively until you get a single digit outcome and then accordingly create a label and the top of chart to show it.

I have modified the JavaScript part of your code

<script>
    function getAutoScaleUnit(maxValue) {
        const units = ['', 'Ten', 'Hundred', 'Thousand', 'Ten Thousand', 'Lakh', 'Million', 'Billion'];
        let scale = 1;
        let unitIndex = 0;

        while (maxValue / scale >= 10 && unitIndex < units.length - 1) {
            scale *= 10;
            unitIndex++;
        }

        return {
            scale,
            label: units[unitIndex] || ''
        };
    }

    function update_plot() {
        const nation = document.getElementById('nation_dropdown').value;
        const time = [1, 2, 3, 4, 5];
        let rawData = [];

        if (nation === 'nation_1') rawData = [1, 2, 1, 3, 2];
        if (nation === 'nation_2') rawData = [1000000, 1040000, 970000, 1200000, 900000];

        const maxVal = Math.max(...rawData);
        const { scale, label } = getAutoScaleUnit(maxVal);
        const scaledData = rawData.map(v => v / scale);

        const ctx = document.getElementById('canvas_national_ts');
        if (Chart.getChart(ctx)) {
            Chart.getChart(ctx).destroy();
        }

        new Chart(ctx, {
            type: 'bar',
            data: {
                labels: time,
                datasets: [{
                    label: `Value (${label || 'Units'})`,
                    data: scaledData,
                    backgroundColor: 'steelblue'
                }]
            },
            options: {
                plugins: {
                    title: {
                        display: true,
                        text: `National Time Series (${label || 'Units'})`,
                        font: {
                            size: 20
                        }
                    }
                },
                scales: {
                    y: {
                        beginAtZero: true,
                        ticks: {
                            callback: function (value) {
                                return value.toFixed(2);
                            }
                        },
                        title: {
                            display: true,
                            text: label || 'Units'
                        }
                    }
                }
            }
        });
    }

    update_plot();
</script>

This will ensure that the effective are of chart remains same.

2 Comments

That's a cool idea. I decided to go with the other answer, since I'd prefer to see the full numbers. but thanks for introducing me to the word "lakh"!
Ohk cool no worries, just did an Indian way of representation by adding lakh over there

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.