Initial plotting support.

This commit is contained in:
jmsgrogan 2023-01-24 17:15:25 +00:00
parent df450a7be0
commit c2027801be
34 changed files with 756 additions and 20 deletions

View file

@ -20,6 +20,7 @@ public:
POINT,
PATH,
CIRCLE,
POLYGON,
UNKNOWN
};

View file

@ -24,7 +24,7 @@ list(APPEND HEADERS
points/PointCollection.h
points/DiscretePoint.h
primitives/Circle.h
primitives/Quad.h
primitives/Polygon.h
primitives/Rectangle.h
primitives/Triangle.h
)
@ -49,7 +49,7 @@ list(APPEND SOURCES
points/PointCollection.cpp
points/DiscretePoint.cpp
primitives/Circle.cpp
primitives/Quad.cpp
primitives/Polygon.cpp
primitives/Rectangle.cpp
primitives/Triangle.cpp
)

View file

@ -1,6 +1,36 @@
#pragma once
#include "Point.h"
#include "Vector.h"
class Rotation
{
public:
enum class Axis
{
X,
Y,
Z,
USER
};
Rotation(double angle = 0.0, Axis axis = Axis::Z, const Point& loc = {}, const Vector& customAxis = {})
{
}
private:
double mAngle{ 0 };
Axis mAxis{ Axis::Z };
Point mPoint;
Vector mCustomAxis;
};
struct Scale
{
};
class Transform
{

View file

@ -0,0 +1,38 @@
#include "Polygon.h"
namespace ntk {
Polygon::Polygon(const std::vector<Point>& points)
: AbstractGeometricItem()
{
if (points.size() > 0)
{
mStartPoint = points[0];
}
mPoints = PointCollection(points);
}
const PointCollection& Polygon::getPoints() const
{
return mPoints;
}
Bounds Polygon::getBounds() const
{
return mPoints.getBounds();
}
const Point& Polygon::getLocation() const
{
return mStartPoint;
}
void Polygon::sample(SparseGrid<bool>*) const
{
}
Polygon::Type Polygon::getType() const
{
return Polygon::Type::POLYGON;
}
}

View file

@ -0,0 +1,27 @@
#pragma once
#include "AbstractGeometricItem.h"
#include "PointCollection.h"
namespace ntk{
class Polygon : public AbstractGeometricItem
{
public:
Polygon(const std::vector<Point>& points);
const PointCollection& getPoints() const;
Bounds getBounds() const override;
const Point& getLocation() const override;
void sample(SparseGrid<bool>*) const override;
Type getType() const override;
private:
Point mStartPoint;
PointCollection mPoints;
};
}

View file

@ -11,7 +11,10 @@ list(APPEND publishing_HEADERS
pdf/PdfWriter.h
plotting/GraphPlotter.h
plotting/PlotNode.h
plotting/Plot.h
plotting/EquationNode.h
plotting/PlotSeriesNode.h
plotting/PlotCaptionNode.h
latex/LatexDocument.h
latex/LatexMathExpression.h
latex/LatexSymbols.h
@ -34,7 +37,10 @@ list(APPEND publishing_LIB_INCLUDES
latex/LatexSymbols.cpp
plotting/GraphPlotter.h
plotting/PlotNode.cpp
plotting/Plot.cpp
plotting/EquationNode.cpp
plotting/PlotSeriesNode.cpp
plotting/PlotCaptionNode.cpp
DocumentConverter.cpp
)

View file

@ -0,0 +1,7 @@
#include "EquationNode.h"
EquationNode::EquationNode(const Transform& t)
: AbstractVisualNode(t)
{
}

View file

@ -0,0 +1,9 @@
#pragma once
#include "AbstractVisualNode.h"
class EquationNode : public AbstractVisualNode
{
public:
EquationNode(const Transform& t = {});
};

View file

@ -0,0 +1,2 @@
#pragma once

View file

@ -0,0 +1,91 @@
#pragma once
#include "Point.h"
#include <string>
#include <vector>
class TextSpan
{
public:
enum class Type
{
PLAIN,
EQUATION
};
TextSpan(const std::string& content, Type type = Type::PLAIN)
: mType(type),
mContent(content)
{
}
private:
Type mType{ Type::PLAIN };
std::string mContent;
};
class PlotCaption
{
public:
PlotCaption() = default;
void setContent(const std::string& plainContent)
{
mContent = { plainContent };
}
void setContent(const std::vector<TextSpan>& content)
{
mContent = content;
}
private:
std::vector<TextSpan> mContent;
};
class PlotSeries
{
public:
PlotSeries(const std::string& label = {})
: mLabel(label)
{
}
void setData(const std::vector<Point>& data)
{
mData = data;
}
private:
std::string mLabel;
std::vector<Point> mData;
};
class Plot
{
public:
Plot() = default;
void setXAxisCaption(const std::string& caption)
{
mXAxisCaption.setContent(caption);
}
void setYAxisCaption(const std::string& caption)
{
mYAxisCaption.setContent(caption);
}
void addSeries(const PlotSeries& series)
{
mSeries.push_back(series);
}
private:
PlotCaption mXAxisCaption;
PlotCaption mYAxisCaption;
std::vector<PlotSeries> mSeries;
};

View file

@ -0,0 +1,16 @@
#include "PlotCaptionNode.h"
#include "TextNode.h"
#include "EquationNode.h"
PlotCaptionNode::PlotCaptionNode(const Transform& transform)
: AbstractVisualNode(transform)
{
}
void PlotCaptionNode::setContent(const PlotCaption& content)
{
mContent = content;
mContentDirty = true;
}

View file

@ -0,0 +1,23 @@
#pragma once
#include "AbstractVisualNode.h"
#include "Plot.h"
class TextNode;
class EquationNode;
class PlotCaptionNode : public AbstractVisualNode
{
public:
PlotCaptionNode(const Transform& transform = {});
void setContent(const PlotCaption& content);
private:
PlotCaption mContent;
bool mContentDirty{ true };
std::vector<std::unique_ptr<TextNode> > mTextContent;
std::vector<std::unique_ptr<EquationNode> > mEquationContent;
};

View file

@ -0,0 +1,60 @@
#include "PlotNode.h"
#include "PlotCaptionNode.h"
#include "PlotSeriesNode.h"
#include "LineNode.h"
PlotNode::PlotNode(const Transform& transform)
: AbstractVisualNode(transform)
{
}
void PlotNode::setContent(Plot* content)
{
mContent = content;
mContentDirty = true;
}
void PlotNode::update(SceneInfo* sceneInfo)
{
if (mContentDirty)
{
createOrUpdateGeometry(sceneInfo);
mContentDirty = false;
}
}
void PlotNode::setAxisEndStyle(LineEndNode::Style style)
{
if (mAxisEndStyle != style)
{
mAxisEndStyle = style;
mContentDirty = true;
}
}
void PlotNode::createOrUpdateGeometry(SceneInfo* sceneInfo)
{
const double height = 100.0;
const double width = 100.0;
Point x_axis_start{ 0.0, height };
std::vector<Point> x_axis_points = { Point(width, 0.0) };
mXAxis = std::make_unique<LineNode>(Transform(x_axis_start), x_axis_points);
mXAxis->setEndEndStyle(mAxisEndStyle);
addChild(mXAxis.get());
Point y_axis_start{ 0.0, height };
std::vector<Point> y_axis_points = { Point(0.0, -height) };
mYAxis = std::make_unique<LineNode>(Transform(y_axis_start), y_axis_points);
mYAxis->setEndEndStyle(mAxisEndStyle);
addChild(mYAxis.get());
}

View file

@ -0,0 +1,40 @@
#pragma once
#include "LineEndNode.h"
#include <vector>
class Plot;
class LineNode;
class PlotCaptionNode;
class PlotSeriesNode;
class PlotNode : public AbstractVisualNode
{
public:
PlotNode(const Transform& transform = {});
void setContent(Plot* content);
void setAxisEndStyle(LineEndNode::Style style);
void update(SceneInfo* sceneInfo) override;
protected:
void createOrUpdateGeometry(SceneInfo* sceneInfo);
Plot* mContent{ nullptr };
bool mContentDirty = true;
std::unique_ptr<LineNode> mXAxis;
std::unique_ptr<LineNode> mYAxis;
LineEndNode::Style mAxisEndStyle{ LineEndNode::Style::NONE };
std::unique_ptr<PlotCaptionNode> mXAxisCaption;
std::unique_ptr<PlotCaptionNode> mYAxisCaption;
std::vector<std::unique_ptr<PlotSeriesNode> > mSeries;
};

View file

@ -0,0 +1,13 @@
#include "PlotSeriesNode.h"
PlotSeriesNode::PlotSeriesNode(const Transform& transform)
: AbstractVisualNode(transform)
{
}
void PlotSeriesNode::setContent(const PlotSeries& content)
{
mContent = content;
mContentDirty = true;
}

View file

@ -0,0 +1,17 @@
#pragma once
#include "AbstractVisualNode.h"
#include "Plot.h"
class PlotSeriesNode : public AbstractVisualNode
{
public:
PlotSeriesNode(const Transform& transform = {});
void setContent(const PlotSeries& content);
private:
PlotSeries mContent;
bool mContentDirty{ true };
};

View file

@ -5,11 +5,10 @@
#include "AbstractGeometricItem.h"
#include "Rectangle.h"
#include "Circle.h"
#include "Path.h"
#include "LineSegment.h"
#include "Line.h"
#include "Path.h"
#include "Polygon.h"
#include "Curve.h"
#include "Arc.h"
@ -61,6 +60,10 @@ void DirectX2dPainter::paint(SceneModel* model)
{
paintLine(model);
}
else if (model->getGeometry()->getType() == AbstractGeometricItem::Type::POLYGON)
{
paintPolygon(model);
}
}
void DirectX2dPainter::paintRect(SceneModel* model)
@ -158,6 +161,54 @@ void DirectX2dPainter::paintLine(SceneModel* model)
onLine(line, path_sink.Get());
path_sink->EndFigure(D2D1_FIGURE_END_OPEN);
path_sink->Close();
auto rt = mD2dInterface->getRenderTarget();
const auto loc = model->getTransform().getLocation();
D2D1_MATRIX_3X2_F translation = D2D1::Matrix3x2F::Translation(static_cast<float>(loc.getX()), static_cast<float>(loc.getY()));
rt->SetTransform(translation);
const auto material = model->getSolidMaterial();
if (material.hasFillColor())
{
mSolidBrush->SetColor(toD2dColor(material.getFillColor()));
rt->FillGeometry(path_geom.Get(), mSolidBrush.Get());
}
if (material.hasStrokeColor())
{
mSolidBrush->SetColor(toD2dColor(material.getStrokeColor()));
rt->DrawGeometry(path_geom.Get(), mSolidBrush.Get(), 1.0f);
}
rt->SetTransform(D2D1::Matrix3x2F::Identity());
}
void DirectX2dPainter::paintPolygon(SceneModel* model)
{
Microsoft::WRL::ComPtr<ID2D1PathGeometry> path_geom;
mD2dInterface->getFactory()->CreatePathGeometry(&path_geom);
Microsoft::WRL::ComPtr<ID2D1GeometrySink> path_sink;
path_geom->Open(&path_sink);
auto polygon = dynamic_cast<ntk::Polygon*>(model->getGeometry());
path_sink->BeginFigure(toD2dPoint(polygon->getLocation()), D2D1_FIGURE_BEGIN_FILLED);
bool first{ true };
for (const auto& point : polygon->getPoints().getPoints())
{
if (first)
{
first = false;
continue;
}
path_sink->AddLine(toD2dPoint(point));
}
path_sink->EndFigure(D2D1_FIGURE_END_CLOSED);
path_sink->Close();

View file

@ -52,6 +52,8 @@ private:
void paintLine(SceneModel* model);
void paintPolygon(SceneModel* model);
static D2D1::ColorF toD2dColor(const Color& color);
static D2D_POINT_2F toD2dPoint(const Point& point);

View file

@ -7,6 +7,8 @@ list(APPEND visual_elements_LIB_INCLUDES
basic_shapes/CircleNode.cpp
basic_shapes/LineNode.h
basic_shapes/LineNode.cpp
basic_shapes/PolygonNode.h
basic_shapes/PolygonNode.cpp
scene/Scene.h
scene/Scene.cpp
scene/SceneInfo.h
@ -40,6 +42,8 @@ list(APPEND visual_elements_LIB_INCLUDES
nodes/PathNode.cpp
nodes/ImageNode.h
nodes/ImageNode.cpp
nodes/LineEndNode.h
nodes/LineEndNode.cpp
nodes/MeshNode.h
nodes/MeshNode.cpp
nodes/TextNode.h

View file

@ -20,6 +20,48 @@ void LineNode::createOrUpdateGeometry(SceneInfo* sceneInfo)
{
auto line = std::make_unique<Line>(Point{ 0, 0 }, mPoints);
mBackgroundItem = std::make_unique<SceneModel>(std::move(line));
if (mStartEndStyle != LineEndNode::Style::NONE)
{
if (!mStartNode)
{
mStartNode = std::make_unique<LineEndNode>(Point{ 0, 0 });
addChild(mStartNode.get());
}
mStartNode->setStyle(mStartEndStyle);
mStartNode->setSize(mStartEndSize);
}
if (mEndEndStyle != LineEndNode::Style::NONE)
{
if (!mEndNode)
{
auto end_loc = mPoints[mPoints.size() - 1];
mEndNode = std::make_unique<LineEndNode>(end_loc);
addChild(mEndNode.get());
}
mEndNode->setStyle(mEndEndStyle);
mEndNode->setSize(mEndEndSize);
}
}
mBackgroundItem->setName(mName + "_Model");
}
void LineNode::setStartEndStyle(LineEndNode::Style style)
{
if (mStartEndStyle != style)
{
mStartEndStyle = style;
mGeometryIsDirty = true;
}
}
void LineNode::setEndEndStyle(LineEndNode::Style style)
{
if (mStartEndStyle != style)
{
mEndEndStyle = style;
mGeometryIsDirty = true;
}
}

View file

@ -4,6 +4,7 @@
#include "SceneInfo.h"
#include "Line.h"
#include "LineEndNode.h"
#include <vector>
@ -14,8 +15,21 @@ public:
Type getType() override;
void setStartEndStyle(LineEndNode::Style style);
void setEndEndStyle(LineEndNode::Style style);
private:
void createOrUpdateGeometry(SceneInfo* sceneInfo) override;
double mStartEndSize{ 5.0 };
double mEndEndSize{ 5.0 };
LineEndNode::Style mStartEndStyle{ LineEndNode::Style::NONE };
LineEndNode::Style mEndEndStyle{ LineEndNode::Style::NONE };
std::vector<Point> mPoints;
std::unique_ptr<LineEndNode> mStartNode;
std::unique_ptr<LineEndNode> mEndNode;
};

View file

@ -0,0 +1,53 @@
#include "PolygonNode.h"
#include "Polygon.h"
#include "SceneInfo.h"
#include "SceneModel.h"
PolygonNode::PolygonNode(const Transform& t)
: GeometryNode(t)
{
}
PolygonNode::~PolygonNode()
{
}
PolygonNode::Type PolygonNode::getType()
{
return Type::Polygon;
}
void PolygonNode::setPoints(const std::vector<Point>& points)
{
mPoints = points;
mGeometryIsDirty = true;
}
void PolygonNode::createOrUpdateGeometry(SceneInfo* sceneInfo)
{
if (!mBackgroundItem)
{
if (sceneInfo->mSupportsGeometryPrimitives)
{
auto polygon = std::make_unique<ntk::Polygon>(mPoints);
mBackgroundItem = std::make_unique<SceneModel>(std::move(polygon));
}
else
{
//auto mesh = MeshPrimitives::buildRectangleAsTriMesh();
//mBackgroundItem = std::make_unique<SceneModel>(std::move(mesh));
}
mBackgroundItem->setName(mName + "_Model");
}
else
{
if (sceneInfo->mSupportsGeometryPrimitives)
{
auto polygon = std::make_unique<ntk::Polygon>(mPoints);
mBackgroundItem->updateGeometry(std::move(polygon));
}
}
}

View file

@ -0,0 +1,19 @@
#pragma once
#include "GeometryNode.h"
class PolygonNode : public GeometryNode
{
public:
PolygonNode(const Transform& t = {});
virtual ~PolygonNode();
Type getType() override;
void setPoints(const std::vector<Point>& points);
private:
void createOrUpdateGeometry(SceneInfo* sceneInfo) override;
std::vector<Point> mPoints;
};

View file

@ -14,7 +14,8 @@ public:
Rectangle,
Circle,
Arc,
Line
Line,
Polygon
};
public:

View file

@ -0,0 +1,63 @@
#include "LineEndNode.h"
#include "GeometryNode.h"
#include "PolygonNode.h"
LineEndNode::LineEndNode(const Transform& t)
: MaterialNode(t)
{
}
void LineEndNode::setStyle(LineEndNode::Style style)
{
if (mStyle != style)
{
mStyle = style;
mContentDirty = true;
}
}
void LineEndNode::setSize(double size)
{
if (mSize != size)
{
mSize = size;
mContentDirty = true;
}
}
void LineEndNode::createOrUpdateGeometry(SceneInfo* sceneInfo)
{
if (!mContentNode)
{
if (mStyle == Style::CLOSED_ARROW)
{
auto polygon = std::make_unique<PolygonNode>();
auto p0 = Point(0.0, -mSize / 2.0);
auto p1 = Point(mSize, 0.0);
auto p2 = Point(0.0, mSize / 2.0);
polygon->setPoints({ p0, p1, p2 });
polygon->setFillColor({ 0, 0, 0 });
mContentNode = std::move(polygon);
addChild(mContentNode.get());
}
}
}
void LineEndNode::update(SceneInfo* sceneInfo)
{
if (mContentDirty)
{
createOrUpdateGeometry(sceneInfo);
mContentDirty = false;
}
if (mMaterialIsDirty)
{
//updateMaterial();
mMaterialIsDirty = false;
}
}

View file

@ -0,0 +1,38 @@
#pragma once
#include "MaterialNode.h"
#include "Vector.h"
class GeometryNode;
class LineEndNode : public MaterialNode
{
public:
enum class Style
{
NONE,
CIRCLE,
CLOSED_ARROW,
OPEN_ARROW,
CURVED_ARROW
};
LineEndNode(const Transform& t = {});
void setStyle(LineEndNode::Style style);
void setSize(double size);
void update(SceneInfo* sceneInfo) override;
private:
void createOrUpdateGeometry(SceneInfo* sceneInfo);
std::unique_ptr<GeometryNode> mContentNode;
Vector mDirection;
bool mContentDirty{ true };
double mSize{ 5.0 };
Style mStyle{ Style::NONE };
};

View file

@ -17,6 +17,7 @@
#include "Path.h"
#include "Line.h"
#include "LineSegment.h"
#include "Polygon.h"
#include "SvgShapeElements.h"
#include "SvgTextElement.h"
@ -149,6 +150,21 @@ void SvgPainter::paintPrimitive(SvgDocument* document, SceneModel* model) const
{
paintLineSegment(document, model);
}
else if (model->getGeometry()->getType() == AbstractGeometricItem::Type::POLYGON)
{
paintPolygon(document, model);
}
}
void SvgPainter::paintPolygon(SvgDocument* document, SceneModel* model) const
{
auto model_polygon = dynamic_cast<ntk::Polygon*>(model->getGeometry());
auto svg_polygon = std::make_unique<SvgPolygon>();
svg_polygon->setPoints(model_polygon->getPoints().getPoints());
setStyle(model, svg_polygon.get());
document->getRoot()->addChild(std::move(svg_polygon));
}
void SvgPainter::paintRect(SvgDocument* document, SceneModel* model) const

View file

@ -32,6 +32,8 @@ private:
void paintPath(SvgDocument* document, SceneModel* model) const;
void paintPolygon(SvgDocument* document, SceneModel* model) const;
void paintText(SvgDocument* document, SceneText* model) const;
void setStyle(SceneModel* model, SvgShapeElement* element) const;

View file

@ -2,6 +2,7 @@ add_subdirectory(test_utils)
add_subdirectory(geometry)
add_subdirectory(graphics)
add_subdirectory(publishing)
add_subdirectory(ui_controls)
file(COPY data/ DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_data)
@ -16,8 +17,7 @@ set(TEST_MODULES
image
ipc
network
mesh
publishing
mesh
video
web
windows)

View file

@ -6,6 +6,7 @@
#include "CircleNode.h"
#include "LineNode.h"
#include "PathNode.h"
#include "PolygonNode.h"
void addRect(const Point& loc, std::vector<std::unique_ptr<MaterialNode> >& nodes, double radius = 0.0)
{
@ -43,6 +44,17 @@ void addPath(const Point& loc, const std::string& path, std::vector<std::unique_
nodes.push_back(std::move(node));
}
void addPolygon(const Point& loc, std::vector<std::unique_ptr<MaterialNode> >& nodes)
{
auto p0 = Point{ 0.0, 0.0 };
auto p1 = Point{ 150.0, 0.0 };
auto p2 = Point{ 75.0, 75.0 };
auto node = std::make_unique<PolygonNode>(loc);
node->setPoints({ p0, p1, p2 });
nodes.push_back(std::move(node));
}
void addShapes(const Point& start_loc, std::vector<std::unique_ptr<MaterialNode> >& nodes, bool use_fill = false)
{
auto loc = start_loc;
@ -57,6 +69,9 @@ void addShapes(const Point& start_loc, std::vector<std::unique_ptr<MaterialNode>
loc.move(100, 0);
addLine(loc, nodes);
loc.move(200, 0);
addPolygon(loc, nodes);
loc = Point(10, 150);
addRect(loc, nodes, 10.0);

View file

@ -1,13 +1,16 @@
set(PUBLISHING_UNIT_TEST_FILES
publishing/TestPdfWriter.cpp
publishing/TestDocumentConverter.cpp
publishing/TestSvgConverter.cpp
publishing/TestSvgToNodeConverter.cpp
publishing/TestLatexConverter.cpp
PARENT_SCOPE
)
set(PUBLISHING_UNIT_TEST_DEPENDENCIES
publishing
PARENT_SCOPE
)
set(MODULE_NAME publishing)
list(APPEND UNIT_TEST_FILES
TestPdfWriter.cpp
TestDocumentConverter.cpp
TestSvgConverter.cpp
TestSvgToNodeConverter.cpp
TestLatexConverter.cpp
TestPlotting.cpp
)
set(UNIT_TEST_TARGET_NAME ${MODULE_NAME}_unit_tests)
add_executable(${UNIT_TEST_TARGET_NAME} ${CMAKE_SOURCE_DIR}/test/test_runner.cpp ${UNIT_TEST_FILES})
target_link_libraries(${UNIT_TEST_TARGET_NAME} PUBLIC test_utils publishing)
set_property(TARGET ${UNIT_TEST_TARGET_NAME} PROPERTY FOLDER test/${MODULE_NAME})

View file

@ -0,0 +1,33 @@
#include "TestFramework.h"
#include "TestUtils.h"
#include "TestRenderUtils.h"
#include "Plot.h"
#include "PlotNode.h"
TEST_CASE(TestPlotting, "[publishing]")
{
auto plot = std::make_unique<Plot>();
plot->setXAxisCaption("X Axis");
plot->setYAxisCaption("Y Axis");
PlotSeries series("Series-1");
std::vector<Point> data{ {0.0, 0.0}, {10.0, 40.0}, {20.0, 80.0} };
series.setData(data);
plot->addSeries(series);
Point loc(10, 10);
auto plot_node = std::make_unique<PlotNode>(Transform(loc));
plot_node->setAxisEndStyle(LineEndNode::Style::CLOSED_ARROW);
plot_node->setContent(plot.get());
TestRenderer renderer(800, 800);
auto scene = renderer.getScene();
scene->addNode(plot_node.get());
renderer.writeSvg(TestUtils::getTestOutputDir(__FILE__) / "plot.svg");
renderer.write(TestUtils::getTestOutputDir(__FILE__) / "plot.png");
}