CHAPTER 8
We have looked a little at rendering lines in our origin, but now we will examine rendering many lines, and we will use a line chart as an example. Line charts display data as a series of connected lines. The lines connect consecutive nodes or points, and depicts continuity or a chronological order to the data in a way which the scatter plot does not. Frequently, the x-axis is seen as time and progresses from early points on the left to later points on the right, as shown in Figure 29.

Line Chart
Figure 29 is a simple line chart that uses the year as the x-axis. It progresses from 1978 to 2013, and at each year a score variable is plotted and connected with the previous point as a straight line. The important difference is that the data must be sorted by the x-axis prior to rendering the lines; otherwise, something like Figure 30 may happen.

Line Chart with Unordered X-Axis
Figure 30 is certainly a line chart, but the line is drawn backwards and forwards with regards to the x-axis values, since the nodes were created in a random order. To properly render a line chart from data with an unordered x-axis, the data must be sorted. I have used the STL (Standard Template Library) stable sort in the following code.
Tip: Always sort the data outside of the Update and Render methods. If the data is sorted outside these critical methods, the speed of the sorting algorithm is largely negligible. Modern hardware will easily sort 1,000,000 nodes in a few seconds, but we can’t afford a few seconds in our Update or Render methods.
Add two files to your project, LineChart.h and LineChart.cpp.
// LineChart.h #pragma once #include "DirectXBase.h" #include "GraphVariable.h" // This class represents a variable rendered as a line class LineChart: public GraphVariable { ID2D1SolidColorBrush* m_solidBrush; // Brush to draw with float m_lineThickness; // Thickness in pixels D2D1::ColorF m_color; // The color of the line // Method to stable-sort the data by the x-axis void SortData(); public: // Public constructor LineChart(float* x, float *y, int count, D2D1::ColorF col, float thickness); // Create the solid brush to draw with virtual void CreateDeviceDependentResources (Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) override;
// The main render method of the line class virtual void Render(Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) override; }; |
// LineChart.cpp #include "pch.h" #include "LineChart.h" #include <vector> #include <algorithm> using namespace D2D1; using namespace DirectX; // Comparison method used by the stable-sort below bool ComparePoints(D2D1_POINT_2F a, D2D1_POINT_2F b) { return a.x < b.x; // Sort on x values } LineChart::LineChart(float* x, float *y, int count, D2D1::ColorF col, float thickness = 3.0f): m_color(0), GraphVariable(x, y, count) { // Save these settings to member variables to // create the brush later: this->m_color = col; this->m_lineThickness = thickness; // Sort the data by the x-axis SortData(); } void LineChart::SortData() { // Sort the data by the x-axis std::vector<D2D1_POINT_2F> sortedNodes(m_points, m_points + m_nodeCount); // Note the use of the stable sort, using an unstable sort // like Quicksort will produce unexpected results! std::stable_sort(sortedNodes.begin(), sortedNodes.end(), ComparePoints); // Copy the sorted points back to the m_pointsArray int counter = 0; for(std::vector<D2D1_POINT_2F>::iterator nodeIterator = sortedNodes.begin(); nodeIterator != sortedNodes.end(); nodeIterator++, counter++) { m_points[counter].x = (*nodeIterator).x; m_points[counter].y = (*nodeIterator).y; } } void LineChart::CreateDeviceDependentResources( Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) { // Create the solid color brush DX::ThrowIfFailed(context->CreateSolidColorBrush( m_color, D2D1::BrushProperties(1.0f), &m_solidBrush)); } |
To create and render a LineChart, we can replace the code we used to create the ScatterPlot. It is also common to render ScatterPlot nodes over the top of a line chart, so I will include the ScatterPlot object as well. The ScatterPlot nodes will be used to accent the LineChart. Add the header to the GraphRenderer.h file.
// // Additional headers for graph objects here // #include "GradientBackground.h" #include "ScatterPlot.h" #include "LineChart.h" #include "Axes.h" |
Add a member variable called m_lineChart to the GraphRenderer class.
// Member variables for displaying FPS float m_timeDelta; // Time since last update call float m_timeTotal; // Total time of application Microsoft::WRL::ComPtr<IDWriteTextFormat> m_textFormat; Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> m_blackBrush;
// Solid background GradientBackground* m_gradientBackground; // Plottable data GraphVariable* m_graphVariable; GraphVariable* m_lineChart; // Axes Axes* m_axes; |
Call the constructor for the LineChart object in the GraphRenderer constructor. I am plotting the LineChart and the ScatterPlot using the same data and color.
GraphRenderer::GraphRenderer() { // Create the gradient background: D2D1_COLOR_F colors[] = { D2D1::ColorF(ColorF::PaleGoldenrod), D2D1::ColorF(ColorF::PaleTurquoise), D2D1::ColorF(0.7f, 0.7f, 1.0f, 1.0f) }; float stops[] = { 0.0f, 0.5f, 1.0f }; m_gradientBackground = new GradientBackground(colors, stops, 3); // Create the scatter plot: const int count = 25; // Create 25 nodes float* x = new float[count]; float* y = new float[count]; // Create random points to plot, these // would usually be read from some data source: for(int i = 0; i < count; i++) { x[i] = (float)(rand() % 2000) - 1000; y[i] = (float)(rand() % 1000) - 500; } m_graphVariable = new ScatterPlot(x, y, 10.0f, D2D1::ColorF::Chocolate, NodeShape::Circle, count); // Create the line chart m_lineChart = new LineChart(x, y, count, D2D1::ColorF::Chocolate, 5.0f); delete[] x; delete[] y; // Create the axes lines m_axes = new Axes(D2D1::ColorF::Black, 5.0f, 0.75f); } |
Call the line chart's CreateDeviceDependentResources method to create the brush to use when drawing the lines.
void GraphRenderer::CreateDeviceResources() { DirectXBase::CreateDeviceResources(); // Call the create device resources for our graph variable m_graphVariable->CreateDeviceDependentResources(m_d2dContext); // Create device resources for the line chart m_lineChart->CreateDeviceDependentResources(m_d2dContext); // Create the brush for the axes m_axes->CreateDeviceDependentResources(m_d2dContext); // Create the solid brush for the text DX::ThrowIfFailed( m_d2dContext->CreateSolidColorBrush(ColorF(ColorF::Black),&m_blackBrush)); } |
And finally, call the m_lineChart::Render method in the GraphRenderer::Render method just prior to plotting the ScatterPlot nodes.
// // Draw objects here // // Render the graph variable(s) m_lineChart->Render(m_d2dContext); m_graphVariable->Render(m_d2dContext); |
Upon running the application, you should see something like Figure 31.

Line Chart
The data in the x-axis may already be ordered (since it may have been collected in chronological order), in which case it need not be sorted at all. The LineChart class should be fairly self-explanatory. We have created a list of points from the data passed to the constructor and in the render method, and we join the points with a collection of lines.