xref: /haiku/src/apps/debuganalyzer/gui/chart/Chart.cpp (revision 239222b2369c39dc52df52b0a7cdd6cc0a91bc92)
1 /*
2  * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 #include "chart/Chart.h"
7 
8 #include <stdio.h>
9 
10 #include <new>
11 
12 #include <ControlLook.h>
13 #include <Region.h>
14 #include <ScrollBar.h>
15 #include <Window.h>
16 
17 #include "chart/ChartAxis.h"
18 #include "chart/ChartDataSource.h"
19 #include "chart/ChartRenderer.h"
20 
21 
22 // #pragma mark - Chart::AxisInfo
23 
24 
25 Chart::AxisInfo::AxisInfo()
26 	:
27 	axis(NULL)
28 {
29 }
30 
31 
32 void
33 Chart::AxisInfo::SetFrame(float left, float top, float right, float bottom)
34 {
35 	frame.Set(left, top, right, bottom);
36 	if (axis != NULL)
37 		axis->SetFrame(frame);
38 }
39 
40 
41 void
42 Chart::AxisInfo::SetRange(const ChartDataRange& range)
43 {
44 	if (axis != NULL)
45 		axis->SetRange(range);
46 }
47 
48 
49 void
50 Chart::AxisInfo::Render(BView* view, const BRect& updateRect)
51 {
52 	if (axis != NULL)
53 		axis->Render(view, updateRect);
54 }
55 
56 
57 // #pragma mark - Chart
58 
59 
60 Chart::Chart(ChartRenderer* renderer, const char* name)
61 	:
62 	BView(name, B_FRAME_EVENTS | B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE),
63 	fRenderer(renderer),
64 	fHScrollSize(0),
65 	fVScrollSize(0),
66 	fHScrollValue(0),
67 	fVScrollValue(0),
68 	fIgnoreScrollEvent(0),
69 	fDomainZoomLimit(0),
70 	fLastMousePos(-1, -1),
71 	fDraggingStartPos(-1, -1)
72 {
73 	SetViewColor(B_TRANSPARENT_32_BIT);
74 
75 //	fRenderer->SetFrame(Bounds());
76 }
77 
78 
79 Chart::~Chart()
80 {
81 }
82 
83 
84 bool
85 Chart::AddDataSource(ChartDataSource* dataSource, int32 index,
86 	ChartRendererDataSourceConfig* config)
87 {
88 	if (dataSource == NULL)
89 		return false;
90 
91 	if (index < 0 || index > fDataSources.CountItems())
92 		index = fDataSources.CountItems();
93 
94 	if (!fDataSources.AddItem(dataSource, index))
95 		return false;
96 
97 	if (!fRenderer->AddDataSource(dataSource, index, config)) {
98 		fDataSources.RemoveItemAt(index);
99 		return false;
100 	}
101 
102 	_UpdateDomainAndRange();
103 
104 	InvalidateLayout();
105 	Invalidate();
106 
107 	return true;
108 }
109 
110 
111 bool
112 Chart::AddDataSource(ChartDataSource* dataSource,
113 	ChartRendererDataSourceConfig* config)
114 {
115 	return AddDataSource(dataSource, -1, config);
116 }
117 
118 
119 bool
120 Chart::RemoveDataSource(ChartDataSource* dataSource)
121 {
122 	if (dataSource == NULL)
123 		return false;
124 
125 	return RemoveDataSource(fDataSources.IndexOf(dataSource));
126 }
127 
128 
129 ChartDataSource*
130 Chart::RemoveDataSource(int32 index)
131 {
132 	if (index < 0 || index >= fDataSources.CountItems())
133 		return NULL;
134 
135 	ChartDataSource* dataSource = fDataSources.RemoveItemAt(index);
136 
137 	fRenderer->RemoveDataSource(dataSource);
138 
139 	_UpdateDomainAndRange();
140 
141 	Invalidate();
142 
143 	return dataSource;
144 }
145 
146 
147 void
148 Chart::RemoveAllDataSources()
149 {
150 	int32 count = fDataSources.CountItems();
151 	for (int32 i = count - 1; i >= 0; i--)
152 		fRenderer->RemoveDataSource(fDataSources.ItemAt(i));
153 
154 	fDataSources.MakeEmpty();
155 
156 	_UpdateDomainAndRange();
157 
158 	InvalidateLayout();
159 	Invalidate();
160 }
161 
162 
163 void
164 Chart::SetAxis(ChartAxisLocation location, ChartAxis* axis)
165 {
166 	switch (location) {
167 		case CHART_AXIS_LEFT:
168 			fLeftAxis.axis = axis;
169 			break;
170 		case CHART_AXIS_TOP:
171 			fTopAxis.axis = axis;
172 			break;
173 		case CHART_AXIS_RIGHT:
174 			fRightAxis.axis = axis;
175 			break;
176 		case CHART_AXIS_BOTTOM:
177 			fBottomAxis.axis = axis;
178 			break;
179 		default:
180 			return;
181 	}
182 
183 	axis->SetLocation(location);
184 
185 	InvalidateLayout();
186 	Invalidate();
187 }
188 
189 
190 void
191 Chart::SetDisplayDomain(ChartDataRange domain)
192 {
193 	// sanitize the supplied range
194 	if (domain.IsValid() && domain.Size() < fDomain.Size()) {
195 		if (domain.min < fDomain.min)
196 			domain.OffsetTo(fDomain.min);
197 		else if (domain.max > fDomain.max)
198 			domain.OffsetBy(fDomain.max - domain.max);
199 	} else
200 		domain = fDomain;
201 
202 	if (domain == fDisplayDomain)
203 		return;
204 
205 	fDisplayDomain = domain;
206 
207 	fRenderer->SetDomain(fDisplayDomain);
208 	fTopAxis.SetRange(fDisplayDomain);
209 	fBottomAxis.SetRange(fDisplayDomain);
210 
211 	_UpdateScrollBar(true);
212 
213 	InvalidateLayout();
214 	Invalidate();
215 }
216 
217 
218 void
219 Chart::SetDisplayRange(ChartDataRange range)
220 {
221 	// sanitize the supplied range
222 	if (range.IsValid() && range.Size() < fRange.Size()) {
223 		if (range.min < fRange.min)
224 			range.OffsetTo(fRange.min);
225 		else if (range.max > fRange.max)
226 			range.OffsetBy(fRange.max - range.max);
227 	} else
228 		range = fRange;
229 
230 	if (range == fDisplayRange)
231 		return;
232 
233 	fDisplayRange = range;
234 
235 	fRenderer->SetRange(fDisplayRange);
236 	fLeftAxis.SetRange(fDisplayRange);
237 	fRightAxis.SetRange(fDisplayRange);
238 
239 	_UpdateScrollBar(false);
240 
241 	InvalidateLayout();
242 	Invalidate();
243 }
244 
245 
246 double
247 Chart::DomainZoomLimit() const
248 {
249 	return fDomainZoomLimit;
250 }
251 
252 
253 void
254 Chart::SetDomainZoomLimit(double limit)
255 {
256 	fDomainZoomLimit = limit;
257 }
258 
259 
260 void
261 Chart::DomainChanged()
262 {
263 	if (ScrollBar(B_HORIZONTAL) != NULL && fDisplayDomain.IsValid())
264 		SetDisplayDomain(fDisplayDomain);
265 	else
266 		SetDisplayDomain(fDomain);
267 }
268 
269 
270 void
271 Chart::RangeChanged()
272 {
273 	if (ScrollBar(B_VERTICAL) != NULL && fDisplayRange.IsValid())
274 		SetDisplayRange(fDisplayRange);
275 	else
276 		SetDisplayRange(fRange);
277 }
278 
279 
280 void
281 Chart::MessageReceived(BMessage* message)
282 {
283 	switch (message->what) {
284 		case B_MOUSE_WHEEL_CHANGED:
285 		{
286 			// We're only interested in Shift + vertical wheel, if the mouse
287 			// is in the chart frame.
288 			float deltaY;
289 			if ((modifiers() & B_SHIFT_KEY) == 0
290 				|| message->FindFloat("be:wheel_delta_y", &deltaY) != B_OK
291 				|| !fChartFrame.InsetByCopy(1, 1).Contains(fLastMousePos)) {
292 				break;
293 			}
294 
295 			_Zoom(fLastMousePos.x, deltaY);
296 
297 			return;
298 		}
299 	}
300 
301 	BView::MessageReceived(message);
302 }
303 
304 
305 void
306 Chart::FrameResized(float newWidth, float newHeight)
307 {
308 //printf("Chart::FrameResized(%f, %f)\n", newWidth, newHeight);
309 //	fRenderer->SetFrame(Bounds());
310 
311 	_UpdateScrollBar(true);
312 	_UpdateScrollBar(false);
313 
314 	Invalidate();
315 }
316 
317 
318 void
319 Chart::MouseDown(BPoint where)
320 {
321 	// ignore, if already dragging or if there's no scrollbar
322 	if (fDraggingStartPos.x >= 0 || ScrollBar(B_HORIZONTAL) == NULL)
323 		return;
324 
325 	// the first button must be pressed
326 	int32 buttons;
327 	if (Window()->CurrentMessage()->FindInt32("buttons", &buttons) != B_OK
328 		|| (buttons & B_PRIMARY_MOUSE_BUTTON) == 0) {
329 		return;
330 	}
331 
332 	fDraggingStartPos = where;
333 	fDraggingStartScrollValue = fHScrollValue;
334 
335 	SetMouseEventMask(B_POINTER_EVENTS);
336 }
337 
338 
339 void
340 Chart::MouseUp(BPoint where)
341 {
342 	// ignore if not dragging, or if the first mouse button is still pressed
343 	int32 buttons;
344 	if (fDraggingStartPos.x < 0
345 		|| Window()->CurrentMessage()->FindInt32("buttons", &buttons) != B_OK
346 		|| (buttons & B_PRIMARY_MOUSE_BUTTON) != 0) {
347 		return;
348 	}
349 
350 	// stop dragging
351 	fDraggingStartPos.x = -1;
352 }
353 
354 
355 void
356 Chart::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage)
357 {
358 	fLastMousePos = where;
359 
360 	if (fDraggingStartPos.x < 0)
361 		return;
362 
363 	ScrollBar(B_HORIZONTAL)->SetValue(fDraggingStartScrollValue
364 		+ fDraggingStartPos.x - where.x);
365 }
366 
367 
368 void
369 Chart::Draw(BRect updateRect)
370 {
371 //printf("Chart::Draw((%f, %f) - (%f, %f))\n", updateRect.left, updateRect.top, updateRect.right, updateRect.bottom);
372 	rgb_color background = ui_color(B_PANEL_BACKGROUND_COLOR);
373 	rgb_color color;
374 
375 	// clear the axes background
376 	if (fLeftAxis.axis != NULL || fTopAxis.axis != NULL
377 		|| fRightAxis.axis != NULL || fBottomAxis.axis != NULL) {
378 		SetLowColor(background);
379 		BRegion clippingRegion(Bounds());
380 		clippingRegion.Exclude(fChartFrame);
381 		ConstrainClippingRegion(&clippingRegion);
382 		FillRect(Bounds(), B_SOLID_LOW);
383 		ConstrainClippingRegion(NULL);
384 	}
385 
386 	// render the axes
387 	fLeftAxis.Render(this, updateRect);
388 	fTopAxis.Render(this, updateRect);
389 	fRightAxis.Render(this, updateRect);
390 	fBottomAxis.Render(this, updateRect);
391 
392 	// draw the frame around the chart area and clear the background
393 	BRect chartFrame(fChartFrame);
394 	be_control_look->DrawBorder(this, chartFrame, updateRect, background,
395 		B_PLAIN_BORDER);
396 		// DrawBorder() insets the supplied rect
397 	SetHighColor(color.set_to(255, 255, 255, 255));
398 	FillRect(chartFrame);
399 
400 	// render the chart
401 	BRegion clippingRegion(chartFrame);
402 	ConstrainClippingRegion(&clippingRegion);
403 	fRenderer->Render(this, updateRect);
404 }
405 
406 
407 void
408 Chart::ScrollTo(BPoint where)
409 {
410 	if (fIgnoreScrollEvent > 0)
411 		return;
412 
413 	_ScrollTo(where.x, true);
414 	_ScrollTo(where.y, false);
415 }
416 
417 
418 BSize
419 Chart::MinSize()
420 {
421 	// TODO: Implement for real!
422 	return BSize(100, 100);
423 }
424 
425 
426 BSize
427 Chart::MaxSize()
428 {
429 	// TODO: Implement for real!
430 	return BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED);
431 }
432 
433 
434 BSize
435 Chart::PreferredSize()
436 {
437 	// TODO: Implement for real!
438 	return MinSize();
439 }
440 
441 
442 void
443 Chart::DoLayout()
444 {
445 	// get size in pixels
446 	BSize size = Bounds().Size();
447 //printf("Chart::DoLayout(%f, %f)\n", size.width, size.height);
448 	int32 width = size.IntegerWidth() + 1;
449 	int32 height = size.IntegerHeight() + 1;
450 
451 	// compute the axis insets
452 	int32 left = 0;
453 	int32 right = 0;
454 	int32 top = 0;
455 	int32 bottom = 0;
456 
457 	if (fLeftAxis.axis != NULL)
458 		left = fLeftAxis.axis->PreferredSize(this, size).IntegerWidth() + 1;
459 	if (fRightAxis.axis != NULL)
460 		right = fRightAxis.axis->PreferredSize(this, size).IntegerWidth() + 1;
461 	if (fTopAxis.axis != NULL)
462 		top = fTopAxis.axis->PreferredSize(this, size).IntegerHeight() + 1;
463 	if (fBottomAxis.axis != NULL) {
464 		bottom = fBottomAxis.axis->PreferredSize(this, size).IntegerHeight()
465 			+ 1;
466 	}
467 
468 	fChartFrame = BRect(left, top, width - right - 1, height - bottom - 1);
469 	fRenderer->SetFrame(fChartFrame.InsetByCopy(1, 1));
470 //printf("  fChartFrame: (%f, %f) - (%f, %f)\n", fChartFrame.left, fChartFrame.top, fChartFrame.right, fChartFrame.bottom);
471 
472 	fLeftAxis.SetFrame(0, fChartFrame.top + 1, fChartFrame.left - 1,
473 		fChartFrame.bottom - 1);
474 	fRightAxis.SetFrame(fChartFrame.right + 1, fChartFrame.top + 1, width - 1,
475 		fChartFrame.bottom - 1);
476 	fTopAxis.SetFrame(fChartFrame.left + 1, 0, fChartFrame.right - 1,
477 		fChartFrame.top - 1);
478 	fBottomAxis.SetFrame(fChartFrame.left + 1, fChartFrame.bottom + 1,
479 		fChartFrame.right - 1, height - 1);
480 }
481 
482 
483 void
484 Chart::_UpdateDomainAndRange()
485 {
486 	ChartDataRange oldDomain = fDomain;
487 	ChartDataRange oldRange = fRange;
488 
489 	if (fDataSources.IsEmpty()) {
490 		fDomain = ChartDataRange();
491 		fRange = ChartDataRange();
492 	} else {
493 		ChartDataSource* firstSource = fDataSources.ItemAt(0);
494 		fDomain = firstSource->Domain();
495 		fRange = firstSource->Range();
496 
497 		for (int32 i = 1; ChartDataSource* source = fDataSources.ItemAt(i);
498 				i++) {
499 			fDomain.Extend(source->Domain());
500 			fRange.Extend(source->Range());
501 		}
502 	}
503 
504 	if (fDomain != oldDomain)
505 		DomainChanged();
506 	if (fRange != oldRange)
507 		RangeChanged();
508 }
509 
510 
511 void
512 Chart::_UpdateScrollBar(bool horizontal)
513 {
514 	const ChartDataRange& range = horizontal ? fDomain : fRange;
515 	const ChartDataRange& displayRange = horizontal
516 		? fDisplayDomain : fDisplayRange;
517 	float chartSize = horizontal ? fChartFrame.Width() : fChartFrame.Height();
518 	chartSize--;	// +1 for pixel size, -2 for border
519 	float& scrollSize = horizontal ? fHScrollSize : fVScrollSize;
520 	float& scrollValue = horizontal ? fHScrollValue : fVScrollValue;
521 	BScrollBar* scrollBar = ScrollBar(horizontal ? B_HORIZONTAL : B_VERTICAL);
522 
523 	float proportion;
524 
525 	if (range.IsValid() && displayRange.IsValid()) {
526 		scrollSize = (range.Size() / displayRange.Size() - 1) * chartSize;
527 		scrollValue = (displayRange.min - range.min) / displayRange.Size()
528 			* chartSize;
529 		proportion = displayRange.Size() / range.Size();
530 	} else {
531 		scrollSize = 0;
532 		scrollValue = 0;
533 		proportion = 1;
534 	}
535 
536 	if (scrollBar != NULL) {
537 		fIgnoreScrollEvent++;
538 		scrollBar->SetRange(0, scrollSize);
539 		fIgnoreScrollEvent--;
540 		scrollBar->SetValue(scrollValue);
541 		scrollBar->SetProportion(proportion);
542 	}
543 }
544 
545 
546 void
547 Chart::_ScrollTo(float value, bool horizontal)
548 {
549 	float& scrollValue = horizontal ? fHScrollValue : fVScrollValue;
550 	if (value == scrollValue)
551 		return;
552 
553 	const ChartDataRange& range = horizontal ? fDomain : fRange;
554 	ChartDataRange displayRange = horizontal ? fDisplayDomain : fDisplayRange;
555 	float chartSize = horizontal ? fChartFrame.Width() : fChartFrame.Height();
556 	chartSize--;	// +1 for pixel size, -2 for border
557 	const float& scrollSize = horizontal ? fHScrollSize : fVScrollSize;
558 
559 	scrollValue = value;
560 	displayRange.OffsetTo(value / scrollSize
561 		* (range.Size() - displayRange.Size()));
562 	if (horizontal)
563 		SetDisplayDomain(displayRange);
564 	else
565 		SetDisplayRange(displayRange);
566 }
567 
568 
569 void
570 Chart::_Zoom(float x, float steps)
571 {
572 	double displayDomainSize = fDisplayDomain.Size();
573 	if (fDomainZoomLimit <= 0 || !fDomain.IsValid() || !fDisplayDomain.IsValid()
574 		|| steps == 0) {
575 		return;
576 	}
577 
578 	// compute the domain point where to zoom in
579 	float chartSize = fChartFrame.Width() - 1;
580 	x -= fChartFrame.left + 1;
581 	double domainPos = (fHScrollValue + x) / (fHScrollSize + chartSize)
582 		* fDomain.Size();
583 
584 	double factor = 2;
585 	if (steps < 0) {
586 		steps = -steps;
587 		factor = 1.0 / factor;
588 	}
589 
590 	for (; steps > 0; steps--)
591 		displayDomainSize *= factor;
592 
593 	if (displayDomainSize < fDomainZoomLimit)
594 		displayDomainSize = fDomainZoomLimit;
595 	if (displayDomainSize > fDomain.Size())
596 		displayDomainSize = fDomain.Size();
597 
598 	if (displayDomainSize == fDisplayDomain.Size())
599 		return;
600 
601 	domainPos -= displayDomainSize * x / chartSize;
602 	SetDisplayDomain(ChartDataRange(domainPos, domainPos + displayDomainSize));
603 		// will adjust the supplied display domain to fit the domain
604 }
605