Best way to draw a line without using a canvas C# - c#

I am wondering what the best way to draw a line, on a maximum value, from a list is without using a canvas?
I have identified the Max, Min and Median I'm wondering what the best way to draw a line/point without using a canvas would be?
public partial class SpectrumControl : UserControl
{
private double Highest;
private double Minimum;
private double Median;
private int Total;
private int CellWidth;
public int Width { get; set; }
public SpectrumControl()
{
InitializeComponent();
}
public void Bind(KLayer klayer)
{
if (Width == 0)
{
Width = 300;
}
Highest = klayer.Values.Max();
Minimum = klayer.Values.Min();
Median = ((Highest - Minimum) / 2) + Minimum;
Total = klayer.Values.Count;
CellWidth = Width / Total;
int rowNumber = 0;
foreach (var item in klayer.Values)
{
var label = CreateLabel(item, rowNumber);
Color backgroundColour = GetColour(item);
stk1.Children.Add(label);
rowNumber++;
}
}
private Label CreateLabel(double item, int rowNumber)
{
var label = new Label()
{
Background = new SolidColorBrush(GetColour(item)),
Width = CellWidth
};
return label;
}
private Color GetColour(double item)
{
byte a = Convert.ToByte(GetTransparency(item)*255);
Color backgroundColour;
if (item < Median)
{
backgroundColour = Color.FromArgb(a, 128, 128, 255);
}
else if (item > Median)
{
backgroundColour = Color.FromArgb(a, 255, 128, 128);
}
else
{
backgroundColour = Colors.White;
}
return backgroundColour;
}
private double GetTransparency(double item)
{
double x = Highest - Minimum;
double difference;
if (item > Median)
{
difference = item - Median;
}
else
{
difference = Median - item;
}
var fraction = difference / x;
return fraction;
}
}

Well, assuming you are going to use something like GridPanel or any other panel, really, you could do this:
var line = new Line();
line.Stroke = System.Windows.Media.Brushes.LightSteelBlue;
line.X1 = 1;
line.X2 = 50;
line.Y1 = 1;
line.Y2 = 50;
line.HorizontalAlignment = HorizontalAlignment.Left;
line.VerticalAlignment = VerticalAlignment.Center;
line.StrokeThickness = 2;
grid.Children.Add(line);
Same thing may be achieved in XAML, but it looks like you prefer to work in code-behind, so that is what I posted here.
Reference: https://msdn.microsoft.com/en-us/library/system.windows.shapes.line%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396
I'm not sure why you'd avoid canvas, though (well, why someone told you to do that). I've created plenty of plots using canvas.

Related

C# Create Grid For Painting Pixels and Rendering Text

I'm trying to find the best path to go about creating a pixel "grid" that would allow basic paint functions such as coloring the pixels by clicking and moving the mouse, selecting an area for copying, pasting, or moving, or using other graphics functions in order to render text or shapes to the pixels. I've looked at a few samples, such as this example which overrides the panel control and has a similar look to what I'm trying to achieve, but the painting is slow and it seems like it wouldn't perform well for drawing with the mouse. Is there a control, or one that I could override, that would allow for the functionality that I'm looking for?
Here's a sample of what the above example looks like:
Sample pixel grid
And the code I adapted from the above example:
public class Pixel
{
public Rectangle Bounds { get; set; }
public bool IsOn { get; set; }
public bool IsSelected { get; set; }
}
public class PixelGridControl : Panel
{
public int Columns = 99;
public int Rows = 63;
private readonly Pixel[,] pixels;
public PixelGridControl()
{
this.DoubleBuffered = true;
this.ResizeRedraw = true;
// initialize pixel grid:
pixels = new Pixel[Columns, Rows];
for (int y = 0; y < Rows; ++y)
{
for (int x = 0; x < Columns; ++x)
{
pixels[x, y] = new Pixel();
}
}
}
// adjust each column and row to fit entire client area:
protected override void OnResize(EventArgs e)
{
int top = 0;
for (int y = 0; y < Rows; ++y)
{
int left = 0;
int height = (this.ClientSize.Height - top) / (Rows - y);
for (int x = 0; x < Columns; ++x)
{
int width = (this.ClientSize.Width - left) / (Columns - x);
pixels[x, y].Bounds = new Rectangle(left, top, width, height);
left += width;
}
top += height;
}
base.OnResize(e);
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
for (int y = 0; y < Rows; ++y)
{
for (int x = 0; x < Columns; ++x)
{
if (pixels[x, y].IsOn)
{
e.Graphics.FillRectangle(Brushes.Gold, pixels[x, y].Bounds);
}
else
{
ControlPaint.DrawButton(e.Graphics, pixels[x, y].Bounds,
ButtonState.Normal);
}
}
}
base.OnPaint(e);
}
// determine which button the user pressed:
protected override void OnMouseDown(MouseEventArgs e)
{
for (int y = 0; y < Rows; ++y)
{
for (int x = 0; x < Columns; ++x)
{
if (pixels[x, y].Bounds.Contains(e.Location))
{
pixels[x, y].IsOn = true;
this.Invalidate();
MessageBox.Show(
string.Format("You pressed on button ({0}, {1})",
x.ToString(), y.ToString())
);
}
}
}
base.OnMouseDown(e);
}
}
Here is an minimal example that uses a bitmap as the pixel storage and displays the enlarged pixels in a panel for editing.
You can use it by setting it up to edit the pixels in an Picturebox pBox_Target.Image, maybe like this:
public Form1()
{
InitializeComponent();
..
..
pixelEditor1.APBox = pBox_Target;
pixelEditor1.TgtBitmap = (Bitmap)pixelEditor1.APBox.Image;
..
}
The editor exposes the pixel size and the color to draw with among a minimum of properties..
all in all it is slightly over 100 lines - of course it is meant to be expanded!
Here it is at work:
class PixelEditor : Panel
{
public Color DrawColor { get; set; }
public Color GridColor { get; set; }
int pixelSize = 8;
public int PixelSize
{
get { return pixelSize; }
set
{
pixelSize = value;
Invalidate();
}
}
public Bitmap TgtBitmap { get; set; }
public Point TgtMousePos { get; set; }
Point lastPoint = Point.Empty;
PictureBox aPBox = null;
public PictureBox APBox {
get { return aPBox; }
set
{
if (value == null) return;
aPBox = value;
aPBox.MouseClick -=APBox_MouseClick;
aPBox.MouseClick +=APBox_MouseClick;
}
}
private void APBox_MouseClick(object sender, MouseEventArgs e)
{
TgtMousePos = e.Location;
Invalidate();
}
public PixelEditor()
{
DoubleBuffered = true;
BackColor = Color.White;
GridColor = Color.DimGray;
DrawColor = Color.Red;
PixelSize = 10;
TgtMousePos = Point.Empty;
if (APBox != null && APBox.Image != null)
TgtBitmap = (Bitmap)APBox.Image;
MouseClick +=PixelEditor_MouseClick;
MouseMove +=PixelEditor_MouseMove;
Paint +=PixelEditor_Paint;
}
private void PixelEditor_Paint(object sender, PaintEventArgs e)
{
if (DesignMode) return;
Graphics g = e.Graphics;
int cols = ClientSize.Width / PixelSize;
int rows = ClientSize.Height / PixelSize;
if (TgtMousePos.X < 0 || TgtMousePos.Y < 0) return;
for (int x = 0; x < cols; x++)
for (int y = 0; y < rows; y++)
{
int sx = TgtMousePos.X + x;
int sy = TgtMousePos.Y + y;
if (sx > TgtBitmap.Width || sy > TgtBitmap.Height) continue;
Color col = TgtBitmap.GetPixel(sx, sy);
using (SolidBrush b = new SolidBrush(col))
using (Pen p = new Pen(GridColor))
{
Rectangle rect = new Rectangle(x * PixelSize, y * PixelSize,
PixelSize, PixelSize);
g.FillRectangle(b, rect);
g.DrawRectangle(p, rect);
}
}
}
private void PixelEditor_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
int x = TgtMousePos.X + e.X / PixelSize;
int y = TgtMousePos.Y + e.Y / PixelSize;
if (new Point(x, y) == lastPoint) return;
Bitmap bmp = (Bitmap)APBox.Image;
bmp.SetPixel(x,y, DrawColor);
APBox.Image = bmp;
Invalidate();
lastPoint = new Point(x, y);
}
private void PixelEditor_MouseClick(object sender, MouseEventArgs e)
{
int x = TgtMousePos.X + e.X / PixelSize;
int y = TgtMousePos.Y + e.Y / PixelSize;
Bitmap bmp = (Bitmap)APBox.Image;
bmp.SetPixel(x,y, DrawColor);
APBox.Image = bmp;
Invalidate();
}
}
Note that this solution neither uses a special pixel class nor makes any provisions for selecting sets of pixels. To implement most selection tools you can use a GraphicsPath; you can the e.g. fill the path or loop over its bounds using IsVisible to enumerate the pixels in the path..
Update: Instead of a Panel, which is a Container control and not really meant to draw onto you can use a Picturebox or a Label (with Autosize=false); both have the DoubleBuffered property turned on out of the box and support drawing better than Panels do.

From C# serverside, Is there anyway to generate a treemap and save as an image?

I have been using this javascript library to create treemap on webpages and it works great. The issue now is that I need to include this in a powerpoint presentation that I am generating on the server side (I am generating the powerpoint using aspose.slides for .net)
The easiest thing I thought of was to try to somehow build a treemap on the server and save as an image (as adding an image into the powerpoint presentation is quite simple) but after googling, I don't see any solution that from C# serverside can generate a treemap as an image.
Does something like this exist where I can create a treemap as an image from a server side C# app.
Given that algorithms are known, it's not hard to just draw a bitmap with a treemap. At the moment I don't have enough time to write code myself, but I have enough time to (almost) mindlessly port some existing code to C# :) Let's take this javascript implementation. It uses algorithm described in this paper. I found some problems in that implementation, which are fixed in C# version. Javascript version works with pure arrays (and arrays of arrays of arrays) of integers. We define some class instead:
public class TreemapItem {
private TreemapItem() {
FillBrush = Brushes.White;
BorderBrush = Brushes.Black;
TextBrush = Brushes.Black;
}
public TreemapItem(string label, int area, Brush fillBrush) : this() {
Label = label;
Area = area;
FillBrush = fillBrush;
Children = null;
}
public TreemapItem(params TreemapItem[] children) : this() {
// in this implementation if there are children - all other properies are ignored
// but this can be changed in future
Children = children;
}
// Label to write on rectangle
public string Label { get; set; }
// color to fill rectangle with
public Brush FillBrush { get; set; }
// color to fill rectangle border with
public Brush BorderBrush { get; set; }
// color of label
public Brush TextBrush { get; set; }
// area
public int Area { get; set; }
// children
public TreemapItem[] Children { get; set; }
}
Then starting to port. First Container class:
class Container {
public Container(int x, int y, int width, int height) {
X = x;
Y = y;
Width = width;
Height = height;
}
public int X { get; }
public int Y { get; }
public int Width { get; }
public int Height { get; }
public int ShortestEdge => Math.Min(Width, Height);
public IDictionary<TreemapItem, Rectangle> GetCoordinates(TreemapItem[] row) {
// getCoordinates - for a row of boxes which we've placed
// return an array of their cartesian coordinates
var coordinates = new Dictionary<TreemapItem, Rectangle>();
var subx = this.X;
var suby = this.Y;
var areaWidth = row.Select(c => c.Area).Sum()/(float) Height;
var areaHeight = row.Select(c => c.Area).Sum()/(float) Width;
if (Width >= Height) {
for (int i = 0; i < row.Length; i++) {
var rect = new Rectangle(subx, suby, (int) (areaWidth), (int) (row[i].Area/areaWidth));
coordinates.Add(row[i], rect);
suby += (int) (row[i].Area/areaWidth);
}
}
else {
for (int i = 0; i < row.Length; i++) {
var rect = new Rectangle(subx, suby, (int) (row[i].Area/areaHeight), (int) (areaHeight));
coordinates.Add(row[i], rect);
subx += (int) (row[i].Area/areaHeight);
}
}
return coordinates;
}
public Container CutArea(int area) {
// cutArea - once we've placed some boxes into an row we then need to identify the remaining area,
// this function takes the area of the boxes we've placed and calculates the location and
// dimensions of the remaining space and returns a container box defined by the remaining area
if (Width >= Height) {
var areaWidth = area/(float) Height;
var newWidth = Width - areaWidth;
return new Container((int) (X + areaWidth), Y, (int) newWidth, Height);
}
else {
var areaHeight = area/(float) Width;
var newHeight = Height - areaHeight;
return new Container(X, (int) (Y + areaHeight), Width, (int) newHeight);
}
}
}
Then Treemap class which builds actual Bitmap
public class Treemap {
public Bitmap Build(TreemapItem[] items, int width, int height) {
var map = BuildMultidimensional(items, width, height, 0, 0);
var bmp = new Bitmap(width, height);
var g = Graphics.FromImage(bmp);
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
foreach (var kv in map) {
var item = kv.Key;
var rect = kv.Value;
// fill rectangle
g.FillRectangle(item.FillBrush, rect);
// draw border
g.DrawRectangle(new Pen(item.BorderBrush, 1), rect);
if (!String.IsNullOrWhiteSpace(item.Label)) {
// draw text
var format = new StringFormat();
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
var font = new Font("Arial", 16);
g.DrawString(item.Label, font, item.TextBrush, new RectangleF(rect.X, rect.Y, rect.Width, rect.Height), format);
}
}
return bmp;
}
private Dictionary<TreemapItem, Rectangle> BuildMultidimensional(TreemapItem[] items, int width, int height, int x, int y) {
var results = new Dictionary<TreemapItem, Rectangle>();
var mergedData = new TreemapItem[items.Length];
for (int i = 0; i < items.Length; i++) {
// calculate total area of children - current item's area is ignored
mergedData[i] = SumChildren(items[i]);
}
// build a map for this merged items (merged because their area is sum of areas of their children)
var mergedMap = BuildFlat(mergedData, width, height, x, y);
for (int i = 0; i < items.Length; i++) {
var mergedChild = mergedMap[mergedData[i]];
// inspect children of children in the same way
if (items[i].Children != null) {
var headerRect = new Rectangle(mergedChild.X, mergedChild.Y, mergedChild.Width, 20);
results.Add(mergedData[i], headerRect);
// reserve 20 pixels of height for header
foreach (var kv in BuildMultidimensional(items[i].Children, mergedChild.Width, mergedChild.Height - 20, mergedChild.X, mergedChild.Y + 20)) {
results.Add(kv.Key, kv.Value);
}
}
else {
results.Add(mergedData[i], mergedChild);
}
}
return results;
}
private Dictionary<TreemapItem, Rectangle> BuildFlat(TreemapItem[] items, int width, int height, int x, int y) {
// normalize all area values for given width and height
Normalize(items, width*height);
var result = new Dictionary<TreemapItem, Rectangle>();
Squarify(items, new TreemapItem[0], new Container(x, y, width, height), result);
return result;
}
private void Normalize(TreemapItem[] data, int area) {
var sum = data.Select(c => c.Area).Sum();
var multi = area/(float) sum;
foreach (var item in data) {
item.Area = (int) (item.Area*multi);
}
}
private void Squarify(TreemapItem[] data, TreemapItem[] currentRow, Container container, Dictionary<TreemapItem, Rectangle> stack) {
if (data.Length == 0) {
foreach (var kv in container.GetCoordinates(currentRow)) {
stack.Add(kv.Key, kv.Value);
}
return;
}
var length = container.ShortestEdge;
var nextPoint = data[0];
if (ImprovesRatio(currentRow, nextPoint, length)) {
currentRow = currentRow.Concat(new[] {nextPoint}).ToArray();
Squarify(data.Skip(1).ToArray(), currentRow, container, stack);
}
else {
var newContainer = container.CutArea(currentRow.Select(c => c.Area).Sum());
foreach (var kv in container.GetCoordinates(currentRow)) {
stack.Add(kv.Key, kv.Value);
}
Squarify(data, new TreemapItem[0], newContainer, stack);
}
}
private bool ImprovesRatio(TreemapItem[] currentRow, TreemapItem nextNode, int length) {
// if adding nextNode
if (currentRow.Length == 0)
return true;
var newRow = currentRow.Concat(new[] {nextNode}).ToArray();
var currentRatio = CalculateRatio(currentRow, length);
var newRatio = CalculateRatio(newRow, length);
return currentRatio >= newRatio;
}
private int CalculateRatio(TreemapItem[] row, int length) {
var min = row.Select(c => c.Area).Min();
var max = row.Select(c => c.Area).Max();
var sum = row.Select(c => c.Area).Sum();
return (int) Math.Max(Math.Pow(length, 2)*max/Math.Pow(sum, 2), Math.Pow(sum, 2)/(Math.Pow(length, 2)*min));
}
private TreemapItem SumChildren(TreemapItem item) {
int total = 0;
if (item.Children?.Length > 0) {
total += item.Children.Sum(c => c.Area);
foreach (var child in item.Children) {
total += SumChildren(child).Area;
}
}
else {
total = item.Area;
}
return new TreemapItem(item.Label, total, item.FillBrush);
}
}
Now let's try to use and see how it goes:
var map = new[] {
new TreemapItem("ItemA", 0, Brushes.DarkGray) {
Children = new[] {
new TreemapItem("ItemA-1", 200, Brushes.White),
new TreemapItem("ItemA-2", 500, Brushes.BurlyWood),
new TreemapItem("ItemA-3", 600, Brushes.Purple),
}
},
new TreemapItem("ItemB", 1000, Brushes.Yellow) {
},
new TreemapItem("ItemC", 0, Brushes.Red) {
Children = new[] {
new TreemapItem("ItemC-1", 200, Brushes.White),
new TreemapItem("ItemC-2", 500, Brushes.BurlyWood),
new TreemapItem("ItemC-3", 600, Brushes.Purple),
}
},
new TreemapItem("ItemD", 2400, Brushes.Blue) {
},
new TreemapItem("ItemE", 0, Brushes.Cyan) {
Children = new[] {
new TreemapItem("ItemE-1", 200, Brushes.White),
new TreemapItem("ItemE-2", 500, Brushes.BurlyWood),
new TreemapItem("ItemE-3", 600, Brushes.Purple),
}
},
};
using (var bmp = new Treemap().Build(map, 1024, 1024)) {
bmp.Save("output.bmp", ImageFormat.Bmp);
}
Output:
This can be extended in multiple ways, and code quality can certainly be improved significantly. But if you would go this way, it can at least give you a good start. Benefit is that it's fast and no external dependencies involved.
If you would want to use it and find some issues or it does not match some of your requirements - feel free to ask and I will improve it when will have more time.
Using the GDI+ api may be your only choice, with good support cross platform. However, there are several potential issues you need to be aware of when doing anything with GDI+ on the server side. It's worth reading this as it explains the current state of drawing Graphics in DotNet and has a point on Server-side processing:
https://github.com/imazen/Graphics-vNext
Having said that; there's this article that deals with what you're asking for:
OutOfMemory Exception when recursively drawing rectangles in GDI+ (It's specifically talking about generating a TreeMap with GDI+ and if you read the comments and answer you'll avoid a lot of the pitfalls)
Once you've generated your image, it's a trivial process to save it to disk somewhere and then, hopefully, embed it within your presentation; you have options to write to streams as well, so it may be possible to directly embed it in the powerpoint file without first saving it to disk.
You can use WPF rendering: http://lordzoltan.blogspot.co.uk/2010/09/using-wpf-to-render-bitmaps.html but it's not without its drawbacks.
(That's a link to my own old blog - but if you search for 'using wpf to generate images' you'll get lots of other examples - many of which are better than mine!)
Generating a tree in WPF is going to be, well, challenging, though - although it can be done, since the WPF drawing primitives are hierarchical in nature.
It might not be suitable, bou could also consider GraphViz - https://github.com/JamieDixon/GraphViz-C-Sharp-Wrapper however I don't know how much luck you'll have executing the command line in a web server.
There are - I expect - bound to be paid-for libraries to do this, as well, as it's a common need.
The Eindhoven University of Technology has published a paper on the algorithm of squarified treemaps. Pascal Laurin has turned this into C#. There is also a Code Project article that has a section about treemaps.
Of course there are also commercial solutions like the one from .NET Charting, Infragistics or Telerik. The downside of those is probably that they are designed as Controls which need to be painted, so you might need some sort of UI thread.
There is also a question here on Stack Overflow that already asked for treemap implementations in C#. Just in case you don't remember.
Since you are already generating the JS and the HTML version of everything, one thing you might want to check out is :
http://www.nrecosite.com/html_to_image_generator_net.aspx
I use this to generate high res-graphic reports straight from my generated pages. It used WKHTML to render it and you can pass a ton of parameters to it to really fine tune it. Its free to most things and it works great. Multi-threading is kind of a pain in the butt with it but I haven't run into to many issues. If you use the NRECO PDf library you can even do batches of stuff also.
With this all you would have to do is render the page like you already are, pipe it through the library and insert into your PPT and all should be good.
Since all you need to do is to extract a screenshot of the web page, it would be more convenient to capture the web page as an Image.
This free library is capable of extracting screenshot from your web page, and it supports Javascript / CSS.
I'm basing the following solution in this project about treemaps in WPF.
Using the data in your link, you can define your model (only with necesary data) like this:
class Data
{
[JsonProperty("$area")]
public float Area { get; set; }
[JsonProperty("$color")]
public Color Color { get; set; }
}
class Item
{
public string Name { get; set; }
public Data Data { get; set; }
public IEnumerable<Item> Children { get; set; }
internal TreeMapData TMData { get; set; }
internal int GetDepth()
{
return Children.Select(c => c.GetDepth()).DefaultIfEmpty().Max() + 1;
}
}
Adding an extra property TreeMapData with some values used in the solution:
class TreeMapData
{
public float Area { get; set; }
public SizeF Size { get; set; }
public PointF Location { get; set; }
}
Now, defining a TreeMap class with the following public members:
class TreeMap
{
public IEnumerable<Item> Items { get; private set; }
public TreeMap(params Item[] items) :
this(items.AsEnumerable()) { }
public TreeMap(IEnumerable<Item> items)
{
Items = items.OrderByDescending(t => t.Data.Area).ThenByDescending(t => t.Children.Count());
}
public Bitmap Draw(int width, int height)
{
var bmp = new Bitmap(width + 1, height + 1);
using (var g = Graphics.FromImage(bmp))
{
DrawIn(g, 0, 0, width, height);
g.Flush();
}
return bmp;
}
//Private members
}
So, you can use it like this:
var treeMap = new TreeMap(items);
var bmp = treeMap.Draw(1366, 768);
And the private/helper members:
private RectangleF emptyArea;
private void DrawIn(Graphics g, float x, float y, float width, float height)
{
Measure(width, height);
foreach (var item in Items)
{
var sFormat = new StringFormat
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Center
};
if (item.Children.Count() > 0)
{
g.FillRectangle(Brushes.DimGray, x + item.TMData.Location.X, y + item.TMData.Location.Y, item.TMData.Size.Width, 15);
g.DrawString(item.Name, SystemFonts.DefaultFont, Brushes.LightGray, new RectangleF(x + item.TMData.Location.X, y + item.TMData.Location.Y, item.TMData.Size.Width, 15), sFormat);
var treeMap = new TreeMap(item.Children);
treeMap.DrawIn(g, x + item.TMData.Location.X, y + item.TMData.Location.Y + 15, item.TMData.Size.Width, item.TMData.Size.Height - 15);
}
else
{
g.FillRectangle(new SolidBrush(item.Data.Color), x + item.TMData.Location.X, y + item.TMData.Location.Y, item.TMData.Size.Width, item.TMData.Size.Height);
g.DrawString(item.Name, SystemFonts.DefaultFont, Brushes.Black, new RectangleF(x + item.TMData.Location.X, y + item.TMData.Location.Y, item.TMData.Size.Width, item.TMData.Size.Height), sFormat);
}
var pen = new Pen(Color.Black, item.GetDepth() * 1.5f);
g.DrawRectangle(pen, x + item.TMData.Location.X, y + item.TMData.Location.Y, item.TMData.Size.Width, item.TMData.Size.Height);
}
g.Flush();
}
private void Measure(float width, float height)
{
emptyArea = new RectangleF(0, 0, width, height);
var area = width * height;
var sum = Items.Sum(t => t.Data.Area + 1);
foreach (var item in Items)
{
item.TMData = new TreeMapData();
item.TMData.Area = area * (item.Data.Area + 1) / sum;
}
Squarify(Items, new List<Item>(), ShortestSide());
foreach (var child in Items)
if (!IsValidSize(child.TMData.Size))
child.TMData.Size = new Size(0, 0);
}
private void Squarify(IEnumerable<Item> items, IEnumerable<Item> row, float sideLength)
{
if (items.Count() == 0)
{
ComputeTreeMaps(row);
return;
}
var item = items.First();
List<Item> row2 = new List<Item>(row);
row2.Add(item);
List<Item> items2 = new List<Item>(items);
items2.RemoveAt(0);
float worst1 = Worst(row, sideLength);
float worst2 = Worst(row2, sideLength);
if (row.Count() == 0 || worst1 > worst2)
Squarify(items2, row2, sideLength);
else
{
ComputeTreeMaps(row);
Squarify(items, new List<Item>(), ShortestSide());
}
}
private void ComputeTreeMaps(IEnumerable<Item> items)
{
var orientation = this.GetOrientation();
float areaSum = 0;
foreach (var item in items)
areaSum += item.TMData.Area;
RectangleF currentRow;
if (orientation == RowOrientation.Horizontal)
{
currentRow = new RectangleF(emptyArea.X, emptyArea.Y, areaSum / emptyArea.Height, emptyArea.Height);
emptyArea = new RectangleF(emptyArea.X + currentRow.Width, emptyArea.Y, Math.Max(0, emptyArea.Width - currentRow.Width), emptyArea.Height);
}
else
{
currentRow = new RectangleF(emptyArea.X, emptyArea.Y, emptyArea.Width, areaSum / emptyArea.Width);
emptyArea = new RectangleF(emptyArea.X, emptyArea.Y + currentRow.Height, emptyArea.Width, Math.Max(0, emptyArea.Height - currentRow.Height));
}
float prevX = currentRow.X;
float prevY = currentRow.Y;
foreach (var item in items)
{
var rect = GetRectangle(orientation, item, prevX, prevY, currentRow.Width, currentRow.Height);
item.TMData.Size = rect.Size;
item.TMData.Location = rect.Location;
ComputeNextPosition(orientation, ref prevX, ref prevY, rect.Width, rect.Height);
}
}
private RectangleF GetRectangle(RowOrientation orientation, Item item, float x, float y, float width, float height)
{
if (orientation == RowOrientation.Horizontal)
return new RectangleF(x, y, width, item.TMData.Area / width);
else
return new RectangleF(x, y, item.TMData.Area / height, height);
}
private void ComputeNextPosition(RowOrientation orientation, ref float xPos, ref float yPos, float width, float height)
{
if (orientation == RowOrientation.Horizontal)
yPos += height;
else
xPos += width;
}
private RowOrientation GetOrientation()
{
return emptyArea.Width > emptyArea.Height ? RowOrientation.Horizontal : RowOrientation.Vertical;
}
private float Worst(IEnumerable<Item> row, float sideLength)
{
if (row.Count() == 0) return 0;
float maxArea = 0;
float minArea = float.MaxValue;
float totalArea = 0;
foreach (var item in row)
{
maxArea = Math.Max(maxArea, item.TMData.Area);
minArea = Math.Min(minArea, item.TMData.Area);
totalArea += item.TMData.Area;
}
if (minArea == float.MaxValue) minArea = 0;
float val1 = (sideLength * sideLength * maxArea) / (totalArea * totalArea);
float val2 = (totalArea * totalArea) / (sideLength * sideLength * minArea);
return Math.Max(val1, val2);
}
private float ShortestSide()
{
return Math.Min(emptyArea.Width, emptyArea.Height);
}
private bool IsValidSize(SizeF size)
{
return (!size.IsEmpty && size.Width > 0 && size.Width != float.NaN && size.Height > 0 && size.Height != float.NaN);
}
private enum RowOrientation
{
Horizontal,
Vertical
}
Finally, to parse and draw the json in the example I'm doing this:
var json = File.ReadAllText(#"treemap.json");
var items = JsonConvert.DeserializeObject<Item>(json);
var treeMap = new TreeMap(items);
var bmp = treeMap.Draw(1366, 768);
bmp.Save("treemap.png", ImageFormat.Png);
And the resulting image:
Actually I don't know if the following can help you or not since you aren't using vsto, AND AS SAID IN THE COMMENTS PROBABLY IS A BAD IDEA.
Starting in Office 2016, treemaps are incorporated as charts. You can read this to see how create treemaps from datasets in Excel.
So, you can generate the chart in Excel and pass it to PowerPoint:
//Start an hidden excel application
var appExcel = new Excel.Application { Visible = false };
var workbook = appExcel.Workbooks.Add();
var sheet = workbook.ActiveSheet;
//Generate some random data
Random r = new Random();
for (int i = 1; i <= 10; i++)
{
sheet.Cells[i, 1].Value2 = ((char)('A' + i - 1)).ToString();
sheet.Cells[i, 2].Value2 = r.Next(1, 20);
}
//Select the data to use in the treemap
var range = sheet.Cells.Range["A1", "B10"];
range.Select();
range.Activate();
//Generate the chart
var shape = sheet.Shapes.AddChart2(-1, (Office.XlChartType)117, 200, 25, 300, 300, null);
shape.Chart.ChartTitle.Caption = "Generated TreeMap Chart";
//Copy the chart
shape.Copy();
appExcel.Quit();
//Start a Powerpoint application
var appPpoint = new Point.Application { Visible = Office.MsoTriState.msoTrue };
var presentation = appPpoint.Presentations.Add();
//Add a blank slide
var master = presentation.SlideMaster;
var slide = presentation.Slides.AddSlide(1, master.CustomLayouts[7]);
//Paste the treemap
slide.Shapes.Paste();
Treemap chart in the slide:
Probably you can generate the treemap using the first part (Excel part) and paste the chart using the tool you said, or save the Powerpoint file with the chart generated in VSTO and open it with the tool.
The benefits are that these objects are real charts not just images, so you can change or add colors, styles, effects easily.

PictureBox updates only on resize

Trying to display a graph on a Form application.
Created a PictureBox control and initialized this class with its value.
On resize, graph always updates; on mouse scroll, it hardly does.
It's GraphBox , PictureBox control, inside a GraphBoxPanel, Panel control.
This the class:
public struct DLT_measure_item
{
public DateTime ts;
public float value;
public int id;
public int X;
public int Y;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct dlt_ser_meas
{
public byte msg_id; // 'D'
public byte meas_count; // Number of measures
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public byte[] port; // Module ID (4b) + Port ID (4b)
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public float[] meas; // measure
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public byte[] msg_end;
}
public class manageGraph
{
private PictureBox box;
public bool displayGrid = true;
private int horAxisMin_p = 0;
private int horAxisMax_p = 300;
private float verAxisMin_p = 0;
private float verAxisMax_p = 40;
public int horAxisMin
{
get { return this.horAxisMin_p; }
set
{
if (value < horAxisMax_p)
{
this.horAxisMin_p = value;
reDraw();
}
}
}
public int horAxisMax
{
get { return this.horAxisMax_p; }
set
{
if (value > horAxisMin_p)
{
this.horAxisMax_p = value;
reDraw();
}
}
}
public float verAxisMin
{
get { return this.verAxisMin_p; }
set
{
if (value < verAxisMax_p)
{
this.verAxisMin_p = value;
verPointPerUnit = graphArea.Height / (verAxisMax_p - this.verAxisMin_p);
}
}
}
public float verAxisMax
{
get { return this.verAxisMax_p; }
set
{
if (value > verAxisMin_p)
{
this.verAxisMax_p = value;
verPointPerUnit = graphArea.Height / (this.verAxisMax_p - verAxisMin_p);
}
}
}
Pen axes = new Pen(Color.Black, (float)1.5);
public int horAxisSpacing = 30;
public int verAxisSpacing = 20;
public int horAxis = 20;
public int verAxis = 20;
private float horPointPerUnit = 1;
private float verPointPerUnit = 1;
public int horAxisTickLen = 5;
public int verAxisTickLen = 5;
public bool horAxisShowTime = false;
private Rectangle graphArea = new Rectangle();
public void reDraw()
{
box.Image.Dispose();
Bitmap GraphBlankImage = new Bitmap(box.Width, box.Height);
box.Image = GraphBlankImage;
updatePointPerUnit();
drawGrid();
box.Refresh();
}
public manageGraph(PictureBox targetImageBoxbox)
{
box = targetImageBoxbox;
horAxisMin_p = 0;
horAxisMax_p = 300;
verAxisMin_p = 0F;
verAxisMax_p = 50F;
updatePointPerUnit();
}
private Point measToPoint(DLT_measure_item measure) {
Point coords = new Point();
coords.X = graphArea.Width - (int)(
((DateTime.Now - measure.ts).TotalSeconds + horAxisMin_p) * horPointPerUnit ) ;
coords.Y = graphArea.Height - (int)(
((measure.value - verAxisMin_p) * verPointPerUnit));
return coords;
}
public manageGraph(PictureBox targetImageBoxbox,
int xmin, int xmax, float ymin, float ymax)
{
box = targetImageBoxbox;
horAxisMin_p = xmin;
horAxisMax_p = xmax;
verAxisMin_p = ymin;
verAxisMax_p = ymax;
updatePointPerUnit();
}
private void updateGraphArea()
{
graphArea = new Rectangle(0, 0, box.Width - horAxis, box.Height - verAxis);
}
private void updatePointPerUnit()
{
updateGraphArea();
horPointPerUnit = graphArea.Width / (horAxisMax_p - horAxisMin_p);
verPointPerUnit = graphArea.Height / (verAxisMax_p - verAxisMin_p);
}
public void drawGrid()
{
//updatePointPerUnit();
using (Graphics g = Graphics.FromImage(box.Image))
{
// X axis
g.DrawLine(axes, graphArea.Left, graphArea.Bottom, box.Width, graphArea.Bottom);
// Y axis
g.DrawLine(axes, graphArea.Right + 1, graphArea.Top, graphArea.Right +1, graphArea.Bottom);
using (Font ArialFont = new Font("Arial", 10))
{
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
// Put x labels
for (int i = 1; i <= (graphArea.Width / horPointPerUnit); i = i + (int)(horAxisSpacing / horPointPerUnit ) + 1)
{
g.DrawString((i).ToString(), ArialFont, Brushes.Black, graphArea.Width - ( i * horPointPerUnit) - 5, graphArea.Bottom +5);
g.DrawLine(axes, graphArea.Width - (i * horPointPerUnit), graphArea.Bottom + (horAxisTickLen / 2), graphArea.Width - (i * horPointPerUnit), graphArea.Bottom - (horAxisTickLen / 2));
}
// Put y labels
for (int i = 1; i <= (graphArea.Height / verPointPerUnit); i = i + (int)(verAxisSpacing / verPointPerUnit) +1)
{
g.DrawString((i).ToString(), ArialFont, Brushes.Black, graphArea.Right + 1 , graphArea.Height - (i * verPointPerUnit) - 8);
g.DrawLine(axes, graphArea.Width - (verAxisTickLen / 2), (i * verPointPerUnit), graphArea.Width + (verAxisTickLen / 2), (i * verPointPerUnit));
}
}
/*Put some random data*/
DLT_measure_item testmeas = new DLT_measure_item();
Point testGraphPoint = new Point();
testmeas.ts = DateTime.Now;
testmeas.value = 0;
testGraphPoint = measToPoint(testmeas);
g.FillEllipse(Brushes.Blue, testGraphPoint.X, testGraphPoint.Y, 4, 4);
for (double i = 0; i < 300; i++)
{
double x = i;
double freq = 10;
double y = 30 - (i/10);
testmeas.value = (float)y;
testmeas.ts = DateTime.Now.AddSeconds(-1 * i);
testGraphPoint = measToPoint(testmeas);
g.FillEllipse(Brushes.Red, testGraphPoint.X, testGraphPoint.Y, 2,2);
}
}
}
}
The initialization:
public DLThermalogMainForm()
{
InitializeComponent();
Bitmap GraphBlankImage = new Bitmap(GraphBox.Width, GraphBox.Height);
GraphBox.Image = GraphBlankImage;
myGraph = new manageGraph(GraphBox);
myGraph.drawGrid();
}
Those the handlers:
private void Form1_ResizeEnd(object sender, EventArgs e)
{
myGraph.reDraw();
OutputTextBox.AppendText("Resize." + Environment.NewLine);
}
private void GraphBox_MouseMove(object sender, MouseEventArgs e)
{
//Get the focus to have the wheel working
GraphBoxPanel.Focus();
}
private void GraphBoxPanel_MouseWheel(object sender, MouseEventArgs e)
{
// Change x axis max value to zoom in/out the graph
myGraph.horAxisMax += e.Delta/ 120;
myGraph.reDraw();
}
On resize event, it always redraws; on mouse wheel, it does quickly only with small horAxisMax values (does it makes sense???), but for larger, it takes many seconds to update, or doesn't at all.
Thank you very much
Change reDraw like this:
public void reDraw()
{
box.Image.Dispose();
Bitmap GraphBlankImage = new Bitmap(box.ClientSize.Width, box.ClientSize.Height);
updatePointPerUnit();
drawGrid(GraphBlankImage);
box.Image = GraphBlankImage;
}
and drawGrid like this:
public void drawGrid(Bitmap bmp)
{
//updatePointPerUnit(); //??
using (Graphics g = Graphics.FromImage(bmp))
{
...
...
...
}
}
Now the Bitmap with the grid should immediately show up in the PictureBox.
As mentioned a Graphics object is a tool to change an associated Bitmap. To pick it up the Bitmap should be assigned to the PictureBoxe's Image.
Also note, that unless your PictureBox has no Border, there is a small difference between the outer size (aka Bounds) and the inner size, the ClientRectangle / ClientSize. The Image should have the ClientSize
You may wonder why your original code doesn't work? After all an Image is a reference type, so changing it, as you did should be enough..
But looking deeper into the source code we find the reason:
The PictureBox's Image is a property and in its setter there is a call to InstallNewImage:
public Image Image {
get {
return image;
}
set {
InstallNewImage(value, ImageInstallationType.DirectlySpecified);
}
}
The same call is also in a few other places like Load or in the setter of ImageLocation. But changing the Image behind the scene alone will not force the PictureBox to make that call. A Refresh() should also do it.. And, as you found out, resizing it will also cause the PictureBox to pick up the changed data in the Image Bitmap..
The easiest way to force updating is simply to invalidate the control.
timer = new Timer();
timer.Interval = 200; //refreshes every 200 ms
timer.Tick += (sender,e) => targetImageBoxbox.Invalidate();
timer.Start();

How to center a bunch of controls programmatically in c#

I'm pretty new to this community, and I have this app that adds controls programmatically.
I would like to center all of the added controls sort of like selecting them and pressing center on Visual Studio. And no I don't want to center each one aside.
Here's the code I used to get all the controls:
private void GetAllControl(Control c, List<Control> list)
{
//gets all controls and saves them to a list
foreach (Control control in c.Controls)
{
list.Add(control);
}
}
//And then call it like this
List<Control> list = new List<Control>();
GetAllControl(PNL_custom, list);
foreach (Play_panel m in list)
{
//And here I want to insert that center code
}
Thanks in advance,
VBTheory
Here's how to center the controls as a GROUP. It's almost the same as before except we compute how far the center of mass for the group has to move to become the center of the parent control. Then we iterate over all the controls and offset their locations by that much. This centers them all while maintaining their positions relative to each other:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
List<Control> list = new List<Control>();
GetAllControl(PNL_custom, list);
CenterControlsAsGroup(list, Direction.Both); // center group in the center of the parent
}
public enum Direction
{
Vertical,
Horizontal,
Both
}
private void CenterControlsAsGroup(List<Control> controls, Direction direction)
{
if (controls.Count > 1)
{
int xSum = 0;
int ySum = 0;
Point center;
foreach (Control ctl in controls)
{
center = new Point(ctl.Location.X + ctl.Width / 2, ctl.Location.Y + ctl.Height / 2);
xSum = xSum + center.X;
ySum = ySum + center.Y;
}
Point average = new Point(xSum / controls.Count, ySum / controls.Count);
center = new Point(controls[0].Parent.Width / 2, controls[0].Parent.Height / 2);
int xOffset = center.X - average.X;
int yOffset = center.Y - average.Y;
foreach (Control ctl in controls)
{
switch (direction)
{
case Direction.Vertical:
ctl.Location = new Point(ctl.Location.X + xOffset, ctl.Location.Y);
break;
case Direction.Horizontal:
ctl.Location = new Point(ctl.Location.X, ctl.Location.Y + yOffset);
break;
case Direction.Both:
ctl.Location = new Point(ctl.Location.X + xOffset, ctl.Location.Y + yOffset);
break;
}
}
}
}
private void GetAllControl(Control c, List<Control> list)
{
//gets all controls and saves them to a list
foreach (Control control in c.Controls)
{
list.Add(control);
}
}
}
"And no I don't want to center each one aside."
So you want to "Align" the List of Controls?...as in:
Format --> Align --> Centers
Format --> Align --> Middles
If yes , then compute the center of each control and add up the X, Y coords so you can compute an "average" point (the center of mass). Now you can iterate over the controls and use that as the aligning X or Y value, depending on your desired direction. Simply subtract half the width or height and keep the other value.
Something like:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
List<Control> list = new List<Control>();
GetAllControl(PNL_custom, list);
CenterControls(list, Direction.Vertical);
}
public enum Direction
{
Vertical,
Horizontal
}
private void CenterControls(List<Control> controls, Direction direction)
{
if (controls.Count > 1)
{
int xSum = 0;
int ySum = 0;
Point center;
foreach (Control ctl in controls)
{
center = new Point(ctl.Location.X + ctl.Width / 2, ctl.Location.Y + ctl.Height / 2);
xSum = xSum + center.X;
ySum = ySum + center.Y;
}
Point average = new Point(xSum / controls.Count, ySum / controls.Count);
foreach (Control ctl in controls)
{
switch (direction)
{
case Direction.Vertical:
ctl.Location = new Point(average.X - ctl.Width / 2, ctl.Location.Y);
break;
case Direction.Horizontal:
ctl.Location = new Point(ctl.Location.X, average.Y - ctl.Height / 2);
break;
}
}
}
}
private void GetAllControl(Control c, List<Control> list)
{
//gets all controls and saves them to a list
foreach (Control control in c.Controls)
{
list.Add(control);
}
}
}
Get the width and height of the control's container (which is either another control or the form). The coordinates of controls are distances in pixels, relative to the upper left corner of their containers (which is (0,0)). So all you have to do is set a control's x coordinate to be (form width - control width) / 2. Same goes for height.
All your controls will be added to a container (most likely the Form) though they could easily be in a group box etc.
To align them center inside the container you need to do a little maths and position them yourself :)
Before you can center the control in the container you need to find the midpoint of the container. This will be the container's width / 2 : container's height / 2.
I will use a single control called cmdButton1 to highlight - you will want to iterate through your list of controls and do this to all of them in turn.
int midParentX = cmdButton1.Parent.width / 2;
int midParentX = cmdButton1.Parent.height / 2;
Then you can position a control at this midpoint:
cmdButton1.Location.X = midParentX;
cmdButton1.Location.Y = midParentY;
However, your control (cmdButton in my example) is anchored at (0,0) being the top left corner of the control, so we need to move it back and up by half its own width and height
cmdButton1.Location.X -= (cmdButton1.width / 2);
cmdButton1.Location.Y -= (cmdButton1.height / 2);
To be more relevant:
foreach (Play_panel m in list)
{
int pX = m.Parent.width;
int pY = m.Parent.height;
m.Location.X = (pX / 2) - (m.width / 2);
m.Location.Y = (pY / 2) - (m.height / 2);
}
So i did it on this way:
public enum ArrangeOrientation : int {
None,
Horizonatal,
Vertical,
HorizontalGrid,
VerticalGrid,
TopLeftGrid,
TopRightGrid,
BottomLeftGrid,
BottomRightGrid
}
private void ArrangeButtons(List<Control> controls, List<Control> parents, ArrangeOrientation orientation, double shrinkFactor = 1d) {
if(controls == null) return;
if(parents == null) parents = new List<Control>();
List<Control> childs = new List<Control>();
Control parent = null;
foreach(Control ctrl in controls) {
if(parent == null && !parents.Contains(ctrl.Parent)) {
parents.Add(ctrl.Parent);
parent = ctrl.Parent;
}
if(parent == ctrl.Parent)
childs.Add(ctrl);
}
if(parent != null && childs.Count > 0) {
ArrangeControlsToGridLayout(childs, orientation, shrinkFactor);
ArrangeButtons(controls, parents, orientation, shrinkFactor);
}
}
private void ArrangeControlsToGridLayout(List<Control> controls, ArrangeOrientation orientation, double shrinkFactor = 1d) {
// do nothing if nothing set
if(orientation == ArrangeOrientation.None) return;
if(shrinkFactor == 0d|| shrinkFactor > 1d) shrinkFactor = 1d;
// buffer controls in separate list to avoid manipulating parameter
List<Control> ctrl = new List<Control>(controls.ToArray());
// remove invisible controls
int j = 0;
while(j < ctrl.Count) {
if(!ctrl[j].Visible) ctrl.RemoveAt(j);
else j++;
}
// loop arrangement
int count = ctrl.Count;
int xDelta, yDelta, xOffs, yOffs, y, x, columns, rows, parentWidth, parentHeight, xShrinkOffs, yShrinkOffs;
if(count >= 1) {
// parents size
parentWidth = ctrl[0].Parent.Width;
parentHeight = ctrl[0].Parent.Height;
// shrink factor offset
parentWidth = Convert.ToInt32(parentWidth * shrinkFactor);
parentHeight = Convert.ToInt32(parentHeight * shrinkFactor);
// shrink factor offset
xShrinkOffs = Convert.ToInt32((ctrl[0].Parent.Width - parentWidth) / 2d);
yShrinkOffs = Convert.ToInt32((ctrl[0].Parent.Height - parentHeight) / 2d);
// calculate columns rows grid layout
if(orientation == ArrangeOrientation.Horizonatal) {
rows = 1;
columns = count;
}
else if(orientation == ArrangeOrientation.Vertical) {
rows = count;
columns = 1;
}
else if(orientation == ArrangeOrientation.TopLeftGrid
|| orientation == ArrangeOrientation.TopRightGrid
|| orientation == ArrangeOrientation.BottomLeftGrid
|| orientation == ArrangeOrientation.BottomRightGrid) {
rows = 1;
columns = count;
}
else {
rows = Convert.ToInt32(Math.Floor(Math.Sqrt(count)));
if(Math.Sqrt(count) % 1d != 0d) rows++;
columns = count / rows + (count % rows != 0 ? 1 : 0);
}
if(orientation == ArrangeOrientation.HorizontalGrid) {
int swap = columns;
columns = rows;
rows = columns;
}
// calculate position offsets, grid distance
xDelta = parentWidth / count;
yDelta = parentHeight / count;
xOffs = xDelta / 2;
yOffs = yDelta / 2;
if(orientation == ArrangeOrientation.TopLeftGrid) {
}
else if(orientation == ArrangeOrientation.TopRightGrid) {
xOffs = parentWidth - xOffs;
xDelta = -xDelta;
}
else if(orientation == ArrangeOrientation.BottomLeftGrid) {
yOffs = parentHeight - yOffs;
yDelta = -yDelta;
}
else if(orientation == ArrangeOrientation.BottomRightGrid) {
xOffs = parentWidth - xOffs;
yOffs = parentHeight - yOffs;
xDelta = -xDelta;
yDelta = -yDelta;
}
else {
xDelta = parentWidth / columns;
yDelta = parentHeight / rows;
xOffs = xDelta / 2;
yOffs = yDelta / 2;
}
// fit controls in grid layout
Point pRoot = new Point(/*ctrl[0].Parent.Location.X + */xOffs, /*ctrl[0].Parent.Location.Y + */yOffs);
y = 0; x = 0;
for(int i = 0; i < count; i++) {
if(orientation == ArrangeOrientation.VerticalGrid) {
// actual x/y - points zero based index
y = Convert.ToInt32(Math.Floor((double)i % rows));
// next row? zero based index
if(i % rows == 0 && i != 0) x++;
}
else {
// actual x/y - points zero based index
x = Convert.ToInt32(Math.Floor((double)i % columns));
// next row? zero based index
if(i % columns == 0 && i != 0) y++;
if(orientation == ArrangeOrientation.TopLeftGrid
|| orientation == ArrangeOrientation.TopRightGrid
|| orientation == ArrangeOrientation.BottomLeftGrid
|| orientation == ArrangeOrientation.BottomRightGrid)
y = x;
} // assign controls to grid
ctrl[i].Location = new Point(pRoot.X + x * xDelta - ctrl[i].Size.Width / 2 + xShrinkOffs, pRoot.Y + y * yDelta - ctrl[i].Size.Height / 2 + yShrinkOffs);
}
}
}
When there are multiple controls,above code was placing all controls on top of each other.
All i am trying to do is to center all the controls inside a panel but to put them next to each other instead of putting one over the other.
Here is my modified code (FYI this will center controls in 1 line not multiple line):
public enum Direction
{
Vertical,
Horizontal,
Both
}
public void CenterControls(List<Control> controls, Direction direction)
{
if (controls.Count > 1)
{
int controls_sum_width = 0;
int controls_seperation = 20;
int parentwidth = 0;
Point center;
foreach (Control ctl in controls)
{
controls_sum_width = controls_sum_width + ctl.Width + controls_seperation;
}
Point Container_center = new Point(controls[0].Parent.Width / 2, controls[0].Parent.Height / 2);
parentwidth = controls[0].Parent.Width;
int xoffset = (parentwidth - controls_sum_width) / 2;
int Location_X = 0;
foreach (Control ctl in controls)
{
center = new Point( ctl.Width / 2, ctl.Height / 2);
int yOffset = Container_center.Y - center.Y;
switch (direction)
{
case Direction.Vertical:
ctl.Location = new Point(ctl.Location.X + xoffset, ctl.Location.Y);
break;
case Direction.Horizontal:
ctl.Location = new Point(ctl.Location.X, yOffset);
break;
case Direction.Both:
ctl.Location = new Point(Location_X + xoffset, yOffset);
break;
}
Location_X = Location_X + ctl.Width+ controls_seperation;
}
}
else
{
Point parent_center;
Point center;
parent_center = new Point(controls[0].Parent.Width / 2, controls[0].Parent.Height / 2);
center = new Point(controls[0].Location.X + controls[0].Width / 2, controls[0].Location.Y + controls[0].Height / 2);
int xOffset = parent_center.X - center.X;
int yOffset = parent_center.Y - center.Y;
switch (direction)
{
case Direction.Vertical:
controls[0].Location = new Point(controls[0].Location.X + xOffset, controls[0].Location.Y);
break;
case Direction.Horizontal:
controls[0].Location = new Point(controls[0].Location.X, controls[0].Location.Y + yOffset);
break;
case Direction.Both:
controls[0].Location = new Point(controls[0].Location.X + xOffset, controls[0].Location.Y + yOffset);
break;
}
}
}
public void GetAllControl(Control c, List<Control> list)
{
//gets all controls and saves them to a list
foreach (Control control in c.Controls)
{
list.Add(control);
}
}

WPF custom panel scaling to fit window

I have a WPF custom panel which arranges its child elements in a spiral shape as per my requirements. The problem I am having is to do with scaling of the items when the window is resized - at the moment it does not scale. Can anyone provide a solution? Thanks - Ben
Custom panel
public class TagPanel : Panel
{
protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize)
{
Size resultSize = new Size(0, 0);
foreach (UIElement child in Children)
{
child.Measure(availableSize);
resultSize.Width = Math.Max(resultSize.Width, child.DesiredSize.Width);
resultSize.Height = Math.Max(resultSize.Height, child.DesiredSize.Height);
}
resultSize.Width = double.IsPositiveInfinity(availableSize.Width) ?
resultSize.Width : availableSize.Width;
resultSize.Height = double.IsPositiveInfinity(availableSize.Height) ?
resultSize.Height : availableSize.Height;
return resultSize;
}
protected class InnerPos
{
public UIElement Element { get; set; }
public Size Size { get; set; }
}
private Point GetSpiralPosition(double theta, Size windowSize)
{
double a = 5.0;
double n = 1.0;
double r = a * (Math.Pow(theta, 1.0 / n));
double x = r * Math.Cos(theta);
double y = r * Math.Sin(theta);
x += windowSize.Width / 2.0;
y += windowSize.Height / 2.0;
return new Point(x,y);
}
private Rect CreateRectangleCenteredAtPoint(Point pt, double width, double height)
{
return new Rect(new Point(pt.X - (width / 2.0), pt.Y - (height / 2.0)),
new Size(width, height));
}
protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize)
{
//double startPos = 0.0;
List<InnerPos> positions = new List<InnerPos>();
foreach (UIElement ch in Children)
{
// If this is the first time
// we've seen this child, add our transforms
//if (ch.RenderTransform as TransformGroup == null)
//{
// ch.RenderTransformOrigin = new Point(0, 0.5);
// TransformGroup group = new TransformGroup();
// ch.RenderTransform = group;
// group.Children.Add(new ScaleTransform());
// group.Children.Add(new TranslateTransform());
//}
positions.Add(new InnerPos()
{
Element = ch,
Size = ch.DesiredSize
});
}
//double currentTopMax
List<Rect> alreadyUsedPositions = new List<Rect>();
foreach (InnerPos child in positions.OrderByDescending(i => i.Size.Width))
{
for (double theta = 0.0; theta < 100.0; theta += 0.1)
{
Point spiralPos = GetSpiralPosition(theta, finalSize);
Rect centeredRect = CreateRectangleCenteredAtPoint(spiralPos,
child.Element.DesiredSize.Width,
child.Element.DesiredSize.Height);
bool posIsOk = true;
foreach (Rect existing in alreadyUsedPositions)
{
bool positionClashes = existing.IntersectsWith(centeredRect);
if (positionClashes == true)
{
posIsOk = false;
break;
}
}
if (posIsOk)
{
alreadyUsedPositions.Add(centeredRect);
child.Element.Arrange(centeredRect);
break;
}
}
}
return finalSize;
}
}
Is the HorizontalAlignment and VerticalAlignment of the Panel set to Stretch and the Width/Height to Auto (double.NaN)?
It looks like you are passing the finalSize into GetSpiralPosition(), which is the function where all the true work happens. Without seeing the code to this function it is difficult to say more, but I assume you are calculating correctly off of the finalSize.
Have you debugged to see if Arrange is getting called with the updated size when the window is resized? There would be two ways to test this: First, put a trace point instead of a breakpoint. Dump finalSize to the output window and see if it changes when the window is resized. Second, add a handler in the window to SizeChanged. Put a breakpoint in the handler. When the breakpoint is hit, put a breakpoint in the arrange method of the custom panel and then run.

Categories