Source for file Legend.php
Documentation is available at Legend.php
* Description: Accesses all Chart Legend characteristics
* @copyright (c) 1995-2010 by Steema Software SL. All Rights Reserved. <info@steema.com>
* @link http://www.steema.com
static $CHECKBOXSIZE = 11;
static $MAXLEGENDCOLUMNS = 2;
private $animations = Array();
private $resizeChart = true;
private $checkBoxes = false;
private $currentPage = true;
private $topLeftPos = 10;
private $maxNumRows = 10;
private $fontSeriesColor= null;
private $dividingLines= null;
function __get( $property ) {
$method = "get{$property}";
function __set ( $property,$value ) {
$method = "set{$property}";
return $this->$method($value);
$this->legendStyle = LegendStyle::$AUTO;
$this->alignment = LegendAlignment::$RIGHT;
$this->textAlign = Array(StringAlignment::$HORIZONTAL_LEFT_ALIGN,
StringAlignment::$VERTICAL_CENTER_ALIGN);
parent::TextShapePosition($c);
$tmpColor = new Color(0,0,0);
$this->getShadow()->getBrush()->setDefaultColor($tmpColor);
$this->items = Array(); // [100][10]
private function getFrame() {
* Determines how Legend text items will be formatted.<br>
* Plain shows the point Label only.<br>
* LeftValue shows the point Value and the point Label. <br>
* RightValue shows the point Label and the point Value. <br>
* LeftPercent shows the percent the point represents and the point Label. <br>
* RightPercent shows the point Label and the percent the points represent. <br>
* XValue shows the point's X value. It applies only to Series with X
* (horizontal) values. <br><br>
* Values are pre-formatted using the Series ValueFormat property. <br>
* Percents are pre-formatted using the Series PercentFormat property. <br><br>
* Default value: LeftValue
* @return LegendTextStyle
* Returns the index of the first displayed value at legend.
* The index can be a series value index or a series index depending
* Specifies how Legend text items will be formatted.<br>
* Default value: LeftValue
* @param value LegendTextStyle
if ($this->textStyle != $value) {
$this->textStyle = $value;
// if ($this->textAlign != $value) {
// $this->textAlign = array($value);
$this->textAlign= Array($value);
* Determines which series is used as data for the Legend entries.<br>
* By default, the Legend chooses the first Active Series with
* ShowInLegend:=true. It only applies to Legend style "Values".
* Determines which series is used as data for the Legend entries.<br>
* Enables/Disables the display of Legend check boxes.<br>
return $this->checkBoxes;
if ($this->symbol != null) {
* Displays the Legend check boxes when true.<br>
* Determines whether or not the Legend shows only the current page items
* when the Chart is divided into pages.<br>
return $this->currentPage;
* When true, the Legend shows only the current page items when the Chart
* is divided into pages.<br>
* The legend text font color to that of the Series color.<br>
return $this->fontSeriesColor;
* Sets the legend text font color to that of the Series color.<br>
* Specifies the Pen attributes used to draw lines separating Legend items.
* Lines are drawn horizontally for Left or Right aligned Legend
* and vertically for Top or Bottom Legend alignments.
if ($this->dividingLines == null) {
$tmpColor = new Color(0,0,0);
$this->dividingLines = new ChartPen($this->chart, $tmpColor, false);
return $this->dividingLines;
* Internal use - serialization
$this->dividingLines = $value;
* Controls the width and position of the color rectangle associated with
if ($this->symbol == null) {
* Internal use - serialization
* Draws the Legend items in opposite direction when true.<br>
* Legend strings are displayed starting at top for Left and Right Aligment
* and starting at left for Top and Bottom Legend orientations. You can use
* Legend.FirstValue to determine the ValueIndex for the first Legend
* Draws the Legend items in opposite direction when true.<br>
* Defines the Legend position.<br>
* Legend can be currently placed at Top, Left, Right and Bottom side of
* Left and Right Legend alignments define a vertical Legend with
* currently one single column of items. <br>
* Top and Bottom Legend alignments define an horizontal Legend with
* currently one single row of items. <br>
* The Legend itself automatically reduces the number of displayed
* legend items based on the available charting space. <br>
* The Legend.ResizeChart property controls if Legend dimensions should be
* used to reduce Chart points space. <br>
* The Legend.GetLegendRect event provides a mechanism to supply the
* desired Rectangle Legend dimensions and placement. <br>
* The Legend.GetLegendPos event can be used to specify fixed Legend items
* The Legend.HorizMargin and VertMargin properties control distance
* between Legend and Left and Right margins. <br>
* The Legend.TopLeftPos property can be used in Left Legend alignments
* to control vertical distance between Legend and Top Chart Margin. <br><br>
* These techniques allow almost complete Legend control. <br>
* @return LegendAlignment
* Defines the Legend position.<br>
* @param value LegendAlignments
* @see LegLegendAlignment
if ($this->alignment != $value) {
$this->alignment = $value;
if ($column < self::$MAXLEGENDCOLUMNS) {
if ($column < self::$MAXLEGENDCOLUMNS) {
* Automatically calculates best fit of legend columns.<br>
* When set to true, columnWidths control the legend width<br>
* Automatically calculates best fit of legend columns.<br>
* When set to true, columnWidths control the legend width<br>
* Defines which is the first Legend item displayed.<br>
* Legend can display all active Series names or all points of a single
* Series. FirstValue should be set accordingly, taking care not to
* overflow the number of active Series or the number of Series points. You
* can use FirstValue to show a specific subset of Series or points in
* Legend. It should be greater or equal to zero, and lower than the number
* of active Series or Series points. See Legend.LegendStyle for a
* description of the different Legend styles. <br>
* Determines which is the first Legend item displayed.<br>
* @see Legend#getFirstValue
* Specifies the Legend's top position in percent of total chart height.<br>
* It's used when Legend.Alignment is Left or Right only. For Top or
* Bottom Legend alignments, you can use the Chart's MarginTop and
return $this->topLeftPos;
* Specifies the Legend's top position in percent of total chart height.<br>
* The Maximum number of Legend Rows displayed for a horizontal Legend
* (Chart Top or Bottom).<br>
return $this->maxNumRows;
* Sets the Maximum number of Legend Rows displayed for a horizontal Legend
* (Chart Top or Bottom).<br>
return parent::getLines();
parent::setLines($value);
* Adds text to the Legend.
return parent::getText();
* Adds text to the Legend.
* The vertical spacing between Legend items (pixels).<br>
return $this->vertSpacing;
* Determines the vertical spacing between Legend items (pixels).<br>
* Speficies the number of screen pixels between Legend and Chart
* By default it is 0, meaning Legend will calculate a predefined margin
* based on total Legend width. It is only used when Legend position is
* Left or Right aligned otherwise use VertMargin. <br>
return $this->horizMargin;
* Speficies the number of screen pixels between Legend and Chart
* The vertical margin in pixels between Legend and Chart
* Legend.ResizeChart must be true and Legend.Alignment must be
* Top or Bottom. When 0, the corresponding Chart margin method is used to
* determine the amount of pixels for margins (Chart.MarginTop for Top
* Legend.alignment and Chart.MarginBottom for Bottom Legend.Alignment).<br>
return $this->vertMargin;
* Determines the vertical margin in pixels between Legend and Chart
* Automatically resizes Chart rectangle to prevent overlap with Legend.<br>
* When set to true, Legend.HorizMargin and Legend.VertMargin control the
* amount of pixels by which the Chart rectangle will be reduced. <br>
return $this->resizeChart;
* Automatically resizes Chart rectangle to prevent overlap with Legend.<br>
* When set to true, Legend.HorizMargin and Legend.VertMargin control the
* amount of pixels by which the Chart rectangle will be reduced. <br>
private function getLegendSeries() {
$tmpResult = $this->series;
if ($tmpResult == null) {
return $this->chart->getFirstActiveSeries();
* Defines which items will be displayed in the Chart Legend. <br>
* Series style shows the Series.Title of all active Series in a Chart.
* Whenever a Series Title is empty, Series Name is used. <br>
* Values style shows a text representation of all points of the first
* active Series in a Chart. <br>
* LastValues style shows the last point value and the Series.Title of all
* active Series in a Chart. It is useful for real-time charting, where
* new points are being added at the end of each Series. <br>
* Auto style (the default) means LegendStyle will be Series when there's
* more than one Active Series, and Values when there's only one Series
* Legend.TextStyle determines how the Series point values are formatted.<br>
return $this->legendStyle;
* Defines which items will be displayed in the Chart Legend. <br>
* @param value LegendStyles
* @see Legend#getLegendStyle
if ($this->legendStyle != $value) {
$this->legendStyle = $value;
$this->calcLegendStyle();
* Sets the Title text and its characteristics at the top of the legend
if($this->title == null) {
$s = $this->chart->seriesLegend($tmp, false);
$s->setActive(!$s->getActive());
private function clickedRow($tmpH, $y) {
for ( $t = 0; $t < $this->numRows; $t++ ) {
$tmp= round($this->getFirstItemTop()+ 1+ $t * $tmpH);
// review TODO before line... int tmp = getShapeBounds().getTop() + 1 + t * tmpH;
if (($y >= $tmp) && ($y <= ($tmp + $tmpH))) {
$result = $this->numRows - $t - 1;
* Returns the index of the clicked Legend Point.
return $this->clicked($p->x, $p->y);
* Returns the index of the clicked Legend Point.
if ($this->numRows > 0) {
$result = $this->clickedRow($tmpH, $y);
if ($this->numCols > 0) {
for ( $t = 0; $t < $this->numCols; $t++ ) {
if (($x >= $tmp2) && ($x <= ($tmp2 + $tmpW))) {
$result = $this->clickedRow($tmpH, $y);
$result *= $this->numCols;
$result += $this->numCols - $t - 1; // 5.02
if ($result > $this->iTotalItems - 1) {
$result = $this->chart->getGraphics3D()->getFontHeight();
$result = max(6 + self::$CHECKBOXSIZE, $result);
$result += $this->vertSpacing;
if ($this->getVertical() && ($this->dividingLines != null) &&
$this->dividingLines->getVisible())
private function calcLegendStyle() {
if (($this->checkBoxes) || ($this->chart->countActiveSeries() > 1)) {
if($this->getLegendSeries() instanceof Custom3DPalette) {
$this->iLegendStyle = LegendStyle::$PALETTE;
private function calcTotalItems() {
for ( $t = 0; $t < $this->chart->getSeriesCount(); $t++ ) {
$s = $this->chart->getSeries($t);
if (($this->checkBoxes || $s->getActive()) && $s->getShowInLegend()) {
$aSeries = $this->getLegendSeries();
if (($aSeries != null) && $aSeries->getShowInLegend()) {
$result = $aSeries->getCountLegendItems() - $this->firstValue;
* Returns true when the legend displays checkboxes and it is showing
* Is read only and returns true only if the legend is left or right
return ($this->alignment == LegendAlignment::$LEFT) ||
($this->alignment == LegendAlignment::$RIGHT);
private function calcSymbolTextPos($leftPos) {
$tmp = $leftPos + self::$LEGENDOFF2 + self::$LEGENDOFF4;
if ($this->getSymbol()->position == LegendSymbolPosition::$LEFT) {
$this->xLegendColor = $tmp;
$this->xLegendText = $this->xLegendColor + $this->iColorWidth + self::$LEGENDOFF4;
$this->xLegendText = $tmp;
$this->xLegendColor = $this->xLegendText + $this->tmpMaxWidth;
private function calcHorizontalPositions() {
$halfMaxWidth = 2 * self::$LEGENDOFF2 + 2 * self::$LEGENDOFF4 +
(($this->tmpMaxWidth + $this->iColorWidth) * $this->numCols) +
((self::$LEGENDOFF2 + self::$LEGENDOFF4) * ($this->numCols - 1));
$halfMaxWidth += self::$LEGENDOFF4 + (self::$CHECKBOXSIZE + self::$LEGENDOFF2) * $this->numCols;
$halfMaxWidth += self::$LEGENDOFF4 * 2;
$halfMaxWidth = min($this->chart->getWidth(), $halfMaxWidth);
($this->chart->getRight() - $this->chart->getLeft() -
2 * $halfMaxWidth) * 0.01); // 5.02
$this->setLeft($this->chart->getGraphics3D()->getXCenter() - $halfMaxWidth + $tmpW);
$this->xLegendBox = $this->shapeBounds->x + self::$LEGENDOFF4;
$tmpW += self::$CHECKBOXSIZE + self::$LEGENDOFF4;
$this->calcSymbolTextPos($tmpW);
private function calcColumnsWidth($numLegendValues) {
$g = $this->chart->getGraphics3D();
for ( $t = 0; $t < self::$MAXLEGENDCOLUMNS- 1; $t++ ) {
$result = $this->iSpaceWidth * self::$MAXLEGENDCOLUMNS - 1;
for ( $t = 0; $t < self::$MAXLEGENDCOLUMNS- 1; $t++ ) {
private function calcWidths() {
$this->tmpMaxWidth = $this->calcColumnsWidth($this->numRows);
$this->tmpTotalWidth = 2 * self::$LEGENDOFF4 + $this->tmpMaxWidth + self::$LEGENDOFF2;
$this->iColorWidth = $this->getSymbol()->calcWidth($this->tmpTotalWidth);
$this->tmpTotalWidth += $this->iColorWidth + 2; // 03 //# 2 pixels
$this->tmpTotalWidth= max($this->tmpTotalWidth,$this->getTitle()->getTotalWidth());
private function calcVerticalPositions() {
if ($this->getFrame()->getVisible()) {
if ($this->getFrame()->getVisible()) {
$this->shapeBounds->x -= self::$CHECKBOXSIZE + self::$LEGENDOFF4;
$this->xLegendBox = $tmpW + self::$LEGENDOFF4;
$tmpW = $this->xLegendBox + self::$CHECKBOXSIZE;
$this->calcSymbolTextPos($tmpW);
private function setRightAlign($column, $isRight) {
if ($this->textAlign==- 1)
$tmpAlign = Array(/*StringAlignment::$HORIZONTAL_LEFT_ALIGN, // RIGHT*/
StringAlignment::$VERTICAL_CENTER_ALIGN);
$this->chart->getGraphics3D()->setTextAlign($tmpAlign);
$this->chart->getGraphics3D()->setTextAlign($this->textAlign);
private function drawSymbol(Series $series, $index, $r) {
if ($this->iColorWidth > 0) {
$series->drawLegend(null, $index, $r);
$tmpColor = new Color(255,255,255); //white
$g= $this->chart->getGraphics3D();
$g->getBrush()->setForegroundColor($tmpColor);
$g->getBrush()->setSolid(true);
private function drawLegendItem($itemIndex, $itemOrder) {
$old_name = TChart::$controlName;
TChart::$controlName .= 'Legend_Item_' . $itemIndex;
if ($itemOrder >= $this->iTotalItems) {
$g = $this->chart->getGraphics3D();
$g->getBrush()->setForegroundColor($this->getColor());
$g->getBrush()->setVisible(false);
$this->prepareSymbolPen();
$tmp = $this->xLegendText;
$this->posYLegend= $this->getFirstItemTop()+ 1;
$posXColor = $this->xLegendColor;
(int) $tmpOrder = $itemOrder;
(int) $tmpOrder = $itemOrder / $this->numCols;
$tmpX = ($this->tmpMaxWidth + $this->iColorWidth + self::$LEGENDOFF4 + self::$LEGENDOFF2);
$tmpX += self::$CHECKBOXSIZE + 2 * self::$LEGENDOFF2;
$tmpX *= ($itemOrder % $this->numCols);
$this->posYLegend += (int) $tmpOrder * $this->itemHeight + ($this->vertSpacing / 2);
if ($this->chart->getParent() != null) {
$tmp, $this->posYLegend, $posXColor);
// TODO $res = $this->chart->getParent()->getLegendResolver()->getItemCoordinates($this, $res);
$this->posYLegend = $res->getY();
$posXColor = $res->getXColor();
$this->posXLegend = $tmp;
if ($this->fontSeriesColor) {
$font->setColor($this->chart->seriesLegend($itemIndex, !$this->checkBoxes)->
$font->setColor($this->tmpSeries->legendItemColor($itemIndex));
for ( $t = 0; $t < self::$MAXLEGENDCOLUMNS- 1; $t++ ) {
$tmpSt = $this->items[$itemOrder][$t];
$tmpAlign = Array(StringAlignment::$VERTICAL_CENTER_ALIGN);
$g->setTextAlign($tmpAlign);
if (($this->textStyle == LegendTextStyle::$XVALUE) ||
($this->textStyle == LegendTextStyle::$VALUE) ||
($this->textStyle == LegendTextStyle::$PERCENT) ||
($this->textStyle == LegendTextStyle::$XANDVALUE) ||
($this->textStyle == LegendTextStyle::$XANDPERCENT) ||
($this->textStyle == LegendTextStyle::$LEFTPERCENT) ||
($this->textStyle == LegendTextStyle::$LEFTVALUE)
$this->setRightAlign($t, true);
$this->setRightAlign($t, false);
$this->setRightAlign($t, true);
$this->setRightAlign($t, false);
$tmpBox = $this->hasCheckBoxes() ? $this->posYLegend + 1 : $this->posYLegend;
$g->textOut($this->posXLegend + 3, $tmpBox+ 1, 0, $tmpSt);
$this->posXLegend += $this->iSpaceWidth;
$r = new Rectangle($posXColor,($this->posYLegend + 1),$this->iColorWidth,
($this->itemHeight + 1));
if ((!$symbol->continuous) || ($itemOrder == 0)) {
$r->height -= 1 + 2 + $this->vertSpacing;
$tmpBox = $this->xLegendBox;
$this->tmpSeries = $this->chart->seriesLegend($itemIndex, false);
Utils:drawCheckBox($tmpBox, $this->posYLegend + (($this->itemHeight - $this->vertSpacing -
self::$CHECKBOXSIZE) / 2) - 1, $g, $this->tmpSeries->getActive(),$checkStyle, $this->getColor());
$this->drawSymbol($this->tmpSeries, - 1, $r);
$this->drawSymbol($this->chart->activeSeriesLegend($itemIndex), - 1, $r);
if ($this->tmpSeries != null) {
$this->drawSymbol($this->tmpSeries, $this->tmpSeries->legendToValueIndex($itemIndex),
$this->drawSymbol(null, - 1, $r);
if (($itemOrder > 0) && ($this->dividingLines != null) &&
$this->dividingLines->getVisible()) {
$g->setPen($this->dividingLines);
$this->posYLegend - ($this->vertSpacing / 2));
$g->verticalLine($this->getShapeBounds()->getLeft() + $tmpX + self::$LEGENDOFF2,
TChart::$controlName= $old_name;
private function drawItems() {
$this->tmpSeries = $this->getLegendSeries();
$this->drawLegendItem($t, ($this->iLastValue - $t));
$this->drawLegendItem($t, ($t - $this->firstValue));
private function setItem($index, $pos) {
$tmpSt = $this->chart->formattedLegend($pos); /* 5.01 */
$this->items[$index][$tmp] = substr($tmpSt,0, $i);
$tmpSt = substr($tmpSt,$i + 1);
} while (!(($i == false) || (strlen($tmpSt) == 0) || ($tmp > 1)));
if ((strlen($tmpSt) != 0) && ($tmp <= 1)) {
$this->items[$index][$tmp] = $tmpSt;
private function getItems() {
private function calcMaxLegendValues($yLegend, $a, $b, $c, $itemHeight) {
if (($yLegend < $a) && ($itemHeight > 0)) {
$result = (int) ((($b - 2 * $this->getFrame()->getWidth()) -
return min($result, $this->iTotalItems);
private function maxLegendValues($yLegend, $itemHeight) {
$tmp= $this->chart->getChartRect();
return $this->calcMaxLegendValues($yLegend, $tmp->getBottom(),
return $this->calcMaxLegendValues($yLegend, $tmp->getRight(),
private function resizeVertical() {
$tmp = 2 * self::$LEGENDOFF2 + $this->itemHeight * $this->numRows;
$tmp += $this->getTitle()->getHeight()+ 2;
if (! $this->getTitle()->getTransparent())
$tmp+= abs($this->getTitle()->getShadow()->getVertSize());
private function calcMargin($margin, $defaultMargin, $size) {
return ($margin == 0) ? $defaultMargin * $size / 100 : $margin;
* Returns the chart rectangle minus the space occupied by the Legend.
$rect->width -= $rect->x;
if (($this->getShadow()->getVisible()) && ($this->shadow->getWidth() < 0)) {
$rect->width += $this->shadow->getWidth();
$rect->y += max(0, $this->getShadow()->getHeight());
$rect->height -= ($rect->y - $tmpRY);
private function getFirstItemTop() {
$result= $result + $this->getTitle()->getHeight();
if (!$this->getTitle()->getTransparent()) {
$result = $result + abs($this->getTitle()->getShadow()->getHeight());
public function paint($g, $rect) {
TChart::$controlName .= 'Legend_';
$this->calcLegendStyle();
($this->chart->getPage()->getMaxPointsPerPage() > 0);
$this->chart->getPage()->getMaxPointsPerPage();
$this->iTotalItems = $this->calcTotalItems();
$this->iTotalItems = min($this->iTotalItems,
$this->chart->getPage()->getMaxPointsPerPage());
// calculate default Height for each Legend Item
// calculate Frame Width offset
if ($this->getFrame()->getVisible()) {
$this->frameWidth = $this->getBevel()->getWidth();
$this->numRows= $this->maxLegendValues($this->getFirstItemTop(),$this->itemHeight);
$this->getItems(); // Visible !!
$this->getItems(); // All !!
$this->tmpMaxWidth = $this->calcColumnsWidth(self::$ALLVALUES);
$this->iColorWidth = $this->getSymbol()->calcWidth($this->tmpMaxWidth);
$this->setBottom($rect->getBottom() - $this->frameWidth - 1);
$this->shapeBounds->y = $rect->y + $this->frameWidth + 1;
$tmpCol = $this->tmpMaxWidth + $this->iColorWidth + 2 * self::$LEGENDOFF4;
$tmpCol += self::$CHECKBOXSIZE + 2 * self::$LEGENDOFF2 + 3 * self::$LEGENDOFF4;
$this->numCols = $this->maxLegendValues(2 * self::$LEGENDOFF4, $tmpCol);
if ($this->numCols > 0) {
$this->numRows = $this->iTotalItems / $this->numCols;
if (($this->iTotalItems % $this->numCols) > 0) {
$this->numRows = min($this->numRows, $this->maxNumRows);
/* adjust the last index now we know the max number of rows... */
$this->numCols * $this->numRows) - 1;
$this->calcVerticalPositions();
$this->calcHorizontalPositions();
if ($this->chart->getParent() != null) {
/* TODO $this->setShapeBounds($this->chart->getParent()->getLegendResolver()->getBounds(
$this->getShapeBounds()));
$g->getFont()->assign($this->getFont());
return $this->animations;
$this->animations[] = $animation;
* Returns the text string corresponding to a Legend position.<br>
* The Legend position depends on Legend.LegendStyle. If LegendStyle is
* lsSeries, then the text string will be the SeriesOrValueIndexth Active
* Series Title.<br> If LegendStyle is lsValues, then the text string will
* be the formatted SeriesOrValueIndexth value of the first Active Series
* If LegendStyle is lsAuto and only one Active Series exists in the Chart,
* then the LegendStyle is considered to be lsValues.<br>
* If there is more than one Active Series then LegendStyle will be
* Values are formatted accordingly to LegendTextStyle.
* @param seriesOrValueIndex int
return $c->getSeriesTitleLegend($seriesOrValueIndex, !$this->getCheckBoxes());
return $c->formattedValueLegend($this->getLegendSeries(), $seriesOrValueIndex);
return $c->formattedValueLegend($c->getSeries($seriesOrValueIndex),
$c->getSeries($seriesOrValueIndex)->
return $c->formattedValueLegend($this->getLegendSeries(), $seriesOrValueIndex);
private function prepareSymbolPen() {
$this->chart->setLegendPen($this->getSymbol()->getDefaultPen() ? null :
private function removeChar($c, $s) {
while (($i = strpos($s,$c)) !== false) {
* Returns the corresponding Legend text for the Series ValueIndex point.
* Legend.LegendTextStyle is used to properly format the point values
if ($valueIndex != self::$ALLVALUES) {
$tmpResult = $aSeries->getLegendString($valueIndex, $this->getTextStyle());
// eliminate breaks in String... }
return $this->removeChar(Language::getString("LineSeparator"), $tmpResult);
|