Skip to content

Commit

Permalink
feat(series): Display values as a duration (RomRider#21)
Browse files Browse the repository at this point in the history
* feat(series): Display values as a duration

* Shouldn't reduce the precision if `to_duration` is true

* Update doc
  • Loading branch information
RomRider authored Jan 30, 2021
1 parent 92fad1b commit 227f0ea
Show file tree
Hide file tree
Showing 10 changed files with 103 additions and 10 deletions.
13 changes: 13 additions & 0 deletions .devcontainer/ui-lovelace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,34 +80,47 @@ views:
name: AVG
curve: smooth
type: line
as_duration: millisecond
group_by:
duration: 10min
func: avg
- entity: sensor.random0_100
name: AVG
curve: smooth
type: line
as_duration: minute
group_by:
duration: 10min
func: avg
- entity: sensor.random0_100
curve: smooth
name: MIN
type: line
as_duration: hour
group_by:
duration: 10min
func: min
- entity: sensor.random0_100
curve: smooth
name: MAX
type: line
as_duration: day
group_by:
duration: 10min
func: max
- entity: sensor.random0_100
curve: smooth
name: LAST
type: line
as_duration: month
group_by:
duration: 10min
func: last
- entity: sensor.random0_100
curve: smooth
name: FIRST
type: line
as_duration: year
group_by:
duration: 10min
func: first
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ The card stricly validates all the options available (but not for the `apex_conf
| `invert` | boolean | `false` | v1.2.0 | Negates the data (`1` -> `-1`). Usefull to display opposites values like network in (standard)/out (inverted) |
| `data_generator` | string | | v1.2.0 | See [data_generator](#data_generator-option) |
| `offset` | string | | NEXT_VERSION | This is different from the main `offset` parameter. This is at the series level. It is only usefull if you want to display data from for eg. yesterday on top of the data from today for the same sensor and compare the data. The time displayed in the tooltip will be wrong as will the x axis information. Valid values are any negative time string, eg: `-1h`, `-12min`, `-1d`, `-1h25`, `-10sec`, ... |
| `to_duration` | string | | NEXT_VERSION | Will pretty print the states as durations. Doesn't affect the graph, only the tooltip/legend/header display. You provide the source unit of your sensor. Valid values are `millisecond`, `second`, `minute`, `hour`, `day`, `week`, `month`, `year`.<br/>Eg: if the state is `345` and `to_duration` is set to `minute` then it would display `5h45m` |


### `show` Options
Expand Down
32 changes: 31 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"localforage": "^1.9.0",
"lz-string": "^1.4.4",
"moment": "^2.29.1",
"moment-duration-format": "^2.3.2",
"moment-range": "^4.0.2",
"parse-duration": "^0.4.4",
"spark-md5": "^3.0.1",
Expand All @@ -57,6 +58,7 @@
"@semantic-release/npm": "^7.0.10",
"@semantic-release/release-notes-generator": "^9.0.1",
"@types/lz-string": "^1.3.34",
"@types/moment-duration-format": "^2.2.2",
"@types/spark-md5": "^3.0.2",
"@typescript-eslint/eslint-plugin": "^4.14.0",
"@typescript-eslint/parser": "^4.14.0",
Expand Down
36 changes: 30 additions & 6 deletions src/apex-layouts.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { HomeAssistant } from 'custom-card-helpers';
import parse from 'parse-duration';
import { DEFAULT_FLOAT_PRECISION, HOUR_24, moment } from './const';
import { DEFAULT_FLOAT_PRECISION, HOUR_24, moment, NO_VALUE } from './const';
import { ChartCardConfig } from './types';
import { computeName, computeUom, mergeDeep } from './utils';
import { computeName, computeUom, mergeDeep, prettyPrintTime } from './utils';

export function getLayoutConfig(config: ChartCardConfig, hass: HomeAssistant | undefined = undefined): unknown {
const def = {
Expand Down Expand Up @@ -59,7 +59,12 @@ export function getLayoutConfig(config: ChartCardConfig, hass: HomeAssistant | u
},
y: {
formatter: function (value, opts, conf = config, hass2 = hass) {
if (value !== null && typeof value === 'number' && !Number.isInteger(value)) {
if (
value !== null &&
typeof value === 'number' &&
!Number.isInteger(value) &&
!conf.series[opts.seriesIndex]?.as_duration
) {
value = (value as number).toFixed(
conf.series[opts.seriesIndex].float_precision === undefined
? DEFAULT_FLOAT_PRECISION
Expand All @@ -72,7 +77,10 @@ export function getLayoutConfig(config: ChartCardConfig, hass: HomeAssistant | u
undefined,
hass2?.states[conf.series[opts.seriesIndex].entity],
);
return [`<strong>${value} ${uom}</strong>`];
return conf.series[opts.seriesIndex]?.as_duration
? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
[`<strong>${prettyPrintTime(value, conf.series[opts.seriesIndex].as_duration!)}</strong>`]
: [`<strong>${value} ${uom}</strong>`];
},
},
fixed: {
Expand All @@ -95,15 +103,31 @@ export function getLayoutConfig(config: ChartCardConfig, hass: HomeAssistant | u
const name =
computeName(opts.seriesIndex, conf, undefined, hass2?.states[conf.series[opts.seriesIndex].entity]) + ':';
let value = opts.w.globals.series[opts.seriesIndex].slice(-1)[0];
if (value !== null && typeof value === 'number' && !Number.isInteger(value)) {
if (
value !== null &&
typeof value === 'number' &&
!Number.isInteger(value) &&
!conf.series[opts.seriesIndex]?.as_duration
) {
value = (value as number).toFixed(
conf.series[opts.seriesIndex].float_precision === undefined
? DEFAULT_FLOAT_PRECISION
: conf.series[opts.seriesIndex].float_precision,
);
}
const uom = computeUom(opts.seriesIndex, conf, undefined, hass2?.states[conf.series[opts.seriesIndex].entity]);
return [name, value === undefined ? `<strong>N/A ${uom}</strong>` : `<strong>${value} ${uom}</strong>`];
let valueString = '';
if (value === undefined || value === null) {
valueString = `<strong>${NO_VALUE} ${uom}</strong>`;
} else {
if (conf.series[opts.seriesIndex]?.as_duration) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
valueString = `<strong>${prettyPrintTime(value, conf.series[opts.seriesIndex].as_duration!)}</strong>`;
} else {
valueString = `<strong>${value} ${uom}</strong>`;
}
}
return [name, valueString];
},
},
stroke: {
Expand Down
11 changes: 8 additions & 3 deletions src/apexcharts-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
log,
mergeDeep,
offsetData,
prettyPrintTime,
validateInterval,
validateOffset,
} from './utils';
Expand All @@ -23,7 +24,7 @@ import GraphEntry from './graphEntry';
import { createCheckers } from 'ts-interface-checker';
import { ChartCardExternalConfig } from './types-config';
import exportedTypeSuite from './types-config-ti';
import { DEFAULT_FLOAT_PRECISION, moment } from './const';
import { DEFAULT_FLOAT_PRECISION, moment, NO_VALUE } from './const';
import {
DEFAULT_COLORS,
DEFAULT_DURATION,
Expand Down Expand Up @@ -314,7 +315,7 @@ class ChartsCard extends LitElement {
private _renderStates(): TemplateResult {
return html`
<div id="header__states">
${this._config?.series.map((_, index) => {
${this._config?.series.map((serie, index) => {
return html`
<div id="states__state">
<div id="state__value">
Expand All @@ -323,7 +324,11 @@ class ChartsCard extends LitElement {
style="${this._config?.header?.colorize_states && this._colors && this._colors.length > 0
? `color: ${this._colors[index % this._colors?.length]};`
: ''}"
>${this._lastState?.[index] === 0 ? 0 : this._lastState?.[index] || 'N/A'}</span
>${this._lastState?.[index] === 0
? 0
: (serie.as_duration
? prettyPrintTime(this._lastState?.[index], serie.as_duration)
: this._lastState?.[index]) || NO_VALUE}</span
>
<span id="uom">${computeUom(index, this._config, this._entities)}</span>
</div>
Expand Down
4 changes: 4 additions & 0 deletions src/const.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Moment from 'moment';
import { extendMoment } from 'moment-range';
import momentDurationFormatSetup from 'moment-duration-format';

momentDurationFormatSetup(Moment);
export const moment = extendMoment(Moment);
export const ONE_HOUR = 1000 * 3600;
export const HOUR_24 = ONE_HOUR * 24;
Expand Down Expand Up @@ -28,3 +30,5 @@ export const DEFAULT_COLORS = [
'#2980b9',
'#8e44ad',
];

export const NO_VALUE = 'N/A';
4 changes: 4 additions & 0 deletions src/types-config-ti.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,16 @@ export const ChartCardSeriesExternalConfig = t.iface([], {
"data_generator": t.opt("string"),
"float_precision": t.opt("number"),
"offset": t.opt("string"),
"as_duration": t.opt("ChartCardPrettyTime"),
"group_by": t.opt(t.iface([], {
"duration": t.opt("string"),
"func": t.opt("GroupByFunc"),
"fill": t.opt("GroupByFill"),
})),
});

export const ChartCardPrettyTime = t.union(t.lit('millisecond'), t.lit('second'), t.lit('minute'), t.lit('hour'), t.lit('day'), t.lit('week'), t.lit('month'), t.lit('year'));

export const GroupByFill = t.union(t.lit('null'), t.lit('last'), t.lit('zero'));

export const GroupByFunc = t.union(t.lit('raw'), t.lit('avg'), t.lit('min'), t.lit('max'), t.lit('last'), t.lit('first'), t.lit('sum'), t.lit('median'), t.lit('delta'));
Expand All @@ -63,6 +66,7 @@ const exportedTypeSuite: t.ITypeSuite = {
ChartCardExternalConfig,
ChartCardSpanExtConfig,
ChartCardSeriesExternalConfig,
ChartCardPrettyTime,
GroupByFill,
GroupByFunc,
ChartCardHeaderExternalConfig,
Expand Down
3 changes: 3 additions & 0 deletions src/types-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,16 @@ export interface ChartCardSeriesExternalConfig {
data_generator?: string;
float_precision?: number;
offset?: string;
as_duration?: ChartCardPrettyTime;
group_by?: {
duration?: string;
func?: GroupByFunc;
fill?: GroupByFill;
};
}

export type ChartCardPrettyTime = 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'year';

export type GroupByFill = 'null' | 'last' | 'zero';

export type GroupByFunc = 'raw' | 'avg' | 'min' | 'max' | 'last' | 'first' | 'sum' | 'median' | 'delta';
Expand Down
7 changes: 7 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { compress as lzStringCompress, decompress as lzStringDecompress } from '
import { ChartCardConfig, EntityCachePoints } from './types';
import { TinyColor } from '@ctrl/tinycolor';
import parse from 'parse-duration';
import { ChartCardPrettyTime } from './types-config';
import { moment, NO_VALUE } from './const';

export function compress(data: unknown): string {
return lzStringCompress(JSON.stringify(data));
Expand Down Expand Up @@ -135,3 +137,8 @@ export function offsetData(data: EntityCachePoints, offset: number | undefined):
}
return data;
}

export function prettyPrintTime(value: string | number | null, unit: ChartCardPrettyTime): string {
if (value === null) return NO_VALUE;
return moment.duration(value, unit).format('y [y] d[d] h[h] m[m] s[s] S[ms]', { trim: 'both' });
}

0 comments on commit 227f0ea

Please sign in to comment.