{"id":23060201,"url":"https://github.com/dbuezas/lovelace-plotly-graph-card","last_synced_at":"2025-05-15T18:04:34.423Z","repository":{"id":41166319,"uuid":"413812496","full_name":"dbuezas/lovelace-plotly-graph-card","owner":"dbuezas","description":"Highly customisable Lovelace card to plot interactive graphs. Brings scrolling, zooming, and much more!","archived":false,"fork":false,"pushed_at":"2025-01-21T18:55:57.000Z","size":32247,"stargazers_count":488,"open_issues_count":41,"forks_count":22,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-31T23:33:40.273Z","etag":null,"topics":["graphs","history","lovelace-custom-card","navigate","plotly","plotlyjs","plots","scroll","zoom"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dbuezas.png","metadata":{"files":{"readme":"readme.md","changelog":"changelog.md","contributing":null,"funding":".github/FUNDING.yml","license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"buy_me_a_coffee":"dbuezas"}},"created_at":"2021-10-05T12:40:17.000Z","updated_at":"2025-03-28T08:13:17.000Z","dependencies_parsed_at":"2023-02-18T22:45:42.136Z","dependency_job_id":"541132f6-8d61-4fdd-bb5d-892612730555","html_url":"https://github.com/dbuezas/lovelace-plotly-graph-card","commit_stats":null,"previous_names":[],"tags_count":93,"template":false,"template_full_name":"custom-cards/boilerplate-card","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dbuezas%2Flovelace-plotly-graph-card","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dbuezas%2Flovelace-plotly-graph-card/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dbuezas%2Flovelace-plotly-graph-card/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dbuezas%2Flovelace-plotly-graph-card/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dbuezas","download_url":"https://codeload.github.com/dbuezas/lovelace-plotly-graph-card/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247755557,"owners_count":20990620,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["graphs","history","lovelace-custom-card","navigate","plotly","plotlyjs","plots","scroll","zoom"],"created_at":"2024-12-16T03:11:37.681Z","updated_at":"2025-04-08T00:34:59.921Z","avatar_url":"https://github.com/dbuezas.png","language":"TypeScript","readme":"[![\"Buy Me A Coffee\"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/dbuezas)\n[![hacs_badge](https://img.shields.io/badge/HACS-Custom-41BDF5.svg?style=for-the-badge)](https://github.com/hacs/integration)\n\n# Plotly Graph Card\n\n\u003cimg src=\"https://user-images.githubusercontent.com/777196/202489269-184d2f30-e834-4bea-8104-5aedb7d6f2d0.gif\" width=\"300\" align=\"left\"\u003e\n\u003cimg src=\"https://user-images.githubusercontent.com/777196/215353175-97118ea7-778b-41b7-96f2-7e52c1c396d3.gif\" width=\"300\" align=\"right\" \u003e\n\n\u003cbr clear=\"both\"/\u003e\n\u003cbr clear=\"both\"/\u003e\n\n\u003cimg src=\"https://user-images.githubusercontent.com/777196/148675247-6e838783-a02a-453c-96b5-8ce86094ece2.gif\" width=\"300\" align=\"left\" \u003e\n\u003cimg width=\"300\" alt=\"image\" src=\"https://user-images.githubusercontent.com/777196/215352580-b2122f49-d37a-452f-9b59-e205bcfb76a1.png\" align=\"right\" \u003e\n\n\u003cbr clear=\"both\"/\u003e\n\u003cbr clear=\"both\"/\u003e\n\n\u003cimg width=\"300\" alt=\"image\" src=\"https://user-images.githubusercontent.com/777196/215352591-4eeec752-6abf-40cf-8214-a38c03d64b43.png\" align=\"left\" \u003e\n\n\u003cimg src=\"https://user-images.githubusercontent.com/777196/198649220-14af3cf2-8948-4174-8138-b669dce5319e.png\" width=\"300\" align=\"right\" \u003e\n\n\u003cbr clear=\"both\"/\u003e\n\n## [Post in HomeAssistant community forum](https://community.home-assistant.io/t/plotly-interactive-graph-card/347746)\n\nYou may find some extra info there in this link\n\n## [Index of examples with images](./discussion-index.md)\n\nYou can browse this list and find yamls by looking at images\n\nCreated with this [quick and dirty script](./discussion-index.mjs)\n\n## More yaml examples\n\nFind more advanced examples in [Show \u0026 Tell](https://github.com/dbuezas/lovelace-plotly-graph-card/discussions/categories/show-and-tell)\n\n## Yaml syntax validatoin\n\nWeb app to assist you with syntax validation and autocomplete: [Plotly graph card yaml editor](https://dbuezas.github.io/lovelace-plotly-graph-card/)\n\n\u003cimg width=\"300\" alt=\"image\" src=\"https://github.com/user-attachments/assets/2c9b3b85-85d4-49c4-80bc-ebc28eeaf141\" \u003e\n\n## Installation\n\n### Via Home Assistant Community Store (Recommended)\n\n1. Install [HACS](https://hacs.xyz/docs/configuration/basic)\n2. Search \u0026 Install `Plotly Graph Card`.\n\n### Manually\n\n1. Go to [Releases](https://github.com/dbuezas/lovelace-plotly-graph-card/releases)\n2. Download `plotly-graph-card.js` and copy it to your Home Assistant config dir as `\u003cconfig\u003e/www/plotly-graph-card.js`\n3. Add a resource to your dashboard configuration. There are two ways:\n   1. **Using UI**: `Settings` → `Dashboards` → `More Options icon` → `Resources` → `Add Resource` → Set Url as `/local/plotly-graph-card.js` → Set Resource type as `JavaScript Module`.\n      _Note: If you do not see the Resources menu, you will need to enable Advanced Mode in your User Profile_\n   2. **Using YAML**: Add following code to lovelace section.\n      ```resources:\n        - url: /local/plotly-graph-card.js\n          type: module\n      ```\n\n## Card Config\n\nVisual Config editor available for Basic Configs (\\*)\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - sensor.monthly_internet_energy\n  - sensor.monthly_teig_energy\n  - sensor.monthly_office_energy\n  - sensor.monthly_waschtrockner_energy\nhours_to_show: 24\nrefresh_interval: 10\n```\n\n(\\*) I'm reusing the editor of the standard History Card. Cheap, yes, but it works fine. Use yaml for advanced functionality\n\n## Advanced\n\n### Filling, line width, color\n\n![](docs/resources/example1.png)\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - entity: sensor.office_plug_wattage\n  # see examples: https://plotly.com/javascript/line-and-scatter/\n  # see full API: https://plotly.com/javascript/reference/scatter/#scatter\n  - entity: sensor.freezer_plug_power\n    fill: tozeroy\n    line:\n      color: red\n      dash: dot\n      width: 1\n\nlayout:\n  plot_bgcolor: lightgray\n  height: 400\nconfig:\n  scrollZoom: false\n\nhours_to_show: 1h\nrefresh_interval: 10 # in seconds\n```\n\n### Range Selector buttons\n\n![](docs/resources/rangeselector.apng)\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - entity: sensor.temperature\nrefresh_interval: 10\nhours_to_show: 12h\nlayout:\n  xaxis:\n    rangeselector:\n      # see examples: https://plotly.com/javascript/range-slider/\n      # see API: https://plotly.com/javascript/reference/layout/xaxis/#layout-xaxis-rangeselector\n      \"y\": 1.2\n      buttons:\n        - count: 1\n          step: minute\n        - count: 1\n          step: hour\n        - count: 12\n          step: hour\n        - count: 1\n          step: day\n        - count: 7\n          step: day\n```\n\nSee also: [autorange_after_scroll](#autorange_after_scroll)\n\nSee also: [Custom buttons](https://github.com/dbuezas/lovelace-plotly-graph-card/discussions/231#discussioncomment-4869001)\n\n![btns](https://user-images.githubusercontent.com/777196/216764329-94b9cd7e-fee9-439b-9134-95b7be626592.gif)\n\n## Features\n\n- Anything you can do with in plotlyjs except maps\n- Zoom / Pan, etc.\n- Data is loaded on demand\n- Axes are automatically configured based on the units of each trace\n- Basic configuration compatible with the History Card\n\nGet ideas from all charts in here https://plotly.com/javascript/\n\n## Entities:\n\n- `entities` translates to the `data` argument in PlotlyJS\n\n  - each `entity` will be translated to a trace inside the data array.\n    - `x` (states) and `y` (timestamps of stored states)\n    - you can add any attribute that works in a plotly trace\n    - see https://plotly.com/javascript/reference/scatter/#scatter-line for more\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - entity: sensor.temperature\n  - entity: sensor.humidity\n```\n\nAlternatively:\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - sensor.temperature\n  - sensor.humidity\n```\n\n## Color schemes\n\nChanges default line colors.\nSee more here: https://github.com/dbuezas/lovelace-plotly-graph-card/blob/master/src/parse-config/parse-color-scheme.ts\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - sensor.temperature1\n  - sensor.temperature2\ncolor_scheme: dutch_field\n# or use numbers instead 0 to 24 available:\n# color_scheme: 1\n# or pass your color scheme\n# color_scheme: [\"#1b9e77\",\"#d95f02\",\"#7570b3\",\"#e7298a\",\"#66a61e\",\"#e6ab02\",\"#a6761d\",\"red\"]\n```\n\n### Attribute values\n\nPlot the attributes of an entity\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - entity: climate.living\n    attribute: temperature\n  - entity: climate.kitchen\n    attribute: temperature\n```\n\n### Statistics support\n\nFetch and plot long-term statistics of an entity\n\n#### for entities with state_class=measurement (normal sensors, like temperature)\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - entity: sensor.temperature\n    statistic: max # `min`, `mean` of `max`\n    period: 5minute # `5minute`, `hour`, `day`, `week`, `month`, `auto` # `auto` varies the period depending on the zoom level\n```\n\n#### for entities with state_class=total (such as utility meters)\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - entity: sensor.temperature\n    statistic: state # `state` or `sum`\n    period: 5minute # `5minute`, `hour`, `day`, `week`, `month`, `auto` # `auto` varies the period depending on the zoom level\n```\n\n#### automatic period\n\nThe option `auto` makes the period relative to the currently visible time range. It picks the longest period, such that there are at least 100 datapoints in screen.\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - entity: sensor.temperature\n    statistic: mean\n    period: auto\n```\n\nIt is equivalent to writing:\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - entity: sensor.temperature\n    statistic: mean\n    period:\n      0m: 5minute\n      100h: hour\n      100d: day\n      100w: week\n      100M: month # note uppercase M for month. Lowercase are minutes\n```\n\n#### step function for auto period\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - entity: sensor.temperature\n    statistic: mean\n    period:\n      0s: 5minute\n      24h: hour # when the visible range is ≥ 1 day, use the `hour` period\n      7d: day # from 7 days on, use `day`\n      6M: week # from 6 months on, use weeks. Note Uppercase M! (lower case m means minutes)\n      1y: month # from 1 year on, use `month\n```\n\nNote that `5minute` period statistics are limited in time as normal recorder history is, contrary to other periods which keep data for years.\n\n## show_value:\n\nShows the value of the last datapoint as text in a scatter plot.\n\n\u003e Warning: don't use it with bar charts, it will only add an extra bar and no text\n\nExamples:\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - entity: sensor.temperature\n    show_value: true\n```\n\nOften one wants this to be the case for all entities\n\n```yaml\ndefaults:\n  entity:\n    show_value: true\n```\n\nIf you want to make extra room for the value, you can either increase the right margin of the whole plot like this:\n\n```yaml\nlayout:\n  margin:\n    r: 100\n```\n\nOr make space inside the the plot like this:\n\n```yaml\ntime_offset: 3h\n```\n\n## Offsets\n\nOffsets are useful to shift data in the temporal axis. For example, if you have a sensor that reports the forecasted temperature 3 hours from now, it means that the current value should be plotted in the future. With the `time_offset` attribute you can shift the data so it is placed in the correct position.\nAnother possible use is to compare past data with the current one. For example, you can plot yesterday's temperature and the current one on top of each other.\n\nThe `time_offset` flag can be specified in two places.\n**1)** When used at the top level of the configuration, it specifies how much \"future\" the graph shows by default. For example, if `hours_to_show` is 16 and `time_offset` is 3h, the graph shows the past 13 hours (16-3) plus the next 3 hours.\n**2)** When used at the trace level, it offsets the trace by the specified amount.\n\n```yaml\ntype: custom:plotly-graph\nhours_to_show: 16\ntime_offset: 3h\nentities:\n  - entity: sensor.current_temperature\n    line:\n      width: 3\n      color: orange\n  - entity: sensor.current_temperature\n    name: Temperature yesterday\n    time_offset: 1d\n    line:\n      width: 1\n      dash: dot\n      color: orange\n  - entity: sensor.temperature_12h_forecast\n    time_offset: 12h\n    name: Forecast temperature\n    line:\n      width: 1\n      dash: dot\n      color: grey\n```\n\n![Graph with offsets](docs/resources/offset-temperature.png)\n\n### Now line\n\nWhen using offsets, it is useful to have a line that indicates the current time. This can be done by using a universal function that returns a line with the current time as x value and 0 and 1 as y values. The line is then hidden from the legend.\n\n```yaml\ntype: custom:plotly-graph\nhours_to_show: 6h\ntime_offset: 3h\nentities:\n  - entity: sensor.forecast_temperature\n    yaxis: y1\n    time_offset: 3h\n  - entity: \"\"\n    name: Now\n    yaxis: y9\n    showlegend: false\n    line:\n      width: 1\n      dash: dot\n      color: deepskyblue\n    x: $ex [Date.now(), Date.now()]\n    y: [0, 1]\nlayout:\n  yaxis9:\n    visible: false\n    fixedrange: true\n```\n\n![Graph with offsets and now-line](docs/resources/offset-nowline.png)\n\n## Duration\n\nWhenever a time duration can be specified, this is the notation to use:\n\n| Unit         | Suffix | Notes    |\n| ------------ | ------ | -------- |\n| Milliseconds | `ms`   |          |\n| Seconds      | `s`    |          |\n| Minutes      | `m`    |          |\n| Hours        | `h`    |          |\n| Days         | `d`    |          |\n| Weeks        | `w`    |          |\n| Months       | `M`    | 30 days  |\n| Years        | `y`    | 365 days |\n\nExample:\n\n```yaml\ntime_offset: 3h\n```\n\n## Extra entity attributes:\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - entity: sensor.temperature_in_celsius\n    name: living temperature in Farenheit # Overrides the entity name\n    unit_of_measurement: °F # Overrides the unit\n    show_value: true # shows the last value as text\n    customdata: |\n      $fn ({states}) =\u003e \n        states.map( () =\u003e ({ extra_attr: \"hello\" }) )\n      # customdata is array with the same number of values as x axis (states)\n      # use statistics instead of states if entity is based on statistic   \n    texttemplate: \u003e- # custom format for show_value\n      \u003cb\u003e%{y}\u003c/b\u003e%{customdata.extra_attr}\u003cbr\u003e\n      # to show only 2 decimals: \"%{y:.2f}\"\n      # see more here: https://plotly.com/javascript/hover-text-and-formatting/\n      # only x, y, customdata are available as %{} template\n\n    hovertemplate: | # custom format for hover text using entity properites name and unit_of_measurement\n      $fn ({ getFromConfig }) =\u003e\n      ` \u003cb\u003e${getFromConfig(\".name\")}\u003c/b\u003e\u003cbr\u003e\n      \u003ci\u003e%{x}\u003c/i\u003e\u003cbr\u003e\n      %{y}${getFromConfig(\".unit_of_measurement\")}\n      \u003cextra\u003e\u003c/extra\u003e` # \u003cextra\u003e\u003c/extra\u003e removes text on the side of the tooltip (it otherwise defaults to the entity name)\n```\n\n### Extend_to_present\n\nThe boolean `extend_to_present` will take the last known datapoint and \"expand\" it to the present by creating a duplicate and setting its date to `now`.\nThis is useful to make the plot look fuller.\nIt's recommended to turn it off when using `offset`s, or when setting the mode of the trace to `markers`.\nDefaults to `true` for state history, and `false` for statistics.\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - entity: sensor.weather_24h_forecast\n    mode: \"markers\"\n    extend_to_present: false # true by default for state history\n  - entity: sensor.actual_temperature\n    statistics: mean\n    extend_to_present: true # false by default for statistics\n```\n\n### `filters:`\n\nFilters are used to process the data before plotting it. Inspired by [ESPHome's sensor filters](https://esphome.io/components/sensor/index.html#sensor-filters).\nFilters are applied in order.\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - entity: sensor.temperature_in_celsius\n    filters:\n      - store_var: myVar # stores the datapoints inside `vars.myVar`\n      - load_var: myVar # loads the datapoints from `vars.myVar`\n\n      # The filters below will only be applied to numeric values. Missing (unavailable) and non-numerics will be left untouched\n      - add: 5 # adds 5 to each datapoint\n      - multiply: 2 # multiplies each datapoint by 2\n      - calibrate_linear:\n        # Left of the arrow are the measurements, right are the expected values.\n        # The mapping is then approximated through linear regression, and that correction is applied to the data.\n        - 0.0 -\u003e 0.0\n        - 40.0 -\u003e 45.0\n        - 100.0 -\u003e 102.5\n      - deduplicate_adjacent # removes all adjacent duplicate values. Useful for type: marker+text\n      - delta # computes the delta between each two consecutive numeric y values.\n      - derivate: h # computes rate of change per unit of time: h # ms (milisecond), s (second), m (minute), h (hour), d (day), w (week), M (month), y (year)\n      - integrate: h # computes area under the curve in a specific unit of time using Right hand riemann integration. Same units as the derivative\n      - integrate:\n          unit: h # defaults to h\n          reset_every: 1h # Defaults to 0 (never reset). Any duration unit (ms, s, m, h, d, w, M, y).\n          offset: 30m # defaults to 0. Resets happen 30m later\n\n      - map_y_numbers: Math.sqrt(y + 10*100) # map the y coordinate of each datapoint. Same available variables as for `map_y`\n      # In the filters below, missing and non numeric datapoints will be discarded\n      - sliding_window_moving_average: # best for smoothing\n          # default parameters:\n          window_size: 10\n          extended: false # when true, smaller window sizes are used on the extremes.\n          centered: true # compensate for averaging lag by offsetting the x axis by half a window_size\n      - exponential_moving_average: # good for smoothing\n          # default parameters:\n          alpha: 0.1 # between 0 an 1. The lower the alpha, the smoother the trace.\n      - median: # got to remove outliers\n          # default parameters:\n          window_size: 10\n          extended: false\n          centered: true\n      - trendline # converts the data to a linear trendline // TODO: force line.shape = linear\n      - trendline: linear # defaults to no forecast, no formula, no error squared\n      - trendline:\n          type: polynomial # linear, polynomial, power, exponential, theil_sen, robust_polynomial, fft\n          forecast: 1d # continue trendline after present. Use global time_offset to show beyond present.\n          degree: 3 # only appliable to polynomial regression and fft.\n          show_formula: true\n          show_r2: true\n      # The filters below receive all datapoints as they come from home assistant. Y values are strings or null (unless previously mapped to numbers or any other type)\n      - map_y: 'y === \"heat\" ? 1 : 0' # map the y values of each datapoint. Variables `i` (index), `x`, `y`, `state`, `statistic`, `xs`, `ys`, `states`, `statistics`, `meta`, `vars` and `hass` are in scope. The outer quoutes are there because yaml doesn't like colons in strings without quoutes.\n      - map_x: new Date(+x + 1000) # map the x coordinate (javascript date object) of each datapoint. Same variables as map_y are in scope\n      - fn: |- # arbitrary function. Only the keys that are returned are replaced. Returning null or undefined, leaves the data unchanged (useful )\n          ({xs, ys, vars, meta, states, statistics, hass}) =\u003e {\n            # either statistics or states will be available, depending on if \"statistics\" are fetched or not\n            # attributes will be available inside states only if an attribute is picked in the trace\n            return {\n              ys: states.map(state =\u003e +state?.attributes?.current_temperature - state?.attributes?.target_temperature + hass.states[\"sensor.temperature\"].state,\n              meta: { unit_of_measurement: \"delta\" }\n            };\n          },\n      - resample: 5m # Rebuilds data so that the timestamps in xs are exact multiples of the specified interval, and without gaps. The parameter is the length of the interval and defaults to 5 minutes (see #duration for the format). This is useful when combining data from multiple entities, as the index of each datapoint will correspond to the same instant of time across them.\n      - filter: y !== null \u0026\u0026 +y \u003e 0 \u0026\u0026 x \u003e new Date(Date.now()-1000*60*60) # filter out datapoints for which this returns false. Also filters from xs, states and statistics. Same variables as map_y are in scope\n      - force_numeric # converts number-lookinig-strings to actual js numbers and removes the rest. Any filters used after this one will receive numbers, not strings or nulls. Also removes respective elements from xs, states and statistics parameters\n```\n\n#### Examples\n\n##### Celcious to farenheit\n\n```yaml\n- entity: sensor.wintergarten_clima_temperature\n  unit_of_measurement: °F\n  filters: # °F = °C×(9/5)+32\n    - multiply: 1.8\n    - add: 32\n```\n\nalternatively,\n\n```yaml\n- entity: sensor.wintergarten_clima_temperature\n  unit_of_measurement: °F\n  filters: # °F = °C×(9/5)+32\n    - map_y_numbers: y * 9/5 + 32\n```\n\n##### Energy from power\n\n```yaml\n- entity: sensor.fridge_power\n  filters:\n    - integrate: h # resulting unit_of_measurement will be Wh (watts hour)\n```\n\n##### Using state attributes\n\n```yaml\n- entity: climate.loungetrv_climate\n  attribute: current_temperature # an attribute must be set to ensure attributes are fetched.\n  filters:\n    - map_y_numbers: |\n        state.state === \"heat\" ? state.attributes.current_temperature : 0\n```\n\nor alternatively,\n\n```yaml\n- map_y_numbers: 'state.state === \"heat\" ? y : 0'\n```\n\nor alternatively,\n\n```yaml\n- map_y_numbers: |\n    {\n      const isHeat = state.state === \"heat\";\n      return isHeat ? y : 0;\n    }\n```\n\nor alternatively,\n\n```yaml\n- map_y: |\n    state?.state === \"heat\" ? state.attributes?.current_temperature : 0\n```\n\nor alternatively,\n\n```yaml\n- fn: |-\n    ({ys, states}) =\u003e ({\n      ys: states.map((state, i) =\u003e\n        state?.state === \"heat\" ? state.attributes?.current_temperature : 0\n      ),\n    }),\n```\n\nor alternatively,\n\n```yaml\n- fn: |-\n    ({ys, states}) =\u003e {\n      return {\n        ys: states.map((state, i) =\u003e\n          state?.state === \"heat\" ? state.attributes?.current_temperature : 0\n        ),\n      }\n    },\n```\n\n#### Advanced\n\n##### Debugging\n\n1. Open [your browser's devtools console](https://balsamiq.com/support/faqs/browserconsole/)\n2. Use `console.log` or the `debugger` statement to execute your map filter step by step\n   ```yaml\n   type: custom:plotly-graph\n   entities:\n     - entity: sensor.temperature_in_celsius\n       statistics: mean\n       filters:\n         - fn: console.log # open the devtools console to see the data\n         - fn: |-\n             (params) =\u003e {\n               const ys = [];\n               debugger;\n               for (let i = 0; i \u003c params.statistics.length; i++){\n                 ys.pushh(params.statistics.max); // \u003c--- here's the bug\n               }\n               return { ys };\n             }\n   ```\n\n##### Using the hass object\n\nFuncitonal filters receive `hass` (Home Assistant) as parameter, which gives you access to the current states of all entities.\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - entity: sensor.power_consumption\n    filters:\n      - map_y: parseFloat(y) * parseFloat(hass.states['sensor.cost'].state)\n```\n\n##### Using vars\n\nCompute absolute humidity\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - entity: sensor.wintergarten_clima_humidity\n    internal: true\n    filters:\n      - resample: 5m # important so the datapoints align in the x axis\n      - map_y: parseFloat(y)\n      - store_var: relative_humidity\n  - entity: sensor.wintergarten_clima_temperature\n    period: 5minute\n    name: Absolute Hty\n    unit_of_measurement: g/m³\n    filters:\n      - resample: 5m\n      - map_y: parseFloat(y)\n      - map_y: (6.112 * Math.exp((17.67 * y)/(y+243.5)) * +vars.relative_humidity.ys[i] * 2.1674)/(273.15+y);\n```\n\nCompute dew point\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - entity: sensor.openweathermap_humidity\n    internal: true\n    period: 5minute # important so the datapoints align in the x axis. Alternative to the resample filter using statistics\n    filters:\n      - map_y: parseFloat(y)\n      - store_var: relative_humidity\n  - entity: sensor.openweathermap_temperature\n    period: 5minute\n    name: Dew point\n    filters:\n      - map_y: parseFloat(y)\n      - map_y: \u003e-\n          {\n            // https://www.omnicalculator.com/physics/dew-point\n            const a = 17.625;\n            const b = 243.04;\n            const T = y;\n            const RH = vars.relative_humidity.ys[i];\n            const α = Math.log(RH/100) + a*T/(b+T);\n            const Ts = (b * α) / (a - α);\n            return Ts; \n          }\nhours_to_show: 24\n```\n\n### `internal:`\n\nsetting it to `true` will remove it from the plot, but the data will still be fetch. Useful when the data is only used by a filter in a different trace. Similar to plotly's `visibility: false`, except it internal traces won't use up new yaxes.\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - entity: sensor.temperature1\n    internal: true\n    period: 5minute\n    filters:\n      - map_y: parseFloat(y)\n      - store_var: temp1\n  - entity: sensor.temperature2\n    period: 5minute\n    name: sum of temperatures\n    filters:\n      - map_y: parseFloat(y)\n      - map_y: y + vars.temp1.ys[i]\n```\n\n### Entity click handlers\n\nWhen the legend is clicked (or doubleclicked), the trace will be hidden (or showed alone) by default. This behaviour is controlled by [layout-legend-itemclick](https://plotly.com/javascript/reference/layout/#layout-legend-itemclick).\nOn top of that, a `$fn` function can be used to add custom behaviour.\nIf a handler returns false, the default behaviour trace toggle behaviour will be disabled, but this will also inhibit the `on_legend_dblclick ` handler. Disable the default behaviour via layout-legend-itemclick instead if you want to use both click and dblclick handlers.\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - entity: sensor.temperature1\n    on_legend_click: |-\n      $fn () =\u003e (event_data) =\u003e {\n        event = new Event( \"hass-more-info\")\n        event.detail =  { entityId: 'sensor.temperature1' };\n        document.querySelector('home-assistant').dispatchEvent(event);\n        return false; // disable trace toggling\n      }\n```\n\nAlternatively, clicking on points of the trace itself.\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - entity: sensor.temperature1\n    on_click: |-\n      $fn () =\u003e (event_data) =\u003e {\n        ...\n        // WARNING: this doesn't work and I don't understand why. Help welcome\n      }\n```\n\nThere is also a double click plot handler, it works on the whole plotting area (not points of an entity). Beware that double click also autoscales the plot.\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - entity: sensor.temperature1\non_dblclick: |-\n  $fn ({ hass }) =\u003e () =\u003e {\n    hass.callService('light', 'turn_on', {\n      entity_id: 'light.portique_lumiere'\n    })\n  }\n```\n\n## Annotation and button click handlers\n\nIn a similar way, you can respond to clicks on annotations (requiring `captureevents: true`).\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - entity: sensor.temperature1\nlayout:\n  annotations:\n    - x: 1\n      xref: paper\n      \"y\": 1\n      yref: paper\n      showarrow: false\n      text: \"📊\"\n      captureevents: true\n      on_click: $ex () =\u003e { window.location=\"/history?entity_id=sensor.temperature1\"; }\n```\n\nOr to clicks on custom update menu buttons.\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - entity: sensor.temperature1\nlayout:\n  updatemenus:\n    - buttons:\n        - label: History\n          method: skip\n          on_click: $ex () =\u003e { window.location=\"/history?entity_id=sensor.temperature1\"; }\n      showactive: false\n      type: buttons\n      x: 1\n      \"y\": 1\n```\n\nSee more in plotly's [official docs](https://plotly.com/javascript/plotlyjs-events)\n\n## Universal functions\n\nJavascript functions allowed everywhere in the yaml. Evaluation is top to bottom and shallow to deep (depth first traversal).\n\nThe returned value will be used as value for the property where it is found. E.g:\n\n```js\nname: $fn ({ hass }) =\u003e hass.states[\"sensor.garden_temperature\"].state\n```\n\nor a universal expression `$ex` (the parameters and arrow are added automatically):\n\n```js\nname: $ex hass.states[\"sensor.garden_temperature\"].state\n```\n\nwhich can also take a block:\n\n```js\nname: |\n  $ex {\n    return hass.states[\"sensor.garden_temperature\"].state\n  }\n```\n\n### Available parameters:\n\nRemember you can add a `console.log(the_object_you_want_to_inspect)` and see its content in the devTools console.\n\n#### Everywhere:\n\n- `getFromConfig: (path) =\u003e value;` Pass a path (e.g `entities.0.name`) and get back its value\n- `get: (path) =\u003e value;` same as `getFromConfig`\n- `hass: HomeAssistant object;` For example: `hass.states[\"sensor.garden_temperature\"].state` to get its current state\n- `vars: Record\u003cstring, any\u003e;` You can communicate between functions with this. E.g `vars.temperatures = ys`\n- `path: string;` The path of the current function\n- `css_vars: HATheme;` The colors set by the active Home Assistant theme (see #ha_theme)\n\n#### Only inside entities\n\n- `xs: Date[];` Array of timestamps\n- `ys: YValue[];` Array of values of the sensor/attribute/statistic\n- `statistics: StatisticValue[];` Array of statistics objects\n- `states: HassEntity[];` Array of state objects\n- `meta: HassEntity[\"attributes\"];` The current attributes of the sensor\n\n#### Gotchas\n\n- The following entity attributes are required for fetching, so if another function needs the entity data it needs to be declared below them. `entity`,`attribute`,`offset`,`statistic`,`period`\n- Functions are allowed for those properties (`entity`, `attribute`, ...) but they do not receive entity data as parameters. You can still use the `hass` parameter to get the last state of an entity if you need to.\n- Functions cannot return functions for performance reasons. (feature request if you need this)\n- Defaults are not applied to the subelements returned by a function. (feature request if you need this)\n- You can get other values from the yaml with the `getFromConfig` parameter, but if they are functions they need to be defined before.\n- Any function which uses the result of a filter, needs to be placed in the YAML below the filter. For instance, `name: $ex ys.at(-1)` where the filter is modifying `ys`.\n- The same is true of consecutive filters - order matters. This is due to the fact that filters are translated internally to function calls, executed in the order they are parsed.\n\n#### Adding the last value to the entitiy's name\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - entity: sensor.garden_temperature\n    name: |\n      $ex meta.friendly_name + \" \" + ys[ys.length - 1]\n```\n\n#### Sharing data across functions\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - entity: sensor.garden_temperature\n\n    # the fn attribute has no meaning, it is just a placeholder to put a function there. It can be any name not used by plotly\n    fn: $ex vars.title = ys[ys.length - 1];\ntitle: $ex vars.title\n```\n\n#### Histograms\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - entity: sensor.openweathermap_temperature\n    x: $ex ys\n    type: histogram\ntitle: Temperature Histogram last 10 days\nhours_to_show: 10d\nraw_plotly_config: true\nlayout:\n  margin:\n    t: 0\n    l: 50\n    b: 40\n  height: 285\n  xaxis:\n    autorange: true\n```\n\n#### custom hover text\n\n```yaml\ntype: custom:plotly-graph\ntitle: hovertemplate\nentities:\n  - entity: climate.living\n    attribute: current_temperature\n    customdata: |\n      $fn ({states}) =\u003e\n        states.map( ({state, attributes}) =\u003e({\n          ...attributes,\n          state\n        })\n      )\n    hovertemplate: |-\n      \u003cbr\u003e \u003cb\u003eMode:\u003c/b\u003e %{customdata.state}\u003cbr\u003e\n      \u003cb\u003eTarget:\u003c/b\u003e%{y}\u003c/br\u003e\n      \u003cb\u003eCurrent:\u003c/b\u003e%{customdata.current_temperature}\n      \u003cextra\u003e\u003c/extra\u003e\nhours_to_show: current_day\n```\n\n## Default trace \u0026 axis styling\n\ndefault configurations for all entities and all xaxes (e.g xaxis, xaxis2, xaxis3, etc) and yaxes (e.g yaxis, yaxis2, yaxis3, etc).\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - sensor.temperature1\n  - sensor.temperature2\ndefaults:\n  entity:\n    fill: tozeroy\n    line:\n      width: 2\n  xaxes:\n    showgrid: false # Disables vertical gridlines\n  yaxes:\n    fixedrange: true # disables vertical zoom \u0026 scroll\n```\n\n## layout:\n\nTo define layout aspects, like margins, title, axes names, ...\nAnything from https://plotly.com/javascript/reference/layout/.\n\n### Home Assistant theming:\n\nToggle Home Assistant theme colors:\n\n- card-background-color\n- primary-background-color\n- primary-color\n- primary-text-color\n- secondary-text-color\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - entity: sensor.temperature_in_celsius\nha_theme: false #defaults to true\n```\n\n### Raw plotly config:\n\nToggle all in-built defaults for layout and entitites. Useful when using histograms, 3d plots, etc.\nWhen true, the `x` and `y` properties of the traces won't be automatically filled with entity data, you need to use $fn for that.\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - entity: sensor.temperature_in_celsius\n    x: $ex xs\n    y: $ex ys\nraw_plotly_config: true # defaults to false\n```\n\n## config:\n\nTo define general configurations like enabling scroll to zoom, disabling the modebar, etc.\nAnything from https://plotly.com/javascript/configuration-options/.\n\n## disable_pinch_to_zoom\n\n```yaml\ndisable_pinch_to_zoom: true # defaults to false\n```\n\nWhen true, the custom implementations of pinch-to-zoom and double-tap-drag-to-zooming will be disabled.\n\n## hours_to_show:\n\nHow many hours are shown.\nExactly the same as the history card, but more powerful\n\n### Fixed Relative Time\n\n- Decimal values (e.g `hours_to_show: 0.5`)\n- Duration strings (e.g `hours_to_show: 2h`, `3d`, `1w`, `1M`). See [Durations](#Duration)\n\n### Dynamic Relative Time\n\nShows the current day, hour, etc from beginning to end.\nThe options are: `current_minute`, `current_hour`, `current_day`, `current_week`, `current_month`, `current_quarter`, `current_year`\nIt can be combined with the global `time_offset`.\n\n## autorange_after_scroll:\n\nRemoves all data out of the visible range, and autoscales after each replot.\nParticularly useful when combined with [Range Selector Buttons](#Range-Selector-buttons)\n\n```yaml\ntype: custom:plotly-graph\nentities:\n  - entity: sensor.garden_temperature\nautorange_after_scroll: true\n```\n\n## refresh_interval:\n\nUpdate data every `refresh_interval` seconds.\n\nExamples:\n\n```yaml\nrefresh_interval: auto # (default) update automatically when an entity changes its state.\nrefresh_interval: 0 # never update.\nrefresh_interval: 5 # update every 5 seconds\n```\n\n## localization:\n\nThe locale is directly taken from Home Assistant's configuration, but can be overridden like this:\n\n```yaml\nconfig:\n  locale: ar\n```\n\n** Home Assistant custom Number and Date format will be ignored, only the language determines the locale **\n\nWhen using `hours_to_show: current_week`, the \"First day of the week\" configured in Home Assistant is used\n\n## Presets\n\nIf you find yourself reusing the same card configuration frequently, you can save it as a preset.\n\n### Setup\n\nPresets are loaded from the global `PlotlyGraphCardPresets` JS object (such that they can be shared across different dashboards).\nThe recommended way to add or modify presets is to set up a `plotly_presets.js` script in the `www` subdirectory of your `config` folder.\n```js\nwindow.PlotlyGraphCardPresets = {\n  // Add your presets here with the following format (or check the examples below)\n  // PresetName: { PresetConfiguration }\n};\n```\nTo ensure this file is loaded on every dashboard, add the following lines to your `configuration.yaml`.\n```yaml\nfrontend:\n  extra_module_url:\n    - /local/plotly_presets.js\n```\nYou might have to clear your browser cache or restart HA for changes to take effect.\n\n### Examples\n\nThe preset configuration should be defined as a JS object instead of the YAML format used by the card.\nBelow is an example YAML configuration that is split into several corresponding presets.\n\n\u003ctable\u003e\n\u003ctr\u003e\n\u003cth\u003eYAML configuration\u003c/th\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n\n```yaml\nhours_to_show: current_day\ntime_offset: -24h\ndefaults:\n  entity:\n    hovertemplate: |\n      $fn ({ get }) =\u003e (\n        `%{y:,.1f} ${get('.unit_of_measurement')}\u003cextra\u003e${get('.name')}\u003c/extra\u003e`\n      )\n  xaxes:\n    showspikes: true\n    spikemode: across\n    spikethickness: -2\n```\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003cth\u003ePreset configurations\u003c/th\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n\n```js\nwindow.PlotlyGraphCardPresets = {\n  yesterday: { // Start of preset with name 'yesterday'\n    hours_to_show: \"current_day\",\n    time_offset: \"-24h\",\n  },\n  simpleHover: { // Start of preset with name 'simpleHover'\n    defaults: {\n      entity: {\n        hovertemplate: ({get}) =\u003e (\n          `%{y:,.1f} ${get('.unit_of_measurement')}\u003cextra\u003e${get('.name')}\u003c/extra\u003e`\n        ),\n      },\n    },\n  },\n  verticalSpikes: { // Start of preset with name 'verticalSpikes'\n    defaults: {\n      xaxes: {\n        showspikes: true,\n        spikemode: \"across\",\n        spikethickness: -2,\n      },\n    },\n  },\n};\n```\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n### Usage\n\nTo use your defined templates, simply specify the preset name under the `preset` key.\nYou can also specify a list of preset names to combine several of them.\n\nE.g. with the above preset definitions, we can show yesterday's temperatures.\n```yaml\ntype: custom:plotly-graph\nentities:\n  - sensor.temperature1\n  - sensor.temperature2\npreset: yesterday\n```\n\nOr show a simplified hover tooltip together with vertical spikes.\n```yaml\ntype: custom:plotly-graph\nentities:\n  - sensor.temperature1\n  - sensor.temperature2\npreset:\n  - simpleHover\n  - verticalSpikes\n```\n\n# deprecations:\n\n### `no_theme`\n\nRenamed to `ha_theme` (inverted logic) in v3.0.0\n\n### `no_default_layout`\n\nReplaced with more general `raw_plotly_config` in v3.0.0.\nIf you were using it, you most likely can delete it and add this to your yaxes defaults:\n\n```yaml\ndefaults:\n  yaxes:\n    side: left\n    overlaying: \"y\"\n    visible: true\n    showgrid: true\n```\n\n### `offset`\n\nRenamed to time_offset in v3.0.0 to avoid conflicts with PlotlyJS bar offset configuration.\n\n### `lambda`\n\nRemoved in v3.0.0, use filters instead. There is most likely a filter (or combination) that will give you the same result, but you can also translate an old lambda to a filter like this:\n\n```yaml\nlambda: |\n  (ys,xs) =\u003e {\n    ...\n    return {x: arr_x, y: arr_y};\n  }\n# becomes\nfilters:\n  - fn: |\n    ({ys,xs}) =\u003e {\n      ...\n      return {xs: arr_x, ys: arr_y};\n    }\n```\n\nand\n\n```yaml\nlambda: |\n  (ys) =\u003e ys.map(y =\u003e y+1...etc...)\n# becomes\nfilters:\n  - map_y: y+1...etc...\n```\n\n### `entities/show_value/right_margin`\n\nRemoved in v3.0.0, use `show_value: true` instead and if necessary, set the global `time_offset` or `layout.margins.r` to make extra space to the right.\n\n### `significant_changes_only`\n\nRemoved in v3.0.0, non significant changes are also fetched now. The bandwidth savings weren't worth the issues it created.\n\n### `minimal_response`\n\nRemoved in v3.0.0, if you need access to the attributes use the 'attribute' parameter instead. It doesn't matter which attribute you pick, all of them are still accessible inside filters and universal functions\n\n# Development\n\n- Clone the repo\n- run `npm i`\n- run `npm start`\n- From a dashboard in edit mode, go to `Manage resources` and add `http://127.0.0.1:8000/plotly-graph-card.js` as url with resource type JavaScript\n- ATTENTION: The development card is `type: custom:plotly-graph-dev` (mind the extra `-dev`)\n- Either use Safari or Enable [chrome://flags/#unsafely-treat-insecure-origin-as-secure](chrome://flags/#unsafely-treat-insecure-origin-as-secure) and add your HA address (e.g http://homeassistant.local:8123): Chrome doesn't allow public network resources from requesting private-network resources - unless the public-network resource is secure (HTTPS) and the private-network resource provides appropriate (yet-undefined) CORS headers. More [here](https://stackoverflow.com/questions/66534759/chrome-cors-error-on-request-to-localhost-dev-server-from-remote-site)\n\n# Build\n\n`npm run build`\n\n# Release\n\n- Click on releases/new draft from tag in github\n- The bundle will be built by the CI action thanks to @zanna-37 in #143\n- The version in the artifact will be set from the created tag while building.\n\n# Popularity\n\n## Star History\n\n[![Star History Chart](https://api.star-history.com/svg?repos=dbuezas/lovelace-plotly-graph-card\u0026type=Date)](https://star-history.com/#dbuezas/lovelace-plotly-graph-card\u0026Date)\n\n","funding_links":["https://buymeacoffee.com/dbuezas","https://www.buymeacoffee.com/dbuezas"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdbuezas%2Flovelace-plotly-graph-card","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdbuezas%2Flovelace-plotly-graph-card","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdbuezas%2Flovelace-plotly-graph-card/lists"}