add pie chart example
This commit is contained in:
Родитель
f23a16d3e6
Коммит
4b64ae4f58
|
@ -9,6 +9,6 @@ namespace SomeChartsAvaloniaExamples;
|
|||
internal class Program {
|
||||
[STAThread]
|
||||
public static void Main(string[] args) {
|
||||
ElementsExamples.RunPieChart();
|
||||
PieChartExample.Run();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
using SomeChartsUi.data;
|
||||
using SomeChartsUi.elements.charts.pie;
|
||||
using SomeChartsUi.themes.colors;
|
||||
using SomeChartsUi.themes.themes;
|
||||
using SomeChartsUiAvalonia.controls.gl;
|
||||
|
||||
namespace SomeChartsAvaloniaExamples.elements;
|
||||
|
||||
public static class PieChartExample {
|
||||
private static readonly float[] _series = {1, 16, 9, 33, 2, 3, 2};
|
||||
private static readonly indexedColor[] _colors = {
|
||||
theme.accent0_ind,
|
||||
theme.default3_ind,
|
||||
theme.default4_ind,
|
||||
theme.default5_ind,
|
||||
theme.normal_ind,
|
||||
theme.default6_ind,
|
||||
theme.default7_ind,
|
||||
};
|
||||
|
||||
public static void Run() {
|
||||
// call RunAfterStart before running avalonia
|
||||
// it will execute after application open
|
||||
// RunAvalonia() blocks current thread
|
||||
AvaloniaRunUtils.RunAfterStart(AddElements);
|
||||
AvaloniaRunUtils.RunAvalonia();
|
||||
}
|
||||
|
||||
private static void AddElements() {
|
||||
AvaloniaGlChartsCanvas canvas = AvaloniaRunUtils.AddGlCanvas();
|
||||
|
||||
IChartData<float> values = new ArrayChartData<float>(_series);
|
||||
IChartData<indexedColor> colors = new ArrayChartData<indexedColor>(_colors);
|
||||
|
||||
// pie chart provides additional data to names
|
||||
// {0} - value
|
||||
// {1} - percent (100 is 100%)
|
||||
// {2} - element id
|
||||
// to truncate output of percent you can add ':0.00'
|
||||
IChartManagedData<string> names = new FuncChartManagedData<string>(i => $"#{i}: {{1:0.00}}%", 1);
|
||||
|
||||
PieChart pie = new(canvas.canvas) {
|
||||
values = values,
|
||||
colors = colors,
|
||||
names = names,
|
||||
isDynamic = true
|
||||
};
|
||||
canvas.AddElement(pie);
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ using MathStuff;
|
|||
using MathStuff.vectors;
|
||||
using SomeChartsUi.data;
|
||||
using SomeChartsUi.themes.colors;
|
||||
using SomeChartsUi.themes.themes;
|
||||
using SomeChartsUi.ui.canvas;
|
||||
using SomeChartsUi.ui.elements;
|
||||
using SomeChartsUi.ui.text;
|
||||
|
@ -10,35 +11,54 @@ namespace SomeChartsUi.elements.charts.pie;
|
|||
|
||||
public class PieChart : RenderableBase, IDownsample {
|
||||
private const int _maxQuality = 1024;
|
||||
private TextMesh _textMesh;
|
||||
public IChartData<indexedColor> colors;
|
||||
|
||||
public IChartData<float> values = null!;
|
||||
public IChartManagedData<string> names = null!;
|
||||
public IChartData<indexedColor> colors = null!;
|
||||
|
||||
public Font font;
|
||||
public float innerScale = 400;
|
||||
public IChartManagedData<string> names;
|
||||
private TextMesh _textMesh;
|
||||
|
||||
public float outerScale = 800;
|
||||
/// <summary>z-axis rotation of chart, in radians</summary>
|
||||
public float rotation;
|
||||
|
||||
public IChartData<float> values;
|
||||
/// <summary>hole size</summary>
|
||||
public float innerScale = 400;
|
||||
|
||||
public PieChart(ChartsCanvas owner) : base(owner) {
|
||||
_textMesh = canvas.factory.CreateTextMesh(this);
|
||||
/// <summary>pie size</summary>
|
||||
public float outerScale = 800;
|
||||
|
||||
font = canvas.GetDefaultFont();
|
||||
}
|
||||
public bool drawLabels = true;
|
||||
|
||||
/// <summary>length of lines using for labels <br/><br/>depends on outerScale</summary>
|
||||
public float labelLineLength = 2;
|
||||
|
||||
/// <summary>get outerScale for each segment separately</summary>
|
||||
public Func<int, float>? getOuterScale;
|
||||
|
||||
/// <summary>get innerScale for each segment separately</summary>
|
||||
public Func<int, float>? getInnerScale;
|
||||
|
||||
public indexedColor labelColor = theme.default5_ind;
|
||||
|
||||
public float downsampleMultiplier { get; set; } = .5f;
|
||||
public float elementScale { get; set; } = 100;
|
||||
|
||||
public PieChart(ChartsCanvas owner) : base(owner) {
|
||||
_textMesh = canvas.factory.CreateTextMesh(this);
|
||||
font = canvas.GetDefaultFont();
|
||||
}
|
||||
|
||||
|
||||
protected override unsafe void GenerateMesh() {
|
||||
mesh!.Clear();
|
||||
_textMesh.ClearMeshes();
|
||||
int len = values.GetLength();
|
||||
if (len < 1) return;
|
||||
//if (!IsVisible(offset, scale)) return;
|
||||
|
||||
rotation += .01f;
|
||||
if (!IsVisible()) return;
|
||||
|
||||
mesh!.Clear();
|
||||
_textMesh.ClearMeshes();
|
||||
|
||||
if (rotation >= MathF.PI * 2) rotation -= MathF.PI * 2;
|
||||
if (rotation < 0) rotation += MathF.PI * 2;
|
||||
|
||||
|
@ -50,8 +70,6 @@ public class PieChart : RenderableBase, IDownsample {
|
|||
|
||||
// * calculate quality (side count)
|
||||
int quality = (int)Math.Clamp(outerScale * canvasScale.avg, 8, _maxQuality);
|
||||
float mulY = 1;
|
||||
// float mulY = owner.valuesScale.X / owner.valuesScale.Y;
|
||||
|
||||
// * calculate mesh data
|
||||
float valueSum = 0;
|
||||
|
@ -81,10 +99,8 @@ public class PieChart : RenderableBase, IDownsample {
|
|||
int iPos = 0;
|
||||
float rotOffset = rotation;
|
||||
for (int i = 0; i < len; i++) {
|
||||
// float curOutScale = GetOutScale(i);
|
||||
// float curOutScale = scale * (i + 20) * .1f;
|
||||
float curOutScale = outerScale;
|
||||
float curInScale = innerScale;
|
||||
float curOutScale = getOuterScale?.Invoke(i) ?? outerScale;
|
||||
float curInScale = getInnerScale?.Invoke(i) ?? innerScale;
|
||||
float rot = pieValues[i] / valueSum * MathF.PI * 2;
|
||||
|
||||
color col = pieColors[i].GetColor();
|
||||
|
@ -97,8 +113,8 @@ public class PieChart : RenderableBase, IDownsample {
|
|||
float cos = MathF.Cos(currentAngle);
|
||||
int vPosCur = vPos + (v << 1);
|
||||
|
||||
mesh.vertices[vPosCur+0] = new(new(sin * curInScale, cos * curInScale * mulY), float3.front, float2.zero, col);
|
||||
mesh.vertices[vPosCur+1] = new(new(sin * curOutScale, cos * curOutScale * mulY), float3.front, float2.zero, col);
|
||||
mesh.vertices[vPosCur+0] = new(new(sin * curInScale, cos * curInScale), float3.front, float2.zero, col);
|
||||
mesh.vertices[vPosCur+1] = new(new(sin * curOutScale, cos * curOutScale), float3.front, float2.zero, col);
|
||||
}
|
||||
|
||||
for (int v = 0; v < curSideCount - 1; v++) {
|
||||
|
@ -113,7 +129,7 @@ public class PieChart : RenderableBase, IDownsample {
|
|||
mesh.indexes[iPosCur + 5] = (ushort)(vPosCur+2);
|
||||
}
|
||||
|
||||
if (curSideCount > 2) {
|
||||
if (drawLabels && curSideCount > 2) {
|
||||
float lineLen1Mul = outerScale * 3;
|
||||
float lineLen2Mul = outerScale;
|
||||
float midAngle = rotOffset + rot * .5f;
|
||||
|
@ -121,11 +137,9 @@ public class PieChart : RenderableBase, IDownsample {
|
|||
|
||||
float sin = MathF.Sin(midAngle);
|
||||
float cos = MathF.Cos(midAngle);
|
||||
float lineLen = lineLen1Mul * 2 * .5f;
|
||||
// float lineLen = lineLen1Mul * (canvasScale.x + Math.Clamp(canvasScale.x, .1f, .5f)) * .5f;
|
||||
float lineLen = lineLen1Mul * .5f * labelLineLength;
|
||||
|
||||
float lineLen2 = MathF.Cos(midAngle) * lineLen2Mul * 2;
|
||||
// float lineLen2 = MathF.Cos(midAngle) * lineLen2Mul * canvasScale.x;
|
||||
float lineLen2 = MathF.Cos(midAngle) * lineLen2Mul;
|
||||
float line2Angle = MathF.PI * .25f;
|
||||
if (midAngle >= MathF.PI) {
|
||||
lineLen = -lineLen;
|
||||
|
@ -133,10 +147,10 @@ public class PieChart : RenderableBase, IDownsample {
|
|||
}
|
||||
if (midAngle is >= MathF.PI * .5f and <= MathF.PI * .5f * 3) line2Angle = -line2Angle;
|
||||
|
||||
float2 lineStart = new(sin * curOutScale, cos * curOutScale * mulY);
|
||||
float2 line2 = new(MathF.Sin(line2Angle) * lineLen2, MathF.Cos(line2Angle) * lineLen2 * mulY);
|
||||
float2 lineStart = new(sin * curOutScale, cos * curOutScale);
|
||||
float2 line2 = new(MathF.Sin(line2Angle) * lineLen2, MathF.Cos(line2Angle) * lineLen2);
|
||||
float2 lineEnd = new(lineStart.x + line2.x, lineStart.y + line2.y);
|
||||
float2 lineEnd2 = new(lineStart.x + lineLen - curOutScale * canvasScale.x * MathF.CopySign(MathF.Sin(midAngle), lineLen), lineStart.y + line2.y);
|
||||
float2 lineEnd2 = new(lineStart.x + lineLen - curOutScale * labelLineLength * MathF.CopySign(MathF.Sin(midAngle), lineLen), lineStart.y + line2.y);
|
||||
float2 labelPos = new(lineEnd2.x, lineEnd2.y + 4);
|
||||
|
||||
float thickness = 1f / canvasScale.avg;
|
||||
|
@ -144,7 +158,7 @@ public class PieChart : RenderableBase, IDownsample {
|
|||
AddLine(mesh, lineEnd, lineEnd2, thickness, col.WithAlpha(255));
|
||||
|
||||
string name = string.Format(names.GetValue(i), pieValues[i], pieValues[i] / valueSum * 100, i);
|
||||
_textMesh.GenerateMesh(name, font, 16 / canvasScale.avg, col, new(labelPos));
|
||||
_textMesh.GenerateMesh(name, font, 16 / canvasScale.avg, labelColor.GetColor(), new(labelPos));
|
||||
}
|
||||
|
||||
vPos += curSideCount * 2;
|
||||
|
@ -157,6 +171,13 @@ public class PieChart : RenderableBase, IDownsample {
|
|||
|
||||
protected override void AfterDraw() {
|
||||
base.AfterDraw();
|
||||
_textMesh.Draw();
|
||||
|
||||
if (IsVisible()) _textMesh.Draw();
|
||||
}
|
||||
|
||||
private bool IsVisible() {
|
||||
float approximateSize = outerScale;
|
||||
if (drawLabels) approximateSize += (outerScale + 1) * labelLineLength;
|
||||
return IsVisibleWithTransform(float2.zero, approximateSize);
|
||||
}
|
||||
}
|
|
@ -90,6 +90,9 @@ public abstract partial class RenderableBase {
|
|||
protected static void Free<T>(T[] arr) => ArrayPool<T>.Shared.Return(arr);
|
||||
protected static unsafe void FreeMem<T>(T* arr) where T : unmanaged => NativeMemory.Free(arr);
|
||||
|
||||
protected bool IsVisible(float2 a, float2 s) => canvas.transform.worldBounds.Contains(a.x, a.y, s.x, s.y);
|
||||
protected bool IsVisibleWithTransform(float2 a, float2 s) => canvas.transform.worldBounds.Contains(a.x + transform.position.x, a.y + transform.position.y, s.x * transform.scale.x, s.y * transform.scale.y);
|
||||
|
||||
public RenderableBase WithPosition(float2 v) {
|
||||
((ChartPropertyValue<Transform>)transform).value.position = v;
|
||||
return this;
|
||||
|
|
Загрузка…
Ссылка в новой задаче