从C#服务器端,是否有生成树映射并另存为映像的方法?

从C#服务器端,是否有生成树映射并另存为映像的方法?,c#,asp.net-mvc,image,treemap,C#,Asp.net Mvc,Image,Treemap,我一直用它在网页上创建树形图,效果很好。现在的问题是,我需要在服务器端生成的powerpoint演示文稿中包含这一点(我使用 我想到的最简单的方法是尝试在服务器上构建一个树状图并保存为图像(因为在powerpoint演示文稿中添加一个图像非常简单),但在谷歌搜索之后,我看不到任何可以从C#serverside生成树状图作为图像的解决方案 我可以从服务器端C#app创建一个树状图作为图像。你可以使用WPF渲染:但它也有缺点 (这是我自己的老博客的链接——但是如果你搜索“使用wpf生成图像”,你会得

我一直用它在网页上创建树形图,效果很好。现在的问题是,我需要在服务器端生成的powerpoint演示文稿中包含这一点(我使用

我想到的最简单的方法是尝试在服务器上构建一个树状图并保存为图像(因为在powerpoint演示文稿中添加一个图像非常简单),但在谷歌搜索之后,我看不到任何可以从C#serverside生成树状图作为图像的解决方案


我可以从服务器端C#app创建一个树状图作为图像。你可以使用WPF渲染:但它也有缺点

(这是我自己的老博客的链接——但是如果你搜索“使用wpf生成图像”,你会得到很多其他的例子——其中很多都比我的好!)

然而,在WPF中生成一棵树将是一项挑战,尽管它是可以做到的,因为WPF绘图原语本质上是分层的

它可能不合适,BUE也可以考虑Gravez——但是我不知道你在Web服务器上执行命令行有多大的运气。


我希望图书馆也会为此付费,因为这是一种常见的需求。

使用GDI+api可能是您的唯一选择,跨平台支持良好。但是,在服务器端使用GDI+进行任何操作时,都需要注意几个潜在的问题。值得一读,因为它解释了在DotNet中绘制图形的当前状态,并且在服务器端处理上有一点:

话虽如此,;这篇文章讨论了你的要求:

(这是专门讨论用GDI+生成树形图的,如果您阅读了评论并回答了问题,您将避免很多陷阱)


生成图像后,将其保存到某个地方的磁盘,然后希望将其嵌入到演示文稿中,这是一个简单的过程;你也可以选择写在流中,所以可以直接将它嵌入到PowerPoint文件中而不首先保存到磁盘。

< P>埃因霍温理工大学已经出版了方形树算法。把它变成了C。还有一个关于树形图的章节

当然,也有商业解决方案,比如来自或。它们的缺点可能是它们被设计成需要绘制的控件,因此您可能需要某种UI线程


对于C#中的treemap实现,还存在一个问题。以防你不记得。

鉴于已知的算法,用树形图绘制位图并不困难。目前,我没有足够的时间自己编写代码,但我有足够的时间(几乎)无意识地将一些现有代码移植到C:)让我们来看看javascript实现。它使用文中描述的算法。我在该实现中发现了一些问题,这些问题在C版本中已经修复。Javascript版本使用整数的纯数组(以及数组的数组)。我们定义了一些类:

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; }
}
然后开始向左舷移动。第一类货柜:

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);
        }
    }
}
现在让我们试着使用它,看看它是如何运行的:

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);
}
输出:

这可以通过多种方式进行扩展,代码质量肯定可以显著提高。但如果你愿意这样做,至少可以给你一个好的开始。好处是它速度快,不涉及外部依赖关系。
如果您想使用它并发现一些问题或它不符合您的某些要求,请随时询问,我会在有更多时间时改进它。

因为您已经生成了JS和HTML版本的所有内容,您可能需要检查的一件事是:

我使用它直接从生成的页面生成高分辨率的图形报告。它使用WKHTML来呈现它,您可以向它传递大量的参数来进行真正的微调。它对大多数事情都是免费的,而且效果很好。多线程是一种痛苦的屁股与它,但我没有遇到很多问题。如果你使用NRECO PDf库,你甚至还可以做成批的工作


有了这个,你所要做的就是把页面呈现成你现在的样子,用管道把它穿过库,插入到你的PPT中,一切都应该很好

我在这篇关于WPF中树映射的文章中基于以下解决方案

使用链接中的数据,可以如下定义模型(仅使用必要的数据):

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;
    }
}
var treeMap = new TreeMap(items);
var bmp = treeMap.Draw(1366, 768);
现在,用以下公共成员定义一个
TreeMap
类:

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
}
以及私人/助手成员:

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
}
private RectangleF emptyArea;
专用空心图纸(图形g、浮动x、浮动y、浮动宽度、浮动高度)
{
测量(宽度、高度);
foreach(项目中的var项目)
{
var sFormat=新的字符串格式
{
对齐=StringAlignment.Center,
LineAlignment=StringAlignment.Center
};
if(item.Children.Count()>0)
{
g、 FillRectangle(画笔.DimGray,x+item.TMData.Location.x,y+item.TMData.Location.y,item.TMData.Size.Width,15);
g、 DrawString(item.Name、SystemFonts.DefaultFont、Brushes.LightGray、新矩形F(x+item.TMData.Location.x、y+item.TMData.Location.y、item.TMData.Size.Width,15)格式);
var treeMap=新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);
}
其他的
{                    
g、 FillRectangle(新的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、Bruss.Black、新矩形F(x+item.TMData
var treeMap = new TreeMap(items);
var bmp = treeMap.Draw(1366, 768);
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
}
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);
//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();