var rnd = new Random();
var days = new[] { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };
var count = days.Length;
var values = new double[count];
var shown = new double[count];
var from = new double[count];
const double plotHeight = 220;
const double scaleMax = 100;
const double slotWidth = 40;
const double slotSpacing = 12;
double plotWidth = count * slotWidth + (count - 1) * slotSpacing;
void Roll()
{
for (int i = 0; i < count; i++)
values[i] = rnd.Next(15, 100);
}
Roll();
var valueLabels = new List<SkiaLabel>();
SkiaLayer plot = null;
SkiaSvg icon = null;
void Reposition()
{
for (int i = 0; i < count; i++)
valueLabels[i].TranslationY = -(plotHeight * (shown[i] / scaleMax) + 12);
}
void Play(double seconds)
{
plot.Animate(seconds, (me, animator, value, dt) =>
{
for (int i = 0; i < count; i++)
shown[i] = from[i] + (values[i] - from[i]) * value;
Reposition();
me.Update();
}, repeat: 0, easing: Easing.CubicOut);
}
void Refresh()
{
Array.Copy(shown, from, count);
Roll();
for (int i = 0; i < count; i++)
valueLabels[i].Text = $"{values[i]: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("Daily Sales")
{
FontSize = 22,
FontAttributes = FontAttributes.Bold,
TextColor = Colors.White,
},
new SkiaLabel("sessions per day, thousands")
{
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 = 18,
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 = days.Select((day, i) => (SkiaControl)new SkiaLabel($"{values[i]:0}")
{
FontSize = 12,
WidthRequest = slotWidth,
TextColor = Color.Parse("#9FB0C9"),
TranslationX = i * (slotWidth + slotSpacing),
TranslationY = -12,
}
.CenterText()
.StartX()
.EndY()
.Adapt(me => valueLabels.Add(me))).ToList()
}
.CenterX()
.Assign(out plot)
.WhenPaint((me, ctx) =>
{
var canvas = ctx.Context.Canvas;
var rect = ctx.Destination;
var scale = ctx.Scale;
var points = new SKPoint[count];
for (int i = 0; i < count; i++)
{
var x = rect.Left + (float)((i * (slotWidth + slotSpacing) + slotWidth / 2) * scale);
var y = rect.Bottom - (float)(plotHeight * (shown[i] / scaleMax) * scale);
points[i] = new SKPoint(x, y);
}
using (var grid = new SKPaint
{
Color = SKColor.Parse("#2A3346"),
StrokeWidth = 1,
Style = SKPaintStyle.Stroke,
})
{
for (int g = 1; g <= 4; g++)
{
var y = rect.Bottom - (float)(plotHeight * scale * g / 4.0);
canvas.DrawLine(rect.Left, y, rect.Right, y, grid);
}
}
using var line = new SKPath();
line.MoveTo(points[0]);
for (int i = 1; i < count; i++)
{
var prev = points[i - 1];
var cur = points[i];
var midX = (prev.X + cur.X) / 2;
line.CubicTo(new SKPoint(midX, prev.Y), new SKPoint(midX, cur.Y), cur);
}
using var area = new SKPath(line);
area.LineTo(points[count - 1].X, rect.Bottom);
area.LineTo(points[0].X, rect.Bottom);
area.Close();
using (var fill = new SKPaint
{
IsAntialias = true,
Style = SKPaintStyle.Fill,
Shader = SKShader.CreateLinearGradient(
new SKPoint(0, rect.Top),
new SKPoint(0, rect.Bottom),
new[] { new SKColor(0x5E, 0xE7, 0xC4, 0x70), new SKColor(0x2E, 0x7B, 0xF6, 0x00) },
new float[] { 0f, 1f },
SKShaderTileMode.Clamp),
})
{
canvas.DrawPath(area, fill);
}
using (var stroke = new SKPaint
{
IsAntialias = true,
Style = SKPaintStyle.Stroke,
StrokeWidth = 2.5f * scale,
StrokeCap = SKStrokeCap.Round,
StrokeJoin = SKStrokeJoin.Round,
Shader = SKShader.CreateLinearGradient(
new SKPoint(rect.Left, 0),
new SKPoint(rect.Right, 0),
new[] { SKColor.Parse("#5EE7C4"), SKColor.Parse("#2E7BF6") },
new float[] { 0f, 1f },
SKShaderTileMode.Clamp),
})
{
canvas.DrawPath(line, stroke);
}
using (var dotFill = new SKPaint
{
IsAntialias = true,
Style = SKPaintStyle.Fill,
Color = SKColor.Parse("#12161F"),
})
using (var dotRing = new SKPaint
{
IsAntialias = true,
Style = SKPaintStyle.Stroke,
StrokeWidth = 2f * scale,
Color = SKColor.Parse("#5EE7C4"),
})
{
foreach (var p in points)
{
canvas.DrawCircle(p, 4f * scale, dotFill);
canvas.DrawCircle(p, 4f * scale, dotRing);
}
}
}),
new SkiaShape()
{
Type = ShapeType.Rectangle,
HeightRequest = 1,
BackgroundColor = Color.Parse("#2A3346"),
}.FillX(),
new SkiaRow()
{
Spacing = slotSpacing,
Margin = new Thickness(0, 8, 0, 0),
Children = days.Select(day => (SkiaControl)new SkiaLabel(day)
{
FontSize = 12,
WidthRequest = slotWidth,
TextColor = Color.Parse("#78849B"),
}.CenterText()).ToList()
}.CenterX(),
}
}
.Center()
.WithPadding(24)
.Initialize(me => Play(0.9)),
}
}.Fill();