Beginning of a framework for drawing charts. Works a bit already, but there are

lots of loose ends.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@30501 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Ingo Weinhold
2009-04-30 12:19:01 +00:00
parent 9431509f5c
commit bea425f17c
24 changed files with 1765 additions and 0 deletions
+2
View File
@@ -12,12 +12,14 @@ MergeObject DebugAnalyzer_gui.o
SubWindow.cpp
SubWindowManager.cpp
:
DebugAnalyzer_gui_chart.o
DebugAnalyzer_gui_main_window.o
DebugAnalyzer_gui_table.o
DebugAnalyzer_gui_thread_window.o
;
HaikuSubInclude chart ;
HaikuSubInclude main_window ;
HaikuSubInclude table ;
HaikuSubInclude thread_window ;
@@ -0,0 +1,103 @@
/*
* Copyright 2009, Ingo Weinhold, [email protected].
* Distributed under the terms of the MIT License.
*/
#include "chart/BigtimeChartAxisLegendSource.h"
#include <stdio.h>
#include "chart/ChartDataRange.h"
#include "chart/StringChartLegend.h"
struct decomposed_time {
bigtime_t hours;
int minutes;
int seconds;
int micros;
};
static void
decompose_time(bigtime_t time, decomposed_time& decomposed)
{
// microsecs
decomposed.micros = time % 1000000;
time /= 1000000;
// secs
decomposed.seconds = time % 60;
time /= 60;
// mins
decomposed.minutes = time % 60;
time /= 60;
// hours
decomposed.hours = time;
}
int32
BigtimeChartAxisLegendSource::GetAxisLegends(const ChartDataRange& range,
ChartLegend** legends, double* values, int32 maxLegends)
{
// interpret range as time range
bigtime_t startTime = (bigtime_t)range.min;
bigtime_t endTime = (bigtime_t)range.max;
printf("BigtimeChartAxisLegendSource::GetAxisLegends(): range: %lld - %lld\n", startTime, endTime);
// TODO: Handle sub-microsecs ranges!
if (startTime >= endTime)
return 0;
bigtime_t positionFactors[4];
positionFactors[3] = 1;
positionFactors[2] = 1000000;
positionFactors[1] = positionFactors[2] * 60;
positionFactors[0] = positionFactors[1] * 60;
// find the main position (h, m, s, us) we want to play with
int32 position = 0;
bigtime_t rangeTime = endTime - startTime;
while (rangeTime / positionFactors[position] + 1 < maxLegends / 2
&& position < 3) {
position++;
}
// adjust the factor so that we get maxLegends / 2 to maxLegends legends
bigtime_t baseInterval = positionFactors[position];
bigtime_t relativeFactor = 1;
while (rangeTime / (baseInterval * relativeFactor) >= maxLegends) {
if (relativeFactor == 1) {
relativeFactor = 2;
} else if (relativeFactor == 2) {
relativeFactor = 5;
} else if (relativeFactor == 5) {
baseInterval *= 10;
relativeFactor = 1;
}
}
// generate the legends
int32 count = 0;
bigtime_t interval = baseInterval * relativeFactor;
bigtime_t time = (startTime + interval - 1) / interval * interval;
for (; time <= endTime; time += interval) {
decomposed_time decomposed;
decompose_time(time, decomposed);
char buffer[128];
snprintf(buffer, sizeof(buffer), "%02lld:%02d:%02d.%06d",
decomposed.hours, decomposed.minutes, decomposed.seconds,
decomposed.micros);
// TODO: Drop superfluous micro seconds digits, or even microseconds and seconds
// completely.
StringChartLegend* legend = new(std::nothrow) StringChartLegend(buffer,
1);
if (legend == 0)
return count;
legends[count] = legend;
values[count++] = (double)time;
}
return count;
}
@@ -0,0 +1,19 @@
/*
* Copyright 2009, Ingo Weinhold, [email protected].
* Distributed under the terms of the MIT License.
*/
#ifndef BIGTIME_CHART_AXIS_LEGEND_SOURCE_H
#define BIGTIME_CHART_AXIS_LEGEND_SOURCE_H
#include "chart/ChartAxisLegendSource.h"
class BigtimeChartAxisLegendSource : public ChartAxisLegendSource {
public:
virtual int32 GetAxisLegends(const ChartDataRange& range,
ChartLegend** legends, double* values,
int32 maxLegends);
};
#endif // BIGTIME_CHART_AXIS_LEGEND_SOURCE_H
+340
View File
@@ -0,0 +1,340 @@
/*
* Copyright 2009, Ingo Weinhold, [email protected].
* Distributed under the terms of the MIT License.
*/
#include "chart/Chart.h"
#include <stdio.h>
#include <new>
#include <ControlLook.h>
#include <Region.h>
#include "chart/ChartAxis.h"
#include "chart/ChartDataSource.h"
#include "chart/ChartRenderer.h"
// #pragma mark - Chart::AxisInfo
Chart::AxisInfo::AxisInfo()
:
axis(NULL)
{
}
void
Chart::AxisInfo::SetFrame(float left, float top, float right, float bottom)
{
frame.Set(left, top, right, bottom);
if (axis != NULL)
axis->SetFrame(frame);
}
void
Chart::AxisInfo::SetRange(const ChartDataRange& range)
{
if (axis != NULL)
axis->SetRange(range);
}
void
Chart::AxisInfo::Render(BView* view, const BRect& updateRect)
{
if (axis != NULL)
axis->Render(view, updateRect);
}
// #pragma mark - Chart
Chart::Chart(ChartRenderer* renderer, const char* name)
:
BView(name, B_FRAME_EVENTS | B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE),
fRenderer(renderer)
{
SetViewColor(B_TRANSPARENT_32_BIT);
// fRenderer->SetFrame(Bounds());
}
Chart::~Chart()
{
}
bool
Chart::AddDataSource(ChartDataSource* dataSource, int32 index,
ChartRendererDataSourceConfig* config)
{
printf("Chart::AddDataSource(%p)\n", dataSource);
if (dataSource == NULL)
return false;
if (index < 0 || index > fDataSources.CountItems())
index = fDataSources.CountItems();
if (!fDataSources.AddItem(dataSource, index))
return false;
if (!fRenderer->AddDataSource(dataSource, index, config)) {
fDataSources.RemoveItemAt(index);
return false;
}
_UpdateDomainAndRange();
InvalidateLayout();
Invalidate();
return true;
}
bool
Chart::AddDataSource(ChartDataSource* dataSource,
ChartRendererDataSourceConfig* config)
{
return AddDataSource(dataSource, -1, config);
}
bool
Chart::RemoveDataSource(ChartDataSource* dataSource)
{
if (dataSource == NULL)
return false;
return RemoveDataSource(fDataSources.IndexOf(dataSource));
}
ChartDataSource*
Chart::RemoveDataSource(int32 index)
{
printf("Chart::RemoveDataSource(%ld)\n", index);
if (index < 0 || index >= fDataSources.CountItems())
return NULL;
ChartDataSource* dataSource = fDataSources.RemoveItemAt(index);
fRenderer->RemoveDataSource(dataSource);
_UpdateDomainAndRange();
return dataSource;
}
void
Chart::RemoveAllDataSources()
{
printf("Chart::RemoveAllDataSources()\n");
int32 count = fDataSources.CountItems();
for (int32 i = count - 1; i >= 0; i--)
fRenderer->RemoveDataSource(fDataSources.ItemAt(i));
fDataSources.MakeEmpty();
_UpdateDomainAndRange();
InvalidateLayout();
Invalidate();
}
void
Chart::SetAxis(ChartAxisLocation location, ChartAxis* axis)
{
switch (location) {
case CHART_AXIS_LEFT:
fLeftAxis.axis = axis;
break;
case CHART_AXIS_TOP:
fTopAxis.axis = axis;
break;
case CHART_AXIS_RIGHT:
fRightAxis.axis = axis;
break;
case CHART_AXIS_BOTTOM:
fBottomAxis.axis = axis;
break;
default:
return;
}
axis->SetLocation(location);
InvalidateLayout();
Invalidate();
}
#if 0
ChartDataRange
Chart::Domain() const
{
return fDataSource != NULL ? fDataSource->Domain() : ChartDataRange();
}
void
Chart::SetDisplayDomain(const ChartDataRange& domain)
{
fRenderer->SetDomain(domain);
Invalidate();
}
void
Chart::SetDisplayDomain(const ChartDataRange& range)
{
fRenderer->SetRange(range);
Invalidate();
}
#endif
void
Chart::FrameResized(float newWidth, float newHeight)
{
//printf("Chart::FrameResized(%f, %f)\n", newWidth, newHeight);
// fRenderer->SetFrame(Bounds());
Invalidate();
}
void
Chart::Draw(BRect updateRect)
{
printf("Chart::Draw((%f, %f) - (%f, %f))\n", updateRect.left, updateRect.top, updateRect.right, updateRect.bottom);
rgb_color background = ui_color(B_PANEL_BACKGROUND_COLOR);
rgb_color color;
// clear the axes background
if (fLeftAxis.axis != NULL || fTopAxis.axis != NULL
|| fRightAxis.axis != NULL || fBottomAxis.axis != NULL) {
SetHighColor(background);
BRegion clippingRegion(Bounds());
clippingRegion.Exclude(fChartFrame);
ConstrainClippingRegion(&clippingRegion);
FillRect(Bounds());
ConstrainClippingRegion(NULL);
}
// render the axes
fLeftAxis.Render(this, updateRect);
fTopAxis.Render(this, updateRect);
fRightAxis.Render(this, updateRect);
fBottomAxis.Render(this, updateRect);
// draw the frame around the chart area and clear the background
BRect chartFrame(fChartFrame);
be_control_look->DrawBorder(this, chartFrame, updateRect, background,
B_PLAIN_BORDER);
// DrawBorder() insets the supplied rect
SetHighColor(color.set_to(255, 255, 255, 255));
FillRect(chartFrame);
// render the chart
BRegion clippingRegion(chartFrame);
ConstrainClippingRegion(&clippingRegion);
fRenderer->Render(this, updateRect);
}
#if 0
BSize
Chart::MinSize()
{
}
BSize
Chart::MaxSize()
{
}
BSize
Chart::PreferredSize()
{
}
#endif
void
Chart::DoLayout()
{
// get size in pixels
BSize size = Bounds().Size();
printf("Chart::DoLayout(%f, %f)\n", size.width, size.height);
int32 width = size.IntegerWidth() + 1;
int32 height = size.IntegerHeight() + 1;
// compute the axis insets
int32 left = 0;
int32 right = 0;
int32 top = 0;
int32 bottom = 0;
if (fLeftAxis.axis != NULL)
left = fLeftAxis.axis->PreferredSize(this).IntegerWidth() + 1;
if (fRightAxis.axis != NULL)
right = fRightAxis.axis->PreferredSize(this).IntegerWidth() + 1;
if (fTopAxis.axis != NULL)
top = fTopAxis.axis->PreferredSize(this).IntegerHeight() + 1;
if (fBottomAxis.axis != NULL)
bottom = fBottomAxis.axis->PreferredSize(this).IntegerHeight() + 1;
fChartFrame = BRect(left, top, width - right - 1, height - bottom - 1);
fRenderer->SetFrame(fChartFrame.InsetByCopy(1, 1));
printf(" fChartFrame: (%f, %f) - (%f, %f)\n", fChartFrame.left, fChartFrame.top, fChartFrame.right, fChartFrame.bottom);
fLeftAxis.SetFrame(0, fChartFrame.top, fChartFrame.left - 1,
fChartFrame.bottom);
fRightAxis.SetFrame(fChartFrame.right + 1, fChartFrame.top, width - 1,
fChartFrame.bottom);
fTopAxis.SetFrame(fChartFrame.left, 0, fChartFrame.right,
fChartFrame.top - 1);
fBottomAxis.SetFrame(fChartFrame.left, fChartFrame.bottom + 1,
fChartFrame.right, height - 1);
}
void
Chart::_UpdateDomainAndRange()
{
if (fDataSources.IsEmpty()) {
fDomain = ChartDataRange();
fRange = ChartDataRange();
return;
} else {
ChartDataSource* firstSource = fDataSources.ItemAt(0);
fDomain = firstSource->Domain();
fRange = firstSource->Range();
for (int32 i = 1; ChartDataSource* source = fDataSources.ItemAt(i);
i++) {
fDomain.Extend(source->Domain());
fRange.Extend(source->Range());
}
}
fRenderer->SetDomain(fDomain);
fRenderer->SetRange(fRange);
fLeftAxis.SetRange(fRange);
fTopAxis.SetRange(fDomain);
fRightAxis.SetRange(fRange);
fBottomAxis.SetRange(fDomain);
}
+95
View File
@@ -0,0 +1,95 @@
/*
* Copyright 2009, Ingo Weinhold, [email protected].
* Distributed under the terms of the MIT License.
*/
#ifndef CHART_H
#define CHART_H
#include <View.h>
#include <ObjectList.h>
#include "chart/ChartDataRange.h"
#include "chart/ChartDefs.h"
class ChartAxis;
class ChartDataSource;
class ChartRenderer;
class ChartRendererDataSourceConfig;
class Chart : public BView {
public:
Chart(ChartRenderer* renderer,
const char* name = NULL);
virtual ~Chart();
bool AddDataSource(ChartDataSource* dataSource,
int32 index,
ChartRendererDataSourceConfig* config
= NULL);
bool AddDataSource(ChartDataSource* dataSource,
ChartRendererDataSourceConfig* config
= NULL);
bool RemoveDataSource(
ChartDataSource* dataSource);
ChartDataSource* RemoveDataSource(int32 index);
void RemoveAllDataSources();
void SetAxis(ChartAxisLocation location,
ChartAxis* axis);
#if 0
ChartDataRange Domain() const;
void SetDisplayDomain(const ChartDataRange& domain);
void SetDisplayRange(const ChartDataRange& range);
#endif
virtual void FrameResized(float newWidth, float newHeight);
virtual void Draw(BRect updateRect);
#if 0
virtual BSize MinSize();
virtual BSize MaxSize();
virtual BSize PreferredSize();
#endif
virtual void DoLayout();
private:
typedef BObjectList<ChartDataSource> DataSourceList;
struct AxisInfo {
ChartAxis* axis;
BRect frame;
AxisInfo();
void SetFrame(float left, float top, float right,
float bottom);
void SetRange(const ChartDataRange& range);
void Render(BView* view, const BRect& updateRect);
};
private:
void _UpdateDomainAndRange();
private:
ChartRenderer* fRenderer;
DataSourceList fDataSources;
AxisInfo fLeftAxis;
AxisInfo fTopAxis;
AxisInfo fRightAxis;
AxisInfo fBottomAxis;
ChartDataRange fDomain;
ChartDataRange fRange;
#if 0
ChartDataRange fDisplayDomain;
ChartDataRange fDisplayRange;
#endif
BRect fChartFrame;
};
#endif // CHART_H
@@ -0,0 +1,14 @@
/*
* Copyright 2009, Ingo Weinhold, [email protected].
* Distributed under the terms of the MIT License.
*/
#include "chart/ChartAxis.h"
// #pragma mark - ChartAxis
ChartAxis::~ChartAxis()
{
}
@@ -0,0 +1,29 @@
/*
* Copyright 2009, Ingo Weinhold, [email protected].
* Distributed under the terms of the MIT License.
*/
#ifndef CHART_AXIS_H
#define CHART_AXIS_H
#include <Rect.h>
#include "chart/ChartDefs.h"
class BView;
class ChartDataRange;
class ChartAxis {
public:
virtual ~ChartAxis();
virtual void SetLocation(ChartAxisLocation location) = 0;
virtual void SetRange(const ChartDataRange& range) = 0;
virtual void SetFrame(BRect frame) = 0;
virtual BSize PreferredSize(BView* view) = 0;
virtual void Render(BView* view, BRect updateRect) = 0;
};
#endif // CHART_AXIS_H
@@ -0,0 +1,11 @@
/*
* Copyright 2009, Ingo Weinhold, [email protected].
* Distributed under the terms of the MIT License.
*/
#include "chart/ChartAxisLegendSource.h"
ChartAxisLegendSource::~ChartAxisLegendSource()
{
}
@@ -0,0 +1,25 @@
/*
* Copyright 2009, Ingo Weinhold, [email protected].
* Distributed under the terms of the MIT License.
*/
#ifndef CHART_AXIS_LEGEND_SOURCE_H
#define CHART_AXIS_LEGEND_SOURCE_H
#include <SupportDefs.h>
class ChartDataRange;
class ChartLegend;
class ChartAxisLegendSource {
public:
virtual ~ChartAxisLegendSource();
virtual int32 GetAxisLegends(const ChartDataRange& range,
ChartLegend** legends, double* values,
int32 maxLegends) = 0;
};
#endif // CHART_AXIS_LEGEND_SOURCE_H
@@ -0,0 +1,63 @@
/*
* Copyright 2009, Ingo Weinhold, [email protected].
* Distributed under the terms of the MIT License.
*/
#ifndef CHART_DATA_RANGE_H
#define CHART_DATA_RANGE_H
#include <algorithm>
class ChartDataRange {
public:
double min;
double max;
ChartDataRange()
:
min(0),
max(0)
{
}
ChartDataRange(double min, double max)
:
min(min),
max(max)
{
}
ChartDataRange(const ChartDataRange& other)
:
min(other.min),
max(other.max)
{
}
ChartDataRange& Extend(const ChartDataRange& other)
{
min = std::min(min, other.min);
max = std::max(max, other.max);
return *this;
}
ChartDataRange& operator=(const ChartDataRange& other)
{
min = other.min;
max = other.max;
return *this;
}
bool operator==(const ChartDataRange& other) const
{
return min == other.min && max == other.max;
}
bool operator!=(const ChartDataRange& other) const
{
return !(*this == other);
}
};
#endif // CHART_DATA_RANGE_H
@@ -0,0 +1,11 @@
/*
* Copyright 2009, Ingo Weinhold, [email protected].
* Distributed under the terms of the MIT License.
*/
#include "chart/ChartDataSource.h"
ChartDataSource::~ChartDataSource()
{
}
@@ -0,0 +1,24 @@
/*
* Copyright 2009, Ingo Weinhold, [email protected].
* Distributed under the terms of the MIT License.
*/
#ifndef CHART_DATA_SOURCE_H
#define CHART_DATA_SOURCE_H
#include <SupportDefs.h>
#include "chart/ChartDataRange.h"
class ChartDataSource {
public:
virtual ~ChartDataSource();
virtual ChartDataRange Domain() const = 0;
virtual ChartDataRange Range() const = 0;
virtual void GetSamples(double start, double end,
double* samples, int32 count) = 0;
};
#endif // CHART_DATA_SOURCE_H
@@ -0,0 +1,17 @@
/*
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#ifndef CHART_DEFS_H
#define CHART_DEFS_H
enum ChartAxisLocation {
CHART_AXIS_LEFT,
CHART_AXIS_TOP,
CHART_AXIS_RIGHT,
CHART_AXIS_BOTTOM
};
#endif // CHART_DEFS_H
@@ -0,0 +1,29 @@
/*
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#include "chart/ChartLegend.h"
// #pragma mark - ChartLegend
ChartLegend::ChartLegend(int32 level)
:
fLevel(level)
{
}
ChartLegend::~ChartLegend()
{
}
// #pragma mark - ChartLegendRenderer
ChartLegendRenderer::~ChartLegendRenderer()
{
}
@@ -0,0 +1,45 @@
/*
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#ifndef CHART_LEGEND_H
#define CHART_LEGEND_H
#include <Point.h>
#include <Size.h>
class BView;
class ChartDataRange;
class ChartLegend {
public:
ChartLegend(int32 level = 0);
// A lower level means more likely to be
// shown. <= 0 is mandatory.
virtual ~ChartLegend();
int32 Level() { return fLevel; }
private:
int32 fLevel;
};
class ChartLegendRenderer {
public:
virtual ~ChartLegendRenderer();
virtual void GetMinimumLegendSpacing(BView* view,
float* horizontal, float* vertical) = 0;
virtual BSize MaximumLegendSize(BView* view) = 0;
virtual BSize LegendSize(ChartLegend* legend,
BView* view) = 0;
virtual void RenderLegend(ChartLegend* legend, BView* view,
BPoint point) = 0;
};
#endif // CHART_LEGEND_H
@@ -0,0 +1,22 @@
/*
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#include "chart/ChartRenderer.h"
// #pragma mark - ChartRenderer
ChartRenderer::~ChartRenderer()
{
}
// #pragma mark - ChartRendererDataSourceConfig
ChartRendererDataSourceConfig::~ChartRendererDataSourceConfig()
{
}
@@ -0,0 +1,41 @@
/*
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#ifndef CHART_RENDERER_H
#define CHART_RENDERER_H
#include <Rect.h>
class BView;
class ChartDataRange;
class ChartDataSource;
class ChartRendererDataSourceConfig {
public:
virtual ~ChartRendererDataSourceConfig();
};
class ChartRenderer {
public:
virtual ~ChartRenderer();
virtual bool AddDataSource(
ChartDataSource* dataSource,
int32 index,
ChartRendererDataSourceConfig* config) = 0;
virtual void RemoveDataSource(
ChartDataSource* dataSource) = 0;
virtual void SetFrame(const BRect& frame) = 0;
virtual void SetDomain(const ChartDataRange& domain) = 0;
virtual void SetRange(const ChartDataRange& range) = 0;
virtual void Render(BView* view, BRect updateRect) = 0;
};
#endif // CHART_RENDERER_H
+20
View File
@@ -0,0 +1,20 @@
SubDir HAIKU_TOP src apps debuganalyzer gui chart ;
UsePrivateHeaders shared ;
UseHeaders $(HAIKU_DEBUG_ANALYZER_HEADERS) ;
MergeObject DebugAnalyzer_gui_chart.o
:
BigtimeChartAxisLegendSource.cpp
Chart.cpp
ChartAxis.cpp
ChartAxisLegendSource.cpp
ChartDataSource.cpp
ChartLegend.cpp
ChartRenderer.cpp
LegendChartAxis.cpp
LineChartRenderer.cpp
StringChartLegend.cpp
;
@@ -0,0 +1,343 @@
/*
* 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)
{
printf("LegendChartAxis::_FilterLegends(%ld, %ld)\n", totalSize, spacing);
// compute the min/max legend levels
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;
// Filter out all higher level legends colliding with lower level or
// preceeding same-level legends. We iterate backwards from the lower to
// the higher levels
for (int32 level = std::max(minLevel, 0L); level <= maxLevel;) {
printf(" level: %ld\n", level);
legendCount = fLegends.CountItems();
// get the first legend position/end
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();
printf(" first legend: pos: %f, size: %f\n", position, info->size.*sizeField);
for (int32 i = 1; (info = fLegends.ItemAt(i)) != NULL; i++) {
float position = _LegendPosition(info->value, info->size.*sizeField,
(float)totalSize, scale);;
printf(" legend %ld: pos: %f, size: %f\n", i, position, info->size.*sizeField);
if (position - spacing < previousEnd
&& (previousLevel <= level
|| info->legend->Level() <= level)
&& std::max(previousLevel, info->legend->Level()) > 0) {
// The item intersects with the previous one -- remove the
// one at the higher level.
if (info->legend->Level() >= previousLevel) {
// This item is at the higher level -- remove it.
delete fLegends.RemoveItemAt(i);
i--;
continue;
}
// The previous item is at the higher level -- remove it.
delete fLegends.RemoveItemAt(i - 1);
i--;
}
if (i == 0 && position < 0)
position = 0;
previousEnd = (int32)ceilf(position + info->size.*sizeField);
previousLevel = info->legend->Level();
}
// repeat with the level as long as we've removed something
if (legendCount == fLegends.CountItems())
level++;
}
}
LegendChartAxis::LegendChartAxis(ChartAxisLegendSource* legendSource,
ChartLegendRenderer* legendRenderer)
:
fLegendSource(legendSource),
fLegendRenderer(legendRenderer),
fLocation(CHART_AXIS_BOTTOM),
fRange(),
fFrame(),
fLegends(20, true),
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)
{
printf("LegendChartAxis::SetFrame((%f, %f) - (%f, %f))\n", frame.left, frame.top, frame.right, frame.bottom);
if (frame != fFrame) {
fFrame = frame;
_InvalidateLayout();
}
}
BSize
LegendChartAxis::PreferredSize(BView* view)
{
// TODO: Implement for real!
BSize size = fLegendRenderer->MaximumLegendSize(view);
if (fLocation == CHART_AXIS_LEFT || fLocation == CHART_AXIS_RIGHT) {
size.width += kChartLegendDistance;
size.height = std::max(size.height * 4, 100.0f);
} else {
size.width = std::max(size.width * 4, 100.0f);
size.height += kChartLegendDistance;
}
return size;
}
void
LegendChartAxis::Render(BView* view, BRect updateRect)
{
printf("LegendChartAxis::Render()\n");
if (!_ValidateLayout(view))
return;
if (fLocation == CHART_AXIS_BOTTOM) {
printf(" rendering...\n");
float totalSize = floorf(fFrame.Width()) + 1;
double rangeSize = fRange.max - fRange.min;
if (rangeSize == 0)
rangeSize = 1.0;
double scale = (double)totalSize / rangeSize;
float BSize::* sizeField = &BSize::width;
// draw the ruler
float rulerLeft = fFrame.left;
float rulerTop = fFrame.top + kChartRulerDistance;
float rulerRight = fFrame.right;
float rulerBottom = rulerTop + kRulerSize;
printf(" ruler: (%f, %f) - (%f, %f)\n", rulerLeft, rulerTop, rulerRight, rulerBottom);
rgb_color black = { 0, 0, 0, 255 };
view->BeginLineArray(3 + fLegends.CountItems());
view->AddLine(BPoint(rulerLeft, rulerTop),
BPoint(rulerLeft, rulerBottom), black);
view->AddLine(BPoint(rulerLeft, rulerBottom),
BPoint(rulerRight, rulerBottom), black);
view->AddLine(BPoint(rulerRight, rulerBottom),
BPoint(rulerRight, rulerTop), black);
// marks
for (int32 i = 0; LegendInfo* info = fLegends.ItemAt(i); i++) {
float position = (info->value - fRange.min) * scale;
position += rulerLeft;
printf(" drawing mark at (%f, %f)\n", position, rulerBottom);
view->AddLine(BPoint(position, rulerBottom),
BPoint(position, rulerBottom + kRulerMarkSize), black);
}
view->EndLineArray();
// draw the legends
float legendTop = rulerBottom + kRulerMarkSize + kRulerLegendDistance;
for (int32 i = 0; LegendInfo* info = fLegends.ItemAt(i); i++) {
float position = _LegendPosition(info->value, info->size.*sizeField,
(float)totalSize, scale);;
printf(" legend %ld: position: (%f, %f), size: (%f, %f)\n", i, position, legendTop, info->size.width, info->size.height);
fLegendRenderer->RenderLegend(info->legend, view,
BPoint(rulerLeft + position, legendTop));
}
}
}
void
LegendChartAxis::_InvalidateLayout()
{
fLayoutValid = false;
}
bool
LegendChartAxis::_ValidateLayout(BView* view)
{
if (fLayoutValid)
return true;
printf("LegendChartAxis::_ValidateLayout()\n");
fLegends.MakeEmpty();
int32 width = fFrame.IntegerWidth() + 1;
int32 height = fFrame.IntegerHeight() + 1;
printf(" width: %ld, height: %ld\n", width, height);
fLegendRenderer->GetMinimumLegendSpacing(view, &fHorizontalSpacing,
&fVerticalSpacing);
// estimate the maximum legend count we might need
int32 maxLegends;
if (fLocation == CHART_AXIS_LEFT || fLocation == CHART_AXIS_RIGHT)
maxLegends = height / (10 + fVerticalSpacing);
else
maxLegends = width / (20 + fHorizontalSpacing);
printf(" max %ld legends\n", maxLegends);
if (maxLegends == 0)
{
printf(" too small for any legends!\n");
return false;
}
// get the legends
ChartLegend* legends[maxLegends];
double values[maxLegends];
int32 legendCount = fLegendSource->GetAxisLegends(fRange, legends, values,
maxLegends);
printf(" got %ld legends\n", legendCount);
if (legendCount == 0)
{
printf(" got no legends from source!\n");
return false;
}
printf(" range: %f - %f\n", fRange.min, fRange.max);
// create legend infos
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);
printf(" legend %ld: size: (%f, %f), value: %f\n", i, size.width, size.height, info->value);
if (info == NULL || !fLegends.AddItem(info)) {
// TODO: Report error!
delete info;
for (int32 k = i; k < legendCount; k++)
delete legends[k];
printf(" failed to create legend info!\n");
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;
}
@@ -0,0 +1,55 @@
/*
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#ifndef LEGEND_CHART_AXIS_H
#define LEGEND_CHART_AXIS_H
#include <ObjectList.h>
#include "chart/ChartAxis.h"
#include "chart/ChartDataRange.h"
class ChartAxisLegendSource;
class ChartLegendRenderer;
class LegendChartAxis : public ChartAxis {
public:
LegendChartAxis(
ChartAxisLegendSource* legendSource,
ChartLegendRenderer* legendRenderer);
virtual ~LegendChartAxis();
virtual void SetLocation(ChartAxisLocation location);
virtual void SetRange(const ChartDataRange& range);
virtual void SetFrame(BRect frame);
virtual BSize PreferredSize(BView* view);
virtual void Render(BView* view, BRect updateRect);
private:
struct LegendInfo;
typedef BObjectList<LegendInfo> LegendList;
private:
void _InvalidateLayout();
bool _ValidateLayout(BView* view);
inline float _LegendPosition(double value, float legendSize,
float totalSize, double scale);
inline void _FilterLegends(int32 totalSize, int32 spacing,
float BSize::* sizeField);
private:
ChartAxisLegendSource* fLegendSource;
ChartLegendRenderer* fLegendRenderer;
ChartAxisLocation fLocation;
ChartDataRange fRange;
BRect fFrame;
LegendList fLegends;
float fHorizontalSpacing;
float fVerticalSpacing;
bool fLayoutValid;
};
#endif // LEGEND_CHART_AXIS_H
@@ -0,0 +1,268 @@
/*
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#include "chart/LineChartRenderer.h"
#include <stdio.h>
#include <View.h>
#include "chart/ChartDataSource.h"
// #pragma mark - DataSourceInfo
struct LineChartRenderer::DataSourceInfo {
public:
ChartDataSource* source;
double* samples;
int32 sampleCount;
bool samplesValid;
LineChartRendererDataSourceConfig config;
public:
DataSourceInfo(ChartDataSource* source,
LineChartRendererDataSourceConfig* config);
~DataSourceInfo();
bool UpdateSamples(const ChartDataRange& domain,
int32 sampleCount);
};
LineChartRenderer::DataSourceInfo::DataSourceInfo(ChartDataSource* source,
LineChartRendererDataSourceConfig* config)
:
source(source),
samples(NULL),
sampleCount(0),
samplesValid(false)
{
if (config != NULL)
this->config = *config;
}
LineChartRenderer::DataSourceInfo::~DataSourceInfo()
{
delete[] samples;
}
bool
LineChartRenderer::DataSourceInfo::UpdateSamples(const ChartDataRange& domain,
int32 sampleCount)
{
if (samplesValid)
return true;
// check the sample count -- we might need to realloc the sample array
if (sampleCount != this->sampleCount) {
delete[] samples;
this->sampleCount = 0;
samples = new(std::nothrow) double[sampleCount];
if (samples == NULL)
return false;
this->sampleCount = sampleCount;
}
// get the new samples
source->GetSamples(domain.min, domain.max, samples, sampleCount);
samplesValid = true;
return true;
}
// #pragma mark - LineChartRenderer
LineChartRenderer::LineChartRenderer()
:
fFrame(),
fDomain(),
fRange()
{
}
LineChartRenderer::~LineChartRenderer()
{
}
bool
LineChartRenderer::AddDataSource(ChartDataSource* dataSource, int32 index,
ChartRendererDataSourceConfig* config)
{
DataSourceInfo* info = new(std::nothrow) DataSourceInfo(dataSource,
dynamic_cast<LineChartRendererDataSourceConfig*>(config));
if (info == NULL)
return false;
if (!fDataSources.AddItem(info, index)) {
delete info;
return false;
}
return true;
}
void
LineChartRenderer::RemoveDataSource(ChartDataSource* dataSource)
{
for (int32 i = 0; DataSourceInfo* info = fDataSources.ItemAt(i); i++) {
info->samplesValid = false;
if (info->source == dataSource) {
delete fDataSources.RemoveItemAt(i);
return;
}
}
}
void
LineChartRenderer::SetFrame(const BRect& frame)
{
fFrame = frame;
_InvalidateSamples();
}
void
LineChartRenderer::SetDomain(const ChartDataRange& domain)
{
if (domain != fDomain) {
fDomain = domain;
_InvalidateSamples();
}
}
void
LineChartRenderer::SetRange(const ChartDataRange& range)
{
if (range != fRange) {
fRange = range;
_InvalidateSamples();
}
}
void
LineChartRenderer::Render(BView* view, BRect updateRect)
{
if (!updateRect.IsValid() || updateRect.left > fFrame.right
|| fFrame.left > updateRect.right) {
return;
}
if (fDomain.min >= fDomain.max || fRange.min >= fRange.max)
return;
if (!_UpdateSamples())
return;
// get the range to draw (draw one more sample on each side)
int32 left = (int32)fFrame.left;
int32 first = (int32)updateRect.left - left - 1;
int32 last = (int32)updateRect.right - left + 1;
if (first < 0)
first = 0;
if (last > fFrame.IntegerWidth())
last = fFrame.IntegerWidth();
if (first > last)
return;
double minRange = fRange.min;
double sampleRange = fRange.max - minRange;
if (sampleRange == 0) {
minRange = fRange.min - 0.5;
sampleRange = 1;
}
double scale = (double)fFrame.IntegerHeight() / sampleRange;
// draw
for (int32 i = 0; DataSourceInfo* info = fDataSources.ItemAt(i); i++) {
printf("LineChartRenderer::Render(): %p\n", info);
view->SetHighColor(info->config.Color());
float bottom = fFrame.bottom;
view->MovePenTo(left + first,
bottom - ((info->samples[first] - minRange) * scale));
for (int32 i = first; i <= last; i++) {
view->StrokeLine(BPoint(float(left + i),
float(bottom - ((info->samples[i] - minRange) * scale))));
//printf(" %f: %f (%f)\n", info->samples[i] - minRange, float(bottom - ((info->samples[i] - minRange) * scale)), bottom);
}
}
}
void
LineChartRenderer::_InvalidateSamples()
{
for (int32 i = 0; DataSourceInfo* info = fDataSources.ItemAt(i); i++)
info->samplesValid = false;
}
bool
LineChartRenderer::_UpdateSamples()
{
int32 width = fFrame.IntegerWidth() + 1;
if (width <= 0)
return false;
for (int32 i = 0; DataSourceInfo* info = fDataSources.ItemAt(i); i++) {
if (!info->UpdateSamples(fDomain, width))
return false;
}
return true;
}
// #pragma mark - LineChartRendererDataSourceConfig
LineChartRendererDataSourceConfig::LineChartRendererDataSourceConfig()
{
fColor.red = 0;
fColor.green = 0;
fColor.blue = 0;
fColor.alpha = 255;
}
LineChartRendererDataSourceConfig::LineChartRendererDataSourceConfig(
const rgb_color& color)
{
fColor = color;
}
LineChartRendererDataSourceConfig::~LineChartRendererDataSourceConfig()
{
}
const rgb_color&
LineChartRendererDataSourceConfig::Color() const
{
return fColor;
}
void
LineChartRendererDataSourceConfig::SetColor(const rgb_color& color)
{
fColor = color;
}
@@ -0,0 +1,71 @@
/*
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#ifndef LINE_CHART_RENDERER_H
#define LINE_CHART_RENDERER_H
#include <InterfaceDefs.h>
#include <Rect.h>
#include <ObjectList.h>
#include "chart/ChartDataRange.h"
#include "chart/ChartRenderer.h"
class BView;
class LineChartRenderer : public ChartRenderer {
public:
class DataSourceConfig;
public:
LineChartRenderer();
virtual ~LineChartRenderer();
virtual bool AddDataSource(ChartDataSource* dataSource,
int32 index,
ChartRendererDataSourceConfig* config);
virtual void RemoveDataSource(ChartDataSource* dataSource);
virtual void SetFrame(const BRect& frame);
virtual void SetDomain(const ChartDataRange& domain);
virtual void SetRange(const ChartDataRange& range);
virtual void Render(BView* view, BRect updateRect);
private:
struct DataSourceInfo;
typedef BObjectList<DataSourceInfo> DataSourceList;
private:
void _InvalidateSamples();
bool _UpdateSamples();
private:
DataSourceList fDataSources;
BRect fFrame;
ChartDataRange fDomain;
ChartDataRange fRange;
};
class LineChartRendererDataSourceConfig : public ChartRendererDataSourceConfig {
public:
LineChartRendererDataSourceConfig();
LineChartRendererDataSourceConfig(
const rgb_color& color);
virtual ~LineChartRendererDataSourceConfig();
const rgb_color& Color() const;
void SetColor(const rgb_color& color);
private:
rgb_color fColor;
};
#endif // LINE_CHART_RENDERER_H
@@ -0,0 +1,80 @@
/*
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#include "chart/StringChartLegend.h"
#include <Font.h>
#include <View.h>
// #pragma mark - StringChartLegend
StringChartLegend::StringChartLegend(const char* string, int32 level)
:
ChartLegend(level),
fString(string)
{
}
// #pragma mark - StringChartLegendRenderer
void
StringChartLegendRenderer::GetMinimumLegendSpacing(BView* view,
float* horizontal, float* vertical)
{
// TODO: Implement for real!
*horizontal = 40;
*vertical = 20;
}
BSize
StringChartLegendRenderer::MaximumLegendSize(BView* view)
{
// TODO: Implement for real!
return BSize(100, 20);
}
BSize
StringChartLegendRenderer::LegendSize(ChartLegend* _legend, BView* view)
{
StringChartLegend* legend = dynamic_cast<StringChartLegend*>(_legend);
if (legend == NULL)
return BSize();
// TODO: It's a bit expensive to do that for each legend!
// TODO: Have a font per renderer?
BFont font;
view->GetFont(&font);
font_height fh;
font.GetHeight(&fh);
float fontHeight = ceilf(fh.ascent) + ceilf(fh.descent);
float width = ceilf(font.StringWidth(legend->String()));
return BSize(width, fontHeight);
}
void
StringChartLegendRenderer::RenderLegend(ChartLegend* _legend, BView* view,
BPoint point)
{
StringChartLegend* legend = dynamic_cast<StringChartLegend*>(_legend);
if (legend == NULL)
return;
BFont font;
view->GetFont(&font);
font_height fh;
font.GetHeight(&fh);
point.y += fh.ascent;
view->SetHighColor(rgb_color().set_to(0, 0, 0, 255));
view->DrawString(legend->String(), point);
}
@@ -0,0 +1,38 @@
/*
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#ifndef STRING_CHART_LEGEND_H
#define STRING_CHART_LEGEND_H
#include <String.h>
#include "chart/ChartLegend.h"
class StringChartLegend : public ChartLegend {
public:
StringChartLegend(const char* string,
int32 level = 0);
const char* String() const { return fString.String(); }
private:
BString fString;
};
class StringChartLegendRenderer : public ChartLegendRenderer {
public:
virtual void GetMinimumLegendSpacing(BView* view,
float* horizontal, float* vertical);
virtual BSize MaximumLegendSize(BView* view);
virtual BSize LegendSize(ChartLegend* legend,
BView* view);
virtual void RenderLegend(ChartLegend* legend, BView* view,
BPoint point);
};
#endif // STRING_CHART_LEGEND_H