Arranging objects in a canvas like SQL Server

H

Heron

Hi,

I'm developing an mapping tool where the shapes are drawn in a canvas with
their related connections, Something like MS SQL server diagramming tool.

Now, I wish to provide an option (like sql server does: "Arrange tables") to
arrange all shapes so their connections so they can be shown in a beautifull
way. Can somebody suggest an alogorithm that will organize the shapes in
such a way that the endresult is clean?

Any help is appreciated.

Regards
 
S

Simon Tamman

Here's some code I use to position shapes in a canvas.
Hope it helps you.

public class ShapePositioner
{
private float m_Width;
private SizeF m_Size;
private float m_Padding;

private List<Shape> m_PositionedShapes = new List<Shape>();
ShapeLayoutGrid m_Grid;

public ShapePositioner(float width, SizeF shapeSize, float padding)
{
this.m_Width = width;
this.m_Size = shapeSize;
this.m_Padding = padding;
}

#region Public API

/// <summary>
/// Positions all of the shapes so that they do not obscure one another
/// </summary>
public void PositionShapes(List<AssignShape> shapes)
{
m_PositionedShapes.Clear();

// create a new grid
m_Grid = new ShapeLayoutGrid(m_Padding,
m_Size, m_Width);

// then position the shapes
foreach (Shape shape in shapes)
{

// find the next available location
Point position = FindNewLocation(0);
PositionShape(shape, position);

}
}

#endregion

#region Private Impl

/// <summary>
/// Positions the shape and marks the position off on the grid
/// </summary>
/// <param name="shape"></param>
/// <param name="location"></param>
private void PositionShape(Shape shape, Point location)
{
// mark the grid
m_Grid.MarkAsOccupied(location);
// extract the exact X and Y
PointF trueLocation = m_Grid.FindLocation(location);
// put the shape into that position
shape.Rectangle = new RectangleF(trueLocation, m_Size);
shape.X = trueLocation.X;
shape.Y = trueLocation.Y;
m_PositionedShapes.Add(shape);
}

/// <summary>
/// Finds the next empty location in the grid
/// starting from the row specified
/// </summary>
/// <param name="startingPoint"></param>
/// <param name="grid"></param>
/// <returns></returns>
private Point FindNewLocation(int startingRow)
{
int y = startingRow;
while (true)
{
for (int x = 0; x < m_Grid.ShapesPerRow; x++)
{
if (!m_Grid.IsPointOccupied(x, y))
{
return new Point(x, y);
}
}
y++;
}
}

#endregion
}

/// <summary>
/// Acts as a grid to position shapes and to record positions
/// where shapes have already been placed.
/// </summary>
public class ShapeLayoutGrid
{
#region Fields
private float m_Padding;
private SizeF m_ShapeSize;
private float m_GraphWidth;
private int[,] m_Grid;
private const int m_DefaultSize = 100;
#endregion

#region Ctor
/// <summary>
/// Ctor
/// </summary>
/// <param name="padding"></param>
/// <param name="shapeSize"></param>
/// <param name="graphWidth"></param>
/// <param name="numberOfShapes"></param>
public ShapeLayoutGrid(float padding, SizeF shapeSize, float graphWidth)
{
this.m_Padding = padding;
this.m_ShapeSize = shapeSize;
this.m_GraphWidth = graphWidth;
CalculateNewGrid(m_DefaultSize);
}
#endregion

#region Properties

/// <summary>
/// The amount of shapes you can fit in a row
/// </summary>
/// <returns></returns>
public int ShapesPerRow
{
get
{
float paddingPlusSize = m_Padding + m_ShapeSize.Width;
// remove some spacing for the margins
float width = m_GraphWidth - (m_Padding * 2);
return Convert.ToInt32(Math.Floor((double)(width / paddingPlusSize)));
}
}

#endregion

#region Public API

/// <summary>
/// Finds the location for the point in the grid
/// </summary>
/// <param name="xy"></param>
/// <returns></returns>
public PointF FindLocation(Point xy)
{
ValidatePoint(xy);
float x = m_Padding;
float y = m_Padding;
x += (xy.X * (m_ShapeSize.Width + m_Padding));
y += (xy.Y * (m_ShapeSize.Height + m_Padding));
return new PointF(x, y);
}

/// <summary>
/// Marks a position in the grid as "occupied"
/// </summary>
/// <param name="xy"></param>
public void MarkAsOccupied(Point xy)
{
ValidatePoint(xy);
if (m_Grid[xy.X, xy.Y] == 1)
{
throw new InvalidOperationException("Point is already occupied");
}
m_Grid[xy.X, xy.Y] = 1;
}

/// <summary>
/// Asks if a particular spot is occupied yet, or not
/// </summary>
/// <param name="xy"></param>
/// <returns></returns>
public bool IsPointOccupied(Point xy)
{
ValidatePoint(xy);
return m_Grid[xy.X, xy.Y] == 1;
}

/// <summary>
/// Asks if a particular spot is occupied yet, or not
/// </summary>
/// <param name="xy"></param>
/// <returns></returns>
public bool IsPointOccupied(int x, int y)
{
return IsPointOccupied(new Point(x, y));
}

#endregion

#region Private Impl

/// <summary>
/// Creates a new grid based on the size and
/// copies the "occpuied" values across.
/// </summary>
/// <param name="size"></param>
private void CalculateNewGrid(int size)
{
int[,] tempGrid = CreateNewGrid(size);
for (int x = 0; x < tempGrid.GetLength(0); x++)
{
for (int y = 0; y < tempGrid.GetLength(1); y++)
{
if (tempGrid[x, y] == 1)
{
if (tempGrid.GetLength(0) >= x
&& tempGrid.GetLength(1) >= y)
{
tempGrid[x, y] = 1;
}
}
}
}
m_Grid = tempGrid;
}

/// <summary>
/// Creates a new grid based on the size and
/// copies the values across.
/// </summary>
/// <param name="size"></param>
/// <returns></returns>
private int[,] CreateNewGrid(int size)
{
int[,] tempGrid = new int[ShapesPerRow, size];

for (int x = 0; x < ShapesPerRow; x++)
{
for (int y = 0; y < size; y++)
{
tempGrid[x, y] = 0;
}
}

return tempGrid;
}

/// <summary>
/// Validates a point, ensures that it not beyond the number
/// of shapes per line and ensures the object has a large enough grid
/// to deal with the request.
/// </summary>
/// <param name="xy"></param>
private void ValidatePoint(PointF xy)
{
if (xy.X > ShapesPerRow)
{
throw new ArgumentException("Point's X is too great");
}
// make the grid larger, if necessary
if (xy.Y > m_Grid.GetLength(1))
{
CalculateNewGrid((int)Math.Floor(xy.Y));
}
}

#endregion
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top