This commit is contained in:
firedef 2022-03-14 19:56:02 +03:00
Родитель f23a16d3e6
Коммит 4b64ae4f58
4 изменённых файлов: 106 добавлений и 32 удалений

Просмотреть файл

@ -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;
/// <summary>pie size</summary>
public float outerScale = 800;
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();
}
public float downsampleMultiplier { get; set; } = .5f;
public float elementScale { get; set; } = 100;
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);
}
}

Просмотреть файл

@ -89,6 +89,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;