1/*
2 * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6#include "chart/LineChartRenderer.h"
7
8#include <stdio.h>
9
10#include <Shape.h>
11#include <View.h>
12
13#include "chart/ChartDataSource.h"
14
15
16// #pragma mark - DataSourceInfo
17
18
19struct LineChartRenderer::DataSourceInfo {
20public:
21			ChartDataSource*	source;
22			double*				samples;
23			int32				sampleCount;
24			bool				samplesValid;
25			LineChartRendererDataSourceConfig config;
26
27public:
28								DataSourceInfo(ChartDataSource* source,
29									LineChartRendererDataSourceConfig* config);
30								~DataSourceInfo();
31
32			bool				UpdateSamples(const ChartDataRange& domain,
33									int32 sampleCount);
34};
35
36
37LineChartRenderer::DataSourceInfo::DataSourceInfo(ChartDataSource* source,
38	LineChartRendererDataSourceConfig* config)
39	:
40	source(source),
41	samples(NULL),
42	sampleCount(0),
43	samplesValid(false)
44{
45	if (config != NULL)
46		this->config = *config;
47}
48
49
50LineChartRenderer::DataSourceInfo::~DataSourceInfo()
51{
52	delete[] samples;
53}
54
55
56bool
57LineChartRenderer::DataSourceInfo::UpdateSamples(const ChartDataRange& domain,
58	int32 sampleCount)
59{
60	if (samplesValid)
61		return true;
62
63	// check the sample count -- we might need to realloc the sample array
64	if (sampleCount != this->sampleCount) {
65		delete[] samples;
66		this->sampleCount = 0;
67
68		samples = new(std::nothrow) double[sampleCount];
69		if (samples == NULL)
70			return false;
71
72		this->sampleCount = sampleCount;
73	}
74
75	// get the new samples
76	source->GetSamples(domain.min, domain.max, samples, sampleCount);
77
78	samplesValid = true;
79	return true;
80}
81
82
83// #pragma mark - LineChartRenderer
84
85
86LineChartRenderer::LineChartRenderer()
87	:
88	fFrame(),
89	fDomain(),
90	fRange()
91{
92}
93
94
95LineChartRenderer::~LineChartRenderer()
96{
97}
98
99
100bool
101LineChartRenderer::AddDataSource(ChartDataSource* dataSource, int32 index,
102	ChartRendererDataSourceConfig* config)
103{
104	DataSourceInfo* info = new(std::nothrow) DataSourceInfo(dataSource,
105		dynamic_cast<LineChartRendererDataSourceConfig*>(config));
106	if (info == NULL)
107		return false;
108
109	if (!fDataSources.AddItem(info, index)) {
110		delete info;
111		return false;
112	}
113
114	return true;
115}
116
117
118void
119LineChartRenderer::RemoveDataSource(ChartDataSource* dataSource)
120{
121	for (int32 i = 0; DataSourceInfo* info = fDataSources.ItemAt(i); i++) {
122		info->samplesValid = false;
123		if (info->source == dataSource) {
124			delete fDataSources.RemoveItemAt(i);
125			return;
126		}
127	}
128}
129
130
131void
132LineChartRenderer::SetFrame(const BRect& frame)
133{
134	fFrame = frame;
135	_InvalidateSamples();
136}
137
138
139void
140LineChartRenderer::SetDomain(const ChartDataRange& domain)
141{
142	if (domain != fDomain) {
143		fDomain = domain;
144		_InvalidateSamples();
145	}
146}
147
148
149void
150LineChartRenderer::SetRange(const ChartDataRange& range)
151{
152	if (range != fRange) {
153		fRange = range;
154		_InvalidateSamples();
155	}
156}
157
158
159void
160LineChartRenderer::Render(BView* view, BRect updateRect)
161{
162	if (!updateRect.IsValid() || updateRect.left > fFrame.right
163		|| fFrame.left > updateRect.right) {
164		return;
165	}
166
167	if (fDomain.min >= fDomain.max || fRange.min >= fRange.max)
168		return;
169
170	if (!_UpdateSamples())
171		return;
172
173	// get the range to draw (draw one more sample on each side)
174	int32 left = (int32)fFrame.left;
175	int32 first = (int32)updateRect.left - left - 1;
176	int32 last = (int32)updateRect.right - left + 1;
177	if (first < 0)
178		first = 0;
179	if (last > fFrame.IntegerWidth())
180		last = fFrame.IntegerWidth();
181	if (first > last)
182		return;
183
184	double minRange = fRange.min;
185	double sampleRange = fRange.max - minRange;
186	if (sampleRange == 0) {
187		minRange = fRange.min - 0.5;
188		sampleRange = 1;
189	}
190	double scale = (double)fFrame.IntegerHeight() / sampleRange;
191
192	// draw
193	view->SetLineMode(B_ROUND_CAP, B_ROUND_JOIN);
194	for (int32 i = 0; DataSourceInfo* info = fDataSources.ItemAt(i); i++) {
195
196		float bottom = fFrame.bottom;
197		BShape shape;
198		shape.MoveTo(BPoint(left + first,
199			bottom - ((info->samples[first] - minRange) * scale)));
200
201		for (int32 i = first; i <= last; i++) {
202			shape.LineTo(BPoint(float(left + i),
203				float(bottom - ((info->samples[i] - minRange) * scale))));
204		}
205		view->SetHighColor(info->config.Color());
206		view->MovePenTo(B_ORIGIN);
207		view->StrokeShape(&shape);
208	}
209}
210
211
212void
213LineChartRenderer::_InvalidateSamples()
214{
215	for (int32 i = 0; DataSourceInfo* info = fDataSources.ItemAt(i); i++)
216		info->samplesValid = false;
217}
218
219
220bool
221LineChartRenderer::_UpdateSamples()
222{
223	int32 width = fFrame.IntegerWidth() + 1;
224	if (width <= 0)
225		return false;
226
227	for (int32 i = 0; DataSourceInfo* info = fDataSources.ItemAt(i); i++) {
228		if (!info->UpdateSamples(fDomain, width))
229			return false;
230	}
231
232	return true;
233}
234
235
236// #pragma mark - LineChartRendererDataSourceConfig
237
238
239LineChartRendererDataSourceConfig::LineChartRendererDataSourceConfig()
240{
241	fColor.red = 0;
242	fColor.green = 0;
243	fColor.blue = 0;
244	fColor.alpha = 255;
245}
246
247
248LineChartRendererDataSourceConfig::LineChartRendererDataSourceConfig(
249	const rgb_color& color)
250{
251	fColor = color;
252}
253
254
255LineChartRendererDataSourceConfig::~LineChartRendererDataSourceConfig()
256{
257}
258
259
260const rgb_color&
261LineChartRendererDataSourceConfig::Color() const
262{
263	return fColor;
264}
265
266
267void
268LineChartRendererDataSourceConfig::SetColor(const rgb_color& color)
269{
270	fColor = color;
271}
272