CHAPTER 11
We now return to our charting application, where we will add a margin around the edge. I am going to add to the code as it was after the “Navigating between Multiple XAML Pages” chapter. Adding a margin to a chart is important, as it can be used to separate the title, axis labels, and grid figures from the data being plotted. The following margin class consists of a collection of four rectangles and a solid color brush. The rectangles form a border around the visible chart area. Add a Margin.h and Margin.cpp file to your project.
// Margin.h #pragma once #include "DirectXBase.h" enum MarginStyle { Absolute, WindowSizeDependent }; class Margin { ID2D1SolidColorBrush* m_solidBrush; // Brush to draw margin D2D1::ColorF m_color; // The color of the margin D2D1_RECT_F m_leftRect; // Rectangles which make the margin D2D1_RECT_F m_rightRect; D2D1_RECT_F m_topRect; D2D1_RECT_F m_bottomRect; MarginStyle m_style; // Style is Absolute or Window size dependent // The size of the margin float m_left, m_right, m_top, m_bottom; public: // Public constructor Margin(float left, float right, float top, float bottom, D2D1::ColorF color, MarginStyle style); // Create the rectangles to draw the margin void CreateWindowSizeDependentResources (Microsoft::WRL::ComPtr<ID2D1DeviceContext> context); // Create the solid brush to draw with void CreateDeviceDependentResources (Microsoft::WRL::ComPtr<ID2D1DeviceContext> context);
// The render method needs to know the panning and scaling void Render(Microsoft::WRL::ComPtr<ID2D1DeviceContext> context); }; |
// Margin.cpp #include "pch.h" #include "Margin.h" Margin::Margin(float left, float right, float top, float bottom, D2D1::ColorF color, MarginStyle style) : m_color(0) { // Save the parameters for the create resources methods this->m_color = color; m_style = style; this->m_left = left; this->m_right = right; this->m_top = top; this->m_bottom = bottom; } void Margin::CreateWindowSizeDependentResources( Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) { // If the sizes of the margin passed were percentages of the screen // we have to multiply the values before creating the rectangles: if(m_style == MarginStyle::WindowSizeDependent) { m_left = context->GetSize().width * m_left; m_right = context->GetSize().width * m_right; m_top = context->GetSize().height * m_top; m_bottom = context->GetSize().height * m_bottom; } // Left rectangle m_leftRect.left = 0; m_leftRect.right = m_left; m_leftRect.top = 0; m_leftRect.bottom = context->GetSize().height;
// Right rectangle m_rightRect.left = context->GetSize().width - m_right; m_rightRect.right = context->GetSize().width; m_rightRect.top = 0; m_rightRect.bottom = context->GetSize().height;
// Top margin m_topRect.left = m_left; m_topRect.right = context->GetSize().width - m_right; m_topRect.top = 0; m_topRect.bottom = m_top; // Bottom margin m_bottomRect.left = m_left; m_bottomRect.right = context->GetSize().width - m_right; m_bottomRect.top = context->GetSize().height - m_bottom; m_bottomRect.bottom = context->GetSize().height; } void Margin::CreateDeviceDependentResources (Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) { // Create the brush DX::ThrowIfFailed(context->CreateSolidColorBrush(m_color, &m_solidBrush)); } void Margin::Render(Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) { // Draw the left margin context->FillRectangle(m_leftRect, m_solidBrush); // Draw the right margin context->FillRectangle(m_rightRect, m_solidBrush); // Draw the top margin context->FillRectangle(m_topRect, m_solidBrush); // Draw the bottom margin context->FillRectangle(m_bottomRect, m_solidBrush); } |
To add a margin object, we need to add a member variable to the graph renderer. Load its resources when appropriate and render it.
Note: The margin class takes a MarginStyle enum as one of the parameters to the constructor. I have added this to enable margins to be created dependent on the size and/or resolution of the device running the application. Using Absolute as this parameter results in the margin's thickness parameters being interpreted as pixels. Using WindowSizeDependent means that the parameters passed in are interpreted as a percentage of the context width and height.
Add the Margin.h file to the Graph Renderer.h headers section:
// // Additional headers for graph objects here // #include "GradientBackground.h" #include "ScatterPlot.h" #include "LineChart.h" #include "Axes.h" #include "Margin.h" |
Add the member variables to the GraphRenderer.h file.
// Plottable data GraphVariable* m_graphVariable; GraphVariable* m_lineChart; // Axes Axes* m_axes; // Margin Margin* m_margin; |
Call the constructor(s) for the margin(s) in the GraphRenderer constructor; this can be done at any time. I have placed it after constructing the m_axes object.
// Create the line chart m_lineChart = new LineChart(x, y, count, D2D1::ColorF::Chocolate, 5.0f); delete[] x; delete[] y; // Create the Axes m_axes = new Axes(D2D1::ColorF::Black, 5.0f, 0.75f); // Create the margin m_margin = new Margin(0.1f, 0.1f, 0.1f, 0.1f, // 10% of window size D2D1::ColorF(0.38f, 0.66f, 0.74f, 1.0f), MarginStyle::WindowSizeDependent); } |
Call the CreateDeviceDependentResources method for the new m_margin object in the GraphRenderer::CreateDeviceResources method (this will create the margin’s brush).
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)); // Create the margin's device dependent resources m_margin->CreateDeviceDependentResources(m_d2dContext); } |
Call the CreateWindowSizeDependentResources method for the margin(s) in the GraphRenderer::CreateWindowSizeDependentResources method (this will create the rectangles to fill for the margin).
void GraphRenderer::CreateWindowSizeDependentResources() { DirectXBase::CreateWindowSizeDependentResources(); // Create window size resources for gradient background m_gradientBackground->CreateWindowSizeDependentResources(m_d2dContext); // Set the initial pan value so the lowest node is visible in the corner m_pan.X = -m_graphVariable->GetMinX(); m_pan.Y = m_graphVariable->GetMinY(); // Create window size dependent resources for the margin m_margin->CreateWindowSizeDependentResources(m_d2dContext); } |
And finally, call Render on the margin(s) in the GraphRenderer::Render method. I have rendered the margins immediately after the transformation matrix is reset, and just prior to rendering the FPS counter. We originally added this reset to make sure the FPS counter was not affected by the pan and zoom, but we can also use it to ensure the margin is not transformed.
// // Draw objects here // // Render the graph variable(s) m_lineChart->Render(m_d2dContext); m_graphVariable->Render(m_d2dContext); // Reset the transform matrix so the time and FPS do not pan or zoom m_d2dContext->SetTransform(m_orientationTransform2D); // Render the margin m_margin->Render(m_d2dContext); // Set up the string to print: std::wstring s = std::wstring( L"Total Time: ") + std::to_wstring(m_timeTotal) + std::wstring(L" FPS: ") + std::to_wstring( (int)(0.5f+1.0f/m_timeDelta)); // FPS rounded to nearest int |
Upon debugging the application, you should see a stormy cyan colored margin surrounding the window, as shown in Figure 34.

Margins