Echarts-知识图谱
demo地址
打开CodePen
效果
思路
1. 生成根节点
2. 根据子节点距离与根节点的角度关系,生成子节点坐标,进而生成子节点
3. 从子节点上按角度生成对应的子节点
4. 递归将根节点与每一层级子节点连线
核心代码
- 定义节点配置
function getNodeConfig() {return {/** 节点间距 */nodeLine: 120,/** 节点大小 */nodeSize: 100,/** 子节点间距 */subNodeLine: 40,/** 子节点大小 */subNodeSize: 60};
}
- 创建节点位置
function createNodePos({ index: i, len: iLen }) {const { nodeLine } = getNodeConfig();const radioDeg = (Math.PI * 2) / iLen;const deg = i * radioDeg + Math.PI / 4;const x = nodeLine * Math.cos(deg);const y = nodeLine * Math.sin(deg);const pos = { x, y };return pos;
}
- 创建子节点位置
function createSubNodePos({ index: i, len: iLen }, { index: j, len: jLen }) {const { nodeLine, subNodeLine } = getNodeConfig();const radioDeg = (Math.PI * 2) / iLen;const deg = i * radioDeg + Math.PI / 4;const parentX = nodeLine * Math.cos(deg);const parentY = nodeLine * Math.sin(deg);const subRadioDeg = (Math.PI * 2) / (jLen + 1);const subDeg = j * subRadioDeg + (Math.PI / 2) * 3 + deg;const x = parentX + subNodeLine * Math.cos(subDeg);const y = parentY + subNodeLine * Math.sin(subDeg);const pos = { x, y };return pos;
}
- 创建节点和链接
function initOption(root) {root.categoryItem = categories?.[root?.category] || {};const list = chartList || [];const graph = {...createNodesLinks(list, root),categories};const chartOption = {color: categories?.map((item) => item?.color),legend: [{orient: 'vertical',left: 0,data: graph.categories.map(function (a) {return a.name;})}],tooltip: {formatter: (params) => {return params?.data?.name;}},animationDuration: 1500,animationEasingUpdate: 'quinticInOut',series: [{type: 'graph',layout: 'none',force: {repulsion: 100},data: graph.nodes,links: graph.links,categories: graph.categories,roam: true,label: {show: true,width: 36,height: 36,overflow: 'breakAll',color: '#f2f2f2',formatter: (params) => {const { name = '', id } = params?.data || {};const len = id === rootId ? 20 : 10;return name?.length > len ? name?.slice(0, len) + '...' : name;}},lineStyle: {color: 'source',curveness: 0.3},emphasis: {focus: 'adjacency',disabled: true,lineStyle: {width: 10}}}]};option = chartOption;
}function createNodesLinks(list = [], root = {}) {const nodes = [];const links = [];const { nodeSize, subNodeSize } = getNodeConfig();nodes.push({id: rootId,category: 0,name: '根节点',...root,symbolSize: nodeSize,x: 0,y: 0});for (let i = 0; i < list.length; i++) {const iIndex = String(i);const categoryItem = categories?.[i];nodes.push({id: iIndex,category: i,symbolSize: 1,label: {show: false},name: categoryItem?.name,...createNodePos({ index: i, len: list.length })});links.push({source: rootId,target: iIndex});for (let j = 0; j < list[i].length; j++) {const jIndex = `${i}.${j}`;const jItem = _.get(list, jIndex, {});nodes.push({id: jIndex,category: i,symbolSize: subNodeSize,...jItem,...createSubNodePos({ index: i, len: list.length }, { index: j, len: list[i].length })});links.push({source: iIndex,target: jIndex});}}return { nodes, links };
};
- 初始化
function init() {const { id, name, key } = { id: '1', name: '青霉素', key: 'drug-research' }const category = categories?.findIndex((item) => item?.key === key);const categoryItem = categories?.[category];initOption({category,dataId: id,name,id: rootId})
}
完整代码
var dom = document.getElementById('chart-container');
var myChart = echarts.init(dom, null, {renderer: 'canvas',useDirtyRect: false
});
var app = {};var option;const categories = [{name: '药物',color: 'rgba(0, 136, 184, 1)',key: 'drug-research',enumKey: 'Drug',fieldKey: 'drug',idKey: 'drug_uid',nameKey: 'drug_name_cn',nameEnKey: 'drug_name_en'},{name: '靶点',color: 'rgba(7, 214, 205, 1)',key: 'target-spot',enumKey: 'Target',fieldKey: 'target',idKey: 'target_uid',nameKey: 'target_name'},{name: '适应症',color: 'rgba(236, 153, 41, 1)',key: 'indications',enumKey: 'Indication',fieldKey: 'indication',idKey: 'indication_uid',nameKey: 'indication_name'},{name: '企业',color: 'rgba(210, 142, 200, 1)',key: 'company',enumKey: 'Entity',fieldKey: 'entity',idKey: 'entity_uid',nameKey: 'entity_name'},{name: '药物设计技术',color: 'rgba(255, 192, 185, 1)',key: 'drug-tech',enumKey: 'Tech',fieldKey: 'tech',idKey: 'tech_name',nameKey: 'tech_name'}];const rootId = 'root';const serverMapData = {"drug": [{"drug_uid": "1","drug_name_cn": "药物1","drug_name_en": "药物en"},{"drug_uid": "2","drug_name_cn": "药物2","drug_name_en": "药物en"},{"drug_uid": "3","drug_name_cn": "药物3","drug_name_en": "药物en"},{"drug_uid": "4","drug_name_cn": "药物4","drug_name_en": "药物en"},{"drug_uid": "5","drug_name_cn": "药物5","drug_name_en": "药物en"},],"target": [{"target_uid": "1","target_name": "靶点1","target_code": ["string"]},{"target_uid": "2","target_name": "靶点2","target_code": ["string"]},{"target_uid": "3","target_name": "靶点3","target_code": ["string"]},{"target_uid": "4","target_name": "靶点4","target_code": ["string"]},{"target_uid": "5","target_name": "靶点5","target_code": ["string"]},],"indication": [{"indication_uid": "1","indication_name": "适应症1","indication_code": ["string"]},{"indication_uid": "2","indication_name": "适应症2","indication_code": ["string"]},{"indication_uid": "3","indication_name": "适应症3","indication_code": ["string"]},{"indication_uid": "4","indication_name": "适应症4","indication_code": ["string"]},{"indication_uid": "5","indication_name": "适应症5","indication_code": ["string"]},],"entity": [{"entity_uid": "1","entity_name": "企业1","entity_code": ["string"]},{"entity_uid": "2","entity_name": "企业2","entity_code": ["string"]},{"entity_uid": "3","entity_name": "企业3","entity_code": ["string"]},{"entity_uid": "4","entity_name": "企业4","entity_code": ["string"]},{"entity_uid": "5","entity_name": "企业5","entity_code": ["string"]},],"tech": [{"tech_name": "技术1"},{"tech_name": "技术2"},{"tech_name": "技术3"},{"tech_name": "技术4"},{"tech_name": "技术5"},]
}const chartList = categories?.map((categoryItem) => {const dataList = serverMapData?.[categoryItem?.fieldKey] || [];return dataList?.map((item) => {return {...item,categoryItem,dataId: item?.[categoryItem?.idKey],name: item?.[categoryItem?.nameKey] || item?.[categoryItem?.nameEnKey]};});
});init();function init() {const { id, name, key } = { id: '1', name: '青霉素', key: 'drug-research' }const category = categories?.findIndex((item) => item?.key === key);const categoryItem = categories?.[category];initOption({category,dataId: id,name,id: rootId})
}function initOption(root) {root.categoryItem = categories?.[root?.category] || {};const list = chartList || [];const graph = {...createNodesLinks(list, root),categories};const chartOption = {color: categories?.map((item) => item?.color),legend: [{orient: 'vertical',left: 0,data: graph.categories.map(function (a) {return a.name;})}],tooltip: {formatter: (params) => {return params?.data?.name;}},animationDuration: 1500,animationEasingUpdate: 'quinticInOut',series: [{type: 'graph',layout: 'none',force: {repulsion: 100},data: graph.nodes,links: graph.links,categories: graph.categories,roam: true,label: {show: true,width: 36,height: 36,overflow: 'breakAll',color: '#f2f2f2',formatter: (params) => {const { name = '', id } = params?.data || {};const len = id === rootId ? 20 : 10;return name?.length > len ? name?.slice(0, len) + '...' : name;}},lineStyle: {color: 'source',curveness: 0.3},emphasis: {focus: 'adjacency',disabled: true,lineStyle: {width: 10}}}]};console.log('chartOption', chartOption)option = chartOption;}function createNodesLinks(list = [], root = {}) {const nodes = [];const links = [];const { nodeSize, subNodeSize } = getNodeConfig();nodes.push({id: rootId,category: 0,name: '根节点',...root,symbolSize: nodeSize,x: 0,y: 0});for (let i = 0; i < list.length; i++) {const iIndex = String(i);const categoryItem = categories?.[i];nodes.push({id: iIndex,category: i,symbolSize: 1,label: {show: false},name: categoryItem?.name,...createNodePos({ index: i, len: list.length })});links.push({source: rootId,target: iIndex});for (let j = 0; j < list[i].length; j++) {const jIndex = `${i}.${j}`;const jItem = _.get(list, jIndex, {});nodes.push({id: jIndex,category: i,symbolSize: subNodeSize,...jItem,...createSubNodePos({ index: i, len: list.length }, { index: j, len: list[i].length })});links.push({source: iIndex,target: jIndex});}}return { nodes, links };};function getNodeConfig() {return {nodeLine: 120,nodeSize: 100,subNodeLine: 40,subNodeSize: 60};}function createNodePos({ index: i, len: iLen }) {const { nodeLine } = getNodeConfig();const radioDeg = (Math.PI * 2) / iLen;const deg = i * radioDeg + Math.PI / 4;const x = nodeLine * Math.cos(deg);const y = nodeLine * Math.sin(deg);const pos = { x, y };return pos;}function createSubNodePos({ index: i, len: iLen }, { index: j, len: jLen }) {const { nodeLine, subNodeLine } = getNodeConfig();const radioDeg = (Math.PI * 2) / iLen;const deg = i * radioDeg + Math.PI / 4;const parentX = nodeLine * Math.cos(deg);const parentY = nodeLine * Math.sin(deg);const subRadioDeg = (Math.PI * 2) / (jLen + 1);const subDeg = j * subRadioDeg + (Math.PI / 2) * 3 + deg;const x = parentX + subNodeLine * Math.cos(subDeg);const y = parentY + subNodeLine * Math.sin(subDeg);const pos = { x, y };return pos;}if (option && typeof option === 'object') {myChart.setOption(option);
}window.addEventListener('resize', myChart.resize);