基于Svelte实现可视化辞典

前言

之前做数据可视化的时候,学习过英国金融时报(Financial Times)的可视化辞典(Visual Vocabulary),遗憾的是中文版的只有PDF没有网页。另外,从网上也查到了基于Tableau实现的可视化辞典,但是体验了一下,感觉有些卡顿。所以,就干脆自己写一个好了。刚好最近了解了一下Svelte,所以就用Svelete实现了中文版的可视化辞典。

设计思路

我自己本身并不懂UI,只能是一通瞎整:在布局上参考了Tableau的可视化辞典,在色彩风格上以FT Times原版的Visual Vocabulary为基础。之所以这么做也有投机取巧之处:

  • FT Times的布局、动画是使用D3.js实现的,我要一模一样就免不了要手撸D3.js;
  • Tableau的图表是基于自身实现的,而实际上我直接使用FT Times原版的SVG就够了。

image-20220707142253690

技术上,一个静态页面而已。前端框架用的Svelte.js, 样式使用的是Bulma CSS。

组件开发

参考Tableau的实现方式,通过不同标签页的切换显示不同类型的可视化图表。主要基于两个组件实现:

  • Panel组件,用来管理各个标签页;
  • TabContent组件,用来管理各个标签页下的内容。

Panel组件

组件代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<script lang="ts">
import TabContent from "./TabContent.svelte";

interface PanelItem {
id: string;
color?: string
text: string;
props?: any;
}
export let items: PanelItem[];
let activeItem = items[0]
let tabHeading;
const scrollTabs = (event)=>{
// 判断是否有横向滚动条
// 如果有,则允许鼠标滚轮
if(tabHeading.scrollWidth > tabHeading.clientWidth) {
event.preventDefault();
tabHeading.scrollLeft+=event.deltaY
}
}
let tabHeadingStyle="";

$:{
tabHeadingStyle = activeItem.color?`color:${activeItem.color};border-bottom-color:${activeItem.color}`:""
}
</script>
<div clas="panel">
<p class="panel-heading">数据的设计 (Designing with data)</p>
<div class="tabs is-centered is-fullwidth is-medium" on:wheel={scrollTabs} bind:this={tabHeading} style="background-color: white;position: sticky; top:0;z-index: 50">
<ul>
{#each items as item}
<li class="{activeItem.id===item.id?'is-active':''}"
on:click={()=>{activeItem=item}}
><a style="{activeItem.id===item.id?tabHeadingStyle:''}">{item.text}</a></li>
{/each}
</ul>
</div>
<TabContent {...activeItem.props}/>
</div>

说明:

  1. 采用Svelte的each语法生成标签页的头;
  2. 使用scrollTabs实现标签的横向滚动;
  3. 使用position:sticky的样式,保证当纵向内容过长时,标签始终位于顶部可见;
  4. 使用Bulma的tabsis-active控制标签的切换;
  5. 通过activeItem保存激活的标签页信息,并传递相关数据。

TabContent组件

虽然名字叫"TabContent",但其实整个页面中只有一个TabContent的实例,叫"PanelContent"可能更贴切一些,不过由于它的内容是靠标签控制的,所以还是叫"TabConent"吧。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<script lang="ts">
export let description;
export let units;
</script>
<div>
<p style="padding: 1.25rem 0">{description}</p>
<div class="columns is-multiline">
{#each units as unit}
<div class="column is-3">
<article class="message">
<div class="message-header" style="background-color: {unit.color}">
</div>
<div class="message-body">
<h2 class="subtitle" style="color: {unit.color}">{unit.heading}</h2>
{#if unit.image}
<img src="./assets/icons/{unit.image}.svg"/>
{/if}
<div class="">{unit.description}</div>
</div>
{#if unit.example}
<footer class="footer">
<div class="divider">情景范例</div>
<p>{unit.example}</p>
</footer>
{/if}
</article>
</div>
{/each}
</div>
</div>

<style>
.message-header {
padding-top: 0;
border-radius: 0;
}
.message {
height: 100%;
}
.message-body {
height: 60%;
}
.footer {
padding: 0;
}
</style>

说明:

  1. Tab组件的属性包括descriptionunits两个,前者用来显示本页的摘要内容,units则用来显示具体的条目;
  2. 出于省事儿的目的,我为Vocabulary和其他的图表标签使用了相同的组件,但是两者要显示的内容其实并不完全一样,所以使用Svelte的if语法控制显示不同的内容;
  3. 对于units,使用Svelte的if语法控制渲染,使用Bulma的message来控制样式。

数据

除了做好组件以外,还需要提供数据,FT Times已经提供了中文的PDF版本,我把需要的内容抠出来保存成一个JSON数组,保存在data.js中,基本的代码类似:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
export const vocabularies =  [
{
id: "vocabulary",
text: "可视化词典",
color: null,
props: {
description: "有很多方法可以使数据可视化--我们怎么知道该选哪一种?点击下面的彩色类别,决定哪种数据关系在你的故事中最重要,然后看看该类别中不同类型的图表,形成一些关于什么可能最有效的初步想法。这个列表并不意味着详尽无遗,也不是一个万能的向导,但它是一个有用的起点,可以使信息量大且有意义的数据可视化。",
units: data
}
},
{
id: "deviation",
text: "离差",
color: "#E6B00F",
props: {
description: "强调相对于一个固定参考值的变化(正╱负值)。通常参考值为零,但也可能是一个目标数值或是长期平均值。也能用来展现态度倾向(正向╱中立╱负面)。",
units: [
{
id: "divergingBar",
color: "#E6B00F",
heading: "分向条形图",
description: "一种简单、标准化的条形图,能同时处理正负数值。",
image: 'bar-diverging'
},
{
id: "divergingStackedBar",
color: "#E6B00F",
heading: "分向堆叠条形图",
description: "最适合用来展现牵涉到态度(正向╱中立╱负面)的调查结果。",
image: 'bar-diverging-stacked'
},
{
id: "spineChart",
color: "#E6B00F",
heading: "成对条形图",
description: "将单一数值分成两组对比的组成(例如男性/女性)。",
image: 'spine'
},
{
id: "filledLine",
color: "#E6B00F",
heading: "盈余/赤字填充线图",
description: "阴影部分能展示出一种平衡关系,或者是相对于某个基准线,或者是两组数据之间。",
image: 'line-surplus-deficit-fill'
},
]
}
},
]

矢量图

矢量图基本来自英文版的项目,不过发现有些矢量图似乎有问题,所以进行了相应的修改。

效果

简单些,直接看图:

visual-vocabulary-example

有兴趣的读者可以看网页:

https://zen-tea.space/visual-vocabulary-cn/

小结

简单总结一下我这个版本的好处:

  1. 提供了FT Visual Vocabulary的中文网页版;
  2. 修正了原有Visual Vocabulary中一些图表的显示问题;
  3. 比Tableau更加流畅。

参考资料

  1. https://github.com/ft-interactive/visual-vocabulary
  2. https://public.tableau.com/app/profile/andy.kriebel/viz/VisualVocabulary/VisualVocabulary
  3. https://github.com/Financial-Times/chart-doctor/blob/main/visual-vocabulary/Visual-vocabulary-chinese-simplified.pdf