Mowzer Mowzer - 4 years ago 156
Javascript Question

Polymer 1.x: Resizing chart after page load

TLDR; Here is the jsBin.

How do I initially paint my

<google-chart>
geochart element within the boundaries of my layout and prevent it from "spilling over" on first paint?

In my custom element, I use a
google-chart
element which in turn uses a Google GeoChart and API. When it first appears on screen, it looks like this. (Notice how the right side spills outside the edge of the
paper-card
?)

Draws outside boundaries at first



What I actually see on first paint. (Highlighted states were declaratively preset.)

First appearance

After I select a state (like, say, California in this case) the chart redraws itself and winds up properly drawn within the boundaries like I want it.

Redraws inside boundaries after clicking a state



What I expect to see on first paint. And after selecting a state.

Appearance after click and redraw

Recreate the Problem



Follow these steps:


  1. Open this jsBin.

  2. Click the button labeled Reveal Chart

  3. ❌ Notice the chart now first appears larger than the max-width of the
    paper-card
    (100px).

  4. Click on a state.

  5. Notice the chart resizes to fit inside the width of the
    paper-card
    .



The problem is with the "hidden" attribute of the "div" tag



The problem (when implementing the below solution suggested by @ScottMiles) is here:

<div id="chart" hidden> <!-- "hidden" attribute is the problem -->
<x-element color="red"
selected='["Colorado", "South Dakota"]'>
</x-element>
</div>


If you remove the
hidden
attribute, the first paint occurs as desired. However, I need the
hidden
attribute on page load for UX reasons.

I have unsuccessfully tried two solutions so far.

Solution attempt 1: Manually redraw on Attached



attached: function() {
this.$.geochart.drawChart(); // Called manually to handle page resizes
}


Solution attempt 2: IronResizableBehavior



behaviors: [
Polymer.IronResizableBehavior,
],
listeners: {
'iron-resize': '_onIronResize',
},
_onIronResize: function() {
this.$.geochart.drawChart(); // Called manually to handle page resizes
},
attached: function() {
this.async(this.notifyResize, 1);
},


Source



http://jsbin.com/zumireradi/1/edit?html,output

<!DOCTYPE html>

<head>
<meta charset="utf-8">
<base href="https://polygit.org/components/">
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link href="polymer/polymer.html" rel="import">
<link href="google-chart/google-chart.html" rel="import">
<link href="paper-card/paper-card.html" rel="import">
</head>

<body>
<dom-module id="x-element">
<template>
<style>
google-chart {
width: 100%;
}
</style>
<br><br><br><br>
<button on-tap="_show">Show Values</button>
<button on-tap="clearAll">Clear All</button>
<button on-tap="selectAll">Select All</button>
<div>[[selectedString]]</div>
<google-chart id="geochart"
type="geo"
options="[[options]]"
data="[[data]]"
on-google-chart-select="_onGoogleChartSelect">
</google-chart>
</template>
<script>
(function() {
// Monkey patch for google-chart
var gcp = Object.getPrototypeOf(document.createElement('google-chart'));
gcp.drawChart = function() {
if (this._canDraw) {
if (!this.options) {
this.options = {};
}
if (!this._chartObject) {
var chartClass = this._chartTypes[this.type];
if (chartClass) {
this._chartObject = new chartClass(this.$.chartdiv);
google.visualization.events.addOneTimeListener(this._chartObject,
'ready', function() {
this.fire('google-chart-render');
}.bind(this));
google.visualization.events.addListener(this._chartObject,
'select', function() {
this.selection = this._chartObject.getSelection();
this.fire('google-chart-select', { selection: this.selection });
}.bind(this));
if (this._chartObject.setSelection){
this._chartObject.setSelection(this.selection);
}
}
}
if (this._chartObject) {
this._chartObject.draw(this._dataTable, this.options);
} else {
this.$.chartdiv.innerHTML = 'Undefined chart type';
}
}
};
Polymer({
is: 'x-element',
/** /
* Fired when user selects chart item.
*
* @event us-map-select
* @param {object} detail Alpabetized array of selected state names.
/**/
properties: {
items: {
type: Array,
value: function() {
return [ 'Alabama', 'Alaska', 'Arizona', 'Arkansas', 'California', 'Colorado', 'Connecticut', 'Delaware', 'Florida', 'Georgia', 'Hawaii', 'Idaho', 'Illinois', 'Indiana', 'Iowa', 'Kansas', 'Kentucky', 'Louisiana', 'Maine', 'Maryland', 'Massachusetts', 'Michigan', 'Minnesota', 'Mississippi', 'Missouri', 'Montana', 'Nebraska', 'Nevada', 'New Hampshire', 'New Jersey', 'New Mexico', 'New York', 'North Carolina', 'North Dakota', 'Ohio', 'Oklahoma', 'Oregon', 'Pennsylvania', 'Rhode Island', 'South Carolina', 'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont', 'Virginia', 'Washington', 'West Virginia', 'Wisconsin', 'Wyoming', ].sort();
},
},
color: {
type: String,
value: 'blue',
},
options: {
type: Object,
computed: '_computeOptions(color)',
},
selected: {
type: Array,
},
data: {
type: Array,
computed: '_computeData(items, selected.length)'
},
selectedString: {
type: String,
computed: '_computeSelectedString(selected.length)',
},
},
attached: function() {
if (this.selected === undefined) {
this.async(function() {
this.selected = [];
}, 0);
}
},
_computeOptions: function() {
return {
region: 'US',
displayMode: 'regions',
resolution: 'provinces',
legend: 'none',
defaultColor: 'white',
colorAxis: {
colors: ['#E0E0E0', this.color],
minValue: 0,
maxValue: 1,
}
}
},
// On select event, compute 'selected'
_onGoogleChartSelect: function(e) {
var string = e.path[0].textContent.split('Select')[0].trim(), // e.g. 'Ohio'
selected = this.selected, // Array of selected items
index = selected.indexOf(string);
// If 'string' is not in 'selected' array, add it; else delete it
if (index === -1) {
this.push('selected', string);
} else {
this.splice('selected', index, 1);
}
},
_computeSelectedString: function(selectedInfo) {
var selected = this.selected.sort();
this.fire('us-map-select', selected);
return selected.join(', ');
},
// After 'items' populates or 'selected' changes, compute 'data'
_computeData: function(items, selectedInfo) {
var data = [],
selected = this.selected,
i = items.length;
while (i--) {
data.unshift([items[i], selected.indexOf(items[i]) > -1 ? 1 : 0]);
}
data.unshift(['State', 'Select']);
return data;
},
clearAll: function() {
this.set('selected', []);
},
selectAll: function() {
this.set('selected', this.items.slice());
},
_show: function() {
//console.log('items', this.items);
console.log('selected', this.selected);
//console.log('data', this.data);
},
});
})();
</script>
</dom-module>
<br /><br /><br />
<paper-card style="max-width:100px;">
<button onclick="unhide()">Reveal Chart</button>
<div id="chart" hidden>
<x-element color="red"
selected='["Colorado", "South Dakota"]'>
</x-element>
</div>
</paper-card>
<script>
function unhide() {
document.getElementById('chart').hidden = false;
}
</script>
</body>

</html>

Answer Source

Polymer generates as much of your application as possible before first-paint to avoid flashing and FOUC. Some elements like google-chart are unhappy if they are rendered before a paint has occurred already (because some measurements are not yet available).

Something to try is deferring your initial drawing of the google-chart, like this:

  1. Leave selected uninitialized. This will prevent chartData from computing and prevent the chart from drawing prematurely.

    selected: {
      type: Array
     }
    
  2. Initialize selected asynchronously from attached:

    attached: function() {
      if (this.selected === undefined) {
        this.async(function() {
          this.selected = []; 
        }, 0);
      }
    }
    

This a bit of a hack, but calling this.async(.., 0) should get you beyond the first-paint. At that point we give selected a value, which triggers chartData computation and chart drawing.

HTH

Update following OP's published JSbin: forcing the chart to redraw after unhiding it fixes the problem: http://jsbin.com/pekahix/edit?html

document.querySelector('#chart').hidden = false;
document.querySelector('x-element').$.geochart.drawChart();
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download