<!DOCTYPE html> <html>
<head>
<meta charset="utf-8"> /* start-css */ <link rel="stylesheet" type="text/css" href="http://unpkg.com/view-design/dist/styles/iview.css"> /* end-css */ <style> html, body, #app { height: 100%; overflow: hidden; } #switch { position: fixed; bottom: 30px; right: 40px; } #switch p { margin-top: 10px } button.filter-switch { position: fixed; top: 3%; left: 2%; } a.ivu-drawer-close { margin-top: 7px; right: 20px; } .ivu-drawer-body { overflow-y: scroll; } .highlight { background-color: yellow; } </style> /* start-js */ <script type="text/javascript" src="http://vuejs.org/js/vue.min.js"></script> <script type="text/javascript" src="http://unpkg.com/view-design/dist/iview.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/echarts@4.8.0/dist/echarts.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/vue-echarts@4.0.2/dist/vue-echarts.min.js"></script> /* end-js */
</head>
<body>
<div id="app"> <Drawer placement="left" v-model="filter" :mask-closable="false" :mask="false" :draggable="true" :transfer="false"> <i-input v-model="keyword" style="width: calc(100% - 27px); margin-bottom: 10px;"></i-input> <Tree :data="relations" show-checkbox @on-check-change="selectRelation($event)" ref="filter-panel" /> </Drawer> <v-chart :options="opts" theme="light" autoresize @click="handleClick($event)"></v-chart> <i-button @click="filter = !filter" type="info" class="filter-switch"> filter <Icon type="ios-arrow-forward" /> </i-button> <div id="switch"> <p>categories <Checkbox v-model="opts.legend[0].show"></Checkbox></p> <p>node-label <Checkbox v-model="opts.series[0].label.show"></Checkbox></p> <p>edge-label <Checkbox v-model="edge_label" @on-change="switch_edge_label"></Checkbox></p> </div> </div> <script> // (function () { let graph = /* start-graph */ { "nodes": [{ "name": "n1", "category": 0 }, { "name": "n2", "category": 1 }, { "name": "n3", "category": 2 }, { "name": "n4", "category": 2 }], "links": [{ "relation_in_graph": "n1 ==> n2", "source": "n1", "target": "n2" }, { "relation_in_graph": "n2 ==> n3", "source": "n2", "target": "n3" }, { "relation_in_graph": "n3 ==> n1", "source": "n3", "target": "n1" }, { "relation_in_graph": "n3 ==> n4", "source": "n3", "target": "n4" }], "categories": [{ "name": "/A" }, { "name": "/B" }, { "name": "/C" }] } /* end-graph */ let source_checkboxes = {}, target_checkboxes = [], child_renderer = (h, { data }) => { let text = data.relation_in_list ? `${data.relation_in_list} ${data.title}` : data.title return h('span', text); }, relation_in_graph_formatter = (params) => { return params.data.relation_in_graph || " " }, add_to_checkboxs_tree = (link, i) => { if (!source_checkboxes[link.source]) { source_checkboxes[link.source] = { title: link.source, expand: true, checked: false, indeterminate: false, children: [] } } let child_checkbox = { link_idx: i, title: link.target, relation_in_list: link.relation_in_list, render: child_renderer, checked: false } target_checkboxes[i] = child_checkbox source_checkboxes[link.source].children.push(child_checkbox) } graph.links.forEach((link, i) => { link.idx = i link.label = { show: false, formatter: relation_in_graph_formatter } Object.assign(link.lineStyle, { opacity: 0.5, width: 1 }) add_to_checkboxs_tree(link, i) }) let node_tooltip = { formatter({ data }) { return data.schema.join("<br>") // let schema = data.schema.join("<br>") // let idx = data.indexes.map((idx) => { // let cols = `[${idx.cols.join(', ')}]` // return idx.uniq ? `${cols}:uniq` : cols // }).join("<br>") // return `${schema}<br>${idx}` } } graph.nodes.forEach((node, i) => { // node.tooltip = node_tooltip node.itemStyle = { opacity: 1 } }) let relations = Object.values(source_checkboxes) // console.log(relations) let opts = { // title: { // text: 'Les Miserables', // subtext: 'Default layout', // top: 'bottom', // left: 'right' // }, tooltip: {}, legend: [{ // selectedMode: 'single', show: true, type: 'scroll', bottom: 0, right: 0, padding: [30, 45, 150, 30], orient: "vertical", align: "right", data: graph.categories.map(function (a) { return a.name; }) }], animationDuration: 1500, animationEasingUpdate: 'quinticInOut', series: [{ type: 'graph', layout: 'force', data: graph.nodes, links: graph.links, categories: graph.categories, roam: true, // focusNodeAdjacency: true, itemStyle: { borderColor: '#fff', borderWidth: 1, shadowBlur: 10, shadowColor: 'rgba(0, 0, 0, 0.3)' }, label: { show: false, position: 'right', formatter: '{b}' }, lineStyle: { color: 'source', curveness: /* start-curveness */ 0.3 /* end-curveness */, }, edgeSymbolSize: 15, emphasis: { lineStyle: { width: 10 } } }] } let checked_links = new Set(), checked_nodes = new Set() Vue.component('v-chart', VueECharts) new Vue({ el: '#app', data: { keyword: "", filter: false, opts: opts, edge_label: false, relations: relations }, watch: { keyword() { tree.search(this.keyword) } }, methods: { switch_edge_label(){ if (checked_nodes.size === 0) { graph.links.forEach((link, i) => { link.label.show = this.edge_label }) return } graph.links.forEach((link, i) => { if (checked_links.has(i) && this.edge_label) { link.label.show = true } else { link.label.show = false } }) }, selectRelation(checkeds) { checked_links = new Set() checked_nodes = new Set() checkeds.forEach((c) => { if (c.link_idx !== undefined) { checked_links.add(c.link_idx) checked_nodes.add(graph.links[c.link_idx].source) } checked_nodes.add(c.title) }) this.switch_edge_label() if (checked_nodes.size === 0) { graph.links.forEach((link) => { link.lineStyle.width = 1 link.lineStyle.opacity = 1 }) graph.nodes.forEach((node) => { node.itemStyle.opacity = 1 }) return } graph.links.forEach((link, i) => { if (checked_links.has(i)) { link.lineStyle.width = 5 link.lineStyle.opacity = 1 } else { link.lineStyle.width = 1 link.lineStyle.opacity = 0.1 } }) graph.nodes.forEach((node, i) => { if (checked_nodes.has(node.name)) { node.itemStyle.opacity = 1 } else { node.itemStyle.opacity = 0.1 } }) }, handleClick(e) { let link = e.data, target_box = target_checkboxes[link.idx], source_box = source_checkboxes[link.source] target_box.checked = !target_box.checked let checked_targets_count = source_box.children.reduce((count, c) => { count = c.checked ? count + 1 : count return count }, 0) if (checked_targets_count === 0) { source_box.checked = false source_box.indeterminate = false } else if (checked_targets_count === source_box.children.length) { source_box.checked = true source_box.indeterminate = false } else { source_box.checked = false source_box.indeterminate = true } this.$nextTick(() => { let checkeds = this.$refs['filter-panel'].getCheckedAndIndeterminateNodes() this.selectRelation(checkeds) }) } } }); const Tree = (function () { const node_class_name = "ivu-tree-children", node_class = "." + node_class_name, text_class = ".ivu-tree-title", highlight_html = '<span class="highlight">$1</span>' class Tree { constructor(selector) { let n = document.querySelector(selector + " " + node_class) this.roots = n ? (new Node(n).this_and_siblings()) : [] } search(keyword) { let kw = keyword === "" ? /./ : new RegExp(keyword) this.show_only_when_match((txt) => { return txt.match(kw) }) if (keyword !== "") { let repl = new RegExp('(' + keyword + ')') this.highlight((txt) => { return txt.replace(repl, highlight_html) }) } else { document.querySelectorAll('.highlight').forEach((n) => { n.parentNode.innerText = n.parentNode.innerText }) } } show_only_when_match(fn) { this.roots.forEach((n) => { n.show_only_when_match(fn) }) } highlight(fn) { this.roots.forEach((n) => { n.highlight(fn) }) } } class Node { constructor(node) { this.raw_node = node this.visible = true let child = this.raw_node.querySelector(node_class) this.children = child ? (new Node(child).this_and_siblings()) : [] } this_and_siblings() { let ss = [this], s = this.raw_node.nextSibling while (s && s.getAttribute && (s.getAttribute('class') === node_class_name)) { ss.push(new Node(s)) s = s.nextSibling } return ss } highlight(fn) { if (!this.visible) { return } this.title_node().innerHTML = fn(this.title_node().innerText) this.children.forEach((c) => { c.highlight(fn) }) } title_node() { return this.raw_node.querySelector(text_class) } show_only_when_match(fn) { let text = this.title_node().innerText if (fn(text)) { this.set_sub_tree_visible() return } this.children.forEach((c) => { c.show_only_when_match(fn) }) if (this.children.some((c) => { return c.visible })) { this.set_display(true) return } this.set_display(false) } set_display(vi) { this.visible = vi this.raw_node.style.display = (vi ? "block" : "none") } set_sub_tree_visible() { this.set_display(true) this.children.forEach((c) => { c.set_sub_tree_visible() }) } } return Tree })() let tree = new Tree('#app') setTimeout(() => { let chart = document.querySelector(".echarts") chart.style.width = "100%" chart.style.height = "100%" }) // })() </script>
</body>
</html>