left-icon

Direct2D Succinctly®
by Chris Rose

Previous
Chapter

of
A
A
A

CHAPTER 8

Line Charts

Line Charts


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.

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.