GDI:造山Chart/Graph?

GDI: Create Mountain Chart/Graph?

我可以使用 Polyline() GDI 函数绘制值来创建图表,但现在我想要填充它的下半部分以创建山型图表。是否有内置的东西可以帮助创建它? (我不需要渐变,但这样会很不错)。

TIA!!

对于这种图表类型你需要绘制一个filled shape. The Polygon函数可以用来绘制一个不规则形状的填充对象:

The polygon is outlined by using the current pen and filled by using the current brush [...].

需要从数据点(从左到右)构建多边形点。要将其变成闭合形状,需要附加图表区域的右下角和左下角点。 Polygon 函数然后通过从最后一个顶点到第一个顶点绘制一条线来自动闭合形状。

以下实现将单个数据集呈现到给定的设备上下文区域:

/// \brief Renders a diagram into a DC
///
/// \param dc       Device context to render into
/// \param area     Diagram area in client coordinates
/// \param pen_col  Diagram outline color
/// \param fill_col Diagram fill color
/// \param data     Data points to render in data coordinate space
/// \param y_min    Y-axis minimum in data coordinate space
/// \param y_max    Y-axis maximum in data coordiante space
void render_diagram(HDC dc, RECT area,
                    COLORREF pen_col, COLORREF fill_col,
                    std::vector<int> const& data, int y_min, int y_max) {
    // Make sure we have data
    if (data.size() < 2) { return; }
    // Make sure the diagram area isn't empty
    if (::IsRectEmpty(&area)) { return; }
    // Make sure the y-scale is sane
    if (y_max <= y_min) { return; }

    std::vector<POINT> polygon{};
    // Reserve enough room for the data points plus bottom/right
    // and bottom/left to close the shape
    polygon.reserve(data.size() + 2);

    auto const area_width{ area.right - area.left };
    auto const area_height{ area.bottom - area.top };
    // Translate coordinates from data space to diagram space
    // In lieu of a `zip` view in C++ we're using a raw loop here
    // (we need the index to scale the x coordinate, so we cannot
    // use a range-based `for` loop)
    for (int index{}; index < static_cast<int>(data.size()); ++index) {
        // Scale x value
        auto const x = ::MulDiv(index, area_width - 1, static_cast<int>(data.size()) - 1) + area.left;
        // Flip y value so that the origin is in the bottom/left
        auto const y_flipped = y_max - (data[index] - y_min);
        // Scale y value
        auto const y = ::MulDiv(y_flipped, area_height - 1, y_max - y_min);

        polygon.emplace_back(POINT{ x, y });
    }

    // Semi-close the shape
    polygon.emplace_back(POINT{ area.right - 1, area.bottom - 1 });
    polygon.emplace_back(POINT{ area.left, area.bottom - 1 });

    // Prepare the DC for rendering
    auto const prev_pen{ ::SelectObject(dc, ::GetStockObject(DC_PEN)) };
    auto const prev_brush{ ::SelectObject(dc, ::GetStockObject(DC_BRUSH)) };
    ::SetDCPenColor(dc, pen_col);
    ::SetDCBrushColor(dc, fill_col);

    // Render the graph
    ::Polygon(dc, polygon.data(), static_cast<int>(polygon.size()));

    // Restore DC (stock objects do not need to be destroyed)
    ::SelectObject(dc, prev_brush);
    ::SelectObject(dc, prev_pen);
}

此函数的大部分内容涉及将数据值转换和缩放到目标(客户端)坐标 space。实际渲染比较紧凑,从评论阅读 // Prepare the DC for rendering.

开始

要对此进行测试,您可以从标准 Windows 桌面应用程序开始,并将以下内容转储到 WM_PAINT 处理程序中:

    case WM_PAINT:
    {
        RECT rc{};
        ::GetClientRect(hWnd, &rc);
        // Leave a 10px border around the diagram area
        ::InflateRect(&rc, -10, -10);

        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);

        auto pen_col = RGB(0x00, 0x91, 0x7C);
        auto fill_col = RGB(0xCC, 0xE9, 0xE4);
        render_diagram(hdc, rc, pen_col, fill_col, g_dataset1, 0, 100);

        pen_col = RGB(0x02, 0x59, 0x55);
        fill_col = RGB(0xCC, 0xDD, 0xDD);
        render_diagram(hdc, rc, pen_col, fill_col, g_dataset2, 0, 100);

        EndPaint(hWnd, &ps);
    }
    break;

g_dataset1/g_dataset2 是包含用作测试输入的随机值的容器。重要的是要理解,最终图表是从后到前呈现的,这意味着具有较小值的数据集需要在具有较高值的​​数据集之后呈现;下半部分反复透支。

这会产生如下所示的输出:

请注意,在 HiDpi 显示设备上,GDI 渲染得到 auto-scaled。这会产生以下内容:

仔细观察,您会发现线条比 1 个设备像素宽。如果您想要更清晰的外观,可以通过将应用程序声明为 DPI aware 来禁用 DPI 虚拟化。对于标准 DPI 显示器,情况不会改变;在 HiDpi 显示器上,渲染现在看起来像这样: