Skip to content

Commit c70ca61

Browse files
committed
feat: neopixel element
1 parent eb3b78b commit c70ca61

File tree

3 files changed

+136
-0
lines changed

3 files changed

+136
-0
lines changed

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export { SevenSegmentElement } from './7segment-element';
55
export { LCD1602Element } from './lcd1602-element';
66
export { fontA00 } from './lcd1602-font-a00';
77
export { fontA02 } from './lcd1602-font-a02';
8+
export { NeoPixelElement } from './neopixel-element';

src/neopixel-element.stories.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { storiesOf } from '@storybook/web-components';
2+
import { withKnobs, number } from '@storybook/addon-knobs';
3+
import { html } from 'lit-html';
4+
import './neopixel-element';
5+
6+
storiesOf('Neopixel', module)
7+
.addDecorator(withKnobs)
8+
.add(
9+
'Neopixel: Red',
10+
() =>
11+
html`
12+
<wokwi-neopixel
13+
r="${number('r', 1, { min: 0, max: 1, range: true, step: 0.01 })}"
14+
g="${number('g', 0, { min: 0, max: 1, range: true, step: 0.01 })}"
15+
b="${number('b', 0, { min: 0, max: 1, range: true, step: 0.01 })}"
16+
></wokwi-neopixel>
17+
`
18+
)
19+
.add(
20+
'Neopixel: White',
21+
() =>
22+
html`
23+
<wokwi-neopixel
24+
r="${number('r', 1, { min: 0, max: 1, range: true, step: 0.01 })}"
25+
g="${number('g', 1, { min: 0, max: 1, range: true, step: 0.01 })}"
26+
b="${number('b', 1, { min: 0, max: 1, range: true, step: 0.01 })}"
27+
></wokwi-neopixel>
28+
`
29+
);

src/neopixel-element.ts

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { customElement, html, LitElement, property } from 'lit-element';
2+
3+
@customElement('wokwi-neopixel')
4+
export class NeoPixelElement extends LitElement {
5+
@property() r = 0;
6+
@property() g = 0;
7+
@property() b = 0;
8+
9+
render() {
10+
const { r, g, b } = this;
11+
const spotOpacity = (value: number) => (value > 0.001 ? 0.7 + value * 0.3 : 0);
12+
const maxOpacity = Math.max(r, g, b);
13+
const minOpacity = Math.min(r, g, b);
14+
const opacityDelta = maxOpacity - minOpacity;
15+
const multiplier = Math.max(1, 2 - opacityDelta * 20);
16+
const glowBase = 0.1 + Math.max(maxOpacity * 2 - opacityDelta * 5, 0);
17+
const glowColor = (value: number) => (value > 0.005 ? 0.1 + value * 0.9 : 0);
18+
const glowOpacity = (value: number) => (value > 0.005 ? glowBase + value * (1 - glowBase) : 0);
19+
const cssVal = (value: number) =>
20+
maxOpacity ? Math.floor(Math.min(glowColor(value / maxOpacity) * multiplier, 1) * 255) : 255;
21+
const cssColor = `rgb(${cssVal(r)}, ${cssVal(g)}, ${cssVal(b)})`;
22+
const bkgWhite =
23+
242 -
24+
(maxOpacity > 0.1 && opacityDelta < 0.2
25+
? Math.floor(maxOpacity * 50 * (1 - opacityDelta / 0.2))
26+
: 0);
27+
const background = `rgb(${bkgWhite}, ${bkgWhite}, ${bkgWhite})`;
28+
return html`
29+
<svg
30+
width="5.6631mm"
31+
height="5mm"
32+
version="1.1"
33+
viewBox="0 0 5.6631 5"
34+
xmlns="http://www.w3.org/2000/svg"
35+
>
36+
<filter id="light1" x="-0.8" y="-0.8" height="2.8" width="2.8">
37+
<feGaussianBlur stdDeviation="${Math.max(0.1, maxOpacity)}" />
38+
</filter>
39+
<filter id="light2" x="-0.8" y="-0.8" height="2.2" width="2.8">
40+
<feGaussianBlur stdDeviation="0.5" />
41+
</filter>
42+
<rect x=".33308" y="0" width="5" height="5" fill="${background}" />
43+
<rect x=".016709" y=".4279" width=".35114" height=".9" fill="#eaeaea" />
44+
<rect x="0" y="3.6518" width=".35114" height=".9" fill="#eaeaea" />
45+
<rect x="5.312" y="3.6351" width=".35114" height=".9" fill="#eaeaea" />
46+
<rect x="5.312" y=".3945" width=".35114" height=".9" fill="#eaeaea" />
47+
<circle cx="2.8331" cy="2.5" r="2.1" fill="#ddd" />
48+
<circle cx="2.8331" cy="2.5" r="1.7325" fill="#e6e6e6" />
49+
<g fill="#bfbfbf">
50+
<path
51+
d="m4.3488 3.3308s-0.0889-0.087-0.0889-0.1341c0-0.047-6e-3 -1.1533-6e-3 -1.1533s-0.0591-0.1772-0.2008-0.1772c-0.14174 0-0.81501 0.012-0.81501 0.012s-0.24805 0.024-0.23624 0.3071c0.0118 0.2835 0.032 2.0345 0.032 2.0345 0.54707-0.046 1.0487-0.3494 1.3146-0.8888z"
52+
/>
53+
<path
54+
d="m4.34 1.6405h-1.0805s-0.24325 0.019-0.26204-0.2423l6e-3 -0.6241c0.57782 0.075 1.0332 0.3696 1.3366 0.8706z"
55+
/>
56+
<path
57+
d="m2.7778 2.6103-0.17127 0.124-0.8091-0.012c-0.17122-0.019-0.17062-0.2078-0.17062-0.2078-1e-3 -0.3746 1e-3 -0.2831-9e-3 -0.8122l-0.31248-0.018s0.43453-0.9216 1.4786-0.9174c-1.1e-4 0.6144-4e-3 1.2289-6e-3 1.8434z"
58+
/>
59+
<path
60+
d="m2.7808 3.0828-0.0915-0.095h-0.96857l-0.0915 0.1447-3e-3 0.1127c0 0.065-0.12108 0.08-0.12108 0.08h-0.20909c0.55906 0.9376 1.4867 0.9155 1.4867 0.9155 1e-3 -0.3845-2e-3 -0.7692-2e-3 -1.1537z"
61+
/>
62+
</g>
63+
<path
64+
d="m4.053 1.8619c-0.14174 0-0.81494 0.013-0.81494 0.013s-0.24797 0.024-0.23616 0.3084c3e-3 0.077 5e-3 0.3235 9e-3 0.5514h1.247c-2e-3 -0.33-4e-3 -0.6942-4e-3 -0.6942s-0.0593-0.1781-0.20102-0.1781z"
65+
fill="#666"
66+
/>
67+
<ellipse
68+
cx="2.5"
69+
cy="2.3"
70+
rx="0.3"
71+
ry="0.3"
72+
fill="red"
73+
opacity=${spotOpacity(r)}
74+
filter="url(#light1)"
75+
></ellipse>
76+
<ellipse
77+
cx="3.5"
78+
cy="3.2"
79+
rx="0.3"
80+
ry="0.3"
81+
fill="green"
82+
opacity=${spotOpacity(g)}
83+
filter="url(#light1)"
84+
></ellipse>
85+
<ellipse
86+
cx="3.3"
87+
cy="1.45"
88+
rx="0.3"
89+
ry="0.3"
90+
fill="blue"
91+
opacity=${spotOpacity(b)}
92+
filter="url(#light1)"
93+
></ellipse>
94+
<ellipse
95+
cx="3"
96+
cy="2.5"
97+
rx="2.2"
98+
ry="2.2"
99+
opacity="${glowOpacity(maxOpacity)}"
100+
fill="${cssColor}"
101+
filter="url(#light2)"
102+
></ellipse>
103+
</svg>
104+
`;
105+
}
106+
}

0 commit comments

Comments
 (0)