echarts树图tree制作家谱教程

echarts树图tree制作家谱教程

缘起

国庆节回老家,看到家族里长辈们整理的家谱并印成了一本书,到我这一代已经是第六代了。我辈分低,从小在村里见了跟我爸年龄差不多的人都是爷爷婆婆的叫,但对于这些族人的族内关系却不清楚——从我记事起就没见过很正式的家谱,据说早先有过,但文革时破四旧遗失了,后来再也没人整理。这次的家谱书比较清晰的记录了每一家在家族中的位置关系和家庭成员信息,对于家族关系用一个跨了四页的表格列出。我翻阅时想到如果在电脑上用图表中的树图把家族关系列出来会更清楚直观,但直到前几天结课才有时间动手。我是做web前端的,用比较熟悉的echarts树图尝试画了一下,经过两周业余时间的开发,实现初版计划的功能。用echarts实现家谱图的过程也遇到不少困难,我把一些解决问题的经验记录一下,供需要到的小伙伴们参考。在截图举例中我将家人的姓名都替换成“名字”二字,以保护姓名隐私。

完成后效果如图:

echarts树图tree制作家谱教程_第1张图片

准备

  1. 需要有基本的web前端开发能力,以及熟悉echarts图表,如果不了解echarts请移步官网Apache ECharts。
  2. 准备一份json数据来记录每个人之间的关系,格式如下,其中name是真实姓名,value是排行(女性为0),children是个数组,代表孩子们,没有孩子不用写,内容也是name、value、children的嵌套模式。编写时建议先写几个人的简单关系,调通后再慢慢增加,以免人数太多(我的家谱中记录151个人,手写json数据500多行),容易因为丢失括号等问题带来麻烦。
    export const data = { 
        name: "祖父", 
        children: [{
            name: "父亲",
            value: 1,
            children: [{
                name: "我",
                value: 1,
                children: [{
                    name: "儿子1",
                    value: 3,
                },{
                    name: "女儿",
                    value: 2,
                }
                …… // 其他孩子
                ],
            },{
                name: "兄弟",
                value: 2,
            }
            …… // 其他兄弟姐妹
            ],
        },{
            name: "伯父",
            value: 1,
        }
        …… // 伯伯、叔叔、姑姑
        ],
    }
  3. 开发环境使用html页面直接引用、vue、react都可以,本案例使用react,需注意的是如果用react17.0.1的时候要用echarts4的版本,因为不支持echats5。项目版本信息如下:
"dependencies": {
    "echarts": "4",
    "sass": "^1.52.3",
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "react-router-dom": "^5.2.0",
    "react-scripts": "4.0.1",
    "web-vitals": "^0.2.4"
},

步骤

1. 引入echarts并初始化,导入数据

import { data as baseData } from '../../data/base'
import echarts from 'echarts' // echarts5.0目前不受支持,只能用4.x

export default function Home() {
    useEffect(() => {
        // svg渲染性能对移动端更友好,且缩放不影响字体清晰度,但使用toolbox内置保存图片会得到svg格式。而截图时svg更清晰。
        const mainChart = echarts.init(document.querySelector('.mainChart'), {}, {renderer: 'svg'})
        mainChart.setOption({
             series: [{
                type: 'tree',
                data: [baseData],
                initialTreeDepth: -1, // 可以让所有节点都展开
                orient: 'vertical', // 方向改为自上而下
            }]
        });
    }, [])

    return 
获取图片
} //scss设置 .p-home { min-width: 1600px; height: 800px; :global { .mainChart { height: 100%; } } }

html中增加样式设置:

完成后得到如下图表,整体结构出来了,但主要问题是名字横排,在离得近的地方重叠了,如果再显示上排行,即使换行也会重叠很多。

echarts树图tree制作家谱教程_第2张图片

2. 增加排行,竖排以避免重叠

由于echarts的树图label不支持文字竖排,我给提了issue(https://github.com/apache/incubator-echarts/issues/13845),等待以后某个版本作为新功能出现。但通过一些技巧,在现行版本上我们依然可以实现这个效果。我的做法是把需要竖排的两行文本穿插了再横排换行,比如名字“刀客”,排行老二,考虑到加上中文括号的效果,会拼接为第一行“ ︵”,注意第一个字符是个全角空格,普通空格在对齐上总不尽人意;第二行“刀老”,第三行“客二”,第四行“ ︶”。

  1. 定义“老大、老二”这样的排行文字格式。
    const getChineseNum = num => {
        const chineseNum = '〇一二三四五六七八九十'.split('')
        if (num < 0) {
            return '待定'
        } else if (num === 1) {
            return '老大'
        } else if (num < 11) {
            return '老' + chineseNum[num]
        } else if (num % 10 === 0) {
            return chineseNum[num / 10] + '十'
        } else if (num < 20) {
            return '十' + chineseNum[num % 10]
        } else {
            return chineseNum[Math.floor(num / 10)] + '十' + chineseNum[num % 10]
        }
    }
  2. 定义拼接文本的方法,注意女性不需要排行
    const getLabel = ({name, value, ranking}) => {
        if (!value) { // 女性名字直接换行
            return name.split('').join('\n')
        }
        ranking = '︵' + ranking + '︶'
        name = ' ' + name
        return ranking.split('').reduce((total, curr, index) => total + (name[index] || ' ') + curr + '\n', '')
    } 
  3. series增加label标签进行格式化,并设置文字大小和定位
label: {
    fontSize: 11,
    position: 'top',
    formatter: params => {
        const {name,value} = params
        const ranking = getChineseNum(params.value)
        return getLabel({name, value, ranking})
    }
},

优化后效果如下

echarts树图tree制作家谱教程_第3张图片

3. 区分男女,优化细节

此时我们看到的家谱已经比较清晰了,然后我们用颜色区分男女,并让对应的节点为实心同色以增加区别度。

先定义方法formatData,以原始数据为准递归遍历,添加样式信息。此处根据男女名称的对齐情况分别设置位置,并根据显示规律,将同一节点下第一位男性向左偏移,最后一个如果是男性向右偏移,而女性全部向右偏移。男性名字统一位置下调。

const formatData = (obj, idx, arr) => {
    Object.keys(obj).forEach(item => {
        if (item === 'children' && obj.children.length) {
            obj.children.forEach((item0, idx, arr) => formatData(item0, idx, arr))
        } else {
            if (item === 'value') {
                if (obj.value === 0) { // 女
                    obj.label = {
                        color: 'crimson',
                        offset: [5, 3]
                    }
                    obj.itemStyle = {
                        color: 'crimson'
                    }
                } else { // 男
                    let left = 0
                    if (!idx) {
                        left = -5 // children中第一个节点标签左偏移
                    }
                    if (idx === arr.length - 1) {
                        left = 5 // children中最后一个节点标签右偏移
                    }
                    obj.label = {
                        offset: [left, 20]
                    }
                }
            }
            if (item === 'name' && obj.name.length === 3) { // 如果是3个字则不显示第一个,即去掉姓氏
                obj.name = obj.name.replace('李', '')
            }
        }
    })
    return obj
}

设置默认label颜色为深蓝色

label: {
    ……
    color: 'darkblue',
}

series中的数据修改,并增加一些属性以让节点样式和名字颜色统一,以及最大程度的利用空间

series:[{
    ……
    left: '1.5%',
    right: '1.5%',
    top: '10%',
    bottom: '5%',
    data: [formatData(baseData)],
    symbol: 'circle',
    symbolSize: 8,
    itemStyle: {
        color: 'darkblue',
        borderWidth: 0
    },
}]

这一步完成后就得到了开篇时截图的样子,家谱图表基本完成。

4. 移动端兼容

因为这个家谱完成后主要通过发微信链接让大家来看,所以要考虑移动端展示的最优方案。如果直接输出网页,在微信浏览器中不能自动横屏,而这个家谱以横屏方式观看比较好,能充分利用空间。所以先检测设备的宽高,如果高度大于宽度则横着显示,即将图表旋转90度再平移到视图范围内。这个功能我写在app.js中。

function App() {
  useEffect(() => {
    const windowW = window.innerWidth // 这里获取的是物理像素,而控制台上获取的是css像素
    const windowH = window.innerHeight
    if (windowH > windowW) { // 竖屏
      const root = document.querySelector('#root')
      const container = document.querySelector(".container")
      const containerW = container.offsetWidth
      root.style.transform = `rotate(90deg)` // 旋转可能会使window.innerWidth、window.innerHeight变化
      root.style.transformOrigin = `${containerW / 4}px ${containerW / 4}px`
    }
  }, [])

  return (
    
      
        
      
    
  );
}

5. 输出保存

图表中自带图片保存工具,可定位至左侧中间。

toolbox:{
    left:0,
    top:'middle',
    feature: {
        saveAsImage: {name:'李氏家谱'}
    }
}, 

经尝试,这个保存按钮在微信浏览器中不起作用,使用自带浏览器打开时,苹果可以保存而安卓无效(issue:Android phones cannot save images using echarts' toolbox-saveAsImage/安卓手机用echarts自带的保存图片工具无法保存 · Issue #13925 · apache/echarts · GitHub),但电脑上是没问题的。考虑到乡亲们的需求情况,家谱信息更新不会太频繁(目前都是我手动修改json文件),我干脆把完整输出的图片挂个链接到页面上,需要的人自己下载即可。这样的副作用就是每次更新家谱后我需手动同步一下图片。

import outputPng from '../../assets/img/family_li.png'
……
return 

当然读者小伙伴们根据需要可以考虑用输出网页为图片的各种方法。

总结

  1. 首先,感谢家族里参与整理家谱的爷爷叔叔们,他们在百忙之中抽时间挨家挨户走访,整理资料,甚至自费印书,才使得我们这代人对祖上关系有了清晰的认识。
  2. 其次,感谢我的家庭,在本来让我照顾孩子或干家务的时间里,允许我挤出十余个小时,历时两三周完成了这个电子家谱的一期目标。二期将准备在地图上实现各成员的位置、展示头像、家庭门头照等,但近期没有开发计划。以后看情况,如果开发总结了经验我会继续更新到这里。
  3. 最后,要完成一件事,一有时机就安排到日程上,既要有计划又要有行动。从前半年我弟回家发给我家谱书的照片时,我就考虑过做电子版,但一直没排上计划。直到持续了5个月的网课即将结束时,我就赶快做了近期计划,并按部就班实现。我几乎每天都很忙,时间抓得很紧,但回头看看,只有放眼长远做出的计划并实施,才能获得长远的收效。希望看到这篇文章的小伙伴们充分利用时间,做出将来引以为傲的成就。

你可能感兴趣的:(前端移动开发,前端,echarts,家谱,电子家谱,树图,族谱)