Skip to content

Commit c32332d

Browse files
committed
Handle full positive/negative values in ActivityRing
Adds logic to display a full circle for 100% positive or negative values in the ActivityRing component, including both fill and stroke. Updates arc calculation and rendering to use new showOnlyPositive and showOnlyNegative flags, ensuring correct visual representation when one value is zero.
1 parent 8b331ef commit c32332d

File tree

1 file changed

+154
-76
lines changed

1 file changed

+154
-76
lines changed

packages/ui/src/activity-ring.tsx

Lines changed: 154 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -105,55 +105,79 @@ export function ActivityRing({
105105
}, [positiveValue, negativeValue]);
106106

107107
// Calculate arc angles based on ratio
108-
const { positiveArc, negativeArc } = useMemo(() => {
109-
const total = positiveValue + negativeValue;
108+
const { positiveArc, negativeArc, showOnlyPositive, showOnlyNegative } =
109+
useMemo(() => {
110+
const total = positiveValue + negativeValue;
110111

111-
// Available sweep angle (360 - 2 gaps)
112-
const availableSweep = 360 - GAP_ANGLE * 2;
112+
// Available sweep angle (360 - 2 gaps)
113+
const availableSweep = 360 - GAP_ANGLE * 2;
113114

114-
if (total === 0) {
115-
// Neutral: equal arcs at 50% each
116-
const halfSweep = availableSweep / 2;
117-
return {
118-
// Right side: from top going clockwise to bottom
119-
positiveArc: { start: GAP_ANGLE / 2, end: GAP_ANGLE / 2 + halfSweep },
120-
// Left side: from bottom going clockwise to top
121-
negativeArc: {
122-
start: 180 + GAP_ANGLE / 2,
123-
end: 180 + GAP_ANGLE / 2 + halfSweep,
124-
},
125-
};
126-
}
115+
if (total === 0) {
116+
// Neutral: equal arcs at 50% each
117+
const halfSweep = availableSweep / 2;
118+
return {
119+
// Right side: from top going clockwise to bottom
120+
positiveArc: { start: GAP_ANGLE / 2, end: GAP_ANGLE / 2 + halfSweep },
121+
// Left side: from bottom going clockwise to top
122+
negativeArc: {
123+
start: 180 + GAP_ANGLE / 2,
124+
end: 180 + GAP_ANGLE / 2 + halfSweep,
125+
},
126+
showOnlyPositive: false,
127+
showOnlyNegative: false,
128+
};
129+
}
127130

128-
const positiveRatio = positiveValue / total;
131+
// When one value is 0, show full circle of the other color (no gaps)
132+
if (positiveValue === 0) {
133+
return {
134+
positiveArc: { start: 0, end: 0 },
135+
negativeArc: { start: 0, end: 360 },
136+
showOnlyPositive: false,
137+
showOnlyNegative: true,
138+
};
139+
}
129140

130-
// Calculate sweeps with minimum visibility
131-
let positiveSweep = positiveRatio * availableSweep;
132-
let negativeSweep = (1 - positiveRatio) * availableSweep;
141+
if (negativeValue === 0) {
142+
return {
143+
positiveArc: { start: 0, end: 360 },
144+
negativeArc: { start: 0, end: 0 },
145+
showOnlyPositive: true,
146+
showOnlyNegative: false,
147+
};
148+
}
133149

134-
// Ensure minimum visibility for non-zero values
135-
if (positiveValue > 0 && positiveSweep < MIN_ARC_DEGREES) {
136-
positiveSweep = MIN_ARC_DEGREES;
137-
negativeSweep = availableSweep - MIN_ARC_DEGREES;
138-
}
139-
if (negativeValue > 0 && negativeSweep < MIN_ARC_DEGREES) {
140-
negativeSweep = MIN_ARC_DEGREES;
141-
positiveSweep = availableSweep - MIN_ARC_DEGREES;
142-
}
150+
const positiveRatio = positiveValue / total;
143151

144-
return {
145-
// Right side: from top going clockwise
146-
positiveArc: {
147-
start: GAP_ANGLE / 2,
148-
end: GAP_ANGLE / 2 + positiveSweep,
149-
},
150-
// Left side: from where positive ends + gap, going clockwise
151-
negativeArc: {
152-
start: GAP_ANGLE / 2 + positiveSweep + GAP_ANGLE,
153-
end: GAP_ANGLE / 2 + positiveSweep + GAP_ANGLE + negativeSweep,
154-
},
155-
};
156-
}, [positiveValue, negativeValue]);
152+
// Calculate sweeps with minimum visibility
153+
let positiveSweep = positiveRatio * availableSweep;
154+
let negativeSweep = (1 - positiveRatio) * availableSweep;
155+
156+
// Ensure minimum visibility for non-zero values
157+
if (positiveValue > 0 && positiveSweep < MIN_ARC_DEGREES) {
158+
positiveSweep = MIN_ARC_DEGREES;
159+
negativeSweep = availableSweep - MIN_ARC_DEGREES;
160+
}
161+
if (negativeValue > 0 && negativeSweep < MIN_ARC_DEGREES) {
162+
negativeSweep = MIN_ARC_DEGREES;
163+
positiveSweep = availableSweep - MIN_ARC_DEGREES;
164+
}
165+
166+
return {
167+
// Right side: from top going clockwise
168+
positiveArc: {
169+
start: GAP_ANGLE / 2,
170+
end: GAP_ANGLE / 2 + positiveSweep,
171+
},
172+
// Left side: from where positive ends + gap, going clockwise
173+
negativeArc: {
174+
start: GAP_ANGLE / 2 + positiveSweep + GAP_ANGLE,
175+
end: GAP_ANGLE / 2 + positiveSweep + GAP_ANGLE + negativeSweep,
176+
},
177+
showOnlyPositive: false,
178+
showOnlyNegative: false,
179+
};
180+
}, [positiveValue, negativeValue]);
157181

158182
const center = size / 2;
159183
const strokeWidth = 3;
@@ -193,8 +217,34 @@ export function ActivityRing({
193217
style={{ width: size, height: size }}
194218
>
195219
<svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
196-
{/* Filled arc for positive (behind stroke) */}
197-
{positiveValue > 0 && (
220+
{/* Full circle fill for 100% positive */}
221+
{showOnlyPositive && (
222+
<circle
223+
cx={center}
224+
cy={center}
225+
r={(fillOuterRadius + fillInnerRadius) / 2}
226+
fill="none"
227+
stroke={COLORS.positive}
228+
strokeWidth={fillOuterRadius - fillInnerRadius}
229+
opacity={0.1}
230+
/>
231+
)}
232+
233+
{/* Full circle fill for 100% negative */}
234+
{showOnlyNegative && (
235+
<circle
236+
cx={center}
237+
cy={center}
238+
r={(fillOuterRadius + fillInnerRadius) / 2}
239+
fill="none"
240+
stroke={COLORS.negative}
241+
strokeWidth={fillOuterRadius - fillInnerRadius}
242+
opacity={0.1}
243+
/>
244+
)}
245+
246+
{/* Filled arc for positive (behind stroke) - only when not full circle */}
247+
{positiveValue > 0 && !showOnlyPositive && (
198248
<path
199249
d={describeFilledArc(
200250
center,
@@ -209,8 +259,8 @@ export function ActivityRing({
209259
/>
210260
)}
211261

212-
{/* Filled arc for negative (behind stroke) */}
213-
{negativeValue > 0 && (
262+
{/* Filled arc for negative (behind stroke) - only when not full circle */}
263+
{negativeValue > 0 && !showOnlyNegative && (
214264
<path
215265
d={describeFilledArc(
216266
center,
@@ -225,35 +275,63 @@ export function ActivityRing({
225275
/>
226276
)}
227277

228-
{/* Positive arc stroke (right side) */}
229-
<path
230-
d={describeArc(
231-
center,
232-
center,
233-
outerRadius,
234-
positiveArc.start,
235-
positiveArc.end,
236-
)}
237-
stroke={positiveColor}
238-
strokeWidth={strokeWidth}
239-
fill="none"
240-
strokeLinecap="round"
241-
/>
242-
243-
{/* Negative arc stroke (left side) */}
244-
<path
245-
d={describeArc(
246-
center,
247-
center,
248-
outerRadius,
249-
negativeArc.start,
250-
negativeArc.end,
251-
)}
252-
stroke={negativeColor}
253-
strokeWidth={strokeWidth}
254-
fill="none"
255-
strokeLinecap="round"
256-
/>
278+
{/* Full circle stroke for 100% positive */}
279+
{showOnlyPositive && (
280+
<circle
281+
cx={center}
282+
cy={center}
283+
r={outerRadius}
284+
fill="none"
285+
stroke={positiveColor}
286+
strokeWidth={strokeWidth}
287+
/>
288+
)}
289+
290+
{/* Full circle stroke for 100% negative */}
291+
{showOnlyNegative && (
292+
<circle
293+
cx={center}
294+
cy={center}
295+
r={outerRadius}
296+
fill="none"
297+
stroke={negativeColor}
298+
strokeWidth={strokeWidth}
299+
/>
300+
)}
301+
302+
{/* Positive arc stroke (right side) - only when not full circle */}
303+
{!showOnlyPositive && !showOnlyNegative && (
304+
<path
305+
d={describeArc(
306+
center,
307+
center,
308+
outerRadius,
309+
positiveArc.start,
310+
positiveArc.end,
311+
)}
312+
stroke={positiveColor}
313+
strokeWidth={strokeWidth}
314+
fill="none"
315+
strokeLinecap="round"
316+
/>
317+
)}
318+
319+
{/* Negative arc stroke (left side) - only when not full circle */}
320+
{!showOnlyPositive && !showOnlyNegative && (
321+
<path
322+
d={describeArc(
323+
center,
324+
center,
325+
outerRadius,
326+
negativeArc.start,
327+
negativeArc.end,
328+
)}
329+
stroke={negativeColor}
330+
strokeWidth={strokeWidth}
331+
fill="none"
332+
strokeLinecap="round"
333+
/>
334+
)}
257335
</svg>
258336

259337
{/* Centered icon */}

0 commit comments

Comments
 (0)