You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

635 lines
14 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#pragma once
#include "DrawOperator/Xy.h"
#include "DrawOperator/one.h"
#include "Util.h"
class CTextFaciesBuilder {
public:
CTextFaciesBuilder& FontLocation(double x, double y)
{
m_x = x;
m_y = y;
return *this;
}
CTextFaciesBuilder& FontSize(double width, double height)
{
m_width = width;
m_height = height;
return *this;
}
CTextFaciesBuilder& Text(const CString& text)
{
m_text = text;
return *this;
}
std::unique_ptr<CText> Build() const
{
std::unique_ptr<CText> pText = std::make_unique<CText>();
const CString formatStr = "%lf,%lf,0.00000000 98\n"
"{\n"
"%s\n"
"}\n"
"%lf %lf 0.000000 0 400 0 0 0 1 0 16 0 0 Times New Roman";
CString content;
content.Format(formatStr, m_x, m_y, m_text, m_width, m_height);
CMemFile memFile((BYTE*)content.GetString(), content.GetLength());
pText->Read(memFile, CUR_VERSION);
return pText;
}
private:
double m_x = 0.0;
double m_y = 0.0;
double m_width = 21.0;
double m_height = 52.0;
CString m_text;
};
/**
* 相类图例 Item
*/
class FaciesLegendItem
{
public:
FaciesLegendItem(CXy& xy, const CString& name, COLORREF color)
: m_sourceXy(xy), m_name(name), m_color(color)
{
}
/**
* 设置相类图例占用范围
*
* \param x 左下角 x 坐标
* \param y 左下角 y 坐标
* \param width 宽
* \param height 高
*/
void SetBounds(double x, double y, double width, double height)
{
m_x = x;
m_y = y;
m_width = width;
m_height = height;
}
/**
* 获取相类图例占用范围
*
* \param x 左下角 x 坐标
* \param y 左下角 y 坐标
* \param width 宽
* \param height 高
*/
void GetBounds(double& x, double& y, double& width, double& height) const
{
x = m_x;
y = m_y;
width = m_width;
height = m_height;
}
/**
* 生成相类图例
*
* \param targetXy 要生成到的图件 xy
*/
void Build(CXy& targetXy) const
{
BuildCurveRange(targetXy);
BuildElement(targetXy);
BuildName(targetXy);
}
private:
/**
* 生成相类图例名称
*
* \param targetXy 要生成的目标图件
*/
void BuildName(CXy& targetXy) const
{
CString name = m_name;
auto [x, y] = MeasureNameLocation(name);
std::unique_ptr<CText> pText = CTextFaciesBuilder()
.FontLocation(x, y)
.FontSize(10, 26)
.Text(name)
.Build();
CLayer* pLayer = targetXy.FindAddLayer(_T("相类图列"));
std::unique_ptr<COne> pOne = std::make_unique<COne>();
pOne->SetLayer(pLayer);
pOne->SetValueSafe(pText.release());
pOne->SetColor(RGB(0, 0, 0));
targetXy.AddTailOne(pOne.release());
}
/**
* 生成相类图例内容
*
* \param targetXy
*/
void BuildElement(CXy& targetXy) const
{
// 生成彩色方块
std::unique_ptr<CCurveEx> pCurve = std::make_unique<CCurveEx>();
pCurve->Create(5);
// 将最下面的 y 向上提升
double y = m_y + m_height * (1 - m_canvasHeightRatio);
// 缩小范围
double left = m_x + 5.0;
double right = m_x + m_width - 5.0;
double bottom = y + 5.0;
double top = m_y + m_height - 5.0;
// 绘制矩形路径(缩小后的范围)
pCurve->x[0] = left;
pCurve->y[0] = bottom;
pCurve->x[1] = right;
pCurve->y[1] = bottom;
pCurve->x[2] = right;
pCurve->y[2] = top;
pCurve->x[3] = left;
pCurve->y[3] = top;
pCurve->x[4] = left;
pCurve->y[4] = bottom;
pCurve->GetLocation();
std::unique_ptr<COne> pOne = std::make_unique<COne>();
pOne->SetValueSafe(pCurve.release());
//增加修饰
pOne->HowToViewCurve = new CHowToViewCurve;
pOne->HowToViewCurve->EnableDrawSourceCurve(true);
CCurvePropertiesEx* pr = new CCurvePropertiesEx;
pr->color = m_color;
pr->m_nFlags = PLINE_SOLID;
pOne->HowToViewCurve->Add(pr);
CLayer* pLayer = targetXy.FindAddLayer(_T("相类图列"));
pOne->SetLayer(pLayer);
pOne->SetColor(m_color); // 使用 FaciesLegendItem 中存储的颜色
targetXy.AddTailOne(pOne.release());
}
/**
* 将点放到目标区域中心
*
* \param point
* \param range
*/
void CenterPointInRange(CPointNameEx& point, const CRect8& range) const
{
point.x0 = range.left + (range.right - range.left) / 2;
point.y0 = range.bottom + (range.top - range.bottom) / 2;
point.SetName(""); // 清空点的名称
}
/**
* 将曲线放到目标区域中心
*
* \param curve
* \param range
*/
void CenterCurveInRange(CCurveEx& curve, const CRect8& range) const
{
CRect8 oldRange;
oldRange.SetRect(1e100, -1e100, -1e100, 1e100);
curve.GetRange(oldRange);
CRect8 newRange = ScaleRect(range, 0.9);
for (int i = 0; i < curve.num; i++)
{
auto [x, y] = ScalePoint(curve.x[i], curve.y[i], oldRange, newRange);
curve.x[i] = x;
curve.y[i] = y;
}
curve.GetLocation();
}
/**
* 绘制相类图例包围线
*
* \param targetXy
*/
void BuildCurveRange(CXy& targetXy) const
{
std::unique_ptr<CCurveEx> pCurve = std::make_unique<CCurveEx>();
pCurve->Create(5);
// 将最下面的 y 向上提升
double y = m_y + m_height * (1 - m_canvasHeightRatio);
pCurve->x[0] = m_x;
pCurve->y[0] = y;
pCurve->x[1] = m_x + m_width;
pCurve->y[1] = y;
pCurve->x[2] = m_x + m_width;
pCurve->y[2] = m_y + m_height;
pCurve->x[3] = m_x;
pCurve->y[3] = m_y + m_height;
pCurve->x[4] = m_x;
pCurve->y[4] = y;
pCurve->GetLocation();
CLayer* pLayer = targetXy.FindAddLayer(_T("相类图列"));
std::unique_ptr<COne> pOne = std::make_unique<COne>();
pOne->SetValueSafe(pCurve.release());
pOne->SetLayer(pLayer);
pOne->SetColor(RGB(0, 0, 0));
targetXy.AddTailOne(pOne.release());
}
/**
* 将点从一个矩形范围缩放到另一个矩形范围
*
* \param x x 坐标
* \param y y 坐标
* \param oldRange 旧的范围
* \param newRange 新的范围
* \return 缩放后的 x y 坐标
*/
std::pair<double, double> ScalePoint(double x, double y, const CRect8& oldRange, const CRect8& newRange) const
{
double xMin = oldRange.left;
double xMax = oldRange.right;
double yMin = oldRange.bottom;
double yMax = oldRange.top;
double newXMin = newRange.left;
double newXMax = newRange.right;
double newYMin = newRange.bottom;
double newYMax = newRange.top;
double newX = ((x - xMin) / (xMax - xMin)) * (newXMax - newXMin) + newXMin;
double newY = ((y - yMin) / (yMax - yMin)) * (newYMax - newYMin) + newYMin;
return { newX, newY };
}
/**
* 获取相类图例绘制区域
*
* \return
*/
CRect8 CanvasRect() const
{
return CRect8(m_x, m_y + m_height, m_x + m_width, m_y + m_height * (1 - m_canvasHeightRatio));
}
/**
* 将矩形向中心缩放
*
* \param rect 要缩放的矩形
* \param scaleFactor 缩放系数
* \return
*/
CRect8 ScaleRect(const CRect8& rect, double scaleFactor) const
{
assert(scaleFactor > 0);
double width = rect.right - rect.left;
double height = rect.top - rect.bottom;
double newWidth = width * scaleFactor;
double newHeight = height * scaleFactor;
double left = rect.left + (width - newWidth) / 2;
double top = rect.top - (height - newHeight) / 2;
double right = rect.right - (width - newWidth) / 2;
double bottom = rect.bottom + (height - newHeight) / 2;
return CRect8(left, top, right, bottom);
}
/**
* 测量名称该显示的 x, y 位置
*
* \param text
* \return
*/
std::pair<double, double> MeasureNameLocation(const CString& name) const
{
double x = m_x + m_width / 2; // 要根据文字的宽度计算 x 的位置,目前把 x 设置到中间位置,貌似文本就显示在中间
double height = m_height * (1 - m_canvasHeightRatio); // 这是名字显示区域高度
double y = m_y + height; // y 抬升,这个位置是我们留给文字显示的区域最高处
y -= height * m_nameOffsetRatio; // 再移动到文字显示位置
return { x, y };
}
// 由于我们大地坐标是笛卡尔坐标系,所以这里的 x y 是左下角位置,这是行业一般惯例
double m_x = 0.0; // 左下 x
double m_y = 0.0; // 左下 y
double m_width = 2.0; // 宽度
double m_height = 1.0; // 高度
double m_nameOffsetRatio = 7.0 / 52.0; // 名称显示位置,相对于名称高度占比
double m_canvasHeightRatio = 65.0 / 117.0; // 绘制区域高度和整个高度的比例
CXy& m_sourceXy; // 原图件xy即要生成符号的图件 xy
CString m_name; // 图例item名称
COLORREF m_color; // 图例item颜色
};
/**
* 相类相类图例
*
*/
class FaciesLegend
{
public:
// 传入名字 + 颜色列表
FaciesLegend(CXy& xy, double x, double y, const CString& title, const std::vector<std::pair<CString, COLORREF>>& items, double width = 600, int columns = 3)
: m_sourceXy(xy), m_x(x), m_y(y), m_width(width), m_columns(columns)
{
for (const auto& item : items)
{
m_boxes.emplace_back(m_sourceXy, item.first, item.second);
}
}
/**
* 构造相类图例
*
* \return 返回构造好的相类图例指针
*/
std::unique_ptr<CInsertBlock> Build()
{
ComputeLayout();
std::unique_ptr<CXy> pNewXy = std::make_unique<CXy>();
pNewXy->color = 0;
// 生成相类图例边框
BuildCurveRange(*pNewXy);
// 生成相类图例标题
BuildTitle(*pNewXy);
// 生成相类图例 item
for (auto& box : m_boxes)
{
box.Build(*pNewXy);
}
// 创建 Block
std::unique_ptr<CInsertBlock> pBlock = std::make_unique<CInsertBlock>();
pBlock->SetXy(pNewXy.release());
return pBlock;
}
/**
* 计算每个相类图例占的空间
*
*/
void ComputeLayout()
{
size_t rows = m_boxes.size() / m_columns + (m_boxes.size() % m_columns == 0 ? 0 : 1);
size_t columns = m_columns;
// 计算出列间隙宽度
double horizontalSpacing = CalcHorizontalSpacing(m_width, static_cast<int>(columns), m_boxWidthSpacingRatio);
// 计算相类图例的宽度
m_boxWidth = horizontalSpacing * m_boxWidthSpacingRatio;
// 计算相类图例的高度,设计宽高比为 10 比 8
m_boxHeight = m_boxWidth * m_widthHeightRatio;
m_titleHeight = m_boxWidth * m_titleWidthRatio;
// 计算相类图例高度
m_height = m_boxHeight * rows + m_titleHeight;
for (size_t i = 0; i < m_boxes.size(); i++)
{
size_t row = i / columns;
size_t column = i % columns;
// x 偏移量由前面的列宽加列间隙组成
double xOffset = column * m_boxWidth + (column + 1) * horizontalSpacing;
double yOffset = (row + 1) * m_boxHeight;
// y 则麻烦的多
// 我们先要计算出相类图例框最高的位置
// 然后减去标题高度
// 再向下减去具体的位置
// 由于我们要计算的 y 是方框左下角的位置,所以还要再减上 boxHeight
yOffset = m_height - yOffset - m_titleHeight;
m_boxes[i].SetBounds(xOffset, yOffset, m_boxWidth, m_boxHeight);
}
OffsetBoxes();
}
double GetWidth() const
{
return m_width;
}
double GetHeight() const
{
return m_height;
}
private:
/**
* 前面的计算是基于 0,0 为左下坐标开始计算的,现在要根本 m_x, m_y 坐标进行偏移
*
*/
void OffsetBoxes()
{
for (auto& box : m_boxes)
{
double x = 0.0;
double y = 0.0;
double width = 0.0;
double height = 0.0;
box.GetBounds(x, y, width, height);
box.SetBounds(m_x + x, m_y + y, width, height);
}
}
// 将笛卡尔坐标系的矩形向中心缩小
std::tuple<double, double, double, double> ScaleRect(double x, double y, double width, double height, double scaleFactor)
{
assert(scaleFactor > 0 && scaleFactor <= 1);
double newWidth = width * scaleFactor;
double newHeight = width * scaleFactor;
double newX = x + ((width - newWidth) / 2);
double newY = y + ((height - newHeight) / 2);
return { newX, newY, newWidth, newHeight };
}
/**
* 绘制最外围的曲线
*
* \param targetXy 要绘制的目标图件
*/
void BuildCurveRange(CXy& targetXy) const
{
std::unique_ptr<CCurveEx> pCurve = std::make_unique<CCurveEx>();
pCurve->Create(5);
pCurve->x[0] = m_x;
pCurve->y[0] = m_y;
pCurve->x[1] = m_x + m_width;
pCurve->y[1] = m_y;
pCurve->x[2] = m_x + m_width;
pCurve->y[2] = m_y + m_height;
pCurve->x[3] = m_x;
pCurve->y[3] = m_y + m_height;
pCurve->x[4] = m_x;
pCurve->y[4] = m_y;
pCurve->GetLocation();
CLayer* pLayer = targetXy.FindAddLayer(_T("相类图列"));
std::unique_ptr<COne> pOne = std::make_unique<COne>();
pOne->SetValueSafe(pCurve.release());
pOne->SetLayer(pLayer);
pOne->HowToViewCurve = CreateBorderHowToView().release();
targetXy.AddTailOne(pOne.release());
}
/**
* 生成“相类图列”这个标题
*
* \param targetXy 要绘制的目标图件
*/
void BuildTitle(CXy& targetXy) const
{
double x = m_x + m_width * 0.5;
double y = m_y + m_height - m_titleHeight * m_titleOffsetRatio;
std::unique_ptr<CText> pText = CTextFaciesBuilder()
.FontLocation(x, y)
.FontSize(21, 52)
.Text("相 类")
.Build();
CLayer* pLayer = targetXy.FindAddLayer(_T("相类图列"));
std::unique_ptr<COne> pOne = std::make_unique<COne>();
pOne->SetLayer(pLayer);
pOne->SetValueSafe(pText.release());
pOne->SetColor(RGB(0, 0, 0));
targetXy.AddTailOne(pOne.release());
}
/**
* 创建相类图例最外围边框线的修饰
*
* \return
*/
std::unique_ptr<CHowToViewCurve> CreateBorderHowToView() const
{
std::unique_ptr<CCurvePropertiesEx> prop1 = std::make_unique<CCurvePropertiesEx>();
prop1->m_size = 0;
prop1->color = abs(16777215);
prop1->m_nFlags = 65568;
prop1->m_SmoothStep = 0;
prop1->m_offset = 0;
prop1->m_stuCurveWave.T = 0;
prop1->m_stuCurveWave.T = 0;
prop1->m_stuCurveWave.A = 0;
prop1->m_stuCurveWave.dnum = 0;
std::unique_ptr<CCurvePropertiesEx> prop2 = std::make_unique<CCurvePropertiesEx>();
prop2->m_size = 0;
prop2->color = abs(0);
prop2->m_nFlags = 65536;
prop2->m_SmoothStep = 0;
prop2->m_offset = 0;
prop2->m_stuCurveWave.T = 0;
prop2->m_stuCurveWave.T = 0;
prop2->m_stuCurveWave.A = 0;
prop2->m_stuCurveWave.dnum = 0;
std::unique_ptr<CHowToViewCurve> pHowToViewCurve = std::make_unique<CHowToViewCurve>();
pHowToViewCurve->Add(prop1.release());
pHowToViewCurve->Add(prop2.release());
return pHowToViewCurve;
}
/**
* 根据 总宽度,列数,列宽和间隙比例 计算间隙宽度
*
* \param width 宽度
* \param n 多少列
* \param ratio 列和间隙的宽度比例
* \return
*/
double CalcHorizontalSpacing(double width, int n, double ratio) const
{
// 总共 n 列,间隙为 n + 1 列
// (n + 1) * space + n * boxWidth = width
// boxWidth = ratio * space
// (n + 1) * space + n * (ratio * space) = width
// (n + 1 + n * ratio) * space = width
// space = width / (n + 1 + n * ratio)
return width / (n + 1 + n * ratio);
}
// 由于我们大地坐标是笛卡尔坐标系,所以这里的 x y 是左下角位置,这是行业一般惯例
double m_x = 0.0;
double m_y = 0.0;
double m_width = 512.0;
double m_height = 421.0;
CString m_title;
// 每列间隔和相类图例 Item 宽度为 46 比 106
double m_boxWidthSpacingRatio = 106.0 / 46.0;
// 图列宽高比
double m_widthHeightRatio = 106.0 / 117.0;
// 标题栏高度与相类图例宽度的比例
double m_titleWidthRatio = 77.0 / 106.0;
// 标题起始位置相对整个标题框高度占比
double m_titleOffsetRatio = 10.0 / 77.0;
double m_boxWidth = 0.0;
double m_boxHeight = 0.0;
double m_titleHeight = 0.0;
int m_columns = 3;
std::vector<CString> m_layers;
std::vector<FaciesLegendItem> m_boxes;
CXy& m_sourceXy;
};