LCOV - code coverage report
Current view: top level - source/gui/ObjectTypes - CChart.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 0 122 0.0 %
Date: 2023-01-19 00:18:29 Functions: 0 12 0.0 %

          Line data    Source code
       1             : /* Copyright (C) 2022 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "CChart.h"
      21             : 
      22             : #include "graphics/Canvas2D.h"
      23             : #include "gui/SettingTypes/CGUIList.h"
      24             : #include "gui/SettingTypes/CGUISeries.h"
      25             : #include "gui/SettingTypes/CGUIString.h"
      26             : #include "ps/CLogger.h"
      27             : #include "ps/CStrInternStatic.h"
      28             : #include "ps/Profile.h"
      29             : 
      30             : #include <cmath>
      31             : 
      32           0 : CChart::CChart(CGUI& pGUI)
      33             :     : IGUIObject(pGUI),
      34             :       IGUITextOwner(*static_cast<IGUIObject*>(this)),
      35             :       m_AxisColor(this, "axis_color"),
      36             :       m_AxisWidth(this, "axis_width"),
      37             :       m_BufferZone(this, "buffer_zone"),
      38             :       m_Font(this, "font"),
      39             :       m_FormatX(this, "format_x"),
      40             :       m_FormatY(this, "format_y"),
      41             :       m_SeriesColor(this, "series_color"),
      42           0 :       m_SeriesSetting(this, "series")
      43             : {
      44           0 : }
      45             : 
      46           0 : CChart::~CChart()
      47             : {
      48           0 : }
      49             : 
      50           0 : void CChart::UpdateCachedSize()
      51             : {
      52           0 :     IGUIObject::UpdateCachedSize();
      53           0 :     IGUITextOwner::UpdateCachedSize();
      54           0 : }
      55             : 
      56           0 : void CChart::HandleMessage(SGUIMessage& Message)
      57             : {
      58           0 :     IGUIObject::HandleMessage(Message);
      59             :     // IGUITextOwner::HandleMessage(Message); performed in UpdateSeries
      60             : 
      61             :     // TODO: implement zoom
      62           0 :     if(Message.type == GUIM_SETTINGS_UPDATED)
      63           0 :         UpdateSeries();
      64           0 : }
      65             : 
      66           0 : void CChart::DrawAxes(CCanvas2D& canvas) const
      67             : {
      68           0 :     canvas.DrawRect(CRect(
      69           0 :         m_CachedActualSize.TopLeft(),
      70           0 :         m_CachedActualSize.BottomLeft() + CVector2D(m_AxisWidth, 0.0f)), m_AxisColor);
      71           0 :     canvas.DrawRect(CRect(
      72           0 :         m_CachedActualSize.BottomLeft() - CVector2D(0.0f, m_AxisWidth),
      73           0 :         m_CachedActualSize.BottomRight()), m_AxisColor);
      74           0 : }
      75             : 
      76           0 : void CChart::Draw(CCanvas2D& canvas)
      77             : {
      78           0 :     PROFILE3("render chart");
      79             : 
      80           0 :     if (m_Series.empty())
      81           0 :         return;
      82             : 
      83           0 :     CRect rect = GetChartRect();
      84           0 :     const float width = rect.GetWidth();
      85           0 :     const float height = rect.GetHeight();
      86             : 
      87           0 :     CVector2D scale(width / (m_RightTop.X - m_LeftBottom.X), height / (m_RightTop.Y - m_LeftBottom.Y));
      88           0 :     std::vector<CVector2D> linePoints;
      89           0 :     for (const CChartData& data : m_Series)
      90             :     {
      91           0 :         if (data.m_Points.empty())
      92           0 :             continue;
      93             :         
      94           0 :         linePoints.clear();
      95           0 :         for (const CVector2D& point : data.m_Points)
      96             :         {
      97           0 :             if (fabs(point.X) != std::numeric_limits<float>::infinity() && fabs(point.Y) != std::numeric_limits<float>::infinity())
      98             :             {
      99           0 :                 linePoints.emplace_back(
     100           0 :                     rect.left + (point.X - m_LeftBottom.X) * scale.X,
     101           0 :                     rect.bottom - (point.Y - m_LeftBottom.Y) * scale.Y);
     102             :             }
     103             :             else
     104             :             {
     105           0 :                 canvas.DrawLine(linePoints, 2.0f, data.m_Color);
     106           0 :                 linePoints.clear();
     107             :             }
     108             :         }
     109           0 :         if (!linePoints.empty())
     110           0 :             canvas.DrawLine(linePoints, 2.0f, data.m_Color);
     111             :     }
     112             : 
     113           0 :     if (m_AxisWidth > 0)
     114           0 :         DrawAxes(canvas);
     115             : 
     116           0 :     for (size_t i = 0; i < m_TextPositions.size(); ++i)
     117           0 :         DrawText(canvas, i, CGUIColor(1.f, 1.f, 1.f, 1.f), m_TextPositions[i]);
     118             : }
     119             : 
     120           0 : CRect CChart::GetChartRect() const
     121             : {
     122             :     return CRect(
     123           0 :         m_CachedActualSize.TopLeft() + CVector2D(m_AxisWidth, m_AxisWidth),
     124           0 :         m_CachedActualSize.BottomRight() - CVector2D(m_AxisWidth, m_AxisWidth)
     125           0 :     );
     126             : }
     127             : 
     128           0 : void CChart::UpdateSeries()
     129             : {
     130           0 :     m_Series.clear();
     131           0 :     m_Series.resize(m_SeriesSetting->m_Series.size());
     132             : 
     133           0 :     for (size_t i = 0; i < m_SeriesSetting->m_Series.size(); ++i)
     134             :     {
     135           0 :         CChartData& data = m_Series[i];
     136             : 
     137           0 :         if (i < m_SeriesColor->m_Items.size() && !data.m_Color.ParseString(m_pGUI, m_SeriesColor->m_Items[i].GetOriginalString().ToUTF8(), 0))
     138           0 :             LOGWARNING("GUI: Error parsing 'series_color' (\"%s\")", utf8_from_wstring(m_SeriesColor->m_Items[i].GetOriginalString()));
     139             : 
     140           0 :         data.m_Points = m_SeriesSetting->m_Series[i];
     141             :     }
     142           0 :     UpdateBounds();
     143             : 
     144           0 :     SetupText();
     145           0 : }
     146             : 
     147           0 : void CChart::SetupText()
     148             : {
     149           0 :     m_GeneratedTexts.clear();
     150           0 :     m_TextPositions.clear();
     151             : 
     152           0 :     if (m_Series.empty())
     153           0 :         return;
     154             : 
     155             :     // Add Y-axis
     156           0 :     const float height = GetChartRect().GetHeight();
     157             :     // TODO: split values depend on the format;
     158           0 :     if (m_EqualY)
     159             :     {
     160             :         // We don't need to generate many items for equal values
     161           0 :         AddFormattedValue(m_FormatY, m_RightTop.Y, m_Font, m_BufferZone);
     162           0 :         m_TextPositions.emplace_back(GetChartRect().TopLeft());
     163             :     }
     164             :     else
     165           0 :         for (int i = 0; i < 3; ++i)
     166             :         {
     167           0 :             AddFormattedValue(m_FormatY, m_RightTop.Y - (m_RightTop.Y - m_LeftBottom.Y) / 3.f * i, m_Font, m_BufferZone);
     168           0 :             m_TextPositions.emplace_back(GetChartRect().TopLeft() + CVector2D(0.f, height / 3.f * i));
     169             :         }
     170             : 
     171             :     // Add X-axis
     172           0 :     const float width = GetChartRect().GetWidth();
     173           0 :     if (m_EqualX)
     174             :     {
     175           0 :         CSize2D text_size = AddFormattedValue(m_FormatX, m_RightTop.X, m_Font, m_BufferZone);
     176           0 :         m_TextPositions.emplace_back(GetChartRect().BottomRight() - text_size);
     177             :     }
     178             :     else
     179           0 :         for (int i = 0; i < 3; ++i)
     180             :         {
     181           0 :             CSize2D text_size = AddFormattedValue(m_FormatX, m_RightTop.X - (m_RightTop.X - m_LeftBottom.X) / 3 * i, m_Font, m_BufferZone);
     182           0 :             m_TextPositions.emplace_back(GetChartRect().BottomRight() - text_size - CVector2D(width / 3 * i, 0.f));
     183             :         }
     184             : }
     185             : 
     186           0 : CSize2D CChart::AddFormattedValue(const CStrW& format, const float value, const CStrW& font, const float buffer_zone)
     187             : {
     188             :     // TODO: we need to catch cases with equal formatted values.
     189           0 :     CGUIString gui_str;
     190           0 :     if (format == L"DECIMAL2")
     191             :     {
     192             :         wchar_t buffer[64];
     193           0 :         swprintf(buffer, 64, L"%.2f", value);
     194           0 :         gui_str.SetValue(buffer);
     195             :     }
     196           0 :     else if (format == L"INTEGER")
     197             :     {
     198             :         wchar_t buffer[64];
     199           0 :         swprintf(buffer, 64, L"%d", std::lround(value));
     200           0 :         gui_str.SetValue(buffer);
     201             :     }
     202           0 :     else if (format == L"DURATION_SHORT")
     203             :     {
     204           0 :         const int seconds = value;
     205             :         wchar_t buffer[64];
     206           0 :         swprintf(buffer, 64, L"%d:%02d", seconds / 60, seconds % 60);
     207           0 :         gui_str.SetValue(buffer);
     208             :     }
     209           0 :     else if (format == L"PERCENTAGE")
     210             :     {
     211             :         wchar_t buffer[64];
     212           0 :         swprintf(buffer, 64, L"%d%%", std::lround(value));
     213           0 :         gui_str.SetValue(buffer);
     214             :     }
     215             :     else
     216             :     {
     217           0 :         LOGERROR("Unsupported chart format: " + format.EscapeToPrintableASCII());
     218           0 :         return CSize2D();
     219             :     }
     220             : 
     221           0 :     return AddText(gui_str, font, 0, buffer_zone).GetSize();
     222             : }
     223             : 
     224           0 : void CChart::UpdateBounds()
     225             : {
     226           0 :     if (m_Series.empty() || m_Series[0].m_Points.empty())
     227             :     {
     228           0 :         m_LeftBottom = m_RightTop = CVector2D(0.f, 0.f);
     229           0 :         return;
     230             :     }
     231             : 
     232           0 :     m_LeftBottom = m_RightTop = m_Series[0].m_Points[0];
     233           0 :     for (const CChartData& data : m_Series)
     234           0 :         for (const CVector2D& point : data.m_Points)
     235             :         {
     236           0 :             if (fabs(point.X) != std::numeric_limits<float>::infinity() && point.X < m_LeftBottom.X)
     237           0 :                 m_LeftBottom.X = point.X;
     238           0 :             if (fabs(point.Y) != std::numeric_limits<float>::infinity() && point.Y < m_LeftBottom.Y)
     239           0 :                 m_LeftBottom.Y = point.Y;
     240             : 
     241           0 :             if (fabs(point.X) != std::numeric_limits<float>::infinity() && point.X > m_RightTop.X)
     242           0 :                 m_RightTop.X = point.X;
     243           0 :             if (fabs(point.Y) != std::numeric_limits<float>::infinity() && point.Y > m_RightTop.Y)
     244           0 :                 m_RightTop.Y = point.Y;
     245             :         }
     246             : 
     247           0 :     m_EqualY = m_RightTop.Y == m_LeftBottom.Y;
     248           0 :     if (m_EqualY)
     249           0 :         m_RightTop.Y += 1;
     250           0 :     m_EqualX = m_RightTop.X == m_LeftBottom.X;
     251           0 :     if (m_EqualX)
     252           0 :         m_RightTop.X += 1;
     253           0 : }

Generated by: LCOV version 1.13