モジュールの解説[作成]
local max=0
local function measure(node)
	local h=0
	if #node.sub~=0 then
		for _,subnode in ipairs(node.sub) do
			h=h+subnode.height
		end
	else
		h=1
	end
	node.height=h
end
local function paint(node,data,x,y)
	if #node.text~=0 then
		if x~=0 then
			table.insert(data[2],{x=x,y=2*y+node.height})
			x=x+1
		end
		table.insert(data[1],{x=x,y=2*y+node.height,text=node.text,type=mw.title.getCurrentTitle().text==node.text and 1 or (mw.title.new(node.text,0).exists and 0 or 2)})
		x=x+#node.text/3+1
	end
	if #node.sub>0 then
		table.insert(data[2],{x=x,y=2*y+node.height})
		x=x+1
		if #node.sub~=1 then
			table.insert(data[3],{x=x,y=2*y+node.sub[1].height,h=2*node.height-(node.sub[#node.sub].height+node.sub[1].height)})
		end
		for _,subnode in ipairs(node.sub) do
			paint(subnode,data,x,y)
			y=y+subnode.height
		end
	end
	if max<x then
		max=x
	end
end
local function create(args)
	local root=nil
	local stack={}
	for line in string.gmatch(mw.text.trim(args[2]),"[^\n]+") do
		local t=string.gsub(line, "^%s*(.-)%s*$", "%1")
		if(#t~=0) then
			if(t:match("{$")) then
				local node={text=string.sub(t,1,-2),sub={}}
				if(#stack==0) then
					root=node
				else
					table.insert(stack[#stack].sub,node)
				end
				table.insert(stack,node)
			elseif(t=="}") then
				measure(stack[#stack])
				table.remove(stack)
			else
				local node={text=t,sub={}}
				measure(node)
				table.insert(stack[#stack].sub,node)
			end
		end
	end
	local data={{},{},{}}
	paint(root,data,0,0)
	return{
		width=max*16+16,
		height=root.height*32+16,
		signals={
			{
				name="o",
				init=nil,
				streams={
					{
						type="rect:click",
						expr="datum.type==1?null:open('https://ja.wikipedia.org/wiki/'+datum.text)"
					}
				}
			}
		},
		data={
			{
				name="text",
				values=data[1]
			},
			{
				name="h",
				values=data[2]
			},
			{
				name="v",
				values=data[3]
			}
		},
		marks={
			{
				type="rect",
				from={
					data="v"
				},
				properties={
					enter={
						x={
							field="x",
							mult=16,
							offset=7
						},
						y={
							field="y",
							mult=16,
							offset=7
						},
						width={
							value=2
						},
						height={
							field="h",
							mult=16,
							offset=2
						},
						fill={
							value="#88c"
						}
					}
				}
			},
			{
				type="rect",
				from={
					data="h"
				},
				properties={
					enter={
						x={
							field="x",
							mult=16,
							offset=7
						},
						y={
							field="y",
							mult=16,
							offset=7
						},
						width={
							value=18
						},
						height={
							value=2
						},
						fill={
							value="#88c"
						}
					}
				}
			},
			{
				type="rect",
				from={
					data="text",
					transform={
						{
							type="formula",
							field="w",
							expr="length(datum.text)+1"
						}
					}
				},
				properties={
					enter={
						x={
							field="x",
							mult=16,
							offset=8
						},
						y={
							field="y",
							mult=16,
							offset=-4
						},
						width={
							field="w",
							mult=16
						},
						height={
							value=24
						},
						fill={
							value="#eef"
						},
						strokeWidth={
							value=2
						},
						stroke={
							value="#88c"
						},
						cursor={
							{
								test="datum.type==1",
								value="default"
							},
							{
								value="pointer"
							}
						}
					}
				}
			},
			{
				type="text",
				interactive=false,
				from={
					data="text"
				},
				properties={
					enter={
						text={
							field="text"
						},
						x={
							field="x",
							mult=16,
							offset=16
						},
						y={
							field="y",
							mult=16,
							offset=8
						},
						fill={
							{
								test="datum.type==2",
								value="#600"
							},
							{
								test="datum.type==1",
								value="black"
							},
							{
								value="#006"
							}
						},
						fontWeight={
							{
								test="datum.type==1",
								value="bold"
							},
							{
								value="normal"
							}
						},
						fontSize={
							value=16
						},
						baseline={
							value="middle"
						},
						font={
							value="TakaoGothic"
						}
					}
				}
			}
		}
	}
end
return {
	_=function(frame)
		return frame:extensionTag('graph',mw.text.jsonEncode(create(frame:getParent().args)),{mode='interactive'})
	end
}