Files
obsidian-excalidraw-plugin/docs/zh-cn/AutomateHowTo.md
dmscode a297dbbe52 Update zh-cn.ts to 74c0af2
And fixed some link path
2024-09-14 08:09:10 +08:00

18 KiB
Raw Blame History

Excalidraw 自动化使用指南

此说明当前更新至 5569cff

English

Excalidraw 自动化允许您使用 Templater 插件创建 Excalidraw 绘图。

通过一些工作,使用 Excalidraw 自动化,您可以根据保管库中的文档生成简单的思维导图、填写 SVG 表单、创建自定义图表等。

您可以通过 ExcalidrawAutomate 对象访问 Excalidraw 自动化。我建议您以以下代码开始您的自动化脚本。

使用 CTRL+Shift+V 将代码粘贴到 Obsidian 中!

const ea = ExcalidrawAutomate;
ea.reset();

第一行创建了一个实用的常量,这样您就可以避免写 100 次 ExcalidrawAutomate

第二行将 ExcalidrawAutomate 重置为默认值。这一点很重要,因为您将不知道之前执行了哪个模板,因此您也不知道 Excalidraw 的状态。

使用 Excalidraw 自动化的基本逻辑

  1. 设置您想要绘制的元素的样式
  2. 添加元素。每添加一个新元素,它都会在上一个元素的上方添加一层,因此在重叠对象的情况下,后添加的元素会在前一个元素之上。
  3. 调用 await ea.create(); 来实例化绘图

您可以在添加不同元素之间更改样式。我将元素样式与创建分开是基于这样的假设:您可能会设置描边颜色、描边样式、描边粗糙度等,并使用这些设置绘制大多数元素。每次添加元素时设置所有这些参数是没有意义的。

在深入探讨之前,这里有两个简单的示例脚本

使用模板在自定义文件夹中创建具有自定义名称的新绘图

这个简单的脚本为您提供了比 Excalidraw 插件设置更大的灵活性,可以为您的绘图命名、将其放入文件夹中,并应用模板。

使用 CTRL+Shift+V 将代码粘贴到 Obsidian 中!

<%*
  const ea = ExcalidrawAutomate;
  ea.reset();
  await ea.create({
    filename    : tp.date.now("HH.mm"), 
    foldername  : tp.date.now("YYYY-MM-DD"),
    templatePath: "Excalidraw/Template1.excalidraw",
    onNewPane   : false
  });
%>

创建一个简单的绘图

使用 CTRL+Shift+V 将代码粘贴到 Obsidian 中!

<%*
  const ea = ExcalidrawAutomate;
  ea.reset();
  ea.addRect(-150,-50,450,300);
  ea.addText(-100,70,"Left to right");
  ea.addArrow([[-100,100],[100,100]]);

  ea.style.strokeColor = "red";
  ea.addText(100,-30,"top to bottom",{width:200,textAligh:"center"});
  ea.addArrow([[200,0],[200,200]]);
  await ea.create();
%>

该脚本将生成以下绘图:

FristDemo

属性和功能一览

这是 ExcalidrawAutomate 实现的接口: 使用 CTRL+Shift+V 将代码粘贴到 Obsidian 中!

ExcalidrawAutomate: {
  style: {
    strokeColor: string;
    backgroundColor: string;
    angle: number;
    fillStyle: FillStyle;
    strokeWidth: number;
    storkeStyle: StrokeStyle;
    roughness: number;
    opacity: number;
    strokeSharpness: StrokeSharpness;
    fontFamily: FontFamily;
    fontSize: number;
    textAlign: string;
    verticalAlign: string;
    startArrowHead: string;
    endArrowHead: string;
  }
  canvas: {theme: string, viewBackgroundColor: string};
  setFillStyle: Function;
  setStrokeStyle: Function;
  setStrokeSharpness: Function;
  setFontFamily: Function;
  setTheme: Function;
  addRect: Function;
  addDiamond: Function;
  addEllipse: Function;
  addText: Function;
  addLine: Function;
  addArrow: Function;
  connectObjects: Function;
  addToGroup: Function;
  toClipboard: Function;
  create: Function;
  createPNG: Function;
  createSVG: Function;
  clear: Function;
  reset: Function;
};

元素样式

正如您所注意到的,某些样式具有设置函数。这是为了帮助您浏览属性的可用值。不过,您并不需要使用设置函数,您也可以直接设置值。

strokeColor

字符串。线条的颜色。CSS 合法颜色值

允许的值包括 HTML 颜色名称、十六进制 RGB 字符串,例如 #FF0000 表示红色。

backgroundColor

字符串。对象的填充颜色。CSS 合法颜色值

允许的值包括 HTML 颜色名称、十六进制 RGB 字符串,例如 #FF0000 表示红色,或 transparent(透明)。

angle

数字。以弧度表示的旋转。90° == Math.PI/2

fillStyle, setFillStyle()

type FillStyle = "hachure" | "cross-hatch" | "solid";
setFillStyle (val:number);

fillStyle 是一个字符串.

setFillStyle() 接受一个数字:

  • 0: "hachure"(斜线填充)
  • 1: "cross-hatch"(交叉斜线填充)
  • 其他任何数字: "solid"(实心填充)

strokeWidth

数字,设置描边的宽度。

strokeStyle, setStrokeStyle()

type StrokeStyle = "solid" | "dashed" | "dotted";
setStrokeStyle (val:number);

strokeStyle 是一个字符串。

setStrokeStyle() 接受一个数字:

  • 0: "solid"(实线)
  • 1: "dashed"(虚线)
  • 其他任何数字: "dotted"(点线)

roughness

数字。在 Excalidraw 中称为“粗糙度”。接受三个值:

  • 0: 建筑师
  • 1: 艺术家
  • 2: 卡通画家

opacity

介于 0 和 100 之间的数字。对象的透明度,包括描边和填充。

strokeSharpness, setStrokeSharpness()

type StrokeSharpness = "round" | "sharp";
setStrokeSharpness(val:nmuber);

strokeSharpness 是一个字符串。

“round” 线条是曲线“sharp” 线条在转折点处断开(硬弯折)。

setStrokeSharpness() 接受一个数字:

  • 0: "round"(圆滑)
  • 其他任何数字: "sharp"(尖锐)

fontFamily, setFontFamily()

数字。有效值为 1、2 和 3。

setFontFamily() 也会接受一个数字并返回字体名称。

  • 1: "Virgil, Segoe UI Emoji"
  • 2: "Helvetica, Segoe UI Emoji"
  • 3: "Cascadia, Segoe UI Emoji"

fontSize

数字。默认值为 20 像素。

textAlign

字符串。文本的水平对齐方式。有效值为 "left"(左对齐)、"center"(居中对齐)、"right"(右对齐)。

在使用 addText() 函数设置固定宽度时,这一点很重要。

verticalAlign

字符串。文本的垂直对齐方式。有效值为 "top"(顶部)和 "middle"(中间)。

在使用 addText() 函数设置固定高度时,这一点很重要。

startArrowHead, endArrowHead

字符串。有效值为 "arrow"(箭头)、"bar"(线条)、"dot"(点)和 "none"(无)。指定箭头的起始和结束。

在使用 addArrow()connectObjects() 函数时,这一点很重要。

canvas

设置画布的属性。

theme, setTheme()

字符串。有效值为 "light"(明亮)和 "dark"(黑暗)。

setTheme() 接受一个数字:

  • 0: "light"(明亮)
  • 其他任何数字: "dark"(黑暗)

viewBackgroundColor

字符串。对象的填充颜色。CSS 合法颜色值

允许的值包括 HTML 颜色名称、十六进制 RGB 字符串,例如 #FF0000 表示红色,或 transparent(透明)。

添加对象

这些函数将向您的绘图中添加对象。画布是无限的,接受负值和正值的 X 和 Y 坐标。X 值从左到右增加Y 值从上到下增加。

坐标

addRect(), addDiamond(), addEllipse()

addRect(topX:number, topY:number, width:number, height:number):string
addDiamond(topX:number, topY:number, width:number, height:number):string
addEllipse(topX:number, topY:number, width:number, height:number):string

返回对象的 id。在用线连接对象时,需要使用 id。请参见后文。

addText

addText(topX:number, topY:number, text:string, formatting?:{width:number, height:number,textAlign: string, verticalAlign:string, box: boolean, boxPadding: number}):string

向绘图中添加文本。

格式参数是可选的:

  • 如果未指定 width(宽度)和 height(高度),函数将根据 fontFamilyfontSize 和提供的文本计算宽度和高度。
  • 如果您希望文本相对于绘图中的其他元素居中,可以提供固定的高度和宽度,同时可以指定 textAlignverticalAlign,如上所述。例如:{width:500, textAlign:"center"}
  • 如果您想在文本周围添加一个框,请设置 {box:true}

返回对象的 id。在用线连接对象时,需要使用 id。请参见后文。如果 {box:true},则返回包围框的 id

addLine()

addLine(points: [[x:number,y:number]]):void

添加一条连接提供的点的线。必须至少包含两个点 points.length >= 2。如果提供的点超过两个,间隔点将作为断点添加。如果 strokeSharpness 设置为 "sharp",线条将在转折处断开;如果设置为 "round",线条将是曲线。

addArrow()

addArrow(points: [[x:number,y:number]],formatting?:{startArrowHead:string,endArrowHead:string,startObjectId:string,endObjectId:string}):void

添加一条连接提供的点的箭头。必须至少包含两个点 points.length >= 2。如果提供的点超过两个,间隔点将作为断点添加。如果元素 style.strokeSharpness 设置为 "sharp",线条将在转折处断开;如果设置为 "round",线条将是曲线。

startArrowHeadendArrowHead 指定要使用的箭头类型,如上所述。有效值为 "none"(无)、"arrow"(箭头)、"dot"(点)和 "bar"(线条)。例如:{startArrowHead: "dot", endArrowHead: "arrow"}

startObjectIdendObjectId 是连接对象的对象 ID。我建议使用 connectObjects 而不是调用 addArrow() 来连接对象。

connectObjects()

declare type ConnectionPoint = "top"|"bottom"|"left"|"right";
connectObjects(objectA: string, connectionA: ConnectionPoint, objectB: string, connectionB: ConnectionPoint, formatting?:{numberOfPoints: number,startArrowHead:string,endArrowHead:string, padding: number}):void

连接两个对象的箭头。

objectAobjectB 是字符串。这些是要连接的对象的 ID。这些 ID 是通过 addRect()addDiamond()addEllipse()addText() 创建这些对象时返回的。

connectionAconnectionB 指定在对象上的连接位置。有效值为:"top"(上)、"bottom"(下)、"left"(左)和 "right"(右)。

numberOfPoints 设置线条的间隔断点数量。默认值为零,意味着箭头的起点和终点之间不会有断点。当在绘图中移动对象时,这些断点将影响 Excalidraw 如何重新调整线条。

startArrowHeadendArrowHead 的功能与 addArrow() 中描述的一致。

addToGroup()

addToGroup(objectIds:[]):void

objectIds 中列出的对象进行分组。

Utility functions

clear()

clear() 将从缓存中清除对象,但会保留元素样式设置。

reset()

reset() 将首先调用 clear(),然后将元素样式重置为默认值。

toClipboard()

async toClipboard(templatePath?:string)

将生成的图形放入剪贴板。当您不想创建新图形,而是想将其他项目粘贴到现有图形上时,这非常有用。

create()

async create(params?:{filename: string, foldername:string, templatePath:string, onNewPane: boolean})

创建图形并打开它。

filename 是要创建的图形的文件名(不带扩展名)。如果为 null,则 Excalidraw 会生成一个文件名。

foldername 是文件应创建的文件夹。如果为 null,则将根据 Excalidraw 设置使用新图形的默认文件夹。

templatePath 是包含完整路径和扩展名的模板文件名。该模板文件将作为基础层添加,所有通过 ExcalidrawAutomate 添加的额外对象将出现在模板元素之上。如果为 null,则不使用模板,即空白图形将作为添加对象的基础。

onNewPane 定义新图形应创建的位置。false 将在当前活动的标签页中打开图形;true 将通过垂直分割当前标签页来打开图形。

示例:

create({filename:"my drawing", foldername:"myfolder/subfolder/", templatePath: "Excalidraw/template.excalidraw", onNewPane: true});

createSVG()

async createSVG(templatePath?:string)

返回一个包含生成图形的 HTML SVGSVGElement

createPNG()

async createPNG(templatePath?:string)

返回一个包含生成图形的 PNG 图像的 blob。

示例

将新图形插入到当前编辑的文档中

此模板将提示您输入图形的标题。它将在您提供的标题下创建一个新图形,并在您正在编辑的文档的文件夹中。然后,它将在光标位置插入新图形,并通过分割当前标签页在新的工作区标签页中打开新图形。

使用 CTRL+Shift+V 将代码粘贴到 Obsidian 中!

<%*
  const defaultTitle = tp.date.now("HHmm")+' '+tp.file.title;
  const title = await tp.system.prompt("Title of the drawing?", defaultTitle);
  const folder = tp.file.folder(true);
  const transcludePath = (folder== '/' ? '' : folder + '/') + title + '.excalidraw';
  tR = String.fromCharCode(96,96,96)+'excalidraw\n[['+transcludePath+']]\n'+String.fromCharCode(96,96,96);
  const ea = ExcalidrawAutomate;
  ea.reset();
  ea.setTheme(1); //set Theme to dark
  await ea.create({
    filename : title,
    foldername : folder,
    //templatePath: 'Excalidraw/Template.excalidraw', //uncomment if you want to use a template
    onNewPane : true
  });
%>

连接对象

使用 CTRL+Shift+V 将代码粘贴到 Obsidian 中!

<%*
  const ea = ExcalidrawAutomate;
  ea.reset();
  ea.addText(-130,-100,"Connecting two objects");
  const a = ea.addRect(-100,-100,100,100);
  const b = ea.addEllipse(200,200,100,100);
  ea.connectObjects(a,"bottom",b,"left",{numberOfPoints: 2}); //see how the line breaks differently when moving objects around
  ea.style.strokeColor = "red";
  ea.connectObjects(a,"right",b,"top",1);
  await ea.create();
%>

使用模板

这个示例与第一个类似,但旋转了 90°并使用了模板同时指定了文件名和保存图形的文件夹并在新的标签页中打开新图形。

使用 CTRL+Shift+V 将代码粘贴到 Obsidian 中!

<%*
  const ea = ExcalidrawAutomate;
  ea.reset();
  ea.style.angle = Math.PI/2; 
  ea.style.strokeWidth = 3.5;
  ea.addRect(-150,-50,450,300);
  ea.addText(-100,70,"Left to right");
  ea.addArrow([[-100,100],[100,100]]);

  ea.style.strokeColor = "red";
  await ea.addText(100,-30,"top to bottom",{width:200,textAlign:"center"});
  ea.addArrow([[200,0],[200,200]]);
  await ea.create({filename:"My Drawing",foldername:"myfolder/fordemo/",templatePath:"Excalidraw/Template2.excalidraw",onNewPane:true});
%>

从文本大纲生成简单思维导图

这是一个稍微复杂一些的示例。这个示例将从一个表格化的大纲生成思维导图。

Drawing 2021-05-05 20 52 34

输入示例:

- Test 1
	- Test 1.1
- Test 2
	- Test 2.1
	- Test 2.2
		- Test 2.2.1
		- Test 2.2.2
		- Test 2.2.3
			- Test 2.2.3.1
- Test 3
	- Test 3.1

The script:

使用 CTRL+Shift+V 将代码粘贴到 Obsidian 中!

<%*
const IDX = Object.freeze({"depth":0, "text":1, "parent":2, "size":3, "children": 4, "objectId":5});

//check if an editor is the active view
const editor = this.app.workspace.activeLeaf?.view?.editor;
if(!editor) return;

//initialize the tree with the title of the document as the first element
let tree = [[0,this.app.workspace.activeLeaf?.view?.getDisplayText(),-1,0,[],0]];
const linecount = editor.lineCount();

//helper function, use regex to calculate indentation depth, and to get line text
function getLineProps (i) {
  props = editor.getLine(i).match(/^(\t*)-\s+(.*)/);
  return [props[1].length+1, props[2]];
}

//a vector that will hold last valid parent for each depth
let parents = [0];

//load outline into tree
for(i=0;i<linecount;i++) {
  [depth,text] = getLineProps(i);
  if(depth>parents.length) parents.push(i+1);
  else parents[depth] = i+1;
  tree.push([depth,text,parents[depth-1],1,[]]);
  tree[parents[depth-1]][IDX.children].push(i+1);
}

//recursive function to crawl the tree and identify height aka. size of each node
function crawlTree(i) {
  if(i>linecount) return 0;
  size = 0;
  if((i+1<=linecount && tree[i+1][IDX.depth] <= tree[i][IDX.depth])|| i == linecount) { //I am a leaf
    tree[i][IDX.size] = 1; 
    return 1; 
  }
  tree[i][IDX.children].forEach((node)=>{ 
    size += crawlTree(node);
  });
  tree[i][IDX.size] = size; 
  return size;   
}

crawlTree(0);

//Build the mindmap in Excalidraw
const width = 300;
const height = 100;
const ea = ExcalidrawAutomate;
ea.reset();

//stores position offset of branch/leaf in height units
offsets = [0];

for(i=0;i<=linecount;i++) {
  depth = tree[i][IDX.depth];
  if (depth == 1) ea.style.strokeColor = '#'+(Math.random()*0xFFFFFF<<0).toString(16);
  tree[i][IDX.objectId] = ea.addText(depth*width,((tree[i][IDX.size]/2)+offsets[depth])*height,tree[i][IDX.text],{box:true});  
  //set child offset equal to parent offset
  if((depth+1)>offsets.length) offsets.push(offsets[depth]);
  else offsets[depth+1] = offsets[depth];
  offsets[depth] += tree[i][IDX.size];
  if(tree[i][IDX.parent]!=-1) {
    ea.connectObjects(tree[tree[i][IDX.parent]][IDX.objectId],"right",tree[i][IDX.objectId],"left",{startArrowHead: 'dot'});
  }
}

await ea.create({onNewPane: true});
%>