icons
11 years ago
markers
10 years ago
GoogleChart.php
11 years ago
GoogleChartApi.php
11 years ago
GoogleChartAxis.php
11 years ago
GoogleChartData.php
11 years ago
GoogleChartIcon.php
11 years ago
GoogleChartMarker.php
11 years ago
LICENSE
11 years ago
GoogleChartData.php
718 lines
| 1 | <?php |
| 2 | |
| 3 | /** @file |
| 4 | * This file is part of Google Chart PHP library. |
| 5 | * |
| 6 | * Copyright (c) 2010 Rémi Lanvin <remi@cloudconnected.fr> |
| 7 | * |
| 8 | * Licensed under the MIT license. |
| 9 | * |
| 10 | * For the full copyright and license information, please view the LICENSE file. |
| 11 | */ |
| 12 | |
| 13 | /** |
| 14 | * A data serie. |
| 15 | * |
| 16 | * This class implement every feature that is directly related to a data serie |
| 17 | * or its representation in the chart. |
| 18 | * |
| 19 | * Some method won't work for all charts, but won't produce an error. |
| 20 | */ |
| 21 | class GoogleChartData |
| 22 | { |
| 23 | /** |
| 24 | * An array of the values of the data serie. |
| 25 | */ |
| 26 | protected $values = null; |
| 27 | /** |
| 28 | * The name of the data serie to be displayed as legend. |
| 29 | */ |
| 30 | protected $legend = null; |
| 31 | /** |
| 32 | * The label of the values of the data serie. Pie Chart only. |
| 33 | */ |
| 34 | protected $labels = null; |
| 35 | |
| 36 | /** |
| 37 | * Indicate if the color has been overridden. |
| 38 | * This variable is used to minimize the request. If no custom color has |
| 39 | * been providen, then the @c cho parameter is not triggered. |
| 40 | */ |
| 41 | protected $chco = false; |
| 42 | /** |
| 43 | * Color of the data serie (string or array) |
| 44 | * Default color by Google Chart API is ffcc33 |
| 45 | */ |
| 46 | protected $color = 'ffcc33'; |
| 47 | |
| 48 | /** |
| 49 | * Indicate if @c chls parameter is needed |
| 50 | */ |
| 51 | protected $chls = false; |
| 52 | |
| 53 | /** |
| 54 | * Thickness of the line. Line Chart only. (@c chls) |
| 55 | */ |
| 56 | protected $thickness = 2; |
| 57 | |
| 58 | /** |
| 59 | * Length of the dash. Line Chart only. (@c chls) |
| 60 | */ |
| 61 | protected $dash_length = null; |
| 62 | |
| 63 | /** |
| 64 | * Length of the spaces between dashes. Line Chart only. (@c chls) |
| 65 | */ |
| 66 | protected $space_length = null; |
| 67 | |
| 68 | /** |
| 69 | * Line fill values (to fill area below a line). (@c chm) |
| 70 | */ |
| 71 | protected $fill = null; |
| 72 | |
| 73 | protected $fill_slices = array(); |
| 74 | |
| 75 | /** |
| 76 | * bool Whether to calculate scale automatically or not. |
| 77 | */ |
| 78 | protected $autoscale = true; |
| 79 | /** |
| 80 | * array The scale, as specified by the user with setScale |
| 81 | */ |
| 82 | protected $scale = null; |
| 83 | |
| 84 | /** |
| 85 | * int Holds the index of the data serie in the chart. Null if not added. |
| 86 | */ |
| 87 | protected $index = null; |
| 88 | |
| 89 | /** |
| 90 | * Create a new data serie. |
| 91 | */ |
| 92 | public function __construct($values) |
| 93 | { |
| 94 | if ( $values !== null && ! is_array($values) ) |
| 95 | throw new InvalidArgumentException('Invalid values (must be an array or null)'); |
| 96 | |
| 97 | $this->values = $values; |
| 98 | } |
| 99 | |
| 100 | /** |
| 101 | * Returns the values of this dataserie |
| 102 | * @return array (or null) |
| 103 | */ |
| 104 | public function getValues() |
| 105 | { |
| 106 | return $this->values; |
| 107 | } |
| 108 | |
| 109 | /** |
| 110 | * @since 0.5 |
| 111 | */ |
| 112 | public function hasValues() |
| 113 | { |
| 114 | return $this->values !== null && ! empty($this->values); |
| 115 | } |
| 116 | |
| 117 | /** |
| 118 | * @since 0.5 |
| 119 | */ |
| 120 | public function computeChd($encoding = GoogleChart::TEXT, $scale = null) |
| 121 | { |
| 122 | // If scale is null, it means that there is not "global" scale for the chart |
| 123 | // Hence we need to determine the scale for this data only |
| 124 | if ( $scale === null ) { |
| 125 | $scale = $this->getScale(); |
| 126 | } |
| 127 | |
| 128 | switch ( $encoding ) { |
| 129 | case GoogleChart::TEXT : |
| 130 | return self::encodeText($this->values, $scale['min'], $scale['max']); |
| 131 | case GoogleChart::SIMPLE_ENCODING : |
| 132 | return self::encodeSimple($this->values, $scale['min'], $scale['max']); |
| 133 | case GoogleChart::EXTENDED_ENCODING : |
| 134 | return self::encodeExtended($this->values, $scale['min'], $scale['max']); |
| 135 | default: |
| 136 | throw new InvalidArgumentException('Invalid encoding format'); |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | |
| 141 | /** |
| 142 | * @name Pie Chart Labels @c chl |
| 143 | */ |
| 144 | //@{ |
| 145 | /** |
| 146 | * @since 0.5 |
| 147 | */ |
| 148 | public function setLabelsAuto() |
| 149 | { |
| 150 | return $this->setLabels(array_keys($this->values)); |
| 151 | } |
| 152 | |
| 153 | /** |
| 154 | * @since 0.5 |
| 155 | */ |
| 156 | public function setLabels($labels) |
| 157 | { |
| 158 | $n = sizeof($labels); |
| 159 | $v = sizeof($this->values); |
| 160 | if ( $n > $v ) { |
| 161 | throw new InvalidArgumentException('Invalid labels, to many labels'); |
| 162 | } |
| 163 | elseif ( $n < $v ) { |
| 164 | $labels += array_fill(0, $v-$n, ''); |
| 165 | } |
| 166 | |
| 167 | $this->labels = $labels; |
| 168 | return $this; |
| 169 | } |
| 170 | |
| 171 | /** |
| 172 | * Return labels set by setLabels() |
| 173 | * @return array(); |
| 174 | */ |
| 175 | public function getLabels() |
| 176 | { |
| 177 | return $this->labels; |
| 178 | } |
| 179 | |
| 180 | /** |
| 181 | * Compute @c chl parameter. |
| 182 | * |
| 183 | * Only for Pie Chart. |
| 184 | * |
| 185 | * If the chart has no label, this function returns a string containing |
| 186 | * an empty label for each value (example "|" for 2 values, "||" for 3, etc.). |
| 187 | * This way, labels are always in sync with the values. The case happens |
| 188 | * with a concentric chart, if the inner chart (first data serie) doesn't |
| 189 | * have label, but the outer chart (second data serie) has. |
| 190 | * |
| 191 | * @internal |
| 192 | * @since 0.5 |
| 193 | */ |
| 194 | public function computeChl() |
| 195 | { |
| 196 | if ( ! $this->values ) |
| 197 | return ''; |
| 198 | |
| 199 | if ( $this->labels === null ) { |
| 200 | return str_repeat('|',sizeof($this->values)-1); |
| 201 | } |
| 202 | return implode('|',$this->labels); |
| 203 | } |
| 204 | //@} |
| 205 | |
| 206 | /** |
| 207 | * Set the index of the data serie in the chart. |
| 208 | * |
| 209 | * @internal |
| 210 | * @note Used by GoogleChart when calling GoogleChart::addData() |
| 211 | * @param $index (int) |
| 212 | * @return $this |
| 213 | */ |
| 214 | public function setIndex($index) |
| 215 | { |
| 216 | if ( ! is_int($index) ) |
| 217 | throw new InvalidArgumentException('Invalid index (must be an integer)'); |
| 218 | |
| 219 | $this->index = (int) $index; |
| 220 | return $this; |
| 221 | } |
| 222 | |
| 223 | /** |
| 224 | * Return the index of the data serie in the chart (null if not in a chart). |
| 225 | * |
| 226 | * @return int or null |
| 227 | */ |
| 228 | public function getIndex() |
| 229 | { |
| 230 | return $this->index; |
| 231 | } |
| 232 | |
| 233 | /** |
| 234 | * Returns true if the data serie has an index, false otherwise. |
| 235 | * |
| 236 | * @return bool |
| 237 | */ |
| 238 | public function hasIndex() |
| 239 | { |
| 240 | return $this->index !== null; |
| 241 | } |
| 242 | |
| 243 | /** |
| 244 | * Enable/disabled autoscaling. |
| 245 | * @param $autoscale (bool) |
| 246 | * @return $this |
| 247 | */ |
| 248 | public function setAutoscale($autoscale) |
| 249 | { |
| 250 | $this->autoscale = $autoscale; |
| 251 | return $this; |
| 252 | } |
| 253 | |
| 254 | /** |
| 255 | * Set the scale of this data serie. |
| 256 | * When using this function, be sure your turned off global autoscaling. |
| 257 | * @see http://code.google.com/p/googlechartphplib/wiki/Autoscaling |
| 258 | * @param $min (int) |
| 259 | * @param $max (int) |
| 260 | */ |
| 261 | public function setScale($min, $max) |
| 262 | { |
| 263 | $this->setAutoscale(false); |
| 264 | $this->scale = array( |
| 265 | 'min' => $min, |
| 266 | 'max' => $max |
| 267 | ); |
| 268 | return $this; |
| 269 | } |
| 270 | |
| 271 | /** |
| 272 | * @since 0.5 |
| 273 | */ |
| 274 | public function getScale() |
| 275 | { |
| 276 | if ( $this->autoscale == true ) { |
| 277 | if ( ! empty($this->values) ) { |
| 278 | $n = min($this->values); |
| 279 | if ( $n > 0 ) |
| 280 | $n = 0; |
| 281 | return array('min' => $n, 'max' => max($this->values)); |
| 282 | } |
| 283 | } |
| 284 | |
| 285 | if ( $this->scale === null ) { |
| 286 | return array('min' => 0, 'max' => 100); |
| 287 | } |
| 288 | |
| 289 | return $this->scale; |
| 290 | } |
| 291 | |
| 292 | /** |
| 293 | * @since 0.5 |
| 294 | */ |
| 295 | public function computeChds() |
| 296 | { |
| 297 | $scale = $this->getScale(); |
| 298 | return $scale['min'].','.$scale['max']; |
| 299 | } |
| 300 | |
| 301 | /** |
| 302 | * @since 0.5 |
| 303 | */ |
| 304 | public function hasCustomScale() |
| 305 | { |
| 306 | return $this->scale !== null || $this->autoscale; |
| 307 | } |
| 308 | |
| 309 | /** |
| 310 | * Chart Legend (chdl) |
| 311 | * |
| 312 | * @param $legend (string) |
| 313 | */ |
| 314 | public function setLegend($legend) |
| 315 | { |
| 316 | $this->legend = $legend; |
| 317 | return $this; |
| 318 | } |
| 319 | |
| 320 | /** |
| 321 | * Return the legend. |
| 322 | * @return string |
| 323 | */ |
| 324 | public function getLegend() |
| 325 | { |
| 326 | return $this->legend; |
| 327 | } |
| 328 | |
| 329 | /** |
| 330 | * Return true if a legend has been set |
| 331 | * @return bool |
| 332 | */ |
| 333 | public function hasCustomLegend() |
| 334 | { |
| 335 | return $this->legend !== null; |
| 336 | } |
| 337 | |
| 338 | /** |
| 339 | * @name Data Serie Color (@c chco). |
| 340 | */ |
| 341 | //@{ |
| 342 | /** |
| 343 | * Set the serie color. |
| 344 | * Color can be an array for bar charts and pie charts. |
| 345 | * |
| 346 | * @param $color (mixed) a RRGGBB string, or an array for Bar Chart and Pie Chart |
| 347 | * @see http://code.google.com/apis/chart/docs/chart_params.html#gcharts_series_color |
| 348 | */ |
| 349 | public function setColor($color) |
| 350 | { |
| 351 | $this->chco = true; |
| 352 | $this->color = $color; |
| 353 | return $this; |
| 354 | } |
| 355 | |
| 356 | /** |
| 357 | * Return the serie colors. |
| 358 | * @return color |
| 359 | */ |
| 360 | public function getColor() |
| 361 | { |
| 362 | return $this->color; |
| 363 | } |
| 364 | |
| 365 | /** |
| 366 | * Compute the @c cho parameter. |
| 367 | * @internal |
| 368 | * @return string |
| 369 | */ |
| 370 | public function computeChco() |
| 371 | { |
| 372 | if ( is_array($this->color) ) |
| 373 | return implode('|',$this->color); |
| 374 | |
| 375 | return $this->color; |
| 376 | } |
| 377 | |
| 378 | /** |
| 379 | * Return true if parameter @chco is needed |
| 380 | * @return true |
| 381 | */ |
| 382 | public function hasChco() |
| 383 | { |
| 384 | return $this->chco; |
| 385 | } |
| 386 | //@} |
| 387 | |
| 388 | /** |
| 389 | * @name Line fill (chm). Line and radar charts only. |
| 390 | */ |
| 391 | //@{ |
| 392 | /** |
| 393 | * Line fill |
| 394 | * |
| 395 | * @param $color (string) RRGGBB color. Supports transparency if you uses |
| 396 | * RRGGBBAA format. |
| 397 | * |
| 398 | * @param $end_line (int) On a multi-line chart, if you want to fill only |
| 399 | * between two lines, you can specify the index of the line at which to stop |
| 400 | * the filling. |
| 401 | * |
| 402 | * @see http://code.google.com/apis/chart/docs/chart_params.html#gcharts_line_fills |
| 403 | */ |
| 404 | public function setFill($color, $end_line = null) |
| 405 | { |
| 406 | $this->fill = array( |
| 407 | 'color' => $color, |
| 408 | 'end_line' => $end_line |
| 409 | ); |
| 410 | } |
| 411 | |
| 412 | /** |
| 413 | * @since 0.5 |
| 414 | */ |
| 415 | public function addFillSlice($color, $start = null, $stop = null) |
| 416 | { |
| 417 | if ( $start !== null && ! is_numeric($start) ) { |
| 418 | throw new InvalidArgumentException('Invalid start index (must be NULL or numeric)'); |
| 419 | } |
| 420 | if ( $stop !== null && ! is_numeric($stop) ) { |
| 421 | throw new InvalidArgumentException('Invalid stop index (must be NULL or numeric)'); |
| 422 | } |
| 423 | |
| 424 | $this->fill_slices[] = array( |
| 425 | 'color' => $color, |
| 426 | 'start' => $start === null ? null : intval($start), |
| 427 | 'stop' => $stop === null ? null : intval($stop) |
| 428 | ); |
| 429 | } |
| 430 | |
| 431 | /** |
| 432 | * @todo Move to compute* |
| 433 | */ |
| 434 | public function computeChm($index) |
| 435 | { |
| 436 | if ( $this->fill === null && ! isset($this->fill_slices[0]) ) |
| 437 | return null; |
| 438 | |
| 439 | $fill = array(); |
| 440 | |
| 441 | if ( $this->fill !== null ) { |
| 442 | $fill[] = sprintf( |
| 443 | '%s,%s,%d,%d,0', |
| 444 | $this->fill['end_line'] === null ? 'B' : 'b', |
| 445 | $this->fill['color'], |
| 446 | $index, |
| 447 | $this->fill['end_line'] |
| 448 | ); |
| 449 | } |
| 450 | |
| 451 | if ( isset($this->fill_slices[0]) ) { |
| 452 | foreach ( $this->fill_slices as $f ) { |
| 453 | $fill[] = sprintf( |
| 454 | 'B,%s,%d,%s:%s,0', |
| 455 | $f['color'], |
| 456 | $index, |
| 457 | $f['start'], |
| 458 | $f['stop'] |
| 459 | ); |
| 460 | } |
| 461 | } |
| 462 | return implode('|',$fill); |
| 463 | } |
| 464 | //@} |
| 465 | |
| 466 | |
| 467 | /** |
| 468 | * @name Line styles (chls). |
| 469 | */ |
| 470 | // @{ |
| 471 | |
| 472 | /** |
| 473 | * Set the thickness of the line (Line Chart only). |
| 474 | * |
| 475 | * @see http://code.google.com/apis/chart/docs/chart_params.html#gcharts_line_styles |
| 476 | * @since 0.5 |
| 477 | */ |
| 478 | public function setThickness($thickness) |
| 479 | { |
| 480 | $this->chls = true; |
| 481 | |
| 482 | $this->thickness = $thickness; |
| 483 | return $this; |
| 484 | } |
| 485 | |
| 486 | /** |
| 487 | * @since 0.5 |
| 488 | */ |
| 489 | public function getThickness() |
| 490 | { |
| 491 | return $this->thickness; |
| 492 | } |
| 493 | |
| 494 | /** |
| 495 | * @since 0.5 |
| 496 | */ |
| 497 | public function setDash($dash_length, $space_length = null) |
| 498 | { |
| 499 | $this->chls = true; |
| 500 | |
| 501 | $this->dash_length = $dash_length; |
| 502 | $this->space_length = $space_length; |
| 503 | return $this; |
| 504 | } |
| 505 | |
| 506 | /** |
| 507 | * @internal |
| 508 | * @since 0.5 |
| 509 | */ |
| 510 | public function computeChls() |
| 511 | { |
| 512 | $str = $this->thickness; |
| 513 | if ( $this->dash_length !== null ) { |
| 514 | $str .= ','.$this->dash_length; |
| 515 | if ( $this->space_length !== null ) { |
| 516 | $str .= ','.$this->space_length; |
| 517 | } |
| 518 | } |
| 519 | return $str; |
| 520 | } |
| 521 | |
| 522 | /** |
| 523 | * @internal |
| 524 | * @since 0.5 |
| 525 | */ |
| 526 | public function hasChls() |
| 527 | { |
| 528 | return $this->chls; |
| 529 | } |
| 530 | //@} |
| 531 | |
| 532 | /** |
| 533 | * @internal |
| 534 | * @since 0.5 |
| 535 | */ |
| 536 | static public function encodeText(array $values) |
| 537 | { |
| 538 | foreach ( $values as & $v ) { |
| 539 | if ( $v === null ) { |
| 540 | $v = '_'; |
| 541 | } |
| 542 | else { |
| 543 | // We can't rely on PHP's default display for float values, as |
| 544 | // Float are actually displayed differently depending on the |
| 545 | // Current locale. |
| 546 | $v = number_format($v, 2, '.', ''); |
| 547 | } |
| 548 | } |
| 549 | return implode(',',$values); |
| 550 | } |
| 551 | |
| 552 | /** |
| 553 | * @internal |
| 554 | * @since 0.5 |
| 555 | */ |
| 556 | static public function encodeSimple(array $values, $min = null, $max = null) |
| 557 | { |
| 558 | if ( $min === null ) { |
| 559 | $min = min($values); |
| 560 | // By default, we only want a min if there is negative values |
| 561 | if ( $min > 0 ) { |
| 562 | $min = 0; |
| 563 | } |
| 564 | } |
| 565 | if ( $max === null ) { |
| 566 | $max = max($values); |
| 567 | } |
| 568 | $max = $max + abs($min); |
| 569 | |
| 570 | $map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; |
| 571 | $str = ''; |
| 572 | |
| 573 | foreach ( $values as $v ) { |
| 574 | if ( $v === null ) { |
| 575 | $str .= '_'; |
| 576 | continue; |
| 577 | } |
| 578 | |
| 579 | $n = round(61 * (($v - $min) / $max)); |
| 580 | if ( $n > 61 ) { |
| 581 | $str .= '9'; |
| 582 | } |
| 583 | elseif ( $n < 0 ) { |
| 584 | $str .= '_'; |
| 585 | } |
| 586 | else { |
| 587 | $str .= $map[$n]; |
| 588 | } |
| 589 | } |
| 590 | return $str; |
| 591 | } |
| 592 | |
| 593 | /** |
| 594 | * @internal |
| 595 | * @since 0.5 |
| 596 | */ |
| 597 | static public function encodeExtended(array $values, $min = null, $max = null) |
| 598 | { |
| 599 | if ( $min === null ) { |
| 600 | $min = min($values); |
| 601 | // By default, we only want a min if there is negative values |
| 602 | if ( $min > 0 ) { |
| 603 | $min = 0; |
| 604 | } |
| 605 | } |
| 606 | if ( $max === null ) { |
| 607 | $max = max($values); |
| 608 | } |
| 609 | $max = $max + abs($min); |
| 610 | if ( $max == 0 ) |
| 611 | return ''; |
| 612 | |
| 613 | $map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.'; |
| 614 | $str = ''; |
| 615 | |
| 616 | foreach ( $values as $v ) { |
| 617 | if ( $v === null ) { |
| 618 | $str .= '__'; |
| 619 | continue; |
| 620 | } |
| 621 | |
| 622 | $n = floor(64 * 64 * (($v - $min) / $max)); |
| 623 | if ( $n > (64*64 - 1) ) { |
| 624 | $str .= '..'; |
| 625 | } |
| 626 | elseif ( $n < 0 ) { |
| 627 | $str .= '__'; |
| 628 | } |
| 629 | else { |
| 630 | $q = floor($n / 64); |
| 631 | $r = $n - 64 * $q; |
| 632 | $str .= $map[$q].$map[$r]; |
| 633 | } |
| 634 | } |
| 635 | return $str; |
| 636 | } |
| 637 | |
| 638 | /** |
| 639 | * linear regression function |
| 640 | * |
| 641 | * @param $data array Points to calculate |
| 642 | * @returns array() m=>slope, b=>intercept |
| 643 | */ |
| 644 | static public function calculateLinearRegression($data) |
| 645 | { |
| 646 | // Calculate number points |
| 647 | $n = count($data); |
| 648 | |
| 649 | $x = array_keys($data); |
| 650 | $y = array_values($data); |
| 651 | |
| 652 | // Calculate sums |
| 653 | $x_sum = array_sum($x); |
| 654 | $y_sum = array_sum($y); |
| 655 | |
| 656 | $xx_sum = 0; |
| 657 | $xy_sum = 0; |
| 658 | |
| 659 | for($i = 0; $i < $n; $i++) { |
| 660 | |
| 661 | $xy_sum+=($x[$i]*$y[$i]); |
| 662 | $xx_sum+=($x[$i]*$x[$i]); |
| 663 | |
| 664 | } |
| 665 | |
| 666 | // Calculate slope |
| 667 | $m = (($n * $xy_sum) - ($x_sum * $y_sum)) / (($n * $xx_sum) - ($x_sum * $x_sum)); |
| 668 | |
| 669 | // Calculate intercept |
| 670 | $b = ($y_sum - ($m * $x_sum)) / $n; |
| 671 | |
| 672 | // Return result |
| 673 | return array($m, $b); |
| 674 | } |
| 675 | |
| 676 | /** |
| 677 | * Function that creates a new GoogleChartData element with trend points based |
| 678 | * on the current values. |
| 679 | * |
| 680 | * @return GoogleChartData Trend data |
| 681 | */ |
| 682 | public function createTrendData() |
| 683 | { |
| 684 | if(!$this->hasValues()) |
| 685 | return null; |
| 686 | |
| 687 | list($slope, $intercept) = self::calculateLinearRegression(array_values($this->values)); |
| 688 | |
| 689 | $n = sizeof($this->values); |
| 690 | $array = array(); |
| 691 | $v = $intercept; |
| 692 | |
| 693 | for ( $i = 1; $i <= $n; $i++ ) { |
| 694 | $v += $slope; |
| 695 | $array[] = round($v,2); |
| 696 | } |
| 697 | |
| 698 | return new self($array); |
| 699 | } |
| 700 | |
| 701 | /** |
| 702 | * Function that returns a LineMarker that indicates the trend of the contained |
| 703 | * data. |
| 704 | * |
| 705 | * @return GoogleChartLineMarker Trend line |
| 706 | */ |
| 707 | public function createTrendMarker() |
| 708 | { |
| 709 | if(!$this->hasValues()) |
| 710 | return null; |
| 711 | |
| 712 | $marker = new GoogleChartLineMarker(); |
| 713 | $marker->setData($this->createTrendData()); |
| 714 | |
| 715 | return $marker; |
| 716 | } |
| 717 | } |
| 718 |