Skip to content

Commit

Permalink
Add car shape
Browse files Browse the repository at this point in the history
  • Loading branch information
Blake-Madden committed Oct 6, 2023
1 parent 932700c commit c70f722
Show file tree
Hide file tree
Showing 7 changed files with 206 additions and 11 deletions.
21 changes: 13 additions & 8 deletions demo/demo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ wxMenuBar* MyFrame::CreateMainMenubar()
fileMenu->Append(MyApp::ID_NEW_BARCHART_IMAGE, _(L"Bar Chart (Commom Image)"));
fileMenu->Append(MyApp::ID_NEW_CATEGORICAL_BARCHART, _(L"Bar Chart (Categorical Data)"));
fileMenu->Append(MyApp::ID_NEW_CATEGORICAL_BARCHART_GROUPED, _(L"Bar Chart (Categorical Data, Grouped)"));
fileMenu->Append(MyApp::ID_NEW_CATEGORICAL_BARCHART_STIPPLED, _(L"Bar Chart (Stipple Brush)"));
fileMenu->Append(MyApp::ID_NEW_CATEGORICAL_BARCHART_STIPPLED, _(L"Bar Chart (Stipple Icon)"));
fileMenu->Append(MyApp::ID_NEW_PIECHART, _(L"Pie Chart"));
fileMenu->Append(MyApp::ID_NEW_PIECHART_GROUPED, _(L"Pie Chart (with Subgroup)"));
fileMenu->Append(MyApp::ID_NEW_DONUTCHART, _(L"Donut Chart"));
Expand Down Expand Up @@ -1050,10 +1050,10 @@ void MyFrame::OnNewWindow(wxCommandEvent& event)
IncludeHeader(true).
PlacementHint(LegendCanvasPlacementHint::RightOfGraph)) );
}
// Bar Chart using a stippled brush
// Bar Chart using a stipple icon
else if (event.GetId() == MyApp::ID_NEW_CATEGORICAL_BARCHART_STIPPLED)
{
subframe->SetTitle(_(L"Bar Chart (Stipple Brush)"));
subframe->SetTitle(_(L"Bar Chart (Stipple Icon)"));
subframe->m_canvas->SetFixedObjectsGridSize(1, 1);
auto mpgData = std::make_shared<Data::Dataset>();
try
Expand All @@ -1074,9 +1074,15 @@ void MyFrame::OnNewWindow(wxCommandEvent& event)
auto plot = std::make_shared<CategoricalBarChart>(subframe->m_canvas);

plot->SetData(mpgData, L"manufacturer");
plot->SetStippleBrush(wxBitmapBundle::FromSVGFile(appDir + L"/res/tobias_Blue_Twingo.svg",

plot->SetBarEffect(BoxEffect::StippleShape);
plot->SetStippleShape(Icons::IconShape::Car);
plot->SetStippleShapeColor(wxColour(29, 29, 37));

// do this to use an image instead of a built-in vector icon:
/* plot->SetStippleBrush(wxBitmapBundle::FromSVGFile(appDir + L"/res/tobias_Blue_Twingo.svg",
Image::GetSVGSize(appDir + L"/res/tobias_Blue_Twingo.svg")));
plot->SetBarEffect(BoxEffect::StippleImage);
plot->SetBarEffect(BoxEffect::StippleImage);*/

subframe->m_canvas->SetFixedObject(0, 0, plot);
}
Expand Down Expand Up @@ -2050,10 +2056,9 @@ void MyFrame::InitToolBar(wxToolBar* toolBar)
toolBar->AddTool(MyApp::ID_NEW_CATEGORICAL_BARCHART_GROUPED, _(L"Bar Chart (Categorical Data, Grouped)"),
wxBitmapBundle::FromSVGFile(appDir + L"/res/barchart.svg", iconSize),
_(L"Bar Chart (Categorical Data, Grouped)"));
toolBar->AddTool(MyApp::ID_NEW_CATEGORICAL_BARCHART_STIPPLED, _(L"Bar Chart (Stipple Brush)"),
toolBar->AddTool(MyApp::ID_NEW_CATEGORICAL_BARCHART_STIPPLED, _(L"Bar Chart (Stipple Icon)"),
wxBitmapBundle::FromSVGFile(appDir + L"/res/barchart.svg", iconSize),
_(L"Bar Chart (Stipple Brush)"));

_(L"Bar Chart (Stipple Icon)"));

toolBar->AddTool(MyApp::ID_NEW_PIECHART, _(L"Pie Chart"),
wxBitmapBundle::FromSVGFile(appDir + L"/res/piechart.svg", iconSize),
Expand Down
1 change: 1 addition & 0 deletions docs/syntax_manual/34-Graphs.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ Properties common to all graphs:
- `"tire"`
- `"snowflake"`
- `"newspaper"`
- `"car"`
- `"line-scheme"`: For graphs that support line schemes only. \
This is an array of line specifications (which can be recycled). \
Each specification contains the following:
Expand Down
3 changes: 2 additions & 1 deletion src/base/icons.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ namespace Wisteria::Icons
Book, /*!< A textbook.*/
Tire, /*!< A car tire.*/
Snowflake, /*!< A snowflake.*/
Newspaper /*!< A newspaper.*/
Newspaper, /*!< A newspaper.*/
Car /*!< A car (specifically, a 2006 Scion xB).*/
};

/// @brief Item to draw on a legend.
Expand Down
3 changes: 2 additions & 1 deletion src/base/reportenumconvert.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ namespace Wisteria
{ L"book", Icons::IconShape::Book },
{ L"tire", Icons::IconShape::Tire },
{ L"snowflake", Icons::IconShape::Snowflake },
{ L"newspaper", Icons::IconShape::Newspaper }
{ L"newspaper", Icons::IconShape::Newspaper },
{ L"car", Icons::IconShape::Car }
};

const auto foundPos = iconEnums.find(std::wstring_view(iconStr.MakeLower().wc_str()));
Expand Down
181 changes: 180 additions & 1 deletion src/base/shapes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,9 @@ namespace Wisteria::GraphItems
case IconShape::Newspaper:
m_drawFunction = &ShapeRenderer::DrawNewspaper;
break;
case IconShape::Car:
m_drawFunction = &ShapeRenderer::DrawCar;
break;
default:
m_drawFunction = nullptr;
break;
Expand Down Expand Up @@ -1246,6 +1249,176 @@ namespace Wisteria::GraphItems
);
}

//---------------------------------------------------
void ShapeRenderer::DrawCar(const wxRect rect, wxDC& dc) const
{
// just to reset when we are done
wxDCPenChanger pc(dc, *wxBLACK_PEN);
wxDCBrushChanger bc(dc, *wxBLACK_BRUSH);

wxRect dcRect(rect);
dcRect.Deflate(
GetGraphItemInfo().GetPen().IsOk() ?
ScaleToScreenAndCanvas(GetGraphItemInfo().GetPen().GetWidth()) :
0);
// adjust to center it horizontally inside of square area
if (rect.GetWidth() == rect.GetHeight())
{
const auto adjustedHeight{ dcRect.GetHeight() * .75 };
const auto adjustTop{ (dcRect.GetHeight() - adjustedHeight) * math_constants::half };
dcRect.SetHeight(adjustedHeight);
dcRect.Offset(wxPoint(0, adjustTop));
}

GraphicsContextFallback gcf{ &dc, dcRect };
auto gc = gcf.GetGraphicsContext();
assert(gc && L"Failed to get graphics context for car icon!");
if (gc != nullptr)
{
const wxPen outlinePen(wxTransparentColour, ScaleToScreenAndCanvas(1));

const auto bodyBrush = gc->CreateLinearGradientBrush(
GetXPosFromLeft(dcRect, -math_constants::full), GetYPosFromTop(dcRect, math_constants::half),
GetXPosFromLeft(dcRect, math_constants::full), GetYPosFromTop(dcRect, math_constants::half),
ApplyParentColorOpacity(ColorContrast::ShadeOrTint(ColorBrewer::GetColor(Color::SmokyBlack))),
ApplyParentColorOpacity(GetGraphItemInfo().GetBrush().GetColour()));
gc->SetPen(outlinePen);
gc->SetBrush(bodyBrush);
// body of car
wxRect bodyRect{ dcRect };
bodyRect.Deflate(ScaleToScreenAndCanvas(1));
bodyRect.SetHeight(bodyRect.GetHeight() * .35);
bodyRect.Offset(0, dcRect.GetHeight() - (bodyRect.GetHeight() * 1.5));
// lower half (bumper area)
wxRect lowerBodyRect{ bodyRect };
lowerBodyRect.SetTop(lowerBodyRect.GetTop() + (lowerBodyRect.GetHeight() / 2));
lowerBodyRect.SetHeight(lowerBodyRect.GetHeight() / 2);

// upper half (headlights area)
// (this is drawn later, after the top area of the car so that it
// covers up any seams)
const double backBumperOffset = bodyRect.GetWidth() * 0.025;
wxRect upperBodyRect{ bodyRect };
upperBodyRect.SetWidth(upperBodyRect.GetWidth() * 0.95);
upperBodyRect.Offset(wxPoint(backBumperOffset, 0));

// top of car
wxRect carTopRect{ bodyRect };
carTopRect.SetWidth(carTopRect.GetWidth() * 0.65);
carTopRect.SetTop(bodyRect.GetTop() - carTopRect.GetHeight() + ScaleToScreenAndCanvas(2));
carTopRect.SetHeight(carTopRect.GetHeight() + ScaleToScreenAndCanvas(2));
carTopRect.Offset(wxPoint(backBumperOffset, 0));
gc->DrawRoundedRectangle(
carTopRect.GetX(), carTopRect.GetY(), carTopRect.GetWidth(), carTopRect.GetHeight(),
ScaleToScreenAndCanvas(2));

// windshield
std::array<wxPoint2DDouble, 4> windshieldSection =
{
carTopRect.GetTopRight(),
wxPoint(carTopRect.GetRight() +
(bodyRect.GetWidth() - carTopRect.GetWidth()) * math_constants::third,
carTopRect.GetBottom()),
wxPoint(carTopRect.GetBottomRight().x - (carTopRect.GetWidth() / 4),
carTopRect.GetBottomRight().y),
wxPoint(carTopRect.GetTopRight().x - (carTopRect.GetWidth() / 4),
carTopRect.GetTopRight().y)
};
std::array<wxPoint2DDouble, 2> windshield =
{
wxPoint(windshieldSection[0].m_x, windshieldSection[0].m_y + ScaleToScreenAndCanvas(1)),
windshieldSection[1]
};
auto windshieldAreaPath = gc->CreatePath();
windshieldAreaPath.MoveToPoint(windshieldSection[0]);
windshieldAreaPath.AddLineToPoint(windshieldSection[1]);
windshieldAreaPath.AddLineToPoint(windshieldSection[2]);
windshieldAreaPath.AddLineToPoint(windshieldSection[3]);
gc->DrawPath(windshieldAreaPath);

gc->SetPen(wxPenInfo(ColorBrewer::GetColor(Colors::Color::DarkGray),
ScaleToScreenAndCanvas(1)).Cap(wxCAP_BUTT));
auto windshieldPath = gc->CreatePath();
windshieldPath.MoveToPoint(windshield[0]);
windshieldPath.AddLineToPoint(windshield[1]);
gc->StrokePath(windshieldPath);
gc->SetPen(outlinePen);

// side windows
auto windowBrush = gc->CreateLinearGradientBrush(
GetXPosFromLeft(dcRect, -math_constants::half), GetYPosFromTop(dcRect, math_constants::half),
GetXPosFromLeft(dcRect, math_constants::three_quarters), GetYPosFromTop(dcRect, math_constants::half),
ApplyParentColorOpacity(ColorBrewer::GetColor(Color::SmokyBlack)),
ApplyParentColorOpacity(ColorBrewer::GetColor(Color::DarkGray)));
gc->SetBrush(windowBrush);
auto sideWindowPath = gc->CreatePath();
sideWindowPath.MoveToPoint(
wxPoint2DDouble(windshield[0].m_x - ScaleToScreenAndCanvas(2),
windshield[0].m_y + ScaleToScreenAndCanvas(1)));
sideWindowPath.AddLineToPoint(
wxPoint2DDouble(windshield[1].m_x - ScaleToScreenAndCanvas(2),
windshield[1].m_y + ScaleToScreenAndCanvas(1)));
sideWindowPath.AddLineToPoint(
wxPoint2DDouble(upperBodyRect.GetX() + ScaleToScreenAndCanvas(2),
windshield[1].m_y + ScaleToScreenAndCanvas(1)));
sideWindowPath.AddLineToPoint(
wxPoint2DDouble(upperBodyRect.GetX() + ScaleToScreenAndCanvas(2),
windshield[0].m_y + ScaleToScreenAndCanvas(1)));
gc->FillPath(sideWindowPath);
gc->SetBrush(bodyBrush);

// divider between windows
gc->SetPen(wxPen(GetGraphItemInfo().GetBrush().GetColour(), ScaleToScreenAndCanvas(1)));
gc->SetBrush(*wxTRANSPARENT_BRUSH);
auto windowRect{ carTopRect };
windowRect.SetWidth(windowRect.GetWidth() * 0.4);
windowRect.Offset(wxPoint(carTopRect.GetWidth() * 0.2, ScaleToScreenAndCanvas(2)));
gc->StrokeLine(windowRect.GetX(), windowRect.GetY(),
windowRect.GetX(), windowRect.GetY() + windowRect.GetHeight());
gc->StrokeLine(windowRect.GetX() + windowRect.GetWidth(), windowRect.GetY(),
windowRect.GetX() + windowRect.GetWidth(), windowRect.GetY() + windowRect.GetHeight());
gc->SetBrush(bodyBrush);
gc->SetPen(outlinePen);

// draw upper body part on top of windshield
gc->DrawRoundedRectangle(
upperBodyRect.GetX(), upperBodyRect.GetY(), upperBodyRect.GetWidth(), upperBodyRect.GetHeight(),
ScaleToScreenAndCanvas(2));

// headlights
auto headlightsRect{ upperBodyRect };
headlightsRect.SetWidth(headlightsRect.GetWidth() * .05);
headlightsRect.SetHeight(headlightsRect.GetHeight() * .25);
headlightsRect.Offset(upperBodyRect.GetWidth() - headlightsRect.GetWidth(),
upperBodyRect.GetHeight() * .25);
gc->SetBrush(gc->CreateLinearGradientBrush(headlightsRect.GetLeft(), headlightsRect.GetTop() / 2,
headlightsRect.GetRight(), headlightsRect.GetTop() / 2,
ApplyParentColorOpacity(ColorBrewer::GetColor(Color::OrangeYellow)),
ApplyParentColorOpacity(ColorBrewer::GetColor(Color::AntiqueWhite))));
gc->DrawRectangle(headlightsRect.GetX(), headlightsRect.GetY(),
headlightsRect.GetWidth(), headlightsRect.GetHeight());
gc->SetBrush(bodyBrush);

// draw bumper area now, to overlay any headlight overlap
gc->DrawRoundedRectangle(
lowerBodyRect.GetX(), lowerBodyRect.GetY(), lowerBodyRect.GetWidth(), lowerBodyRect.GetHeight(),
ScaleToScreenAndCanvas(2));

// the tires
wxRect tireRect{ dcRect };
tireRect.SetWidth(dcRect.GetWidth() * .25);
tireRect.SetHeight(tireRect.GetWidth());
tireRect.SetTop(dcRect.GetTop() + (dcRect.GetHeight() - tireRect.GetHeight()));
tireRect.SetLeft(dcRect.GetLeft() + dcRect.GetWidth() * math_constants::tenth);

DrawTire(tireRect, gc);

tireRect.SetLeft((dcRect.GetRight() - tireRect.GetWidth()) -
dcRect.GetWidth() * math_constants::tenth);
DrawTire(tireRect, gc);
}
}

//---------------------------------------------------
void ShapeRenderer::DrawTire(const wxRect rect, wxDC& dc) const
{
Expand All @@ -1255,7 +1428,13 @@ namespace Wisteria::GraphItems

GraphicsContextFallback gcf{ &dc, rect };
auto gc = gcf.GetGraphicsContext();
assert(gc && L"Failed to get graphics context for asterisk icon!");
assert(gc && L"Failed to get graphics context for tire icon!");
DrawTire(rect, gc);
}

//---------------------------------------------------
void ShapeRenderer::DrawTire(wxRect rect, wxGraphicsContext* gc) const
{
if (gc != nullptr)
{
wxPen scaledPen(ColorBrewer::GetColor(Colors::Color::DarkGray),
Expand Down
5 changes: 5 additions & 0 deletions src/base/shapes.h
Original file line number Diff line number Diff line change
Expand Up @@ -290,9 +290,14 @@ namespace Wisteria::GraphItems
/// @param rect The area to draw the image within.
/// @param dc The DC to draw to.
void DrawNewspaper(wxRect rect, wxDC& dc) const;
/// @brief Draws a car.
/// @param rect The area to draw the image within.
/// @param dc The DC to draw to.
void DrawCar(wxRect rect, wxDC& dc) const;
/// @}
private:
void DrawAsterisk(wxRect rect, wxGraphicsContext* gc) const;
void DrawTire(wxRect rect, wxGraphicsContext* gc) const;
/// @brief Sets the base color (if in use), performs the provided rendering lambda,
/// sets the brush, then runs the rendering lambda again.
void DrawWithBaseColorAndBrush(wxDC& dc, const std::function<void(void)>& fn) const;
Expand Down
3 changes: 3 additions & 0 deletions src/graphs/barchart.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,9 @@ namespace Wisteria::Graphs
GetStippleShape() == Icons::IconShape::Woman ||
GetStippleShape() == Icons::IconShape::Man)
{ shapeWidth *= 0.6; }
// likewise, handle icons that are wider than others
if (GetStippleShape() == Icons::IconShape::Car)
{ shapeWidth *= 1.25; }
auto currentXLeft = lineXStart;
while (currentXLeft < (lineXStart + barLength))
{
Expand Down

0 comments on commit c70f722

Please sign in to comment.