1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3
4#include "graph.h"
5
6#include <engine/graphics.h>
7#include <engine/textrender.h>
8
9#include <limits>
10
11CGraph::CGraph(int MaxEntries, int Precision, bool SummaryStats) :
12 m_Entries(MaxEntries * (sizeof(CEntry) + 2 * CRingBufferBase::ITEM_SIZE), CRingBufferBase::FLAG_RECYCLE),
13 m_Precision(Precision),
14 m_SummaryStats(SummaryStats)
15{
16}
17
18void CGraph::Init(float Min, float Max)
19{
20 m_Entries.Clear();
21 m_pFirstScaled = nullptr;
22 m_RenderedTotalTime = 0;
23 m_Average = 0.0f;
24 SetMin(Min);
25 SetMax(Max);
26}
27
28void CGraph::SetMin(float Min)
29{
30 m_MinRange = m_MinValue = m_MinAxis = Min;
31}
32
33void CGraph::SetMax(float Max)
34{
35 m_MaxRange = m_MaxValue = m_MaxAxis = Max;
36}
37
38void CGraph::Scale(int64_t WantedTotalTime)
39{
40 // Scale X axis for wanted total time
41 if(m_Entries.First() != nullptr)
42 {
43 const int64_t EndTime = m_Entries.Last()->m_Time;
44 bool ScaleTotalTime = false;
45 m_pFirstScaled = nullptr;
46
47 if(m_Entries.First()->m_Time >= EndTime - WantedTotalTime)
48 {
49 m_pFirstScaled = m_Entries.First();
50 }
51 else
52 {
53 m_pFirstScaled = m_Entries.Last();
54 while(m_pFirstScaled)
55 {
56 CEntry *pPrev = m_Entries.Prev(pCurrent: m_pFirstScaled);
57 if(pPrev == nullptr)
58 break;
59 if(pPrev->m_Time < EndTime - WantedTotalTime)
60 {
61 // Scale based on actual total time instead of based on wanted total time,
62 // to avoid flickering last segment due to rounding errors.
63 ScaleTotalTime = true;
64 break;
65 }
66 m_pFirstScaled = pPrev;
67 }
68 }
69
70 m_RenderedTotalTime = ScaleTotalTime ? (EndTime - m_pFirstScaled->m_Time) : WantedTotalTime;
71
72 // Ensure that color is applied to first line segment
73 if(m_pFirstScaled)
74 {
75 m_pFirstScaled->m_ApplyColor = true;
76 CEntry *pNext = m_Entries.Next(pCurrent: m_pFirstScaled);
77 if(pNext != nullptr)
78 {
79 pNext->m_ApplyColor = true;
80 }
81 }
82 }
83 else
84 {
85 m_pFirstScaled = nullptr;
86 m_RenderedTotalTime = 0;
87 }
88
89 // Scale Y axis
90 m_Average = 0.0f;
91 size_t NumValues = 0;
92 m_MinAxis = m_MinRange;
93 m_MaxAxis = m_MaxRange;
94 m_MinValue = std::numeric_limits<float>::max();
95 m_MaxValue = std::numeric_limits<float>::min();
96 for(CEntry *pEntry = m_pFirstScaled; pEntry != nullptr; pEntry = m_Entries.Next(pCurrent: pEntry))
97 {
98 const float Value = pEntry->m_Value;
99 m_Average += Value;
100 ++NumValues;
101
102 if(Value > m_MaxAxis)
103 {
104 m_MaxAxis = Value;
105 }
106 else if(Value < m_MinAxis)
107 {
108 m_MinAxis = Value;
109 }
110
111 if(Value > m_MaxValue)
112 {
113 m_MaxValue = Value;
114 }
115 else if(Value < m_MinValue)
116 {
117 m_MinValue = Value;
118 }
119 }
120 if(m_MaxValue < m_MinValue)
121 {
122 m_MinValue = m_MaxValue = 0.0f;
123 }
124 if(NumValues)
125 {
126 m_Average /= NumValues;
127 }
128}
129
130void CGraph::Add(float Value, ColorRGBA Color)
131{
132 InsertAt(Time: time_get(), Value, Color);
133}
134
135void CGraph::InsertAt(int64_t Time, float Value, ColorRGBA Color)
136{
137 CEntry *pEntry = m_Entries.Allocate(Size: sizeof(CEntry));
138 pEntry->m_Time = Time;
139 pEntry->m_Value = Value;
140 pEntry->m_Color = Color;
141
142 // Determine whether the line (pPrev, pEntry) has different
143 // vertex colors than the line (pPrevPrev, pPrev).
144 CEntry *pPrev = m_Entries.Prev(pCurrent: pEntry);
145 if(pPrev == nullptr)
146 {
147 pEntry->m_ApplyColor = true;
148 }
149 else
150 {
151 CEntry *pPrevPrev = m_Entries.Prev(pCurrent: pPrev);
152 if(pPrevPrev == nullptr)
153 {
154 pEntry->m_ApplyColor = true;
155 }
156 else
157 {
158 pEntry->m_ApplyColor = Color != pPrev->m_Color || pPrev->m_Color != pPrevPrev->m_Color;
159 }
160 }
161}
162
163void CGraph::RenderDataLines(IGraphics *pGraphics, float x, float y, float w, float h)
164{
165 if(m_pFirstScaled == nullptr)
166 {
167 return;
168 }
169
170 IGraphics::CLineItemBatch LineItemBatch;
171 pGraphics->LinesBatchBegin(pBatch: &LineItemBatch);
172
173 const int64_t StartTime = m_pFirstScaled->m_Time;
174
175 CEntry *pEntry0 = m_pFirstScaled;
176 int a0 = round_to_int(f: (pEntry0->m_Time - StartTime) * w / m_RenderedTotalTime);
177 int v0 = round_to_int(f: (pEntry0->m_Value - m_MinAxis) * h / (m_MaxAxis - m_MinAxis));
178 while(pEntry0 != nullptr)
179 {
180 CEntry *pEntry1 = m_Entries.Next(pCurrent: pEntry0);
181 if(pEntry1 == nullptr)
182 break;
183
184 const int a1 = round_to_int(f: (pEntry1->m_Time - StartTime) * w / m_RenderedTotalTime);
185 const int v1 = round_to_int(f: (pEntry1->m_Value - m_MinAxis) * h / (m_MaxAxis - m_MinAxis));
186
187 if(pEntry1->m_ApplyColor)
188 {
189 pGraphics->LinesBatchEnd(pBatch: &LineItemBatch);
190 pGraphics->LinesBatchBegin(pBatch: &LineItemBatch);
191
192 const IGraphics::CColorVertex aColorVertices[] = {
193 IGraphics::CColorVertex(0, pEntry0->m_Color.r, pEntry0->m_Color.g, pEntry0->m_Color.b, pEntry0->m_Color.a),
194 IGraphics::CColorVertex(1, pEntry1->m_Color.r, pEntry1->m_Color.g, pEntry1->m_Color.b, pEntry1->m_Color.a)};
195 pGraphics->SetColorVertex(pArray: aColorVertices, Num: std::size(aColorVertices));
196 }
197 const IGraphics::CLineItem Item = IGraphics::CLineItem(
198 x + a0,
199 y + h - v0,
200 x + a1,
201 y + h - v1);
202 pGraphics->LinesBatchDraw(pBatch: &LineItemBatch, pArray: &Item, Num: 1);
203
204 pEntry0 = pEntry1;
205 a0 = a1;
206 v0 = v1;
207 }
208 pGraphics->LinesBatchEnd(pBatch: &LineItemBatch);
209}
210
211void CGraph::Render(IGraphics *pGraphics, ITextRender *pTextRender, float x, float y, float w, float h, const char *pDescription)
212{
213 const float FontSize = 12.0f;
214 const float Spacing = 2.0f;
215 const float HeaderHeight = FontSize + 2.0f * Spacing;
216
217 pGraphics->TextureClear();
218
219 pGraphics->QuadsBegin();
220 pGraphics->SetColor(ColorRGBA(0.0f, 0.0f, 0.0f, 0.7f));
221 const IGraphics::CQuadItem ItemHeader(x, y, w, HeaderHeight);
222 pGraphics->QuadsDrawTL(pArray: &ItemHeader, Num: 1);
223 const IGraphics::CQuadItem ItemFooter(x, y + h - HeaderHeight, w, HeaderHeight);
224 pGraphics->QuadsDrawTL(pArray: &ItemFooter, Num: 1);
225 pGraphics->SetColor(ColorRGBA(0.0f, 0.0f, 0.0f, 0.6f));
226 const IGraphics::CQuadItem ItemGraph(x, y + HeaderHeight, w, h - 2.0f * HeaderHeight);
227 pGraphics->QuadsDrawTL(pArray: &ItemGraph, Num: 1);
228 pGraphics->QuadsEnd();
229
230 pGraphics->LinesBegin();
231 pGraphics->SetColor(ColorRGBA(0.5f, 0.5f, 0.5f, 0.75f));
232 const IGraphics::CLineItem aLineItems[] = {
233 IGraphics::CLineItem(ItemGraph.m_X, ItemGraph.m_Y + (ItemGraph.m_Height * 3) / 4, ItemGraph.m_X + ItemGraph.m_Width, ItemGraph.m_Y + (ItemGraph.m_Height * 3) / 4),
234 IGraphics::CLineItem(ItemGraph.m_X, ItemGraph.m_Y + ItemGraph.m_Height / 2, ItemGraph.m_X + ItemGraph.m_Width, ItemGraph.m_Y + ItemGraph.m_Height / 2),
235 IGraphics::CLineItem(ItemGraph.m_X, ItemGraph.m_Y + ItemGraph.m_Height / 4, ItemGraph.m_X + ItemGraph.m_Width, ItemGraph.m_Y + ItemGraph.m_Height / 4)};
236 pGraphics->LinesDraw(pArray: aLineItems, Num: std::size(aLineItems));
237 pGraphics->LinesEnd();
238
239 RenderDataLines(pGraphics, x: ItemGraph.m_X, y: ItemGraph.m_Y + 1.0f, w: ItemGraph.m_Width, h: ItemGraph.m_Height + 2.0f);
240
241 char aBuf[128];
242
243 pTextRender->Text(x: ItemHeader.m_X + Spacing, y: ItemHeader.m_Y + Spacing, Size: FontSize, pText: pDescription);
244
245 if(m_SummaryStats)
246 {
247 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Avg. %.*f ↓ %.*f ↑ %.*f", m_Precision, m_Average, m_Precision, m_MinValue, m_Precision, m_MaxValue);
248 pTextRender->Text(x: ItemFooter.m_X + Spacing, y: ItemFooter.m_Y + ItemFooter.m_Height - FontSize - Spacing, Size: FontSize, pText: aBuf);
249 }
250
251 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%.*f", m_Precision, m_MaxAxis);
252 pTextRender->Text(x: ItemHeader.m_X + ItemHeader.m_Width - pTextRender->TextWidth(Size: FontSize, pText: aBuf) - Spacing, y: ItemHeader.m_Y + Spacing, Size: FontSize, pText: aBuf);
253
254 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%.*f", m_Precision, m_MinAxis);
255 pTextRender->Text(x: ItemFooter.m_X + ItemFooter.m_Width - pTextRender->TextWidth(Size: FontSize, pText: aBuf) - Spacing, y: ItemFooter.m_Y + ItemFooter.m_Height - FontSize - Spacing, Size: FontSize, pText: aBuf);
256}
257