scale and previousScale arguments contain the
* new and previous scale.
*
* mxEvent.TRANSLATE fires after the translate was changed in setTranslate. The
* translate and previousTranslate arguments contain
* the new and previous value for translate.
*
* Variable: EMPTY_POINT
*
* An empty instance.
*/
var $EMPTY_POINT;
/**
* Variable: graph
*
* Holds the .
*/
var $graph;
/**
* Variable: graphBounds
*
* Holds the bounds of the current view.
*/
var $graphBounds;
/**
* Variable: scale
*
* Holds the current scale.
*/
var $scale = 1;
/**
* Variable: translate
*
* Holds the current translate.
*/
var $translate;
/**
* Variable: states
*
* Maps from cells to states.
*/
var $states = array();
/**
* Constructor: mxGraphView
*
* Constructs a new view for the specified .
*/
function mxGraphView($graph)
{
$this->EMPTY_POINT = new mxPoint();
$this->graph = $graph;
$this->translate = new mxPoint();
$this->graphBounds = new mxRectangle();
}
/**
* Function: setScale
*
* Sets the scale, revalidates the view and fires
* a scale event.
*/
function setScale($scale)
{
$previous = $this->scale;
if ($this->scale != $scale)
{
$this->scale = $scale;
$this->revalidate();
}
$this->fireEvent(new mxEventObject(mxEvent::$SCALE, "scale", $scale, "previousScale", $previous));
}
/**
* Function: setTranslate
*
* Sets the translation, revalidates the view and
* fires a translate event.
*/
function setTranslate($translate)
{
$previous = $this->translate;
if ($this->translate->x != $translate->x ||
$this->translate->y != $translate->y)
{
$this->translate = $translate;
$this->revalidate();
}
$this->fireEvent(new mxEventObject(mxEvent::$TRANSLATE, "translate", $translate, "previousTranslate", $previous));
}
/**
* Function: getGraphBounds
*
* Returns .
*/
function getGraphBounds()
{
return $this->graphBounds;
}
/**
* Function: setGraphBounds
*
* Sets .
*/
function setGraphBounds($value)
{
$this->graphBounds = $value;
}
/**
* Function: getBounds
*
* Returns the bounding for for an array of cells or null, if no cells are
* specified.
*/
function getBounds($cells, $boundingBox = false)
{
$cellCount = sizeof($cells);
$result = null;
if ($cellCount > 0)
{
$model = $this->graph->getModel();
for ($i = 0; $i < $cellCount; $i++)
{
if ($model->isVertex($cells[$i]) || $model->isEdge($cells[$i]))
{
$state = $this->getState($cells[$i]);
if ($state != null)
{
$bounds = ($boundingBox) ? $state->boundingBox : $state;
if ($bounds != null)
{
if ($result == null)
{
$result = new mxRectangle($bounds->x, $bounds->y,
$bounds->width, $bounds->height);
}
else
{
$result->add($bounds);
}
}
}
}
}
}
return $result;
}
/**
* Function: invalidate
*/
function revalidate()
{
$this->invalidate();
$this->validate();
}
/**
* Function: invalidate
*
* Invalidates the cached cell states.
*/
function invalidate()
{
// LATER: Invalidate cell states recursively
$this->states = array();
}
/**
* Function: validate
*
* Calls and and updates the
* using . Finally the background is validated using
* .
*
* Parameters:
*
* cell - Optional to be used as the root of the validation.
* Default is the root of the model.
*/
function validate($cell = null)
{
// Checks if cache is invalid
if (sizeof($this->states) == 0)
{
$graphBounds = $this->getBoundingBox($this->validateCellState(
$this->validateCell(($cell != null) ? $cell : $this->graph->model->root)));
$this->setGraphBounds(isset($graphBounds) ? $graphBounds : new mxRectangle());
}
}
/**
* Function: getBoundingBox
*
* Returns the bounding box of the shape and the label for the given
* and its children if recurse is true.
*
* Parameters:
*
* state - whose bounding box should be returned.
* recurse - Optional boolean indicating if the children should be included.
* Default is true.
*/
function getBoundingBox($state, $recurse = true)
{
$bbox = null;
if ($state != null)
{
if ($state->boundingBox != null)
{
$bbox = $state->boundingBox->copy();
}
if ($recurse)
{
$model = $this->graph->getModel();
$childCount = $model->getChildCount($state->cell);
for ($i = 0; $i < $childCount; $i++)
{
$bounds = $this->getBoundingBox($this->getState($model->getChildAt($state->cell, $i)));
if ($bounds != null)
{
if ($bbox == null)
{
$bbox = $bounds;
}
else
{
$bbox->add($bounds);
}
}
}
}
}
return $bbox;
}
/**
* Function: validateCell
*
* Recursively creates the cell state for the given cell if visible is true and
* the given cell is visible. If the cell is not visible but the state exists
* then it is removed using .
*
* Parameters:
*
* cell - whose should be created.
* visible - Optional boolean indicating if the cell should be visible. Default
* is true.
*/
function validateCell($cell, $visible = true)
{
if ($cell != null)
{
$visible = $visible && $this->graph->isCellVisible($cell);
$state = $this->getState($cell, $visible);
if ($state != null && !$visible)
{
$this->removeState($cell);
}
else
{
$model = $this->graph->getModel();
$childCount = $model->getChildCount($cell);
for ($i = 0; $i < $childCount; $i++)
{
$this->validateCell($model->getChildAt($cell, $i), $visible &&
!$this->graph->isCellCollapsed($cell));
}
}
}
return $cell;
}
/**
* Function: validateCellStates
*
* Validates and repaints the for the given .
*
* Parameters:
*
* cell - whose should be validated.
* recurse - Optional boolean indicating if the children of the cell should be
* validated. Default is true.
*/
function validateCellState($cell, $recurse = true)
{
$state = null;
if ($cell != null)
{
$state = $this->getState($cell);
if ($state != null)
{
$model = $this->graph->getModel();
if ($state->invalid)
{
$state->invalid = false;
$this->validateCellState($model->getParent($cell), false);
$source = $this->validateCellState($this->getVisibleTerminal($cell, true), false);
$target = $this->validateCellState($this->getVisibleTerminal($cell, false), false);
$this->updateCellState($state, $source, $target);
if ($model->isEdge($cell) || $model->isVertex($cell))
{
$this->updateLabelBounds($state);
$this->updateBoundingBox($state);
}
}
if ($recurse)
{
$childCount = $model->getChildCount($cell);
for ($i = 0; $i < $childCount; $i++)
{
$this->validateCellState($model->getChildAt($cell, $i));
}
}
}
}
return $state;
}
/**
* Function: updateCellState
*
* Updates the given .
*
* Parameters:
*
* state - to be updated.
* source - that represents the visible source.
* target - that represents the visible target.
*/
function updateCellState($state, $source, $target)
{
$state->absoluteOffset->x = 0;
$state->absoluteOffset->y = 0;
$state->origin->x = 0;
$state->origin->y = 0;
$state->length = 0;
$model = $this->graph->getModel();
$pState = $this->getState($model->getParent($state->cell));
if ($pState != null)
{
$state->origin->x += $pState->origin->x;
$state->origin->y += $pState->origin->y;
}
$offset = $this->graph->getChildOffsetForCell($state->cell);
if ($offset != null)
{
$state->origin->x += $offset->x;
$state->origin->y += $offset->y;
}
$geo = $this->graph->getCellGeometry($state->cell);
if ($geo != null)
{
if (!$model->isEdge($state->cell))
{
$offset = $geo->offset;
if ($offset == null)
{
$offset = $this->EMPTY_POINT;
}
if ($geo->relative && $pState != null)
{
if ($model->isEdge($pState->cell))
{
$origin = $this->getPoint($pState, $geo);
if ($origin != null)
{
$state->origin->x += ($origin->x / $this->scale) - $pState->origin->x - $this->translate->x;
$state->origin->y += ($origin->y / $this->scale) - $pState->origin->y - $this->translate->y;
}
}
else
{
$state->origin->x += $geo->x * $pState->width / $this->scale + $offset->x;
$state->origin->y += $geo->y * $pState->height / $this->scale + $offset->y;
}
}
else
{
$state->absoluteOffset->x = $this->scale * $offset->x;
$state->absoluteOffset->y = $this->scale * $offset->y;
$state->origin->x += $geo->x;
$state->origin->y += $geo->y;
}
}
$state->x = $this->scale * ($this->translate->x + $state->origin->x);
$state->y = $this->scale * ($this->translate->y + $state->origin->y);
$state->width = $this->scale * $geo->width;
$state->height = $this->scale * $geo->height;
if ($model->isVertex($state->cell))
{
$this->updateVertexState($state, $geo);
}
if ($model->isEdge($state->cell))
{
$this->updateEdgeState($state, $geo, $source, $target);
}
}
}
/**
* Function: updateVertexState
*
* Validates the given cell state.
*/
function updateVertexState($state, $geo)
{
// LATER: Add support for rotation
$this->updateVertexLabelOffset($state);
}
/**
* Function: updateEdgeState
*
* Validates the given cell state.
*/
function updateEdgeState($state, $geo, $source, $target)
{
// This will remove edges with no terminals and no terminal points
// as such edges are invalid and produce NPEs in the edge styles.
// Also removes connected edges that have no visible terminals.
if (($this->graph->model->getTerminal($state->cell, true) != null && $source == null) ||
($source == null && $geo->getTerminalPoint(true) == null) ||
($this->graph->model->getTerminal($state->cell, false) != null && $target == null) ||
($target == null && $geo->getTerminalPoint(false) == null))
{
$this->removeState($state->cell, true);
}
else
{
$this->updateFixedTerminalPoints($state, $source, $target);
$this->updatePoints($state, $geo->points, $source, $target);
$this->updateFloatingTerminalPoints($state, $source, $target);
$pts = $state->absolutePoints;
if ($pts == null || sizeof($pts) < 1 || $pts[0] == null || $pts[sizeof($pts) - 1] == null)
{
// This will remove edges with invalid points from the list of states in the view.
// Happens if the one of the terminals and the corresponding terminal point is null.
$this->removeState($state->cell, true);
}
else
{
$this->updateEdgeBounds($state);
$state->absoluteOffset = $this->getPoint($state, $geo);
}
}
}
/**
* Function: updateVertexLabelOffset
*
* Updates the absoluteOffset of the given vertex cell state. This takes
* into account the label position styles.
*
* Parameters:
*
* state - whose absolute offset should be updated.
*/
function updateVertexLabelOffset($state)
{
$horizontal = mxUtils::getValue($state->style,
mxConstants::$STYLE_LABEL_POSITION,
mxConstants::$ALIGN_CENTER);
if ($horizontal == mxConstants::$ALIGN_LEFT)
{
$state->absoluteOffset->x -= $state->width;
}
else if ($horizontal == mxConstants::$ALIGN_RIGHT)
{
$state->absoluteOffset->x += $state->width;
}
$vertical = mxUtils::getValue($state->style,
mxConstants::$STYLE_VERTICAL_LABEL_POSITION,
mxConstants::$ALIGN_MIDDLE);
if ($vertical == mxConstants::$ALIGN_TOP)
{
$state->absoluteOffset->y -= $state->height;
}
else if ($vertical == mxConstants::$ALIGN_BOTTOM)
{
$state->absoluteOffset->y += $state->height;
}
}
/**
* Function: updateLabelBounds
*
* Updates the label bounds in the given state.
*/
function updateLabelBounds($state)
{
$cell = $state->cell;
$style = $state->style;
if (mxUtils::getValue($style, mxConstants::$STYLE_OVERFLOW) == "fill")
{
$state->labelBounds = new mxRectangle($state->x, $state->y, $state->width, $state->height);
}
else
{
$label = $this->graph->getLabel($cell);
$vertexBounds = (!$this->graph->model->isEdge($cell)) ? $state : null;
$state->labelBounds = mxUtils::getLabelPaintBounds($label, $style,
false, $state->absoluteOffset, $vertexBounds, $this->scale);
}
}
/**
* Function: updateBoundingBox
*
* Updates the bounding box in the given cell state.
*/
function updateBoundingBox($state)
{
// Gets the cell bounds and adds shadows and markers
$rect = new mxRectangle($state->x, $state->y, $state->width, $state->height);
$style = $state->style;
// Adds extra pixels for the marker and stroke assuming
// that the border stroke is centered around the bounds
// and the first pixel is drawn inside the bounds
$strokeWidth = max(1, mxUtils::getNumber($style,
mxConstants::$STYLE_STROKEWIDTH, 1) * $this->scale);
$strokeWidth -= max(1, $strokeWidth / 2);
if ($this->graph->model->isEdge($state->cell))
{
$ms = 0;
if (isset($style[mxConstants::$STYLE_ENDARROW])
|| isset($style[mxConstants::$STYLE_STARTARROW]))
{
$ms = round(mxConstants::$DEFAULT_MARKERSIZE * $this->scale);
}
// Adds the strokewidth
$rect->grow($ms + $strokeWidth);
// Adds worst case border for an arrow shape
if (mxUtils::getValue($style, mxConstants::$STYLE_SHAPE) ==
mxConstants::$SHAPE_ARROW)
{
$rect->grow(mxConstants::$ARROW_WIDTH / 2);
}
}
else
{
$rect->grow($strokeWidth);
}
// Adds extra pixels for the shadow
if (mxUtils::getValue($style, mxConstants::$STYLE_SHADOW, false) == true)
{
$rect->width += mxConstants::$SHADOW_OFFSETX;
$rect->height += mxConstants::$SHADOW_OFFSETY;
}
// Adds oversize images in labels
if (mxUtils::getValue($style, mxConstants::$STYLE_SHAPE) ==
mxConstants::$SHAPE_LABEL)
{
if (mxUtils::getValue($style, mxConstants::$STYLE_IMAGE) != null)
{
$w = mxUtils::$getValue($style,
mxConstants::$STYLE_IMAGE_WIDTH,
mxConstants::$DEFAULT_IMAGESIZE) * $this->scale;
$h = mxUtils::$getValue($style,
mxConstants::$STYLE_IMAGE_HEIGHT,
mxConstants::$DEFAULT_IMAGESIZE) * $this->scale;
$x = $state->x;
$y = 0;
$imgAlign = mxUtils::getValue($style, mxConstants::$STYLE_IMAGE_ALIGN,
mxConstants::$ALIGN_CENTER);
$imgValign = mxUtils::getValue(style,
mxConstants::$STYLE_IMAGE_VERTICAL_ALIGN,
mxConstants::$ALIGN_MIDDLE);
if ($imgAlign == mxConstants::$ALIGN_RIGHT)
{
$x += $state->width - $w;
}
else if ($imgAlign == mxConstants::$ALIGN_CENTER)
{
$x += ($state->width - $w) / 2;
}
if ($imgValign == mxConstants::$ALIGN_TOP)
{
$y = $state->y;
}
else if ($imgValign == mxConstants::$ALIGN_BOTTOM)
{
$y = $state->y + $state->height - $h;
}
else
{
$y = $state->y + ($state->height - $h) / 2;
}
$rect->add(new mxRectangle($x, $y, $w, $h));
}
}
// No need to add rotated rectangle bounds here because
// GD does not support rotation
// Unifies the cell bounds and the label bounds
$rect->add($state->labelBounds);
$state->boundingBox = $rect;
return $rect;
}
/**
* Function: updateFixedTerminalPoints
*
* Sets the initial absolute terminal points in the given state before the edge
* style is computed.
*
* Parameters:
*
* edge - whose initial terminal points should be updated.
* source - which represents the source terminal.
* target - which represents the target terminal.
*/
function updateFixedTerminalPoints($edge, $source, $target)
{
$this->updateFixedTerminalPoint($edge, $source, true,
$this->graph->getConnectionConstraint($edge, $source, true));
$this->updateFixedTerminalPoint($edge, $target, false,
$this->graph->getConnectionConstraint($edge, $target, false));
}
/**
* Function: updateFixedTerminalPoint
*
* Sets the fixed source or target terminal point on the given edge.
*
* Parameters:
*
* edge - whose terminal point should be updated.
* terminal - which represents the actual terminal.
* source - Boolean that specifies if the terminal is the source.
* constraint - that specifies the connection.
*/
function updateFixedTerminalPoint($edge, $terminal, $source, $constraint)
{
$pt = null;
if (isset($constraint))
{
$pt = $this->graph->getConnectionPoint($terminal, $constraint);
}
if (!isset($pt) && !isset($terminal))
{
$s = $this->scale;
$tr = $this->translate;
$orig = $edge->origin;
$geo = $this->graph->getCellGeometry($edge->cell);
$pt = $geo->getTerminalPoint($source);
if (isset($pt))
{
$pt = new mxPoint($s * ($tr->x + $pt->x + $orig->x),
$s * ($tr->y + $pt->y + $orig->y));
}
}
if (!is_array($edge->absolutePoints))
{
$edge->absolutePoints = array();
}
$n = sizeof($edge->absolutePoints);
if ($source)
{
if ($n > 0)
{
$state->absolutePoints[0] = $pt;
}
else
{
array_push($edge->absolutePoints, $pt);
}
}
else
{
$n = sizeof($edge->absolutePoints);
if ($n > 1)
{
$edge->absolutePoints[$n - 1] = $pt;
}
else
{
array_push($edge->absolutePoints, $pt);
}
}
}
/**
* Function: updatePoints
*
* Updates the absolute points in the given state using the specified array
* of as the relative points.
*
* Parameters:
*
* edge - whose absolute points should be updated.
* points - Array of that constitute the relative points.
* source - that represents the source terminal.
* target - that represents the target terminal.
*/
function updatePoints($edge, $points, $source, $target)
{
if (isset($edge))
{
$pts = array();
array_push($pts, $edge->absolutePoints[0]);
$edgeStyle = $this->getEdgeStyle($edge, $points, $source, $target);
if (isset($edgeStyle))
{
$src = $this->getTerminalPort($edge, $source, true);
$trg = $this->getTerminalPort($edge, $target, false);
$edgeStyle->apply($edge, $src, $trg, $points, $pts);
}
else if (isset($points))
{
for ($i = 0; $i < sizeof($points); $i++)
{
if (isset($points[$i]))
{
$pt = $points[$i]->copy();
array_push($pts, $this->transformControlPoint($edge, $pt));
}
}
}
$n = sizeof($edge->absolutePoints);
array_push($pts, $edge->absolutePoints[$n-1]);
$edge->absolutePoints = $pts;
}
}
/**
* Function: transformControlPoint
*
* Transforms the given control point to an absolute point.
*/
function transformControlPoint($state, $pt)
{
$orig = $state->origin;
return new mxPoint($this->scale * ($pt->x + $this->translate->x + $orig->x),
$this->scale * ($pt->y + $this->translate->y + $orig->y));
}
/**
* Function: getEdgeStyle
*
* Returns the edge style function to be used to render the given edge
* state.
*/
function getEdgeStyle($edge, $points, $source, $target)
{
$edgeStyle = null;
if (isset($source) && $source === $target)
{
$edgeStyle = mxUtils::getValue($edge->style, mxConstants::$STYLE_LOOP);
if (!isset($edgeStyle))
{
$edgeStyle = $this->graph->defaultLoopStyle;
}
}
else if (!mxUtils::getValue($edge->style, mxConstants::$STYLE_NOEDGESTYLE, false))
{
$edgeStyle = mxUtils::getValue($edge->style, mxConstants::$STYLE_EDGE);
}
// Converts string values to objects
if (is_string($edgeStyle))
{
$tmp = mxStyleRegistry::getValue($edgeStyle);
if ($tmp == null && strpos($edgeStyle, ".") !== false)
{
$tmp = mxUtils::evaluate($edgeStyle);
}
$edgeStyle = $tmp;
}
if ($edgeStyle instanceof mxEdgeStyleFunction)
{
return $edgeStyle;
}
return null;
}
/**
* Function: updateFloatingTerminalPoints
*
* Updates the terminal points in the given state after the edge style was
* computed for the edge.
*
* Parameters:
*
* state - whose terminal points should be updated.
* source - that represents the source terminal.
* target - that represents the target terminal.
*/
function updateFloatingTerminalPoints($state, $source, $target)
{
$pts = $state->absolutePoints;
$p0 = $pts[0];
$pe = $pts[sizeof($pts) - 1];
if (!isset($pe) && isset($target))
{
$this->updateFloatingTerminalPoint($state, $target, $source, false);
}
if (!isset($p0) && isset($source))
{
$this->updateFloatingTerminalPoint($state, $source, $target, true);
}
}
/**
* Function: updateFloatingTerminalPoint
*
* Updates the absolute terminal point in the given state for the given
* start and end state, where start is the source if source is true.
*
* Parameters:
*
* edge - whose terminal point should be updated.
* start - for the terminal on "this" side of the edge.
* end - for the terminal on the other side of the edge.
* source - Boolean indicating if start is the source terminal state.
*/
function updateFloatingTerminalPoint($edge, $start, $end, $source)
{
$start = $this->getTerminalPort($edge, $start, $source);
$next = $this->getNextPoint($edge, $end, $source);
$border = mxUtils::getNumber($edge->style, mxConstants::$STYLE_PERIMETER_SPACING);
$border = mxUtils::getNumber($edge->style, ($source) ?
mxConstants::$STYLE_SOURCE_PERIMETER_SPACING :
mxConstants::$STYLE_TARGET_PERIMETER_SPACING);
$pt = $this->getPerimeterPoint($start, $next, $this->graph->isOrthogonal($edge), $border);
$index = ($source) ? 0 : sizeof($edge->absolutePoints) - 1;
$edge->absolutePoints[$index] = $pt;
}
/**
* Function: getTerminalPort
*
* Returns an that represents the source or target terminal or
* port for the given edge.
*
* Parameters:
*
* state - that represents the state of the edge.
* terminal - that represents the terminal.
* source - Boolean indicating if the given terminal is the source terminal.
*/
function getTerminalPort($state, $terminal, $source)
{
$key = ($source) ? mxConstants::$STYLE_SOURCE_PORT
: mxConstants::$STYLE_TARGET_PORT;
$id = mxUtils::getValue($state->style, $key);
if ($id != null)
{
$tmp = $this->getState($this->graph->model->getCell($id));
// Only uses ports where a cell state exists
if (isset($tmp))
{
$terminal = $tmp;
}
}
return $terminal;
}
/**
* Function: getPerimeterPoint
*
* Returns an that defines the location of the intersection point between
* the perimeter and the line between the center of the shape and the given point.
*
* Parameters:
*
* terminal - for the source or target terminal.
* next - that lies outside of the given terminal.
* orthogonal - Boolean that specifies if the orthogonal projection onto
* the perimeter should be returned. If this is false then the intersection
* of the perimeter and the line between the next and the center point is
* returned.
* border - Optional border between the perimeter and the shape.
*/
function getPerimeterPoint($terminal, $next, $orthogonal, $border = null)
{
$point = null;
if ($terminal != null)
{
$perimeter = $this->getPerimeterFunction($terminal);
if (isset($perimeter) && isset($next))
{
$bounds = $this->getPerimeterBounds($terminal, $border);
if ($bounds->width > 0 || $bounds->height > 0)
{
$point = $perimeter->apply($bounds, $terminal, $next, $orthogonal);
}
}
if (!isset($point))
{
$point = $this->getPoint($terminal);
}
}
return $point;
}
/**
* Function: getRoutingCenterX
*
* Returns the x-coordinate of the center point for automatic routing.
*/
function getRoutingCenterX($state)
{
$f = ($state->style != null) ? mxUtils::getNumber($state->style,
mxConstants::$STYLE_ROUTING_CENTER_X) : 0;
return $state->getCenterX() + $f * $state->width;
}
/**
* Function: getRoutingCenterY
*
* Returns the y-coordinate of the center point for automatic routing.
*/
function getRoutingCenterY($state)
{
$f = ($state->style != null) ? mxUtils::getNumber($state->style,
mxConstants::$STYLE_ROUTING_CENTER_Y) : 0;
return $state->getCenterY() + $f * $state->height;
}
/**
* Function: getPerimeterBounds
*
* Returns the perimeter bounds for the given terminal, edge pair as an
* .
*
* Parameters:
*
* terminal - that represents the terminal.
* border - Number that adds a border between the shape and the perimeter.
*/
function getPerimeterBounds($terminal, $border = 0)
{
if ($terminal != null)
{
$border += mxUtils::getNumber($terminal->style, mxConstants::$STYLE_PERIMETER_SPACING);
}
return $terminal->getPerimeterBounds($border * $this->scale);
}
/**
* Function: getPerimeterFunction
*
* Returns the perimeter function for the given state.
*/
function getPerimeterFunction($state)
{
$perimeter = mxUtils::getValue($state->style, mxConstants::$STYLE_PERIMETER);
// Converts string values to objects
if (is_string($perimeter))
{
$tmp = mxStyleRegistry::getValue($perimeter);
if ($tmp == null && strpos($perimeter, ".") !== false)
{
$tmp = mxUtils::evaluate($perimeter);
}
$perimeter = $tmp;
}
if ($perimeter instanceof mxPerimeterFunction)
{
return $perimeter;
}
return null;
}
/**
* Function: getNextPoint
*
* Returns the nearest point in the list of absolute points or the center
* of the opposite terminal.
*
* Parameters:
*
* edge - that represents the edge.
* opposite - that represents the opposite terminal.
* source - Boolean indicating if the next point for the source or target
* should be returned.
*/
function getNextPoint($edge, $opposite, $source)
{
$pts = $edge->absolutePoints;
$point = null;
if ($pts != null && sizeof($pts) >= 2)
{
$count = sizeof($pts);
$index = ($source) ? min(1, $count - 1) : max(0, $count - 2);
$point = $pts[$index];
}
if (!isset($point) && isset($opposite))
{
$point = new mxPoint($opposite->getCenterX(), $opposite->getCenterY());
}
return $point;
}
/**
* Function: getVisibleTerminal
*
* Returns the nearest ancestor terminal that is visible. The edge appears
* to be connected to this terminal on the display.
*
* Parameters:
*
* edge - whose visible terminal should be returned.
* source - Boolean that specifies if the source or target terminal
* should be returned.
*/
function getVisibleTerminal($edge, $source)
{
$model = $this->graph->model;
$result = $model->getTerminal($edge, $source);
$best = $result;
while ($result != null)
{
if (!$this->graph->isCellVisible($best)||
$this->graph->isCellCollapsed($result))
{
$best = $result;
}
$result = $model->getParent($result);
}
// Checks if the result is not a layer
if ($model->getParent($best) === $model->getRoot())
{
$best = null;
}
return $best;
}
/**
* Function: updateEdgeBounds
*
* Updates the bounds of the specified state based on the
* absolute points in the state.
*/
function updateEdgeBounds($state)
{
$points = $state->absolutePoints;
$p0 = $points[0];
$n = sizeof($points);
$pe = $points[$n-1];
if ($p0->x != $pe->x || $p0->y != $pe->y)
{
$dx = $pe->x - $p0->x;
$dy = $pe->y - $p0->y;
$state->terminalDistance = sqrt($dx*$dx+$dy*$dy);
}
else
{
$state->terminalDistance = 0;
}
$length = 0;
$segments = array();
$pt = $p0;
if ($pt != null)
{
$minX = $pt->x;
$minY = $pt->y;
$maxX = $minX;
$maxY = $minY;
for ($i = 1; $i < $n; $i++)
{
$tmp = $points[$i];
if ($tmp != null)
{
$dx = $pt->x - $tmp->x;
$dy = $pt->y - $tmp->y;
$segment = sqrt($dx*$dx+$dy*$dy);
array_push($segments, $segment);
$length += $segment;
$pt = $tmp;
$minX = min($pt->x, $minX);
$minY = min($pt->y, $minY);
$maxX = max($pt->x, $maxX);
$maxY = max($pt->y, $maxY);
}
}
$state->length = $length;
$state->segments = $segments;
$state->x = $minX;
$state->y = $minY;
$state->width = $maxX - $minX;
$state->height = $maxY - $minY;
}
}
/**
* Function: getPoint
*
* Returns the absolute point on the edge for the given relative
* as an . The edge is represented by the given
* .
*
* Parameters:
*
* state - that represents the state of the parent edge.
* geometry - that represents the relative location.
*/
function getPoint($state, $geometry = null)
{
$x = $state->getCenterX();
$y = $state->getCenterY();
if (isset($state->segments) && (!isset($geometry) || $geometry->relative))
{
$gx = (isset($geometry)) ? $geometry->x / 2 : 0;
$pointCount = sizeof($state->absolutePoints);
$dist = ($gx + 0.5) * $state->length;
$segments = $state->segments;
$segment = $segments[0];
$length = 0;
$index = 1;
while ($dist > $length + $segment && $index < $pointCount - 1)
{
$length += $segment;
$segment = $segments[$index++];
}
$factor = ($segment == 0) ? 0 : ($dist - $length) / $segment;
$p0 = $state->absolutePoints[$index - 1];
$pe = $state->absolutePoints[$index];
if ($p0 != null && $pe != null)
{
$gy = 0;
$offsetX = 0;
$offsetY = 0;
if (isset($geometry))
{
$gy = $geometry->y;
$offset = $geometry->offset;
if (isset($offset))
{
$offsetX = $offset->x;
$offsetY = $offset->y;
}
}
$dx = $pe->x - $p0->x;
$dy = $pe->y - $p0->y;
$nx = ($segment == 0) ? 0 : $dy / $segment;
$ny = ($segment == 0) ? 0 : $dx / $segment;
$x = $p0->x + $dx * $factor + ($nx * $gy + $offsetX) * $this->scale;
$y = $p0->y + $dy * $factor - ($ny * $gy - $offsetY) * $this->scale;
}
}
else if (isset($geometry))
{
$offset = $geometry->offset;
if (isset($offset))
{
$x += $offset->x;
$y += $offset->y;
}
}
return new mxPoint($x, $y);
}
/**
* Function: getState
*
* Returns the cell state for the specified cell. If
* create is true then the state is created and added
* to the cache if it does not yet exist.
*/
function getState($cell, $create = false)
{
$state = null;
if ($cell != null)
{
$id = $this->getHashCode($cell);
$state = (isset($this->states[$id])) ? $this->states[$id] : null;
if ($state == null && $create && $this->graph->isCellVisible($cell))
{
$state = $this->createState($cell);
$this->states[$id] = $state;
}
}
return $state;
}
/**
* Function: getHashCode
*
* Returns a unique string that represents the given instance.
*/
function getHashCode($cell)
{
// PHP >= 5.2
if (function_exists("spl_object_hash"))
{
return spl_object_hash($cell);
}
else
{
return (string) $cell;
}
}
/**
* Function: getStates
*
* Returns the for the given array of . The array
* contains all states that are not null, that is, the returned array may
* have less elements than the given array.
*/
function getStates()
{
return $this->states;
}
/**
* Function: getStates
*
* Returns the for the given array of . The array
* contains all states that are not null, that is, the returned array may
* have less elements than the given array.
*/
function getCellStates($cells)
{
$result = array();
$count = sizeof($cells);
for ($i = 0; $i < $count; $i++)
{
$state = $this->getState($cells[$i]);
if ($state != null)
{
array_push($result, $state);
}
}
return $result;
}
/**
* Function: removeState
*
* Removes and returns the mxCellState for the given cell.
*/
function removeState($cell, $recurse = false)
{
if ($recurse)
{
$model = $this->graph->getModel();
$childCount = $model->getChildCount($cell);
for ($i = 0; $i < $childCount; $i++)
{
$this->removeState($model->getChildAt($cell, $i), true);
}
}
$state = null;
if ($cell != null)
{
$id = $this->getHashCode($cell);
$state = $this->states[$id];
unset($this->states[$id]);
}
return $state;
}
/**
* Function: createState
*
* Creates the state for the specified cell.
*/
function createState($cell)
{
$style = $this->graph->getCellStyle($cell);
return new mxCellState($this, $cell, $style);
}
}
?>