Improve chart loading behaviour and some other chart fixes (#1042)

* show is loading and animate chart display

* let tooltip overflow in more-info

* graph chart tooltip overflow

* style toopltip a bit more like material design guidelines

* smoother tooltip appearing

* timeline transition time

* max-widht -> width
This commit is contained in:
NovapaX 2018-03-29 01:16:25 +02:00 committed by Paulus Schoutsen
parent fb8fb09c73
commit 0790cd1ac9
6 changed files with 109 additions and 30 deletions

View File

@ -11,8 +11,14 @@
paper-card:not([dialog]) .content { paper-card:not([dialog]) .content {
padding: 0 16px 16px; padding: 0 16px 16px;
} }
paper-card[dialog] {
padding-top: 16px;
background-color: transparent;
}
paper-card { paper-card {
width: 100%; width: 100%;
/* prevent new stacking context, chart tooltip needs to overflow */
position: static;
} }
.header { .header {
@apply --paper-font-headline; @apply --paper-font-headline;

View File

@ -5,10 +5,14 @@
<dom-module id="ha-chart-base"> <dom-module id="ha-chart-base">
<template> <template>
<style> <style>
:host {
display: block;
}
.chartHeader { .chartHeader {
display: flex;
padding: 6px 0 0 0; padding: 6px 0 0 0;
width: 100% width: 100%;
display: flex;
flex-direction: row;
} }
.chartHeader > div { .chartHeader > div {
vertical-align: top; vertical-align: top;
@ -16,9 +20,12 @@
} }
.chartHeader > div.chartTitle { .chartHeader > div.chartTitle {
padding-top: 8px; padding-top: 8px;
flex: 0 0 0;
max-width: 30%;
} }
.chartHeader > div.chartLegend { .chartHeader > div.chartLegend {
flex: auto; flex: 1 1 1;
min-width: 70%;
} }
:root{ :root{
user-select: none; user-select: none;
@ -27,15 +34,17 @@
-ms-user-select: none; -ms-user-select: none;
} }
.chartTooltip { .chartTooltip {
font-size: 90%;
opacity: 1; opacity: 1;
position: absolute; position: absolute;
background: rgba(0, 0, 0, .7); background: rgba(80, 80, 80, .9);
color: white; color: white;
border-radius: 3px; border-radius: 3px;
pointer-events: none; pointer-events: none;
transform: translate(-50%, 0); transform: translate(-50%, 5px);
z-index: 1000; z-index: 1000;
width: 200px; width: 200px;
transition: opacity 0.15s ease-in-out;
} }
.chartLegend ul, .chartLegend ul,
.chartTooltip ul { .chartTooltip ul {
@ -54,17 +63,17 @@
.chartLegend li { .chartLegend li {
display: inline-block; display: inline-block;
padding: 0 5px; padding: 0 5px;
max-width: 49%; width: 49%;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
box-sizing: border-box; box-sizing: border-box;
} }
.chartLegend li:nth-child(odd):last-of-type { .chartLegend li:nth-child(odd):last-of-type {
/* Make last item take full width if it is add-numbered. */ /* Make last item take full width if it is odd-numbered. */
max-width: 100%; width: 100%;
} }
.chartLegend li.hidden { .chartLegend li[data-hidden] {
text-decoration: line-through; text-decoration: line-through;
} }
.chartLegend em, .chartLegend em,
@ -85,7 +94,7 @@
<div class="chartLegend"> <div class="chartLegend">
<ul> <ul>
<template is="dom-repeat" items="[[metas]]"> <template is="dom-repeat" items="[[metas]]">
<li on-click="_legendClick" class$="[[item.hidden]]"> <li on-click="_legendClick" data-hidden$="[[item.hidden]]">
<em style$="background-color:[[item.bgColor]]"></em> <em style$="background-color:[[item.bgColor]]"></em>
[[item.label]] [[item.label]]
</li> </li>
@ -136,6 +145,12 @@
data: Object, data: Object,
identifier: String, identifier: String,
isZoomable: Boolean, isZoomable: Boolean,
rendered: {
type: Boolean,
notify: true,
value: false,
readOnly: true,
},
}; };
} }
@ -153,9 +168,7 @@
xPadding: '0', xPadding: '0',
yPadding: '0' yPadding: '0'
}); });
if (!this._chart) { this.onPropsChange();
requestAnimationFrame(() => requestAnimationFrame(() => this.onPropsChange()));
}
this._resizeListener = () => { this._resizeListener = () => {
this._debouncer = Polymer.Debouncer.debounce( this._debouncer = Polymer.Debouncer.debounce(
this._debouncer, this._debouncer,
@ -271,8 +284,9 @@
_legendClick(event) { _legendClick(event) {
event = event || window.event; event = event || window.event;
event.stopPropagation();
let target = event.target || event.srcElement; let target = event.target || event.srcElement;
while (target.nodeName !== 'LI') { while (target.nodeName !== 'LI') { // user clicked child, find parent LI
target = target.parentElement; target = target.parentElement;
} }
const index = event.model.itemsIndex; const index = event.model.itemsIndex;
@ -345,6 +359,9 @@
return; return;
} }
this._customTooltips({ opacity: 0 }); this._customTooltips({ opacity: 0 });
const plugins = [
{ afterRender: () => this._setRendered(true) }
];
let options = { let options = {
responsive: true, responsive: true,
maintainAspectRatio: false, maintainAspectRatio: false,
@ -419,7 +436,8 @@
const chartData = { const chartData = {
type: this.data.type, type: this.data.type,
data: this.data.data, data: this.data.data,
options: options options: options,
plugins: plugins,
}; };
// Async resize after dom update // Async resize after dom update
this._chart = new Chart(ctx, chartData); this._chart = new Chart(ctx, chartData);

View File

@ -4,11 +4,21 @@
<dom-module id='state-history-chart-line'> <dom-module id='state-history-chart-line'>
<template> <template>
<style>
:host {
display: block;
overflow: hidden;
height: 0;
transition: height 0.3s ease-in-out;
}
</style>
<ha-chart-base <ha-chart-base
id='chart'
data="[[chartData]]" data="[[chartData]]"
is-zoomable="[[isZoomable]]" is-zoomable="[[isZoomable]]"
identifier="[[identifier]]"> identifier="[[identifier]]"
</ha-chart-base> rendered="{{rendered}}"
></ha-chart-base>
</template> </template>
</dom-module> </dom-module>
<script> <script>
@ -27,6 +37,11 @@ class StateHistoryChartLine extends Polymer.Element {
}, },
endTime: Object, endTime: Object,
rendered: {
type: Boolean,
value: false,
observer: '_onRenderedChanged'
}
}; };
} }
static get observers() { static get observers() {
@ -43,6 +58,16 @@ class StateHistoryChartLine extends Polymer.Element {
this.drawChart(); this.drawChart();
} }
_onRenderedChanged(rendered) {
if (rendered) this.animateHeight();
}
animateHeight() {
requestAnimationFrame(() => requestAnimationFrame(() => {
this.style.height = this.$.chart.scrollHeight + 'px';
}));
}
drawChart() { drawChart() {
const unit = this.unit; const unit = this.unit;
const deviceStates = this.data; const deviceStates = this.data;

View File

@ -4,7 +4,21 @@
<dom-module id='state-history-chart-timeline'> <dom-module id='state-history-chart-timeline'>
<template> <template>
<ha-chart-base data="[[chartData]]"></ha-chart-base> <style>
:host {
display: block;
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
:host([rendered]) {
opacity: 1;
}
</style>
<ha-chart-base
data="[[chartData]]"
rendered="{{rendered}}"
></ha-chart-base>
</template> </template>
</dom-module> </dom-module>
<script> <script>
@ -23,6 +37,11 @@ class StateHistoryChartTimeline extends Polymer.Element {
}, },
noSingle: Boolean, noSingle: Boolean,
endTime: Date, endTime: Date,
rendered: {
type: Boolean,
value: false,
reflectToAttribute: true,
}
}; };
} }

View File

@ -9,11 +9,21 @@
<style> <style>
:host { :host {
display: block; display: block;
/* height of single timeline chart = 58px */
min-height: 58px;
}
.info {
text-align: center;
line-height: 58px;
color: var(--secondary-text-color);
} }
</style> </style>
<template is='dom-if' class='info' if='[[_computeIsLoading(isLoadingData)]]'>
<div class='info'>Loading state history...</div>
</template>
<template is='dom-if' if='[[_computeIsEmpty(historyData)]]'> <template is='dom-if' class='info' if='[[_computeIsEmpty(isLoadingData, historyData)]]'>
No state history found. <div class='info'>No state history found.</div>
</template> </template>
<template is='dom-if' if='[[historyData.timeline.length]]'> <template is='dom-if' if='[[historyData.timeline.length]]'>
@ -48,10 +58,7 @@ class StateHistoryCharts extends Polymer.Element {
value: null, value: null,
}, },
isLoadingData: { isLoading: Boolean,
type: Boolean,
value: true,
},
endTime: { endTime: {
type: Object, type: Object,
@ -67,12 +74,18 @@ class StateHistoryCharts extends Polymer.Element {
return !noSingle && data && data.length === 1; return !noSingle && data && data.length === 1;
} }
_computeIsEmpty(historyData) { _computeIsEmpty(isLoadingData, historyData) {
return !historyData || !historyData.timeline || !historyData.line || const historyDataEmpty = (!historyData || !historyData.timeline || !historyData.line ||
(historyData.timeline.length === 0 && (historyData.timeline.length === 0 &&
historyData.line.length === 0); historyData.line.length === 0));
return !isLoadingData && historyDataEmpty;
} }
_computeIsLoading(isLoading) {
return isLoading && !this.historyData;
}
_computeEndTime(endTime, upToNow) { _computeEndTime(endTime, upToNow) {
// We don't really care about the value of historyData, but if it change we want to update // We don't really care about the value of historyData, but if it change we want to update
// endTime. // endTime.

View File

@ -27,8 +27,6 @@
} }
state-history-charts { state-history-charts {
position: relative;
z-index: 1;
max-width: 100%; max-width: 100%;
margin-bottom: 16px; margin-bottom: 16px;
} }