initial Commit

This commit is contained in:
2026-07-22 12:43:08 +02:00
commit 6adb8566a7
36 changed files with 5848 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
Unreal/PS_Chart/Binaries/
Unreal/PS_Chart/DerivedDataCache/
Unreal/PS_Chart/Intermediate/
Unreal/PS_Chart/Plugins/PS_Charts/Binaries/
Unreal/PS_Chart/Plugins/PS_Charts/Intermediate/
Unreal/PS_Chart/Saved/
Unreal/PS_Chart/.vsconfig
Unreal/PS_Chart/.vs/

View File

View File

@@ -0,0 +1,102 @@
[/Script/EngineSettings.GameMapsSettings]
GameDefaultMap=/Engine/Maps/Templates/OpenWorld
[/Script/Engine.RendererSettings]
r.AllowStaticLighting=False
r.GenerateMeshDistanceFields=True
r.DynamicGlobalIlluminationMethod=1
r.ReflectionMethod=1
r.SkinCache.CompileShaders=True
r.RayTracing=True
r.RayTracing.RayTracingProxies.ProjectEnabled=True
r.Substrate=True
r.Substrate.ProjectGBufferFormat=0
r.Shadow.Virtual.Enable=1
r.DefaultFeature.AutoExposure.ExtendDefaultLuminanceRange=True
r.DefaultFeature.LocalExposure.HighlightContrastScale=0.8
r.DefaultFeature.LocalExposure.ShadowContrastScale=0.8
[/Script/WindowsTargetPlatform.WindowsTargetSettings]
DefaultGraphicsRHI=DefaultGraphicsRHI_DX12
DefaultGraphicsRHI=DefaultGraphicsRHI_DX12
-D3D12TargetedShaderFormats=PCD3D_SM5
+D3D12TargetedShaderFormats=PCD3D_SM6
-D3D11TargetedShaderFormats=PCD3D_SM5
+D3D11TargetedShaderFormats=PCD3D_SM5
Compiler=Default
AudioSampleRate=48000
AudioCallbackBufferFrameSize=1024
AudioNumBuffersToEnqueue=1
AudioMaxChannels=0
AudioNumSourceWorkers=4
SpatializationPlugin=
SourceDataOverridePlugin=
ReverbPlugin=
OcclusionPlugin=
CompressionOverrides=(bOverrideCompressionTimes=False,DurationThreshold=5.000000,MaxNumRandomBranches=0,SoundCueQualityIndex=0)
CacheSizeKB=65536
MaxChunkSizeOverrideKB=0
bResampleForDevice=False
MaxSampleRate=48000.000000
HighSampleRate=32000.000000
MedSampleRate=24000.000000
LowSampleRate=12000.000000
MinSampleRate=8000.000000
CompressionQualityModifier=1.000000
AutoStreamingThreshold=0.000000
SoundCueCookQualityIndex=-1
[/Script/LinuxTargetPlatform.LinuxTargetSettings]
-TargetedRHIs=SF_VULKAN_SM5
+TargetedRHIs=SF_VULKAN_SM6
[/Script/MacTargetPlatform.MacTargetSettings]
-TargetedRHIs=SF_METAL_SM5
+TargetedRHIs=SF_METAL_SM6
[/Script/HardwareTargeting.HardwareTargetingSettings]
TargetedHardwareClass=Desktop
AppliedTargetedHardwareClass=Desktop
DefaultGraphicsPerformance=Maximum
AppliedDefaultGraphicsPerformance=Maximum
[/Script/WorldPartitionEditor.WorldPartitionEditorSettings]
CommandletClass=Class'/Script/UnrealEd.WorldPartitionConvertCommandlet'
[/Script/Engine.UserInterfaceSettings]
bAuthorizeAutomaticWidgetVariableCreation=False
FontDPIPreset=Standard
FontDPI=72
[/Script/Engine.Engine]
+ActiveGameNameRedirects=(OldGameName="TP_Blank",NewGameName="/Script/PS_Chart")
+ActiveGameNameRedirects=(OldGameName="/Script/TP_Blank",NewGameName="/Script/PS_Chart")
[/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings]
bEnablePlugin=True
bAllowNetworkConnection=True
SecurityToken=B8E5DD624BE974F0AB52AB843A534888
bIncludeInShipping=False
bAllowExternalStartInShipping=False
bCompileAFSProject=False
bUseCompression=False
bLogFiles=False
bReportStats=False
ConnectionType=USBOnly
bUseManualIPAddress=False
ManualIPAddress=

View File

@@ -0,0 +1,6 @@
[/Script/CommonUI.CommonUISettings]
CommonButtonAcceptKeyHandling=TriggerClick
[/Script/EngineSettings.GeneralProjectSettings]
ProjectID=100A1AE54BE88648E293EDA16702B4BF

View File

@@ -0,0 +1,84 @@
[/Script/Engine.InputSettings]
-AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f))
-AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f))
-AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f))
-AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f))
-AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f))
-AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f))
-AxisConfig=(AxisKeyName="Mouse2D",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f))
+AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="Mouse2D",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="MouseWheelAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="Gamepad_LeftTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="Gamepad_RightTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="Gamepad_Special_Left_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="Gamepad_Special_Left_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="Vive_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="Vive_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="Vive_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="Vive_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="Vive_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="Vive_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="MixedReality_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="MixedReality_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="MixedReality_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="MixedReality_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="MixedReality_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="MixedReality_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="MixedReality_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="MixedReality_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="MixedReality_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="MixedReality_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="OculusTouch_Left_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="OculusTouch_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="OculusTouch_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="OculusTouch_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="OculusTouch_Right_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="OculusTouch_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Left_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Left_Grip_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Right_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Right_Grip_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
bAltEnterTogglesFullscreen=True
bF11TogglesFullscreen=True
bUseMouseForTouch=False
bEnableMouseSmoothing=True
bEnableFOVScaling=True
bCaptureMouseOnLaunch=True
bEnableLegacyInputScales=True
bEnableMotionControls=True
bFilterInputByPlatformUser=False
bShouldFlushPressedKeysOnViewportFocusLost=True
bAlwaysShowTouchInterface=False
bShowConsoleOnFourFingerTap=True
bEnableGestureRecognizer=False
bUseAutocorrect=False
DefaultViewportMouseCaptureMode=CapturePermanently_IncludingInitialMouseDown
DefaultViewportMouseLockMode=LockOnCapture
FOVScale=0.011110
DoubleClickTime=0.200000
DefaultPlayerInputClass=/Script/EnhancedInput.EnhancedPlayerInput
DefaultInputComponentClass=/Script/EnhancedInput.EnhancedInputComponent
DefaultTouchInterface=/Engine/MobileResources/HUD/DefaultVirtualJoysticks.DefaultVirtualJoysticks
-ConsoleKeys=Tilde
+ConsoleKeys=Tilde

3440
Unreal/PS_Chart/PS_Chart.sln Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
{
"FileVersion": 3,
"EngineAssociation": "5.7",
"Category": "",
"Description": "",
"Modules": [
{
"Name": "PS_Chart",
"Type": "Runtime",
"LoadingPhase": "Default"
}
],
"Plugins": [
{
"Name": "ModelingToolsEditorMode",
"Enabled": true,
"TargetAllowList": [
"Editor"
]
},
{
"Name": "PS_Charts",
"Enabled": true
}
]
}

View 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"
}
]
}

View 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) |

View File

@@ -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"
});
}
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,8 @@
// PS_Charts - module runtime
#include "Modules/ModuleManager.h"
class FPS_ChartsModule : public IModuleInterface
{
};
IMPLEMENT_MODULE(FPS_ChartsModule, PS_Charts)

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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);
}

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -0,0 +1,15 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
using System.Collections.Generic;
public class PS_ChartTarget : TargetRules
{
public PS_ChartTarget(TargetInfo Target) : base(Target)
{
Type = TargetType.Game;
DefaultBuildSettings = BuildSettingsVersion.V6;
IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_7;
ExtraModuleNames.Add("PS_Chart");
}
}

View File

@@ -0,0 +1,23 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class PS_Chart : ModuleRules
{
public PS_Chart(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput" });
PrivateDependencyModuleNames.AddRange(new string[] { });
// Uncomment if you are using Slate UI
// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
// Uncomment if you are using online features
// PrivateDependencyModuleNames.Add("OnlineSubsystem");
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
}
}

View File

@@ -0,0 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "PS_Chart.h"
#include "Modules/ModuleManager.h"
IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, PS_Chart, "PS_Chart" );

View File

@@ -0,0 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"

View File

@@ -0,0 +1,15 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
using System.Collections.Generic;
public class PS_ChartEditorTarget : TargetRules
{
public PS_ChartEditorTarget( TargetInfo Target) : base(Target)
{
Type = TargetType.Editor;
DefaultBuildSettings = BuildSettingsVersion.V6;
IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_7;
ExtraModuleNames.Add("PS_Chart");
}
}