skip to Main Content

I am trying to create 3, responsive, bar charts on the same page using the same script with different data.

My problem comes when the window is resized. I keep running into elements that are only updated in the last chart to be rendered.

I apologize for not providing a jsfiddle, but I couldn’t get the fiddle to render my code (fiddle – newbee)

The following snippet of code just produces the axis’. First problem I encounter is with d3.axisBottom(scale) – as an example of what I’m encountering.

Page:

<!DOCTYPE html>
<html lang="en">
<head>
  <title>AgeReport 1.3</title>

    <script src="jquery-1.11.2.js"></script>
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <script 
  src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js">
  </script>`

    <link rel=stylesheet type=text/css 
href="https://cdnjs.cloudflare.com/ajax/libs/twitter-
bootstrap/3.3.5/css/bootstrap.min.css">

   <style>
       body {
           background-color: #e6e6e6;
           font-family: sans-serif;
           font-size: 12px;
       }
        section {
            position: relative;
            padding: 24px;
            height: 100%;
        }
                   .section-body:first-child {
            margin-top: 24px;
        }
                   .col-md-3, .col-lg-3, .col-md-4, .col-lg-4, .col-md-6, 
 .col-lg-6 {
            padding: 0;
        }
       .card {
          position: relative;
          background-color: #F5F5F5;
          color: #212121;
          -webkit-border-radius: 2px;
          -moz-border-radius: 2px;
          border-radius: 2px;
          border: 1px solid #BDBDBD;
          box-shadow: none;
          height: 100%;
        }
        .card-head.card-head-xs {
          line-height: 32px;
          min-height: 36px;
        }
        .card-head {
          position: relative;
          line-height: 40px;
          min-height: 44px;
          vertical-align: middle;
          border-radius: 2px 2px 0 0;
        }
       section {
           background-repeat: no-repeat;
       }
       .row {
           margin-left: 0;
       }
       .col-md-4 {
           padding-left: 5px;
           padding-right: 5px;
       }
       .card-head {
           background-color: #bdbdbd;
       }
       .card-body {
           padding: 10px 10px 10px 10px;
       }

       /*-- Age Chart --*/

       #age-chart-row {
           position: relative;
           height: 345px;
       }
       #age-chart-row .card {
           border-width: 1px 1px 0 1px;
       }
       .chart-container {
           height: 288px;
           border: 1px solid #bcbcbc;
           background-color: white;
       }
       .age-chart-canvas {
           position: absolute;
           width: 100%;
            height: 100%;
       }
        .axis--y .tick line {
            stroke: lightgray;
        }

    </style>

    <script type="text/javascript">

        var ageDistributionData = {....}'

        $(document).ready(function() {

            var vData = ageDistributionData;
            var dData = ageDistributionData;
            var mData = ageDistributionData;

            var vChart = new AgeChart("#vChart", vData);
            var dChart = new AgeChart("#dChart", dData);
            var mChart = new AgeChart("#mChart", mData);

        });

    </script>
</head>

<body>
    <section>
        <div class="section-body">
            <div id="age-chart-row" class="row">
                <div class="col-md-4">
                    <div class="card">
                        <div class="card-head card-head-xs"></div>
                        <div class="card-body">
                            <div id="vChart" class="chart-container"></div>
                        </div>
                    </div>
                </div>
                <div class="col-md-4">
                    <div class="card">
                        <div class="card-head card-head-xs"></div>
                        <div class="card-body">
                            <div id="dChart" class="chart-container"></div>
                        </div>
                    </div>
                </div>
                <div class="col-md-4">
                    <div class="card">
                        <div class="card-head card-head-xs"></div>
                        <div class="card-body">
                            <div id="mChart" class="chart-container"></div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </section>
</body>
</html>

Chart script:

function AgeChart(container, data) {

var containerW, containerH, chartW, chartH, svg, chartG,
    xScale, x, y, gAxis, xAxis, yAxis, xBottom, bars;
var margin = {top: 20, right: 10, bottom: 50, left: 60};

//-- axis labels -> will need translating --
var yLabelStr = "Anzahl",
    xLabelStr = "Alter";

// parse raw data
var agedData = parseAgedData(data),
    sizeMax = data.AgeDistribution.DataOwnerSizeMax,
    countMax = 0;
    for (var i = 0; i < agedData.length; i++) {
        if(agedData[i].doCount > countMax) {
            countMax = agedData[i].doCount;
        }
    };

// init scales
var xScale = d3.scaleBand()
    .paddingInner(0.16)
    .paddingOuter(0.1);
xScale.domain(agedData.map(function (d) {
    return d.label;
}));
x = d3.scaleBand()
    .paddingInner(0.16)
    .paddingOuter(0.1);
x.domain(agedData.map(function (d, i) {
    return i + 1;
}));
y = d3.scaleLinear();
y.domain([0, countMax]);

// init containers
svg = d3.select(container)
    .append("svg")
    .attr("id", container.substring(1) + "Svg")
    .classed("age-chart-canvas", true);
chartG = svg.append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

// init axis lines
yAxis = chartG.append("g")
    .attr("class", "axis axis--y");
gAxis = chartG.append("g")
    .attr("class", "axis axis--x");
xAxis = gAxis.append("line")
    .style("stroke", "black")
    .attr("x1", 0)
    .attr("x2", 0)
    .classed("x-domain", true);

// render chart
render();

function render() {

    updateDimensions();

    // set tick lines
    yAxis.call(d3.axisLeft(y))
        .append("text")
        .attr("transform", "rotate(-90)")
        .attr("y", 6)
        .attr("dy", "0.71em")
        .attr("text-anchor", "end")
        .text(function(d) {
            return d;
        });
    d3.selectAll("g.axis--y g.tick line")
        .attr("x1", function(d) {
            return chartW;
        });
    gAxis.attr("transform", "translate(0," + chartH + ")")
        .call(new d3.axisBottom(xScale));
    gAxis.selectAll(".domain").remove();
    d3.selectAll(".x-domain")
        .attr("x2", function(d) {
            return chartW;
        });

    d3.selectAll("g.axis--x g.tick line")
        .attr("y2", function(d, i) {
            if(i%2 == 0)
                return 6;
            else
                return 16;
        })
        .style("fill", "#bdbdbd");
    d3.selectAll("g.axis--x g.tick text")
        .attr("y", function(d, i) {
            if(i%2 == 0)
                return 9;
            else
                return 20;
        });

};

function updateDimensions() {
    containerW = getDimension(container, "width");
    containerH = getDimension(container, "height");
    chartW = containerW - margin.left - margin.right;
    chartH = containerH - margin.top - margin.bottom;

    // set axis scales
    xScale.rangeRound([0, chartW]);
    x.rangeRound([0, chartW]);
    y.rangeRound([chartH, 0]);
};

d3.select(window).on("resize", render);

};

Thanks!

2

Answers


  1. Chosen as BEST ANSWER

    I seem to have found a solution. After boning up on Javascript objects, I re-wrote my code to create and return a chart object that encapsulates the chart properties and methods. I can then call the render method in multiple chart objects to update them individually.

    the new chart script:

    function AgeChart(chartContainer, data) {
    
        var chartObj = {
    
        // public properties
        xAxisLabel: "Alter",
        yAxisLabel: "Anzahl",
        margin: {top: 20, right: 10, bottom: 50, left: 60},
        width: 0,
        height: 0,
        dummyData: 0,
        sizeMax: 0,
        countMax: 0,
    
        // private properties
        $chartGroup: 0,
        $container: "",
        $d: 0,
        $svg: 0,
        $x: d3.scaleBand()
            .paddingInner(0.16)
            .paddingOuter(0.1),
        $xscale: d3.scaleBand()
            .paddingInner(0.16)
            .paddingOuter(0.1),
        $y: d3.scaleLinear(),
        $xAxis: 0,
        $yAxis: 0,
    
        // property sets
        get data() { return this.$d; },
        set data(d) {
            this.$d = d;
            this.dummyData = parseAgedData(d);
            this.sizeMax = d.AgeDistribution.DataOwnerSizeMax;
            for (var i = 0; i < this.dummyData.length; i++) {
                if(this.dummyData[i].doCount > this.countMax) {
                    this.countMax = chartObj.dummyData[i].doCount;
                }
            };
            this.$xscale.domain(this.dummyData.map(function(d) {
                return d.label;
            }));
            this.$x.domain(this.dummyData.map(function(d,i) {
                return i + 1;
            }))
            this.$y.domain([0, this.countMax]);
        },
        get container() { return this.$container },
        set container(c) {
            this.$container = c;
            updateDimensions();
            this.$svg = d3.select(this.container)
                .append("svg")
                .attr("id", chartObj.container.substring(1) + "Svg")
                .classed("age-chart-canvas", true);
            this.$chartGroup = this.$svg.append("g")
                .attr("transform", "translate(" + this.margin.left + "," + this.margin.top + ")");
            this.$yAxis = this.$chartGroup.append("g")
                .attr("class", "axis axis--y");
            this.$xAxis = this.$chartGroup.append("g")
                .attr("class", "axis axis--x")
                .attr("transform", "translate(0," + this.height + ")");
        },
    
        // methods
        render: function() {
    
            updateDimensions();
    
            this.$xscale.rangeRound([0, this.width]);
            this.$x.rangeRound([0, this.width]);
            this.$y.rangeRound([this.height, 0]);
            this.$yAxis.call(d3.axisLeft(this.$y))
                .append("text")
                .attr("transform", "rotate(-90)")
                .attr("y", 6)
                .attr("dy", "0.71em")
                .attr("text-anchor", "end")
                .text(function(d) {
                    return d;
                });
            var chartW = this.width;
            d3.selectAll("g.axis--y g.tick line")
                .attr("x1", function(d) {
                    return chartW;
                });
            this.$xAxis.call(d3.axisBottom(this.$xscale));
            d3.selectAll("g.axis--x g.tick line")
                .attr("y2", function(d, i) {
                    if(i%2 == 0)
                        return 6;
                    else
                        return 16;
                })
                .style("fill", "#bdbdbd");
            d3.selectAll("g.axis--x g.tick text")
                .attr("y", function(d, i) {
                    if(i%2 == 0)
                        return 9;
                    else
                        return 20;
                });
    
           }
        };
    
        chartObj.data = data;
        chartObj.container = chartContainer;
        chartObj.render();
    
        function updateDimensions() {
    
            var containerW = getDimension(chartObj.container, "width"),
            containerH = getDimension(chartObj.container, "height");
            chartObj.width = containerW - chartObj.margin.left -   
            chartObj.margin.right;
            chartObj.height = containerH - chartObj.margin.top -   
            chartObj.margin.bottom;
    
       };
    
        return chartObj;
    
    };
    

    then on my page I call render() when the window is resized and all charts are updated independently...

        <script type="text/javascript">
    
            $(document).ready(function() {
    
                var vData = ageDistributionData;
                var dData = ageDistributionData;
                var mData = ageDistributionData;
    
                var vChart = AgeChart("#vChart", vData);
                var dChart = new AgeChart("#dChart", dData);
                var mChart = new AgeChart("#mChart", mData);
    
                window.addEventListener("resize", function() {
                    vChart.render();
                    dChart.render();
                    mChart.render();
                })
    
            });
    
        </script>
    

    Any tips or suggestions are most welcome.


  2. I was also stuck on this same issue. I know you found a solution but I thought I’d offer what I found in case it helps someone else who ends up at this page, since this question more accurately described my problem than anything else I’ve found.

    My solution finally came from the following Stack Overflow post. The answer there explains that you need to add a namespace to the listener, otherwise the existing listener will be removed when a new one is added.

    How to have multiple d3 window resize events

    For this example, I had to change this code:

        d3.select(window).on("resize", render);
    

    to

        var resizeId = "resize." + scope.chartId
        d3.select(window).on(resizeId, render);
    

    where scope.chartId was a unique id for each chart so that the listener was also unique and would not be removed when a new chart was added.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search