mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 18:26:35 +00:00
Custom chart legend (#24227)
* Custom chart legend * limit legend label length * fade long legends * tweak margin * new design * fix margins * lighter background * fix variable height charts * tweak legend button * lint * switch to secondary-text-color * Card option to expand legend * Update src/components/chart/ha-chart-base.ts Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com> * pr comments * use ha-assist-chip * pr comments * Apply suggestions from code review Co-authored-by: Bram Kragten <mail@bramkragten.nl> --------- Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com> Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
parent
151a7fbc40
commit
5c933a43b2
@ -1,11 +1,12 @@
|
|||||||
import { consume } from "@lit-labs/context";
|
import { consume } from "@lit-labs/context";
|
||||||
import { ResizeController } from "@lit-labs/observers/resize-controller";
|
import { ResizeController } from "@lit-labs/observers/resize-controller";
|
||||||
import { mdiRestart } from "@mdi/js";
|
import { mdiChevronDown, mdiChevronUp, mdiRestart } from "@mdi/js";
|
||||||
import { differenceInMinutes } from "date-fns";
|
import { differenceInMinutes } from "date-fns";
|
||||||
import type { DataZoomComponentOption } from "echarts/components";
|
import type { DataZoomComponentOption } from "echarts/components";
|
||||||
import type { EChartsType } from "echarts/core";
|
import type { EChartsType } from "echarts/core";
|
||||||
import type {
|
import type {
|
||||||
ECElementEvent,
|
ECElementEvent,
|
||||||
|
LegendComponentOption,
|
||||||
XAXisOption,
|
XAXisOption,
|
||||||
YAXisOption,
|
YAXisOption,
|
||||||
} from "echarts/types/dist/shared";
|
} from "echarts/types/dist/shared";
|
||||||
@ -25,8 +26,11 @@ import { isMac } from "../../util/is_mac";
|
|||||||
import "../ha-icon-button";
|
import "../ha-icon-button";
|
||||||
import { formatTimeLabel } from "./axis-label";
|
import { formatTimeLabel } from "./axis-label";
|
||||||
import { ensureArray } from "../../common/array/ensure-array";
|
import { ensureArray } from "../../common/array/ensure-array";
|
||||||
|
import "../chips/ha-assist-chip";
|
||||||
|
|
||||||
export const MIN_TIME_BETWEEN_UPDATES = 60 * 5 * 1000;
|
export const MIN_TIME_BETWEEN_UPDATES = 60 * 5 * 1000;
|
||||||
|
const LEGEND_OVERFLOW_LIMIT = 10;
|
||||||
|
const LEGEND_OVERFLOW_LIMIT_MOBILE = 6;
|
||||||
|
|
||||||
@customElement("ha-chart-base")
|
@customElement("ha-chart-base")
|
||||||
export class HaChartBase extends LitElement {
|
export class HaChartBase extends LitElement {
|
||||||
@ -40,8 +44,8 @@ export class HaChartBase extends LitElement {
|
|||||||
|
|
||||||
@property({ type: String }) public height?: string;
|
@property({ type: String }) public height?: string;
|
||||||
|
|
||||||
@property({ attribute: "external-hidden", type: Boolean })
|
@property({ attribute: "expand-legend", type: Boolean })
|
||||||
public externalHidden = false;
|
public expandLegend?: boolean;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
@consume({ context: themesContext, subscribe: true })
|
@consume({ context: themesContext, subscribe: true })
|
||||||
@ -53,6 +57,8 @@ export class HaChartBase extends LitElement {
|
|||||||
|
|
||||||
@state() private _minutesDifference = 24 * 60;
|
@state() private _minutesDifference = 24 * 60;
|
||||||
|
|
||||||
|
@state() private _hiddenDatasets = new Set<string>();
|
||||||
|
|
||||||
private _modifierPressed = false;
|
private _modifierPressed = false;
|
||||||
|
|
||||||
private _isTouchDevice = "ontouchstart" in window;
|
private _isTouchDevice = "ontouchstart" in window;
|
||||||
@ -135,8 +141,8 @@ export class HaChartBase extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let chartOptions: ECOption = {};
|
let chartOptions: ECOption = {};
|
||||||
if (changedProps.has("data")) {
|
if (changedProps.has("data") || changedProps.has("_hiddenDatasets")) {
|
||||||
chartOptions.series = this.data;
|
chartOptions.series = this._getSeries();
|
||||||
}
|
}
|
||||||
if (changedProps.has("options")) {
|
if (changedProps.has("options")) {
|
||||||
chartOptions = { ...chartOptions, ...this._createOptions() };
|
chartOptions = { ...chartOptions, ...this._createOptions() };
|
||||||
@ -151,15 +157,20 @@ export class HaChartBase extends LitElement {
|
|||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
class=${classMap({
|
class="container ${classMap({ "has-height": !!this.height })}"
|
||||||
"chart-container": true,
|
|
||||||
"has-legend": !!this.options?.legend,
|
|
||||||
})}
|
|
||||||
style=${styleMap({
|
style=${styleMap({
|
||||||
height: this.height ?? `${this._getDefaultHeight()}px`,
|
height: this.height,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div class="chart"></div>
|
<div
|
||||||
|
class="chart-container"
|
||||||
|
style=${styleMap({
|
||||||
|
height: this.height ? undefined : `${this._getDefaultHeight()}px`,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div class="chart"></div>
|
||||||
|
</div>
|
||||||
|
${this._renderLegend()}
|
||||||
${this._isZoomed
|
${this._isZoomed
|
||||||
? html`<ha-icon-button
|
? html`<ha-icon-button
|
||||||
class="zoom-reset"
|
class="zoom-reset"
|
||||||
@ -174,6 +185,74 @@ export class HaChartBase extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _renderLegend() {
|
||||||
|
if (!this.options?.legend || !this.data) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
const legend = ensureArray(this.options.legend)[0] as LegendComponentOption;
|
||||||
|
if (!legend.show) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
const datasets = ensureArray(this.data);
|
||||||
|
const items = (legend.data ||
|
||||||
|
datasets
|
||||||
|
.filter((d) => (d.data as any[])?.length && (d.id || d.name))
|
||||||
|
.map((d) => d.name ?? d.id) ||
|
||||||
|
[]) as string[];
|
||||||
|
|
||||||
|
const isMobile = window.matchMedia(
|
||||||
|
"all and (max-width: 450px), all and (max-height: 500px)"
|
||||||
|
).matches;
|
||||||
|
const overflowLimit = isMobile
|
||||||
|
? LEGEND_OVERFLOW_LIMIT_MOBILE
|
||||||
|
: LEGEND_OVERFLOW_LIMIT;
|
||||||
|
return html`<div class="chart-legend">
|
||||||
|
<ul>
|
||||||
|
${items.map((item: string, index: number) => {
|
||||||
|
if (!this.expandLegend && index >= overflowLimit) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
const dataset = datasets.find(
|
||||||
|
(d) => d.id === item || d.name === item
|
||||||
|
);
|
||||||
|
const color = dataset?.color as string;
|
||||||
|
const borderColor = dataset?.itemStyle?.borderColor as string;
|
||||||
|
return html`<li
|
||||||
|
.name=${item}
|
||||||
|
@click=${this._legendClick}
|
||||||
|
class=${classMap({ hidden: this._hiddenDatasets.has(item) })}
|
||||||
|
.title=${item}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="bullet"
|
||||||
|
style=${styleMap({
|
||||||
|
backgroundColor: color,
|
||||||
|
borderColor: borderColor || color,
|
||||||
|
})}
|
||||||
|
></div>
|
||||||
|
<div class="label">${item}</div>
|
||||||
|
</li>`;
|
||||||
|
})}
|
||||||
|
${items.length > overflowLimit
|
||||||
|
? html`<li>
|
||||||
|
<ha-assist-chip
|
||||||
|
@click=${this._toggleExpandedLegend}
|
||||||
|
filled
|
||||||
|
label=${`${this.hass.localize(
|
||||||
|
`ui.components.history_charts.${this.expandLegend ? "collapse_legend" : "expand_legend"}`
|
||||||
|
)} (${items.length})`}
|
||||||
|
>
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="trailing-icon"
|
||||||
|
.path=${this.expandLegend ? mdiChevronUp : mdiChevronDown}
|
||||||
|
></ha-svg-icon>
|
||||||
|
</ha-assist-chip>
|
||||||
|
</li>`
|
||||||
|
: nothing}
|
||||||
|
</ul>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
private _formatTimeLabel = (value: number | Date) =>
|
private _formatTimeLabel = (value: number | Date) =>
|
||||||
formatTimeLabel(
|
formatTimeLabel(
|
||||||
value,
|
value,
|
||||||
@ -195,16 +274,6 @@ export class HaChartBase extends LitElement {
|
|||||||
echarts.registerTheme("custom", this._createTheme());
|
echarts.registerTheme("custom", this._createTheme());
|
||||||
|
|
||||||
this.chart = echarts.init(container, "custom");
|
this.chart = echarts.init(container, "custom");
|
||||||
this.chart.on("legendselectchanged", (params: any) => {
|
|
||||||
if (this.externalHidden) {
|
|
||||||
const isSelected = params.selected[params.name];
|
|
||||||
if (isSelected) {
|
|
||||||
fireEvent(this, "dataset-unhidden", { name: params.name });
|
|
||||||
} else {
|
|
||||||
fireEvent(this, "dataset-hidden", { name: params.name });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.chart.on("datazoom", (e: any) => {
|
this.chart.on("datazoom", (e: any) => {
|
||||||
const { start, end } = e.batch?.[0] ?? e;
|
const { start, end } = e.batch?.[0] ?? e;
|
||||||
this._isZoomed = start !== 0 || end !== 100;
|
this._isZoomed = start !== 0 || end !== 100;
|
||||||
@ -219,7 +288,10 @@ export class HaChartBase extends LitElement {
|
|||||||
this.chart?.getZr()?.setCursorStyle("default");
|
this.chart?.getZr()?.setCursorStyle("default");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.chart.setOption({ ...this._createOptions(), series: this.data });
|
this.chart.setOption({
|
||||||
|
...this._createOptions(),
|
||||||
|
series: this._getSeries(),
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
this._loading = false;
|
this._loading = false;
|
||||||
}
|
}
|
||||||
@ -299,6 +371,9 @@ export class HaChartBase extends LitElement {
|
|||||||
},
|
},
|
||||||
dataZoom: this._getDataZoomConfig(),
|
dataZoom: this._getDataZoomConfig(),
|
||||||
...this.options,
|
...this.options,
|
||||||
|
legend: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
xAxis,
|
xAxis,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -507,6 +582,15 @@ export class HaChartBase extends LitElement {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _getSeries() {
|
||||||
|
if (!Array.isArray(this.data)) {
|
||||||
|
return this.data;
|
||||||
|
}
|
||||||
|
return this.data.filter(
|
||||||
|
(d) => !this._hiddenDatasets.has(String(d.name ?? d.id))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private _getDefaultHeight() {
|
private _getDefaultHeight() {
|
||||||
return Math.max(this.clientWidth / 2, 200);
|
return Math.max(this.clientWidth / 2, 200);
|
||||||
}
|
}
|
||||||
@ -540,19 +624,52 @@ export class HaChartBase extends LitElement {
|
|||||||
this.chart?.dispatchAction({ type: "dataZoom", start: 0, end: 100 });
|
this.chart?.dispatchAction({ type: "dataZoom", start: 0, end: 100 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _legendClick(ev: any) {
|
||||||
|
if (!this.chart) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const name = ev.currentTarget?.name;
|
||||||
|
if (this._hiddenDatasets.has(name)) {
|
||||||
|
this._hiddenDatasets.delete(name);
|
||||||
|
fireEvent(this, "dataset-unhidden", { name });
|
||||||
|
} else {
|
||||||
|
this._hiddenDatasets.add(name);
|
||||||
|
fireEvent(this, "dataset-hidden", { name });
|
||||||
|
}
|
||||||
|
this.requestUpdate("_hiddenDatasets");
|
||||||
|
}
|
||||||
|
|
||||||
|
private _toggleExpandedLegend() {
|
||||||
|
this.expandLegend = !this.expandLegend;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.chart?.resize();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
letter-spacing: normal;
|
letter-spacing: normal;
|
||||||
}
|
}
|
||||||
.chart-container {
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
}
|
||||||
|
.container.has-height {
|
||||||
max-height: var(--chart-max-height, 350px);
|
max-height: var(--chart-max-height, 350px);
|
||||||
}
|
}
|
||||||
.chart {
|
.chart-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
max-height: var(--chart-max-height, 350px);
|
||||||
|
}
|
||||||
|
.has-height .chart-container {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.chart {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
.zoom-reset {
|
.zoom-reset {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -564,8 +681,66 @@ export class HaChartBase extends LitElement {
|
|||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
border: 1px solid var(--divider-color);
|
border: 1px solid var(--divider-color);
|
||||||
}
|
}
|
||||||
.has-legend .zoom-reset {
|
.chart-legend {
|
||||||
top: 64px;
|
max-height: 60%;
|
||||||
|
overflow-y: auto;
|
||||||
|
margin: 12px 0 0;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
.chart-legend ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.chart-legend li {
|
||||||
|
height: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 2px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
max-width: 220px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.chart-legend .hidden {
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
.chart-legend .label {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.chart-legend .bullet {
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: block;
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
margin-right: 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-inline-end: 4px;
|
||||||
|
margin-inline-start: initial;
|
||||||
|
direction: var(--direction);
|
||||||
|
}
|
||||||
|
.chart-legend .hidden .bullet {
|
||||||
|
border-color: var(--secondary-text-color) !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
ha-assist-chip {
|
||||||
|
height: 100%;
|
||||||
|
--_label-text-weight: 500;
|
||||||
|
--_leading-space: 8px;
|
||||||
|
--_trailing-space: 8px;
|
||||||
|
--_icon-label-space: 4px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -63,6 +63,9 @@ export class StateHistoryChartLine extends LitElement {
|
|||||||
|
|
||||||
@property({ type: String }) public height?: string;
|
@property({ type: String }) public height?: string;
|
||||||
|
|
||||||
|
@property({ attribute: "expand-legend", type: Boolean })
|
||||||
|
public expandLegend?: boolean;
|
||||||
|
|
||||||
@state() private _chartData: LineSeriesOption[] = [];
|
@state() private _chartData: LineSeriesOption[] = [];
|
||||||
|
|
||||||
@state() private _entityIds: string[] = [];
|
@state() private _entityIds: string[] = [];
|
||||||
@ -87,9 +90,9 @@ export class StateHistoryChartLine extends LitElement {
|
|||||||
.options=${this._chartOptions}
|
.options=${this._chartOptions}
|
||||||
.height=${this.height}
|
.height=${this.height}
|
||||||
style=${styleMap({ height: this.height })}
|
style=${styleMap({ height: this.height })}
|
||||||
external-hidden
|
|
||||||
@dataset-hidden=${this._datasetHidden}
|
@dataset-hidden=${this._datasetHidden}
|
||||||
@dataset-unhidden=${this._datasetUnhidden}
|
@dataset-unhidden=${this._datasetUnhidden}
|
||||||
|
.expandLegend=${this.expandLegend}
|
||||||
></ha-chart-base>
|
></ha-chart-base>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -271,16 +274,12 @@ export class StateHistoryChartLine extends LitElement {
|
|||||||
} as YAXisOption,
|
} as YAXisOption,
|
||||||
legend: {
|
legend: {
|
||||||
show: this.showNames,
|
show: this.showNames,
|
||||||
type: "scroll",
|
|
||||||
animationDurationUpdate: 400,
|
|
||||||
icon: "circle",
|
|
||||||
padding: [20, 0],
|
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
...(this.showNames ? {} : { top: 30 }), // undefined is the same as 0
|
top: 15,
|
||||||
left: rtl ? 1 : Math.max(this.paddingYAxis, this._yWidth),
|
left: rtl ? 1 : Math.max(this.paddingYAxis, this._yWidth),
|
||||||
right: rtl ? Math.max(this.paddingYAxis, this._yWidth) : 1,
|
right: rtl ? Math.max(this.paddingYAxis, this._yWidth) : 1,
|
||||||
bottom: 30,
|
bottom: 20,
|
||||||
},
|
},
|
||||||
visualMap: this._visualMap,
|
visualMap: this._visualMap,
|
||||||
tooltip: {
|
tooltip: {
|
||||||
|
@ -71,6 +71,9 @@ export class StateHistoryCharts extends LitElement {
|
|||||||
|
|
||||||
@property({ type: String }) public height?: string;
|
@property({ type: String }) public height?: string;
|
||||||
|
|
||||||
|
@property({ attribute: "expand-legend", type: Boolean })
|
||||||
|
public expandLegend?: boolean;
|
||||||
|
|
||||||
private _computedStartTime!: Date;
|
private _computedStartTime!: Date;
|
||||||
|
|
||||||
private _computedEndTime!: Date;
|
private _computedEndTime!: Date;
|
||||||
@ -154,6 +157,7 @@ export class StateHistoryCharts extends LitElement {
|
|||||||
.fitYData=${this.fitYData}
|
.fitYData=${this.fitYData}
|
||||||
@y-width-changed=${this._yWidthChanged}
|
@y-width-changed=${this._yWidthChanged}
|
||||||
.height=${this.virtualize ? undefined : this.height}
|
.height=${this.virtualize ? undefined : this.height}
|
||||||
|
.expandLegend=${this.expandLegend}
|
||||||
></state-history-chart-line>
|
></state-history-chart-line>
|
||||||
</div> `;
|
</div> `;
|
||||||
}
|
}
|
||||||
@ -303,6 +307,8 @@ export class StateHistoryCharts extends LitElement {
|
|||||||
|
|
||||||
.entry-container.line {
|
.entry-container.line {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
padding-top: 8px;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.entry-container:hover {
|
.entry-container:hover {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import type {
|
import type {
|
||||||
BarSeriesOption,
|
BarSeriesOption,
|
||||||
LineSeriesOption,
|
LineSeriesOption,
|
||||||
|
ZRColor,
|
||||||
} from "echarts/types/dist/shared";
|
} from "echarts/types/dist/shared";
|
||||||
import type { PropertyValues, TemplateResult } from "lit";
|
import type { PropertyValues, TemplateResult } from "lit";
|
||||||
import { css, html, LitElement } from "lit";
|
import { css, html, LitElement } from "lit";
|
||||||
@ -90,6 +91,9 @@ export class StatisticsChart extends LitElement {
|
|||||||
|
|
||||||
@property({ type: String }) public height?: string;
|
@property({ type: String }) public height?: string;
|
||||||
|
|
||||||
|
@property({ attribute: "expand-legend", type: Boolean })
|
||||||
|
public expandLegend?: boolean;
|
||||||
|
|
||||||
@state() private _chartData: (LineSeriesOption | BarSeriesOption)[] = [];
|
@state() private _chartData: (LineSeriesOption | BarSeriesOption)[] = [];
|
||||||
|
|
||||||
@state() private _legendData: string[] = [];
|
@state() private _legendData: string[] = [];
|
||||||
@ -169,9 +173,9 @@ export class StatisticsChart extends LitElement {
|
|||||||
.options=${this._chartOptions}
|
.options=${this._chartOptions}
|
||||||
.height=${this.height}
|
.height=${this.height}
|
||||||
style=${styleMap({ height: this.height })}
|
style=${styleMap({ height: this.height })}
|
||||||
external-hidden
|
|
||||||
@dataset-hidden=${this._datasetHidden}
|
@dataset-hidden=${this._datasetHidden}
|
||||||
@dataset-unhidden=${this._datasetUnhidden}
|
@dataset-unhidden=${this._datasetUnhidden}
|
||||||
|
.expandLegend=${this.expandLegend}
|
||||||
></ha-chart-base>
|
></ha-chart-base>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -301,14 +305,10 @@ export class StatisticsChart extends LitElement {
|
|||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
show: !this.hideLegend,
|
show: !this.hideLegend,
|
||||||
type: "scroll",
|
|
||||||
animationDurationUpdate: 400,
|
|
||||||
icon: "circle",
|
|
||||||
padding: [20, 0],
|
|
||||||
data: this._legendData,
|
data: this._legendData,
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
...(this.hideLegend ? { top: this.unit ? 30 : 5 } : {}), // undefined is the same as 0
|
top: 15,
|
||||||
left: 1,
|
left: 1,
|
||||||
right: 1,
|
right: 1,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
@ -348,7 +348,11 @@ export class StatisticsChart extends LitElement {
|
|||||||
let colorIndex = 0;
|
let colorIndex = 0;
|
||||||
const statisticsData = Object.entries(this.statisticsData);
|
const statisticsData = Object.entries(this.statisticsData);
|
||||||
const totalDataSets: typeof this._chartData = [];
|
const totalDataSets: typeof this._chartData = [];
|
||||||
const legendData: { name: string; color: string }[] = [];
|
const legendData: {
|
||||||
|
name: string;
|
||||||
|
color?: ZRColor;
|
||||||
|
borderColor?: ZRColor;
|
||||||
|
}[] = [];
|
||||||
const statisticIds: string[] = [];
|
const statisticIds: string[] = [];
|
||||||
let endTime: Date;
|
let endTime: Date;
|
||||||
|
|
||||||
@ -399,7 +403,7 @@ export class StatisticsChart extends LitElement {
|
|||||||
|
|
||||||
// The datasets for the current statistic
|
// The datasets for the current statistic
|
||||||
const statDataSets: (LineSeriesOption | BarSeriesOption)[] = [];
|
const statDataSets: (LineSeriesOption | BarSeriesOption)[] = [];
|
||||||
const statLegendData: { name: string; color: string }[] = [];
|
const statLegendData: typeof legendData = [];
|
||||||
|
|
||||||
const pushData = (
|
const pushData = (
|
||||||
start: Date,
|
start: Date,
|
||||||
@ -465,15 +469,6 @@ export class StatisticsChart extends LitElement {
|
|||||||
sortedTypes.forEach((type) => {
|
sortedTypes.forEach((type) => {
|
||||||
if (statisticsHaveType(stats, type)) {
|
if (statisticsHaveType(stats, type)) {
|
||||||
const band = drawBands && (type === "min" || type === "max");
|
const band = drawBands && (type === "min" || type === "max");
|
||||||
if (!this.hideLegend) {
|
|
||||||
const showLegend = hasMean
|
|
||||||
? type === "mean"
|
|
||||||
: displayedLegend === false;
|
|
||||||
if (showLegend) {
|
|
||||||
statLegendData.push({ name, color });
|
|
||||||
}
|
|
||||||
displayedLegend = displayedLegend || showLegend;
|
|
||||||
}
|
|
||||||
statTypes.push(type);
|
statTypes.push(type);
|
||||||
const borderColor =
|
const borderColor =
|
||||||
band && hasMean ? color + (this.hideLegend ? "00" : "7F") : color;
|
band && hasMean ? color + (this.hideLegend ? "00" : "7F") : color;
|
||||||
@ -517,6 +512,19 @@ export class StatisticsChart extends LitElement {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!this.hideLegend) {
|
||||||
|
const showLegend = hasMean
|
||||||
|
? type === "mean"
|
||||||
|
: displayedLegend === false;
|
||||||
|
if (showLegend) {
|
||||||
|
statLegendData.push({
|
||||||
|
name,
|
||||||
|
color: series.color as ZRColor,
|
||||||
|
borderColor: series.itemStyle?.borderColor,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
displayedLegend = displayedLegend || showLegend;
|
||||||
|
}
|
||||||
statDataSets.push(series);
|
statDataSets.push(series);
|
||||||
statisticIds.push(statistic_id);
|
statisticIds.push(statistic_id);
|
||||||
}
|
}
|
||||||
@ -564,12 +572,15 @@ export class StatisticsChart extends LitElement {
|
|||||||
this.unit = unit;
|
this.unit = unit;
|
||||||
}
|
}
|
||||||
|
|
||||||
legendData.forEach(({ name, color }) => {
|
legendData.forEach(({ name, color, borderColor }) => {
|
||||||
// Add an empty series for the legend
|
// Add an empty series for the legend
|
||||||
totalDataSets.push({
|
totalDataSets.push({
|
||||||
id: name + "-legend",
|
id: name + "-legend",
|
||||||
name: name,
|
name: name,
|
||||||
color,
|
color,
|
||||||
|
itemStyle: {
|
||||||
|
borderColor,
|
||||||
|
},
|
||||||
type: this.chartType,
|
type: this.chartType,
|
||||||
data: [],
|
data: [],
|
||||||
xAxisIndex: 1,
|
xAxisIndex: 1,
|
||||||
|
@ -123,7 +123,6 @@ export class HuiEnergyDevicesDetailGraphCard
|
|||||||
})}"
|
})}"
|
||||||
>
|
>
|
||||||
<ha-chart-base
|
<ha-chart-base
|
||||||
external-hidden
|
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.data=${this._chartData}
|
.data=${this._chartData}
|
||||||
.options=${this._createOptions(
|
.options=${this._createOptions(
|
||||||
@ -193,7 +192,7 @@ export class HuiEnergyDevicesDetailGraphCard
|
|||||||
icon: "circle",
|
icon: "circle",
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
top: 45,
|
top: 15,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 1,
|
left: 1,
|
||||||
right: 1,
|
right: 1,
|
||||||
|
@ -287,6 +287,7 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard {
|
|||||||
.fitYData=${this._config.fit_y_data || false}
|
.fitYData=${this._config.fit_y_data || false}
|
||||||
.height=${hasFixedHeight ? "100%" : undefined}
|
.height=${hasFixedHeight ? "100%" : undefined}
|
||||||
.narrow=${narrow}
|
.narrow=${narrow}
|
||||||
|
.expandLegend=${this._config.expand_legend}
|
||||||
></state-history-charts>
|
></state-history-charts>
|
||||||
`}
|
`}
|
||||||
</div>
|
</div>
|
||||||
@ -311,8 +312,9 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard {
|
|||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
}
|
}
|
||||||
.content {
|
.content {
|
||||||
padding: 0 16px 8px 16px;
|
padding: 0 16px 8px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.has-header {
|
.has-header {
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
|
@ -296,6 +296,7 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
|
|||||||
? differenceInDays(this._energyEnd, this._energyStart)
|
? differenceInDays(this._energyEnd, this._energyStart)
|
||||||
: this._config.days_to_show || DEFAULT_DAYS_TO_SHOW}
|
: this._config.days_to_show || DEFAULT_DAYS_TO_SHOW}
|
||||||
.height=${hasFixedHeight ? "100%" : undefined}
|
.height=${hasFixedHeight ? "100%" : undefined}
|
||||||
|
.expandLegend=${this._config.expand_legend}
|
||||||
></statistics-chart>
|
></statistics-chart>
|
||||||
</div>
|
</div>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
@ -380,7 +381,6 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
.content {
|
.content {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
padding-top: 0;
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
.has-header {
|
.has-header {
|
||||||
|
@ -359,6 +359,7 @@ export interface HistoryGraphCardConfig extends LovelaceCardConfig {
|
|||||||
max_y_axis?: number;
|
max_y_axis?: number;
|
||||||
fit_y_data?: boolean;
|
fit_y_data?: boolean;
|
||||||
split_device_classes?: boolean;
|
split_device_classes?: boolean;
|
||||||
|
expand_legend?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StatisticsGraphCardConfig extends EnergyCardBaseConfig {
|
export interface StatisticsGraphCardConfig extends EnergyCardBaseConfig {
|
||||||
@ -375,6 +376,7 @@ export interface StatisticsGraphCardConfig extends EnergyCardBaseConfig {
|
|||||||
hide_legend?: boolean;
|
hide_legend?: boolean;
|
||||||
logarithmic_scale?: boolean;
|
logarithmic_scale?: boolean;
|
||||||
energy_date_selection?: boolean;
|
energy_date_selection?: boolean;
|
||||||
|
expand_legend?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StatisticCardConfig extends LovelaceCardConfig {
|
export interface StatisticCardConfig extends LovelaceCardConfig {
|
||||||
|
@ -843,7 +843,9 @@
|
|||||||
"duration": "Duration",
|
"duration": "Duration",
|
||||||
"source_history": "Source: History",
|
"source_history": "Source: History",
|
||||||
"source_stats": "Source: Long term statistics",
|
"source_stats": "Source: Long term statistics",
|
||||||
"zoom_reset": "Reset zoom"
|
"zoom_reset": "Reset zoom",
|
||||||
|
"expand_legend": "More",
|
||||||
|
"collapse_legend": "Less"
|
||||||
},
|
},
|
||||||
"map": {
|
"map": {
|
||||||
"error": "Unable to load map"
|
"error": "Unable to load map"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user