使用什么工具绘制文件树形图?

114
给定一个文件树 - 一个包含目录的目录等,如何编写一个脚本来创建文件树的图表,并将其嵌入到可以在文字处理文档中使用的图形文件中。
我倾向于矢量(SVG、EPS、EMF 等)文件。
该工具必须在 Windows 上运行,但最好是跨平台的。
该工具可以是商业软件,但最好是免费的。
更新 2012-02-20。
该问题与文档子项目有关。我不得不解释文件(特别是资源和配置文件)位于哪里。最终我使用了 dos tree 命令。对于短文件夹,我截屏了结果,对于长文件夹,我重定向到一个文本文件,然后进行编辑。例如,如果一个子文件夹包含 20 个同类型的文件,而这些文件都对我要说明的观点没有实际意义,我会保留其中两个文件并用一行 "..." 替换其余文件。之后,我再次将文件打印到控制台并进行截屏。
在截屏之前,我必须将前景色修改为黑色,背景色修改为白色,以便外观更好,并在需要打印文档时节省墨水。
非常令人惊讶的是,没有更好的工具可供使用。如果我有时间,我会编写一个 Visio 扩展程序,或者可能是一些生成 SVG 的命令行工具。由于 SVG 是 HTML5 的一个子标准,因此它甚至可以轻松地包含在在线文档中。
更新 2017-10-17。
很抱歉,此问题被认为不属于 SO 范围而被删除。因此我改了一下措辞。我需要一个脚本 - 而不是所见即所得的工具。因此,任何脚本语言或库都可以。因此,这是一个代码编写问题,我认为属于 SO。

13
为什么这个问题被关闭了?有编程DSL(领域特定语言)可以绘制树形结构,例如像Graphviz这样的工具可以通过"编程方式"解决这个问题。 - Piotr Lesnicki
6
我将重新打开这个(暂定),因为如果只是简单地“如何显示屏幕上的内容”,他会要求一个屏幕截图工具。如果他想要绘制它,很可能是为了设计文档或演示文稿,因此他最终会编写程序。 - paxdiablo
2
同意。我以前也需要过这种功能,不得不用 Visio 来模拟。当时是为了欧盟的文档要求。肯定跟代码有关。 - Joseph Ferris
7
非常愚蠢,将此作为离题关闭。我也发现了需要某些东西的需求..SO喜欢进行审查。 - Boltimuss
1
如果我的问题不适合在这里,请原谅。我理解原因。感谢所有回答过我的人,非常有帮助。为了澄清,我需要一个图表来包含项目树的文档。截图不够用,因为整个树比一个屏幕还长。 - Michael
显示剩余2条评论
5个回答

136

从 MS-DOS 的 tree 命令复制和粘贴也可能对您有用。示例:

tree

C:\Foobar>tree
C:.
├───FooScripts
├───barconfig
├───Baz
│   ├───BadBaz
│   └───Drop
...

tree /F

C:\Foobar>tree
C:.
├───FooScripts
│    foo.sh
├───barconfig
│    bar.xml
├───Baz
│   ├───BadBaz
│   │    badbaz.xml
│   └───Drop
...

tree /A

C:\Foobar>tree /A
C:.
+---FooScripts
+---barconfig
+---Baz
¦   +---BadBaz
¦   \---Drop
...

tree /F /A

C:\Foobar>tree /A
C:.
+---FooScripts
¦    foo.sh
+---barconfig
¦    bar.xml
+---Baz
¦   +---BadBaz
¦   ¦    badbaz.xml
¦   \---Drop
...

语法 [来源]

tree [drive:][路径] [/F] [/A]

drive:\路径 — 盘符和包含磁盘的目录,以显示目录结构但不列出文件。

/F — 包括每个目录中的所有文件。

/A — 将链接线所使用的图形字符替换为扩展字符,而不是图形字符。 /a 用于不支持图形字符的代码页,并将输出发送到无法正确解释图形字符的打印机。


1
好主意,但如果有带重音字母的文件/文件夹,它们将使用OEM字符集而不是ANSI字符集。当然,对于大多数用户(至少是英语用户),这可能不是问题。半图形字符也是如此。 - PhiLho
7
Linux也有类似的“tree”命令,我在查看Stack Overflow的问题后刚刚发现了这一点。感谢你指出我应该寻找的名称!使用“tree -A”可以创建树形结构并使用漂亮的绘图字符;而普通的“tree”则限制于ASCII字符。 - Brandon Rhodes
1
将文件结构保存到文件中: tree > file_structure.txt 我知道这个命令在Unix系统上可以使用,但是我不确定在Windows上是否也可以。 - Lucio Mollinedo
有没有办法限制显示的文件数量?例如,我在一个子文件夹中有数百个具有相同“名称逻辑”的文件。我想只显示其中的一些文件并显示文件数量,而不是所有文件。 - p.deman

19

Graphviz - 来自网页:

Graphviz布局程序使用一种简单的文本语言描述图形,并生成多种有用的格式,例如图片和SVG用于Web页面,Postscript用于包含在PDF或其他文档中;或在交互式图形浏览器中显示。(Graphviz还支持GXL,一种XML方言。)

这是我发现最简单且最有效的工具来创建各种盒式图表。我有并使用Visio和OmniGraffle,但总是有“再做一点调整”的诱惑。

编写代码以生成Graphviz所需的“dot文件”格式也非常容易,因此自动化图表生成也很容易实现。


5
为什么不在Windows文件系统上建立文件结构并使用屏幕截图工具(如HyperSnap或广泛使用的Alt-PrtScr)捕获资源管理器窗口的一部分,然后将其填充为所需名称。
我曾经在展示一个具有可折叠部分的网页应用程序时这样做,只需要创建看起来像所需条目的文件。HyperSnap至少支持JPG格式(可能还支持其他格式,但我从未去了解过)。
或者你可以截取资源管理器中的图标+/-,在MS Word Draw中使用它们制作图片,但我从未能让MS Word Draw正常运行。

1
资源管理器无法显示嵌套的文件夹,只显示顶层文件夹。 - Eliezer Miron

5
如承诺一样,这是我的开罗版本。我用Lua编写了它,使用lfs遍历目录。我喜欢这些小挑战,因为它们让我探索我想挖掘已久的API...
lfs和LuaCairo都是跨平台的,所以它应该可以在其他系统上运行(在法语WinXP Pro SP3上测试过)。
我制作了一个第一版,当我遍历树时绘制文件名。优点:没有内存开销。不便之处:我必须预先指定图像大小,因此列表很可能被截断。
所以我制作了这个版本,首先遍历目录树,将其存储在Lua表中。然后,知道文件数,创建适合(至少垂直)的画布并绘制名称。
您可以轻松地在PNG渲染和SVG之间切换。后者的问题:Cairo以低级别生成它,绘制字母而不是使用SVG的文本功能。好吧,至少,它保证即使在没有字体的系统上也能准确呈现。但文件更大...如果之后压缩它以获得.svgz文件,这不是真正的问题。
或者直接生成SVG也不应该太难,我过去使用Lua生成SVG。
-- LuaFileSystem <http://www.keplerproject.org/luafilesystem/>
require"lfs"
-- LuaCairo <http://www.dynaset.org/dogusanh/>
require"lcairo"
local CAIRO = cairo


local PI = math.pi
local TWO_PI = 2 * PI

--~ local dirToList = arg[1] or "C:/PrgCmdLine/Graphviz"
--~ local dirToList = arg[1] or "C:/PrgCmdLine/Tecgraf"
local dirToList = arg[1] or "C:/PrgCmdLine/tcc"
-- Ensure path ends with /
dirToList = string.gsub(dirToList, "([^/])$", "%1/")
print("Listing: " .. dirToList)
local fileNb = 0

--~ outputType = 'svg'
outputType = 'png'

-- dirToList must have a trailing slash
function ListDirectory(dirToList)
  local dirListing = {}
  for file in lfs.dir(dirToList) do
    if file ~= ".." and file ~= "." then
      local fileAttr = lfs.attributes(dirToList .. file)
      if fileAttr.mode == "directory" then
        dirListing[file] = ListDirectory(dirToList .. file .. '/')
      else
        dirListing[file] = ""
      end
      fileNb = fileNb + 1
    end
  end
  return dirListing
end

--dofile[[../Lua/DumpObject.lua]] -- My own dump routine
local dirListing = ListDirectory(dirToList)
--~ print("\n" .. DumpObject(dirListing))
print("Found " .. fileNb .. " files")

--~ os.exit()

-- Constants to change to adjust aspect
local initialOffsetX = 20
local offsetY = 50
local offsetIncrementX = 20
local offsetIncrementY = 12
local iconOffset = 10

local width = 800 -- Still arbitrary
local titleHeight = width/50
local height = offsetIncrementY * (fileNb + 1) + titleHeight
local outfile = "CairoDirTree." .. outputType

local ctxSurface
if outputType == 'svg' then
  ctxSurface = cairo.SvgSurface(outfile, width, height)
else
  ctxSurface = cairo.ImageSurface(CAIRO.FORMAT_RGB24, width, height)
end
local ctx = cairo.Context(ctxSurface)

-- Display a file name
-- file is the file name to display
-- offsetX is the indentation
function DisplayFile(file, bIsDir, offsetX)
  if bIsDir then
    ctx:save()
    ctx:select_font_face("Sans", CAIRO.FONT_SLANT_NORMAL, CAIRO.FONT_WEIGHT_BOLD)
    ctx:set_source_rgb(0.5, 0.0, 0.7)
  end

  -- Display file name
  ctx:move_to(offsetX, offsetY)
  ctx:show_text(file)

  if bIsDir then
    ctx:new_sub_path() -- Position independent of latest move_to
    -- Draw arc with absolute coordinates
    ctx:arc(offsetX - iconOffset, offsetY - offsetIncrementY/3, offsetIncrementY/3, 0, TWO_PI)
    -- Violet disk
    ctx:set_source_rgb(0.7, 0.0, 0.7)
    ctx:fill()
    ctx:restore() -- Restore original settings
  end

  -- Increment line offset
  offsetY = offsetY + offsetIncrementY
end

-- Erase background (white)
ctx:set_source_rgb(1.0, 1.0, 1.0)
ctx:paint()

--~ ctx:set_line_width(0.01)

-- Draw in dark blue
ctx:set_source_rgb(0.0, 0.0, 0.3)
ctx:select_font_face("Sans", CAIRO.FONT_SLANT_NORMAL, CAIRO.FONT_WEIGHT_BOLD)
ctx:set_font_size(titleHeight)
ctx:move_to(5, titleHeight)
-- Display title
ctx:show_text("Directory tree of " .. dirToList)

-- Select font for file names
ctx:select_font_face("Sans", CAIRO.FONT_SLANT_NORMAL, CAIRO.FONT_WEIGHT_NORMAL)
ctx:set_font_size(10)
offsetY = titleHeight * 2

-- Do the job
function DisplayDirectory(dirToList, offsetX)
  for k, v in pairs(dirToList) do
--~ print(k, v)
    if type(v) == "table" then
      -- Sub-directory
      DisplayFile(k, true, offsetX)
      DisplayDirectory(v, offsetX + offsetIncrementX)
    else
      DisplayFile(k, false, offsetX)
    end
  end
end

DisplayDirectory(dirListing, initialOffsetX)

if outputType == 'svg' then
    cairo.show_page(ctx)
else
  --cairo.surface_write_to_png(ctxSurface, outfile)
  ctxSurface:write_to_png(outfile)
end

ctx:destroy()
ctxSurface:destroy()

print("Found " .. fileNb .. " files")

当然,您可以更改样式。我没有绘制连接线,因为我认为这并非必要。以后我可能会选择性地添加它们。


2
使用Graphviz是一个好建议:您可以生成dot文件,它会完成测量字符串、布局等繁重的工作。此外,它可以将图形输出为许多格式,包括矢量格式。
我在邮件列表中找到了一个精确做到这一点的Perl程序,但我现在找不到它了!我复制了示例dot文件并学习了它,因为我不太了解这种声明性语法,我想学习更多。
问题:使用最新的Graphviz,我遇到了错误(或者更准确地说,警告,因为最终的图表已经生成),原始图表和我手写的图表都有这个错误。一些搜索显示,这个错误出现在旧版本中,并在更近期的版本中消失了。看起来它又回来了。
我仍然提供这个文件,也许它可以成为某人的起点,或者对您的需求足够(当然,您仍然需要生成它)。
digraph tree
{
  rankdir=LR;

  DirTree [label="Directory Tree" shape=box]

  a_Foo_txt [shape=point]
  f_Foo_txt [label="Foo.txt", shape=none]
  a_Foo_txt -> f_Foo_txt

  a_Foo_Bar_html [shape=point]
  f_Foo_Bar_html [label="Foo Bar.html", shape=none]
  a_Foo_Bar_html -> f_Foo_Bar_html

  a_Bar_png [shape=point]
  f_Bar_png [label="Bar.png", shape=none]
  a_Bar_png -> f_Bar_png

  a_Some_Dir [shape=point]
  d_Some_Dir [label="Some Dir", shape=ellipse]
  a_Some_Dir -> d_Some_Dir

  a_VBE_C_reg [shape=point]
  f_VBE_C_reg [label="VBE_C.reg", shape=none]
  a_VBE_C_reg -> f_VBE_C_reg

  a_P_Folder [shape=point]
  d_P_Folder [label="P Folder", shape=ellipse]
  a_P_Folder -> d_P_Folder

  a_Processing_20081117_7z [shape=point]
  f_Processing_20081117_7z [label="Processing-20081117.7z", shape=none]
  a_Processing_20081117_7z -> f_Processing_20081117_7z

  a_UsefulBits_lua [shape=point]
  f_UsefulBits_lua [label="UsefulBits.lua", shape=none]
  a_UsefulBits_lua -> f_UsefulBits_lua

  a_Graphviz [shape=point]
  d_Graphviz [label="Graphviz", shape=ellipse]
  a_Graphviz -> d_Graphviz

  a_Tree_dot [shape=point]
  f_Tree_dot [label="Tree.dot", shape=none]
  a_Tree_dot -> f_Tree_dot

  {
    rank=same;
    DirTree -> a_Foo_txt -> a_Foo_Bar_html -> a_Bar_png -> a_Some_Dir -> a_Graphviz [arrowhead=none]
  }
  {
    rank=same;
    d_Some_Dir -> a_VBE_C_reg -> a_P_Folder -> a_UsefulBits_lua [arrowhead=none]
  }
  {
    rank=same;
    d_P_Folder -> a_Processing_20081117_7z [arrowhead=none]
  }
  {
    rank=same;
    d_Graphviz -> a_Tree_dot [arrowhead=none]
  }
}

> dot -Tpng Tree.dot -o Tree.png
Error: lost DirTree a_Foo_txt edge
Error: lost a_Foo_txt a_Foo_Bar_html edge
Error: lost a_Foo_Bar_html a_Bar_png edge
Error: lost a_Bar_png a_Some_Dir edge
Error: lost a_Some_Dir a_Graphviz edge
Error: lost d_Some_Dir a_VBE_C_reg edge
Error: lost a_VBE_C_reg a_P_Folder edge
Error: lost a_P_Folder a_UsefulBits_lua edge
Error: lost d_P_Folder a_Processing_20081117_7z edge
Error: lost d_Graphviz a_Tree_dot edge

我会尝试另一种方法,使用Cairo,它也能够输出多种格式。这样做需要更多的工作(计算位置/偏移量),但结构很简单,不应该太难。



网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接