* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#include "chart/LegendChartAxis.h"
#include <limits.h>
#include <stdio.h>
#include <algorithm>
#include <new>
#include <Font.h>
#include <View.h>
#include "chart/ChartLegend.h"
#include "chart/ChartAxisLegendSource.h"
static const int32 kChartRulerDistance = 2;
static const int32 kRulerSize = 3;
static const int32 kRulerMarkSize = 3;
static const int32 kRulerLegendDistance = 2;
static const int32 kChartLegendDistance
= kChartRulerDistance + kRulerSize + kRulerMarkSize + kRulerLegendDistance;
struct LegendChartAxis::LegendInfo {
ChartLegend* legend;
double value;
BSize size;
LegendInfo(ChartLegend* legend, double value, BSize size)
:
legend(legend),
value(value),
size(size)
{
}
~LegendInfo()
{
delete legend;
}
};
float
LegendChartAxis::_LegendPosition(double value, float legendSize,
float totalSize, double scale)
{
float position = (value - fRange.min) * scale - legendSize / 2;
if (position + legendSize > totalSize)
position = totalSize - legendSize;
if (position < 0)
position = 0;
return position;
}
void
LegendChartAxis::_FilterLegends(int32 totalSize, int32 spacing,
float BSize::* sizeField)
{
int32 legendCount = fLegends.CountItems();
int32 minLevel = INT_MAX;
int32 maxLevel = 0;
for (int32 i = 0; i < legendCount; i++) {
LegendInfo* info = fLegends.ItemAt(i);
int32 level = info->legend->Level();
if (level < minLevel)
minLevel = level;
if (level > maxLevel)
maxLevel = level;
}
if (maxLevel <= 0)
return;
double rangeSize = fRange.max - fRange.min;
if (rangeSize == 0)
rangeSize = 1.0;
double scale = (double)totalSize / rangeSize;
for (int32 level = std::max(minLevel, (int32)0); level <= maxLevel;) {
legendCount = fLegends.CountItems();
LegendInfo* info = fLegends.ItemAt(0);
float position = _LegendPosition(info->value, info->size.*sizeField,
(float)totalSize, scale);;
int32 previousEnd = (int32)ceilf(position + info->size.*sizeField);
int32 previousLevel = info->legend->Level();
for (int32 i = 1; (info = fLegends.ItemAt(i)) != NULL; i++) {
float position = _LegendPosition(info->value, info->size.*sizeField,
(float)totalSize, scale);;
if (position - spacing < previousEnd
&& (previousLevel <= level
|| info->legend->Level() <= level)
&& std::max(previousLevel, info->legend->Level()) > 0) {
if (info->legend->Level() >= previousLevel) {
delete fLegends.RemoveItemAt(i);
i--;
continue;
}
delete fLegends.RemoveItemAt(i - 1);
i--;
}
if (i == 0 && position < 0)
position = 0;
previousEnd = (int32)ceilf(position + info->size.*sizeField);
previousLevel = info->legend->Level();
}
if (legendCount == fLegends.CountItems())
level++;
}
}
LegendChartAxis::LegendChartAxis(ChartAxisLegendSource* legendSource,
ChartLegendRenderer* legendRenderer)
:
fLegendSource(legendSource),
fLegendRenderer(legendRenderer),
fLocation(CHART_AXIS_BOTTOM),
fRange(),
fFrame(),
fLegends(20),
fHorizontalSpacing(20),
fVerticalSpacing(10),
fLayoutValid(false)
{
}
LegendChartAxis::~LegendChartAxis()
{
}
void
LegendChartAxis::SetLocation(ChartAxisLocation location)
{
if (location != fLocation) {
fLocation = location;
_InvalidateLayout();
}
}
void
LegendChartAxis::SetRange(const ChartDataRange& range)
{
if (range != fRange) {
fRange = range;
_InvalidateLayout();
}
}
void
LegendChartAxis::SetFrame(BRect frame)
{
if (frame != fFrame) {
fFrame = frame;
_InvalidateLayout();
}
}
BSize
LegendChartAxis::PreferredSize(BView* view, BSize maxSize)
{
float hSpacing, vSpacing;
int32 maxLegends = _EstimateMaxLegendCount(view, maxSize, &hSpacing,
&vSpacing);
BSize spacing(hSpacing, vSpacing);
if (maxLegends < 4)
maxLegends = 4;
ChartLegend* legends[maxLegends];
double values[maxLegends];
int32 legendCount = fLegendSource->GetAxisLegends(fRange, legends, values,
maxLegends);
float BSize::* sizeField;
float BSize::* otherSizeField;
if (fLocation == CHART_AXIS_LEFT || fLocation == CHART_AXIS_RIGHT) {
sizeField = &BSize::height;
otherSizeField = &BSize::width;
} else {
sizeField = &BSize::width;
otherSizeField = &BSize::height;
}
BSize preferredSize;
for (int32 i = 0; i < legendCount; i++) {
ChartLegend* legend = legends[i];
BSize size = fLegendRenderer->LegendSize(legend, view);
delete legend;
if (size.*sizeField > preferredSize.*sizeField)
preferredSize.*sizeField = size.*sizeField;
if (size.*otherSizeField > preferredSize.*otherSizeField)
preferredSize.*otherSizeField = size.*otherSizeField;
}
preferredSize.*sizeField
= ceilf(preferredSize.*sizeField * 2 + spacing.*sizeField);
preferredSize.*otherSizeField += kChartLegendDistance;
return preferredSize;
}
void
LegendChartAxis::Render(BView* view, BRect updateRect)
{
if (!_ValidateLayout(view))
return;
float valueDirection;
float rulerDirection;
float BSize::* sizeField;
float BSize::* otherSizeField;
float BPoint::* pointField;
float BPoint::* otherPointField;
switch (fLocation) {
case CHART_AXIS_LEFT:
case CHART_AXIS_RIGHT:
valueDirection = -1;
rulerDirection = fLocation == CHART_AXIS_LEFT ? -1 : 1;
sizeField = &BSize::height;
otherSizeField = &BSize::width;
pointField = &BPoint::y;
otherPointField = &BPoint::x;
break;
case CHART_AXIS_TOP:
case CHART_AXIS_BOTTOM:
valueDirection = 1;
rulerDirection = fLocation == CHART_AXIS_TOP ? -1 : 1;
sizeField = &BSize::width;
otherSizeField = &BSize::height;
pointField = &BPoint::x;
otherPointField = &BPoint::y;
break;
default:
return;
}
float totalSize = floorf(fFrame.Size().*sizeField) + 1;
double rangeSize = fRange.max - fRange.min;
if (rangeSize == 0)
rangeSize = 1.0;
double scale = (double)totalSize / rangeSize;
float rulerStart = fFrame.LeftBottom().*pointField;
float rulerChartClosest = rulerDirection == 1
? fFrame.LeftTop().*otherPointField + kChartRulerDistance
: fFrame.RightBottom().*otherPointField - kChartRulerDistance;
float rulerEnd = fFrame.RightTop().*pointField;
float rulerChartDistant = rulerChartClosest + rulerDirection * kRulerSize;
rgb_color black = { 0, 0, 0, 255 };
view->BeginLineArray(3 + fLegends.CountItems());
BPoint first;
first.*pointField = rulerStart;
first.*otherPointField = rulerChartClosest;
BPoint second = first;
second.*otherPointField = rulerChartDistant;
BPoint third = second;
third.*pointField = rulerEnd;
BPoint fourth = third;
fourth.*otherPointField = rulerChartClosest;
view->AddLine(first, second, black);
view->AddLine(second, third, black);
view->AddLine(third, fourth, black);
for (int32 i = 0; LegendInfo* info = fLegends.ItemAt(i); i++) {
float position = (info->value - fRange.min) * scale;
position = rulerStart + valueDirection * position;
first.*pointField = position;
first.*otherPointField = rulerChartDistant;
second.*pointField = position;
second.*otherPointField = rulerChartDistant
+ rulerDirection * kRulerMarkSize;
view->AddLine(first, second, black);
}
view->EndLineArray();
float legendRulerClosest = rulerChartDistant
+ rulerDirection * (kRulerMarkSize + kRulerLegendDistance);
for (int32 i = 0; LegendInfo* info = fLegends.ItemAt(i); i++) {
float position = _LegendPosition(info->value, info->size.*sizeField,
(float)totalSize, scale);;
first.*pointField = rulerStart
+ (valueDirection == 1
? position : -position - info->size.*sizeField);
first.*otherPointField = rulerDirection == 1
? legendRulerClosest
: legendRulerClosest - info->size.*otherSizeField;
fLegendRenderer->RenderLegend(info->legend, view, first);
}
}
void
LegendChartAxis::_InvalidateLayout()
{
fLayoutValid = false;
}
bool
LegendChartAxis::_ValidateLayout(BView* view)
{
if (fLayoutValid)
return true;
fLegends.MakeEmpty();
int32 width = fFrame.IntegerWidth() + 1;
int32 height = fFrame.IntegerHeight() + 1;
int32 maxLegends = _EstimateMaxLegendCount(view, fFrame.Size(),
&fHorizontalSpacing, &fVerticalSpacing);
if (maxLegends == 0)
return false;
ChartLegend* legends[maxLegends];
double values[maxLegends];
int32 legendCount = fLegendSource->GetAxisLegends(fRange, legends, values,
maxLegends);
if (legendCount == 0)
return false;
for (int32 i = 0; i < legendCount; i++) {
ChartLegend* legend = legends[i];
BSize size = fLegendRenderer->LegendSize(legend, view);
LegendInfo* info = new(std::nothrow) LegendInfo(legend, values[i],
size);
if (info == NULL || !fLegends.AddItem(info)) {
delete info;
for (int32 k = i; k < legendCount; k++)
delete legends[k];
return false;
}
}
if (fLocation == CHART_AXIS_LEFT || fLocation == CHART_AXIS_RIGHT)
_FilterLegends(height, fVerticalSpacing, &BSize::height);
else
_FilterLegends(width, fHorizontalSpacing, &BSize::width);
fLayoutValid = true;
return true;
}
int32
LegendChartAxis::_EstimateMaxLegendCount(BView* view, BSize size,
float* _hSpacing, float* _vSpacing)
{
fLegendRenderer->GetMinimumLegendSpacing(view, _hSpacing, _vSpacing);
if (fLocation == CHART_AXIS_LEFT || fLocation == CHART_AXIS_RIGHT)
return (int32)((size.IntegerHeight() + 1) / (10 + *_vSpacing));
return (int32)((size.IntegerWidth() + 1) / (20 + *_hSpacing));
}