#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 Build() const { std::unique_ptr pText = std::make_unique(); 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 pText = CTextFaciesBuilder() .FontLocation(x, y) .FontSize(10, 26) .Text(name) .Build(); CLayer* pLayer = targetXy.FindAddLayer(_T("相类图列")); std::unique_ptr pOne = std::make_unique(); 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 pCurve = std::make_unique(); 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 pOne = std::make_unique(); 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 pCurve = std::make_unique(); 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 pOne = std::make_unique(); 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 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 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>& 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 Build() { ComputeLayout(); std::unique_ptr pNewXy = std::make_unique(); pNewXy->color = 0; // 生成相类图例边框 BuildCurveRange(*pNewXy); // 生成相类图例标题 BuildTitle(*pNewXy); // 生成相类图例 item for (auto& box : m_boxes) { box.Build(*pNewXy); } // 创建 Block std::unique_ptr pBlock = std::make_unique(); 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(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 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 pCurve = std::make_unique(); 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 pOne = std::make_unique(); 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 pText = CTextFaciesBuilder() .FontLocation(x, y) .FontSize(21, 52) .Text("相 类") .Build(); CLayer* pLayer = targetXy.FindAddLayer(_T("相类图列")); std::unique_ptr pOne = std::make_unique(); pOne->SetLayer(pLayer); pOne->SetValueSafe(pText.release()); pOne->SetColor(RGB(0, 0, 0)); targetXy.AddTailOne(pOne.release()); } /** * 创建相类图例最外围边框线的修饰 * * \return */ std::unique_ptr CreateBorderHowToView() const { std::unique_ptr prop1 = std::make_unique(); 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 prop2 = std::make_unique(); 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 pHowToViewCurve = std::make_unique(); 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 m_layers; std::vector m_boxes; CXy& m_sourceXy; };