initial Commit
This commit is contained in:
25
Unreal/PS_Chart/Plugins/PS_Charts/PS_Charts.uplugin
Normal file
25
Unreal/PS_Chart/Plugins/PS_Charts/PS_Charts.uplugin
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"FileVersion": 3,
|
||||
"Version": 1,
|
||||
"VersionName": "1.0",
|
||||
"FriendlyName": "PS Charts",
|
||||
"Description": "Graphiques natifs UMG/Slate (camembert, histogramme, courbes) sans navigateur web.",
|
||||
"Category": "User Interface",
|
||||
"CreatedBy": "Proserve",
|
||||
"CreatedByURL": "",
|
||||
"DocsURL": "",
|
||||
"MarketplaceURL": "",
|
||||
"SupportURL": "",
|
||||
"CanContainContent": false,
|
||||
"IsBetaVersion": false,
|
||||
"IsExperimentalVersion": false,
|
||||
"Installed": false,
|
||||
"EnabledByDefault": true,
|
||||
"Modules": [
|
||||
{
|
||||
"Name": "PS_Charts",
|
||||
"Type": "Runtime",
|
||||
"LoadingPhase": "Default"
|
||||
}
|
||||
]
|
||||
}
|
||||
63
Unreal/PS_Chart/Plugins/PS_Charts/README.md
Normal file
63
Unreal/PS_Chart/Plugins/PS_Charts/README.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# PS Charts
|
||||
|
||||
Plugin de graphiques **100 % natif UMG / Slate** (aucun navigateur web), pensé pour remplacer SimpleCharts sur les besoins de base :
|
||||
|
||||
- **PS Pie Chart** — camembert / donut
|
||||
- **PS Bar Chart** — histogramme (barres groupées, multi-séries)
|
||||
- **PS Line Chart** — courbes (multi-séries, lissage optionnel)
|
||||
|
||||
Module runtime unique `PS_Charts`, dépendances : Core, CoreUObject, Engine, Slate, SlateCore, UMG. Fonctionne sur toutes les plateformes (pas de CEF/WebBrowser).
|
||||
|
||||
## Utilisation dans l'éditeur UMG
|
||||
|
||||
Les trois widgets apparaissent dans la palette UMG, catégorie **PS Charts**. Chaque widget affiche des données d'exemple dans le designer ; toutes les propriétés (données, style, axes) sont éditables dans le panneau Détails et la prévisualisation se met à jour immédiatement.
|
||||
|
||||
## Injection des valeurs (Blueprint ou C++)
|
||||
|
||||
### Camembert (`UPSPieChart`)
|
||||
|
||||
- `SetValues(TMap<FString, float>)` — remplace toutes les parts (équivalent du `data` de SimpleCharts)
|
||||
- `SetSlices(TArray<FPSPieSlice>)` — contrôle complet (ordre, couleurs personnalisées)
|
||||
- `SetSliceValue(Label, Value)` — met à jour une part (la crée si absente)
|
||||
- `ClearData()`
|
||||
|
||||
Options : `InnerRadiusRatio` (0 = camembert, >0 = donut), `StartAngleDegrees`, `GapAngleDegrees`, `LabelType` (nom / valeur / pourcentage...), `ValueSuffix`.
|
||||
|
||||
### Histogramme (`UPSBarChart`) et courbes (`UPSLineChart`)
|
||||
|
||||
- `SetCategories(TArray<FString>)` — étiquettes de l'axe X
|
||||
- `SetSeries(TArray<FPSChartSeries>)` — remplace toutes les séries (`Name` + `Values`)
|
||||
- `SetSingleSeries(TArray<float>, Name)` — cas simple à une seule série
|
||||
- `SetSeriesValues(Name, TArray<float>)` — met à jour une série existante (la crée si absente)
|
||||
- `ClearData()`
|
||||
|
||||
Options d'axe : `bShowGrid`, `bAutoRange` (échelle automatique « propre »), `bIncludeZero`, `AxisMinValue` / `AxisMaxValue`, `ValueSuffix`.
|
||||
Histogramme : `CategoryGapRatio`, `BarGapRatio`, `bShowValueLabels`.
|
||||
Courbes : `Thickness`, `bShowPoints`, `PointRadius`, `bSmooth` (Catmull-Rom), `bShowValueLabels`.
|
||||
|
||||
### Style commun
|
||||
|
||||
`Title`, `TitleColor`, `TitleFontSize`, `TextColor`, `FontSize`, `Palette` (couleurs auto des séries/parts), `LegendPosition` (haut/bas/gauche/droite/masquée), `BackgroundColor`.
|
||||
|
||||
Après modification directe d'une propriété en Blueprint (sans passer par un setter), appeler `Refresh()` pour repousser les valeurs vers le rendu.
|
||||
|
||||
### Exemple C++
|
||||
|
||||
```cpp
|
||||
#include "PSBarChart.h"
|
||||
|
||||
BarChart->SetCategories({ TEXT("Jan"), TEXT("Feb"), TEXT("Mar") });
|
||||
BarChart->SetSeriesValues(TEXT("Ventes"), { 12.f, 25.f, 18.f }); // mise a jour live
|
||||
```
|
||||
|
||||
Les widgets Slate purs (`SPSPieChart`, `SPSBarChart`, `SPSLineChart`) sont exportés et utilisables directement hors UMG.
|
||||
|
||||
## Migration depuis SimpleCharts
|
||||
|
||||
| SimpleCharts | PS Charts |
|
||||
|---|---|
|
||||
| `UPieChart::SetSeries(FPieSeries)` avec `data : TMap<FString,float>` | `UPSPieChart::SetValues(TMap<FString,float>)` |
|
||||
| `UBarChart::SetSeries(TArray<FBarSeries>)` (`name` + `data`) | `UPSBarChart::SetSeries(TArray<FPSChartSeries>)` (`Name` + `Values`) |
|
||||
| `ULineChart::SetSeries(TArray<FLineSeries>)` (`smooth`) | `UPSLineChart::SetSeries(...)` + `bSmooth` |
|
||||
| `SetLegend(show, position, orient, color, fontSize)` | `LegendPosition`, `TextColor`, `FontSize` + `Refresh()` |
|
||||
| Thèmes ECharts | `Palette` (tableau de couleurs) |
|
||||
@@ -0,0 +1,19 @@
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class PS_Charts : ModuleRules
|
||||
{
|
||||
public PS_Charts(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicDependencyModuleNames.AddRange(new string[]
|
||||
{
|
||||
"Core",
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"UMG"
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// PS_Charts - widget UMG histogramme
|
||||
#include "PSBarChart.h"
|
||||
|
||||
#include "SPSBarChart.h"
|
||||
|
||||
UPSBarChart::UPSBarChart()
|
||||
{
|
||||
// Donnees d'exemple pour la previsualisation dans le designer UMG
|
||||
Categories = { TEXT("Jan"), TEXT("Feb"), TEXT("Mar"), TEXT("Apr"), TEXT("May"), TEXT("Jun") };
|
||||
Series.Emplace(TEXT("Serie 1"), TArray<float>{ 12.f, 25.f, 18.f, 30.f, 22.f, 27.f });
|
||||
Series.Emplace(TEXT("Serie 2"), TArray<float>{ 8.f, 15.f, 22.f, 18.f, 26.f, 20.f });
|
||||
}
|
||||
|
||||
void UPSBarChart::ReleaseSlateResources(bool bReleaseChildren)
|
||||
{
|
||||
Super::ReleaseSlateResources(bReleaseChildren);
|
||||
MyChart.Reset();
|
||||
}
|
||||
|
||||
TSharedRef<SWidget> UPSBarChart::RebuildWidget()
|
||||
{
|
||||
MyChart = SNew(SPSBarChart);
|
||||
ApplyToSlate();
|
||||
return MyChart.ToSharedRef();
|
||||
}
|
||||
|
||||
void UPSBarChart::ApplyToSlate()
|
||||
{
|
||||
if (!MyChart.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MyChart->SetCommonParams(MakeCommonParams());
|
||||
MyChart->SetAxisParams(MakeAxisParams());
|
||||
|
||||
FPSBarParams Params;
|
||||
Params.CategoryGapRatio = CategoryGapRatio;
|
||||
Params.BarGapRatio = BarGapRatio;
|
||||
Params.bShowValueLabels = bShowValueLabels;
|
||||
MyChart->SetBarParams(Params);
|
||||
|
||||
MyChart->SetData(Categories, Series);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// PS_Charts - base commune histogramme / courbes
|
||||
#include "PSCartesianChartBase.h"
|
||||
|
||||
void UPSCartesianChartBase::SetCategories(const TArray<FString>& InCategories)
|
||||
{
|
||||
Categories = InCategories;
|
||||
ApplyToSlate();
|
||||
}
|
||||
|
||||
void UPSCartesianChartBase::SetSeries(const TArray<FPSChartSeries>& InSeries)
|
||||
{
|
||||
Series = InSeries;
|
||||
ApplyToSlate();
|
||||
}
|
||||
|
||||
void UPSCartesianChartBase::SetSingleSeries(const TArray<float>& InValues, const FString& InName)
|
||||
{
|
||||
Series.Reset(1);
|
||||
Series.Emplace(InName, InValues);
|
||||
ApplyToSlate();
|
||||
}
|
||||
|
||||
void UPSCartesianChartBase::SetSeriesValues(const FString& InName, const TArray<float>& InValues)
|
||||
{
|
||||
FPSChartSeries* Found = Series.FindByPredicate([&InName](const FPSChartSeries& OneSeries)
|
||||
{
|
||||
return OneSeries.Name == InName;
|
||||
});
|
||||
|
||||
if (Found)
|
||||
{
|
||||
Found->Values = InValues;
|
||||
}
|
||||
else
|
||||
{
|
||||
Series.Emplace(InName, InValues);
|
||||
}
|
||||
ApplyToSlate();
|
||||
}
|
||||
|
||||
void UPSCartesianChartBase::ClearData()
|
||||
{
|
||||
Categories.Reset();
|
||||
Series.Reset();
|
||||
ApplyToSlate();
|
||||
}
|
||||
|
||||
FPSAxisParams UPSCartesianChartBase::MakeAxisParams() const
|
||||
{
|
||||
FPSAxisParams Params;
|
||||
Params.bShowGrid = bShowGrid;
|
||||
Params.GridColor = GridColor;
|
||||
Params.AxisColor = AxisColor;
|
||||
Params.bAutoRange = bAutoRange;
|
||||
Params.bIncludeZero = bIncludeZero;
|
||||
Params.MinValue = AxisMinValue;
|
||||
Params.MaxValue = AxisMaxValue;
|
||||
Params.ValueSuffix = ValueSuffix;
|
||||
return Params;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// PS_Charts - palette et resolution des couleurs
|
||||
#include "PSChartTypes.h"
|
||||
|
||||
namespace PSCharts
|
||||
{
|
||||
|
||||
const TArray<FLinearColor>& DefaultPalette()
|
||||
{
|
||||
static const TArray<FLinearColor> Palette = {
|
||||
FLinearColor(FColor(0x4E, 0x79, 0xA7)),
|
||||
FLinearColor(FColor(0xF2, 0x8E, 0x2B)),
|
||||
FLinearColor(FColor(0xE1, 0x57, 0x59)),
|
||||
FLinearColor(FColor(0x76, 0xB7, 0xB2)),
|
||||
FLinearColor(FColor(0x59, 0xA1, 0x4F)),
|
||||
FLinearColor(FColor(0xED, 0xC9, 0x48)),
|
||||
FLinearColor(FColor(0xB0, 0x7A, 0xA1)),
|
||||
FLinearColor(FColor(0xFF, 0x9D, 0xA7)),
|
||||
FLinearColor(FColor(0x9C, 0x75, 0x5F)),
|
||||
FLinearColor(FColor(0xBA, 0xB0, 0xAC))
|
||||
};
|
||||
return Palette;
|
||||
}
|
||||
|
||||
FLinearColor ResolveColor(int32 Index, const TArray<FLinearColor>& Palette, bool bUseCustom, const FLinearColor& Custom)
|
||||
{
|
||||
if (bUseCustom)
|
||||
{
|
||||
return Custom;
|
||||
}
|
||||
const TArray<FLinearColor>& Colors = Palette.Num() > 0 ? Palette : DefaultPalette();
|
||||
return Colors[FMath::Abs(Index) % Colors.Num()];
|
||||
}
|
||||
|
||||
} // namespace PSCharts
|
||||
@@ -0,0 +1,49 @@
|
||||
// PS_Charts - base commune des widgets UMG
|
||||
#include "PSChartWidgetBase.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "PS_Charts"
|
||||
|
||||
UPSChartWidgetBase::UPSChartWidgetBase()
|
||||
{
|
||||
Palette = PSCharts::DefaultPalette();
|
||||
}
|
||||
|
||||
void UPSChartWidgetBase::SetChartTitle(FText InTitle)
|
||||
{
|
||||
Title = InTitle;
|
||||
ApplyToSlate();
|
||||
}
|
||||
|
||||
void UPSChartWidgetBase::Refresh()
|
||||
{
|
||||
ApplyToSlate();
|
||||
}
|
||||
|
||||
void UPSChartWidgetBase::SynchronizeProperties()
|
||||
{
|
||||
Super::SynchronizeProperties();
|
||||
ApplyToSlate();
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
const FText UPSChartWidgetBase::GetPaletteCategory()
|
||||
{
|
||||
return LOCTEXT("PaletteCategory", "PS Charts");
|
||||
}
|
||||
#endif
|
||||
|
||||
FPSChartCommonParams UPSChartWidgetBase::MakeCommonParams() const
|
||||
{
|
||||
FPSChartCommonParams Params;
|
||||
Params.Title = Title;
|
||||
Params.TitleColor = TitleColor;
|
||||
Params.TitleFontSize = TitleFontSize;
|
||||
Params.TextColor = TextColor;
|
||||
Params.FontSize = FontSize;
|
||||
Params.Palette = Palette;
|
||||
Params.LegendPosition = LegendPosition;
|
||||
Params.BackgroundColor = BackgroundColor;
|
||||
return Params;
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,8 @@
|
||||
// PS_Charts - module runtime
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
class FPS_ChartsModule : public IModuleInterface
|
||||
{
|
||||
};
|
||||
|
||||
IMPLEMENT_MODULE(FPS_ChartsModule, PS_Charts)
|
||||
@@ -0,0 +1,495 @@
|
||||
// PS_Charts - utilitaires de dessin partages
|
||||
#include "PSChartsPaintUtil.h"
|
||||
|
||||
#include "Fonts/FontMeasure.h"
|
||||
#include "Framework/Application/SlateApplication.h"
|
||||
#include "Rendering/SlateResourceHandle.h"
|
||||
#include "Styling/CoreStyle.h"
|
||||
#include "Textures/SlateShaderResource.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
double NiceNum(double Range, bool bRound)
|
||||
{
|
||||
if (Range <= 0.0)
|
||||
{
|
||||
return 1.0;
|
||||
}
|
||||
const double Exponent = FMath::FloorToDouble(FMath::LogX(10.0, Range));
|
||||
const double Fraction = Range / FMath::Pow(10.0, Exponent);
|
||||
double Nice;
|
||||
if (bRound)
|
||||
{
|
||||
Nice = (Fraction < 1.5) ? 1.0 : (Fraction < 3.0) ? 2.0 : (Fraction < 7.0) ? 5.0 : 10.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Nice = (Fraction <= 1.0) ? 1.0 : (Fraction <= 2.0) ? 2.0 : (Fraction <= 5.0) ? 5.0 : 10.0;
|
||||
}
|
||||
return Nice * FMath::Pow(10.0, Exponent);
|
||||
}
|
||||
|
||||
TSharedRef<class FSlateFontMeasure> GetFontMeasure()
|
||||
{
|
||||
return FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
|
||||
}
|
||||
}
|
||||
|
||||
namespace PSChartsPaint
|
||||
{
|
||||
|
||||
const FSlateBrush* GetWhiteBrush()
|
||||
{
|
||||
return FCoreStyle::Get().GetBrush("GenericWhiteBox");
|
||||
}
|
||||
|
||||
FSlateFontInfo GetFont(float Size, bool bBold)
|
||||
{
|
||||
return FCoreStyle::GetDefaultFontStyle(bBold ? "Bold" : "Regular", FMath::RoundToInt(Size));
|
||||
}
|
||||
|
||||
FVector2f PolarPoint(const FVector2f& Center, float Radius, float AngleRad)
|
||||
{
|
||||
return FVector2f(Center.X + Radius * FMath::Sin(AngleRad), Center.Y - Radius * FMath::Cos(AngleRad));
|
||||
}
|
||||
|
||||
FVector2f GetAtlasUV(const FSlateResourceHandle& Handle)
|
||||
{
|
||||
if (const FSlateShaderResourceProxy* Proxy = Handle.GetResourceProxy())
|
||||
{
|
||||
return Proxy->StartUV + Proxy->SizeUV * 0.5f;
|
||||
}
|
||||
return FVector2f(0.5f, 0.5f);
|
||||
}
|
||||
|
||||
void AddArcSector(TArray<FSlateVertex>& Verts, TArray<SlateIndex>& Indices,
|
||||
const FSlateRenderTransform& RenderTransform, const FVector2f& AtlasUV,
|
||||
const FVector2f& Center, float OuterRadius, float InnerRadius,
|
||||
float StartAngleRad, float EndAngleRad, const FColor& Color)
|
||||
{
|
||||
const float Sweep = EndAngleRad - StartAngleRad;
|
||||
if (Sweep <= 0.f || OuterRadius <= 0.f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InnerRadius = FMath::Clamp(InnerRadius, 0.f, OuterRadius);
|
||||
const int32 NumSegments = FMath::Clamp(FMath::CeilToInt(Sweep / FMath::DegreesToRadians(4.f)), 1, 256);
|
||||
const int32 BaseIndex = Verts.Num();
|
||||
|
||||
for (int32 Segment = 0; Segment <= NumSegments; ++Segment)
|
||||
{
|
||||
const float Angle = StartAngleRad + Sweep * Segment / NumSegments;
|
||||
const FVector2f Inner = (InnerRadius > KINDA_SMALL_NUMBER) ? PolarPoint(Center, InnerRadius, Angle) : Center;
|
||||
const FVector2f Outer = PolarPoint(Center, OuterRadius, Angle);
|
||||
Verts.Add(FSlateVertex::Make<ESlateVertexRounding::Disabled>(RenderTransform, Inner, AtlasUV, Color));
|
||||
Verts.Add(FSlateVertex::Make<ESlateVertexRounding::Disabled>(RenderTransform, Outer, AtlasUV, Color));
|
||||
}
|
||||
|
||||
for (int32 Segment = 0; Segment < NumSegments; ++Segment)
|
||||
{
|
||||
const SlateIndex I0 = BaseIndex + Segment * 2;
|
||||
Indices.Add(I0);
|
||||
Indices.Add(I0 + 1);
|
||||
Indices.Add(I0 + 3);
|
||||
Indices.Add(I0);
|
||||
Indices.Add(I0 + 3);
|
||||
Indices.Add(I0 + 2);
|
||||
}
|
||||
}
|
||||
|
||||
void AddCircle(TArray<FSlateVertex>& Verts, TArray<SlateIndex>& Indices,
|
||||
const FSlateRenderTransform& RenderTransform, const FVector2f& AtlasUV,
|
||||
const FVector2f& Center, float Radius, const FColor& Color, int32 NumSegments)
|
||||
{
|
||||
if (Radius <= 0.f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NumSegments = FMath::Clamp(NumSegments, 6, 64);
|
||||
const int32 BaseIndex = Verts.Num();
|
||||
Verts.Add(FSlateVertex::Make<ESlateVertexRounding::Disabled>(RenderTransform, Center, AtlasUV, Color));
|
||||
|
||||
for (int32 Segment = 0; Segment <= NumSegments; ++Segment)
|
||||
{
|
||||
const float Angle = 2.f * PI * Segment / NumSegments;
|
||||
Verts.Add(FSlateVertex::Make<ESlateVertexRounding::Disabled>(RenderTransform, PolarPoint(Center, Radius, Angle), AtlasUV, Color));
|
||||
}
|
||||
|
||||
for (int32 Segment = 0; Segment < NumSegments; ++Segment)
|
||||
{
|
||||
Indices.Add(BaseIndex);
|
||||
Indices.Add(BaseIndex + 1 + Segment);
|
||||
Indices.Add(BaseIndex + 2 + Segment);
|
||||
}
|
||||
}
|
||||
|
||||
void ComputeNiceAxis(float DataMin, float DataMax, int32 TargetTicks, float& OutMin, float& OutMax, float& OutStep)
|
||||
{
|
||||
if (!FMath::IsFinite(DataMin) || !FMath::IsFinite(DataMax))
|
||||
{
|
||||
DataMin = 0.f;
|
||||
DataMax = 1.f;
|
||||
}
|
||||
if (DataMax < DataMin)
|
||||
{
|
||||
Swap(DataMin, DataMax);
|
||||
}
|
||||
if (FMath::IsNearlyEqual(DataMax, DataMin))
|
||||
{
|
||||
const float Pad = FMath::Max(1.f, FMath::Abs(DataMax) * 0.1f);
|
||||
DataMin -= Pad;
|
||||
DataMax += Pad;
|
||||
}
|
||||
|
||||
const double Range = NiceNum(double(DataMax) - double(DataMin), false);
|
||||
OutStep = float(NiceNum(Range / FMath::Max(1, TargetTicks - 1), true));
|
||||
OutMin = float(FMath::FloorToDouble(DataMin / OutStep) * OutStep);
|
||||
OutMax = float(FMath::CeilToDouble(DataMax / OutStep) * OutStep);
|
||||
}
|
||||
|
||||
FString FormatValue(float Value, int32 MaxDecimals)
|
||||
{
|
||||
FString Result = FString::Printf(TEXT("%.*f"), MaxDecimals, Value);
|
||||
if (Result.Contains(TEXT(".")))
|
||||
{
|
||||
while (Result.EndsWith(TEXT("0")))
|
||||
{
|
||||
Result.LeftChopInline(1, EAllowShrinking::No);
|
||||
}
|
||||
if (Result.EndsWith(TEXT(".")))
|
||||
{
|
||||
Result.LeftChopInline(1, EAllowShrinking::No);
|
||||
}
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
FString FormatAxisValue(float Value, float Step)
|
||||
{
|
||||
int32 Decimals = 0;
|
||||
if (Step > 0.f && Step < 1.f)
|
||||
{
|
||||
Decimals = FMath::Clamp(FMath::CeilToInt(-FMath::LogX(10.f, Step)), 0, 3);
|
||||
}
|
||||
return FString::Printf(TEXT("%.*f"), Decimals, Value);
|
||||
}
|
||||
|
||||
TArray<FVector2f> SmoothPolyline(const TArray<FVector2f>& Points, int32 SubdivisionsPerSegment)
|
||||
{
|
||||
if (Points.Num() < 3)
|
||||
{
|
||||
return Points;
|
||||
}
|
||||
|
||||
SubdivisionsPerSegment = FMath::Clamp(SubdivisionsPerSegment, 2, 32);
|
||||
TArray<FVector2f> Result;
|
||||
Result.Reserve((Points.Num() - 1) * SubdivisionsPerSegment + 1);
|
||||
Result.Add(Points[0]);
|
||||
|
||||
for (int32 Index = 0; Index < Points.Num() - 1; ++Index)
|
||||
{
|
||||
const FVector2f P0 = Points[FMath::Max(Index - 1, 0)];
|
||||
const FVector2f P1 = Points[Index];
|
||||
const FVector2f P2 = Points[Index + 1];
|
||||
const FVector2f P3 = Points[FMath::Min(Index + 2, Points.Num() - 1)];
|
||||
|
||||
for (int32 Sub = 1; Sub <= SubdivisionsPerSegment; ++Sub)
|
||||
{
|
||||
const float T = float(Sub) / SubdivisionsPerSegment;
|
||||
const float T2 = T * T;
|
||||
const float T3 = T2 * T;
|
||||
// Catmull-Rom uniforme
|
||||
const FVector2f Point = 0.5f * ((2.f * P1)
|
||||
+ (-P0 + P2) * T
|
||||
+ (2.f * P0 - 5.f * P1 + 4.f * P2 - P3) * T2
|
||||
+ (-P0 + 3.f * P1 - 3.f * P2 + P3) * T3);
|
||||
Result.Add(Point);
|
||||
}
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
void DrawTextAt(FSlateWindowElementList& OutDrawElements, int32 LayerId, const FGeometry& Geometry,
|
||||
const FVector2f& Position, const FVector2f& Size, const FString& Text, const FSlateFontInfo& Font,
|
||||
const FLinearColor& Color, ESlateDrawEffect DrawEffects)
|
||||
{
|
||||
FSlateDrawElement::MakeText(OutDrawElements, LayerId,
|
||||
Geometry.ToPaintGeometry(Size, FSlateLayoutTransform(Position)),
|
||||
Text, Font, DrawEffects, Color);
|
||||
}
|
||||
|
||||
void DrawBackground(FSlateWindowElementList& OutDrawElements, int32& LayerId, const FGeometry& Geometry,
|
||||
const FLinearColor& Color, ESlateDrawEffect DrawEffects)
|
||||
{
|
||||
if (Color.A <= 0.f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
FSlateDrawElement::MakeBox(OutDrawElements, LayerId,
|
||||
Geometry.ToPaintGeometry(FVector2f(Geometry.GetLocalSize()), FSlateLayoutTransform(FVector2f::ZeroVector)),
|
||||
GetWhiteBrush(), DrawEffects, Color);
|
||||
++LayerId;
|
||||
}
|
||||
|
||||
FSlateRect DrawTitle(FSlateWindowElementList& OutDrawElements, int32& LayerId, const FGeometry& Geometry,
|
||||
const FSlateRect& Rect, const FText& Title, const FLinearColor& Color, float FontSize, ESlateDrawEffect DrawEffects)
|
||||
{
|
||||
if (Title.IsEmpty())
|
||||
{
|
||||
return Rect;
|
||||
}
|
||||
|
||||
const FSlateFontInfo Font = GetFont(FontSize, true);
|
||||
const FVector2D TextSize = GetFontMeasure()->Measure(Title.ToString(), Font);
|
||||
const float X = FMath::Max(Rect.Left, Rect.Left + (Rect.GetSize().X - float(TextSize.X)) * 0.5f);
|
||||
DrawTextAt(OutDrawElements, LayerId, Geometry, FVector2f(X, Rect.Top), FVector2f(TextSize), Title.ToString(), Font, Color, DrawEffects);
|
||||
++LayerId;
|
||||
|
||||
FSlateRect Result = Rect;
|
||||
Result.Top += float(TextSize.Y) + 6.f;
|
||||
return Result;
|
||||
}
|
||||
|
||||
FSlateRect DrawLegend(FSlateWindowElementList& OutDrawElements, int32& LayerId, const FGeometry& Geometry,
|
||||
const FSlateRect& Rect, const TArray<FLegendEntry>& Entries, EPSChartLegendPosition Position,
|
||||
const FLinearColor& TextColor, float FontSize, ESlateDrawEffect DrawEffects)
|
||||
{
|
||||
if (Position == EPSChartLegendPosition::None || Entries.Num() == 0)
|
||||
{
|
||||
return Rect;
|
||||
}
|
||||
|
||||
const FSlateFontInfo Font = GetFont(FontSize);
|
||||
TSharedRef<FSlateFontMeasure> FontMeasure = GetFontMeasure();
|
||||
const float FontHeight = float(FontMeasure->GetMaxCharacterHeight(Font));
|
||||
const float SwatchSize = 11.f;
|
||||
const float SwatchPad = 5.f;
|
||||
const float EntryGap = 14.f;
|
||||
const float EntryHeight = FMath::Max(SwatchSize, FontHeight);
|
||||
const FSlateBrush* WhiteBrush = GetWhiteBrush();
|
||||
|
||||
TArray<float> TextWidths;
|
||||
TextWidths.Reserve(Entries.Num());
|
||||
float MaxEntryWidth = 0.f;
|
||||
float TotalWidth = 0.f;
|
||||
for (const FLegendEntry& Entry : Entries)
|
||||
{
|
||||
const float TextWidth = float(FontMeasure->Measure(Entry.Label, Font).X);
|
||||
TextWidths.Add(TextWidth);
|
||||
const float EntryWidth = SwatchSize + SwatchPad + TextWidth;
|
||||
MaxEntryWidth = FMath::Max(MaxEntryWidth, EntryWidth);
|
||||
TotalWidth += EntryWidth;
|
||||
}
|
||||
TotalWidth += EntryGap * (Entries.Num() - 1);
|
||||
|
||||
FSlateRect Result = Rect;
|
||||
|
||||
auto DrawEntry = [&](int32 Index, float X, float Y)
|
||||
{
|
||||
FSlateDrawElement::MakeBox(OutDrawElements, LayerId,
|
||||
Geometry.ToPaintGeometry(FVector2f(SwatchSize, SwatchSize), FSlateLayoutTransform(FVector2f(X, Y + (EntryHeight - SwatchSize) * 0.5f))),
|
||||
WhiteBrush, DrawEffects, Entries[Index].Color);
|
||||
DrawTextAt(OutDrawElements, LayerId + 1, Geometry,
|
||||
FVector2f(X + SwatchSize + SwatchPad, Y + (EntryHeight - FontHeight) * 0.5f),
|
||||
FVector2f(TextWidths[Index], FontHeight), Entries[Index].Label, Font, TextColor, DrawEffects);
|
||||
};
|
||||
|
||||
if (Position == EPSChartLegendPosition::Top || Position == EPSChartLegendPosition::Bottom)
|
||||
{
|
||||
const float StartY = (Position == EPSChartLegendPosition::Top) ? Rect.Top : Rect.Bottom - EntryHeight;
|
||||
float X = FMath::Max(Rect.Left, Rect.Left + (Rect.GetSize().X - TotalWidth) * 0.5f);
|
||||
for (int32 Index = 0; Index < Entries.Num(); ++Index)
|
||||
{
|
||||
DrawEntry(Index, X, StartY);
|
||||
X += SwatchSize + SwatchPad + TextWidths[Index] + EntryGap;
|
||||
}
|
||||
|
||||
if (Position == EPSChartLegendPosition::Top)
|
||||
{
|
||||
Result.Top += EntryHeight + 8.f;
|
||||
}
|
||||
else
|
||||
{
|
||||
Result.Bottom -= EntryHeight + 8.f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const float LineHeight = EntryHeight + 4.f;
|
||||
const float TotalHeight = LineHeight * Entries.Num();
|
||||
const float X = (Position == EPSChartLegendPosition::Left) ? Rect.Left : Rect.Right - MaxEntryWidth;
|
||||
float Y = FMath::Max(Rect.Top, Rect.Top + (Rect.GetSize().Y - TotalHeight) * 0.5f);
|
||||
for (int32 Index = 0; Index < Entries.Num(); ++Index)
|
||||
{
|
||||
DrawEntry(Index, X, Y);
|
||||
Y += LineHeight;
|
||||
}
|
||||
|
||||
if (Position == EPSChartLegendPosition::Left)
|
||||
{
|
||||
Result.Left += MaxEntryWidth + 12.f;
|
||||
}
|
||||
else
|
||||
{
|
||||
Result.Right -= MaxEntryWidth + 12.f;
|
||||
}
|
||||
}
|
||||
|
||||
LayerId += 2;
|
||||
return Result;
|
||||
}
|
||||
|
||||
FAxisLayout DrawValueAxis(FSlateWindowElementList& OutDrawElements, int32& LayerId, const FGeometry& Geometry,
|
||||
const FSlateRect& Rect, float BottomReserve, const FPSAxisParams& Axis, float DataMin, float DataMax,
|
||||
const FLinearColor& TextColor, float FontSize, ESlateDrawEffect DrawEffects)
|
||||
{
|
||||
FAxisLayout Layout;
|
||||
|
||||
if (Axis.bAutoRange)
|
||||
{
|
||||
if (Axis.bIncludeZero)
|
||||
{
|
||||
DataMin = FMath::Min(DataMin, 0.f);
|
||||
DataMax = FMath::Max(DataMax, 0.f);
|
||||
}
|
||||
ComputeNiceAxis(DataMin, DataMax, 5, Layout.Min, Layout.Max, Layout.Step);
|
||||
}
|
||||
else
|
||||
{
|
||||
Layout.Min = Axis.MinValue;
|
||||
Layout.Max = (Axis.MaxValue > Axis.MinValue) ? Axis.MaxValue : Axis.MinValue + 1.f;
|
||||
Layout.Step = float(NiceNum((Layout.Max - Layout.Min) / 4.0, true));
|
||||
}
|
||||
|
||||
const FSlateFontInfo Font = GetFont(FontSize);
|
||||
TSharedRef<FSlateFontMeasure> FontMeasure = GetFontMeasure();
|
||||
const float FontHeight = float(FontMeasure->GetMaxCharacterHeight(Font));
|
||||
|
||||
// Graduations
|
||||
TArray<float> TickValues;
|
||||
for (int32 Tick = 0; Tick <= 64; ++Tick)
|
||||
{
|
||||
const float Value = Layout.Min + Layout.Step * Tick;
|
||||
if (Value > Layout.Max + Layout.Step * 0.001f)
|
||||
{
|
||||
break;
|
||||
}
|
||||
TickValues.Add(Value);
|
||||
}
|
||||
|
||||
float MaxLabelWidth = 0.f;
|
||||
TArray<FString> TickLabels;
|
||||
TickLabels.Reserve(TickValues.Num());
|
||||
for (const float Value : TickValues)
|
||||
{
|
||||
FString Label = FormatAxisValue(Value, Layout.Step) + Axis.ValueSuffix;
|
||||
MaxLabelWidth = FMath::Max(MaxLabelWidth, float(FontMeasure->Measure(Label, Font).X));
|
||||
TickLabels.Add(MoveTemp(Label));
|
||||
}
|
||||
|
||||
Layout.PlotRect = FSlateRect(
|
||||
Rect.Left + MaxLabelWidth + 8.f,
|
||||
Rect.Top + FontHeight * 0.6f,
|
||||
Rect.Right - 4.f,
|
||||
Rect.Bottom - BottomReserve);
|
||||
|
||||
if (Layout.PlotRect.GetSize().X < 10.f || Layout.PlotRect.GetSize().Y < 10.f)
|
||||
{
|
||||
return Layout;
|
||||
}
|
||||
Layout.bValid = true;
|
||||
|
||||
// Grille + etiquettes de graduations
|
||||
for (int32 Index = 0; Index < TickValues.Num(); ++Index)
|
||||
{
|
||||
const float Y = Layout.ValueToY(TickValues[Index]);
|
||||
|
||||
if (Axis.bShowGrid)
|
||||
{
|
||||
TArray<FVector2f> GridPoints;
|
||||
GridPoints.Add(FVector2f(Layout.PlotRect.Left, Y));
|
||||
GridPoints.Add(FVector2f(Layout.PlotRect.Right, Y));
|
||||
FSlateDrawElement::MakeLines(OutDrawElements, LayerId, Geometry.ToPaintGeometry(),
|
||||
MoveTemp(GridPoints), DrawEffects, Axis.GridColor, false, 1.f);
|
||||
}
|
||||
|
||||
const float LabelWidth = float(FontMeasure->Measure(TickLabels[Index], Font).X);
|
||||
DrawTextAt(OutDrawElements, LayerId + 1, Geometry,
|
||||
FVector2f(Layout.PlotRect.Left - 6.f - LabelWidth, Y - FontHeight * 0.5f),
|
||||
FVector2f(LabelWidth, FontHeight), TickLabels[Index], Font, TextColor, DrawEffects);
|
||||
}
|
||||
|
||||
// Axe Y (gauche)
|
||||
{
|
||||
TArray<FVector2f> AxisPoints;
|
||||
AxisPoints.Add(FVector2f(Layout.PlotRect.Left, Layout.PlotRect.Top));
|
||||
AxisPoints.Add(FVector2f(Layout.PlotRect.Left, Layout.PlotRect.Bottom));
|
||||
FSlateDrawElement::MakeLines(OutDrawElements, LayerId + 2, Geometry.ToPaintGeometry(),
|
||||
MoveTemp(AxisPoints), DrawEffects, Axis.AxisColor, false, 1.f);
|
||||
}
|
||||
|
||||
// Axe X (bas) et ligne du zero si necessaire
|
||||
{
|
||||
TArray<FVector2f> AxisPoints;
|
||||
AxisPoints.Add(FVector2f(Layout.PlotRect.Left, Layout.PlotRect.Bottom));
|
||||
AxisPoints.Add(FVector2f(Layout.PlotRect.Right, Layout.PlotRect.Bottom));
|
||||
FSlateDrawElement::MakeLines(OutDrawElements, LayerId + 2, Geometry.ToPaintGeometry(),
|
||||
MoveTemp(AxisPoints), DrawEffects, Axis.AxisColor, false, 1.f);
|
||||
}
|
||||
if (Layout.Min < 0.f && Layout.Max > 0.f)
|
||||
{
|
||||
const float ZeroY = Layout.ValueToY(0.f);
|
||||
TArray<FVector2f> ZeroPoints;
|
||||
ZeroPoints.Add(FVector2f(Layout.PlotRect.Left, ZeroY));
|
||||
ZeroPoints.Add(FVector2f(Layout.PlotRect.Right, ZeroY));
|
||||
FSlateDrawElement::MakeLines(OutDrawElements, LayerId + 2, Geometry.ToPaintGeometry(),
|
||||
MoveTemp(ZeroPoints), DrawEffects, Axis.AxisColor, false, 1.f);
|
||||
}
|
||||
|
||||
LayerId += 3;
|
||||
return Layout;
|
||||
}
|
||||
|
||||
void DrawCategoryLabels(FSlateWindowElementList& OutDrawElements, int32 LayerId, const FGeometry& Geometry,
|
||||
const FAxisLayout& Layout, const TArray<FString>& Labels, TFunctionRef<float(int32)> GetCenterX,
|
||||
float SlotWidth, const FLinearColor& TextColor, float FontSize, ESlateDrawEffect DrawEffects)
|
||||
{
|
||||
if (Labels.Num() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const FSlateFontInfo Font = GetFont(FontSize);
|
||||
TSharedRef<FSlateFontMeasure> FontMeasure = GetFontMeasure();
|
||||
const float FontHeight = float(FontMeasure->GetMaxCharacterHeight(Font));
|
||||
|
||||
float MaxLabelWidth = 0.f;
|
||||
TArray<float> LabelWidths;
|
||||
LabelWidths.Reserve(Labels.Num());
|
||||
for (const FString& Label : Labels)
|
||||
{
|
||||
const float Width = float(FontMeasure->Measure(Label, Font).X);
|
||||
LabelWidths.Add(Width);
|
||||
MaxLabelWidth = FMath::Max(MaxLabelWidth, Width);
|
||||
}
|
||||
|
||||
// Saute des etiquettes si elles ne tiennent pas dans la largeur d'un emplacement
|
||||
const int32 SkipStep = (SlotWidth > 1.f) ? FMath::Max(1, FMath::CeilToInt((MaxLabelWidth + 6.f) / SlotWidth)) : 1;
|
||||
|
||||
for (int32 Index = 0; Index < Labels.Num(); ++Index)
|
||||
{
|
||||
if (Index % SkipStep != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const float X = FMath::Clamp(GetCenterX(Index) - LabelWidths[Index] * 0.5f,
|
||||
0.f, FMath::Max(0.f, Layout.PlotRect.Right - LabelWidths[Index]));
|
||||
DrawTextAt(OutDrawElements, LayerId, Geometry,
|
||||
FVector2f(X, Layout.PlotRect.Bottom + 5.f),
|
||||
FVector2f(LabelWidths[Index], FontHeight), Labels[Index], Font, TextColor, DrawEffects);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace PSChartsPaint
|
||||
@@ -0,0 +1,95 @@
|
||||
// PS_Charts - utilitaires de dessin partages entre les widgets Slate
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Rendering/DrawElements.h"
|
||||
#include "Fonts/SlateFontInfo.h"
|
||||
#include "PSChartTypes.h"
|
||||
|
||||
namespace PSChartsPaint
|
||||
{
|
||||
struct FLegendEntry
|
||||
{
|
||||
FString Label;
|
||||
FLinearColor Color;
|
||||
};
|
||||
|
||||
/** Resultat du trace de l'axe des valeurs. */
|
||||
struct FAxisLayout
|
||||
{
|
||||
bool bValid = false;
|
||||
FSlateRect PlotRect = FSlateRect(0, 0, 0, 0);
|
||||
float Min = 0.f;
|
||||
float Max = 1.f;
|
||||
float Step = 1.f;
|
||||
|
||||
float ValueToY(float Value) const
|
||||
{
|
||||
const float Range = FMath::Max(Max - Min, KINDA_SMALL_NUMBER);
|
||||
return PlotRect.Bottom - (Value - Min) / Range * PlotRect.GetSize().Y;
|
||||
}
|
||||
};
|
||||
|
||||
const FSlateBrush* GetWhiteBrush();
|
||||
|
||||
FSlateFontInfo GetFont(float Size, bool bBold = false);
|
||||
|
||||
/** Point sur un cercle ; angle 0 = midi, sens horaire, en radians. */
|
||||
FVector2f PolarPoint(const FVector2f& Center, float Radius, float AngleRad);
|
||||
|
||||
/** Ajoute un secteur annulaire (part de camembert / donut) au buffer de vertex. */
|
||||
void AddArcSector(TArray<FSlateVertex>& Verts, TArray<SlateIndex>& Indices,
|
||||
const FSlateRenderTransform& RenderTransform, const FVector2f& AtlasUV,
|
||||
const FVector2f& Center, float OuterRadius, float InnerRadius,
|
||||
float StartAngleRad, float EndAngleRad, const FColor& Color);
|
||||
|
||||
/** Ajoute un disque plein (marqueur de point) au buffer de vertex. */
|
||||
void AddCircle(TArray<FSlateVertex>& Verts, TArray<SlateIndex>& Indices,
|
||||
const FSlateRenderTransform& RenderTransform, const FVector2f& AtlasUV,
|
||||
const FVector2f& Center, float Radius, const FColor& Color, int32 NumSegments = 20);
|
||||
|
||||
/** UV du centre de la texture blanche dans son atlas. */
|
||||
FVector2f GetAtlasUV(const FSlateResourceHandle& Handle);
|
||||
|
||||
/** Echelle "propre" (bornes et pas arrondis) pour l'axe des valeurs. */
|
||||
void ComputeNiceAxis(float DataMin, float DataMax, int32 TargetTicks, float& OutMin, float& OutMax, float& OutStep);
|
||||
|
||||
/** Formate une valeur en supprimant les zeros inutiles. */
|
||||
FString FormatValue(float Value, int32 MaxDecimals = 2);
|
||||
|
||||
/** Formate une valeur de graduation selon le pas de l'axe. */
|
||||
FString FormatAxisValue(float Value, float Step);
|
||||
|
||||
/** Interpolation Catmull-Rom d'une polyligne. */
|
||||
TArray<FVector2f> SmoothPolyline(const TArray<FVector2f>& Points, int32 SubdivisionsPerSegment = 12);
|
||||
|
||||
void DrawBackground(FSlateWindowElementList& OutDrawElements, int32& LayerId, const FGeometry& Geometry,
|
||||
const FLinearColor& Color, ESlateDrawEffect DrawEffects);
|
||||
|
||||
/** Dessine le titre centre en haut et retourne le rect restant. */
|
||||
FSlateRect DrawTitle(FSlateWindowElementList& OutDrawElements, int32& LayerId, const FGeometry& Geometry,
|
||||
const FSlateRect& Rect, const FText& Title, const FLinearColor& Color, float FontSize, ESlateDrawEffect DrawEffects);
|
||||
|
||||
/** Dessine la legende et retourne le rect restant pour le contenu. */
|
||||
FSlateRect DrawLegend(FSlateWindowElementList& OutDrawElements, int32& LayerId, const FGeometry& Geometry,
|
||||
const FSlateRect& Rect, const TArray<FLegendEntry>& Entries, EPSChartLegendPosition Position,
|
||||
const FLinearColor& TextColor, float FontSize, ESlateDrawEffect DrawEffects);
|
||||
|
||||
/**
|
||||
* Calcule l'echelle, dessine grille + graduations + axes, et retourne la zone de trace.
|
||||
* BottomReserve : hauteur reservee sous la zone de trace (etiquettes de categories).
|
||||
*/
|
||||
FAxisLayout DrawValueAxis(FSlateWindowElementList& OutDrawElements, int32& LayerId, const FGeometry& Geometry,
|
||||
const FSlateRect& Rect, float BottomReserve, const FPSAxisParams& Axis, float DataMin, float DataMax,
|
||||
const FLinearColor& TextColor, float FontSize, ESlateDrawEffect DrawEffects);
|
||||
|
||||
/** Dessine les etiquettes de categories sous la zone de trace (saute des etiquettes si trop serrees). */
|
||||
void DrawCategoryLabels(FSlateWindowElementList& OutDrawElements, int32 LayerId, const FGeometry& Geometry,
|
||||
const FAxisLayout& Layout, const TArray<FString>& Labels, TFunctionRef<float(int32)> GetCenterX,
|
||||
float SlotWidth, const FLinearColor& TextColor, float FontSize, ESlateDrawEffect DrawEffects);
|
||||
|
||||
/** Dessine une chaine a une position locale donnee. */
|
||||
void DrawTextAt(FSlateWindowElementList& OutDrawElements, int32 LayerId, const FGeometry& Geometry,
|
||||
const FVector2f& Position, const FVector2f& Size, const FString& Text, const FSlateFontInfo& Font,
|
||||
const FLinearColor& Color, ESlateDrawEffect DrawEffects);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// PS_Charts - widget UMG courbes
|
||||
#include "PSLineChart.h"
|
||||
|
||||
#include "SPSLineChart.h"
|
||||
|
||||
UPSLineChart::UPSLineChart()
|
||||
{
|
||||
// Pour une courbe, l'echelle automatique colle aux donnees plutot qu'au zero
|
||||
bIncludeZero = false;
|
||||
|
||||
// Donnees d'exemple pour la previsualisation dans le designer UMG
|
||||
Categories = { TEXT("Jan"), TEXT("Feb"), TEXT("Mar"), TEXT("Apr"), TEXT("May"), TEXT("Jun") };
|
||||
Series.Emplace(TEXT("Serie 1"), TArray<float>{ 12.f, 25.f, 18.f, 30.f, 22.f, 27.f });
|
||||
Series.Emplace(TEXT("Serie 2"), TArray<float>{ 8.f, 15.f, 22.f, 18.f, 26.f, 20.f });
|
||||
}
|
||||
|
||||
void UPSLineChart::ReleaseSlateResources(bool bReleaseChildren)
|
||||
{
|
||||
Super::ReleaseSlateResources(bReleaseChildren);
|
||||
MyChart.Reset();
|
||||
}
|
||||
|
||||
TSharedRef<SWidget> UPSLineChart::RebuildWidget()
|
||||
{
|
||||
MyChart = SNew(SPSLineChart);
|
||||
ApplyToSlate();
|
||||
return MyChart.ToSharedRef();
|
||||
}
|
||||
|
||||
void UPSLineChart::ApplyToSlate()
|
||||
{
|
||||
if (!MyChart.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MyChart->SetCommonParams(MakeCommonParams());
|
||||
MyChart->SetAxisParams(MakeAxisParams());
|
||||
|
||||
FPSLineParams Params;
|
||||
Params.Thickness = Thickness;
|
||||
Params.bShowPoints = bShowPoints;
|
||||
Params.PointRadius = PointRadius;
|
||||
Params.bSmooth = bSmooth;
|
||||
Params.bShowValueLabels = bShowValueLabels;
|
||||
MyChart->SetLineParams(Params);
|
||||
|
||||
MyChart->SetData(Categories, Series);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// PS_Charts - widget UMG camembert
|
||||
#include "PSPieChart.h"
|
||||
|
||||
#include "SPSPieChart.h"
|
||||
|
||||
UPSPieChart::UPSPieChart()
|
||||
{
|
||||
// Donnees d'exemple pour la previsualisation dans le designer UMG
|
||||
Slices.Emplace(TEXT("Alpha"), 40.f);
|
||||
Slices.Emplace(TEXT("Beta"), 30.f);
|
||||
Slices.Emplace(TEXT("Gamma"), 20.f);
|
||||
Slices.Emplace(TEXT("Delta"), 10.f);
|
||||
}
|
||||
|
||||
void UPSPieChart::SetSlices(const TArray<FPSPieSlice>& InSlices)
|
||||
{
|
||||
Slices = InSlices;
|
||||
ApplyToSlate();
|
||||
}
|
||||
|
||||
void UPSPieChart::SetValues(const TMap<FString, float>& InValues)
|
||||
{
|
||||
Slices.Reset(InValues.Num());
|
||||
for (const TPair<FString, float>& Pair : InValues)
|
||||
{
|
||||
Slices.Emplace(Pair.Key, Pair.Value);
|
||||
}
|
||||
ApplyToSlate();
|
||||
}
|
||||
|
||||
void UPSPieChart::SetSliceValue(const FString& InLabel, float InValue)
|
||||
{
|
||||
FPSPieSlice* Found = Slices.FindByPredicate([&InLabel](const FPSPieSlice& Slice)
|
||||
{
|
||||
return Slice.Label == InLabel;
|
||||
});
|
||||
|
||||
if (Found)
|
||||
{
|
||||
Found->Value = InValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
Slices.Emplace(InLabel, InValue);
|
||||
}
|
||||
ApplyToSlate();
|
||||
}
|
||||
|
||||
void UPSPieChart::ClearData()
|
||||
{
|
||||
Slices.Reset();
|
||||
ApplyToSlate();
|
||||
}
|
||||
|
||||
void UPSPieChart::ReleaseSlateResources(bool bReleaseChildren)
|
||||
{
|
||||
Super::ReleaseSlateResources(bReleaseChildren);
|
||||
MyChart.Reset();
|
||||
}
|
||||
|
||||
TSharedRef<SWidget> UPSPieChart::RebuildWidget()
|
||||
{
|
||||
MyChart = SNew(SPSPieChart);
|
||||
ApplyToSlate();
|
||||
return MyChart.ToSharedRef();
|
||||
}
|
||||
|
||||
void UPSPieChart::ApplyToSlate()
|
||||
{
|
||||
if (!MyChart.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MyChart->SetCommonParams(MakeCommonParams());
|
||||
|
||||
FPSPieParams Params;
|
||||
Params.InnerRadiusRatio = InnerRadiusRatio;
|
||||
Params.StartAngleDegrees = StartAngleDegrees;
|
||||
Params.GapAngleDegrees = GapAngleDegrees;
|
||||
Params.LabelType = LabelType;
|
||||
Params.ValueSuffix = ValueSuffix;
|
||||
MyChart->SetPieParams(Params);
|
||||
|
||||
MyChart->SetSlices(Slices);
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
// PS_Charts - histogramme Slate natif
|
||||
#include "SPSBarChart.h"
|
||||
|
||||
#include "Fonts/FontMeasure.h"
|
||||
#include "Framework/Application/SlateApplication.h"
|
||||
#include "PSChartsPaintUtil.h"
|
||||
#include "Rendering/DrawElements.h"
|
||||
#include "Styling/CoreStyle.h"
|
||||
|
||||
using namespace PSChartsPaint;
|
||||
|
||||
void SPSBarChart::Construct(const FArguments& InArgs)
|
||||
{
|
||||
SetCanTick(false);
|
||||
SetClipping(EWidgetClipping::ClipToBounds);
|
||||
}
|
||||
|
||||
void SPSBarChart::SetData(const TArray<FString>& InCategories, const TArray<FPSChartSeries>& InSeries)
|
||||
{
|
||||
Categories = InCategories;
|
||||
Series = InSeries;
|
||||
Invalidate(EInvalidateWidgetReason::Paint);
|
||||
}
|
||||
|
||||
void SPSBarChart::SetCommonParams(const FPSChartCommonParams& InParams)
|
||||
{
|
||||
Common = InParams;
|
||||
Invalidate(EInvalidateWidgetReason::Paint);
|
||||
}
|
||||
|
||||
void SPSBarChart::SetAxisParams(const FPSAxisParams& InParams)
|
||||
{
|
||||
Axis = InParams;
|
||||
Invalidate(EInvalidateWidgetReason::Paint);
|
||||
}
|
||||
|
||||
void SPSBarChart::SetBarParams(const FPSBarParams& InParams)
|
||||
{
|
||||
Bar = InParams;
|
||||
Invalidate(EInvalidateWidgetReason::Paint);
|
||||
}
|
||||
|
||||
FVector2D SPSBarChart::ComputeDesiredSize(float) const
|
||||
{
|
||||
return FVector2D(400.0, 260.0);
|
||||
}
|
||||
|
||||
int32 SPSBarChart::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect,
|
||||
FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
|
||||
{
|
||||
const ESlateDrawEffect DrawEffects = (bParentEnabled && IsEnabled()) ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect;
|
||||
const FLinearColor WidgetTint = InWidgetStyle.GetColorAndOpacityTint();
|
||||
auto Tinted = [&WidgetTint](const FLinearColor& Color) { return Color * WidgetTint; };
|
||||
|
||||
const FVector2f LocalSize = FVector2f(AllottedGeometry.GetLocalSize());
|
||||
FSlateRect Rect(0.f, 0.f, LocalSize.X, LocalSize.Y);
|
||||
int32 Layer = LayerId;
|
||||
|
||||
DrawBackground(OutDrawElements, Layer, AllottedGeometry, Tinted(Common.BackgroundColor), DrawEffects);
|
||||
Rect = DrawTitle(OutDrawElements, Layer, AllottedGeometry, Rect, Common.Title, Tinted(Common.TitleColor), Common.TitleFontSize, DrawEffects);
|
||||
|
||||
// Legende (noms des series)
|
||||
if (Common.LegendPosition != EPSChartLegendPosition::None && Series.Num() > 0)
|
||||
{
|
||||
TArray<FLegendEntry> Entries;
|
||||
Entries.Reserve(Series.Num());
|
||||
for (int32 Index = 0; Index < Series.Num(); ++Index)
|
||||
{
|
||||
const FString Name = Series[Index].Name.IsEmpty() ? FString::Printf(TEXT("Serie %d"), Index + 1) : Series[Index].Name;
|
||||
Entries.Add({ Name, Tinted(PSCharts::ResolveColor(Index, Common.Palette, Series[Index].bUseCustomColor, Series[Index].CustomColor)) });
|
||||
}
|
||||
Rect = DrawLegend(OutDrawElements, Layer, AllottedGeometry, Rect, Entries, Common.LegendPosition, Tinted(Common.TextColor), Common.FontSize, DrawEffects);
|
||||
}
|
||||
|
||||
// Nombre de points et bornes des donnees
|
||||
int32 NumPoints = 0;
|
||||
float DataMin = TNumericLimits<float>::Max();
|
||||
float DataMax = TNumericLimits<float>::Lowest();
|
||||
for (const FPSChartSeries& OneSeries : Series)
|
||||
{
|
||||
NumPoints = FMath::Max(NumPoints, OneSeries.Values.Num());
|
||||
for (const float Value : OneSeries.Values)
|
||||
{
|
||||
DataMin = FMath::Min(DataMin, Value);
|
||||
DataMax = FMath::Max(DataMax, Value);
|
||||
}
|
||||
}
|
||||
if (NumPoints == 0)
|
||||
{
|
||||
return Layer;
|
||||
}
|
||||
if (DataMin > DataMax)
|
||||
{
|
||||
DataMin = 0.f;
|
||||
DataMax = 1.f;
|
||||
}
|
||||
|
||||
const FSlateFontInfo Font = GetFont(Common.FontSize);
|
||||
TSharedRef<FSlateFontMeasure> FontMeasure = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
|
||||
const float FontHeight = float(FontMeasure->GetMaxCharacterHeight(Font));
|
||||
|
||||
const FAxisLayout Layout = DrawValueAxis(OutDrawElements, Layer, AllottedGeometry, Rect, FontHeight + 8.f,
|
||||
Axis, DataMin, DataMax, Tinted(Common.TextColor), Common.FontSize, DrawEffects);
|
||||
if (!Layout.bValid)
|
||||
{
|
||||
return Layer;
|
||||
}
|
||||
|
||||
// Geometrie des barres
|
||||
const float PlotWidth = Layout.PlotRect.GetSize().X;
|
||||
const float SlotWidth = PlotWidth / NumPoints;
|
||||
const float InnerWidth = SlotWidth * (1.f - FMath::Clamp(Bar.CategoryGapRatio, 0.f, 0.9f));
|
||||
const int32 NumSeries = FMath::Max(1, Series.Num());
|
||||
const float BarSlotWidth = InnerWidth / NumSeries;
|
||||
const float BarGap = BarSlotWidth * FMath::Clamp(Bar.BarGapRatio, 0.f, 0.9f);
|
||||
const float BarWidth = FMath::Max(1.f, BarSlotWidth - BarGap);
|
||||
const float BaseValue = FMath::Clamp(0.f, Layout.Min, Layout.Max);
|
||||
const float BaseY = Layout.ValueToY(BaseValue);
|
||||
const FSlateBrush* WhiteBrush = GetWhiteBrush();
|
||||
|
||||
for (int32 SeriesIndex = 0; SeriesIndex < Series.Num(); ++SeriesIndex)
|
||||
{
|
||||
const FPSChartSeries& OneSeries = Series[SeriesIndex];
|
||||
const FLinearColor Color = Tinted(PSCharts::ResolveColor(SeriesIndex, Common.Palette, OneSeries.bUseCustomColor, OneSeries.CustomColor));
|
||||
|
||||
for (int32 PointIndex = 0; PointIndex < OneSeries.Values.Num() && PointIndex < NumPoints; ++PointIndex)
|
||||
{
|
||||
const float RawValue = OneSeries.Values[PointIndex];
|
||||
const float Value = FMath::Clamp(RawValue, Layout.Min, Layout.Max);
|
||||
const float ValueY = Layout.ValueToY(Value);
|
||||
const float Top = FMath::Min(BaseY, ValueY);
|
||||
const float Height = FMath::Abs(BaseY - ValueY);
|
||||
if (Height < KINDA_SMALL_NUMBER)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const float X = Layout.PlotRect.Left + PointIndex * SlotWidth + (SlotWidth - InnerWidth) * 0.5f
|
||||
+ SeriesIndex * BarSlotWidth + BarGap * 0.5f;
|
||||
|
||||
FSlateDrawElement::MakeBox(OutDrawElements, Layer,
|
||||
AllottedGeometry.ToPaintGeometry(FVector2f(BarWidth, Height), FSlateLayoutTransform(FVector2f(X, Top))),
|
||||
WhiteBrush, DrawEffects, Color);
|
||||
|
||||
if (Bar.bShowValueLabels)
|
||||
{
|
||||
const FString Label = FormatValue(RawValue) + Axis.ValueSuffix;
|
||||
const FVector2D TextSize = FontMeasure->Measure(Label, Font);
|
||||
const float LabelX = X + (BarWidth - float(TextSize.X)) * 0.5f;
|
||||
const float LabelY = (RawValue >= BaseValue) ? Top - FontHeight - 2.f : Top + Height + 2.f;
|
||||
DrawTextAt(OutDrawElements, Layer + 1, AllottedGeometry, FVector2f(LabelX, LabelY),
|
||||
FVector2f(TextSize), Label, Font, Tinted(Common.TextColor), DrawEffects);
|
||||
}
|
||||
}
|
||||
}
|
||||
Layer += Bar.bShowValueLabels ? 2 : 1;
|
||||
|
||||
// Etiquettes de categories
|
||||
TArray<FString> Labels;
|
||||
Labels.Reserve(NumPoints);
|
||||
for (int32 Index = 0; Index < NumPoints; ++Index)
|
||||
{
|
||||
Labels.Add(Categories.IsValidIndex(Index) ? Categories[Index] : FString::FromInt(Index + 1));
|
||||
}
|
||||
DrawCategoryLabels(OutDrawElements, Layer, AllottedGeometry, Layout, Labels,
|
||||
[&Layout, SlotWidth](int32 Index) { return Layout.PlotRect.Left + (Index + 0.5f) * SlotWidth; },
|
||||
SlotWidth, Tinted(Common.TextColor), Common.FontSize, DrawEffects);
|
||||
++Layer;
|
||||
|
||||
return Layer;
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
// PS_Charts - courbes Slate natives
|
||||
#include "SPSLineChart.h"
|
||||
|
||||
#include "Fonts/FontMeasure.h"
|
||||
#include "Framework/Application/SlateApplication.h"
|
||||
#include "PSChartsPaintUtil.h"
|
||||
#include "Rendering/DrawElements.h"
|
||||
#include "Styling/CoreStyle.h"
|
||||
|
||||
using namespace PSChartsPaint;
|
||||
|
||||
void SPSLineChart::Construct(const FArguments& InArgs)
|
||||
{
|
||||
SetCanTick(false);
|
||||
SetClipping(EWidgetClipping::ClipToBounds);
|
||||
}
|
||||
|
||||
void SPSLineChart::SetData(const TArray<FString>& InCategories, const TArray<FPSChartSeries>& InSeries)
|
||||
{
|
||||
Categories = InCategories;
|
||||
Series = InSeries;
|
||||
Invalidate(EInvalidateWidgetReason::Paint);
|
||||
}
|
||||
|
||||
void SPSLineChart::SetCommonParams(const FPSChartCommonParams& InParams)
|
||||
{
|
||||
Common = InParams;
|
||||
Invalidate(EInvalidateWidgetReason::Paint);
|
||||
}
|
||||
|
||||
void SPSLineChart::SetAxisParams(const FPSAxisParams& InParams)
|
||||
{
|
||||
Axis = InParams;
|
||||
Invalidate(EInvalidateWidgetReason::Paint);
|
||||
}
|
||||
|
||||
void SPSLineChart::SetLineParams(const FPSLineParams& InParams)
|
||||
{
|
||||
Line = InParams;
|
||||
Invalidate(EInvalidateWidgetReason::Paint);
|
||||
}
|
||||
|
||||
FVector2D SPSLineChart::ComputeDesiredSize(float) const
|
||||
{
|
||||
return FVector2D(400.0, 260.0);
|
||||
}
|
||||
|
||||
int32 SPSLineChart::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect,
|
||||
FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
|
||||
{
|
||||
const ESlateDrawEffect DrawEffects = (bParentEnabled && IsEnabled()) ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect;
|
||||
const FLinearColor WidgetTint = InWidgetStyle.GetColorAndOpacityTint();
|
||||
auto Tinted = [&WidgetTint](const FLinearColor& Color) { return Color * WidgetTint; };
|
||||
|
||||
const FVector2f LocalSize = FVector2f(AllottedGeometry.GetLocalSize());
|
||||
FSlateRect Rect(0.f, 0.f, LocalSize.X, LocalSize.Y);
|
||||
int32 Layer = LayerId;
|
||||
|
||||
DrawBackground(OutDrawElements, Layer, AllottedGeometry, Tinted(Common.BackgroundColor), DrawEffects);
|
||||
Rect = DrawTitle(OutDrawElements, Layer, AllottedGeometry, Rect, Common.Title, Tinted(Common.TitleColor), Common.TitleFontSize, DrawEffects);
|
||||
|
||||
// Legende (noms des series)
|
||||
if (Common.LegendPosition != EPSChartLegendPosition::None && Series.Num() > 0)
|
||||
{
|
||||
TArray<FLegendEntry> Entries;
|
||||
Entries.Reserve(Series.Num());
|
||||
for (int32 Index = 0; Index < Series.Num(); ++Index)
|
||||
{
|
||||
const FString Name = Series[Index].Name.IsEmpty() ? FString::Printf(TEXT("Serie %d"), Index + 1) : Series[Index].Name;
|
||||
Entries.Add({ Name, Tinted(PSCharts::ResolveColor(Index, Common.Palette, Series[Index].bUseCustomColor, Series[Index].CustomColor)) });
|
||||
}
|
||||
Rect = DrawLegend(OutDrawElements, Layer, AllottedGeometry, Rect, Entries, Common.LegendPosition, Tinted(Common.TextColor), Common.FontSize, DrawEffects);
|
||||
}
|
||||
|
||||
// Nombre de points et bornes des donnees
|
||||
int32 NumPoints = 0;
|
||||
float DataMin = TNumericLimits<float>::Max();
|
||||
float DataMax = TNumericLimits<float>::Lowest();
|
||||
for (const FPSChartSeries& OneSeries : Series)
|
||||
{
|
||||
NumPoints = FMath::Max(NumPoints, OneSeries.Values.Num());
|
||||
for (const float Value : OneSeries.Values)
|
||||
{
|
||||
DataMin = FMath::Min(DataMin, Value);
|
||||
DataMax = FMath::Max(DataMax, Value);
|
||||
}
|
||||
}
|
||||
if (NumPoints == 0)
|
||||
{
|
||||
return Layer;
|
||||
}
|
||||
if (DataMin > DataMax)
|
||||
{
|
||||
DataMin = 0.f;
|
||||
DataMax = 1.f;
|
||||
}
|
||||
|
||||
const FSlateFontInfo Font = GetFont(Common.FontSize);
|
||||
TSharedRef<FSlateFontMeasure> FontMeasure = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
|
||||
const float FontHeight = float(FontMeasure->GetMaxCharacterHeight(Font));
|
||||
|
||||
const FAxisLayout Layout = DrawValueAxis(OutDrawElements, Layer, AllottedGeometry, Rect, FontHeight + 8.f,
|
||||
Axis, DataMin, DataMax, Tinted(Common.TextColor), Common.FontSize, DrawEffects);
|
||||
if (!Layout.bValid)
|
||||
{
|
||||
return Layer;
|
||||
}
|
||||
|
||||
// Position X de chaque point (reparties bord a bord)
|
||||
const float PlotWidth = Layout.PlotRect.GetSize().X;
|
||||
auto PointX = [&Layout, PlotWidth, NumPoints](int32 Index) -> float
|
||||
{
|
||||
if (NumPoints <= 1)
|
||||
{
|
||||
return Layout.PlotRect.Left + PlotWidth * 0.5f;
|
||||
}
|
||||
return Layout.PlotRect.Left + PlotWidth * Index / (NumPoints - 1);
|
||||
};
|
||||
|
||||
// Courbes
|
||||
const FSlateBrush* WhiteBrush = GetWhiteBrush();
|
||||
const FSlateResourceHandle& ResourceHandle = WhiteBrush->GetRenderingResource();
|
||||
const FVector2f AtlasUV = GetAtlasUV(ResourceHandle);
|
||||
const FSlateRenderTransform& RenderTransform = AllottedGeometry.GetAccumulatedRenderTransform();
|
||||
|
||||
TArray<FSlateVertex> MarkerVerts;
|
||||
TArray<SlateIndex> MarkerIndices;
|
||||
|
||||
for (int32 SeriesIndex = 0; SeriesIndex < Series.Num(); ++SeriesIndex)
|
||||
{
|
||||
const FPSChartSeries& OneSeries = Series[SeriesIndex];
|
||||
if (OneSeries.Values.Num() == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const FLinearColor Color = Tinted(PSCharts::ResolveColor(SeriesIndex, Common.Palette, OneSeries.bUseCustomColor, OneSeries.CustomColor));
|
||||
|
||||
TArray<FVector2f> Points;
|
||||
Points.Reserve(OneSeries.Values.Num());
|
||||
for (int32 PointIndex = 0; PointIndex < OneSeries.Values.Num() && PointIndex < NumPoints; ++PointIndex)
|
||||
{
|
||||
Points.Add(FVector2f(PointX(PointIndex), Layout.ValueToY(OneSeries.Values[PointIndex])));
|
||||
}
|
||||
|
||||
if (Points.Num() >= 2)
|
||||
{
|
||||
TArray<FVector2f> LinePoints = Line.bSmooth ? SmoothPolyline(Points) : Points;
|
||||
FSlateDrawElement::MakeLines(OutDrawElements, Layer, AllottedGeometry.ToPaintGeometry(),
|
||||
MoveTemp(LinePoints), DrawEffects, Color, true, Line.Thickness);
|
||||
}
|
||||
|
||||
if (Line.bShowPoints || Points.Num() == 1)
|
||||
{
|
||||
const FColor MarkerColor = Color.ToFColorSRGB();
|
||||
for (const FVector2f& Point : Points)
|
||||
{
|
||||
AddCircle(MarkerVerts, MarkerIndices, RenderTransform, AtlasUV, Point, Line.PointRadius, MarkerColor);
|
||||
}
|
||||
}
|
||||
|
||||
if (Line.bShowValueLabels)
|
||||
{
|
||||
for (int32 PointIndex = 0; PointIndex < Points.Num(); ++PointIndex)
|
||||
{
|
||||
const FString Label = FormatValue(OneSeries.Values[PointIndex]) + Axis.ValueSuffix;
|
||||
const FVector2D TextSize = FontMeasure->Measure(Label, Font);
|
||||
DrawTextAt(OutDrawElements, Layer + 2, AllottedGeometry,
|
||||
FVector2f(Points[PointIndex].X - float(TextSize.X) * 0.5f, Points[PointIndex].Y - Line.PointRadius - 3.f - FontHeight),
|
||||
FVector2f(TextSize), Label, Font, Tinted(Common.TextColor), DrawEffects);
|
||||
}
|
||||
}
|
||||
}
|
||||
++Layer;
|
||||
|
||||
if (MarkerVerts.Num() > 0)
|
||||
{
|
||||
FSlateDrawElement::MakeCustomVerts(OutDrawElements, Layer, ResourceHandle, MarkerVerts, MarkerIndices, nullptr, 0, 0, DrawEffects);
|
||||
}
|
||||
Layer += Line.bShowValueLabels ? 2 : 1;
|
||||
|
||||
// Etiquettes de categories
|
||||
TArray<FString> Labels;
|
||||
Labels.Reserve(NumPoints);
|
||||
for (int32 Index = 0; Index < NumPoints; ++Index)
|
||||
{
|
||||
Labels.Add(Categories.IsValidIndex(Index) ? Categories[Index] : FString::FromInt(Index + 1));
|
||||
}
|
||||
const float SlotWidth = PlotWidth / FMath::Max(1, NumPoints - 1);
|
||||
DrawCategoryLabels(OutDrawElements, Layer, AllottedGeometry, Layout, Labels,
|
||||
PointX, SlotWidth, Tinted(Common.TextColor), Common.FontSize, DrawEffects);
|
||||
++Layer;
|
||||
|
||||
return Layer;
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
// PS_Charts - camembert Slate natif
|
||||
#include "SPSPieChart.h"
|
||||
|
||||
#include "Fonts/FontMeasure.h"
|
||||
#include "Framework/Application/SlateApplication.h"
|
||||
#include "PSChartsPaintUtil.h"
|
||||
#include "Rendering/DrawElements.h"
|
||||
#include "Styling/CoreStyle.h"
|
||||
|
||||
using namespace PSChartsPaint;
|
||||
|
||||
void SPSPieChart::Construct(const FArguments& InArgs)
|
||||
{
|
||||
SetCanTick(false);
|
||||
SetClipping(EWidgetClipping::ClipToBounds);
|
||||
}
|
||||
|
||||
void SPSPieChart::SetSlices(const TArray<FPSPieSlice>& InSlices)
|
||||
{
|
||||
Slices = InSlices;
|
||||
Invalidate(EInvalidateWidgetReason::Paint);
|
||||
}
|
||||
|
||||
void SPSPieChart::SetCommonParams(const FPSChartCommonParams& InParams)
|
||||
{
|
||||
Common = InParams;
|
||||
Invalidate(EInvalidateWidgetReason::Paint);
|
||||
}
|
||||
|
||||
void SPSPieChart::SetPieParams(const FPSPieParams& InParams)
|
||||
{
|
||||
Pie = InParams;
|
||||
Invalidate(EInvalidateWidgetReason::Paint);
|
||||
}
|
||||
|
||||
FVector2D SPSPieChart::ComputeDesiredSize(float) const
|
||||
{
|
||||
return FVector2D(256.0, 256.0);
|
||||
}
|
||||
|
||||
int32 SPSPieChart::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect,
|
||||
FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
|
||||
{
|
||||
const ESlateDrawEffect DrawEffects = (bParentEnabled && IsEnabled()) ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect;
|
||||
const FLinearColor WidgetTint = InWidgetStyle.GetColorAndOpacityTint();
|
||||
auto Tinted = [&WidgetTint](const FLinearColor& Color) { return Color * WidgetTint; };
|
||||
|
||||
const FVector2f LocalSize = FVector2f(AllottedGeometry.GetLocalSize());
|
||||
FSlateRect Rect(0.f, 0.f, LocalSize.X, LocalSize.Y);
|
||||
int32 Layer = LayerId;
|
||||
|
||||
DrawBackground(OutDrawElements, Layer, AllottedGeometry, Tinted(Common.BackgroundColor), DrawEffects);
|
||||
Rect = DrawTitle(OutDrawElements, Layer, AllottedGeometry, Rect, Common.Title, Tinted(Common.TitleColor), Common.TitleFontSize, DrawEffects);
|
||||
|
||||
// Total des valeurs positives
|
||||
float Total = 0.f;
|
||||
for (const FPSPieSlice& Slice : Slices)
|
||||
{
|
||||
Total += FMath::Max(0.f, Slice.Value);
|
||||
}
|
||||
|
||||
// Legende
|
||||
if (Common.LegendPosition != EPSChartLegendPosition::None && Slices.Num() > 0)
|
||||
{
|
||||
TArray<FLegendEntry> Entries;
|
||||
Entries.Reserve(Slices.Num());
|
||||
for (int32 Index = 0; Index < Slices.Num(); ++Index)
|
||||
{
|
||||
Entries.Add({ Slices[Index].Label, Tinted(PSCharts::ResolveColor(Index, Common.Palette, Slices[Index].bUseCustomColor, Slices[Index].CustomColor)) });
|
||||
}
|
||||
Rect = DrawLegend(OutDrawElements, Layer, AllottedGeometry, Rect, Entries, Common.LegendPosition, Tinted(Common.TextColor), Common.FontSize, DrawEffects);
|
||||
}
|
||||
|
||||
if (Total <= 0.f || Rect.GetSize().X < 16.f || Rect.GetSize().Y < 16.f)
|
||||
{
|
||||
return Layer;
|
||||
}
|
||||
|
||||
// Rayon : reserve la place des etiquettes autour du camembert
|
||||
const FSlateFontInfo Font = GetFont(Common.FontSize);
|
||||
TSharedRef<FSlateFontMeasure> FontMeasure = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
|
||||
const float FontHeight = float(FontMeasure->GetMaxCharacterHeight(Font));
|
||||
|
||||
auto MakeLabel = [this, Total](const FPSPieSlice& Slice) -> FString
|
||||
{
|
||||
const float Percent = (Total > 0.f) ? Slice.Value / Total * 100.f : 0.f;
|
||||
switch (Pie.LabelType)
|
||||
{
|
||||
case EPSPieLabelType::Name: return Slice.Label;
|
||||
case EPSPieLabelType::Value: return FormatValue(Slice.Value) + Pie.ValueSuffix;
|
||||
case EPSPieLabelType::Percent: return FString::Printf(TEXT("%.1f%%"), Percent);
|
||||
case EPSPieLabelType::NameValue: return FString::Printf(TEXT("%s : %s%s"), *Slice.Label, *FormatValue(Slice.Value), *Pie.ValueSuffix);
|
||||
case EPSPieLabelType::NamePercent: return FString::Printf(TEXT("%s : %.1f%%"), *Slice.Label, Percent);
|
||||
default: return FString();
|
||||
}
|
||||
};
|
||||
|
||||
float LabelReserveX = 0.f;
|
||||
float LabelReserveY = 0.f;
|
||||
if (Pie.LabelType != EPSPieLabelType::None)
|
||||
{
|
||||
float MaxLabelWidth = 0.f;
|
||||
for (const FPSPieSlice& Slice : Slices)
|
||||
{
|
||||
if (Slice.Value > 0.f)
|
||||
{
|
||||
MaxLabelWidth = FMath::Max(MaxLabelWidth, float(FontMeasure->Measure(MakeLabel(Slice), Font).X));
|
||||
}
|
||||
}
|
||||
LabelReserveX = MaxLabelWidth + 10.f;
|
||||
LabelReserveY = FontHeight + 10.f;
|
||||
}
|
||||
|
||||
float OuterRadius = 0.5f * FMath::Min(Rect.GetSize().X - 2.f * LabelReserveX, Rect.GetSize().Y - 2.f * LabelReserveY);
|
||||
// Si les etiquettes ne laissent plus de place, on retombe sur un rayon minimal lisible
|
||||
OuterRadius = FMath::Max(OuterRadius, 0.25f * FMath::Min(Rect.GetSize().X, Rect.GetSize().Y));
|
||||
const float InnerRadius = OuterRadius * FMath::Clamp(Pie.InnerRadiusRatio, 0.f, 0.95f);
|
||||
const FVector2f Center((Rect.Left + Rect.Right) * 0.5f, (Rect.Top + Rect.Bottom) * 0.5f);
|
||||
|
||||
// Parts
|
||||
const FSlateBrush* WhiteBrush = GetWhiteBrush();
|
||||
const FSlateResourceHandle& ResourceHandle = WhiteBrush->GetRenderingResource();
|
||||
const FVector2f AtlasUV = GetAtlasUV(ResourceHandle);
|
||||
const FSlateRenderTransform& RenderTransform = AllottedGeometry.GetAccumulatedRenderTransform();
|
||||
|
||||
TArray<FSlateVertex> Verts;
|
||||
TArray<SlateIndex> Indices;
|
||||
const float GapRad = FMath::DegreesToRadians(FMath::Max(0.f, Pie.GapAngleDegrees));
|
||||
float Angle = FMath::DegreesToRadians(Pie.StartAngleDegrees);
|
||||
|
||||
for (int32 Index = 0; Index < Slices.Num(); ++Index)
|
||||
{
|
||||
const FPSPieSlice& Slice = Slices[Index];
|
||||
if (Slice.Value <= 0.f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const float Sweep = Slice.Value / Total * 2.f * PI;
|
||||
const float EffectiveGap = FMath::Min(GapRad, Sweep * 0.5f);
|
||||
const float StartAngle = Angle + EffectiveGap * 0.5f;
|
||||
const float EndAngle = Angle + Sweep - EffectiveGap * 0.5f;
|
||||
Angle += Sweep;
|
||||
|
||||
if (EndAngle <= StartAngle)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const FLinearColor Color = Tinted(PSCharts::ResolveColor(Index, Common.Palette, Slice.bUseCustomColor, Slice.CustomColor));
|
||||
AddArcSector(Verts, Indices, RenderTransform, AtlasUV, Center, OuterRadius, InnerRadius, StartAngle, EndAngle, Color.ToFColorSRGB());
|
||||
}
|
||||
|
||||
if (Verts.Num() > 0)
|
||||
{
|
||||
FSlateDrawElement::MakeCustomVerts(OutDrawElements, Layer, ResourceHandle, Verts, Indices, nullptr, 0, 0, DrawEffects);
|
||||
++Layer;
|
||||
}
|
||||
|
||||
// Etiquettes
|
||||
if (Pie.LabelType != EPSPieLabelType::None)
|
||||
{
|
||||
Angle = FMath::DegreesToRadians(Pie.StartAngleDegrees);
|
||||
for (const FPSPieSlice& Slice : Slices)
|
||||
{
|
||||
if (Slice.Value <= 0.f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const float Sweep = Slice.Value / Total * 2.f * PI;
|
||||
const float MidAngle = Angle + Sweep * 0.5f;
|
||||
Angle += Sweep;
|
||||
|
||||
// Trop petit pour une etiquette lisible
|
||||
if (Slice.Value / Total < 0.01f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const FString Label = MakeLabel(Slice);
|
||||
const FVector2D TextSize = FontMeasure->Measure(Label, Font);
|
||||
const FVector2f Anchor = PolarPoint(Center, OuterRadius + 6.f, MidAngle);
|
||||
const float DirX = FMath::Sin(MidAngle);
|
||||
const float DirY = -FMath::Cos(MidAngle);
|
||||
const float X = Anchor.X - float(TextSize.X) * (1.f - DirX) * 0.5f;
|
||||
const float Y = Anchor.Y - float(TextSize.Y) * (1.f - DirY) * 0.5f;
|
||||
DrawTextAt(OutDrawElements, Layer, AllottedGeometry, FVector2f(X, Y), FVector2f(TextSize), Label, Font, Tinted(Common.TextColor), DrawEffects);
|
||||
}
|
||||
++Layer;
|
||||
}
|
||||
|
||||
return Layer;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// PS_Charts - widget UMG histogramme
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "PSCartesianChartBase.h"
|
||||
#include "PSBarChart.generated.h"
|
||||
|
||||
class SPSBarChart;
|
||||
|
||||
/** Histogramme natif (UMG) : barres verticales groupees par categorie. */
|
||||
UCLASS(meta = (DisplayName = "PS Bar Chart"))
|
||||
class PS_CHARTS_API UPSBarChart : public UPSCartesianChartBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPSBarChart();
|
||||
|
||||
/** Proportion vide entre les categories (0..0.9). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Bar", meta = (ClampMin = 0.0, ClampMax = 0.9))
|
||||
float CategoryGapRatio = 0.25f;
|
||||
|
||||
/** Ecart entre les barres d'une meme categorie (0..0.9). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Bar", meta = (ClampMin = 0.0, ClampMax = 0.9))
|
||||
float BarGapRatio = 0.1f;
|
||||
|
||||
/** Affiche la valeur au-dessus de chaque barre. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Bar")
|
||||
bool bShowValueLabels = false;
|
||||
|
||||
//~ UWidget
|
||||
virtual void ReleaseSlateResources(bool bReleaseChildren) override;
|
||||
|
||||
protected:
|
||||
virtual TSharedRef<SWidget> RebuildWidget() override;
|
||||
virtual void ApplyToSlate() override;
|
||||
|
||||
TSharedPtr<SPSBarChart> MyChart;
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
// PS_Charts - base commune histogramme / courbes (donnees + axe des valeurs)
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "PSChartWidgetBase.h"
|
||||
#include "PSCartesianChartBase.generated.h"
|
||||
|
||||
/** Base des graphiques a axes (histogramme, courbes) : categories, series et axe des valeurs. */
|
||||
UCLASS(Abstract)
|
||||
class PS_CHARTS_API UPSCartesianChartBase : public UPSChartWidgetBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Etiquettes de l'axe X (une par point ; complete par des numeros si insuffisant). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Data")
|
||||
TArray<FString> Categories;
|
||||
|
||||
/** Series de valeurs. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Data")
|
||||
TArray<FPSChartSeries> Series;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Axis")
|
||||
bool bShowGrid = true;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Axis")
|
||||
FLinearColor GridColor = FLinearColor(1.f, 1.f, 1.f, 0.12f);
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Axis")
|
||||
FLinearColor AxisColor = FLinearColor(1.f, 1.f, 1.f, 0.5f);
|
||||
|
||||
/** Echelle automatique de l'axe des valeurs. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Axis")
|
||||
bool bAutoRange = true;
|
||||
|
||||
/** Force l'inclusion du zero dans l'echelle automatique. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Axis", meta = (EditCondition = "bAutoRange"))
|
||||
bool bIncludeZero = true;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Axis", meta = (EditCondition = "!bAutoRange"))
|
||||
float AxisMinValue = 0.f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Axis", meta = (EditCondition = "!bAutoRange"))
|
||||
float AxisMaxValue = 100.f;
|
||||
|
||||
/** Unite ajoutee aux valeurs de l'axe (ex : " %"). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Axis")
|
||||
FString ValueSuffix;
|
||||
|
||||
/** Remplace les etiquettes de l'axe X. */
|
||||
UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_Charts|Data")
|
||||
void SetCategories(const TArray<FString>& InCategories);
|
||||
|
||||
/** Remplace toutes les series. */
|
||||
UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_Charts|Data")
|
||||
void SetSeries(const TArray<FPSChartSeries>& InSeries);
|
||||
|
||||
/** Remplace les donnees par une serie unique. */
|
||||
UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_Charts|Data")
|
||||
void SetSingleSeries(const TArray<float>& InValues, const FString& InName = TEXT(""));
|
||||
|
||||
/** Met a jour les valeurs de la serie nommee (la cree si absente). */
|
||||
UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_Charts|Data")
|
||||
void SetSeriesValues(const FString& InName, const TArray<float>& InValues);
|
||||
|
||||
/** Supprime toutes les donnees. */
|
||||
UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_Charts|Data")
|
||||
void ClearData();
|
||||
|
||||
protected:
|
||||
FPSAxisParams MakeAxisParams() const;
|
||||
};
|
||||
@@ -0,0 +1,145 @@
|
||||
// PS_Charts - types partages entre les widgets Slate et UMG
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "PSChartTypes.generated.h"
|
||||
|
||||
/** Position de la legende autour du graphique. */
|
||||
UENUM(BlueprintType)
|
||||
enum class EPSChartLegendPosition : uint8
|
||||
{
|
||||
None UMETA(DisplayName = "Masquee"),
|
||||
Top,
|
||||
Bottom,
|
||||
Left,
|
||||
Right
|
||||
};
|
||||
|
||||
/** Contenu des etiquettes affichees a cote des parts du camembert. */
|
||||
UENUM(BlueprintType)
|
||||
enum class EPSPieLabelType : uint8
|
||||
{
|
||||
None UMETA(DisplayName = "Masquees"),
|
||||
Name,
|
||||
Value,
|
||||
Percent,
|
||||
NameValue,
|
||||
NamePercent
|
||||
};
|
||||
|
||||
/** Une part de camembert. */
|
||||
USTRUCT(BlueprintType)
|
||||
struct FPSPieSlice
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart")
|
||||
FString Label;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart")
|
||||
float Value = 0.f;
|
||||
|
||||
/** Si false, la couleur est prise automatiquement dans la palette du graphique. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart")
|
||||
bool bUseCustomColor = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart", meta = (EditCondition = "bUseCustomColor"))
|
||||
FLinearColor CustomColor = FLinearColor::White;
|
||||
|
||||
FPSPieSlice() = default;
|
||||
FPSPieSlice(const FString& InLabel, float InValue)
|
||||
: Label(InLabel), Value(InValue)
|
||||
{}
|
||||
};
|
||||
|
||||
/** Une serie de valeurs (histogramme ou courbe). */
|
||||
USTRUCT(BlueprintType)
|
||||
struct FPSChartSeries
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart")
|
||||
FString Name;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart")
|
||||
TArray<float> Values;
|
||||
|
||||
/** Si false, la couleur est prise automatiquement dans la palette du graphique. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart")
|
||||
bool bUseCustomColor = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart", meta = (EditCondition = "bUseCustomColor"))
|
||||
FLinearColor CustomColor = FLinearColor::White;
|
||||
|
||||
FPSChartSeries() = default;
|
||||
FPSChartSeries(const FString& InName, const TArray<float>& InValues)
|
||||
: Name(InName), Values(InValues)
|
||||
{}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Parametres passes des widgets UMG aux widgets Slate (non exposes a l'UHT)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Parametres communs a tous les graphiques. */
|
||||
struct FPSChartCommonParams
|
||||
{
|
||||
FText Title;
|
||||
FLinearColor TitleColor = FLinearColor::White;
|
||||
float TitleFontSize = 16.f;
|
||||
FLinearColor TextColor = FLinearColor(0.9f, 0.9f, 0.9f);
|
||||
float FontSize = 11.f;
|
||||
TArray<FLinearColor> Palette;
|
||||
EPSChartLegendPosition LegendPosition = EPSChartLegendPosition::Bottom;
|
||||
FLinearColor BackgroundColor = FLinearColor::Transparent;
|
||||
};
|
||||
|
||||
/** Parametres specifiques au camembert. */
|
||||
struct FPSPieParams
|
||||
{
|
||||
float InnerRadiusRatio = 0.f; // 0 = camembert plein, >0 = donut
|
||||
float StartAngleDegrees = 0.f; // 0 = midi, sens horaire
|
||||
float GapAngleDegrees = 0.f; // ecart angulaire entre les parts
|
||||
EPSPieLabelType LabelType = EPSPieLabelType::NamePercent;
|
||||
FString ValueSuffix; // unite ajoutee aux valeurs (ex : " %")
|
||||
};
|
||||
|
||||
/** Parametres de l'axe des valeurs (histogramme / courbe). */
|
||||
struct FPSAxisParams
|
||||
{
|
||||
bool bShowGrid = true;
|
||||
FLinearColor GridColor = FLinearColor(1.f, 1.f, 1.f, 0.12f);
|
||||
FLinearColor AxisColor = FLinearColor(1.f, 1.f, 1.f, 0.5f);
|
||||
bool bAutoRange = true;
|
||||
bool bIncludeZero = true;
|
||||
float MinValue = 0.f;
|
||||
float MaxValue = 100.f;
|
||||
FString ValueSuffix; // unite ajoutee aux valeurs de l'axe
|
||||
};
|
||||
|
||||
/** Parametres specifiques a l'histogramme. */
|
||||
struct FPSBarParams
|
||||
{
|
||||
float CategoryGapRatio = 0.25f; // proportion vide entre les categories
|
||||
float BarGapRatio = 0.1f; // ecart entre les barres d'une meme categorie
|
||||
bool bShowValueLabels = false;
|
||||
};
|
||||
|
||||
/** Parametres specifiques aux courbes. */
|
||||
struct FPSLineParams
|
||||
{
|
||||
float Thickness = 2.f;
|
||||
bool bShowPoints = true;
|
||||
float PointRadius = 3.5f;
|
||||
bool bSmooth = false;
|
||||
bool bShowValueLabels = false;
|
||||
};
|
||||
|
||||
namespace PSCharts
|
||||
{
|
||||
/** Palette par defaut (10 couleurs). */
|
||||
PS_CHARTS_API const TArray<FLinearColor>& DefaultPalette();
|
||||
|
||||
/** Couleur effective d'une part / serie selon la palette. */
|
||||
PS_CHARTS_API FLinearColor ResolveColor(int32 Index, const TArray<FLinearColor>& Palette, bool bUseCustom, const FLinearColor& Custom);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// PS_Charts - base commune des widgets UMG
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Components/Widget.h"
|
||||
#include "PSChartTypes.h"
|
||||
#include "PSChartWidgetBase.generated.h"
|
||||
|
||||
/** Base commune des graphiques UMG : titre, palette, legende, couleurs de texte. */
|
||||
UCLASS(Abstract)
|
||||
class PS_CHARTS_API UPSChartWidgetBase : public UWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPSChartWidgetBase();
|
||||
|
||||
/** Titre affiche en haut du graphique (vide = pas de titre). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Style")
|
||||
FText Title;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Style")
|
||||
FLinearColor TitleColor = FLinearColor::White;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Style", meta = (ClampMin = 6, ClampMax = 64))
|
||||
float TitleFontSize = 16.f;
|
||||
|
||||
/** Couleur du texte (etiquettes, axes, legende). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Style")
|
||||
FLinearColor TextColor = FLinearColor(0.9f, 0.9f, 0.9f);
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Style", meta = (ClampMin = 6, ClampMax = 32))
|
||||
float FontSize = 11.f;
|
||||
|
||||
/** Couleurs attribuees automatiquement aux parts / series. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Style")
|
||||
TArray<FLinearColor> Palette;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Style")
|
||||
EPSChartLegendPosition LegendPosition = EPSChartLegendPosition::Bottom;
|
||||
|
||||
/** Couleur de fond (transparent par defaut). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Style")
|
||||
FLinearColor BackgroundColor = FLinearColor::Transparent;
|
||||
|
||||
/** Change le titre du graphique. */
|
||||
UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_Charts|Style")
|
||||
void SetChartTitle(FText InTitle);
|
||||
|
||||
/** Repousse toutes les proprietes vers le rendu (a appeler apres modification directe des proprietes en Blueprint). */
|
||||
UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_Charts|Style")
|
||||
void Refresh();
|
||||
|
||||
//~ UWidget
|
||||
virtual void SynchronizeProperties() override;
|
||||
#if WITH_EDITOR
|
||||
virtual const FText GetPaletteCategory() override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
/** Pousse toutes les proprietes vers le widget Slate. */
|
||||
virtual void ApplyToSlate() {}
|
||||
|
||||
FPSChartCommonParams MakeCommonParams() const;
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
// PS_Charts - widget UMG courbes
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "PSCartesianChartBase.h"
|
||||
#include "PSLineChart.generated.h"
|
||||
|
||||
class SPSLineChart;
|
||||
|
||||
/** Graphique en courbes natif (UMG), multi-series. */
|
||||
UCLASS(meta = (DisplayName = "PS Line Chart"))
|
||||
class PS_CHARTS_API UPSLineChart : public UPSCartesianChartBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPSLineChart();
|
||||
|
||||
/** Epaisseur des courbes. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Line", meta = (ClampMin = 0.5, ClampMax = 16))
|
||||
float Thickness = 2.f;
|
||||
|
||||
/** Affiche un point a chaque valeur. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Line")
|
||||
bool bShowPoints = true;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Line", meta = (ClampMin = 1, ClampMax = 16, EditCondition = "bShowPoints"))
|
||||
float PointRadius = 3.5f;
|
||||
|
||||
/** Lisse les courbes (interpolation Catmull-Rom). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Line")
|
||||
bool bSmooth = false;
|
||||
|
||||
/** Affiche la valeur au-dessus de chaque point. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Line")
|
||||
bool bShowValueLabels = false;
|
||||
|
||||
//~ UWidget
|
||||
virtual void ReleaseSlateResources(bool bReleaseChildren) override;
|
||||
|
||||
protected:
|
||||
virtual TSharedRef<SWidget> RebuildWidget() override;
|
||||
virtual void ApplyToSlate() override;
|
||||
|
||||
TSharedPtr<SPSLineChart> MyChart;
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
// PS_Charts - widget UMG camembert
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "PSChartWidgetBase.h"
|
||||
#include "PSPieChart.generated.h"
|
||||
|
||||
class SPSPieChart;
|
||||
|
||||
/** Camembert / donut natif (UMG). */
|
||||
UCLASS(meta = (DisplayName = "PS Pie Chart"))
|
||||
class PS_CHARTS_API UPSPieChart : public UPSChartWidgetBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPSPieChart();
|
||||
|
||||
/** Parts du camembert. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Data")
|
||||
TArray<FPSPieSlice> Slices;
|
||||
|
||||
/** 0 = camembert plein, >0 = donut. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Pie", meta = (ClampMin = 0.0, ClampMax = 0.9))
|
||||
float InnerRadiusRatio = 0.f;
|
||||
|
||||
/** Angle de depart en degres (0 = midi, sens horaire). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Pie")
|
||||
float StartAngleDegrees = 0.f;
|
||||
|
||||
/** Ecart angulaire entre les parts, en degres. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Pie", meta = (ClampMin = 0, ClampMax = 20))
|
||||
float GapAngleDegrees = 0.f;
|
||||
|
||||
/** Contenu des etiquettes autour du camembert. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Pie")
|
||||
EPSPieLabelType LabelType = EPSPieLabelType::NamePercent;
|
||||
|
||||
/** Unite ajoutee aux valeurs (ex : " %"). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chart|Pie")
|
||||
FString ValueSuffix;
|
||||
|
||||
/** Remplace toutes les parts. */
|
||||
UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_Charts|Data")
|
||||
void SetSlices(const TArray<FPSPieSlice>& InSlices);
|
||||
|
||||
/** Remplace les parts a partir d'une map libelle -> valeur (couleurs automatiques). */
|
||||
UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_Charts|Data")
|
||||
void SetValues(const TMap<FString, float>& InValues);
|
||||
|
||||
/** Met a jour la valeur d'une part (la cree si absente). */
|
||||
UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_Charts|Data")
|
||||
void SetSliceValue(const FString& InLabel, float InValue);
|
||||
|
||||
/** Supprime toutes les parts. */
|
||||
UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_Charts|Data")
|
||||
void ClearData();
|
||||
|
||||
//~ UWidget
|
||||
virtual void ReleaseSlateResources(bool bReleaseChildren) override;
|
||||
|
||||
protected:
|
||||
virtual TSharedRef<SWidget> RebuildWidget() override;
|
||||
virtual void ApplyToSlate() override;
|
||||
|
||||
TSharedPtr<SPSPieChart> MyChart;
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
// PS_Charts - histogramme Slate natif
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Widgets/SLeafWidget.h"
|
||||
#include "PSChartTypes.h"
|
||||
|
||||
/** Histogramme (barres verticales groupees) dessine entierement en Slate. */
|
||||
class PS_CHARTS_API SPSBarChart : public SLeafWidget
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SPSBarChart) {}
|
||||
SLATE_END_ARGS()
|
||||
|
||||
void Construct(const FArguments& InArgs);
|
||||
|
||||
void SetData(const TArray<FString>& InCategories, const TArray<FPSChartSeries>& InSeries);
|
||||
void SetCommonParams(const FPSChartCommonParams& InParams);
|
||||
void SetAxisParams(const FPSAxisParams& InParams);
|
||||
void SetBarParams(const FPSBarParams& InParams);
|
||||
|
||||
//~ SWidget
|
||||
virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect,
|
||||
FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override;
|
||||
virtual FVector2D ComputeDesiredSize(float LayoutScaleMultiplier) const override;
|
||||
|
||||
private:
|
||||
TArray<FString> Categories;
|
||||
TArray<FPSChartSeries> Series;
|
||||
FPSChartCommonParams Common;
|
||||
FPSAxisParams Axis;
|
||||
FPSBarParams Bar;
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
// PS_Charts - courbes Slate natives
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Widgets/SLeafWidget.h"
|
||||
#include "PSChartTypes.h"
|
||||
|
||||
/** Graphique en courbes (multi-series) dessine entierement en Slate. */
|
||||
class PS_CHARTS_API SPSLineChart : public SLeafWidget
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SPSLineChart) {}
|
||||
SLATE_END_ARGS()
|
||||
|
||||
void Construct(const FArguments& InArgs);
|
||||
|
||||
void SetData(const TArray<FString>& InCategories, const TArray<FPSChartSeries>& InSeries);
|
||||
void SetCommonParams(const FPSChartCommonParams& InParams);
|
||||
void SetAxisParams(const FPSAxisParams& InParams);
|
||||
void SetLineParams(const FPSLineParams& InParams);
|
||||
|
||||
//~ SWidget
|
||||
virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect,
|
||||
FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override;
|
||||
virtual FVector2D ComputeDesiredSize(float LayoutScaleMultiplier) const override;
|
||||
|
||||
private:
|
||||
TArray<FString> Categories;
|
||||
TArray<FPSChartSeries> Series;
|
||||
FPSChartCommonParams Common;
|
||||
FPSAxisParams Axis;
|
||||
FPSLineParams Line;
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
// PS_Charts - camembert Slate natif
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Widgets/SLeafWidget.h"
|
||||
#include "PSChartTypes.h"
|
||||
|
||||
/** Camembert / donut dessine entierement en Slate (aucune texture ni navigateur). */
|
||||
class PS_CHARTS_API SPSPieChart : public SLeafWidget
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SPSPieChart) {}
|
||||
SLATE_END_ARGS()
|
||||
|
||||
void Construct(const FArguments& InArgs);
|
||||
|
||||
void SetSlices(const TArray<FPSPieSlice>& InSlices);
|
||||
void SetCommonParams(const FPSChartCommonParams& InParams);
|
||||
void SetPieParams(const FPSPieParams& InParams);
|
||||
|
||||
//~ SWidget
|
||||
virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect,
|
||||
FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override;
|
||||
virtual FVector2D ComputeDesiredSize(float LayoutScaleMultiplier) const override;
|
||||
|
||||
private:
|
||||
TArray<FPSPieSlice> Slices;
|
||||
FPSChartCommonParams Common;
|
||||
FPSPieParams Pie;
|
||||
};
|
||||
Reference in New Issue
Block a user