DrawFiddle▶ RunEdit in Fiddle
var rnd = new Random();
var names = new[] { "Phones", "Laptops", "Tablets", "TVs", "Audio", "Watches" };
var palette = new[] { "#5EE7C4", "#3FC7DE", "#2E7BF6", "#6C5CE7", "#B15EE7", "#E75E9B" };
var count = names.Length;

const double plotWidth = 320;
const double plotHeight = 300;
const double bubbleGap = 5;

var values = new double[count];
var toX = new double[count];
var toY = new double[count];
var toR = new double[count];
var fromX = new double[count];
var fromY = new double[count];
var fromR = new double[count];
var curX = new double[count];
var curY = new double[count];
var curR = new double[count];

var circles = new List<SkiaShape>();
var nameLabels = new List<SkiaLabel>();
var shareLabels = new List<SkiaLabel>();
SkiaLayer plot = null;
SkiaSvg icon = null;

void Roll()
{
    for (int i = 0; i < count; i++)
        values[i] = rnd.Next(5, 40);
}

// one pass of overlap resolution + bounds clamp; returns true if anything moved.
// shared by the packer and by every animation frame, so both are always valid.
bool Separate(double[] x, double[] y, double[] r)
{
    var moved = false;
    for (int i = 0; i < count; i++)
    {
        for (int j = i + 1; j < count; j++)
        {
            var dx = x[j] - x[i];
            var dy = y[j] - y[i];
            var d = Math.Sqrt(dx * dx + dy * dy);
            if (d < 0.001) { dx = 0.5; dy = 0.2; d = 0.54; }
            var minDistance = r[i] + r[j] + bubbleGap;
            if (d < minDistance - 0.01)
            {
                var push = (minDistance - d) / 2;
                var ux = dx / d;
                var uy = dy / d;
                x[i] -= ux * push; y[i] -= uy * push;
                x[j] += ux * push; y[j] += uy * push;
                moved = true;
            }
        }
    }
    for (int i = 0; i < count; i++)
    {
        x[i] = Math.Clamp(x[i], r[i], plotWidth - r[i]);
        y[i] = Math.Clamp(y[i], r[i], plotHeight - r[i]);
    }
    return moved;
}

void Pack()
{
    var sum = values.Sum();
    // radius from SQRT of value so AREA is proportional, not radius
    var k = Math.Sqrt(0.48 * plotWidth * plotHeight / (Math.PI * sum));
    for (int i = 0; i < count; i++)
    {
        toR[i] = k * Math.Sqrt(values[i]);
        var a = Math.PI * 2 * i / count;
        toX[i] = plotWidth / 2 + Math.Cos(a) * plotWidth / 6;
        toY[i] = plotHeight / 2 + Math.Sin(a) * plotHeight / 6;
    }

    for (int iter = 0; iter < 300; iter++)
    {
        for (int i = 0; i < count; i++)
        {
            toX[i] += (plotWidth / 2 - toX[i]) * 0.02;
            toY[i] += (plotHeight / 2 - toY[i]) * 0.02;
        }
        Separate(toX, toY, toR);
    }

    // settle with no centre pull, so separation is the LAST thing that happens
    for (int iter = 0; iter < 400 && Separate(toX, toY, toR); iter++) { }

    var minX = double.MaxValue; var maxX = double.MinValue;
    var minY = double.MaxValue; var maxY = double.MinValue;
    for (int i = 0; i < count; i++)
    {
        minX = Math.Min(minX, toX[i] - toR[i]);
        maxX = Math.Max(maxX, toX[i] + toR[i]);
        minY = Math.Min(minY, toY[i] - toR[i]);
        maxY = Math.Max(maxY, toY[i] + toR[i]);
    }
    var shiftX = (plotWidth - (maxX + minX)) / 2;
    var shiftY = (plotHeight - (maxY + minY)) / 2;
    for (int i = 0; i < count; i++)
    {
        toX[i] += shiftX;
        toY[i] += shiftY;
    }
}

void Place(int i, double x, double y, double r)
{
    circles[i].WidthRequest = r * 2;
    circles[i].HeightRequest = r * 2;
    circles[i].TranslationX = x - r;
    circles[i].TranslationY = y - r;
    nameLabels[i].IsVisible = r >= 30;
    shareLabels[i].IsVisible = r >= 18;
}

void Play(double seconds)
{
    plot.Animate(seconds, (me, animator, value, dt) =>
    {
        // one pass: lerp position + radius, and total the area while we are here
        var targetArea = 0.48 * plotWidth * plotHeight;
        var frameArea = 0.0;
        for (int i = 0; i < count; i++)
        {
            curX[i] = fromX[i] + (toX[i] - fromX[i]) * value;
            curY[i] = fromY[i] + (toY[i] - fromY[i]) * value;
            curR[i] = fromR[i] + (toR[i] - fromR[i]) * value;
            frameArea += Math.PI * curR[i] * curR[i];
        }

        // lerped radii can exceed what fits in the box (two circles near max at once).
        // that frame is geometrically unsolvable, so scale it back before separating.
        if (frameArea > targetArea)
        {
            var shrink = Math.Sqrt(targetArea / frameArea);
            for (int i = 0; i < count; i++)
                curR[i] *= shrink;
        }

        // the lerp is only a guide — resolve it at THIS frame's radii
        for (int pass = 0; pass < 200 && Separate(curX, curY, curR); pass++) { }

        for (int i = 0; i < count; i++)
            Place(i, curX[i], curY[i], curR[i]);
    }, repeat: 0, easing: Easing.CubicOut);
}

void Retarget()
{
    for (int i = 0; i < count; i++)
    {
        fromX[i] = circles[i].TranslationX + circles[i].WidthRequest / 2;
        fromY[i] = circles[i].TranslationY + circles[i].HeightRequest / 2;
        fromR[i] = circles[i].WidthRequest / 2;
    }
    Roll();
    Pack();
    var sum = values.Sum();
    for (int i = 0; i < count; i++)
        shareLabels[i].Text = $"{values[i] / sum * 100:0}%";
}

void Refresh()
{
    Retarget();
    icon?.AnimateRotation(0, 360, seconds: 0.7, easing: Easing.CubicInOut);
    Play(0.7);
}

Roll();

return new SkiaLayer()
{
    BackgroundColor = Color.Parse("#12161F"),
    Children = new List<SkiaControl>()
    {
        new SkiaStack()
        {
            Spacing = 4,
            Children = new List<SkiaControl>()
            {
                new SkiaLayer()
                {
                    Children = new List<SkiaControl>()
                    {
                        new SkiaStack()
                        {
                            Spacing = 2,
                            Children = new List<SkiaControl>()
                            {
                                new SkiaLabel("Sales by Category")
                                {
                                    FontSize = 22,
                                    FontAttributes = FontAttributes.Bold,
                                    TextColor = Colors.White,
                                },
                                new SkiaLabel("units sold this quarter")
                                {
                                    FontSize = 12,
                                    TextColor = Color.Parse("#78849B"),
                                },
                            }
                        }.StartX().CenterY(),

                        new SkiaShape()
                        {
                            Type = ShapeType.Circle,
                            WidthRequest = 30,
                            LockRatio = 1,
                            BackgroundColor = Color.Parse("#262F42"),
                            StrokeColor = Color.Parse("#2E7BF6"),
                            StrokeWidth = 1,
                            AnimationTapped = SkiaTouchAnimation.Ripple,
                            TouchEffectColor = Color.Parse("#5EE7C4"),
                            Children = new List<SkiaControl>()
                            {
                                new SkiaSvg()
                                {
                                    SvgString = "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='white' d='M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-8 8s3.58 8 8 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z'/></svg>",
                                    TintColor = Color.Parse("#5EE7C4"),
                                    WidthRequest = 16,
                                    LockRatio = 1,
                                }.Center().Assign(out icon),
                            }
                        }
                        .EndX()
                        .CenterY()
                        .OnTapped(me => Refresh()),
                    }
                }.FillX(),

                new SkiaLayer()
                {
                    WidthRequest = plotWidth,
                    HeightRequest = plotHeight,
                    Margin = new Thickness(0, 16, 0, 0),
                    Children = names.Select((name, i) => (SkiaControl)new SkiaShape()
                    {
                        Type = ShapeType.Circle,
                        WidthRequest = 0,
                        HeightRequest = 0,
                        BackgroundColor = Color.Parse(palette[i]).WithAlpha(0.22f),
                        StrokeColor = Color.Parse(palette[i]),
                        StrokeWidth = 1.5,
                        Children = new List<SkiaControl>()
                        {
                            new SkiaStack()
                            {
                                Spacing = 0,
                                Children = new List<SkiaControl>()
                                {
                                    new SkiaLabel(name)
                                    {
                                        FontSize = 12,
                                        TextColor = Colors.White,
                                    }
                                    .CenterX()
                                    .Adapt(me => nameLabels.Add(me)),

                                    new SkiaLabel("0%")
                                    {
                                        FontSize = 14,
                                        FontAttributes = FontAttributes.Bold,
                                        TextColor = Color.Parse(palette[i]),
                                    }
                                    .CenterX()
                                    .Adapt(me => shareLabels.Add(me)),
                                }
                            }.Center(),
                        }
                    }
                    .StartX()
                    .StartY()
                    .Adapt(me => circles.Add(me))).ToList()
                }
                .CenterX()
                .Assign(out plot),
            }
        }
        .Center()
        .WithWidth(plotWidth)
        .WithPadding(24)
        .Initialize(me =>
        {
            Pack();
            var sum = values.Sum();
            for (int i = 0; i < count; i++)
            {
                fromX[i] = plotWidth / 2;
                fromY[i] = plotHeight / 2;
                fromR[i] = 0;
                shareLabels[i].Text = $"{values[i] / sum * 100:0}%";
            }
            Play(0.9);
        }),
    }
}.Fill();