left-icon

Direct2D Succinctly®
by Chris Rose

Previous
Chapter

of
A
A
A

CHAPTER 6

Infinite Lines and the Axes

Infinite Lines and the Axes


In this section, we will introduce one method of rendering infinite lines. I will use the example of rendering the chart’s axes that intersect at the origin. Scatter plots and line charts often have the concept of an origin which is the point (0, 0) in the chart’s coordinates. Sometimes this point is very important, and we will display it on the chart as two intersecting lines: one representing the 0 point for the x-axis and another representing the 0 point for the y-axis.

Add two files to your project, Axes.h and Axes.cpp.

// Axes.h

#pragma once

#include "DirectXBase.h"

// This class represents a graph's axes as two perpendicular lines

class Axes {

     ID2D1SolidColorBrush* m_solidBrush; // Brush to draw with

     float m_lineThickness; // Thickness in pixels

     float m_opacity;      // Opacity, 0.0f is invisible 1.0f is solid

     D2D1::ColorF m_color;  // The color of the lines

public:

     // Public constructor

     Axes(D2D1::ColorF col, float thickness, float opacity);

     // 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,

          float panX, float panY, float scaleX, float scaleY);

};

// Axes.cpp

#include "pch.h"

#include "Axes.h"

using namespace D2D1;

using namespace DirectX;

Axes::Axes(D2D1::ColorF col, float thickness = 3.0f, float opacity = 1.0f): m_color(0)

{

// Save these settings to member variables so

// they can create the brush later:

this->m_color = col;

this->m_lineThickness = thickness;

this->m_opacity = opacity;

}

void Axes::CreateDeviceDependentResources(

Microsoft::WRL::ComPtr<ID2D1DeviceContext> context){

// Create the solid color brush

DX::ThrowIfFailed(context->CreateSolidColorBrush(

     m_color, D2D1::BrushProperties(m_opacity), &m_solidBrush));

}

void Axes::Render(Microsoft::WRL::ComPtr<ID2D1DeviceContext> context, float panX, float panY, float scaleX, float scaleY) {

// Draw infinite vertical line with 0.0f as the x-coordinate

context->DrawLine(

     D2D1::Point2F(

          0.0f,   // Horizontal axis

          (-context->GetSize().height - panY) / scaleY // Top of the screen

          ),

     D2D1::Point2F(

          0.0f,   // Horizontal axis

          (-panY) / scaleY // Bottom of the screen

          ),

     m_solidBrush,

     m_lineThickness/scaleX);

// Draw infinite horizontal line with 0.0f as the y-coordinate

context->DrawLine(

     D2D1::Point2F(

          -(panX)/scaleX,  // Left side of screen

          0.0f     // Vertical axis

          ),

     D2D1::Point2F(

          (context->GetSize().width - panX)/scaleX, // Right side of screen

          0.0f     // Vertical axis

          ),

     m_solidBrush,

     m_lineThickness/scaleY);

}

The constructor saves some settings to member variables. The CreateDeviceDependentResources method creates a brush to paint the lines.

The axis lines are theoretically infinite in length. It does not matter how far left, right, up, or down the user pans around the graph's plane, and the ends of these lines should never be visible. To achieve this effect we draw two lines, one for the x-axis and the other for the y-axis. The horizontal line (which marks the 0 point for the y-axis) is drawn the same width as the screen, and the vertical line (which marks the 0 point for the x-axis) has the same length as the screen's height. In this way, regardless of how far the chart is panned, the axis lines will always be drawn across the whole screen if it is visible at all. This will appear as infinite when actually these lines are quite short.

The actual drawing of the lines is achieved through the use of Context’s DrawLine method. This method takes two points, a brush and a line thickness. The line is drawn to connect the two points.

The thickness of the lines is static. I have assumed that even if the user zooms out thousands of units, the origin line should still be visible. Likewise, if the user zooms right into tiny points around the origin, it should not scale to the zoom and take up the entire screen. I have made our origin a standard thickness in pixels no matter the zoom factor by dividing the thickness by the current scale or zoom. In the code I have undone the panning and zooming manually to achieve this.

Note: When drawing lines with some thickness other than 1.0f, it is the center (lengthwise) of the line that will be at the specified coordinate. This is different from bitmaps whose top left corner is drawn at the specified coordinate. This means that if you draw a line from (0, 0) to (100, 0) with a thickness of 30, the top edge of the line will be drawn at (0, (-30/2)) and the lower edge will be drawn at (0, (30/2)).

I have also included a margin. This is the amount, in pixels, short of the screen's edge that the lines will be drawn. It can be used to produce a crosshair origin instead of infinite lines for the axes. To instantiate an object of our new Axes class, add the header to the GraphRenderer.h file. I have included a gradient background and scatter plot in the following code and I have highlighted the code that deals with the origin in blue.

//

// Additional headers for graph objects here

//

#include "GradientBackground.h"

#include "ScatterPlot.h"

#include "Axes.h"

Also add an Axes member variable to this file.

private:

     // Global pan value for moving the chart with the mouse

     Windows::Foundation::Point m_pan;

     // Background

     GradientBackground *m_gradientBackground;

     // Axes

     Axes* m_axes;

     // Data

     ScatterPlot* m_scatterPlot;

Create the chart objects in the GraphRenderer's constructor. These can be created in any order. I have created the Axes last.

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;

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_scatterPlot = new ScatterPlot(x, y, 10.0f, D2D1::ColorF::Chocolate, NodeShape::Circle, count);

delete[] x;

delete[] y;

// Create the Axes

m_axes = new Axes(D2D1::ColorF::Black, 5.0f, 0.75f);

}

Call the Axes’ CreateDeviceResources method so it can initialize its solid color brush.

void GraphRenderer::CreateDeviceResources() {

DirectXBase::CreateDeviceResources();

// Create the brush for the scatter plot:

m_scatterPlot->CreateDeviceDependentResources(m_d2dContext);

// Create the brush for the Axes

m_axes->CreateDeviceDependentResources(m_d2dContext);

}

And finally, call the origin's render method in the GraphRenderer’s Render method.

void GraphRenderer::Render() {

m_d2dContext->BeginDraw();

// Clear to some color other than blank

m_d2dContext->Clear(D2D1::ColorF(ColorF::CornflowerBlue));

// Reset the transform matrix so our background does not pan

m_d2dContext->SetTransform(m_orientationTransform2D);

// Draw the background

m_gradientBackground->Render(m_d2dContext);

// The scale matrix inverts the y-axis

Matrix3x2F scale = Matrix3x2F::Scale(1.0f, -1.0f, D2D1::Point2F(0.0f, 0.0f));

// The pans added to the screen height so origin is at lower left

Matrix3x2F panMatrix = Matrix3x2F::Translation

     (m_pan.X, m_pan.Y + m_d2dContext->GetSize().height);

// Apply the scale and the pan

m_d2dContext->SetTransform(scale*panMatrix*m_orientationTransform2D);

// Draw the axes

m_axes->Render(m_d2dContext, m_pan.X, m_pan.Y, 1.0f, -1.0f);

//

// Draw objects here

//

m_scatterPlot->Render(m_d2dContext);

// Ignore D2DERR_RECREATE_TARGET error

HRESULT hr = m_d2dContext->EndDraw();

if (hr != D2DERR_RECREATE_TARGET) DX::ThrowIfFailed(hr);

}

Upon running the application and panning a little to the right and upwards, you will see the origin and the scatter plot. This is the (0, 0) point in our chart’s world coordinates as shown in Figure 28.

Axis 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.