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