From c70f7224195da7909dd1614d7421a51be8e5fa25 Mon Sep 17 00:00:00 2001 From: Blake-Madden Date: Fri, 6 Oct 2023 12:19:58 -0400 Subject: [PATCH] Add car shape --- demo/demo.cpp | 21 ++-- docs/syntax_manual/34-Graphs.Rmd | 1 + src/base/icons.h | 3 +- src/base/reportenumconvert.h | 3 +- src/base/shapes.cpp | 181 ++++++++++++++++++++++++++++++- src/base/shapes.h | 5 + src/graphs/barchart.cpp | 3 + 7 files changed, 206 insertions(+), 11 deletions(-) diff --git a/demo/demo.cpp b/demo/demo.cpp index 387da667..395a3e08 100644 --- a/demo/demo.cpp +++ b/demo/demo.cpp @@ -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")); @@ -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(); try @@ -1074,9 +1074,15 @@ void MyFrame::OnNewWindow(wxCommandEvent& event) auto plot = std::make_shared(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); } @@ -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), diff --git a/docs/syntax_manual/34-Graphs.Rmd b/docs/syntax_manual/34-Graphs.Rmd index 427e8a43..b65b3523 100644 --- a/docs/syntax_manual/34-Graphs.Rmd +++ b/docs/syntax_manual/34-Graphs.Rmd @@ -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: diff --git a/src/base/icons.h b/src/base/icons.h index 220daf6d..35c67318 100644 --- a/src/base/icons.h +++ b/src/base/icons.h @@ -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. diff --git a/src/base/reportenumconvert.h b/src/base/reportenumconvert.h index 18887ca6..dc7b8fda 100644 --- a/src/base/reportenumconvert.h +++ b/src/base/reportenumconvert.h @@ -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())); diff --git a/src/base/shapes.cpp b/src/base/shapes.cpp index c4bfe850..fc2112d0 100644 --- a/src/base/shapes.cpp +++ b/src/base/shapes.cpp @@ -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; @@ -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 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 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 { @@ -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), diff --git a/src/base/shapes.h b/src/base/shapes.h index 257252ff..386dd3c5 100644 --- a/src/base/shapes.h +++ b/src/base/shapes.h @@ -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& fn) const; diff --git a/src/graphs/barchart.cpp b/src/graphs/barchart.cpp index b0dc50bd..51c5b970 100644 --- a/src/graphs/barchart.cpp +++ b/src/graphs/barchart.cpp @@ -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)) {