diff --git a/cypress/e2e/weather-bar.cy.ts b/cypress/e2e/weather-bar.cy.ts
index d5bd9eab..3e1f7274 100644
--- a/cypress/e2e/weather-bar.cy.ts
+++ b/cypress/e2e/weather-bar.cy.ts
@@ -292,6 +292,14 @@ describe('Weather bar', () => {
'WNW',
'WNW'
];
+ const expectedWindBearings = [
+ 253,
+ 278,
+ 293,
+ 359,
+ 285,
+ 283
+ ];
it('shows wind speed/direction if specified in config', () => {
cy.configure({
@@ -332,6 +340,21 @@ describe('Weather bar', () => {
});
});
+ it('shows wind barbs if specified in config', () => {
+ cy.configure({
+ show_wind: 'barb'
+ });
+ cy.get('weather-bar')
+ .shadow()
+ .find('div.axes > div.bar-block div.wind span')
+ .should('have.length', 6)
+ .each((el, i) => {
+ cy.wrap(el).should('have.attr', 'title', `${expectedWindSpeeds[i]} mph ${expectedWindDirections[i]}`)
+ .find('svg').should('exist')
+ .and('have.attr', 'style', `transform:rotate(${expectedWindBearings[i]}deg);`);
+ });
+ });
+
it('does not show precipitation by default', () => {
cy.get('weather-bar')
.shadow()
diff --git a/rollup.config.js b/rollup.config.js
index 45205d56..97d02f4c 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -14,6 +14,7 @@ import { ignoreSelectFiles } from './elements/ignore/select';
import { ignoreSwitchFiles } from './elements/ignore/switch';
const dev = process.env.ROLLUP_WATCH;
+if (dev) console.log('Development build');
const serveopts = {
contentBase: ['./dist'],
@@ -42,6 +43,7 @@ const plugins = [
json(),
babel({
exclude: 'node_modules/**',
+ babelHelpers: 'bundled'
}),
dev && serve(serveopts),
!dev && terser(),
diff --git a/src/editor.ts b/src/editor.ts
index 2aca2882..04ffbb2b 100644
--- a/src/editor.ts
+++ b/src/editor.ts
@@ -157,6 +157,7 @@ export class HourlyWeatherCardEditor extends ScopedRegistryHost(LitElement) impl
${localize('editor.both')}
${localize('editor.speed_only')}
${localize('editor.direction_only')}
+ ${localize('editor.barb')}
new Date(f.datetime).getDate());
const uniqueDates = new Set(dates);
diff --git a/src/lib/svg-wind-barbs/LICENSE b/src/lib/svg-wind-barbs/LICENSE
new file mode 100644
index 00000000..d6be4690
--- /dev/null
+++ b/src/lib/svg-wind-barbs/LICENSE
@@ -0,0 +1,25 @@
+BSD 2-Clause License
+
+Copyright (c) 2021-present, Qulle
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/src/lib/svg-wind-barbs/MODIFICATIONS b/src/lib/svg-wind-barbs/MODIFICATIONS
new file mode 100644
index 00000000..e38a1fc0
--- /dev/null
+++ b/src/lib/svg-wind-barbs/MODIFICATIONS
@@ -0,0 +1,7 @@
+This library code is based on the open-source work at https://github.com/qulle/svg-wind-barbs licensed under the BSD
+2-clause license. It has been modified to fit the needs of this project. Specifically:
+
+- The source was refactored into Typescript
+- The exported function now returns a bare SVG `path` rather than a fully-formed SVG with `style` tag
+- Fills and strokes are fully handled in CSS classes rather than hard-coded
+- SVG strings are returned as Lit HTML template results rather than raw strings
diff --git a/src/lib/svg-wind-barbs/index.ts b/src/lib/svg-wind-barbs/index.ts
new file mode 100644
index 00000000..b99c1c5e
--- /dev/null
+++ b/src/lib/svg-wind-barbs/index.ts
@@ -0,0 +1,91 @@
+import { svg, TemplateResult } from "lit";
+
+const WIND_BARB_0 = svg``;
+const WIND_BARB_2 = svg``;
+const WIND_BARB_5 = svg``;
+const WIND_BARB_10 = svg``;
+const WIND_BARB_15 = svg``;
+const WIND_BARB_20 = svg``;
+const WIND_BARB_25 = svg``;
+const WIND_BARB_30 = svg``;
+const WIND_BARB_35 = svg``;
+const WIND_BARB_40 = svg``;
+const WIND_BARB_45 = svg``;
+const WIND_BARB_50 = svg``;
+const WIND_BARB_55 = svg``;
+const WIND_BARB_60 = svg``;
+const WIND_BARB_65 = svg``;
+const WIND_BARB_70 = svg``;
+const WIND_BARB_75 = svg``;
+const WIND_BARB_80 = svg``;
+const WIND_BARB_85 = svg``;
+const WIND_BARB_90 = svg``;
+const WIND_BARB_95 = svg``;
+const WIND_BARB_100 = svg``;
+const WIND_BARB_105 = svg``;
+const WIND_BARB_110 = svg``;
+const WIND_BARB_115 = svg``;
+const WIND_BARB_120 = svg``;
+const WIND_BARB_125 = svg``;
+const WIND_BARB_130 = svg``;
+const WIND_BARB_135 = svg``;
+const WIND_BARB_140 = svg``;
+const WIND_BARB_145 = svg``;
+const WIND_BARB_150 = svg``;
+const WIND_BARB_155 = svg``;
+const WIND_BARB_160 = svg``;
+const WIND_BARB_165 = svg``;
+const WIND_BARB_170 = svg``;
+const WIND_BARB_175 = svg``;
+const WIND_BARB_180 = svg``;
+const WIND_BARB_185 = svg``;
+const WIND_BARB_190 = svg``;
+
+/**
+ * Get SVG barb for the specified wind speed
+ * @param windSpeed Wind speed in meters per second
+ * @returns Lit template of SVG path for barb
+ */
+export function getWindBarbSVG(windSpeed: number): TemplateResult<2> {
+ if (windSpeed >= 0.0 && windSpeed < 1.0) return WIND_BARB_0;
+ else if (windSpeed >= 1.0 && windSpeed < 2.5) return WIND_BARB_2;
+ else if (windSpeed >= 2.5 && windSpeed < 5.0) return WIND_BARB_5;
+ else if (windSpeed >= 5.0 && windSpeed < 7.5) return WIND_BARB_10;
+ else if (windSpeed >= 7.5 && windSpeed < 10.0) return WIND_BARB_15;
+ else if (windSpeed >= 10.0 && windSpeed < 12.5) return WIND_BARB_20;
+ else if (windSpeed >= 12.5 && windSpeed < 15.0) return WIND_BARB_25;
+ else if (windSpeed >= 15.0 && windSpeed < 17.5) return WIND_BARB_30;
+ else if (windSpeed >= 17.5 && windSpeed < 20.0) return WIND_BARB_35;
+ else if (windSpeed >= 20.0 && windSpeed < 22.5) return WIND_BARB_40;
+ else if (windSpeed >= 22.5 && windSpeed < 25.0) return WIND_BARB_45;
+ else if (windSpeed >= 25.0 && windSpeed < 27.5) return WIND_BARB_50;
+ else if (windSpeed >= 27.5 && windSpeed < 30.0) return WIND_BARB_55;
+ else if (windSpeed >= 30.0 && windSpeed < 32.5) return WIND_BARB_60;
+ else if (windSpeed >= 32.5 && windSpeed < 35.0) return WIND_BARB_65;
+ else if (windSpeed >= 35.0 && windSpeed < 37.5) return WIND_BARB_70;
+ else if (windSpeed >= 37.5 && windSpeed < 40.0) return WIND_BARB_75;
+ else if (windSpeed >= 40.0 && windSpeed < 42.5) return WIND_BARB_80;
+ else if (windSpeed >= 42.5 && windSpeed < 45.0) return WIND_BARB_85;
+ else if (windSpeed >= 45.0 && windSpeed < 47.5) return WIND_BARB_90;
+ else if (windSpeed >= 47.5 && windSpeed < 50.0) return WIND_BARB_95;
+ else if (windSpeed >= 50.0 && windSpeed < 52.5) return WIND_BARB_100;
+ else if (windSpeed >= 52.5 && windSpeed < 55.0) return WIND_BARB_105;
+ else if (windSpeed >= 55.0 && windSpeed < 57.5) return WIND_BARB_110;
+ else if (windSpeed >= 57.5 && windSpeed < 60.0) return WIND_BARB_115;
+ else if (windSpeed >= 60.0 && windSpeed < 62.5) return WIND_BARB_120;
+ else if (windSpeed >= 62.5 && windSpeed < 65.0) return WIND_BARB_125;
+ else if (windSpeed >= 65.0 && windSpeed < 67.5) return WIND_BARB_130;
+ else if (windSpeed >= 67.5 && windSpeed < 70.0) return WIND_BARB_135;
+ else if (windSpeed >= 70.0 && windSpeed < 72.5) return WIND_BARB_140;
+ else if (windSpeed >= 72.5 && windSpeed < 75.0) return WIND_BARB_145;
+ else if (windSpeed >= 75.0 && windSpeed < 77.5) return WIND_BARB_150;
+ else if (windSpeed >= 77.5 && windSpeed < 80.0) return WIND_BARB_155;
+ else if (windSpeed >= 80.0 && windSpeed < 82.5) return WIND_BARB_160;
+ else if (windSpeed >= 82.5 && windSpeed < 85.0) return WIND_BARB_165;
+ else if (windSpeed >= 85.0 && windSpeed < 87.5) return WIND_BARB_170;
+ else if (windSpeed >= 87.5 && windSpeed < 90.0) return WIND_BARB_175;
+ else if (windSpeed >= 90.0 && windSpeed < 92.5) return WIND_BARB_180;
+ else if (windSpeed >= 92.5 && windSpeed < 95.0) return WIND_BARB_185;
+ else if (windSpeed >= 95.0 && windSpeed < 97.5) return WIND_BARB_190;
+ else return WIND_BARB_0;
+}
diff --git a/src/localize/languages/de.json b/src/localize/languages/de.json
index 8639f099..3c9f184c 100644
--- a/src/localize/languages/de.json
+++ b/src/localize/languages/de.json
@@ -18,7 +18,8 @@
"neither": "Weder",
"both": "Beide",
"speed_only": "Nur Geschwindigkeit",
- "direction_only": "Nur Richtung"
+ "direction_only": "Nur Richtung",
+ "barb": "Als Windbarbe"
},
"errors": {
"missing_entity": "Keine Wetter-Entität festgelegt",
diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json
index ad3851e6..f53ba222 100644
--- a/src/localize/languages/en.json
+++ b/src/localize/languages/en.json
@@ -18,7 +18,8 @@
"neither": "Neither",
"both": "Both",
"speed_only": "Speed only",
- "direction_only": "Direction only"
+ "direction_only": "Direction only",
+ "barb": "As wind barb"
},
"errors": {
"missing_entity": "entity is missing in configuration",
diff --git a/src/localize/languages/es.json b/src/localize/languages/es.json
index 1cc3c338..b4651491 100644
--- a/src/localize/languages/es.json
+++ b/src/localize/languages/es.json
@@ -18,7 +18,8 @@
"neither": "Ninguno de los dos",
"both": "Ambas cosas",
"speed_only": "Solo velocidad",
- "direction_only": "Solo dirección"
+ "direction_only": "Solo dirección",
+ "barb": "Como púa de viento"
},
"errors": {
"missing_entity": "falta la entidad en la configuración",
diff --git a/src/localize/languages/fr.json b/src/localize/languages/fr.json
index 57d887e8..3eeb4286 100644
--- a/src/localize/languages/fr.json
+++ b/src/localize/languages/fr.json
@@ -18,7 +18,8 @@
"neither": "Ni",
"both": "Tous les deux",
"speed_only": "Vitesse uniquement",
- "direction_only": "Sens uniquement"
+ "direction_only": "Sens uniquement",
+ "barb": "Comme barbillon de vent"
},
"errors": {
"missing_entity": "Entité manquante dans la configuration",
diff --git a/src/localize/languages/it.json b/src/localize/languages/it.json
index 61dbca98..f0eca94f 100644
--- a/src/localize/languages/it.json
+++ b/src/localize/languages/it.json
@@ -18,7 +18,8 @@
"neither": "Né",
"both": "Tutti e due",
"speed_only": "Solo velocità",
- "direction_only": "Solo direzione"
+ "direction_only": "Solo direzione",
+ "barb": "Come una punta di vento"
},
"errors": {
"missing_entity": "entità mancante nella configurazione",
@@ -64,4 +65,4 @@
"nw": "NO",
"nnw": "NNO"
}
-}
+}
\ No newline at end of file
diff --git a/src/localize/languages/nb.json b/src/localize/languages/nb.json
index 29380e74..b0b6e625 100644
--- a/src/localize/languages/nb.json
+++ b/src/localize/languages/nb.json
@@ -18,7 +18,8 @@
"neither": "Ingen",
"both": "Både",
"speed_only": "Kun hastighet",
- "direction_only": "Kun retning"
+ "direction_only": "Kun retning",
+ "barb": "Som vindmothak"
},
"errors": {
"missing_entity": "entity mangler i konfigurasjonen",
diff --git a/src/localize/languages/nl.json b/src/localize/languages/nl.json
index 335fb767..aa721f7d 100644
--- a/src/localize/languages/nl.json
+++ b/src/localize/languages/nl.json
@@ -18,7 +18,8 @@
"neither": "Geen van beide",
"both": "Beide",
"speed_only": "Alleen snelheid",
- "direction_only": "Alleen richting"
+ "direction_only": "Alleen richting",
+ "barb": "Als windhaak"
},
"errors": {
"missing_entity": "entity ontbreekt in configuratie",
@@ -64,4 +65,4 @@
"nw": "NW",
"nnw": "NNW"
}
-}
+}
\ No newline at end of file
diff --git a/src/localize/languages/pl.json b/src/localize/languages/pl.json
index 69b4aa06..a63ba923 100644
--- a/src/localize/languages/pl.json
+++ b/src/localize/languages/pl.json
@@ -18,7 +18,8 @@
"neither": "Żaden",
"both": "Obie",
"speed_only": "Tylko prędkość",
- "direction_only": "Tylko kierunek"
+ "direction_only": "Tylko kierunek",
+ "barb": "Jak kolce wiatru"
},
"errors": {
"missing_entity": "encja nie istnieje",
diff --git a/src/localize/languages/pt-BR.json b/src/localize/languages/pt-BR.json
index 06d29084..edc36da6 100644
--- a/src/localize/languages/pt-BR.json
+++ b/src/localize/languages/pt-BR.json
@@ -18,7 +18,8 @@
"neither": "Nenhum",
"both": "Ambos",
"speed_only": "Apenas velocidade",
- "direction_only": "Apenas direção"
+ "direction_only": "Apenas direção",
+ "barb": "Como farpa de vento"
},
"errors": {
"missing_entity": "entidade está faltando na configuração",
diff --git a/src/localize/languages/pt.json b/src/localize/languages/pt.json
index b6db91b0..33b8e8cf 100644
--- a/src/localize/languages/pt.json
+++ b/src/localize/languages/pt.json
@@ -18,7 +18,8 @@
"neither": "Nenhum",
"both": "Ambos",
"speed_only": "Apenas velocidade",
- "direction_only": "Apenas direção"
+ "direction_only": "Apenas direção",
+ "barb": "Como farpa de vento"
},
"errors": {
"missing_entity": "A entidade não existe na configuração",
@@ -64,4 +65,4 @@
"nw": "NO",
"nnw": "NNO"
}
-}
+}
\ No newline at end of file
diff --git a/src/types.ts b/src/types.ts
index e6f082c6..f8e7fc36 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -7,7 +7,7 @@ declare global {
}
}
-export type WindType = 'true' | 'false' | 'speed' | 'direction';
+export type WindType = 'true' | 'false' | 'speed' | 'direction' | 'barb';
export interface HourlyWeatherCardConfig extends LovelaceCardConfig {
type: string;
@@ -21,7 +21,7 @@ export interface HourlyWeatherCardConfig extends LovelaceCardConfig {
colors?: ColorConfig;
hide_hours?: boolean;
hide_temperatures?: boolean;
- show_wind?: WindType; // 'true' | 'false' | 'speed' | 'direction'
+ show_wind?: WindType; // 'true' | 'false' | 'speed' | 'direction' | 'barb'
show_precipitation_amounts?: boolean;
label_spacing?: string; // number
test_gui?: boolean;
@@ -74,7 +74,9 @@ export interface SegmentTemperature {
export interface SegmentWind {
hour: string,
windSpeed: string,
- windDirection: string
+ windSpeedRawMS: number,
+ windDirection: string,
+ windDirectionRaw: number
}
export interface SegmentPrecipitation {
diff --git a/src/weather-bar.ts b/src/weather-bar.ts
index 8a19bfc4..7b7cdf6f 100644
--- a/src/weather-bar.ts
+++ b/src/weather-bar.ts
@@ -3,6 +3,7 @@ import { property } from "lit/decorators.js";
import { StyleInfo, styleMap } from 'lit/directives/style-map.js';
import tippy, { Instance } from 'tippy.js';
import { LABELS, ICONS } from "./conditions";
+import { getWindBarbSVG } from "./lib/svg-wind-barbs";
import type { ColorMap, ConditionSpan, SegmentTemperature, SegmentWind, SegmentPrecipitation, WindType } from "./types";
const tippyStyles: string = process.env.TIPPY_CSS || '';
@@ -71,14 +72,18 @@ export class WeatherBar extends LitElement {
const hideTemperature = this.hide_temperatures || skipLabel;
const showWindSpeed = (this.show_wind === 'true' || this.show_wind === 'speed') && !skipLabel;
const showWindDirection = (this.show_wind === 'true' || this.show_wind === 'direction') && !skipLabel;
+ const showWindBarb = this.show_wind === 'barb' && !skipLabel;
const showPrecipitationAmounts = this.show_precipitation_amounts && !skipLabel;
const { hour, temperature } = this.temperatures[i];
- const { windSpeed, windDirection } = this.wind[i];
+ const { windSpeed, windSpeedRawMS, windDirection, windDirectionRaw } = this.wind[i];
const wind: TemplateResult[] = [];
if (showWindSpeed) wind.push(html`${windSpeed}`);
if (showWindSpeed && showWindDirection) wind.push(html`
`);
if (showWindDirection) wind.push(html`${windDirection}`);
+ if (showWindBarb) wind.push(html`
+ ${this.getWindBarb(windSpeedRawMS, windDirectionRaw)}
+ `);
const { precipitationAmount } = this.precipitation[i];
barBlocks.push(html`
@@ -133,6 +138,15 @@ export class WeatherBar extends LitElement {
`;
}
+ private getWindBarb(speed: number, direction: number): TemplateResult {
+ const svgStyles = {
+ transform: `rotate(${direction}deg)`
+ };
+ return html``;
+ }
+
static styles = [unsafeCSS(tippyStyles), css`
.main {
--color-clear-night: #111;
@@ -275,5 +289,22 @@ export class WeatherBar extends LitElement {
line-height: 1.1rem;
padding-top: 0.1rem;
}
+ .barb {
+ transform-box: fill-box;
+ transform-origin: center;
+ height: 3rem;
+ }
+ .svg-wb, .svg-wb-fill {
+ fill: var(--primary-text-color, black);
+ }
+ .svg-wb, .svg-wb-stroke {
+ stroke: var(--primary-text-color, black);
+ }
+ .svg-wb {
+ stroke-width: 3;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+ stroke-miterlimit: 10;
+ }
`];
}