在Obsidian上使用DataView和ChartsView进行数据分析和可视化

在Obsidian上使用DataView和ChartsView进行数据分析和可视化

Posted by WW on March 5, 2022

DataView 和 ChartsView

DataView是个可以将整个文件库当作数据库进行读取分析的插件,它提供了笔记中属性和任务信息的标记方法,以及用于查询展示数据的接口。 可以用它将你感兴趣的带有特定标签属性路径信息的笔记查询出来,并且可以做简单的表格可视化。笔记中的任务,包括创建时间截至时间完成状态等信息,也可以通过类似的方法分析和展示。

ChartsView是一个图表可视化的插件,可以跟DataView结合起来,将DataView得到的结果,在更丰富的图表上展示。

DataView

CodeBlock中的代码可以有两种语言形式,但是同时只能使用一种。 一种是Query查询语言,类似SQL。

```dataview 
TABLE rating AS "Rating", summary AS "Summary" FROM #games SORT rating DESC 
```

一种是Js语言,当作Js代码执行,所以代码需要符合Js语法,通过DataView提供的对象即可完成相关操作。

```dataviewjs 
dv.pages("#thing")
```

以下简单介绍标记语法和Js接口就。详细请见文档

标记语法
在笔记中标记属性
BasicField:: Basic

**BoldField**:: Bold

my mode is [mod:: nice]

以上属性可以通过page.BasicFieldpage.BasicField以及page.mod访问。 另外page还有以下属性

  • file.name: The file title (a string).
  • file.folder: The path of the folder this file belongs to.
  • file.path: The full file path (a string).
  • file.link: A link to the file (a link).
  • file.size: The size (in bytes) of the file (a number).
  • file.ctime: The date that the file was created (a date + time).
  • file.cday: The date that the file was created (just a date).
  • file.mtime: The date that the file was last modified (a date + time).
  • file.mday: The date that the file was last modified (just a date).
  • file.tags: An array of all unique tags in the note. Subtags are broken down by each level, so #Tag/1/A will be stored in the array as [#Tag, #Tag/1, #Tag/1/A].
  • file.etags: An array of all explicit tags in the note; unlike file.tags, does not include subtags.
  • file.inlinks: An array of all incoming links to this file.
  • file.outlinks: An array of all outgoing links from this file.
  • file.aliases: An array of all aliases for the note.
  • file.tasks: An array of all tasks (I.e., - [ ] blah blah blah) in this file.

If the file has a date inside its title (of form yyyy-mm-dd or yyyymmdd), or has a Date field/inline field, it also has the following attributes:

  • file.day: An explicit date associated with the file.
任务中也可标记属性。
  • Hello, this is some [due::2022-08-15] [mod::fine].
  • Get up
  • Code [due::2022-08-15] [mod::fine].
  • 任务截至时间是当天 [due::2022-03-05] [mod::fine].
  • 任务截至时间是昨天 [due::2022-03-04] [mod::fine].
  • 任务截至时间是去年 [due::2021-03-04] [mod::fine].
  • 任务截至时间是明天 [due::2022-03-06] [mod::fine].
  • I finished this on [completion::2022-03-05]. [mod::nice].

可以通过以下方式访问

page.file.tasks.due
page.file.tasks[1].completion
page.file.tasks[0].mod
page.file.tasks[1].mod

As with pages, Dataview adds a number of implicit fields to each task:

  • Tasks inherit all fields from their parent page - so if you have a rating field in your page, you can also access it on your task.
  • completed: Whether or not this specific task has been completed; this does not consider the completion/non-completion of any child tasks.
  • fullyCompleted: Whether or not this task and all of its subtasks are completed.
  • text: The text of this task.
  • line: The line this task shows up on.
  • path: The full path of the file this task is in.
  • section: A link to the section this task is contained in.
  • link: A link to the closest linkable block near this task; useful for making links which go to the task.
  • subtasks: Any subtasks of this task.
  • real: If true, this is a real task; otherwise, it is a list element above/below a task.
  • completion: The date a task was completed. If not annotated, will default to file modified time.
  • due: The date a task is due, if it has one.
  • created: The date a task was created. If not annotated, defaults to file creation time.
  • annotated: True if the task has any custom annotations, and false otherwise.
文件寻址

根据标签路径等信息筛选文件

let page = dv.current();  // 当前文件
let pages = dv.pages() // Vault 中所有的文件在
pages = dv.pages("#books") // 所有带标签的文件 
pages = dv.pages('"folder"') // 在文件夹中所有的文件,注意引号
pages = dv.pages("#yes or -#no") // 组合筛选
let page_path = dv.pagePaths(); //获取满足筛选要求的所有文件的路径
dv.page("Index")  // 单个文件寻址,自动补全后缀
dv.page("books/The Raisin.md") // 单个文件寻址 /books/The Raisin.md
页面渲染

在页面上创建可见的元素



let page_path = dv.pagePaths('"Daily"'); //获取满足筛选要求的所有文件的路径
let pages = dv.pages('"Daily"') ;
let page = dv.current();

dv.el("b", "This is some bold text");
dv.header(1, "Big!"); 
dv.header(6, "Tiny");
dv.paragraph("This is some text, paragraph");
dv.span("This is some text,span");

dv.paragraph("可以用来调试,简单的显示js变量");

dv.paragraph(page_path);
dv.header(4,"测试标记");
dv.header(4,page.mod);
dv.header(4,page.BasicField);

dv.header(4,page.BoldField);
dv.header(4,page.file.tasks.due);
dv.header(4,page.file.tasks[1].completion);
dv.header(4,page.file.tasks[0].mod);
dv.header(4,page.file.tasks[1].mod);
日期
 
dv.header(4, "测试日期");
let date1 = dv.date("today");
let date2 = dv.date("2022-03-01");
let dur = (date1 - date2) ;
let dur_day = dur / (1000 * 60 * 60 * 24);
dv.header(3, date1);
dv.header(3, date2);

dv.header(3, "dur_day = " + dur_day );

let date3 =   Date("2022/03/02"); // js Date
简单可视化

可以简单的显示list列表,taskList任务,table表格

let page_path = dv.pagePaths('"Daily"'); //获取满足筛选要求的所有文件的路径
let pages = dv.pages('"Daily"') ;
let page = dv.current();

dv.list([1, 2, 3])
dv.list(page_path);
dv.list(pages.file.name);
dv.header(4,"所有文件");
dv.list(pages.file.link);
dv.header(4,"where 筛选 今天之前的文件");
dv.list(pages.file.where(p => (  (p.day - dv.date("today"))  /(1000 * 60 * 60 * 24) <0) ).link);

dv.header(4,"where 筛选 今天之前的文件的任务");
dv.taskList(pages.file.where(p => (  (p.day - dv.date("today"))  /(1000 * 60 * 60 * 24) <0 ) ).tasks);

dv.header(4,"where 筛选 今天之前的文件的未完成任务");
dv.taskList(pages.file.where(p => ( (p.day - dv.date("today"))  /(1000 * 60 * 60 * 24) <0 ) ).tasks.where(t => !t.completed));

dv.header(4,"任务计数");

let task_num = page.file.tasks.length;
dv.header(4, "task_num = " + task_num);

let unfinished_tak_num = page.file.tasks.where(t=> !t.completed).length

dv.header(4, "unfinished_tak_num = " + unfinished_tak_num);

let unfinished_task_with_due_num  =page.file.tasks.where(t=> !t.completed && t.due ).length


dv.header(4, "unfinished_task_with_due_num = " + unfinished_task_with_due_num);

let fail_task_num = page.file.tasks.where(t=> !t.completed && t.due && t.due < dv.date("today")).length;

dv.header(4, "fail_task_num = " + fail_task_num);
dv.taskList(page.file.tasks.where(t=> !t.completed && t.due && t.due < dv.date("today")));

dv.taskList(page.file.tasks.where(t=> !t.completed && t.due && ((t.due - dv.date("today") )/(1000 * 60 * 60 * 24)===0  ) ));


dv.taskList(page.file.tasks.where(t=> !t.completed && t.due && ((t.due - dv.date("today") )/(1000 * 60 * 60 * 24)>0  ) ));

复杂例子统计任务完成情况
  let page = dv.pages('"Daily"');
  let task = page.file.tasks;
   
let all_unfinished_task_num = task.where(t => !t.completed).length;
let all_unfinished_due_task = task.where(t => !t.completed && t.due 
 && t.due < dv.date("today"));

let all_unfinished_due_task_num = all_unfinished_due_task.length;
let all_unfinished_todue_task = task.where(t => !t.completed && t.due 
 && t.due >= dv.date("today")).sort(t => t.due);

let all_unfinished_todue_task_num = all_unfinished_todue_task.length;


let task_in_5_day = all_unfinished_todue_task.where(t=> !t.completed && t.due && (((t.due - dv.date("today") )/(1000 * 60 * 60 * 24))<5  ) ).sort(t => t.due)
;

let task_in_30_day = all_unfinished_todue_task.where(t=> !t.completed && t.due && (((t.due - dv.date("today") )/(1000 * 60 * 60 * 24))<30  ) ).sort(t => t.due)
;

let task_beyond_30_day = all_unfinished_todue_task.where(t=> !t.completed && t.due && (((t.due - dv.date("today") )/(1000 * 60 * 60 * 24))>30  ) )
;




dv.table(["历史未完成任务","超期任务","待办任务"],[[all_unfinished_task_num,all_unfinished_due_task_num,all_unfinished_todue_task_num]]);

dv.header(1,"超期任务");
dv.taskList(
all_unfinished_due_task
);

dv.header(1,"5天内待办任务");
dv.taskList(
task_in_5_day
);

dv.header(1,"30天内待办任务");
dv.taskList(
task_in_30_day
);

dv.header(1,"超过30天内待办任务");
dv.taskList(
task_beyond_30_day
);

ChartsView

饼状图

可以简单的给定一个字典组成的数组,字典的key需要与options中的field对应。

```chartsview
#-----------------#
#- chart type    -#
#-----------------#
type: Pie

#-----------------#
#- chart data    -#
#-----------------#
data:
  - type: "Wage income per capita (¥)"
    value: 17917
  - type: "Operating net income per capita (¥)"
    value: 5307
  - type: "Property Per Capita Net Income (¥)"
    value: 2791
  - type: "Transfer of net income per capita (¥)"
    value: 6173

#-----------------#
#- chart options -#
#-----------------#
options:
  angleField: "value"
  colorField: "type"
  radius: 0.5
  label:
    type: "spider"
    content: "{percentage}\n{name}"
  legend:
    layout: "horizontal"
    position: "bottom"
```

也可以与DataView结合使用。

#-----------------#
#- chart type    -#
#-----------------#
type: Pie

#-----------------#
#- chart data    -#
#-----------------#
data: |
  dataviewjs:
  let table = [{type : "good",value:12},{type : "bad",value:4}];
  return table;

#-----------------#
#- chart options -#
#-----------------#
options:
  angleField: "value"
  colorField: "type"
  radius: 0.5
  label:
    type: "spider"
    content: "{percentage}\n{name}"
  legend:
    layout: "horizontal"
    position: "bottom"
条行图

与DataView生成文件数量统计的条行图。

#-----------------#
#- chart type    -#
#-----------------#
type: Column

#-----------------#
#- chart data    -#
#-----------------#
data: |
  dataviewjs:
  return dv.pages()
           .groupBy(p => p.file.folder)
           .map(p => ({folder: p.key || "ROOT", count: p.rows.length}))
           .array();


#-----------------#
#- chart options -#
#-----------------#
options:
  xField: "folder"
  yField: "count"
  padding: auto
  label:
    position: "middle"
    style:
      opacity: 0.6
      fontSize: 12
  columnStyle:
    fillOpacity: 0.5
    lineWidth: 1
    strokeOpacity: 0.7
    shadowColor: "grey"
    shadowBlur: 10
    shadowOffsetX: 5
    shadowOffsetY: 5
  xAxis:
    label:
      autoHide: false
      autoRotate: true
  meta:
    count:
      alias: "Count"