⛏️ index : haiku.git

/*
 * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */

#include "chart/LineChartRenderer.h"

#include <stdio.h>

#include <Shape.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
	view->SetLineMode(B_ROUND_CAP, B_ROUND_JOIN);
	for (int32 i = 0; DataSourceInfo* info = fDataSources.ItemAt(i); i++) {

		float bottom = fFrame.bottom;
		BShape shape;
		shape.MoveTo(BPoint(left + first,
			bottom - ((info->samples[first] - minRange) * scale)));

		for (int32 i = first; i <= last; i++) {
			shape.LineTo(BPoint(float(left + i),
				float(bottom - ((info->samples[i] - minRange) * scale))));
		}
		view->SetHighColor(info->config.Color());
		view->MovePenTo(B_ORIGIN);
		view->StrokeShape(&shape);
	}
}


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;
}