feat: slider timebar component

This commit is contained in:
baizn 2020-07-15 14:47:48 +08:00
parent 973d9cf2ef
commit 1bb68f16cd
5 changed files with 341 additions and 3 deletions

View File

@ -69,6 +69,8 @@
"**/*.{js,ts,tsx}": "npm run lint-staged:js"
},
"dependencies": {
"@antv/color-util": "^2.0.5",
"@antv/component": "^0.6.1",
"@antv/dom-util": "^2.0.1",
"@antv/event-emitter": "~0.1.0",
"@antv/g-base": "^0.4.1",

View File

@ -20,6 +20,7 @@ const Bundling = Plugins.Bundling;
const Menu = Plugins.Menu;
const ToolBar = Plugins.ToolBar
const Tooltip = Plugins.Tooltip
const TimeBar = Plugins.TimeBar
export {
registerNode,
@ -38,7 +39,8 @@ export {
registerBehavior,
Algorithm,
ToolBar,
Tooltip
Tooltip,
TimeBar
};
export default {
@ -59,6 +61,7 @@ export default {
Menu: Plugins.Menu,
ToolBar: Plugins.ToolBar,
Tooltip: Plugins.Tooltip,
TimeBar,
Algorithm,
Arrow,
Marker

View File

@ -4,6 +4,7 @@ import Minimap from './minimap';
import Bundling from './bundling';
import ToolBar from './toolBar'
import Tooltip from './tooltip'
import TimeBar from './timeBar'
export default {
Menu,
@ -11,5 +12,6 @@ export default {
Minimap,
Bundling,
ToolBar,
Tooltip
Tooltip,
TimeBar
};

View File

@ -0,0 +1,240 @@
import modifyCSS from '@antv/dom-util/lib/modify-css';
import createDOM from '@antv/dom-util/lib/create-dom';
import isString from '@antv/util/lib/is-string'
import { IGroup } from '@antv/g-base'
import { Canvas } from '@antv/g-canvas'
import { Slider } from '@antv/component'
import { ShapeStyle, GraphData } from '../../types';
import Base, { IPluginBaseConfig } from '../base';
import { IGraph } from '../../interface/graph';
interface Data {
date: string;
value: number;
}
interface Callback {
originValue: number[];
value: number[];
target: IGroup;
}
interface TrendConfig {
readonly data: Data[];
// 样式
readonly smooth?: boolean;
readonly isArea?: boolean;
readonly backgroundStyle?: ShapeStyle;
readonly lineStyle?: ShapeStyle;
readonly areaStyle?: ShapeStyle;
};
type TimeBarOption = Partial<{
// position size
readonly x: number;
readonly y: number;
readonly width: number;
readonly height: number;
readonly backgroundStyle: ShapeStyle;
readonly foregroundStyle: ShapeStyle;
readonly handlerStyle: ShapeStyle;
readonly textStyle: ShapeStyle;
// 允许滑动位置
readonly minLimit: number;
readonly maxLimit: number;
// 初始位置
readonly start: number;
readonly end: number;
// 滑块文本
readonly minText: string;
readonly maxText: string;
readonly trend: TrendConfig;
}>;
interface TimeBarConfig extends IPluginBaseConfig {
width?: number;
height?: number;
timebar: TimeBarOption;
rangeChange?: (graph: IGraph, min: number, max: number) => void;
}
export default class TimeBar extends Base {
private cacheGraphData: GraphData
constructor(cfg?: TimeBarConfig) {
super(cfg);
}
public getDefaultCfgs(): TimeBarConfig {
return {
width: 400,
height: 50,
rangeChange: null,
timebar: {
x: 10,
y: 10,
width: 400,
height: 26,
minLimit: 0.05,
maxLimit: 0.95,
start: 0.1,
end: 0.9,
}
};
}
public init() {
const timeBarConfig: TimeBarOption = this.get('timebar')
const { trend = {} as TrendConfig } = timeBarConfig
const { data = [] } = trend
if (!data || data.length === 0) {
console.warn('TimeBar 中没有传入数据')
return
}
const container = this.get('container')
let timebar
if (!container) {
timebar = createDOM(`<div id='g6-component-timebar'></div>`)
modifyCSS(timebar, { position: 'absolute' });
document.body.appendChild(timebar)
} else if (isString(container)) {
timebar = createDOM(`<div id=${container}></div>`)
modifyCSS(timebar, { position: 'absolute' });
document.body.appendChild(timebar)
} else {
timebar = container
}
this.set('timeBarContainer', timebar)
this.initTimeBar(timebar)
}
private initTimeBar(container: HTMLDivElement) {
const width = this.get('width')
const height = this.get('height')
const canvas = new Canvas({
container,
width,
height,
});
const group = canvas.addGroup({
id: 'timebar-plugin',
})
const timeBarConfig: TimeBarOption = this.get('timebar')
const { trend = {} as TrendConfig , ...option } = timeBarConfig
const config = {
container: group,
minText: option.start,
maxText: option.end,
...option
}
// 是否显示 TimeBar 根据是否传入了数据来确定
const { data = [], ...trendOption } = trend
const trendData = data.map(d => d.value)
config['trendCfg'] = {
...trendOption,
data: trendData
}
config.minText = data[0].date
config.maxText = data[data.length - 1].date
this.set('trendData', data)
console.log('配置项', config)
const slider = new Slider(config)
slider.init();
slider.render()
this.set('slider', slider)
this.bindEvent()
}
/**
*
*/
private bindEvent() {
const graph: IGraph = this.get('graph')
const slider = this.get('slider')
const rangeChange = this.get('rangeChange')
const trendData: Data[] = this.get('trendData')
slider.on('valuechanged', (evt: Callback) => {
const { value } = evt
const min = Math.round(trendData.length * value[0])
let max = Math.round(trendData.length * value[1])
max = max > trendData.length ? trendData.length : max
const minText = trendData[min].date
const maxText = trendData[max].date
slider.set('minText', minText)
slider.set('maxText', maxText)
if (rangeChange) {
rangeChange(graph, minText, maxText)
} else {
// 自动过滤数据,并渲染 graph
const graphData = graph.save() as GraphData
if (!this.cacheGraphData) {
this.cacheGraphData = graphData
}
// 过滤不在 min 和 max 范围内的节点
const filterData = this.cacheGraphData.nodes.filter((d: any) => d.date >= minText && d.date <= maxText)
const nodeIds = filterData.map(node => node.id)
// 过滤 source 或 target 不在 min 和 max 范围内的边
const fileterEdges = this.cacheGraphData.edges.filter(edge => nodeIds.includes(edge.source) && nodeIds.includes(edge.target))
graph.changeData({
nodes: filterData,
edges: fileterEdges
})
}
})
}
public show() {
const slider = this.get('slider')
slider.show()
}
public hide() {
const slider = this.get('slider')
slider.hide()
}
public destroy() {
this.cacheGraphData = null
const slider = this.get('slider')
if (slider) {
slider.off('valuechanged')
slider.destroy()
}
const timeBarContainer = this.get('timeBarContainer')
if (timeBarContainer) {
document.body.removeChild(timeBarContainer);
}
}
}

View File

@ -0,0 +1,91 @@
import G6 from '../../../src';
const div = document.createElement('div');
div.id = 'timebar-plugin';
document.body.appendChild(div);
const data = {
nodes: [
{
id: 'node1',
label: 'node1',
x: 100,
y: 100
},
{
id: 'node2',
label: 'node2',
x: 150,
y: 300
}
],
edges: [
{
source: 'node1',
target: 'node2'
}
]
}
for(let i = 0; i < 100; i++) {
const id = `node-${i}`
data.nodes.push({
id,
label: `node${i}`,
date: `2020${i}`,
value: Math.round(Math.random() * 300)
})
const edgeId = i + 3
data.edges.push({
source: `node-${Math.round(Math.random() * 90)}`,
target: `node-${Math.round(Math.random() * 90)}`
})
}
describe('tooltip', () => {
it('tooltip with default', () => {
const timeBarData = []
for(let i = 0; i < 100; i++) {
timeBarData.push({
date: `2020${i}`,
value: Math.round(Math.random() * 300)
})
}
const timebar = new G6.TimeBar({
timebar: {
trend: {
data: timeBarData,
isArea: false,
smooth: true,
}
}
});
const tooltip = new G6.Tooltip()
const graph = new G6.Graph({
container: div,
width: 500,
height: 500,
plugins: [timebar, tooltip],
modes: {
default: ['drag-node', 'zoom-canvas', 'drag-canvas']
},
defaultEdge: {
style: {
lineAppendWidth: 20
}
}
});
graph.data(data)
graph.render()
const timebarPlugin = graph.get('plugins')[0]
console.log(timebarPlugin)
// expect(timebarPlugin.get('offset')).toBe(6)
// expect(timebarPlugin.get('tooltip').outerHTML).toBe(`<div class="g6-component-tooltip" style="position: absolute; visibility: hidden;"></div>`)
// graph.destroy()
})
});