@opstel/modern-gauges 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -0
- package/dist/index.cjs.js +17 -0
- package/dist/index.es.js +198 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# modern-gauge
|
|
2
|
+
|
|
3
|
+
An animated half-circle SVG gauge component for React. Zero runtime dependencies beyond React itself.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Smooth animated arc fill that interpolates from the previous value on each update
|
|
8
|
+
- Gradient color progression across the arc and tick marks
|
|
9
|
+
- Automatically generated minor tick marks between major labels
|
|
10
|
+
- Responsive — fills its container via SVG `viewBox`
|
|
11
|
+
- Optional card wrapper with shadow, or render bare into any layout
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install modern-gauge
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```jsx
|
|
22
|
+
import { HalfGauge } from 'modern-gauge';
|
|
23
|
+
|
|
24
|
+
function Dashboard() {
|
|
25
|
+
return (
|
|
26
|
+
<HalfGauge
|
|
27
|
+
value={72}
|
|
28
|
+
ticks={[0, 20, 40, 60, 80, 100]}
|
|
29
|
+
colors={['#f44336', '#ffeb3b', '#4caf50']}
|
|
30
|
+
title="CPU (%)"
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Props
|
|
37
|
+
|
|
38
|
+
| Prop | Type | Default | Description |
|
|
39
|
+
|---|---|---|---|
|
|
40
|
+
| `value` | `number` | — | Current value to display |
|
|
41
|
+
| `ticks` | `number[]` | `[0, 5, 10, 15, 20]` | Ordered scale values. First = min, last = max. Labels are rendered at each tick; minor ticks are inserted automatically between them. |
|
|
42
|
+
| `colors` | `string[]` | `['#f44336', '#9c27b0']` | Two or more CSS colors forming the gradient from min to max. |
|
|
43
|
+
| `title` | `string` | — | Optional label displayed below the value |
|
|
44
|
+
| `withoutBackground` | `boolean` | `false` | When `true`, renders only the gauge SVG with no card wrapper. Use this when embedding inside your own container. |
|
|
45
|
+
| `className` | `string` | — | CSS class applied to the card wrapper (ignored when `withoutBackground` is true) |
|
|
46
|
+
| `style` | `object` | — | Inline styles merged into the card wrapper (ignored when `withoutBackground` is true) |
|
|
47
|
+
|
|
48
|
+
## Examples
|
|
49
|
+
|
|
50
|
+
### Minimal
|
|
51
|
+
|
|
52
|
+
```jsx
|
|
53
|
+
<HalfGauge value={12} />
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Custom scale and colors
|
|
57
|
+
|
|
58
|
+
```jsx
|
|
59
|
+
<HalfGauge
|
|
60
|
+
value={3500}
|
|
61
|
+
ticks={[0, 1000, 2000, 3000, 4000, 5000, 6000]}
|
|
62
|
+
colors={['#4caf50', '#ffeb3b', '#f44336']}
|
|
63
|
+
title="RPM"
|
|
64
|
+
/>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Embedded in a custom container (no card)
|
|
68
|
+
|
|
69
|
+
```jsx
|
|
70
|
+
<div style={{ width: 200, height: 130 }}>
|
|
71
|
+
<HalfGauge
|
|
72
|
+
value={55}
|
|
73
|
+
ticks={[0, 25, 50, 75, 100]}
|
|
74
|
+
colors={['#2196f3', '#e91e63']}
|
|
75
|
+
withoutBackground
|
|
76
|
+
/>
|
|
77
|
+
</div>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Custom card size via style prop
|
|
81
|
+
|
|
82
|
+
```jsx
|
|
83
|
+
<HalfGauge
|
|
84
|
+
value={8}
|
|
85
|
+
ticks={[0, 2, 4, 6, 8, 10]}
|
|
86
|
+
title="Score"
|
|
87
|
+
style={{ width: '12rem', height: '10rem', flexGrow: 0 }}
|
|
88
|
+
/>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Tick behaviour
|
|
92
|
+
|
|
93
|
+
The `ticks` array defines where major labels appear. Minor ticks are inserted automatically:
|
|
94
|
+
|
|
95
|
+
- **≤ 5 major ticks** → 4 minor ticks per gap
|
|
96
|
+
- **6–16 major ticks** → 2 minor ticks per gap
|
|
97
|
+
- **> 16 major ticks** → no minor ticks; every other label is hidden to avoid crowding
|
|
98
|
+
|
|
99
|
+
## Peer Dependencies
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"react": ">=17.0.0",
|
|
104
|
+
"react-dom": ">=17.0.0"
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
MIT
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("react/jsx-runtime"),y=require("react");(function(){if(typeof document>"u")return;const a="__modern-half-gauge-styles__";if(document.getElementById(a))return;const o=document.createElement("style");o.id=a,o.textContent=`
|
|
2
|
+
@keyframes modern-gauge-fill-arc {
|
|
3
|
+
from { stroke-dasharray: var(--mg-initial-dash, 0) 500; }
|
|
4
|
+
to { stroke-dasharray: var(--mg-final-dash, 0) 500; }
|
|
5
|
+
}
|
|
6
|
+
@keyframes modern-gauge-fill-tick {
|
|
7
|
+
from { stroke: #9e9e9e; }
|
|
8
|
+
to { stroke: var(--mg-tick-color, #9e9e9e); }
|
|
9
|
+
}
|
|
10
|
+
.modern-gauge-arc {
|
|
11
|
+
animation: modern-gauge-fill-arc 1s ease-out forwards;
|
|
12
|
+
}
|
|
13
|
+
.modern-gauge-tick {
|
|
14
|
+
animation: modern-gauge-fill-tick 1s ease-out forwards;
|
|
15
|
+
animation-delay: var(--mg-tick-delay, 0s);
|
|
16
|
+
}
|
|
17
|
+
`,document.head.appendChild(o)})();const C=({value:h,colors:a,title:o,minMax:f})=>{const n=f||[0,5,10,15,20],m=n.at(0)??0,Y=n.at(-1)??1,g=h??m,x=y.useRef(`mg-${Math.random().toString(36).substring(2,9)}`).current,p=y.useRef(m);y.useEffect(()=>{const e=setTimeout(()=>{p.current=g},1e3);return()=>clearTimeout(e)},[g]);const j=Y-m||1,b=Math.min(1,Math.max(0,(p.current-m)/j)),v=Math.min(1,Math.max(0,(g-m)/j)),M=Math.PI*40,L=v*M,w=b*M,S=n.length>8;let c=[];if(n.length<17){const e=n.length<6?4:2,i=e+1;for(let r=0;r<n.length;r++)if(c.push({value:n[r],isMajor:!0}),r<n.length-1){const d=n[r],l=(n[r+1]-d)/i;for(let u=1;u<=e;u++)c.push({value:d+u*l,isMajor:!1})}}else c=n.map(e=>({value:e,isMajor:!0}));const I=c.filter(e=>e.isMajor),X=c.map((e,i)=>{const r=c.length-1,s=(i/r*180-180)*(Math.PI/180),l=36,u=e.isMajor?5:2.5,T=I.indexOf(e),F=e.isMajor&&(!S||T%2===0),R=50+l*Math.cos(s),A=45+l*Math.sin(s),E=50+(l-u)*Math.cos(s),G=45+(l-u)*Math.sin(s),k=l-10,P=50+k*Math.cos(s),W=45+k*Math.sin(s);return{value:e.value,innerX:E,innerY:G,outerX:R,outerY:A,labelX:P,labelY:W,showLabel:F}}),$=a&&a.length>=2?a:["#f44336","#9c27b0"];return t.jsx("div",{style:{height:"100%",width:"100%"},children:t.jsxs("div",{style:{width:"100%",height:"100%",position:"relative"},children:[t.jsxs("svg",{width:"100%",height:"100%",viewBox:"0 0 100 55",children:[t.jsx("defs",{children:t.jsx("linearGradient",{id:`gradient-${x}`,gradientUnits:"userSpaceOnUse",x1:10,y1:45,x2:90,y2:45,children:$.map((e,i)=>t.jsx("stop",{offset:`${i/($.length-1)*100}%`,stopColor:e},i))})}),t.jsx("path",{stroke:"#9e9e9e",strokeWidth:"3",strokeLinecap:"round",fill:"none",d:"M 10 45 A 40 40 0 1 1 90 45"}),t.jsx("path",{className:"modern-gauge-arc",fill:"none",strokeWidth:"3",stroke:`url(#gradient-${x})`,strokeLinecap:"round",strokeDashoffset:"0",style:{"--mg-initial-dash":`${w}`,"--mg-final-dash":`${L}`},d:"M 10 45 A 40 40 0 1 1 90 45"}),X.map((e,i)=>{const r=i/(X.length-1),d=r<=v,s=r;return t.jsxs("g",{children:[t.jsx("line",{x1:e.innerX,y1:e.innerY,x2:e.outerX,y2:e.outerY,strokeWidth:"1",strokeLinecap:"round",stroke:"#9e9e9e"}),t.jsx("line",{className:"modern-gauge-tick",x1:e.innerX,y1:e.innerY,x2:e.outerX,y2:e.outerY,strokeWidth:"1",strokeLinecap:"round",style:{"--mg-tick-color":`url(#gradient-${x})`,"--mg-tick-delay":d?`${s}s`:"0s",animationPlayState:d?"running":"paused"}}),e.showLabel&&t.jsx("text",{x:e.labelX,y:e.labelY,textAnchor:"middle",dominantBaseline:"middle",fontSize:"5",fill:"#9e9e9e",fontFamily:"sans-serif",children:e.value})]},e.value)})]}),t.jsxs("div",{style:{position:"absolute",top:"70%",left:"50%",transform:"translate(-50%, -50%)",textAlign:"center",pointerEvents:"none",whiteSpace:"nowrap"},children:[t.jsx("div",{style:{fontSize:"3rem",fontWeight:500,lineHeight:1.2},children:h}),o&&t.jsx("div",{style:{fontSize:"0.9rem",marginTop:"4px"},children:o})]})]},g)})},O=({value:h,colors:a,title:o,minMax:f})=>t.jsx(C,{value:h,colors:a,title:o,minMax:f});exports.HalfGauge=O;
|
package/dist/index.es.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { jsx as t, jsxs as f } from "react/jsx-runtime";
|
|
2
|
+
import { useRef as j, useEffect as V } from "react";
|
|
3
|
+
(function() {
|
|
4
|
+
if (typeof document > "u") return;
|
|
5
|
+
const o = "__modern-half-gauge-styles__";
|
|
6
|
+
if (document.getElementById(o)) return;
|
|
7
|
+
const s = document.createElement("style");
|
|
8
|
+
s.id = o, s.textContent = `
|
|
9
|
+
@keyframes modern-gauge-fill-arc {
|
|
10
|
+
from { stroke-dasharray: var(--mg-initial-dash, 0) 500; }
|
|
11
|
+
to { stroke-dasharray: var(--mg-final-dash, 0) 500; }
|
|
12
|
+
}
|
|
13
|
+
@keyframes modern-gauge-fill-tick {
|
|
14
|
+
from { stroke: #9e9e9e; }
|
|
15
|
+
to { stroke: var(--mg-tick-color, #9e9e9e); }
|
|
16
|
+
}
|
|
17
|
+
.modern-gauge-arc {
|
|
18
|
+
animation: modern-gauge-fill-arc 1s ease-out forwards;
|
|
19
|
+
}
|
|
20
|
+
.modern-gauge-tick {
|
|
21
|
+
animation: modern-gauge-fill-tick 1s ease-out forwards;
|
|
22
|
+
animation-delay: var(--mg-tick-delay, 0s);
|
|
23
|
+
}
|
|
24
|
+
`, document.head.appendChild(s);
|
|
25
|
+
})();
|
|
26
|
+
const _ = ({ value: m, colors: o, title: s, minMax: p }) => {
|
|
27
|
+
const n = p || [0, 5, 10, 15, 20], h = n.at(0) ?? 0, b = n.at(-1) ?? 1, g = m ?? h, y = j(`mg-${Math.random().toString(36).substring(2, 9)}`).current, x = j(h);
|
|
28
|
+
V(() => {
|
|
29
|
+
const e = setTimeout(() => {
|
|
30
|
+
x.current = g;
|
|
31
|
+
}, 1e3);
|
|
32
|
+
return () => clearTimeout(e);
|
|
33
|
+
}, [g]);
|
|
34
|
+
const v = b - h || 1, L = Math.min(
|
|
35
|
+
1,
|
|
36
|
+
Math.max(0, (x.current - h) / v)
|
|
37
|
+
), M = Math.min(
|
|
38
|
+
1,
|
|
39
|
+
Math.max(0, (g - h) / v)
|
|
40
|
+
), X = Math.PI * 40, w = M * X, S = L * X, I = n.length > 8;
|
|
41
|
+
let c = [];
|
|
42
|
+
if (n.length < 17) {
|
|
43
|
+
const e = n.length < 6 ? 4 : 2, i = e + 1;
|
|
44
|
+
for (let r = 0; r < n.length; r++)
|
|
45
|
+
if (c.push({ value: n[r], isMajor: !0 }), r < n.length - 1) {
|
|
46
|
+
const d = n[r], l = (n[r + 1] - d) / i;
|
|
47
|
+
for (let u = 1; u <= e; u++)
|
|
48
|
+
c.push({ value: d + u * l, isMajor: !1 });
|
|
49
|
+
}
|
|
50
|
+
} else
|
|
51
|
+
c = n.map((e) => ({ value: e, isMajor: !0 }));
|
|
52
|
+
const F = c.filter((e) => e.isMajor), $ = c.map((e, i) => {
|
|
53
|
+
const r = c.length - 1, a = (i / r * 180 - 180) * (Math.PI / 180), l = 36, u = e.isMajor ? 5 : 2.5, T = F.indexOf(e), A = e.isMajor && (!I || T % 2 === 0), E = 50 + l * Math.cos(a), W = 45 + l * Math.sin(a), C = 50 + (l - u) * Math.cos(a), G = 45 + (l - u) * Math.sin(a), Y = l - 10, P = 50 + Y * Math.cos(a), R = 45 + Y * Math.sin(a);
|
|
54
|
+
return {
|
|
55
|
+
value: e.value,
|
|
56
|
+
innerX: C,
|
|
57
|
+
innerY: G,
|
|
58
|
+
outerX: E,
|
|
59
|
+
outerY: W,
|
|
60
|
+
labelX: P,
|
|
61
|
+
labelY: R,
|
|
62
|
+
showLabel: A
|
|
63
|
+
};
|
|
64
|
+
}), k = o && o.length >= 2 ? o : ["#f44336", "#9c27b0"];
|
|
65
|
+
return /* @__PURE__ */ t("div", { style: { height: "100%", width: "100%" }, children: /* @__PURE__ */ f(
|
|
66
|
+
"div",
|
|
67
|
+
{
|
|
68
|
+
style: { width: "100%", height: "100%", position: "relative" },
|
|
69
|
+
children: [
|
|
70
|
+
/* @__PURE__ */ f("svg", { width: "100%", height: "100%", viewBox: "0 0 100 55", children: [
|
|
71
|
+
/* @__PURE__ */ t("defs", { children: /* @__PURE__ */ t(
|
|
72
|
+
"linearGradient",
|
|
73
|
+
{
|
|
74
|
+
id: `gradient-${y}`,
|
|
75
|
+
gradientUnits: "userSpaceOnUse",
|
|
76
|
+
x1: 10,
|
|
77
|
+
y1: 45,
|
|
78
|
+
x2: 90,
|
|
79
|
+
y2: 45,
|
|
80
|
+
children: k.map((e, i) => /* @__PURE__ */ t(
|
|
81
|
+
"stop",
|
|
82
|
+
{
|
|
83
|
+
offset: `${i / (k.length - 1) * 100}%`,
|
|
84
|
+
stopColor: e
|
|
85
|
+
},
|
|
86
|
+
i
|
|
87
|
+
))
|
|
88
|
+
}
|
|
89
|
+
) }),
|
|
90
|
+
/* @__PURE__ */ t(
|
|
91
|
+
"path",
|
|
92
|
+
{
|
|
93
|
+
stroke: "#9e9e9e",
|
|
94
|
+
strokeWidth: "3",
|
|
95
|
+
strokeLinecap: "round",
|
|
96
|
+
fill: "none",
|
|
97
|
+
d: "M 10 45 A 40 40 0 1 1 90 45"
|
|
98
|
+
}
|
|
99
|
+
),
|
|
100
|
+
/* @__PURE__ */ t(
|
|
101
|
+
"path",
|
|
102
|
+
{
|
|
103
|
+
className: "modern-gauge-arc",
|
|
104
|
+
fill: "none",
|
|
105
|
+
strokeWidth: "3",
|
|
106
|
+
stroke: `url(#gradient-${y})`,
|
|
107
|
+
strokeLinecap: "round",
|
|
108
|
+
strokeDashoffset: "0",
|
|
109
|
+
style: {
|
|
110
|
+
"--mg-initial-dash": `${S}`,
|
|
111
|
+
"--mg-final-dash": `${w}`
|
|
112
|
+
},
|
|
113
|
+
d: "M 10 45 A 40 40 0 1 1 90 45"
|
|
114
|
+
}
|
|
115
|
+
),
|
|
116
|
+
$.map((e, i) => {
|
|
117
|
+
const r = i / ($.length - 1), d = r <= M, a = r;
|
|
118
|
+
return /* @__PURE__ */ f("g", { children: [
|
|
119
|
+
/* @__PURE__ */ t(
|
|
120
|
+
"line",
|
|
121
|
+
{
|
|
122
|
+
x1: e.innerX,
|
|
123
|
+
y1: e.innerY,
|
|
124
|
+
x2: e.outerX,
|
|
125
|
+
y2: e.outerY,
|
|
126
|
+
strokeWidth: "1",
|
|
127
|
+
strokeLinecap: "round",
|
|
128
|
+
stroke: "#9e9e9e"
|
|
129
|
+
}
|
|
130
|
+
),
|
|
131
|
+
/* @__PURE__ */ t(
|
|
132
|
+
"line",
|
|
133
|
+
{
|
|
134
|
+
className: "modern-gauge-tick",
|
|
135
|
+
x1: e.innerX,
|
|
136
|
+
y1: e.innerY,
|
|
137
|
+
x2: e.outerX,
|
|
138
|
+
y2: e.outerY,
|
|
139
|
+
strokeWidth: "1",
|
|
140
|
+
strokeLinecap: "round",
|
|
141
|
+
style: {
|
|
142
|
+
"--mg-tick-color": `url(#gradient-${y})`,
|
|
143
|
+
"--mg-tick-delay": d ? `${a}s` : "0s",
|
|
144
|
+
animationPlayState: d ? "running" : "paused"
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
),
|
|
148
|
+
e.showLabel && /* @__PURE__ */ t(
|
|
149
|
+
"text",
|
|
150
|
+
{
|
|
151
|
+
x: e.labelX,
|
|
152
|
+
y: e.labelY,
|
|
153
|
+
textAnchor: "middle",
|
|
154
|
+
dominantBaseline: "middle",
|
|
155
|
+
fontSize: "5",
|
|
156
|
+
fill: "#9e9e9e",
|
|
157
|
+
fontFamily: "sans-serif",
|
|
158
|
+
children: e.value
|
|
159
|
+
}
|
|
160
|
+
)
|
|
161
|
+
] }, e.value);
|
|
162
|
+
})
|
|
163
|
+
] }),
|
|
164
|
+
/* @__PURE__ */ f(
|
|
165
|
+
"div",
|
|
166
|
+
{
|
|
167
|
+
style: {
|
|
168
|
+
position: "absolute",
|
|
169
|
+
top: "70%",
|
|
170
|
+
left: "50%",
|
|
171
|
+
transform: "translate(-50%, -50%)",
|
|
172
|
+
textAlign: "center",
|
|
173
|
+
pointerEvents: "none",
|
|
174
|
+
whiteSpace: "nowrap"
|
|
175
|
+
},
|
|
176
|
+
children: [
|
|
177
|
+
/* @__PURE__ */ t("div", { style: { fontSize: "3rem", fontWeight: 500, lineHeight: 1.2 }, children: m }),
|
|
178
|
+
s && /* @__PURE__ */ t(
|
|
179
|
+
"div",
|
|
180
|
+
{
|
|
181
|
+
style: {
|
|
182
|
+
fontSize: "0.9rem",
|
|
183
|
+
marginTop: "4px"
|
|
184
|
+
},
|
|
185
|
+
children: s
|
|
186
|
+
}
|
|
187
|
+
)
|
|
188
|
+
]
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
]
|
|
192
|
+
},
|
|
193
|
+
g
|
|
194
|
+
) });
|
|
195
|
+
}, q = ({ value: m, colors: o, title: s, minMax: p }) => /* @__PURE__ */ t(_, { value: m, colors: o, title: s, minMax: p });
|
|
196
|
+
export {
|
|
197
|
+
q as HalfGauge
|
|
198
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@opstel/modern-gauges",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Animated half-circle gauges React component — zero runtime dependencies",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"react",
|
|
7
|
+
"gauge",
|
|
8
|
+
"chart",
|
|
9
|
+
"svg",
|
|
10
|
+
"animation",
|
|
11
|
+
"meter",
|
|
12
|
+
"dashboard"
|
|
13
|
+
],
|
|
14
|
+
"author": "Caleb <caleb.pope@opstel.com>",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"main": "dist/index.cjs.js",
|
|
20
|
+
"module": "dist/index.es.js",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"import": "./dist/index.es.js",
|
|
24
|
+
"require": "./dist/index.cjs.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"README.md"
|
|
30
|
+
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "vite build",
|
|
33
|
+
"dev": "vite build --watch",
|
|
34
|
+
"prepublishOnly": "npm run build"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"react": ">=17.0.0",
|
|
38
|
+
"react-dom": ">=17.0.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@vitejs/plugin-react": "^4.0.0",
|
|
42
|
+
"react": "^18.0.0",
|
|
43
|
+
"react-dom": "^18.0.0",
|
|
44
|
+
"vite": "^5.0.0"
|
|
45
|
+
}
|
|
46
|
+
}
|