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.
WebBrowser
control fromWinForms
(without showing the form) to open your image generated by javascript and then useControl.DrawToBitmap
method to get you image example : #4674802 – Benisch