UE5 实现UMG柱状图控件

使用UE5原生方式实现常用图表控件之一——柱状图,可用于蓝图调用。

实现效果如下:

实现思路:

1、使用使用四个顶点组成2个三角形填充区域进行柱状绘制

2、根据X轴的类别个数分别计算出每个类别的宽度及每个柱状允许的宽度并取合适值

3、Y轴坐标与屏幕坐标值的转换

源码:

BarChartWidget.h

#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "BarChartWidget.generated.h"


/**
 *
 */
UCLASS()
class UICHARTS2D_API UBarChartWidget : public UUserWidget
{
	GENERATED_BODY()

protected:
	virtual int32 NativePaint(const FPaintArgs& Args, const FGeometry& Geometry, const FSlateRect& Rect, FSlateWindowElementList& DrawElements,
		int32 LayerId, const FWidgetStyle& Style, bool ParentEnable) const override;

	virtual FReply NativeOnMouseMove(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;

	virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override;

	UPROPERTY(BlueprintReadOnly)
		float Min = 0;

	UPROPERTY(BlueprintReadOnly)
		float Max = 1;

	/*每条Bar的宽度*/
	float BarWidth = 2;
	/*每个Label的Bar宽度*/
	float BarItemSpace = 2;

	TArray<TArray<float>> ValuesArray;


	float CurrentValue = 0.0f;

	int32 WhichIdx = -1;

public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
		FSlateFontInfo Font;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
		FVector2D AreaSize = FVector2D(240, 200);
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
		FVector2D LocationOffset = FVector2D(40, 30);

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
		TArray<FString> CategoryNames;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
		TArray<FString> LabelNames;



	UPROPERTY(EditAnywhere, BlueprintReadWrite)
		TArray<FColor> Colors;

	UFUNCTION(BlueprintCallable)
		virtual void ClearCategories();

	UFUNCTION(BlueprintCallable)
		virtual void ConfigXAxisLabels(const TArray<FString>& InLabels);

	UFUNCTION(BlueprintCallable)
		virtual void ConfigCategoryValues(const FString& InCategoryName, const TArray<float>& InValues);





};

BarChartWidget.cpp


#include "BarChartWidget.h"

int32 UBarChartWidget::NativePaint(const FPaintArgs& Args, const FGeometry& Geometry, const FSlateRect& Rect, FSlateWindowElementList& DrawElements, int32 LayerId, const FWidgetStyle& Style, bool ParentEnable) const
{
	for (int idx = 0; idx < ValuesArray.Num(); idx++)
	{
		TArray<FSlateVertex> VertexArray;
		TArray<SlateIndex> VertexIndex;

		FSlateVertex LeftBottomVertex;
		FSlateVertex LeftTopVertex;
		FSlateVertex RightBottomVertex;
		FSlateVertex RightTopVertex;

		FVector2D LeftBottomPosition;
		FVector2D LeftTopPosition;
		FVector2D RightBottomPosition;
		FVector2D RightTopPosition;

		FColor color = Colors.IsValidIndex(idx) ? Colors[idx] : FColor::White;

		LeftBottomVertex.Color = color;
		LeftTopVertex.Color = color;
		RightBottomVertex.Color = color;
		RightTopVertex.Color = color;

		int32 Start = 0;
		int32 End = AreaSize.X;
		int32 Width = FMath::RoundToInt32(BarWidth);
		float Range = Max - Min;

		TArray<FVector3d> TextPosition;

		for (int32 X = 0; X < ValuesArray[idx].Num(); X++)
		{
			bool WriteText = true;
			float value = ValuesArray[idx][X];
			if (value > CurrentValue && CurrentValue < Max)
			{
				value = CurrentValue;
				WriteText = false;
			}
			if (value < Min)
			{
				value = Min;
			}

			float Y = AreaSize.Y * (1.f - value / Range);
			if (FMath::IsNearlyEqual(Y, AreaSize.Y))
			{
				Y = AreaSize.Y - 1;
			}



			LeftBottomPosition = FVector2D(
				LocationOffset.X + Width * idx + BarItemSpace * X + Width / 2,
				LocationOffset.Y + AreaSize.Y);
			LeftTopPosition = FVector2D(
				LocationOffset.X + Width * idx + BarItemSpace * X + Width / 2,
				LocationOffset.Y + Y);

			RightBottomPosition = FVector2D(
				LocationOffset.X + Width * idx + BarItemSpace * X + Width + Width / 2,
				LocationOffset.Y + AreaSize.Y);
			RightTopPosition = FVector2D(
				LocationOffset.X + Width * idx + BarItemSpace * X + Width + Width / 2,
				LocationOffset.Y + Y);

			if (WriteText && WhichIdx == X)
			{
				TextPosition.Add(FVector3d(X, LeftTopPosition.X, LeftTopPosition.Y - 12));
			}


			const FSlateRenderTransform& SlateRenderTransform = Geometry.ToPaintGeometry().GetAccumulatedRenderTransform();
			LeftBottomPosition = SlateRenderTransform.TransformPoint(LeftBottomPosition);
			LeftTopPosition = SlateRenderTransform.TransformPoint(LeftTopPosition);
			RightBottomPosition = SlateRenderTransform.TransformPoint(RightBottomPosition);
			RightTopPosition = SlateRenderTransform.TransformPoint(RightTopPosition);


			LeftBottomVertex.Position = FVector2f(LeftBottomPosition.X, LeftBottomPosition.Y);
			LeftTopVertex.Position = FVector2f(LeftTopPosition.X, LeftTopPosition.Y);
			RightBottomVertex.Position = FVector2f(RightBottomPosition.X, RightBottomPosition.Y);
			RightTopVertex.Position = FVector2f(RightTopPosition.X, RightTopPosition.Y);

			int32 LBVertexIndex = VertexArray.Add(LeftBottomVertex);
			int32 LTdVertexIndex = VertexArray.Add(LeftTopVertex);
			int32 RBVertexIndex = VertexArray.Add(RightBottomVertex);
			int32 RTVertexIndex = VertexArray.Add(RightTopVertex);

			VertexIndex.Add(LBVertexIndex);
			VertexIndex.Add(LTdVertexIndex);
			VertexIndex.Add(RBVertexIndex);

			VertexIndex.Add(RBVertexIndex);
			VertexIndex.Add(RTVertexIndex);
			VertexIndex.Add(LTdVertexIndex);
		}

		for (FSlateVertex& SlateVertex : VertexArray)
		{
			SlateVertex.TexCoords[0] = 0.f;
			SlateVertex.TexCoords[1] = 0.f;
		}

		const FSlateBrush* Brush = FCoreStyle::Get().GetBrush(TEXT("PIECHART"));
		const FSlateResourceHandle Handle = FSlateApplication::Get().GetRenderer()->GetResourceHandle(*Brush);
		FSlateDrawElement::MakeCustomVerts(DrawElements, LayerId, Handle, VertexArray, VertexIndex, nullptr, 0, 0);

		TArray<FVector2D> Points;
		Points.Add(FVector2D(LocationOffset.X, AreaSize.Y + LocationOffset.Y));
		Points.Add(FVector2D(AreaSize.X + LocationOffset.X, AreaSize.Y + LocationOffset.Y));

		FSlateDrawElement::MakeLines(DrawElements, LayerId, Geometry.ToPaintGeometry(), Points, ESlateDrawEffect::None,
			FColor(255, 255, 255, 33),
			true,
			1);

		for (int Pos = 0; Pos < TextPosition.Num(); Pos++)
		{
			auto position = TextPosition[Pos];
			int32 x = FMath::CeilToInt32(position.X);

			FVector2D TxtPos = FVector2D(position.Y, position.Z);
			auto TextGeometry = Geometry.MakeChild(FVector2D(60, 12), FSlateLayoutTransform(TxtPos));

			FString ValueLabel = FString::Printf(TEXT("%d"), FMath::CeilToInt32(ValuesArray[idx][x]));
			FSlateDrawElement::MakeText(DrawElements, LayerId, TextGeometry.ToPaintGeometry(), FText::FromString(ValueLabel), Font);
		}

	}


	return LayerId++;
}


void UBarChartWidget::NativeTick(const FGeometry& MyGeometry, float InDeltaTime)
{
	float Delta = InDeltaTime * 1000;

	if (CurrentValue <= Max)
	{
		CurrentValue += Delta;
	}
	else
	{
		CurrentValue = Max;
	}

	Super::NativeTick(MyGeometry, InDeltaTime);
}

FReply UBarChartWidget::NativeOnMouseMove(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
	auto HoverPostion = InGeometry.AbsoluteToLocal(InMouseEvent.GetScreenSpacePosition());

	if (HoverPostion.X < LocationOffset.X || HoverPostion.Y < LocationOffset.Y)
	{
		WhichIdx = -1;

	}
	else if (HoverPostion.Y > (LocationOffset.Y + AreaSize.Y))
	{
		WhichIdx = -1;
	}
	else
	{
		WhichIdx = FMath::TruncToInt32((HoverPostion.X - LocationOffset.X) / BarItemSpace);
	}
	return Super::NativeOnMouseMove(InGeometry, InMouseEvent);
}

void UBarChartWidget::ClearCategories()
{
	CategoryNames.Empty();
	for (int32 Index = 0; Index < ValuesArray.Num(); Index++)
	{
		ValuesArray[Index].Empty();
	}
	ValuesArray.Empty();
	BarWidth = 2;
	BarItemSpace = 4;
	Min = 0;
	Max = 1;
}

void UBarChartWidget::ConfigXAxisLabels(const TArray<FString>& InLabels)
{
	LabelNames = InLabels;
}

void UBarChartWidget::ConfigCategoryValues(const FString& InCategoryName, const TArray<float>& InValues)
{
	CategoryNames.Add(InCategoryName);
	ValuesArray.Add(InValues);
	for (int32 Index = 0; Index < InValues.Num(); Index++)
	{
		float value = InValues[Index];
		Min = FMath::Min(Min, value);
		Max = FMath::Max(Max, value);
	}

	Min = FMath::FloorToInt32(Min);
	Max = FMath::RoundToInt32(Max);

	if (LabelNames.Num() > 0)
	{
		BarItemSpace = AreaSize.X / (LabelNames.Num());
		BarWidth = BarItemSpace / (CategoryNames.Num() + 1);
	}
	CurrentValue = 0.0f;
}

相关推荐

  1. pyqtgraph 实时更新

    2024-04-28 08:34:01       33 阅读
  2. ECharts实现简单饼

    2024-04-28 08:34:01       52 阅读
  3. <span style='color:red;'>UE</span><span style='color:red;'>5</span> <span style='color:red;'>UMG</span>

    UE5 UMG

    2024-04-28 08:34:01      155 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-28 08:34:01       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-28 08:34:01       100 阅读
  3. 在Django里面运行非项目文件

    2024-04-28 08:34:01       82 阅读
  4. Python语言-面向对象

    2024-04-28 08:34:01       91 阅读

热门阅读

  1. 如何精通ChatGPT Prompt:步骤详解

    2024-04-28 08:34:01       28 阅读
  2. 【QT进阶】Qt线程与并发之线程和并发的简单介绍

    2024-04-28 08:34:01       30 阅读
  3. 神经网络与深度学习中的目标检测与语义分割

    2024-04-28 08:34:01       31 阅读
  4. 关于Kotlin

    2024-04-28 08:34:01       25 阅读
  5. Spring 2.x整合Activiti 7

    2024-04-28 08:34:01       30 阅读
  6. 计数原理基础知识

    2024-04-28 08:34:01       25 阅读
  7. 计算机网络—网络层

    2024-04-28 08:34:01       24 阅读
  8. Bun 入门到精通(二)——初始化

    2024-04-28 08:34:01       32 阅读
  9. 数据结构 : 树的分类及在数据库索引中的运用

    2024-04-28 08:34:01       26 阅读
  10. C语言--strlen函数的模拟实现(3种)

    2024-04-28 08:34:01       27 阅读
  11. 英语六级常用词汇2

    2024-04-28 08:34:01       33 阅读
  12. MongoDB的基础使用

    2024-04-28 08:34:01       31 阅读
  13. Circuits--Sequential--Finite4

    2024-04-28 08:34:01       31 阅读
  14. SQL优化方案示例

    2024-04-28 08:34:01       33 阅读