var rnd = new Random();
var names = new[] { "Direct", "Search", "Social", "Email", "Referral" };
var palette = new[] { "#5EE7C4", "#3FC7DE", "#2E7BF6", "#6C5CE7", "#B15EE7" };
var count = names.Length;
var values = new double[count];
var shown = new double[count];
var from = new double[count];
const double donutSize = 220;
const double ringWidth = 26;
const double gapDegrees = 3;
void Roll()
{
for (int i = 0; i < count; i++)
values[i] = rnd.Next(8, 60);
}
Roll();
var percentLabels = new List<SkiaLabel>();
SkiaLabel totalLabel = null;
SkiaLayer plot = null;
SkiaSvg icon = null;
void Play(double seconds)
{
plot.Animate(seconds, (me, animator, value, dt) =>
{
double sum = 0;
for (int i = 0; i < count; i++)
{
shown[i] = from[i] + (values[i] - from[i]) * value;
sum += shown[i];
}
totalLabel.Text = $"{sum:0}";
me.Update();
}, repeat: 0, easing: Easing.CubicOut);
}
void Refresh()
{
Array.Copy(shown, from, count);
Roll();
var total = values.Sum();
for (int i = 0; i < count; i++)
percentLabels[i].Text = $"{values[i] / total * 100:0}%";
icon?.AnimateRotation(0, 360, seconds: 0.7, easing: Easing.CubicInOut);
Play(0.7);
}
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("Traffic by Channel")
{
FontSize = 22,
FontAttributes = FontAttributes.Bold,
TextColor = Colors.White,
},
new SkiaLabel("share of sessions this week")
{
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 = donutSize,
HeightRequest = donutSize,
Margin = new Thickness(0, 20, 0, 0),
Children = new List<SkiaControl>()
{
new SkiaStack()
{
Spacing = 0,
Children = new List<SkiaControl>()
{
new SkiaLabel("0")
{
FontSize = 30,
FontAttributes = FontAttributes.Bold,
TextColor = Colors.White,
}.CenterX().Assign(out totalLabel),
new SkiaLabel("sessions")
{
FontSize = 11,
TextColor = Color.Parse("#78849B"),
}.CenterX(),
}
}.Center(),
}
}
.CenterX()
.Assign(out plot)
.WhenPaint((me, ctx) =>
{
var canvas = ctx.Context.Canvas;
var rect = ctx.Destination;
var scale = ctx.Scale;
var inset = (float)(ringWidth * scale / 2);
var oval = new SKRect(rect.Left + inset, rect.Top + inset, rect.Right - inset, rect.Bottom - inset);
using (var track = new SKPaint
{
IsAntialias = true,
Style = SKPaintStyle.Stroke,
StrokeWidth = (float)(ringWidth * scale),
Color = SKColor.Parse("#1B2231"),
})
{
canvas.DrawOval(oval, track);
}
var total = shown.Sum();
if (total <= 0)
return;
float angle = -90;
for (int i = 0; i < count; i++)
{
var sweep = (float)(shown[i] / total * 360.0);
var drawn = sweep - (float)gapDegrees;
if (drawn > 0.5f)
{
using var arc = new SKPaint
{
IsAntialias = true,
Style = SKPaintStyle.Stroke,
StrokeWidth = (float)(ringWidth * scale),
StrokeCap = SKStrokeCap.Round,
Color = SKColor.Parse(palette[i]),
};
using var path = new SKPath();
path.AddArc(oval, angle + (float)gapDegrees / 2, drawn);
canvas.DrawPath(path, arc);
}
angle += sweep;
}
}),
new SkiaStack()
{
Spacing = 10,
Margin = new Thickness(0, 20, 0, 0),
WidthRequest=220,
HorizontalOptions = LayoutOptions.Center,
Children = names.Select((name, i) => (SkiaControl)new SkiaLayer()
{
Children = new List<SkiaControl>()
{
new SkiaRow()
{
Spacing = 10,
Children = new List<SkiaControl>()
{
new SkiaShape()
{
Type = ShapeType.Circle,
WidthRequest = 9,
LockRatio = 1,
BackgroundColor = Color.Parse(palette[i]),
}.CenterY(),
new SkiaLabel(name)
{
FontSize = 13,
TextColor = Color.Parse("#9FB0C9"),
}.CenterY(),
}
}.StartX().CenterY(),
new SkiaLabel("0%")
{
FontSize = 13,
FontAttributes = FontAttributes.Bold,
TextColor = Colors.White,
}
.EndX()
.CenterY()
.Adapt(me => percentLabels.Add(me)),
}
}.FillX()).ToList()
}.CenterX(),
}
}
.Center()
.WithWidth(320)
.WithPadding(24)
.Initialize(me =>
{
var total = values.Sum();
for (int i = 0; i < count; i++)
percentLabels[i].Text = $"{values[i] / total * 100:0}%";
Play(0.9);
}),
}
}.Fill();