xref: /haiku/src/apps/debuganalyzer/gui/chart/LegendChartAxis.cpp (revision b671e9bbdbd10268a042b4f4cc4317ccd03d105e)
1 /*
2  * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 #include "chart/LegendChartAxis.h"
7 
8 #include <limits.h>
9 #include <stdio.h>
10 
11 #include <algorithm>
12 #include <new>
13 
14 #include <Font.h>
15 #include <View.h>
16 
17 #include "chart/ChartLegend.h"
18 #include "chart/ChartAxisLegendSource.h"
19 
20 
21 static const int32 kChartRulerDistance = 2;
22 static const int32 kRulerSize = 3;
23 static const int32 kRulerMarkSize = 3;
24 static const int32 kRulerLegendDistance = 2;
25 static const int32 kChartLegendDistance
26 	= kChartRulerDistance + kRulerSize + kRulerMarkSize + kRulerLegendDistance;
27 
28 
29 
30 struct LegendChartAxis::LegendInfo {
31 	ChartLegend*	legend;
32 	double			value;
33 	BSize			size;
34 
35 	LegendInfo(ChartLegend* legend, double value, BSize size)
36 		:
37 		legend(legend),
38 		value(value),
39 		size(size)
40 	{
41 	}
42 
43 	~LegendInfo()
44 	{
45 		delete legend;
46 	}
47 };
48 
49 
50 float
51 LegendChartAxis::_LegendPosition(double value, float legendSize,
52 	float totalSize, double scale)
53 {
54 	float position = (value - fRange.min) * scale - legendSize / 2;
55 	if (position + legendSize > totalSize)
56 		position = totalSize - legendSize;
57 	if (position < 0)
58 		position = 0;
59 	return position;
60 }
61 
62 
63 void
64 LegendChartAxis::_FilterLegends(int32 totalSize, int32 spacing,
65 	float BSize::* sizeField)
66 {
67 	// compute the min/max legend levels
68 	int32 legendCount = fLegends.CountItems();
69 	int32 minLevel = INT_MAX;
70 	int32 maxLevel = 0;
71 
72 	for (int32 i = 0; i < legendCount; i++) {
73 		LegendInfo* info = fLegends.ItemAt(i);
74 		int32 level = info->legend->Level();
75 		if (level < minLevel)
76 			minLevel = level;
77 		if (level > maxLevel)
78 			maxLevel = level;
79 	}
80 
81 	if (maxLevel <= 0)
82 		return;
83 
84 	double rangeSize = fRange.max - fRange.min;
85 	if (rangeSize == 0)
86 		rangeSize = 1.0;
87 	double scale = (double)totalSize / rangeSize;
88 
89 	// Filter out all higher level legends colliding with lower level or
90 	// preceeding same-level legends. We iterate backwards from the lower to
91 	// the higher levels
92 	for (int32 level = std::max(minLevel, 0L); level <= maxLevel;) {
93 		legendCount = fLegends.CountItems();
94 
95 		// get the first legend position/end
96 		LegendInfo* info = fLegends.ItemAt(0);
97 		float position = _LegendPosition(info->value, info->size.*sizeField,
98 			(float)totalSize, scale);;
99 
100 		int32 previousEnd = (int32)ceilf(position + info->size.*sizeField);
101 		int32 previousLevel = info->legend->Level();
102 
103 		for (int32 i = 1; (info = fLegends.ItemAt(i)) != NULL; i++) {
104 			float position = _LegendPosition(info->value, info->size.*sizeField,
105 				(float)totalSize, scale);;
106 
107 			if (position - spacing < previousEnd
108 				&& (previousLevel <= level
109 					|| info->legend->Level() <= level)
110 				&& std::max(previousLevel, info->legend->Level()) > 0) {
111 				// The item intersects with the previous one -- remove the
112 				// one at the higher level.
113 				if (info->legend->Level() >= previousLevel) {
114 					// This item is at the higher level -- remove it.
115 					delete fLegends.RemoveItemAt(i);
116 					i--;
117 					continue;
118 				}
119 
120 				// The previous item is at the higher level -- remove it.
121 				delete fLegends.RemoveItemAt(i - 1);
122 				i--;
123 			}
124 
125 			if (i == 0 && position < 0)
126 				position = 0;
127 			previousEnd = (int32)ceilf(position + info->size.*sizeField);
128 			previousLevel = info->legend->Level();
129 		}
130 
131 		// repeat with the level as long as we've removed something
132 		if (legendCount == fLegends.CountItems())
133 			level++;
134 	}
135 }
136 
137 
138 LegendChartAxis::LegendChartAxis(ChartAxisLegendSource* legendSource,
139 	ChartLegendRenderer* legendRenderer)
140 	:
141 	fLegendSource(legendSource),
142 	fLegendRenderer(legendRenderer),
143 	fLocation(CHART_AXIS_BOTTOM),
144 	fRange(),
145 	fFrame(),
146 	fLegends(20, true),
147 	fHorizontalSpacing(20),
148 	fVerticalSpacing(10),
149 	fLayoutValid(false)
150 {
151 }
152 
153 
154 LegendChartAxis::~LegendChartAxis()
155 {
156 }
157 
158 
159 void
160 LegendChartAxis::SetLocation(ChartAxisLocation location)
161 {
162 	if (location != fLocation) {
163 		fLocation = location;
164 		_InvalidateLayout();
165 	}
166 }
167 
168 
169 void
170 LegendChartAxis::SetRange(const ChartDataRange& range)
171 {
172 	if (range != fRange) {
173 		fRange = range;
174 		_InvalidateLayout();
175 	}
176 }
177 
178 
179 void
180 LegendChartAxis::SetFrame(BRect frame)
181 {
182 	if (frame != fFrame) {
183 		fFrame = frame;
184 		_InvalidateLayout();
185 	}
186 }
187 
188 
189 BSize
190 LegendChartAxis::PreferredSize(BView* view, BSize maxSize)
191 {
192 	// estimate the maximum legend count we might need
193 	float hSpacing, vSpacing;
194 	int32 maxLegends = _EstimateMaxLegendCount(view, maxSize, &hSpacing,
195 		&vSpacing);
196 	BSize spacing(hSpacing, vSpacing);
197 	if (maxLegends < 4)
198 		maxLegends = 4;
199 
200 	// get the legends
201 	ChartLegend* legends[maxLegends];
202 	double values[maxLegends];
203 
204 	int32 legendCount = fLegendSource->GetAxisLegends(fRange, legends, values,
205 		maxLegends);
206 
207 	// get the sizes, delete the legends, and compute the preferred size
208 	float BSize::* sizeField;
209 	float BSize::* otherSizeField;
210 	if (fLocation == CHART_AXIS_LEFT || fLocation == CHART_AXIS_RIGHT) {
211 		sizeField = &BSize::height;
212 		otherSizeField = &BSize::width;
213 	} else {
214 		sizeField = &BSize::width;
215 		otherSizeField = &BSize::height;
216 	}
217 
218 	BSize preferredSize;
219 
220 	for (int32 i = 0; i < legendCount; i++) {
221 		ChartLegend* legend = legends[i];
222 		BSize size = fLegendRenderer->LegendSize(legend, view);
223 		delete legend;
224 
225 		if (size.*sizeField > preferredSize.*sizeField)
226 			preferredSize.*sizeField = size.*sizeField;
227 		if (size.*otherSizeField > preferredSize.*otherSizeField)
228 			preferredSize.*otherSizeField = size.*otherSizeField;
229 	}
230 
231 	// Suppose we want to have at least 2 legends.
232 	preferredSize.*sizeField
233 		= ceilf(preferredSize.*sizeField * 2 + spacing.*sizeField);
234 	preferredSize.*otherSizeField += kChartLegendDistance;
235 
236 	return preferredSize;
237 }
238 
239 
240 void
241 LegendChartAxis::Render(BView* view, BRect updateRect)
242 {
243 	if (!_ValidateLayout(view))
244 		return;
245 
246 	float valueDirection;
247 	float rulerDirection;
248 	float BSize::* sizeField;
249 	float BSize::* otherSizeField;
250 	float BPoint::* pointField;
251 	float BPoint::* otherPointField;
252 
253 	switch (fLocation) {
254 		case CHART_AXIS_LEFT:
255 		case CHART_AXIS_RIGHT:
256 			valueDirection = -1;
257 			rulerDirection = fLocation == CHART_AXIS_LEFT ? -1 : 1;
258 			sizeField = &BSize::height;
259 			otherSizeField = &BSize::width;
260 			pointField = &BPoint::y;
261 			otherPointField = &BPoint::x;
262 			break;
263 		case CHART_AXIS_TOP:
264 		case CHART_AXIS_BOTTOM:
265 			valueDirection = 1;
266 			rulerDirection = fLocation == CHART_AXIS_TOP ? -1 : 1;
267 			sizeField = &BSize::width;
268 			otherSizeField = &BSize::height;
269 			pointField = &BPoint::x;
270 			otherPointField = &BPoint::y;
271 			break;
272 		default:
273 			return;
274 	}
275 
276 	float totalSize = floorf(fFrame.Size().*sizeField) + 1;
277 	double rangeSize = fRange.max - fRange.min;
278 	if (rangeSize == 0)
279 		rangeSize = 1.0;
280 	double scale = (double)totalSize / rangeSize;
281 
282 	// draw the ruler
283 	float rulerStart = fFrame.LeftBottom().*pointField;
284 	float rulerChartClosest = rulerDirection == 1
285 		? fFrame.LeftTop().*otherPointField + kChartRulerDistance
286 		: fFrame.RightBottom().*otherPointField - kChartRulerDistance;
287 	float rulerEnd = fFrame.RightTop().*pointField;
288 	float rulerChartDistant = rulerChartClosest + rulerDirection * kRulerSize;
289 
290 	rgb_color black = { 0, 0, 0, 255 };
291 	view->BeginLineArray(3 + fLegends.CountItems());
292 	BPoint first;
293 	first.*pointField = rulerStart;
294 	first.*otherPointField = rulerChartClosest;
295 	BPoint second = first;
296 	second.*otherPointField = rulerChartDistant;
297 	BPoint third = second;
298 	third.*pointField = rulerEnd;
299 	BPoint fourth = third;
300 	fourth.*otherPointField = rulerChartClosest;
301 	view->AddLine(first, second, black);
302 	view->AddLine(second, third, black);
303 	view->AddLine(third, fourth, black);
304 
305 	// marks
306 	for (int32 i = 0; LegendInfo* info = fLegends.ItemAt(i); i++) {
307 		float position = (info->value - fRange.min) * scale;
308 		position = rulerStart + valueDirection * position;
309 		first.*pointField = position;
310 		first.*otherPointField = rulerChartDistant;
311 		second.*pointField = position;
312 		second.*otherPointField = rulerChartDistant
313 			+ rulerDirection * kRulerMarkSize;
314 		view->AddLine(first, second, black);
315 	}
316 	view->EndLineArray();
317 
318 	// draw the legends
319 	float legendRulerClosest = rulerChartDistant
320 		+ rulerDirection * (kRulerMarkSize + kRulerLegendDistance);
321 
322 	for (int32 i = 0; LegendInfo* info = fLegends.ItemAt(i); i++) {
323 		float position = _LegendPosition(info->value, info->size.*sizeField,
324 			(float)totalSize, scale);;
325 
326 		first.*pointField = rulerStart
327 			+ (valueDirection == 1
328 				? position : -position - info->size.*sizeField);
329 		first.*otherPointField = rulerDirection == 1
330 			? legendRulerClosest
331 			: legendRulerClosest - info->size.*otherSizeField;
332 
333 		fLegendRenderer->RenderLegend(info->legend, view, first);
334 	}
335 }
336 
337 
338 void
339 LegendChartAxis::_InvalidateLayout()
340 {
341 	fLayoutValid = false;
342 }
343 
344 
345 bool
346 LegendChartAxis::_ValidateLayout(BView* view)
347 {
348 	if (fLayoutValid)
349 		return true;
350 
351 	fLegends.MakeEmpty();
352 
353 	int32 width = fFrame.IntegerWidth() + 1;
354 	int32 height = fFrame.IntegerHeight() + 1;
355 
356 	// estimate the maximum legend count we might need
357 	int32 maxLegends = _EstimateMaxLegendCount(view, fFrame.Size(),
358 		&fHorizontalSpacing, &fVerticalSpacing);
359 
360 	if (maxLegends == 0)
361 		return false;
362 
363 	// get the legends
364 	ChartLegend* legends[maxLegends];
365 	double values[maxLegends];
366 
367 	int32 legendCount = fLegendSource->GetAxisLegends(fRange, legends, values,
368 		maxLegends);
369 	if (legendCount == 0)
370 		return false;
371 
372 	// create legend infos
373 	for (int32 i = 0; i < legendCount; i++) {
374 		ChartLegend* legend = legends[i];
375 		BSize size = fLegendRenderer->LegendSize(legend, view);
376 		LegendInfo* info = new(std::nothrow) LegendInfo(legend, values[i],
377 			size);
378 		if (info == NULL || !fLegends.AddItem(info)) {
379 			// TODO: Report error!
380 			delete info;
381 			for (int32 k = i; k < legendCount; k++)
382 				delete legends[k];
383 			return false;
384 		}
385 	}
386 
387 	if (fLocation == CHART_AXIS_LEFT || fLocation == CHART_AXIS_RIGHT)
388 		_FilterLegends(height, fVerticalSpacing, &BSize::height);
389 	else
390 		_FilterLegends(width, fHorizontalSpacing, &BSize::width);
391 
392 	fLayoutValid = true;
393 	return true;
394 }
395 
396 
397 int32
398 LegendChartAxis::_EstimateMaxLegendCount(BView* view, BSize size,
399 	float* _hSpacing, float* _vSpacing)
400 {
401 	// get the legend spacing
402 	fLegendRenderer->GetMinimumLegendSpacing(view, _hSpacing, _vSpacing);
403 
404 	// estimate the maximum legend count we might need
405 	if (fLocation == CHART_AXIS_LEFT || fLocation == CHART_AXIS_RIGHT)
406 		return (int32)((size.IntegerHeight() + 1) / (10 + *_vSpacing));
407 	return (int32)((size.IntegerWidth() + 1) / (20 + *_hSpacing));
408 }
409