Excel's composite header design - Poi+ tree data structure

In an MIS, it may be more or less related to the report. For Java development, the report design that may be used more is POI to construct Excel. Some time ago, I took this part out and looked back at it to taste the original design.

Requirement: Multi-line composite header design

analyze:

Observe this header structure, the parts that need to be designed are:

  1. Multi-row and multi-column merged cells for table names
  2. Merge of multiple rows and single columns of a column
  3. In a certain group of compound units, it is necessary to design a complex structure that combines single row and multiple columns, and multiple rows and single columns.

 For single-column design, it is actually to create cells, simply store values, and merge, and there is no difficulty.

But for the complex cell design of 4D-6F, it needs to be implemented in a special way.

start designing

1. Basic table schema

First remove the complex parts and build the ordinary table first:

import java.io.File;

import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.util.CellRangeAddress;

public class ExcelUtil {
    public static void main(String[] args) throws Exception {
        File file = new File("D:/temp/test.xls");
        file.createNewFile();
        //1. 创建excel文件
        HSSFWorkbook workbook = new HSSFWorkbook();
        
        //1.1 创建单元格样式
        HSSFCellStyle alignCenterStyle = workbook.createCellStyle();
        alignCenterStyle.setAlignment(HorizontalAlignment.CENTER);
        //表名样式:水平、垂直居中
        HSSFCellStyle font24Style = workbook.createCellStyle();
        font24Style.setAlignment(HorizontalAlignment.CENTER);
        font24Style.setVerticalAlignment(VerticalAlignment.CENTER);
        //24号字体
        HSSFFont font24 = workbook.createFont();
        font24.setFontHeightInPoints((short) 24);
        font24Style.setFont(font24);
        
        
        //2. 创建一个工作表Sheet
        HSSFSheet sheet = workbook.createSheet("新工作表");
        //2.1 设置列宽 -- 列宽是属于一个工作表的特征
        sheet.setColumnWidth(0, 6 * 2 * 256);
        sheet.setColumnWidth(1, 6 * 2 * 256);
        sheet.setColumnWidth(2, 6 * 2 * 256);
        sheet.setColumnWidth(3, 6 * 2 * 256);
        sheet.setColumnWidth(4, 6 * 2 * 256);
        sheet.setColumnWidth(5, 6 * 2 * 256);
        sheet.setColumnWidth(6, 6 * 2 * 256);
        
        //3.1 创建一个行
        int row = 0;
        HSSFRow tableHeadRow = sheet.createRow(row++);
        //3.2 在这个行中,创建一行单元格
        for (int i = 0; i < 7; i++) {
            tableHeadRow.createCell(i);
        }
        tableHeadRow.setRowStyle(font24Style);
        //再创建两个行
        sheet.createRow(row++);
        sheet.createRow(row++);
        //3.3 设置该单元格的内容
        HSSFCell tableHeadCell = tableHeadRow.getCell(0);
        tableHeadCell.setCellValue("表名");
        tableHeadCell.setCellStyle(font24Style);
        //表头的数据应该是很多单元格的合并、居中
        //四个参数:开始行,结束行,开始列,结束列
        sheet.addMergedRegion(new CellRangeAddress(0, row - 1, 0, 6));
        
        //4. 写表头
        HSSFRow headRow = sheet.createRow(row);
        headRow.setHeightInPoints(20);
        headRow.createCell(0).setCellValue("第0列");
        headRow.createCell(1).setCellValue("第1列");
        headRow.createCell(2).setCellValue("第2列");
        headRow.createCell(3).setCellValue("第3列");
        
        sheet.addMergedRegion(new CellRangeAddress(3, 3, 4, 6));
        
        //Workbook写入file中
        workbook.write(file);
        workbook.close();
    }
}

The table created after running is as follows:

After using the configuration file, you can realize the dynamic text, column width, etc. of the header. The changes are as follows:

Settings in the configuration file:

第0列=6
第1列=4
第2列=8
第3列=10

Modified source code:

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.util.CellRangeAddress;

public class ExcelUtil {
    public static final int COLUMN_SIZE = 8;
    
    public static void main(String[] args) throws Exception {
        //选用LinkedHashMap,保证迭代顺序的一致性
        Map<String, Integer> map = new LinkedHashMap<>();
        //加载配置文件(不用Properties读,在配置文件中的表头顺序即为创建Excel表格的顺序)
        InputStreamReader reader = new InputStreamReader(new FileInputStream("table.properties"), "UTF-8");
        BufferedReader br = new BufferedReader(reader);
        String readLine;
        while ((readLine = br.readLine()) != null) {
            String[] arr = readLine.split("=");
            map.put(arr[0], Integer.parseInt(arr[1]));
        }
        br.close();
        
        File file = new File("D:/temp/test.xls");
        file.createNewFile();
        //1. 创建excel文件
        HSSFWorkbook workbook = new HSSFWorkbook();
        
        //1.1 创建单元格样式
        HSSFCellStyle alignCenterStyle = workbook.createCellStyle();
        alignCenterStyle.setAlignment(HorizontalAlignment.CENTER);
        //表名样式:水平、垂直居中
        HSSFCellStyle font24Style = workbook.createCellStyle();
        font24Style.setAlignment(HorizontalAlignment.CENTER);
        font24Style.setVerticalAlignment(VerticalAlignment.CENTER);
        //24号字体
        HSSFFont font24 = workbook.createFont();
        font24.setFontHeightInPoints((short) 24);
        font24Style.setFont(font24);
        
        
        //2. 创建一个工作表Sheet
        HSSFSheet sheet = workbook.createSheet("新工作表");
        
        //3.1 创建一个行
        int row = 0;
        HSSFRow tableHeadRow = sheet.createRow(row++);
        //3.2 在这个行中,创建一行单元格
        for (int i = 0; i < COLUMN_SIZE; i++) {
            tableHeadRow.createCell(i);
        }
        tableHeadRow.setRowStyle(font24Style);
        //再创建两个行
        sheet.createRow(row++);
        sheet.createRow(row++);
        //3.3 设置该单元格的内容
        HSSFCell tableHeadCell = tableHeadRow.getCell(0);
        tableHeadCell.setCellValue("表名");
        tableHeadCell.setCellStyle(font24Style);
        //表头的数据应该是很多单元格的合并、居中
        //四个参数:开始行,结束行,开始列,结束列
        sheet.addMergedRegion(new CellRangeAddress(0, row - 1, 0, COLUMN_SIZE - 1));
        
        //4. 写表头
        HSSFRow headRow = sheet.createRow(row);
        headRow.setHeightInPoints(20);
        int index = 0;
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            headRow.createCell(index).setCellValue(entry.getKey());
            //设置列宽
            sheet.setColumnWidth(index++, entry.getValue() * 2 * 256);
        }
        
        sheet.addMergedRegion(new CellRangeAddress(3, 3, 4, 6));
        
        //Workbook写入file中
        workbook.write(file);
        workbook.close();
    }
}

running result:

 

2. Design the storage method of the composite header

Looking closely at the complex header, it is found that all children have the same parent cell (single row and multiple columns), and each cross-column cell has multiple child cells, which is very similar to the folder structure of Explorer!

Each folder has a folder name and a storage space in which either files or folders are stored.

 

To convert this structure into a data structure, the closest design method should be like this:

public class Node {
    private String text;
    private Integer width; //这个单元格应该在Excel中占有的宽度
    private Map<String, Node> map;
}

text represents the folder name, width represents the capacity of the folder, and map represents the "name-reference" mapping of all files/folders within the folder!

In this way, complex relationships of compound headers can be expressed.

 

3. Design the expression of the composite header

After the selection of the data structure is determined, it is necessary to choose a method that can convert the required content into nodes that can express a clear hierarchical relationship and then combine them into a tree.

There are actually many such solutions. The solution provided here is a set of expressions similar to OGNL:

First-level node. Second-level node. Third-level node = text

It seems to be similar to the package name. . . (manually funny)

 

 After modifying the configuration file:

第0列=6
第1列=4
第2列=8
第3列=10
第4列.子1=8
第4列.子2.孙1=4
第4列.子2.孙2=4
第5列=5

Pay attention to the writing rules of the expression configuration file:

All child nodes of the same parent node must be written in order from top to bottom. If the order is unreasonable, an exception will occur. . .

 

4. Parse Expressions

For expressions such as "first-level node. second-level node. third-level node", the hierarchical relationship can be clearly known.

In the code written before, it is equivalent to parse all nodes as first-level nodes:

public class ExcelUtil {
    public static final int COLUMN_SIZE = 8;
    
    public static void main(String[] args) throws Exception {
        //选用LinkedHashMap,保证迭代顺序的一致性
        Map<String, Integer> map = new LinkedHashMap<>();
        //加载配置文件(不用Properties读,在配置文件中的表头顺序即为创建Excel表格的顺序)
        InputStreamReader reader = new InputStreamReader(new FileInputStream("table.properties"), "UTF-8");
        BufferedReader br = new BufferedReader(reader);
        String readLine;
        while ((readLine = br.readLine()) != null) {
            String[] arr = readLine.split("=");
            map.put(arr[0], Integer.parseInt(arr[1]));
        }
        br.close();
    }
}

Finally, after parsing it out, it is a map collection

The task now is nothing more than to convert this map set into a series of node sequences with a hierarchical relationship.

After the task is clarified, the next step is to iterate over the content of the map and parse the key.

 

We regard this operation as saving files, one by one into the "root directory"

import java.util.LinkedHashMap;
import java.util.Map;

public class Node {
    private String text = null;
    private Integer width = 0;
    private Map<String, Node> map = new LinkedHashMap<>();
    
    /**
     * 根据表达式,解析出该表达式对应的Node结点,并存入
     * @author LinkedBear
     * @param text
     */
    public void addNodeByStringExpressionAndWidth(String text, Integer width) {
        //从根目录开始
        Map<String, Node> rootMap = map;
        String[] arr = text.split("\\.");
        //读到叶子结点的前一个结点处
        for (int i = 0; i < arr.length - 1; i++) {
            //逐层目录读取,如果没有get到,就创建一个新的目录
            Node node = rootMap.get(arr[i]);
            if (node == null) {
                node = new Node(arr[i]);
                rootMap.put(arr[i], node);
            }
            //新目录的大小要同步上
            node.setWidth(node.getWidth() + width);
            rootMap = node.getMap();
        }
        //此时的rootMap就是叶子结点所在的目录
        rootMap.put(arr[arr.length - 1], new Node(arr[arr.length - 1], width));
    }
    
    public Node(String text, Integer width) {
        this.text = text;
        this.width = width;
    }
    public Node(String text) {
        this.text = text;
    }
    public Node() {
    }
}

 

From the debugging results, the data has been correctly stored in the root:

 

The rest is to consider how to integrate this hierarchical relationship into a set of data, which can be used directly in poi:

/**
 * 遍历自己的存储空间,将所有结点按顺序整理成List
 * @author LinkedBear
 * @return
 */
public List<Node> parseToSeqNodes() {
    List<Node> list = new ArrayList<>();
    for (Map.Entry<String, Node> entry : map.entrySet()) {
        //先把自己保存进去
        list.add(entry.getValue());
        //如果该节点的map不是空集合,证明这是一个“文件夹”(根节点)
        //需要把自己add进去的同时,把它的孩子也全部add进去
        if (entry.getValue().getMap() != null && entry.getValue().getMap().size() > 0) {
            List<Node> nodes = entry.getValue().parseToSeqNodes();//递归调用
            list.addAll(nodes);
        }
    }
    return list;
}

 

In this way, the client can get all the nodes in just a few sentences:

//迭代Map集合,并重构一套“根目录”
Node root = new Node();
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    root.addNodeByStringExpressionAndWidth(entry.getKey(), entry.getValue());
}

//遍历“根目录”
List<Node> nodes = root.parseToSeqNodes();

 

But then we don't yet know the depth of this file tree!

You need to adjust the logic in Node to calculate the depth.

/**
 * 计算深度
 * @author LinkedBear
 * @return
 */
public int getDeep() {
    if (map.isEmpty()) {
        return 0;
    }
    List<Integer> list = new ArrayList<>();
    for (Map.Entry<String, Node> entry : map.entrySet()) {
        list.add(entry.getValue().getDeep());
    }
    Collections.sort(list);
    return list.get(list.size() - 1) + 1;
}

 

The role of depth: merge cells to make the header more beautiful.

 

5. Rebuild the header

After the reconstruction, the number of columns is also dynamic, which is obtained from the data just sorted.

Ignore the problem of merging cells first, traverse and create directly (ignored by other codes):

//4.1 创建多个行,并用数组存储
HSSFRow[] rows = new HSSFRow[rootDeep];
for (int i = 0; i < rows.length; i++) {
    rows[i] = sheet.createRow(row + i);
}
row += rows.length;

int columnIndex = 0;
//4.2 遍历所有结点
for (Node node : nodes) {
    //获取该节点的深度
    int deep = node.getDeep();
    System.out.println(deep);
    //从下往上取行,向右创建
    int topRowIndex = rows.length - deep - 1;//计算这个结点的控制范围上限
    for (int i = rows.length - 1; i >= 0; i--) {
        rows[i].createCell(columnIndex);
    }
    rows[topRowIndex].getCell(columnIndex).setCellValue(node.getText());
    columnIndex++;
}

operation result:

Basically has seen the prototype!

 

The rest: Convert linear headers to composite headers

If you only look at the results of the above operation, it is impossible to judge who belongs to whom (although the words betray them...)

Therefore, a method is needed to determine how many child nodes and how many leaf nodes are under which node (because the width of the leaf node is the width of the entire parent node).

Continue to add the following methods to the Node class:

/**
 * 获取该结点下的所有叶子节点的数量
 * @author LinkedBear
 * @return
 */
public int getChildrenCount() {
    if (map.isEmpty()) {
        return 1; //就自己一个
    }
    int count = 0;
    for (Map.Entry<String, Node> entry : map.entrySet()) {
        count += entry.getValue().getChildrenCount();
    }
    return count;
}

Through this method, it is also possible to know whether the node is a leaf node, and at the same time, it can also determine the number of leaf nodes owned by the node, so as to control the correlation of merged cells.

 

Also, notice that the build for these lines should not be a serial build, but a parallel build!

So just like the Row above, use an array to record where the cells of each row are constructed:

int[] columnIndexArr = new int[rootDeepLength];

 

After that, modify the client method as follows (full text):

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.util.CellRangeAddress;

public class ExcelUtil {
    public static void main(String[] args) throws Exception {
        //选用LinkedHashMap,保证迭代顺序的一致性
        Map<String, Integer> map = new LinkedHashMap<>();
        //加载配置文件(不用Properties读,在配置文件中的表头顺序即为创建Excel表格的顺序)
        InputStreamReader reader = new InputStreamReader(new FileInputStream("table.properties"), "UTF-8");
        BufferedReader br = new BufferedReader(reader);
        String readLine;
        while ((readLine = br.readLine()) != null) {
            String[] arr = readLine.split("=");
            map.put(arr[0], Integer.parseInt(arr[1]));
        }
        br.close();
        
        //迭代Map集合,并重构一套“根目录”
        Node root = new Node();
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            root.addNodeByStringExpressionAndWidth(entry.getKey(), entry.getValue());
        }
        
        //遍历“根目录”
        List<Node> nodes = root.parseToSeqNodes();
        //获取树的深度
        Integer rootDeepLength = root.getDeepLength();
        
        
        File file = new File("D:/temp/test.xls");
        file.createNewFile();
        //1. 创建excel文件
        HSSFWorkbook workbook = new HSSFWorkbook();
        
        //1.1 创建单元格样式
        HSSFCellStyle alignCenterStyle = workbook.createCellStyle();
        alignCenterStyle.setAlignment(HorizontalAlignment.CENTER);
        //表名样式:水平、垂直居中
        HSSFCellStyle font24Style = workbook.createCellStyle();
        font24Style.setAlignment(HorizontalAlignment.CENTER);
        font24Style.setVerticalAlignment(VerticalAlignment.CENTER);
        //24号字体
        HSSFFont font24 = workbook.createFont();
        font24.setFontHeightInPoints((short) 24);
        font24Style.setFont(font24);
        
        
        //2. 创建一个工作表Sheet
        HSSFSheet sheet = workbook.createSheet("新工作表");
        
        //3.1 创建一个行
        int row = 0;
        //3.2 在这个行中,创建一行单元格
        HSSFRow tableHeadRow = sheet.createRow(row);
        HSSFCell tableHeadCell = tableHeadRow.createCell(row);
        tableHeadRow.setRowStyle(font24Style);
        //3.3 设置该单元格的内容
        tableHeadCell.setCellValue("表名");
        tableHeadCell.setCellStyle(font24Style);
        row++;
        //表名的合并单元格无法在写表头之前计算(没有确定出有多少叶子结点)
        
        //row = 1
        
        //4. 写表头
        //4.1 创建多个行,并用数组存储
        HSSFRow[] rows = new HSSFRow[rootDeepLength];
        for (int i = 0; i < rows.length; i++) {
            rows[i] = sheet.createRow(row + i);
        }
        int[] columnIndexArr = new int[rootDeepLength];
            
        //4.2 遍历所有结点
        for (Node node : nodes) {
            //获取该节点的深度
            int deep = node.getDeepLength();
            //深度为0,这是普通一级结点
            if (deep == 0) {
                //从下往上取行,向右创建
                int topRowIndex = node.getDeep();//获取这个结点的控制范围上限
                int bottomRowIndex = rows.length - deep - 1;//计算这个结点的控制范围下限
                for (int i = rows.length - 1; i >= 0; i--) {
                    rows[i].createCell(columnIndexArr[i]);
                }
                rows[topRowIndex].getCell(columnIndexArr[topRowIndex]).setCellValue(node.getText());
                //一列多行,但如果只有一行,就没有必要合并了
                if (topRowIndex != bottomRowIndex) {
                    sheet.addMergedRegion(new CellRangeAddress(row + topRowIndex, row + bottomRowIndex, columnIndexArr[topRowIndex], columnIndexArr[topRowIndex]));
                }
                //涉及到的列的下标数组统一往后推一格
                for (int i = topRowIndex; i <= bottomRowIndex; i++) {
                    columnIndexArr[i] += 1;
                }
                //最后一行一定全是叶子结点,要控制列宽
                sheet.setColumnWidth(columnIndexArr[columnIndexArr.length - 1], node.getWidth() * 2 * 256);
            }else {
                //深度不为0,复合结点,需要复杂构建
                //从下往上取行,向右创建
                int topRowIndex = node.getDeep();//获取这个结点的控制范围上限
                int bottomRowIndex = rows.length - deep - 1;//计算这个结点的控制范围下限
                int childrenCount = node.getChildrenCount();
                //并行创建,能控制到的每一行都要创建足够的容量使得下面的叶子结点能放得下
                for (int i = bottomRowIndex; i >= topRowIndex; i--) {
                    for (int j = 0; j < childrenCount; j++) {
                        rows[i].createCell(columnIndexArr[i] + j);
                    }
                    columnIndexArr[i] += childrenCount;
                }
                //填充值,合并单元格(不需要判定是否为一个单元格)
                rows[bottomRowIndex].getCell(columnIndexArr[bottomRowIndex] - childrenCount).setCellValue(node.getText());
                sheet.addMergedRegion(new CellRangeAddress(row + topRowIndex, row + bottomRowIndex, columnIndexArr[topRowIndex] - childrenCount, columnIndexArr[topRowIndex] - 1));
            }
        }
        row += rows.length;
        
        //表头的数据应该是很多单元格的合并、居中
        //四个参数:开始行,结束行,开始列,结束列
        //因为上面加了1,这里还要抵消掉
        sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, columnIndexArr[columnIndexArr.length - 1] - 1));
        
        //Workbook写入file中
        workbook.write(file);
        workbook.close();
    }
}

Full text of Node.java:

package com.linkedbear.ssms.report;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class Node {
    private String text = null;
    private Integer width = 0;
    private Integer deep = 0;
    private Map<String, Node> map = new LinkedHashMap<>();
    
    /**
     * 根据表达式,解析出该表达式对应的Node结点,并存入
     * @author LinkedBear
     * @param text
     */
    public void addNodeByStringExpressionAndWidth(String text, Integer width) {
        //从根目录开始
        Map<String, Node> rootMap = map;
        String[] arr = text.split("\\.");
        
        Node node = null;
        //读到叶子结点的前一个结点处
        for (int i = 0; i < arr.length - 1; i++) {
            //逐层目录读取,如果没有get到,就创建一个新的目录
            node = rootMap.get(arr[i]);
            if (node == null) {
                node = new Node(arr[i]);
                rootMap.put(arr[i], node);
            }
            //新目录的大小要同步上
            node.setWidth(node.getWidth() + width);
            rootMap = node.getMap();
        }
        //此时的rootMap就是叶子结点所在的目录
        rootMap.put(arr[arr.length - 1], new Node(arr[arr.length - 1], width, arr.length - 1));
        //还要给这个文件的父文件夹设置deep
        if (node != null) {
            node.setDeep(arr.length - 2);
        }
    }
    
    /**
     * 遍历自己的存储空间,将所有结点按顺序整理成List
     * @author LinkedBear
     * @Time 2018年3月26日 下午2:50:05
     * @return
     */
    public List<Node> parseToSeqNodes() {
        List<Node> list = new ArrayList<>();
        for (Map.Entry<String, Node> entry : map.entrySet()) {
            //先把自己保存进去
            list.add(entry.getValue());
            //如果该节点的map不是空集合,证明这是一个“文件夹”(根节点)
            //需要把自己add进去的同时,把它的孩子也全部add进去
            if (entry.getValue().getMap() != null && entry.getValue().getMap().size() > 0) {
                List<Node> nodes = entry.getValue().parseToSeqNodes();//递归调用
                list.addAll(nodes);
            }
        }
        return list;
    }
    
    /**
     * 计算深度
     * @author LinkedBear
     * @return
     */
    public int getDeepLength() {
        if (map.isEmpty()) {
            return 0;
        }
        List<Integer> list = new ArrayList<>();
        for (Map.Entry<String, Node> entry : map.entrySet()) {
            list.add(entry.getValue().getDeepLength());
        }
        Collections.sort(list);
        return list.get(list.size() - 1) + 1;
    }
    
    /**
     * 获取该结点下的所有叶子节点的数量
     * @author LinkedBear
     * @return
     */
    public int getChildrenCount() {
        //如果map为空,证明是叶子结点,要计数
        if (map.isEmpty()) {
            return 1;
        }
        //由于不是叶子结点,所以不能计当前的数,这里的基数为0
        int count = 0;
        for (Map.Entry<String, Node> entry : map.entrySet()) {
            count += entry.getValue().getChildrenCount();
        }
        return count;
    }
    
    public Node(String text, Integer width, Integer deep) {
        this.text = text;
        this.width = width;
        this.deep = deep;
    }
    public Node(String text, Integer width) {
        this.text = text;
        this.width = width;
    }
    public Node(String text) {
        this.text = text;
    }
    public Node() {
    }
    //get,set
}

 

Final running result:

 

As for the subsequent tuning styles, it is very simple. Baidu searched a lot of  ̄へ ̄

 

Please indicate the source of the original text when reprinting: https://my.oschina.net/u/LinkedBear/blog/1785242

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324454242&siteId=291194637