UE5 实现UMG曲线图控件

本文提供一种使用UE的原生方式创建常用图表控件之一——曲线图。

实现效果如下:

实现思路:

1、圆滑曲线的可以依靠FRichCurve来对关键点进行定位后进行曲线模拟

2、坐标轴的曲线点与绘图的屏幕坐标点的转换

3、使用FSlateDrawElement::MakeLines进行画线

实现方式:

第一步:新建一个Class类,继承自UUserWidget类(依赖模块需要添加SlateCore和UMG)

第二步:重载NativePaint方法,实现曲线图绘制

第三步:重载NativeTick方法,当重载数据后添加曲线绘制的动画实现

第四步:重载NativeOnMouseMove方法,判断当前鼠标位置,控制是否显示曲线点的值标签

源码如下:

SmoothedLineWidget.h

#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "ChartData.h"
#include "SmoothedLineWidget.generated.h"

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

public:
	USmoothedLineWidget(const FObjectInitializer& ObjectInitializer);

	UFUNCTION(BlueprintCallable)
		void AddCategoryValues(const FString& CateName,const TArray<float>& InValues);//计算对应的key值,默认在绘制前已调用

	UFUNCTION(BlueprintCallable)
		void ClearCateries();

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
		FVector2D Size = FVector2D(300, 250);//大小
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
		FVector2D LocationOffset = FVector2D(10, 10);//位置
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
		float BrushSize = 2;//笔刷大小
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
		TArray<FColor> Colors;//颜色

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
		FSlateFontInfo Font;

	UPROPERTY(BlueprintReadWrite)
		float Min = 0;
	UPROPERTY(BlueprintReadWrite)
		float Max = 1;
	UPROPERTY(BlueprintReadWrite)
		bool DrawPoint=false;

protected:
	virtual int32 NativePaint(
		const FPaintArgs& Args,
		const FGeometry& AllottedGeometry,
		const FSlateRect& MyCullingRect,
		FSlateWindowElementList& OutDrawElements,
		int32 LayerId,
		const FWidgetStyle& InWidgetStyle,
		bool bParentEnabled) const override;//绘制函数

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

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

	void GeneratePoints();

	FColor GetColorByIndex(int32 Index) const;

	int32 WhichIdx = -1;

private:
	float BarItemSpace = 1.0f;
	float CurrentValue = 0.0f;

	TArray<FChartCategoryData> CategoryArray;


	void DrawSmoothedLine(
		FSlateWindowElementList& OutDrawElement,
		int InLayerId,
		const FGeometry& InAllottedGeometry,
		FChartCategoryData InData,
		float InThickness,
		FColor InColor
	)const;//线的绘制
};

SmoothedLineWidget.cpp

#include "SmoothedLineWidget.h"
#include "Components/CanvasPanelSlot.h"

USmoothedLineWidget::USmoothedLineWidget(const FObjectInitializer& ObjectInitializer)
	:Super(ObjectInitializer)
{
}

void USmoothedLineWidget::ClearCateries()
{
	CategoryArray.Empty();
	Min = 0;
	Max = 1;
}

FColor USmoothedLineWidget::GetColorByIndex(int32 Index) const
{
	if (Colors.Num() > 0)
	{
		return Colors[Index % Colors.Num()];
	}
	return FColor::White;
}

void USmoothedLineWidget::AddCategoryValues(const FString& CateName, const TArray<float>& InValues)
{
	if (InValues.Num() < 2)
		return;


	FChartCategoryData CategoryData = FChartCategoryData();
	CategoryData.CategoryName = CateName;
	CategoryData.Color = GetColorByIndex(CategoryArray.Num());

	for (int32 Index = 0; Index < InValues.Num(); Index++)
	{
		float value = InValues[Index];
		Min = FMath::Min(Min, value);
		Max = FMath::Max(Max, value);

		FChartCategoryItem Item = FChartCategoryItem();
		Item.Value = value;
		CategoryData.Data.Add(Item);
		CategoryData.Total += value;
	}

	CategoryArray.Add(CategoryData);
	Min = FMath::FloorToInt32(Min);
	Max = FMath::RoundToInt32(Max);

	BarItemSpace = Size.X / InValues.Num();
	GeneratePoints();
}


void USmoothedLineWidget::GeneratePoints()
{
	float WidgetWidth = Size.X;
	float WidgetHeight = Size.Y;
	float Range = Max - Min;
	for (int idx = 0; idx < CategoryArray.Num(); idx++)
	{
		auto InValues = CategoryArray[idx];

		float Space = WidgetWidth / (InValues.Data.Num() - 1);
		for (int32 Index = 0; Index < InValues.Data.Num(); Index++)
		{
			float value = InValues.Data[Index].Value;

			float ScaleValue = WidgetHeight * (value / Range);


			FVector2D KeyPosition(Space * Index, WidgetHeight - ScaleValue);
			InValues.Data[Index].PositionX = KeyPosition.X;
			InValues.Data[Index].PositionY = KeyPosition.Y;
		}
		CategoryArray[idx] = MoveTemp(InValues);

	}
}

int32 USmoothedLineWidget::NativePaint(const FPaintArgs& Args,
	const FGeometry& AllottedGeometry,
	const FSlateRect& MyCullingRect,
	FSlateWindowElementList& OutDrawElements,
	int32 LayerId,
	const FWidgetStyle& InWidgetStyle,
	bool bParentEnabled) const
{
	TArray<FVector2D> XYLines;
	XYLines.Add(FVector2D(LocationOffset.X, Size.Y + LocationOffset.Y));
	XYLines.Add(FVector2D(Size.X + LocationOffset.X, Size.Y + LocationOffset.Y));

	FSlateDrawElement::MakeLines(
		OutDrawElements,
		LayerId,
		AllottedGeometry.ToPaintGeometry(),
		XYLines,
		ESlateDrawEffect::None,
		FColor::FromHex(TEXT("FFFFFF33")),
		true,
		1
	);


	for (int32 idx = 0; idx < CategoryArray.Num(); idx++)
	{
		auto Category = CategoryArray[idx];

		DrawSmoothedLine(
			OutDrawElements,
			LayerId,
			AllottedGeometry,
			Category,
			BrushSize,
			CategoryArray[idx].Color);
	}
	return LayerId++;
}




void USmoothedLineWidget::DrawSmoothedLine(
	FSlateWindowElementList& OutDrawElement,
	int InLayerId,
	const FGeometry& InAllottedGeometry,
	FChartCategoryData InData,
	float InThickness,
	FColor InColor) const
{

	if (InData.Data.Num() < 2)
		return;


	TArray<FVector2D> InPoints;
	for (int32 i = 0; i < InData.Data.Num(); i++)
	{
		auto item = InData.Data[i];
		InPoints.Add(FVector2D(item.PositionX, item.PositionY));
	}

	FRichCurve* RichCurve = new FRichCurve();

	for (FVector2D InPoint : InPoints)
	{
		FKeyHandle KeyHandle = RichCurve->AddKey(InPoint.X, InPoint.Y);
		RichCurve->SetKeyInterpMode(KeyHandle, ERichCurveInterpMode::RCIM_None);
	}

	TArray<FVector2D> ResultPoints;


	float WidgetWidth = Size.X;
	float WidgetHeight = Size.Y;
	int32 Begin = 0;
	int32 End = WidgetWidth;
	for (int32 X = Begin; X <= End && X <= CurrentValue; X++)
	{
		float Y = RichCurve->Eval(X);
		FVector2D ResultPoint(X + LocationOffset.X, Y + LocationOffset.Y);
		ResultPoints.Add(ResultPoint);
	}

	delete RichCurve;

	FSlateDrawElement::MakeLines(
		OutDrawElement,
		InLayerId,
		InAllottedGeometry.ToPaintGeometry(),
		ResultPoints,
		ESlateDrawEffect::None,
		InColor,
		true,
		InThickness
	);

	if (FMath::IsNearlyEqual(CurrentValue, WidgetWidth))
	{
		FSlateBrush* Brush = new FSlateBrush();
		Brush->ImageSize = FVector2D(6, 6);
		Brush->DrawAs = ESlateBrushDrawType::Image;
		Brush->TintColor = FLinearColor(1.0f, 1.0f, 1.0f, 1.0f);
		for (int32 Index = 0; Index < InData.Data.Num(); Index++)
		{

			auto Item = InData.Data[Index];
			auto Point = FVector2D(Item.PositionX, Item.PositionY);
			if (DrawPoint)
			{
				FVector2D Position = FVector2D(Point.X + LocationOffset.X - 3, Point.Y + LocationOffset.Y - 3);
				auto Geometry = InAllottedGeometry.MakeChild(FVector2D(6, 6), FSlateLayoutTransform(Position));
				FSlateDrawElement::MakeBox(OutDrawElement, InLayerId, Geometry.ToPaintGeometry(), Brush, ESlateDrawEffect::None, FLinearColor(InData.Color));
			}

			if (Index == WhichIdx)
			{
				FVector2D TextPosition = FVector2D(Point.X + LocationOffset.X - 5, Point.Y + LocationOffset.Y - 15);
				auto TextGeometry = InAllottedGeometry.MakeChild(FVector2D(60, 12), FSlateLayoutTransform(TextPosition));
				FSlateDrawElement::MakeText(OutDrawElement, InLayerId, TextGeometry.ToPaintGeometry(), FText::FromString(FString::Printf(TEXT("%d"), FMath::CeilToInt32(Item.Value))), Font);
			}
		}
		delete Brush;
	}

}



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

	if (CurrentValue < Size.X)
	{
		CurrentValue += Delta;
	}
	else
	{
		CurrentValue = Size.X;
	}

	Super::NativeTick(MyGeometry, InDeltaTime);
}

FReply USmoothedLineWidget::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 + Size.Y))
	{
		WhichIdx = -1;
	}
	else
	{
		WhichIdx = FMath::TruncToInt32((HoverPostion.X - LocationOffset.X) / BarItemSpace);
	}
	return Super::NativeOnMouseMove(InGeometry, InMouseEvent);
}

  • 15
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IgoAheadNow

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值