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