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
21static const int32 kChartRulerDistance = 2;
22static const int32 kRulerSize = 3;
23static const int32 kRulerMarkSize = 3;
24static const int32 kRulerLegendDistance = 2;
25static const int32 kChartLegendDistance
26	= kChartRulerDistance + kRulerSize + kRulerMarkSize + kRulerLegendDistance;
27
28
29
30struct 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
50float
51LegendChartAxis::_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
63void
64LegendChartAxis::_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, (int32)0); 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
138LegendChartAxis::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
154LegendChartAxis::~LegendChartAxis()
155{
156}
157
158
159void
160LegendChartAxis::SetLocation(ChartAxisLocation location)
161{
162	if (location != fLocation) {
163		fLocation = location;
164		_InvalidateLayout();
165	}
166}
167
168
169void
170LegendChartAxis::SetRange(const ChartDataRange& range)
171{
172	if (range != fRange) {
173		fRange = range;
174		_InvalidateLayout();
175	}
176}
177
178
179void
180LegendChartAxis::SetFrame(BRect frame)
181{
182	if (frame != fFrame) {
183		fFrame = frame;
184		_InvalidateLayout();
185	}
186}
187
188
189BSize
190LegendChartAxis::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
240void
241LegendChartAxis::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
338void
339LegendChartAxis::_InvalidateLayout()
340{
341	fLayoutValid = false;
342}
343
344
345bool
346LegendChartAxis::_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
397int32
398LegendChartAxis::_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