mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +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 { 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 type { DataZoomComponentOption } from "echarts/components";
|
||||
import type { EChartsType } from "echarts/core";
|
||||
import type {
|
||||
ECElementEvent,
|
||||
LegendComponentOption,
|
||||
XAXisOption,
|
||||
YAXisOption,
|
||||
} from "echarts/types/dist/shared";
|
||||
@ -25,8 +26,11 @@ import { isMac } from "../../util/is_mac";
|
||||
import "../ha-icon-button";
|
||||
import { formatTimeLabel } from "./axis-label";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import "../chips/ha-assist-chip";
|
||||
|
||||
export const MIN_TIME_BETWEEN_UPDATES = 60 * 5 * 1000;
|
||||
const LEGEND_OVERFLOW_LIMIT = 10;
|
||||
const LEGEND_OVERFLOW_LIMIT_MOBILE = 6;
|
||||
|
||||
@customElement("ha-chart-base")
|
||||
export class HaChartBase extends LitElement {
|
||||
@ -40,8 +44,8 @@ export class HaChartBase extends LitElement {
|
||||
|
||||
@property({ type: String }) public height?: string;
|
||||
|
||||
@property({ attribute: "external-hidden", type: Boolean })
|
||||
public externalHidden = false;
|
||||
@property({ attribute: "expand-legend", type: Boolean })
|
||||
public expandLegend?: boolean;
|
||||
|
||||
@state()
|
||||
@consume({ context: themesContext, subscribe: true })
|
||||
@ -53,6 +57,8 @@ export class HaChartBase extends LitElement {
|
||||
|
||||
@state() private _minutesDifference = 24 * 60;
|
||||
|
||||
@state() private _hiddenDatasets = new Set<string>();
|
||||
|
||||
private _modifierPressed = false;
|
||||
|
||||
private _isTouchDevice = "ontouchstart" in window;
|
||||
@ -135,8 +141,8 @@ export class HaChartBase extends LitElement {
|
||||
return;
|
||||
}
|
||||
let chartOptions: ECOption = {};
|
||||
if (changedProps.has("data")) {
|
||||
chartOptions.series = this.data;
|
||||
if (changedProps.has("data") || changedProps.has("_hiddenDatasets")) {
|
||||
chartOptions.series = this._getSeries();
|
||||
}
|
||||
if (changedProps.has("options")) {
|
||||
chartOptions = { ...chartOptions, ...this._createOptions() };
|
||||
@ -151,15 +157,20 @@ export class HaChartBase extends LitElement {
|
||||
protected render() {
|
||||
return html`
|
||||
<div
|
||||
class=${classMap({
|
||||
"chart-container": true,
|
||||
"has-legend": !!this.options?.legend,
|
||||
})}
|
||||
class="container ${classMap({ "has-height": !!this.height })}"
|
||||
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
|
||||
? html`<ha-icon-button
|
||||
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) =>
|
||||
formatTimeLabel(
|
||||
value,
|
||||
@ -195,16 +274,6 @@ export class HaChartBase extends LitElement {
|
||||
echarts.registerTheme("custom", this._createTheme());
|
||||
|
||||
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) => {
|
||||
const { start, end } = e.batch?.[0] ?? e;
|
||||
this._isZoomed = start !== 0 || end !== 100;
|
||||
@ -219,7 +288,10 @@ export class HaChartBase extends LitElement {
|
||||
this.chart?.getZr()?.setCursorStyle("default");
|
||||
}
|
||||
});
|
||||
this.chart.setOption({ ...this._createOptions(), series: this.data });
|
||||
this.chart.setOption({
|
||||
...this._createOptions(),
|
||||
series: this._getSeries(),
|
||||
});
|
||||
} finally {
|
||||
this._loading = false;
|
||||
}
|
||||
@ -299,6 +371,9 @@ export class HaChartBase extends LitElement {
|
||||
},
|
||||
dataZoom: this._getDataZoomConfig(),
|
||||
...this.options,
|
||||
legend: {
|
||||
show: false,
|
||||
},
|
||||
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() {
|
||||
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 });
|
||||
}
|
||||
|
||||
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`
|
||||
:host {
|
||||
display: block;
|
||||
position: relative;
|
||||
letter-spacing: normal;
|
||||
}
|
||||
.chart-container {
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
.container.has-height {
|
||||
max-height: var(--chart-max-height, 350px);
|
||||
}
|
||||
.chart {
|
||||
.chart-container {
|
||||
width: 100%;
|
||||
max-height: var(--chart-max-height, 350px);
|
||||
}
|
||||
.has-height .chart-container {
|
||||
flex: 1;
|
||||
}
|
||||
.chart {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.zoom-reset {
|
||||
position: absolute;
|
||||
@ -564,8 +681,66 @@ export class HaChartBase extends LitElement {
|
||||
color: var(--primary-color);
|
||||
border: 1px solid var(--divider-color);
|
||||
}
|
||||
.has-legend .zoom-reset {
|
||||
top: 64px;
|
||||
.chart-legend {
|
||||
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({ attribute: "expand-legend", type: Boolean })
|
||||
public expandLegend?: boolean;
|
||||
|
||||
@state() private _chartData: LineSeriesOption[] = [];
|
||||
|
||||
@state() private _entityIds: string[] = [];
|
||||
@ -87,9 +90,9 @@ export class StateHistoryChartLine extends LitElement {
|
||||
.options=${this._chartOptions}
|
||||
.height=${this.height}
|
||||
style=${styleMap({ height: this.height })}
|
||||
external-hidden
|
||||
@dataset-hidden=${this._datasetHidden}
|
||||
@dataset-unhidden=${this._datasetUnhidden}
|
||||
.expandLegend=${this.expandLegend}
|
||||
></ha-chart-base>
|
||||
`;
|
||||
}
|
||||
@ -271,16 +274,12 @@ export class StateHistoryChartLine extends LitElement {
|
||||
} as YAXisOption,
|
||||
legend: {
|
||||
show: this.showNames,
|
||||
type: "scroll",
|
||||
animationDurationUpdate: 400,
|
||||
icon: "circle",
|
||||
padding: [20, 0],
|
||||
},
|
||||
grid: {
|
||||
...(this.showNames ? {} : { top: 30 }), // undefined is the same as 0
|
||||
top: 15,
|
||||
left: rtl ? 1 : Math.max(this.paddingYAxis, this._yWidth),
|
||||
right: rtl ? Math.max(this.paddingYAxis, this._yWidth) : 1,
|
||||
bottom: 30,
|
||||
bottom: 20,
|
||||
},
|
||||
visualMap: this._visualMap,
|
||||
tooltip: {
|
||||
|
@ -71,6 +71,9 @@ export class StateHistoryCharts extends LitElement {
|
||||
|
||||
@property({ type: String }) public height?: string;
|
||||
|
||||
@property({ attribute: "expand-legend", type: Boolean })
|
||||
public expandLegend?: boolean;
|
||||
|
||||
private _computedStartTime!: Date;
|
||||
|
||||
private _computedEndTime!: Date;
|
||||
@ -154,6 +157,7 @@ export class StateHistoryCharts extends LitElement {
|
||||
.fitYData=${this.fitYData}
|
||||
@y-width-changed=${this._yWidthChanged}
|
||||
.height=${this.virtualize ? undefined : this.height}
|
||||
.expandLegend=${this.expandLegend}
|
||||
></state-history-chart-line>
|
||||
</div> `;
|
||||
}
|
||||
@ -303,6 +307,8 @@ export class StateHistoryCharts extends LitElement {
|
||||
|
||||
.entry-container.line {
|
||||
flex: 1;
|
||||
padding-top: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.entry-container:hover {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import type {
|
||||
BarSeriesOption,
|
||||
LineSeriesOption,
|
||||
ZRColor,
|
||||
} from "echarts/types/dist/shared";
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
@ -90,6 +91,9 @@ export class StatisticsChart extends LitElement {
|
||||
|
||||
@property({ type: String }) public height?: string;
|
||||
|
||||
@property({ attribute: "expand-legend", type: Boolean })
|
||||
public expandLegend?: boolean;
|
||||
|
||||
@state() private _chartData: (LineSeriesOption | BarSeriesOption)[] = [];
|
||||
|
||||
@state() private _legendData: string[] = [];
|
||||
@ -169,9 +173,9 @@ export class StatisticsChart extends LitElement {
|
||||
.options=${this._chartOptions}
|
||||
.height=${this.height}
|
||||
style=${styleMap({ height: this.height })}
|
||||
external-hidden
|
||||
@dataset-hidden=${this._datasetHidden}
|
||||
@dataset-unhidden=${this._datasetUnhidden}
|
||||
.expandLegend=${this.expandLegend}
|
||||
></ha-chart-base>
|
||||
`;
|
||||
}
|
||||
@ -301,14 +305,10 @@ export class StatisticsChart extends LitElement {
|
||||
},
|
||||
legend: {
|
||||
show: !this.hideLegend,
|
||||
type: "scroll",
|
||||
animationDurationUpdate: 400,
|
||||
icon: "circle",
|
||||
padding: [20, 0],
|
||||
data: this._legendData,
|
||||
},
|
||||
grid: {
|
||||
...(this.hideLegend ? { top: this.unit ? 30 : 5 } : {}), // undefined is the same as 0
|
||||
top: 15,
|
||||
left: 1,
|
||||
right: 1,
|
||||
bottom: 0,
|
||||
@ -348,7 +348,11 @@ export class StatisticsChart extends LitElement {
|
||||
let colorIndex = 0;
|
||||
const statisticsData = Object.entries(this.statisticsData);
|
||||
const totalDataSets: typeof this._chartData = [];
|
||||
const legendData: { name: string; color: string }[] = [];
|
||||
const legendData: {
|
||||
name: string;
|
||||
color?: ZRColor;
|
||||
borderColor?: ZRColor;
|
||||
}[] = [];
|
||||
const statisticIds: string[] = [];
|
||||
let endTime: Date;
|
||||
|
||||
@ -399,7 +403,7 @@ export class StatisticsChart extends LitElement {
|
||||
|
||||
// The datasets for the current statistic
|
||||
const statDataSets: (LineSeriesOption | BarSeriesOption)[] = [];
|
||||
const statLegendData: { name: string; color: string }[] = [];
|
||||
const statLegendData: typeof legendData = [];
|
||||
|
||||
const pushData = (
|
||||
start: Date,
|
||||
@ -465,15 +469,6 @@ export class StatisticsChart extends LitElement {
|
||||
sortedTypes.forEach((type) => {
|
||||
if (statisticsHaveType(stats, type)) {
|
||||
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);
|
||||
const borderColor =
|
||||
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);
|
||||
statisticIds.push(statistic_id);
|
||||
}
|
||||
@ -564,12 +572,15 @@ export class StatisticsChart extends LitElement {
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
legendData.forEach(({ name, color }) => {
|
||||
legendData.forEach(({ name, color, borderColor }) => {
|
||||
// Add an empty series for the legend
|
||||
totalDataSets.push({
|
||||
id: name + "-legend",
|
||||
name: name,
|
||||
color,
|
||||
itemStyle: {
|
||||
borderColor,
|
||||
},
|
||||
type: this.chartType,
|
||||
data: [],
|
||||
xAxisIndex: 1,
|
||||
|
@ -123,7 +123,6 @@ export class HuiEnergyDevicesDetailGraphCard
|
||||
})}"
|
||||
>
|
||||
<ha-chart-base
|
||||
external-hidden
|
||||
.hass=${this.hass}
|
||||
.data=${this._chartData}
|
||||
.options=${this._createOptions(
|
||||
@ -193,7 +192,7 @@ export class HuiEnergyDevicesDetailGraphCard
|
||||
icon: "circle",
|
||||
},
|
||||
grid: {
|
||||
top: 45,
|
||||
top: 15,
|
||||
bottom: 0,
|
||||
left: 1,
|
||||
right: 1,
|
||||
|
@ -287,6 +287,7 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard {
|
||||
.fitYData=${this._config.fit_y_data || false}
|
||||
.height=${hasFixedHeight ? "100%" : undefined}
|
||||
.narrow=${narrow}
|
||||
.expandLegend=${this._config.expand_legend}
|
||||
></state-history-charts>
|
||||
`}
|
||||
</div>
|
||||
@ -311,8 +312,9 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.content {
|
||||
padding: 0 16px 8px 16px;
|
||||
padding: 0 16px 8px;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
.has-header {
|
||||
padding-top: 0;
|
||||
|
@ -296,6 +296,7 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
|
||||
? differenceInDays(this._energyEnd, this._energyStart)
|
||||
: this._config.days_to_show || DEFAULT_DAYS_TO_SHOW}
|
||||
.height=${hasFixedHeight ? "100%" : undefined}
|
||||
.expandLegend=${this._config.expand_legend}
|
||||
></statistics-chart>
|
||||
</div>
|
||||
</ha-card>
|
||||
@ -380,7 +381,6 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
.content {
|
||||
padding: 16px;
|
||||
padding-top: 0;
|
||||
flex: 1;
|
||||
}
|
||||
.has-header {
|
||||
|
@ -359,6 +359,7 @@ export interface HistoryGraphCardConfig extends LovelaceCardConfig {
|
||||
max_y_axis?: number;
|
||||
fit_y_data?: boolean;
|
||||
split_device_classes?: boolean;
|
||||
expand_legend?: boolean;
|
||||
}
|
||||
|
||||
export interface StatisticsGraphCardConfig extends EnergyCardBaseConfig {
|
||||
@ -375,6 +376,7 @@ export interface StatisticsGraphCardConfig extends EnergyCardBaseConfig {
|
||||
hide_legend?: boolean;
|
||||
logarithmic_scale?: boolean;
|
||||
energy_date_selection?: boolean;
|
||||
expand_legend?: boolean;
|
||||
}
|
||||
|
||||
export interface StatisticCardConfig extends LovelaceCardConfig {
|
||||
|
@ -843,7 +843,9 @@
|
||||
"duration": "Duration",
|
||||
"source_history": "Source: History",
|
||||
"source_stats": "Source: Long term statistics",
|
||||
"zoom_reset": "Reset zoom"
|
||||
"zoom_reset": "Reset zoom",
|
||||
"expand_legend": "More",
|
||||
"collapse_legend": "Less"
|
||||
},
|
||||
"map": {
|
||||
"error": "Unable to load map"
|
||||
|
Loading…
x
Reference in New Issue
Block a user