mirror of
https://gitee.com/antv/g6.git
synced 2024-11-30 02:38:20 +08:00
feat: add timebar examples
This commit is contained in:
parent
745cef2344
commit
c944186cab
@ -7,18 +7,26 @@
|
||||
{
|
||||
"filename": "timebar.js",
|
||||
"title": {
|
||||
"zh": "时间轴",
|
||||
"en": "ToolBar"
|
||||
"zh": "趋势时间轴",
|
||||
"en": "TrendTimeBar"
|
||||
},
|
||||
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*eq2kQ4N-pNcAAAAAAAAAAABkARQnAQ"
|
||||
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*lfvIQJYbs7oAAAAAAAAAAAAAARQnAQ"
|
||||
},
|
||||
{
|
||||
"filename": "configTimebar.js",
|
||||
"filename": "simple-timebar.js",
|
||||
"title": {
|
||||
"zh": "定义时间轴样式",
|
||||
"en": "ToolBar"
|
||||
"zh": "简易时间轴",
|
||||
"en": "SimperTimeBar"
|
||||
},
|
||||
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*eq2kQ4N-pNcAAAAAAAAAAABkARQnAQ"
|
||||
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*g2zhQqP6ruYAAAAAAAAAAAAAARQnAQ"
|
||||
},
|
||||
{
|
||||
"filename": "slice-timebar.js",
|
||||
"title": {
|
||||
"zh": "刻度时间轴",
|
||||
"en": "SliceTimeBar"
|
||||
},
|
||||
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*n6ECQ7Jn5pQAAAAAAAAAAAAAARQnAQ"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
114
examples/tool/timebar/demo/simple-timebar.js
Normal file
114
examples/tool/timebar/demo/simple-timebar.js
Normal file
@ -0,0 +1,114 @@
|
||||
import G6 from '@antv/g6';
|
||||
import insertCss from 'insert-css';
|
||||
|
||||
// 我们用 insert-css 演示引入自定义样式
|
||||
// 推荐将样式添加到自己的样式文件中
|
||||
// 若拷贝官方代码,别忘了 npm install insert-css
|
||||
insertCss(`
|
||||
.g6-component-timebar {
|
||||
top: 450px;
|
||||
left: 100px;
|
||||
}
|
||||
`);
|
||||
|
||||
const data = {
|
||||
nodes: [],
|
||||
edges: [],
|
||||
};
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const id = `node-${i}`;
|
||||
data.nodes.push({
|
||||
id,
|
||||
date: `2020${i}`,
|
||||
value: Math.round(Math.random() * 300),
|
||||
});
|
||||
|
||||
data.edges.push({
|
||||
source: `node-${Math.round(Math.random() * 90)}`,
|
||||
target: `node-${Math.round(Math.random() * 90)}`,
|
||||
});
|
||||
}
|
||||
const graphDiv = document.getElementById('container');
|
||||
|
||||
const width = document.getElementById('container').scrollWidth;
|
||||
const height = document.getElementById('container').scrollHeight || 500;
|
||||
|
||||
const timeBarData = [];
|
||||
|
||||
const nodeSize = 20;
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
timeBarData.push({
|
||||
date: `2020${i}`,
|
||||
value: Math.round(Math.random() * 300),
|
||||
});
|
||||
}
|
||||
|
||||
const timebar = new G6.TimeBar({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 500,
|
||||
height: 150,
|
||||
padding: 10,
|
||||
type: 'simple',
|
||||
trend: {
|
||||
data: timeBarData
|
||||
}
|
||||
});
|
||||
|
||||
// constrained the layout inside the area
|
||||
const constrainBox = { x: 10, y: 10, width: 580, height: 450 };
|
||||
|
||||
const onTick = () => {
|
||||
let minx = 99999999;
|
||||
let maxx = -99999999;
|
||||
let miny = 99999999;
|
||||
let maxy = -99999999;
|
||||
data.nodes.forEach((node) => {
|
||||
if (minx > node.x) {
|
||||
minx = node.x;
|
||||
}
|
||||
if (maxx < node.x) {
|
||||
maxx = node.x;
|
||||
}
|
||||
if (miny > node.y) {
|
||||
miny = node.y;
|
||||
}
|
||||
if (maxy < node.y) {
|
||||
maxy = node.y;
|
||||
}
|
||||
});
|
||||
const scalex = (constrainBox.width - nodeSize / 2) / (maxx - minx);
|
||||
const scaley = (constrainBox.height - nodeSize / 2) / (maxy - miny);
|
||||
data.nodes.forEach((node) => {
|
||||
node.x = (node.x - minx) * scalex + constrainBox.x;
|
||||
node.y = (node.y - miny) * scaley + constrainBox.y;
|
||||
});
|
||||
};
|
||||
|
||||
const graph = new G6.Graph({
|
||||
container: 'container',
|
||||
width,
|
||||
height: height - 100,
|
||||
linkCenter: true,
|
||||
plugins: [timebar],
|
||||
layout: {
|
||||
type: 'force',
|
||||
preventOverlap: true,
|
||||
onTick,
|
||||
},
|
||||
defaultNode: {
|
||||
size: nodeSize,
|
||||
type: 'circle',
|
||||
style: {
|
||||
fill: '#DEE9FF',
|
||||
stroke: '#5B8FF9',
|
||||
},
|
||||
},
|
||||
modes: {
|
||||
default: ['drag-node'],
|
||||
},
|
||||
});
|
||||
graph.data(data);
|
||||
graph.render();
|
@ -5,9 +5,9 @@ import insertCss from 'insert-css';
|
||||
// 推荐将样式添加到自己的样式文件中
|
||||
// 若拷贝官方代码,别忘了 npm install insert-css
|
||||
insertCss(`
|
||||
#g6-component-timebar {
|
||||
top: 540px;
|
||||
left: 10px;
|
||||
.g6-component-timebar {
|
||||
top: 450px;
|
||||
left: 100px;
|
||||
}
|
||||
`);
|
||||
|
||||
@ -44,49 +44,34 @@ for (let i = 0; i < 100; i++) {
|
||||
|
||||
const nodeSize = 20;
|
||||
|
||||
const timebar = new G6.TimeBar({
|
||||
width: 600,
|
||||
timebar: {
|
||||
width: 580,
|
||||
minLimit: 0,
|
||||
maxLimit: 1,
|
||||
start: 0,
|
||||
end: 0.5,
|
||||
backgroundStyle: {
|
||||
fill: '#ddd',
|
||||
opacity: 0.2,
|
||||
lineWidth: 1,
|
||||
stroke: '#aaa',
|
||||
},
|
||||
foregroundStyle: {
|
||||
fill: '#f00',
|
||||
opacity: 0.1,
|
||||
},
|
||||
trend: {
|
||||
let count = 0;
|
||||
const timebar = new G6.TimeBar({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 500,
|
||||
height: 150,
|
||||
padding: 10,
|
||||
type: 'slice',
|
||||
slice: {
|
||||
data: timeBarData,
|
||||
isArea: false,
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
stroke: '#f00',
|
||||
lineWidth: 2,
|
||||
opacity: 0.5,
|
||||
width: 500,
|
||||
height: 42,
|
||||
padding: 2,
|
||||
tickLabelFormatter: d => {
|
||||
count++;
|
||||
const dateStr = `${d.date}`
|
||||
if ((count - 1) % 10 === 0) {
|
||||
return `${dateStr.substr(0, 4)}-${dateStr.substr(4, 2)}-${dateStr.substr(6, 2)}`;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
tooltipFomatter: d => {
|
||||
const dateStr = `${d}`
|
||||
return `${dateStr.substr(0, 4)}-${dateStr.substr(4, 2)}-${dateStr.substr(6, 2)}`;
|
||||
}
|
||||
},
|
||||
textStyle: {
|
||||
fontWeight: 500,
|
||||
fill: '#000',
|
||||
fontSize: 14,
|
||||
},
|
||||
handlerStyle: {
|
||||
width: 10,
|
||||
height: 35,
|
||||
},
|
||||
},
|
||||
rangeChange: (graph, min, max) => {
|
||||
// 拿到 Graph 实例和 timebar 上范围,自己可以控制图上的渲染逻辑
|
||||
console.log(graph, min, max);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// constrained the layout inside the area
|
||||
const constrainBox = { x: 10, y: 10, width: 580, height: 450 };
|
||||
|
||||
@ -121,7 +106,7 @@ const onTick = () => {
|
||||
const graph = new G6.Graph({
|
||||
container: 'container',
|
||||
width,
|
||||
height: height - 50,
|
||||
height: height - 100,
|
||||
linkCenter: true,
|
||||
plugins: [timebar],
|
||||
layout: {
|
@ -5,9 +5,9 @@ import insertCss from 'insert-css';
|
||||
// 推荐将样式添加到自己的样式文件中
|
||||
// 若拷贝官方代码,别忘了 npm install insert-css
|
||||
insertCss(`
|
||||
#g6-component-timebar {
|
||||
top: 540px;
|
||||
left: 10px;
|
||||
.g6-component-timebar {
|
||||
top: 450px;
|
||||
left: 100px;
|
||||
}
|
||||
`);
|
||||
|
||||
@ -46,16 +46,14 @@ for (let i = 0; i < 100; i++) {
|
||||
}
|
||||
|
||||
const timebar = new G6.TimeBar({
|
||||
width: 600,
|
||||
timebar: {
|
||||
width: 550,
|
||||
trend: {
|
||||
data: timeBarData,
|
||||
isArea: false,
|
||||
smooth: true,
|
||||
},
|
||||
start: 0.4,
|
||||
end: 0.5,
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 500,
|
||||
height: 150,
|
||||
padding: 10,
|
||||
type: 'trend',
|
||||
trend: {
|
||||
data: timeBarData
|
||||
},
|
||||
});
|
||||
|
||||
@ -93,7 +91,7 @@ const onTick = () => {
|
||||
const graph = new G6.Graph({
|
||||
container: 'container',
|
||||
width,
|
||||
height: height - 50,
|
||||
height: height - 100,
|
||||
linkCenter: true,
|
||||
plugins: [timebar],
|
||||
layout: {
|
||||
|
@ -7,4 +7,4 @@ G6 中内置的 TimeBar 组件。
|
||||
|
||||
## 使用指南
|
||||
|
||||
下面的代码演示展示了如何在图上使用 TimeBar。TimeBar 的样式可以参考 demo 2 进行配置。
|
||||
下面的代码演示展示了如何在图上使用 TimeBar。目前仅提供了三种类型的默认时间轴组件,更多配置的时间轴组件敬请期待。
|
||||
|
@ -1,4 +1,4 @@
|
||||
// window.g6 = require('./src/index.ts'); // import the source for debugging
|
||||
window.g6 = require('./dist/g6.min.js'); // import the package for webworker
|
||||
window.g6 = require('./src/index.ts'); // import the source for debugging
|
||||
// window.g6 = require('./dist/g6.min.js'); // import the package for webworker
|
||||
window.insertCss = require('insert-css');
|
||||
window.Chart = require('@antv/chart-node-g6');
|
||||
|
@ -71,7 +71,6 @@
|
||||
},
|
||||
"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.5.1",
|
||||
|
@ -1,4 +1,3 @@
|
||||
import GCanvas from '@antv/g-canvas/lib/canvas';
|
||||
import Base, { IPluginBaseConfig } from '../base';
|
||||
import isString from '@antv/util/lib/is-string';
|
||||
import createDOM from '@antv/dom-util/lib/create-dom';
|
||||
|
10
src/plugins/timeBar/constant.ts
Normal file
10
src/plugins/timeBar/constant.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export const TIMELINE_START = 'timelinestart';
|
||||
export const TIMELINE_END = 'timelineend';
|
||||
|
||||
export const VALUE_CHANGE = 'valueChange';
|
||||
|
||||
export const TIMEBAR_CONFIG_CHANGE = 'timebarConfigChanged'
|
||||
|
||||
export const PLAY_PAUSE_BTN = 'playPauseBtn'
|
||||
export const NEXT_STEP_BTN = 'nextStepBtn'
|
||||
export const PRE_STEP_BTN = 'preStepBtn'
|
@ -3,10 +3,6 @@ import { deepMix } from '@antv/util'
|
||||
import Button from './timeButton';
|
||||
import { ShapeStyle } from '../../types';
|
||||
|
||||
export const TIMELINE_START = 'timelinestart';
|
||||
export const TIMELINE_CHANGE = 'timelinechange';
|
||||
export const TIMELINE_END = 'timelineend';
|
||||
|
||||
const DEFAULT_RECT_FILL = '#ccc'
|
||||
const DEFAULT_RECT_STROKE = 'green'
|
||||
const DEFAULT_PLAYBTN_STYLE = {
|
||||
@ -64,10 +60,6 @@ export type ControllerCfg = Partial<{
|
||||
readonly playBtnStyle?: ShapeStyle;
|
||||
}>
|
||||
|
||||
/**
|
||||
* 参考示例
|
||||
* https://www.gapminder.org/tools/#$state$time$value=1870&delay:100;;&chart-type=bubbles
|
||||
*/
|
||||
export default class ControllerBtn {
|
||||
/** 是否处于播放状态 */
|
||||
private isPlay: boolean;
|
||||
@ -372,6 +364,11 @@ export default class ControllerBtn {
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.group.off('playPauseBtn:click')
|
||||
this.speedGroup.off('speed-rect:click')
|
||||
if (this.toggleGroup) {
|
||||
this.toggleGroup.off('toggle-model:click')
|
||||
this.toggleGroup.destroy()
|
||||
}
|
||||
this.speedGroup.destroy()
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ const DEFAULT_STYLE = {
|
||||
opacity: 1,
|
||||
cursor: 'ew-resize',
|
||||
// 高亮的颜色
|
||||
highLightFill: '#FFF',
|
||||
highLightFill: '#0050b3',
|
||||
};
|
||||
|
||||
const SIMPLE_DEFAULT_STYLE = {
|
||||
@ -194,7 +194,9 @@ export default class Handler {
|
||||
// 移动到对应的位置
|
||||
this.updateXY();
|
||||
|
||||
this.bindEvents();
|
||||
if (this.handleType === 'trend') {
|
||||
this.bindEvents();
|
||||
}
|
||||
}
|
||||
|
||||
private bindEvents() {
|
||||
|
@ -1,79 +1,74 @@
|
||||
import modifyCSS from '@antv/dom-util/lib/modify-css';
|
||||
import createDOM from '@antv/dom-util/lib/create-dom';
|
||||
import { IGroup } from '@antv/g-base';
|
||||
import { Canvas } from '@antv/g-canvas';
|
||||
import { Slider } from '@antv/component';
|
||||
import { ShapeStyle, GraphData } from '../../types';
|
||||
/**
|
||||
* 基于 G 的时间轴组件
|
||||
*/
|
||||
import GCanvas from '@antv/g-canvas/lib/canvas';
|
||||
import GSVGCanvas from '@antv/g-svg/lib/canvas';
|
||||
import { IGroup, ICanvas } from '@antv/g-base';
|
||||
import createDOM from '@antv/dom-util/lib/create-dom'
|
||||
import modifyCSS from '@antv/dom-util/lib/modify-css'
|
||||
import { isString } from '@antv/util'
|
||||
import Base, { IPluginBaseConfig } from '../base';
|
||||
import TrendTimeBar, { SliderOption, ControllerCfg } from './trendTimeBar'
|
||||
import TimeBarSlice, { TimeBarSliceOption } from './timeBarSlice'
|
||||
import { IGraph } from '../../interface/graph';
|
||||
import { VALUE_CHANGE } from './constant'
|
||||
import { GraphData, ShapeStyle } from '../../types';
|
||||
import { Interval } from './trend';
|
||||
|
||||
interface Data {
|
||||
date: string;
|
||||
value: number;
|
||||
}
|
||||
// simple 版本默认高度
|
||||
const DEFAULT_SIMPLE_HEIGHT = 8
|
||||
|
||||
interface Callback {
|
||||
// trend 版本默认高度
|
||||
const DEFAULT_TREND_HEIGHT = 26
|
||||
|
||||
export interface Callback {
|
||||
originValue: number[];
|
||||
value: number[];
|
||||
target: IGroup;
|
||||
}
|
||||
|
||||
interface TrendConfig {
|
||||
readonly data: Data[];
|
||||
// 数据
|
||||
readonly data: {
|
||||
date: string;
|
||||
value: string;
|
||||
}[];
|
||||
// 位置大小
|
||||
readonly x?: number;
|
||||
readonly y?: number;
|
||||
readonly width?: number;
|
||||
readonly height?: number;
|
||||
// 样式
|
||||
readonly smooth?: boolean;
|
||||
readonly isArea?: boolean;
|
||||
readonly backgroundStyle?: ShapeStyle;
|
||||
readonly lineStyle?: ShapeStyle;
|
||||
readonly areaStyle?: ShapeStyle;
|
||||
readonly interval?: Interval;
|
||||
}
|
||||
|
||||
interface ExtendedTrendConfig {
|
||||
readonly data: number[];
|
||||
// 样式
|
||||
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: {
|
||||
width: number;
|
||||
height: number;
|
||||
style: 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;
|
||||
|
||||
readonly trendCfg: ExtendedTrendConfig;
|
||||
}>;
|
||||
|
||||
interface TimeBarConfig extends IPluginBaseConfig {
|
||||
width?: number;
|
||||
height?: number;
|
||||
timebar: TimeBarOption;
|
||||
rangeChange?: (graph: IGraph, min: number, max: number) => void;
|
||||
// position size
|
||||
readonly x?: number;
|
||||
readonly y?: number;
|
||||
readonly width?: number;
|
||||
readonly height?: number;
|
||||
readonly padding?: number;
|
||||
|
||||
readonly type?: 'trend' | 'simple' | 'slice';
|
||||
// 趋势图配置项
|
||||
readonly trend?: TrendConfig;
|
||||
// 滑块、及前后背景的配置
|
||||
readonly slider?: SliderOption;
|
||||
|
||||
// 刻度时间轴配置项
|
||||
readonly slice?: TimeBarSliceOption;
|
||||
|
||||
// 控制按钮
|
||||
readonly controllerCfg?: ControllerCfg;
|
||||
|
||||
rangeChange?: (graph: IGraph, minValue: string, maxValue: string) => void;
|
||||
valueChange?: (graph: IGraph, value: string) => void;
|
||||
}
|
||||
|
||||
export default class TimeBar extends Base {
|
||||
@ -81,125 +76,162 @@ export default class TimeBar extends Base {
|
||||
|
||||
public getDefaultCfgs(): TimeBarConfig {
|
||||
return {
|
||||
width: 400,
|
||||
height: 50,
|
||||
rangeChange: null,
|
||||
timebar: {
|
||||
x: 10,
|
||||
y: 10,
|
||||
width: 380,
|
||||
height: 26,
|
||||
minLimit: 0,
|
||||
maxLimit: 1,
|
||||
container: null,
|
||||
className: 'g6-component-timebar',
|
||||
padding: 10,
|
||||
type: 'trend',
|
||||
trend: {
|
||||
data: [],
|
||||
isArea: false,
|
||||
smooth: true
|
||||
},
|
||||
controllerCfg: {
|
||||
speed: 2,
|
||||
loop: false,
|
||||
},
|
||||
slider: {
|
||||
start: 0.1,
|
||||
end: 0.9,
|
||||
minText: 'min',
|
||||
maxText: 'max',
|
||||
},
|
||||
slice: {
|
||||
start: 0.1,
|
||||
end: 0.9,
|
||||
data: []
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
constructor(cfgs?: TimeBarConfig) {
|
||||
super(cfgs)
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 TimeBar 的容器
|
||||
*/
|
||||
public initContainer() {
|
||||
const graph: IGraph = this.get('graph');
|
||||
const { width, height } = this._cfgs
|
||||
const className: string = this.get('className') || 'g6-component-timebar';
|
||||
|
||||
const container: HTMLDivElement | null = this.get('container');
|
||||
|
||||
const graphContainer = this.get('graph').get('container');
|
||||
|
||||
let timebar;
|
||||
let timeBarContainer;
|
||||
if (!container) {
|
||||
timebar = createDOM(`<div class='g6-component-timebar'></div>`);
|
||||
modifyCSS(timebar, { position: 'absolute' });
|
||||
timeBarContainer = createDOM(`<div class='${className}'></div>`);
|
||||
modifyCSS(timeBarContainer, { position: 'absolute' });
|
||||
} else {
|
||||
timebar = container;
|
||||
timeBarContainer = container;
|
||||
}
|
||||
graphContainer.appendChild(timebar);
|
||||
|
||||
graphContainer.appendChild(timeBarContainer);
|
||||
|
||||
this.set('timeBarContainer', timebar);
|
||||
this.set('timeBarContainer', timeBarContainer);
|
||||
|
||||
this.initTimeBar(timebar);
|
||||
let canvas;
|
||||
const renderer = graph.get('renderer');
|
||||
if (renderer === 'SVG') {
|
||||
canvas = new GSVGCanvas({
|
||||
container: timeBarContainer,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
} else {
|
||||
canvas = new GCanvas({
|
||||
container: timeBarContainer,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
}
|
||||
this.set('canvas', canvas);
|
||||
}
|
||||
|
||||
private initTimeBar(container: HTMLDivElement) {
|
||||
const width = this.get('width');
|
||||
const height = this.get('height');
|
||||
const canvas = new Canvas({
|
||||
container,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
public init() {
|
||||
this.initContainer()
|
||||
const canvas: ICanvas = this.get('canvas')
|
||||
const timeBarGroup = canvas.addGroup({
|
||||
name: 'timebar-group'
|
||||
})
|
||||
|
||||
const group = canvas.addGroup({
|
||||
id: 'timebar-plugin',
|
||||
});
|
||||
this.set('timeBarGroup', timeBarGroup)
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
const min = Math.round(data.length * option.start);
|
||||
let max = Math.round(data.length * option.end);
|
||||
max = max >= data.length ? data.length - 1 : max;
|
||||
|
||||
config.minText = data[min].date;
|
||||
config.maxText = data[max].date;
|
||||
|
||||
this.set('trendData', data);
|
||||
|
||||
const slider = new Slider(config);
|
||||
|
||||
slider.init();
|
||||
slider.render();
|
||||
|
||||
this.set('slider', slider);
|
||||
|
||||
this.bindEvent();
|
||||
this.renderTrend()
|
||||
this.initEvent()
|
||||
}
|
||||
|
||||
/**
|
||||
* 当滑动时,最小值和最大值会变化,变化以后触发相应事件
|
||||
*/
|
||||
private bindEvent() {
|
||||
const slider = this.get('slider');
|
||||
const { start, end } = this.get('timebar');
|
||||
const graph: IGraph = this.get('graph');
|
||||
graph.on('afterrender', (e) => {
|
||||
this.filterData({ value: [start, end] });
|
||||
});
|
||||
private renderTrend() {
|
||||
const { width, x, y, padding, type, trend, slider, controllerCfg } = this._cfgs
|
||||
const { data, ...other } = trend
|
||||
|
||||
slider.on('valuechanged', (evt: Callback) => {
|
||||
this.filterData(evt);
|
||||
});
|
||||
const realWidth = width - 2 * padding
|
||||
const defaultHeight = type === 'trend' ? DEFAULT_TREND_HEIGHT : DEFAULT_SIMPLE_HEIGHT
|
||||
|
||||
const graph = this.get('graph')
|
||||
const group = this.get('timeBarGroup')
|
||||
const canvas = this.get('canvas')
|
||||
|
||||
let timebar = null
|
||||
if (type === 'trend' || type === 'simple') {
|
||||
timebar = new TrendTimeBar({
|
||||
graph,
|
||||
canvas,
|
||||
group,
|
||||
type,
|
||||
x: x + padding,
|
||||
y: type === 'trend' ? y + padding : y + padding + 15,
|
||||
width: realWidth,
|
||||
height: defaultHeight,
|
||||
padding,
|
||||
trendCfg: {
|
||||
...other,
|
||||
data: data.map(d => d.value)
|
||||
},
|
||||
...slider,
|
||||
ticks: data.map(d => d.date),
|
||||
handlerStyle: {
|
||||
...slider.handlerStyle,
|
||||
height: slider.height || defaultHeight
|
||||
},
|
||||
controllerCfg
|
||||
})
|
||||
} else if (type === 'slice') {
|
||||
const { slice } = this._cfgs
|
||||
// 刻度时间轴
|
||||
timebar = new TimeBarSlice({
|
||||
graph,
|
||||
canvas,
|
||||
group,
|
||||
x: x + padding,
|
||||
y: y + padding,
|
||||
...slice
|
||||
})
|
||||
}
|
||||
|
||||
this.set('timebar', timebar)
|
||||
}
|
||||
|
||||
private filterData(evt) {
|
||||
const { value } = evt;
|
||||
|
||||
let trendData = null
|
||||
const type = this._cfgs.type
|
||||
if (type === 'trend' || type === 'simple') {
|
||||
trendData = this._cfgs.trend.data
|
||||
} else if (type === 'slice') {
|
||||
trendData = this._cfgs.slice.data
|
||||
}
|
||||
|
||||
if (!trendData || trendData.length === 0) {
|
||||
console.warn('请配置 TimeBar 组件的数据')
|
||||
return
|
||||
}
|
||||
|
||||
const trendData: Data[] = this.get('trendData');
|
||||
const rangeChange = this.get('rangeChange');
|
||||
const graph: IGraph = this.get('graph');
|
||||
const slider = this.get('slider');
|
||||
|
||||
const min = Math.round(trendData.length * value[0]);
|
||||
let max = Math.round(trendData.length * value[1]);
|
||||
max = max >= trendData.length ? trendData.length - 1 : max;
|
||||
@ -207,8 +239,10 @@ export default class TimeBar extends Base {
|
||||
const minText = trendData[min].date;
|
||||
const maxText = trendData[max].date;
|
||||
|
||||
slider.set('minText', minText);
|
||||
slider.set('maxText', maxText);
|
||||
if (type !== 'slice') {
|
||||
const timebar = this.get('timebar');
|
||||
timebar.setText(minText, maxText)
|
||||
}
|
||||
|
||||
if (rangeChange) {
|
||||
rangeChange(graph, minText, maxText);
|
||||
@ -242,26 +276,38 @@ export default class TimeBar extends Base {
|
||||
}
|
||||
}
|
||||
|
||||
public show() {
|
||||
const slider = this.get('slider');
|
||||
slider.show();
|
||||
}
|
||||
private initEvent() {
|
||||
let start = 0
|
||||
let end = 0
|
||||
const type = this._cfgs.type
|
||||
if (!type || type === 'trend' || type === 'simple') {
|
||||
start = this._cfgs.slider.start
|
||||
end = this._cfgs.slider.end
|
||||
} else if (type === 'slice') {
|
||||
start = this._cfgs.slice.start
|
||||
end = this._cfgs.slice.end
|
||||
}
|
||||
|
||||
public hide() {
|
||||
const slider = this.get('slider');
|
||||
slider.hide();
|
||||
const graph: IGraph = this.get('graph');
|
||||
graph.on('afterrender', () => {
|
||||
this.filterData({ value: [start, end] });
|
||||
});
|
||||
|
||||
// 时间轴的值发生改变的事件
|
||||
graph.on(VALUE_CHANGE, (evt: Callback) => {
|
||||
// 范围变化
|
||||
this.filterData(evt);
|
||||
});
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.cacheGraphData = null;
|
||||
|
||||
const slider = this.get('slider');
|
||||
|
||||
if (slider) {
|
||||
slider.off('valuechanged');
|
||||
slider.destroy();
|
||||
const timebar = this.get('timebar')
|
||||
if (timebar && timebar.destory) {
|
||||
timebar.destory()
|
||||
}
|
||||
|
||||
super.destroy();
|
||||
|
||||
const timeBarContainer = this.get('timeBarContainer');
|
||||
if (timeBarContainer) {
|
||||
let container: HTMLDivElement | null = this.get('container');
|
||||
|
@ -1,301 +0,0 @@
|
||||
/**
|
||||
* 基于 G 的时间轴组件
|
||||
*/
|
||||
import GCanvas from '@antv/g-canvas/lib/canvas';
|
||||
import GSVGCanvas from '@antv/g-svg/lib/canvas';
|
||||
import { IGroup, ICanvas } from '@antv/g-base';
|
||||
import createDOM from '@antv/dom-util/lib/create-dom'
|
||||
import { isString } from '@antv/util'
|
||||
import Base, { IPluginBaseConfig } from '../base';
|
||||
import TrendTimeBar, { SliderOption, VALUE_CHANGE, ControllerCfg } from './trendTimeBar'
|
||||
import TimeBarSlice, { TimeBarSliceOption } from './timeBarSlice'
|
||||
import { IGraph } from '../../interface/graph';
|
||||
import { GraphData, ShapeStyle } from '../../types';
|
||||
import { Interval } from './trend';
|
||||
|
||||
// simple 版本默认高度
|
||||
const DEFAULT_SIMPLE_HEIGHT = 8
|
||||
|
||||
// trend 版本默认高度
|
||||
const DEFAULT_TREND_HEIGHT = 26
|
||||
|
||||
export interface Callback {
|
||||
originValue: number[];
|
||||
value: number[];
|
||||
target: IGroup;
|
||||
}
|
||||
|
||||
interface TrendConfig {
|
||||
// 数据
|
||||
readonly data: {
|
||||
date: string;
|
||||
value: string;
|
||||
}[];
|
||||
// 位置大小
|
||||
readonly x?: number;
|
||||
readonly y?: number;
|
||||
readonly width?: number;
|
||||
readonly height?: number;
|
||||
// 样式
|
||||
readonly smooth?: boolean;
|
||||
readonly isArea?: boolean;
|
||||
readonly backgroundStyle?: ShapeStyle;
|
||||
readonly lineStyle?: ShapeStyle;
|
||||
readonly areaStyle?: ShapeStyle;
|
||||
readonly interval?: Interval;
|
||||
}
|
||||
|
||||
interface TimeBarConfig extends IPluginBaseConfig {
|
||||
// position size
|
||||
readonly x?: number;
|
||||
readonly y?: number;
|
||||
readonly width?: number;
|
||||
readonly height?: number;
|
||||
readonly padding?: number;
|
||||
|
||||
readonly type?: 'trend' | 'simple' | 'slice';
|
||||
// 趋势图配置项
|
||||
readonly trend?: TrendConfig;
|
||||
// 滑块、及前后背景的配置
|
||||
readonly slider?: SliderOption;
|
||||
|
||||
// 刻度时间轴配置项
|
||||
readonly slice?: TimeBarSliceOption;
|
||||
|
||||
// 控制按钮
|
||||
readonly controllerCfg?: ControllerCfg;
|
||||
|
||||
rangeChange?: (graph: IGraph, minValue: string, maxValue: string) => void;
|
||||
valueChange?: (graph: IGraph, value: string) => void;
|
||||
}
|
||||
|
||||
export default class TimeBar extends Base {
|
||||
private cacheGraphData: GraphData;
|
||||
|
||||
public getDefaultCfgs(): TimeBarConfig {
|
||||
return {
|
||||
container: null,
|
||||
className: 'g6-component-timebar',
|
||||
padding: 10,
|
||||
type: 'trend',
|
||||
trend: {
|
||||
data: [],
|
||||
isArea: false,
|
||||
smooth: true
|
||||
},
|
||||
controllerCfg: {
|
||||
speed: 2,
|
||||
loop: false,
|
||||
},
|
||||
slider: {
|
||||
start: 0.1,
|
||||
end: 0.9,
|
||||
minText: 'min',
|
||||
maxText: 'max',
|
||||
},
|
||||
slice: {
|
||||
start: 0.1,
|
||||
end: 0.9,
|
||||
data: []
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
constructor(cfgs?: TimeBarConfig) {
|
||||
super(cfgs)
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 TimeBar 的容器
|
||||
*/
|
||||
public initContainer() {
|
||||
const graph: IGraph = this.get('graph');
|
||||
const { width, height } = this._cfgs
|
||||
const className: string = this.get('className') || 'g6-component-timebar';
|
||||
let parentNode: string | HTMLElement = this.get('container');
|
||||
const container: HTMLElement = createDOM(
|
||||
`<div class='${className}' style='position: absolute; width: ${width}px; height: ${height}px;'></div>`,
|
||||
);
|
||||
|
||||
if (isString(parentNode)) {
|
||||
parentNode = document.getElementById(parentNode) as HTMLElement;
|
||||
}
|
||||
|
||||
if (parentNode) {
|
||||
parentNode.appendChild(container);
|
||||
} else {
|
||||
graph.get('container').appendChild(container);
|
||||
}
|
||||
|
||||
this.set('container', container);
|
||||
|
||||
let canvas;
|
||||
const renderer = graph.get('renderer');
|
||||
if (renderer === 'SVG') {
|
||||
canvas = new GSVGCanvas({
|
||||
container: container,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
} else {
|
||||
canvas = new GCanvas({
|
||||
container: container,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
}
|
||||
this.set('canvas', canvas);
|
||||
}
|
||||
|
||||
public init() {
|
||||
this.initContainer()
|
||||
const canvas: ICanvas = this.get('canvas')
|
||||
const timeBarGroup = canvas.addGroup({
|
||||
name: 'timebar-group'
|
||||
})
|
||||
|
||||
this.set('timeBarGroup', timeBarGroup)
|
||||
|
||||
this.renderTrend()
|
||||
this.initEvent()
|
||||
}
|
||||
|
||||
private renderTrend() {
|
||||
const { width, x, y, padding, type, trend, slider, controllerCfg } = this._cfgs
|
||||
const { data, ...other } = trend
|
||||
|
||||
const realWidth = width - 2 * padding
|
||||
const defaultHeight = type === 'trend' ? DEFAULT_TREND_HEIGHT : DEFAULT_SIMPLE_HEIGHT
|
||||
|
||||
const graph = this.get('graph')
|
||||
const group = this.get('timeBarGroup')
|
||||
const canvas = this.get('canvas')
|
||||
|
||||
let timebar = null
|
||||
if (type === 'trend' || type === 'simple') {
|
||||
timebar = new TrendTimeBar({
|
||||
graph,
|
||||
canvas,
|
||||
group,
|
||||
type,
|
||||
x: x + padding,
|
||||
y: type === 'trend' ? y + padding : y + padding + 15,
|
||||
width: realWidth,
|
||||
height: defaultHeight,
|
||||
padding,
|
||||
trendCfg: {
|
||||
...other,
|
||||
data: data.map(d => d.value)
|
||||
},
|
||||
...slider,
|
||||
ticks: data.map(d => d.date),
|
||||
handlerStyle: {
|
||||
...slider.handlerStyle,
|
||||
height: slider.height || defaultHeight
|
||||
},
|
||||
controllerCfg
|
||||
})
|
||||
} else if (type === 'slice') {
|
||||
const { slice } = this._cfgs
|
||||
// 刻度时间轴
|
||||
timebar = new TimeBarSlice({
|
||||
graph,
|
||||
canvas,
|
||||
group,
|
||||
x: x + padding,
|
||||
y: y + padding,
|
||||
...slice
|
||||
})
|
||||
}
|
||||
|
||||
this.set('timebar', timebar)
|
||||
}
|
||||
|
||||
private filterData(evt) {
|
||||
const { value } = evt;
|
||||
// TODO 不同类型的 TimeBar 取不同地方的data
|
||||
let trendData = null
|
||||
const type = this._cfgs.type
|
||||
if (type === 'trend' || type === 'simple') {
|
||||
trendData = this._cfgs.trend.data
|
||||
} else if (type === 'slice') {
|
||||
trendData = this._cfgs.slice.data
|
||||
}
|
||||
// const { data: trendData } = this._cfgs.trend
|
||||
const rangeChange = this.get('rangeChange');
|
||||
const graph: IGraph = this.get('graph');
|
||||
|
||||
const min = Math.round(trendData.length * value[0]);
|
||||
let max = Math.round(trendData.length * value[1]);
|
||||
max = max >= trendData.length ? trendData.length - 1 : max;
|
||||
|
||||
const minText = trendData[min].date;
|
||||
const maxText = trendData[max].date;
|
||||
|
||||
if (type !== 'slice') {
|
||||
const timebar = this.get('timebar');
|
||||
timebar.setText(minText, maxText)
|
||||
}
|
||||
|
||||
if (rangeChange) {
|
||||
rangeChange(graph, minText, maxText);
|
||||
} else {
|
||||
// 自动过滤数据,并渲染 graph
|
||||
const graphData = graph.save() as GraphData;
|
||||
|
||||
if (
|
||||
!this.cacheGraphData ||
|
||||
(this.cacheGraphData.nodes && this.cacheGraphData.nodes.length === 0)
|
||||
) {
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private initEvent() {
|
||||
let start = 0
|
||||
let end = 0
|
||||
const type = this._cfgs.type
|
||||
if (!type || type === 'trend' || type === 'simple') {
|
||||
start = this._cfgs.slider.start
|
||||
end = this._cfgs.slider.end
|
||||
} else if (type === 'slice') {
|
||||
start = this._cfgs.slice.start
|
||||
end = this._cfgs.slice.end
|
||||
}
|
||||
|
||||
const graph: IGraph = this.get('graph');
|
||||
graph.on('afterrender', () => {
|
||||
this.filterData({ value: [start, end] });
|
||||
});
|
||||
|
||||
// 时间轴的值发生改变的事件
|
||||
graph.on(VALUE_CHANGE, (evt: Callback) => {
|
||||
// 范围变化
|
||||
this.filterData(evt);
|
||||
});
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
super.destroy();
|
||||
const group = this.get('timeBarGroup')
|
||||
group.off('playPauseBtn:click')
|
||||
}
|
||||
}
|
@ -1,486 +0,0 @@
|
||||
/**
|
||||
* 基于 G 的时间轴组件
|
||||
*/
|
||||
import GCanvas from '@antv/g-canvas/lib/canvas';
|
||||
import GSVGCanvas from '@antv/g-svg/lib/canvas';
|
||||
import { ICanvas } from '@antv/g-base';
|
||||
import createDOM from '@antv/dom-util/lib/create-dom'
|
||||
import { isString } from '@antv/util'
|
||||
import Base, { IPluginBaseConfig } from '../base';
|
||||
import TimeBarTooltip from './timeBarTooltip';
|
||||
import { SliderOption, ControllerCfg } from './trendTimeBar'
|
||||
import { IGraph } from '../../interface/graph';
|
||||
import { GraphData } from '../../types';
|
||||
|
||||
interface TrendConfig {
|
||||
readonly slider?: SliderOption;
|
||||
// 数据
|
||||
readonly data: {
|
||||
date: string;
|
||||
value: string;
|
||||
}[];
|
||||
// 位置大小
|
||||
readonly x?: number;
|
||||
readonly y?: number;
|
||||
readonly width?: number;
|
||||
readonly height?: number;
|
||||
// 样式
|
||||
readonly smooth?: boolean;
|
||||
readonly isArea?: boolean;
|
||||
readonly backgroundStyle?: object;
|
||||
readonly lineStyle?: object;
|
||||
readonly areaStyle?: object;
|
||||
}
|
||||
|
||||
interface TickStyle {
|
||||
fill?: string,
|
||||
stroke?: string,
|
||||
lineWidth?: number,
|
||||
opacity?: number,
|
||||
fillOpacity?: number,
|
||||
strokeOpacity?: number
|
||||
}
|
||||
|
||||
interface TimeBarConfig extends IPluginBaseConfig {
|
||||
// position size
|
||||
readonly x?: number;
|
||||
readonly y?: number;
|
||||
readonly width?: number;
|
||||
readonly height?: number;
|
||||
readonly padding?: number;
|
||||
|
||||
// styles
|
||||
readonly selectedTickStyle?: TickStyle;
|
||||
readonly unselectedTickStyle?: TickStyle
|
||||
readonly tooltipBackgroundColor?: string;
|
||||
|
||||
// 趋势图配置项
|
||||
readonly trend?: TrendConfig;
|
||||
// 滑块、及前后背景的配置
|
||||
readonly slider?: SliderOption;
|
||||
// 自定义标签格式化函数
|
||||
readonly tickLabelFormatter?: (d: any) => string | boolean;
|
||||
// 自定义 tooltip 内容格式化函数
|
||||
readonly tooltipFomatter?: (d: any) => string;
|
||||
|
||||
// 控制按钮
|
||||
readonly controllerCfg?: ControllerCfg;
|
||||
|
||||
// readonly opti
|
||||
rangeChange?: (graph: IGraph, minValue: string, maxValue: string) => void;
|
||||
valueChange?: (graph: IGraph, value: string) => void;
|
||||
}
|
||||
|
||||
export default class TimeBarSlice extends Base {
|
||||
private cacheGraphData: GraphData;
|
||||
|
||||
public getDefaultCfgs(): TimeBarConfig {
|
||||
return {
|
||||
container: null,
|
||||
className: 'g6-component-timebar',
|
||||
padding: 2,
|
||||
trend: {
|
||||
data: [],
|
||||
isArea: false,
|
||||
smooth: true
|
||||
},
|
||||
controllerCfg: {
|
||||
speed: 2,
|
||||
loop: false,
|
||||
},
|
||||
slider: {
|
||||
minLimit: 0,
|
||||
maxLimit: 1,
|
||||
start: 0.1,
|
||||
end: 0.9,
|
||||
minText: 'min',
|
||||
maxText: 'max',
|
||||
},
|
||||
selectedTickStyle: {
|
||||
fill: '#5B8FF9'
|
||||
},
|
||||
unselectedTickStyle: {
|
||||
fill: '#e6e8e9'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
constructor(cfgs?: TimeBarConfig) {
|
||||
super(cfgs)
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 TimeBar 的容器
|
||||
*/
|
||||
public initContainer() {
|
||||
const graph: IGraph = this.get('graph');
|
||||
const { width, height } = this._cfgs
|
||||
const className: string = this.get('className') || 'g6-component-timebar';
|
||||
let parentNode: string | HTMLElement = this.get('container');
|
||||
const container: HTMLElement = createDOM(
|
||||
`<div class='${className}' style='position: absolute; width: ${width}px; height: ${height}px;'></div>`,
|
||||
);
|
||||
|
||||
if (isString(parentNode)) {
|
||||
parentNode = document.getElementById(parentNode) as HTMLElement;
|
||||
}
|
||||
|
||||
if (parentNode) {
|
||||
parentNode.appendChild(container);
|
||||
} else {
|
||||
graph.get('container').appendChild(container);
|
||||
}
|
||||
|
||||
this.set('container', container);
|
||||
|
||||
let canvas;
|
||||
const renderer = graph.get('renderer');
|
||||
if (renderer === 'SVG') {
|
||||
canvas = new GSVGCanvas({
|
||||
container: container,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
} else {
|
||||
canvas = new GCanvas({
|
||||
container: container,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
}
|
||||
this.set('canvas', canvas);
|
||||
}
|
||||
|
||||
public init() {
|
||||
this.initContainer()
|
||||
const canvas: ICanvas = this.get('canvas')
|
||||
const timeBarGroup = canvas.addGroup({
|
||||
name: 'timebar-group'
|
||||
})
|
||||
|
||||
this.set('timeBarGroup', timeBarGroup)
|
||||
|
||||
this.renderSlices()
|
||||
// this.renderTimeLine()
|
||||
this.initEvent()
|
||||
}
|
||||
|
||||
private renderSlices() {
|
||||
const self = this;
|
||||
const ratio = 0.6
|
||||
const { width, height, x, y, padding, trend, slider, tickLabelFormatter } = self._cfgs
|
||||
const { data, ...other } = trend
|
||||
const { start, end } = slider;
|
||||
|
||||
// const realHeight = height - 2 * padding
|
||||
const realWidth = width - 2 * padding
|
||||
const fontSize = 10;
|
||||
const labelLineHeight = 4;
|
||||
const labelAreaHeight = 3 * padding + labelLineHeight + fontSize;
|
||||
const ticksAreaHeight = height - labelAreaHeight - 2 * padding;
|
||||
|
||||
// styles
|
||||
const selectedTickStyle = self.get('selectedTickStyle');
|
||||
const unselectedTickStyle = self.get('unselectedTickStyle');
|
||||
|
||||
const gap = 2;
|
||||
const ticksLength = data.length;
|
||||
const tickWidth = (realWidth - gap * (ticksLength - 1)) / ticksLength;
|
||||
self.set('tickWidth', tickWidth);
|
||||
const group = self.get('timeBarGroup');
|
||||
const tickRects = [];
|
||||
const labels = [];
|
||||
|
||||
const startTickId = Math.round(ticksLength * start);
|
||||
const endTickId = Math.round(ticksLength * end);
|
||||
self.set('startTickRectId', startTickId);
|
||||
self.set('endickRectId', endTickId);
|
||||
|
||||
data.forEach((d, i) => {
|
||||
// draw the tick rects
|
||||
const selected = i >= startTickId && i <= endTickId;
|
||||
const tickStyle = selected ? selectedTickStyle : unselectedTickStyle
|
||||
const rect = group.addShape('rect', {
|
||||
attrs: {
|
||||
x: padding + i * (tickWidth + gap),
|
||||
y: padding,
|
||||
width: tickWidth,
|
||||
height: ticksAreaHeight,
|
||||
...tickStyle
|
||||
},
|
||||
draggable: true,
|
||||
name: `tick-rect-${i}`
|
||||
});
|
||||
// draw the pick tick rects
|
||||
const pickRect = group.addShape('rect', {
|
||||
attrs: {
|
||||
x: padding + i * tickWidth + gap * (2 * i - 1) / 2,
|
||||
y: padding,
|
||||
width: (i === 0 || i === ticksLength - 1) ? (tickWidth + gap / 2) : (tickWidth + gap),
|
||||
height: ticksAreaHeight,
|
||||
fill: '#fff',
|
||||
opacity: 0
|
||||
},
|
||||
draggable: true,
|
||||
name: `pick-rect-${i}`
|
||||
});
|
||||
|
||||
const rectBBox = rect.getBBox();
|
||||
const centerX = (rectBBox.minX + rectBBox.maxX) / 2;
|
||||
tickRects.push({
|
||||
rect,
|
||||
pickRect,
|
||||
value: d.date,
|
||||
x: centerX,
|
||||
y: rectBBox.minY
|
||||
});
|
||||
|
||||
let label = undefined;
|
||||
if (tickLabelFormatter) {
|
||||
label = tickLabelFormatter(d);
|
||||
if (!isString(label) && label) { // return true
|
||||
label = d.date;
|
||||
}
|
||||
} else if (i % Math.round(ticksLength / 10) === 0) {
|
||||
label = d.date;
|
||||
}
|
||||
if (label) {
|
||||
labels.push(label);
|
||||
|
||||
// draw tick lines
|
||||
const lineStartY = rectBBox.maxY + padding * 2;
|
||||
group.addShape('line', {
|
||||
attrs: {
|
||||
stroke: '#BFBFBF',
|
||||
x1: centerX,
|
||||
y1: lineStartY,
|
||||
x2: centerX,
|
||||
y2: lineStartY + labelLineHeight
|
||||
}
|
||||
});
|
||||
|
||||
const labelStartY = lineStartY + labelLineHeight + padding;
|
||||
const text = group.addShape('text', {
|
||||
attrs: {
|
||||
fill: '#8c8c8c',
|
||||
stroke: '#fff',
|
||||
lineWidth: 1,
|
||||
x: centerX,
|
||||
y: labelStartY,
|
||||
textAlign: 'center',
|
||||
text: label,
|
||||
textBaseline: 'top',
|
||||
fontSize: 10
|
||||
},
|
||||
capture: false
|
||||
});
|
||||
const textBBox = text.getBBox();
|
||||
if (textBBox.maxX > width) {
|
||||
text.attr('textAlign', 'right');
|
||||
} else if (textBBox.minX < 0) {
|
||||
text.attr('textAlign', 'left');
|
||||
}
|
||||
// draw tick labels
|
||||
}
|
||||
});
|
||||
self.set('tickRects', tickRects);
|
||||
}
|
||||
|
||||
|
||||
private filterData(evt) {
|
||||
const { value } = evt;
|
||||
const { data: trendData } = this._cfgs.trend
|
||||
const rangeChange = this.get('rangeChange');
|
||||
const graph: IGraph = this.get('graph');
|
||||
|
||||
const min = Math.round(trendData.length * value[0]);
|
||||
let max = Math.round(trendData.length * value[1]);
|
||||
max = max >= trendData.length ? trendData.length - 1 : max;
|
||||
|
||||
const minText = trendData[min].date;
|
||||
const maxText = trendData[max].date;
|
||||
|
||||
if (rangeChange) {
|
||||
rangeChange(graph, minText, maxText);
|
||||
} else {
|
||||
// 自动过滤数据,并渲染 graph
|
||||
const graphData = graph.save() as GraphData;
|
||||
|
||||
if (
|
||||
!this.cacheGraphData ||
|
||||
(this.cacheGraphData.nodes && this.cacheGraphData.nodes.length === 0)
|
||||
) {
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private renderCurrentData(value: string) {
|
||||
const valueChange = this.get('valueChange');
|
||||
const graph: IGraph = this.get('graph');
|
||||
|
||||
if (valueChange) {
|
||||
valueChange(graph, value);
|
||||
} else {
|
||||
// 自动过滤数据,并渲染 graph
|
||||
const graphData = graph.save() as GraphData;
|
||||
|
||||
if (
|
||||
!this.cacheGraphData ||
|
||||
(this.cacheGraphData.nodes && this.cacheGraphData.nodes.length === 0)
|
||||
) {
|
||||
this.cacheGraphData = graphData;
|
||||
}
|
||||
|
||||
// 过滤当前的节点
|
||||
const filterData = this.cacheGraphData.nodes.filter(
|
||||
(d: any) => d.date === value,
|
||||
);
|
||||
|
||||
const nodeIds = filterData.map((node) => node.id);
|
||||
|
||||
// 过滤 source 或 target
|
||||
const fileterEdges = this.cacheGraphData.edges.filter(
|
||||
(edge) => nodeIds.includes(edge.source) && nodeIds.includes(edge.target),
|
||||
);
|
||||
|
||||
graph.changeData({
|
||||
nodes: filterData,
|
||||
edges: fileterEdges,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private initEvent() {
|
||||
const self = this;
|
||||
const { start, end } = self._cfgs.slider;
|
||||
const graph: IGraph = self.get('graph');
|
||||
graph.on('afterrender', () => {
|
||||
self.filterData({ value: [start, end] });
|
||||
});
|
||||
|
||||
const group = self.get('timeBarGroup');
|
||||
|
||||
group.on('click', e => {
|
||||
const tickRects = self.get('tickRects');
|
||||
// cancel the selected ticks
|
||||
const unselectedTickStyle = self.get('unselectedTickStyle');
|
||||
tickRects.forEach(tickRect => {
|
||||
tickRect.rect.attr(unselectedTickStyle);
|
||||
})
|
||||
const targetRect = e.target;
|
||||
if (targetRect.get('type') !== 'rect') return;
|
||||
const id = parseInt(targetRect.get('name').split('-')[2]);
|
||||
const selectedTickStyle = self.get('selectedTickStyle');
|
||||
tickRects[id].rect.attr(selectedTickStyle);
|
||||
self.set('startTickRectId', id);
|
||||
self.set('endTickRectId', id);
|
||||
const ticksLength = tickRects.length;
|
||||
const start = id / ticksLength;
|
||||
this.filterData({ value: [start, start] });
|
||||
});
|
||||
group.on('dragstart', e => {
|
||||
const tickRects = self.get('tickRects');
|
||||
// cancel the selected ticks
|
||||
const unselectedTickStyle = self.get('unselectedTickStyle');
|
||||
tickRects.forEach(tickRect => {
|
||||
tickRect.rect.attr(unselectedTickStyle);
|
||||
})
|
||||
const targetRect = e.target;
|
||||
const id = parseInt(targetRect.get('name').split('-')[2]);
|
||||
const selectedTickStyle = self.get('selectedTickStyle');
|
||||
tickRects[id].rect.attr(selectedTickStyle);
|
||||
self.set('startTickRectId', id);
|
||||
const ticksLength = tickRects.length;
|
||||
const start = id / ticksLength;
|
||||
this.filterData({ value: [start, start] });
|
||||
self.set('dragging', true);
|
||||
});
|
||||
group.on('dragover', e => {
|
||||
if (!self.get('dragging')) return;
|
||||
if (e.target.get('type') !== 'rect') return;
|
||||
const id = parseInt(e.target.get('name').split('-')[2]);
|
||||
const startTickRectId = self.get('startTickRectId');
|
||||
const tickRects = self.get('tickRects');
|
||||
const selectedTickStyle = self.get('selectedTickStyle');
|
||||
const unselectedTickStyle = self.get('unselectedTickStyle');
|
||||
for (let i = 0; i < tickRects.length; i++) {
|
||||
const style = i >= startTickRectId && i <= id ? selectedTickStyle : unselectedTickStyle;
|
||||
tickRects[i].rect.attr(style);
|
||||
}
|
||||
const ticksLength = tickRects.length;
|
||||
self.set('endTickRectId', id);
|
||||
const start = startTickRectId / ticksLength;
|
||||
const end = id / ticksLength;
|
||||
this.filterData({ value: [start, end] });
|
||||
});
|
||||
|
||||
group.on('drop', e => {
|
||||
if (!self.get('dragging')) return;
|
||||
self.set('dragging', false);
|
||||
if (e.target.get('type') !== 'rect') return;
|
||||
const startTickRectId = self.get('startTickRectId');
|
||||
const id = parseInt(e.target.get('name').split('-')[2]);
|
||||
if (id < startTickRectId) return;
|
||||
const selectedTickStyle = self.get('selectedTickStyle');
|
||||
const tickRects = self.get('tickRects');
|
||||
tickRects[id].rect.attr(selectedTickStyle);
|
||||
self.set('endTickRectId', id);
|
||||
const ticksLength = tickRects.length;
|
||||
const start = startTickRectId / ticksLength;
|
||||
const end = id / ticksLength;
|
||||
this.filterData({ value: [start, end] });
|
||||
});
|
||||
|
||||
// tooltip
|
||||
const { tooltipBackgroundColor, tooltipFomatter } = self._cfgs;
|
||||
const tooltip = new TimeBarTooltip({
|
||||
container: self.get('container') as HTMLElement,
|
||||
backgroundColor: tooltipBackgroundColor
|
||||
});
|
||||
const tickRects = self.get('tickRects');
|
||||
const canvas = self.get('canvas');
|
||||
tickRects.forEach(tickRect => {
|
||||
const pickRect = tickRect.pickRect;
|
||||
pickRect.on('mouseenter', e => {
|
||||
const rect = e.target;
|
||||
if (rect.get('type') !== 'rect') return;
|
||||
const id = parseInt(rect.get('name').split('-')[2]);
|
||||
const clientPoint = canvas.getClientByPoint(tickRects[id].x, tickRects[id].y)
|
||||
tooltip.show({
|
||||
x: tickRects[id].x,
|
||||
y: tickRects[id].y,
|
||||
clientX: clientPoint.x,
|
||||
clientY: clientPoint.y,
|
||||
text: tooltipFomatter ? tooltipFomatter(tickRects[id].value) : tickRects[id].value
|
||||
})
|
||||
});
|
||||
pickRect.on('mouseleave', e => {
|
||||
tooltip.hide();
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
super.destroy();
|
||||
const group = this.get('timeBarGroup')
|
||||
group.off('playPauseBtn:click')
|
||||
}
|
||||
}
|
@ -1,18 +1,13 @@
|
||||
/**
|
||||
* 基于 G 的时间轴组件
|
||||
* 基于 G 的刻度时间轴组件
|
||||
*/
|
||||
import GCanvas from '@antv/g-canvas/lib/canvas';
|
||||
import GSVGCanvas from '@antv/g-svg/lib/canvas';
|
||||
import { ICanvas, IGroup } from '@antv/g-base';
|
||||
import createDOM from '@antv/dom-util/lib/create-dom'
|
||||
import { isString } from '@antv/util'
|
||||
import TimeBarTooltip from './timeBarTooltip';
|
||||
import ControllerBtn from './controllerBtn'
|
||||
import TrendTimeBar, { VALUE_CHANGE, TIMELINE_START, TIMELINE_CHANGE, TIMELINE_END } from './trendTimeBar'
|
||||
import { VALUE_CHANGE, TIMELINE_START, TIMELINE_END, PLAY_PAUSE_BTN, NEXT_STEP_BTN, PRE_STEP_BTN, TIMEBAR_CONFIG_CHANGE } from './constant'
|
||||
import { IGraph } from '../../interface/graph';
|
||||
import { GraphData, ShapeStyle } from '../../types';
|
||||
import { Callback } from './timeBar';
|
||||
|
||||
import { ShapeStyle } from '../../types';
|
||||
|
||||
const DEFAULT_SELECTEDTICK_STYLE = {
|
||||
fill: '#5B8FF9'
|
||||
@ -109,26 +104,6 @@ export default class TimeBarSlice {
|
||||
private playHandler: number;
|
||||
private frameCount: number = 0;
|
||||
|
||||
public getDefaultCfgs(): TimeBarSliceConfig {
|
||||
return {
|
||||
canvas: null,
|
||||
graph: null,
|
||||
group: null,
|
||||
padding: 2,
|
||||
data: [],
|
||||
start: 0.1,
|
||||
end: 0.9,
|
||||
selectedTickStyle: {
|
||||
fill: '#5B8FF9'
|
||||
},
|
||||
unselectedTickStyle: {
|
||||
fill: '#e6e8e9'
|
||||
},
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
}
|
||||
|
||||
constructor(cfgs?: TimeBarSliceConfig) {
|
||||
const {
|
||||
graph,
|
||||
@ -332,7 +307,6 @@ export default class TimeBarSlice {
|
||||
|
||||
const ticksLength = tickRects.length;
|
||||
const start = id / ticksLength;
|
||||
// this.filterData({ value: [start, start] });
|
||||
this.graph.emit(VALUE_CHANGE, { value: [start, start] })
|
||||
}
|
||||
});
|
||||
@ -352,7 +326,6 @@ export default class TimeBarSlice {
|
||||
|
||||
const ticksLength = tickRects.length;
|
||||
const start = id / ticksLength;
|
||||
// this.filterData({ value: [start, start] });
|
||||
this.graph.emit(VALUE_CHANGE, { value: [start, start] })
|
||||
|
||||
this.dragging = true
|
||||
@ -375,7 +348,6 @@ export default class TimeBarSlice {
|
||||
|
||||
const start = startTickRectId / ticksLength;
|
||||
const end = id / ticksLength;
|
||||
// this.filterData({ value: [start, end] });
|
||||
this.graph.emit(VALUE_CHANGE, { value: [start, end] })
|
||||
});
|
||||
|
||||
@ -397,17 +369,9 @@ export default class TimeBarSlice {
|
||||
const ticksLength = tickRects.length;
|
||||
const start = startTickRectId / ticksLength;
|
||||
const end = id / ticksLength;
|
||||
// this.filterData({ value: [start, end] });
|
||||
this.graph.emit(VALUE_CHANGE, { value: [start, end] })
|
||||
});
|
||||
|
||||
// 时间轴的值发生改变的事件
|
||||
// this.graph.on(VALUE_CHANGE, (evt: Callback) => {
|
||||
// // 范围变化
|
||||
// this.filterData(evt);
|
||||
// console.log(evt)
|
||||
// });
|
||||
|
||||
// tooltip
|
||||
const { tooltipBackgroundColor, tooltipFomatter, canvas } = this
|
||||
const tooltip = new TimeBarTooltip({
|
||||
@ -440,22 +404,22 @@ export default class TimeBarSlice {
|
||||
const group = this.group;
|
||||
// 播放区按钮控制
|
||||
/** 播放/暂停事件 */
|
||||
group.on('playPauseBtn:click', () => {
|
||||
group.on(`${PLAY_PAUSE_BTN}:click`, () => {
|
||||
this.isPlay = !this.isPlay;
|
||||
this.changePlayStatus();
|
||||
})
|
||||
|
||||
// 处理前进一步的事件
|
||||
group.on('nextStepBtn:click', () => {
|
||||
group.on(`${NEXT_STEP_BTN}:click`, () => {
|
||||
this.updateStartEnd(1);
|
||||
})
|
||||
|
||||
// 处理后退一步的事件
|
||||
group.on('preStepBtn:click', () => {
|
||||
group.on(`${PRE_STEP_BTN}:click`, () => {
|
||||
this.updateStartEnd(-1);
|
||||
})
|
||||
|
||||
group.on('timebarConfigChanged', ({ type, speed }) => {
|
||||
group.on(TIMEBAR_CONFIG_CHANGE, ({ type, speed }) => {
|
||||
this.currentSpeed = speed
|
||||
})
|
||||
}
|
||||
@ -530,4 +494,30 @@ export default class TimeBarSlice {
|
||||
const end = self.endTickRectId / ticksLength;
|
||||
this.graph.emit(VALUE_CHANGE, { value: [start, end] })
|
||||
}
|
||||
|
||||
public destory() {
|
||||
this.graph.off(VALUE_CHANGE)
|
||||
|
||||
const group = this.sliceGroup
|
||||
|
||||
group.off('click')
|
||||
group.off('dragstart')
|
||||
group.off('dragover')
|
||||
group.off('drop')
|
||||
|
||||
this.tickRects.forEach(tickRect => {
|
||||
const pickRect = tickRect.pickRect;
|
||||
pickRect.off('mouseenter')
|
||||
pickRect.off('mouseleave')
|
||||
})
|
||||
|
||||
this.tickRects.length = 0
|
||||
|
||||
group.off(`${PLAY_PAUSE_BTN}:click`)
|
||||
group.off(`${NEXT_STEP_BTN}:click`)
|
||||
group.off(`${PRE_STEP_BTN}:click`)
|
||||
group.off(TIMEBAR_CONFIG_CHANGE)
|
||||
|
||||
this.sliceGroup.destroy()
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ import Handler from './handler';
|
||||
import ControllerBtn from './controllerBtn'
|
||||
import { IGraph } from '../../interface/graph';
|
||||
import { ShapeStyle } from '../../types';
|
||||
import { VALUE_CHANGE, TIMELINE_START, TIMEBAR_CONFIG_CHANGE,
|
||||
PLAY_PAUSE_BTN, NEXT_STEP_BTN, PRE_STEP_BTN, TIMELINE_END } from './constant';
|
||||
|
||||
/**
|
||||
* 一些默认的样式配置
|
||||
@ -40,12 +42,6 @@ export const TEXT_STYLE = {
|
||||
opacity: 0.45,
|
||||
};
|
||||
|
||||
export const TIMELINE_START = 'timelinestart';
|
||||
export const TIMELINE_CHANGE = 'timelinechange';
|
||||
export const TIMELINE_END = 'timelineend';
|
||||
|
||||
export const VALUE_CHANGE = 'valueChange';
|
||||
|
||||
export type ControllerCfg = Partial<{
|
||||
readonly x: number;
|
||||
readonly y: number;
|
||||
@ -491,27 +487,27 @@ export default class TrendTimeBar {
|
||||
|
||||
// 播放区按钮控制
|
||||
/** 播放/暂停事件 */
|
||||
this.group.on('playPauseBtn:click', () => {
|
||||
this.group.on(`${PLAY_PAUSE_BTN}:click`, () => {
|
||||
this.isPlay = !this.isPlay;
|
||||
this.currentHandler = this.maxHandlerShape
|
||||
this.changePlayStatus();
|
||||
})
|
||||
|
||||
// 处理前进一步的事件
|
||||
this.group.on('nextStepBtn:click', () => {
|
||||
this.group.on(`${NEXT_STEP_BTN}:click`, () => {
|
||||
this.currentHandler = this.maxHandlerShape
|
||||
this.updateStartEnd(0.01);
|
||||
this.updateUI()
|
||||
})
|
||||
|
||||
// 处理后退一步的事件
|
||||
this.group.on('preStepBtn:click', () => {
|
||||
this.group.on(`${PRE_STEP_BTN}:click`, () => {
|
||||
this.currentHandler = this.maxHandlerShape
|
||||
this.updateStartEnd(-0.01);
|
||||
this.updateUI()
|
||||
})
|
||||
|
||||
this.group.on('timebarConfigChanged', ({ type, speed }) => {
|
||||
this.group.on(TIMEBAR_CONFIG_CHANGE, ({ type, speed }) => {
|
||||
this.currentSpeed = speed
|
||||
this.currentMode = type
|
||||
if (type === 'single') {
|
||||
@ -798,4 +794,36 @@ export default class TrendTimeBar {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public destory() {
|
||||
this.graph.off(VALUE_CHANGE)
|
||||
|
||||
const group = this.group
|
||||
|
||||
const minHandleShapeGroup = group.find(g => g.get('name') === 'minHandlerShape')
|
||||
if (minHandleShapeGroup) {
|
||||
minHandleShapeGroup.off('minHandlerShape-handler:mousedown')
|
||||
minHandleShapeGroup.off('minHandlerShape-handler:touchstart');
|
||||
minHandleShapeGroup.destroy()
|
||||
}
|
||||
|
||||
const maxHandleShapeGroup = group.find(g => g.get('name') === 'maxHandlerShape')
|
||||
// 2. 右滑块的滑动
|
||||
if (maxHandleShapeGroup) {
|
||||
maxHandleShapeGroup.off('maxHandlerShape-handler:mousedown');
|
||||
maxHandleShapeGroup.off('maxHandlerShape-handler:touchstart');
|
||||
maxHandleShapeGroup.destroy()
|
||||
}
|
||||
|
||||
// 3. 前景选中区域
|
||||
this.foregroundShape.off('mousedown');
|
||||
this.foregroundShape.off('touchstart');
|
||||
this.foregroundShape.destroy()
|
||||
|
||||
group.off(`${PLAY_PAUSE_BTN}:click`)
|
||||
group.off(`${NEXT_STEP_BTN}:click`)
|
||||
group.off(`${PRE_STEP_BTN}:click`)
|
||||
group.off(TIMEBAR_CONFIG_CHANGE)
|
||||
group.destroy()
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import G6, { Graph } from '../../../src';
|
||||
import { IGraph } from '../../../src/interface/graph';
|
||||
import TimeBar from '../../../src/plugins/timeBar/timeBar';
|
||||
|
||||
let graph: IGraph = null;
|
||||
|
||||
@ -50,7 +49,7 @@ const TimeBarS = () => {
|
||||
console.log('timeBarData', timeBarData)
|
||||
|
||||
let count = 0;
|
||||
const timebar = new TimeBar({
|
||||
const timebar = new G6.TimeBar({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 500,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import G6 from '../../../src';
|
||||
import TimeBar from '../../../src/plugins/timeBar/timeBar'
|
||||
import TimeBar from '../../../src/plugins/timeBar'
|
||||
const div = document.createElement('div');
|
||||
div.id = 'timebar-plugin';
|
||||
document.body.appendChild(div);
|
||||
|
Loading…
Reference in New Issue
Block a user