mirror of
https://github.com/home-assistant/frontend.git
synced 2026-11-06 04:54:12 +00:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca456105e5 | ||
|
|
9f12b54a14 | ||
|
|
cf25121dda | ||
|
|
71f53ae3e2 | ||
|
|
8843596e42 | ||
|
|
d06ec13af6 |
No files matched your search
@@ -0,0 +1,19 @@
|
||||
import type { ReactiveElement } from "lit";
|
||||
import { ContextSubscriptionController } from "../../../src/common/decorators/consume";
|
||||
import { uiContext } from "../../../src/data/context";
|
||||
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
|
||||
// A demo that provides its own hass hides the gallery theme from its children,
|
||||
// so pass the gallery light or dark mode on to it.
|
||||
export const followGalleryTheme = (
|
||||
host: ReactiveElement,
|
||||
hass: MockHomeAssistant
|
||||
) =>
|
||||
new ContextSubscriptionController(host, uiContext, (ui) => {
|
||||
if (ui) {
|
||||
hass.updateHass({
|
||||
themes: ui.themes,
|
||||
selectedTheme: ui.selectedTheme,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -10,6 +10,7 @@ import type {
|
||||
} from "../../../../src/data/history";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
import { followGalleryTheme } from "../../data/follow-gallery-theme";
|
||||
|
||||
const HOUR = 60 * 60 * 1000;
|
||||
const HOURS = 48;
|
||||
@@ -166,6 +167,7 @@ export class DemoStateHistoryCharts extends LitElement {
|
||||
super.firstUpdated(changedProps);
|
||||
const hass = provideHass(this);
|
||||
hass.updateTranslations(null, "en");
|
||||
followGalleryTheme(this, hass);
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../../src/components/chart/statistics-chart";
|
||||
import "../../../../src/components/ha-button";
|
||||
import "../../../../src/components/ha-card";
|
||||
import type {
|
||||
Statistics,
|
||||
@@ -11,7 +12,9 @@ import type {
|
||||
} from "../../../../src/data/recorder";
|
||||
import { StatisticMeanType } from "../../../../src/data/recorder";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import type { HASSDomEvent } from "../../../../src/common/dom/fire_event";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
import { followGalleryTheme } from "../../data/follow-gallery-theme";
|
||||
|
||||
const HOUR = 60 * 60 * 1000;
|
||||
const HOURS = 48;
|
||||
@@ -108,6 +111,64 @@ interface Example {
|
||||
height?: string;
|
||||
}
|
||||
|
||||
interface TimeRange {
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
|
||||
interface RangeExample {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
statistics: Statistics;
|
||||
statTypes: StatisticType[];
|
||||
chartType: "line" | "bar";
|
||||
unit: string;
|
||||
selectRange: boolean;
|
||||
hint: string;
|
||||
}
|
||||
|
||||
const RANGE_EXAMPLES: RangeExample[] = [
|
||||
{
|
||||
id: "point",
|
||||
title: "Point selection",
|
||||
description:
|
||||
"A click selects the hour that contains it, and everything outside fades. Dragging still zooms.",
|
||||
statistics: { [HEAT_PUMP_ID]: HEAT_PUMP },
|
||||
statTypes: ["change"],
|
||||
chartType: "bar",
|
||||
unit: "kWh",
|
||||
selectRange: false,
|
||||
hint: "Click on a bar to select its hour.",
|
||||
},
|
||||
{
|
||||
id: "range-bar",
|
||||
title: "Range selection on bars",
|
||||
description:
|
||||
"With select-range, dragging selects a time range instead of zooming, so several bars can be selected at once. A click still selects one bar.",
|
||||
statistics: { [HEAT_PUMP_ID]: HEAT_PUMP },
|
||||
statTypes: ["change"],
|
||||
chartType: "bar",
|
||||
unit: "kWh",
|
||||
selectRange: true,
|
||||
hint: "Click or drag on the chart to select one or more bars.",
|
||||
},
|
||||
{
|
||||
id: "range",
|
||||
title: "Range selection on lines",
|
||||
description: "The same on a line chart.",
|
||||
statistics: { [LIVING_ROOM_ID]: LIVING_ROOM },
|
||||
statTypes: ["mean", "min", "max"],
|
||||
chartType: "line",
|
||||
unit: "°C",
|
||||
selectRange: true,
|
||||
hint: "Click or drag on the chart to select a period.",
|
||||
},
|
||||
];
|
||||
|
||||
const formatRange = (range: TimeRange) =>
|
||||
`${new Date(range.start).toLocaleString()} to ${new Date(range.end).toLocaleString()}`;
|
||||
|
||||
const EXAMPLES: Example[] = [
|
||||
{
|
||||
title: "Sum as bars",
|
||||
@@ -159,17 +220,20 @@ const EXAMPLES: Example[] = [
|
||||
export class DemoStatisticsChart extends LitElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@state() private _selections: Record<string, TimeRange | undefined> = {};
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues<this>) {
|
||||
super.firstUpdated(changedProps);
|
||||
const hass = provideHass(this);
|
||||
hass.updateTranslations(null, "en");
|
||||
followGalleryTheme(this, hass);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.hass) {
|
||||
return nothing;
|
||||
}
|
||||
return EXAMPLES.map(
|
||||
return html`${EXAMPLES.map(
|
||||
(example) => html`
|
||||
<ha-card header=${example.title}>
|
||||
<div class="card-content">
|
||||
@@ -191,7 +255,66 @@ export class DemoStatisticsChart extends LitElement {
|
||||
</div>
|
||||
</ha-card>
|
||||
`
|
||||
);
|
||||
)}
|
||||
${RANGE_EXAMPLES.map((example) => this._renderRangeExample(example))}`;
|
||||
}
|
||||
|
||||
private _renderRangeExample(example: RangeExample) {
|
||||
const selection = this._selections[example.id];
|
||||
return html`
|
||||
<ha-card header=${example.title}>
|
||||
<div class="card-content">
|
||||
<p>${example.description}</p>
|
||||
<statistics-chart
|
||||
.statisticsData=${example.statistics}
|
||||
.metadata=${METADATA}
|
||||
.names=${NAMES}
|
||||
.statTypes=${example.statTypes}
|
||||
.chartType=${example.chartType}
|
||||
period="hour"
|
||||
.unit=${example.unit}
|
||||
.startTime=${START}
|
||||
.endTime=${END}
|
||||
.highlight=${selection}
|
||||
.clickForMoreInfo=${false}
|
||||
data-example=${example.id}
|
||||
.selectRange=${example.selectRange}
|
||||
hide-legend
|
||||
@chart-select=${this._rangeSelected}
|
||||
></statistics-chart>
|
||||
<div class="selection">
|
||||
${
|
||||
selection
|
||||
? html`
|
||||
<span>${formatRange(selection)}</span>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="s"
|
||||
data-example=${example.id}
|
||||
@click=${this._clearSelection}
|
||||
>
|
||||
Clear
|
||||
</ha-button>
|
||||
`
|
||||
: html`<span class="hint">${example.hint}</span>`
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private _rangeSelected(ev: HASSDomEvent<HASSDomEvents["chart-select"]>) {
|
||||
const id = (ev.currentTarget as HTMLElement).dataset.example!;
|
||||
this._selections = {
|
||||
...this._selections,
|
||||
[id]: ev.detail,
|
||||
};
|
||||
}
|
||||
|
||||
private _clearSelection(ev: Event) {
|
||||
const id = (ev.currentTarget as HTMLElement).dataset.example!;
|
||||
this._selections = { ...this._selections, [id]: undefined };
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
@@ -202,6 +325,20 @@ export class DemoStatisticsChart extends LitElement {
|
||||
p {
|
||||
margin-top: 0;
|
||||
}
|
||||
.selection {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--ha-space-2);
|
||||
min-height: 48px;
|
||||
margin-top: var(--ha-space-2);
|
||||
padding: 0 var(--ha-space-2) 0 var(--ha-space-4);
|
||||
border-radius: var(--ha-border-radius-lg);
|
||||
background-color: var(--secondary-background-color);
|
||||
}
|
||||
.hint {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,10 @@ import {
|
||||
mdiRestart,
|
||||
} from "@mdi/js";
|
||||
import { differenceInMinutes } from "date-fns";
|
||||
import type { DataZoomComponentOption } from "echarts/components";
|
||||
import type {
|
||||
DataZoomComponentOption,
|
||||
MarkAreaComponentOption,
|
||||
} from "echarts/components";
|
||||
import type { EChartsType } from "echarts/core";
|
||||
import type {
|
||||
ECElementEvent,
|
||||
@@ -90,6 +93,12 @@ export const DEFAULT_CHART_WIDTH = 500;
|
||||
// is short enough for a whole screenful; on a desktop that would cover the page.
|
||||
const VISIBILITY_ROOT_MARGIN_NARROW = "100%";
|
||||
const VISIBILITY_ROOT_MARGIN = "300px";
|
||||
const CLICK_DRAG_TOLERANCE = 4;
|
||||
// The selection is shown by the mask, not by the brush
|
||||
const BRUSH_STYLE = {
|
||||
color: "rgba(0, 0, 0, 0)",
|
||||
borderColor: "rgba(0, 0, 0, 0)",
|
||||
};
|
||||
|
||||
export type ZoomSliderMode = "auto" | "always" | "never";
|
||||
const DEFERRED_PROPS = [
|
||||
@@ -97,7 +106,14 @@ const DEFERRED_PROPS = [
|
||||
"data",
|
||||
"_hiddenDatasets",
|
||||
"_isZoomed",
|
||||
"highlight",
|
||||
] as const;
|
||||
const MASK_OPACITY = 0.7;
|
||||
|
||||
export interface ChartTimeRange {
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
|
||||
type RawSeriesOption = Exclude<
|
||||
NonNullable<ECOption["series"]>,
|
||||
@@ -176,6 +192,17 @@ export class HaChartBase extends MobileAwareMixin(LitElement) {
|
||||
@property({ attribute: "grow-for-zoom-slider", type: Boolean })
|
||||
public growForZoomSlider = false;
|
||||
|
||||
@property({ attribute: "select-range", type: Boolean })
|
||||
public selectRange = false;
|
||||
|
||||
// Everything outside this time range fades
|
||||
@property({ attribute: false }) public highlight?: ChartTimeRange;
|
||||
|
||||
// Rounds a selection to the periods of the data, or drops it
|
||||
@property({ attribute: false }) public snapSelection?: (
|
||||
range: ChartTimeRange
|
||||
) => ChartTimeRange | undefined;
|
||||
|
||||
// extraComponents is not reactive and should not trigger updates
|
||||
public extraComponents?: any[];
|
||||
|
||||
@@ -201,6 +228,10 @@ export class HaChartBase extends MobileAwareMixin(LitElement) {
|
||||
|
||||
@state() private _isZoomed = false;
|
||||
|
||||
private _pointerDownX?: number;
|
||||
|
||||
private _selectingRange?: ChartTimeRange;
|
||||
|
||||
// Separate from _isZoomed, whose touch handling reruns graph layouts
|
||||
@state() private _isGraphRoamed = false;
|
||||
|
||||
@@ -454,6 +485,9 @@ export class HaChartBase extends MobileAwareMixin(LitElement) {
|
||||
if (!this.chart) {
|
||||
return;
|
||||
}
|
||||
if (changedProps.has("selectRange")) {
|
||||
this._updateSelectRange();
|
||||
}
|
||||
const themeChanged = changedProps.has("_themes") && this.hasUpdated;
|
||||
if (!themeChanged && !DEFERRED_PROPS.some((p) => changedProps.has(p))) {
|
||||
return;
|
||||
@@ -499,6 +533,8 @@ export class HaChartBase extends MobileAwareMixin(LitElement) {
|
||||
) {
|
||||
this._disposeSonification();
|
||||
}
|
||||
} else if (changedProps.has("highlight")) {
|
||||
this._updateMask();
|
||||
}
|
||||
if (changedProps.has("options")) {
|
||||
chartOptions = { ...chartOptions, ...this._createOptions() };
|
||||
@@ -905,6 +941,45 @@ export class HaChartBase extends MobileAwareMixin(LitElement) {
|
||||
this.chart.on("click", (e: ECElementEvent) => {
|
||||
fireEvent(this, "chart-click", e);
|
||||
});
|
||||
this.chart.getZr().on("mousedown", (e: ECElementEvent) => {
|
||||
this._pointerDownX = e.offsetX;
|
||||
});
|
||||
this.chart.getZr().on("click", (e: ECElementEvent) => {
|
||||
const point = [e.offsetX, e.offsetY];
|
||||
// A drag that ends on the plot is not a click
|
||||
const dragged =
|
||||
this._pointerDownX !== undefined &&
|
||||
Math.abs(e.offsetX - this._pointerDownX) > CLICK_DRAG_TOLERANCE;
|
||||
if (dragged || !this.chart?.containPixel("grid", point)) {
|
||||
return;
|
||||
}
|
||||
const [x] = this.chart.convertFromPixel("grid", point) as number[];
|
||||
const selection = this._snapRange({ start: x, end: x });
|
||||
if (selection) {
|
||||
fireEvent(this, "chart-select", selection);
|
||||
}
|
||||
});
|
||||
this.chart.on("brush", (e: any) => {
|
||||
const range = e.areas?.[0]?.coordRange as number[] | undefined;
|
||||
if (range) {
|
||||
this._selectingRange = this._snapRange({
|
||||
start: range[0],
|
||||
end: range[1],
|
||||
});
|
||||
this._updateMask();
|
||||
}
|
||||
});
|
||||
this.chart.on("brushEnd", (e: any) => {
|
||||
const range = e.areas?.[0]?.coordRange as number[] | undefined;
|
||||
this.chart?.dispatchAction({ type: "brush", areas: [] });
|
||||
this._selectingRange = undefined;
|
||||
this._updateMask();
|
||||
const selection =
|
||||
range && this._snapRange({ start: range[0], end: range[1] });
|
||||
if (selection) {
|
||||
fireEvent(this, "chart-select", selection);
|
||||
}
|
||||
});
|
||||
this.chart.on("sankeyroam", () => {
|
||||
const option = this.chart!.getOption();
|
||||
const series = option.series as any[];
|
||||
@@ -970,6 +1045,9 @@ export class HaChartBase extends MobileAwareMixin(LitElement) {
|
||||
this._legendItemCount = this._getLegendItems()?.length;
|
||||
this._updateSankeyRoam();
|
||||
this._updateDragToZoom();
|
||||
if (this.selectRange) {
|
||||
this._updateSelectRange();
|
||||
}
|
||||
if (zoomStart !== 0 || zoomEnd !== 100) {
|
||||
this.zoom(zoomStart, zoomEnd, true);
|
||||
}
|
||||
@@ -1021,7 +1099,8 @@ export class HaChartBase extends MobileAwareMixin(LitElement) {
|
||||
|
||||
// A chart can switch type (e.g. pie and bar) without being rebuilt
|
||||
private _updateDragToZoom() {
|
||||
const active = !this._touchInput && this._hasZoomableXAxis();
|
||||
const active =
|
||||
!this._touchInput && this._hasZoomableXAxis() && !this.selectRange;
|
||||
if (!this.chart || active === this._dragToZoom) {
|
||||
return;
|
||||
}
|
||||
@@ -1033,6 +1112,73 @@ export class HaChartBase extends MobileAwareMixin(LitElement) {
|
||||
});
|
||||
}
|
||||
|
||||
private _snapRange(range: ChartTimeRange) {
|
||||
return this.snapSelection ? this.snapSelection(range) : range;
|
||||
}
|
||||
|
||||
private _getMaskArea(): MarkAreaComponentOption | undefined {
|
||||
const range = this._selectingRange ?? this.highlight;
|
||||
if (!range) {
|
||||
return undefined;
|
||||
}
|
||||
const style = getComputedStyle(this);
|
||||
return {
|
||||
silent: true,
|
||||
z: 10,
|
||||
itemStyle: {
|
||||
color:
|
||||
style.getPropertyValue("--card-background-color").trim() ||
|
||||
style.getPropertyValue("--primary-background-color").trim(),
|
||||
opacity: MASK_OPACITY,
|
||||
},
|
||||
data: [
|
||||
[{ xAxis: -Infinity }, { xAxis: range.start }],
|
||||
[{ xAxis: range.end }, { xAxis: Infinity }],
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// Only the first series carries the mask, merged in without resending data
|
||||
private _updateMask() {
|
||||
this.chart?.setOption({
|
||||
series: [{ markArea: this._getMaskArea() ?? { data: [] } }],
|
||||
});
|
||||
}
|
||||
|
||||
private _updateSelectRange() {
|
||||
if (!this.chart) {
|
||||
return;
|
||||
}
|
||||
if (this.selectRange) {
|
||||
this.chart.setOption({
|
||||
brush: {
|
||||
xAxisIndex: 0,
|
||||
// Only measures the range, the mask fades what is outside
|
||||
seriesIndex: [],
|
||||
transformable: false,
|
||||
brushStyle: BRUSH_STYLE,
|
||||
},
|
||||
});
|
||||
}
|
||||
const brushCursor = {
|
||||
type: "takeGlobalCursor",
|
||||
key: "brush",
|
||||
brushOption: {
|
||||
brushType: this.selectRange ? "lineX" : false,
|
||||
brushMode: "single",
|
||||
brushStyle: BRUSH_STYLE,
|
||||
},
|
||||
};
|
||||
// Brush and drag to zoom share the global cursor, the last one taken wins
|
||||
if (this.selectRange) {
|
||||
this._updateDragToZoom();
|
||||
this.chart.dispatchAction(brushCursor);
|
||||
} else {
|
||||
this.chart.dispatchAction(brushCursor);
|
||||
this._updateDragToZoom();
|
||||
}
|
||||
}
|
||||
|
||||
private _hasZoomableXAxis() {
|
||||
const xAxis = ensureArray(this.options?.xAxis)?.[0] as
|
||||
XAXisOption | undefined;
|
||||
@@ -1475,7 +1621,8 @@ export class HaChartBase extends MobileAwareMixin(LitElement) {
|
||||
private _getSeries() {
|
||||
const xAxis = (this.options?.xAxis?.[0] ?? this.options?.xAxis) as
|
||||
XAXisOption | undefined;
|
||||
const series = ensureArray(this.data).map((s) => {
|
||||
const maskArea = this._getMaskArea();
|
||||
const series = ensureArray(this.data).map((s, index) => {
|
||||
const data = this._hiddenDatasets.has(String(s.id ?? s.name))
|
||||
? undefined
|
||||
: s.data;
|
||||
@@ -1513,6 +1660,16 @@ export class HaChartBase extends MobileAwareMixin(LitElement) {
|
||||
} as HaECSeriesItem;
|
||||
}
|
||||
}
|
||||
// ECharts merges series by id and would keep a mask left out here
|
||||
if (s.type === "line" || s.type === "bar") {
|
||||
result = {
|
||||
...result,
|
||||
markArea:
|
||||
index === 0 && maskArea
|
||||
? maskArea
|
||||
: ((s as LineSeriesOption).markArea ?? { data: [] }),
|
||||
} as HaECSeriesItem;
|
||||
}
|
||||
return processSeriesTooltipFormatter(result);
|
||||
});
|
||||
return series as ECOption["series"];
|
||||
@@ -1762,7 +1919,7 @@ export class HaChartBase extends MobileAwareMixin(LitElement) {
|
||||
// it, so a quick drag out would lose the release and its selection
|
||||
if (
|
||||
ev.type === "pointerdown" &&
|
||||
this._dragToZoom &&
|
||||
(this._dragToZoom || this.selectRange) &&
|
||||
this._isZoomPress(ev)
|
||||
) {
|
||||
this.chart
|
||||
@@ -2337,6 +2494,7 @@ declare global {
|
||||
"dataset-hidden": { id: string };
|
||||
"dataset-unhidden": { id: string };
|
||||
"chart-click": ECElementEvent;
|
||||
"chart-select": ChartTimeRange;
|
||||
"legend-label-click": { id: string };
|
||||
"chart-zoom": {
|
||||
start: number;
|
||||
|
||||
@@ -5,7 +5,7 @@ import type {
|
||||
import type { HassConfig, HassEntities } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
@@ -49,7 +49,12 @@ import type {
|
||||
HomeAssistantUI,
|
||||
} from "../../types";
|
||||
import { getPeriodicAxisLabelConfig } from "./axis-label";
|
||||
import type { CustomLegendOption } from "./ha-chart-base";
|
||||
import type {
|
||||
ChartTimeRange,
|
||||
CustomLegendOption,
|
||||
HaChartBase,
|
||||
ZoomSliderMode,
|
||||
} from "./ha-chart-base";
|
||||
import "./ha-chart-base";
|
||||
import { sideTooltipPosition } from "./chart-tooltip-position";
|
||||
import "./ha-chart-tooltip-marker";
|
||||
@@ -128,6 +133,16 @@ export class StatisticsChart extends LitElement {
|
||||
@property({ attribute: "expand-legend", type: Boolean })
|
||||
public expandLegend?: boolean;
|
||||
|
||||
@property({ attribute: "select-range", type: Boolean })
|
||||
public selectRange = false;
|
||||
|
||||
@property({ attribute: "zoom-slider" })
|
||||
public zoomSlider: ZoomSliderMode = "auto";
|
||||
|
||||
@property({ attribute: false }) public highlight?: ChartTimeRange;
|
||||
|
||||
@query("ha-chart-base") private _chartBase?: HaChartBase;
|
||||
|
||||
@state() private _chartData: (LineSeriesOption | BarSeriesOption)[] = [];
|
||||
|
||||
@state() private _legendData: CustomLegendOption["data"];
|
||||
@@ -244,8 +259,12 @@ export class StatisticsChart extends LitElement {
|
||||
return html`
|
||||
<ha-chart-base
|
||||
.data=${this._chartData}
|
||||
.highlight=${this.highlight}
|
||||
.snapSelection=${this._snapSelection}
|
||||
.options=${this._chartOptions}
|
||||
.height=${this.height}
|
||||
.selectRange=${this.selectRange}
|
||||
.zoomSlider=${this.zoomSlider}
|
||||
style=${styleMap({ height: this.height })}
|
||||
@dataset-hidden=${this._datasetHidden}
|
||||
@dataset-unhidden=${this._datasetUnhidden}
|
||||
@@ -259,6 +278,34 @@ export class StatisticsChart extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
public resetZoom() {
|
||||
this._chartBase?.zoom(0, 100);
|
||||
}
|
||||
|
||||
// A click selects the period that contains it, a drag every period it
|
||||
// touches
|
||||
private _snapSelection = (
|
||||
range: ChartTimeRange
|
||||
): ChartTimeRange | undefined => {
|
||||
let start: number | undefined;
|
||||
let end: number | undefined;
|
||||
Object.values(this.statisticsData ?? {}).forEach((values) =>
|
||||
values.forEach((value) => {
|
||||
const touched =
|
||||
range.start === range.end
|
||||
? value.start <= range.start && range.start < value.end
|
||||
: value.start < range.end && value.end > range.start;
|
||||
if (touched) {
|
||||
start = Math.min(start ?? value.start, value.start);
|
||||
end = Math.max(end ?? value.end, value.end);
|
||||
}
|
||||
})
|
||||
);
|
||||
return start === undefined || end === undefined
|
||||
? undefined
|
||||
: { start, end };
|
||||
};
|
||||
|
||||
private _datasetHidden(ev: HASSDomEvent<HASSDomEvents["dataset-hidden"]>) {
|
||||
this._hiddenStats.add(ev.detail.id);
|
||||
this.requestUpdate("_hiddenStats");
|
||||
|
||||
@@ -15,6 +15,8 @@ import {
|
||||
DataZoomComponent,
|
||||
VisualMapComponent,
|
||||
ToolboxComponent,
|
||||
MarkAreaComponent,
|
||||
BrushComponent,
|
||||
} from "echarts/components";
|
||||
|
||||
// Features like Universal Transition and Label Layout
|
||||
@@ -48,6 +50,8 @@ import type {
|
||||
GridComponentOption,
|
||||
DataZoomComponentOption,
|
||||
VisualMapComponentOption,
|
||||
MarkAreaComponentOption,
|
||||
BrushComponentOption,
|
||||
} from "echarts/components";
|
||||
import type { ComposeOption } from "echarts/core";
|
||||
|
||||
@@ -62,6 +66,8 @@ export type ECOption = ComposeOption<
|
||||
| GridComponentOption
|
||||
| DataZoomComponentOption
|
||||
| VisualMapComponentOption
|
||||
| MarkAreaComponentOption
|
||||
| BrushComponentOption
|
||||
| SankeySeriesOption
|
||||
| GraphSeriesOption
|
||||
| SunburstSeriesOption
|
||||
@@ -91,6 +97,8 @@ echarts.use([
|
||||
UniversalTransition,
|
||||
CanvasRenderer,
|
||||
ToolboxComponent,
|
||||
MarkAreaComponent,
|
||||
BrushComponent,
|
||||
]);
|
||||
|
||||
export { LinearGradient };
|
||||
|
||||
Reference in new issue
Block a user