mirror of
https://gitee.com/antv/g6.git
synced 2024-12-02 11:48:29 +08:00
feat: slice type timebar and tooltp for timebar.
This commit is contained in:
parent
ef57f7909a
commit
9e03a2d359
486
src/plugins/timeBar/timeBarSlice.ts
Normal file
486
src/plugins/timeBar/timeBarSlice.ts
Normal file
@ -0,0 +1,486 @@
|
||||
/**
|
||||
* 基于 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')
|
||||
}
|
||||
}
|
161
src/plugins/timeBar/timeBarTooltip.ts
Normal file
161
src/plugins/timeBar/timeBarTooltip.ts
Normal file
@ -0,0 +1,161 @@
|
||||
import { IGroup, IShape } from '@antv/g-base';
|
||||
import createDOM from '@antv/dom-util/lib/create-dom'
|
||||
import { dataToPath, linePathToAreaPath } from './path';
|
||||
import { isNumber, isString } from '@antv/util';
|
||||
import modifyCSS from '@antv/dom-util/lib/modify-css';
|
||||
|
||||
export const BACKGROUND_STYLE = {
|
||||
opacity: 0.5,
|
||||
fill: '#000',
|
||||
};
|
||||
export const ARROW_STYLE = {
|
||||
opacity: 0.5,
|
||||
fill: '#000',
|
||||
r: 5
|
||||
};
|
||||
export const TEXT_STYLE = {
|
||||
fill: '#fff',
|
||||
fontSize: 12
|
||||
};
|
||||
|
||||
interface ArrowStyle {
|
||||
r?: number,
|
||||
fill?: string,
|
||||
stroke?: string,
|
||||
lineWidth?: number
|
||||
};
|
||||
|
||||
export interface TimeBarTooltipCfg {
|
||||
readonly container: HTMLElement | string;
|
||||
readonly className?: string;
|
||||
// 位置大小
|
||||
readonly x?: number;
|
||||
readonly y?: number;
|
||||
readonly width?: number;
|
||||
readonly height?: number;
|
||||
readonly padding?: number[];
|
||||
// 文本
|
||||
readonly text?: string;
|
||||
// 样式
|
||||
readonly backgroundColor?: string;
|
||||
readonly textColor?: string;
|
||||
readonly opacity?: number;
|
||||
readonly fontSize?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 缩略趋势图
|
||||
*/
|
||||
export default class TimeBarTooltip {
|
||||
private container: HTMLElement | string;
|
||||
private className: string;
|
||||
// 生成的 shape
|
||||
public backgroundDOM: HTMLElement;
|
||||
public arrowDOM: HTMLElement;
|
||||
// 位置大小配置
|
||||
private x: number;
|
||||
private y: number;
|
||||
// style
|
||||
private padding: number[];
|
||||
private backgroundColor: string;
|
||||
private textColor: string;
|
||||
private parentHeight: number;
|
||||
private parentWidth: number;
|
||||
private opacity: number;
|
||||
private fontSize: number;
|
||||
|
||||
private text: string;
|
||||
|
||||
constructor(cfg?: TimeBarTooltipCfg) {
|
||||
const {
|
||||
x = 0,
|
||||
y = 0,
|
||||
container,
|
||||
text,
|
||||
padding = [4, 4, 4, 4],
|
||||
className = 'g6-component-timebar-tooltip',
|
||||
backgroundColor = '#000',
|
||||
textColor = '#fff',
|
||||
opacity = 0.8,
|
||||
fontSize = 12
|
||||
} = cfg;
|
||||
|
||||
this.container = container;
|
||||
this.className = className;
|
||||
this.backgroundColor = backgroundColor;
|
||||
this.textColor = textColor;
|
||||
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.text = text;
|
||||
this.padding = padding;
|
||||
this.opacity = opacity;
|
||||
this.fontSize = fontSize;
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* 首次渲染
|
||||
* @private
|
||||
*/
|
||||
private render() {
|
||||
const self = this;
|
||||
const { className, x, y, backgroundColor,
|
||||
textColor, text, padding, opacity, fontSize } = self;
|
||||
|
||||
let parentNode: string | HTMLElement = self.container;
|
||||
const container: HTMLElement = createDOM(
|
||||
`<div class='${className}' style="position: sticky; width: fit-content; height: fit-content; opacity: ${opacity}"></div>`,
|
||||
);
|
||||
if (isString(parentNode)) {
|
||||
parentNode = document.getElementById(parentNode) as HTMLElement;
|
||||
}
|
||||
parentNode.appendChild(container);
|
||||
self.parentHeight = parentNode.offsetHeight;
|
||||
self.parentWidth = parentNode.offsetWidth;
|
||||
modifyCSS(container, { visibility: 'hidden', top: 0, left: 0 });
|
||||
|
||||
const background: HTMLElement = createDOM(`
|
||||
<div style='position: absolute; white-space:nowrap; background-color: ${backgroundColor}; font-size: ${fontSize}px; border-radius: 4px; width: fit-content; height: fit-content; color: ${textColor}; padding: ${padding[0]}px ${padding[1]}px ${padding[2]}px ${padding[3]}px'></div>`);
|
||||
background.innerHTML = text;
|
||||
container.appendChild(background);
|
||||
self.backgroundDOM = background;
|
||||
|
||||
const arrow: HTMLElement = createDOM(
|
||||
`<div style='position: absolute; width: 0px; height: 0px; border-left: 5px solid transparent; border-right: 5px solid transparent; border-top: 10px solid ${backgroundColor}'></div>`
|
||||
)
|
||||
container.appendChild(arrow);
|
||||
self.arrowDOM = arrow;
|
||||
|
||||
|
||||
self.container = container;
|
||||
}
|
||||
|
||||
public show(cfg) {
|
||||
const self = this;
|
||||
const { text, x, y, clientX, clientY } = cfg;
|
||||
self.backgroundDOM.innerHTML = text;
|
||||
const backgroundWidth = self.backgroundDOM.offsetWidth;
|
||||
const backgroundHeight = self.backgroundDOM.offsetHeight;
|
||||
const arrowWidth = self.arrowDOM.offsetWidth;
|
||||
const arrowHeight = self.arrowDOM.offsetHeight;
|
||||
const containerHeight = backgroundHeight + arrowHeight;
|
||||
modifyCSS(self.container as HTMLElement, { marginTop: `${-self.parentHeight - containerHeight - 8}px`, left: `${clientX}px`, visibility: 'visible', });
|
||||
|
||||
modifyCSS(self.backgroundDOM, { marginLeft: `${-backgroundWidth / 2}px` })
|
||||
modifyCSS(self.arrowDOM, { marginLeft: `${-arrowWidth / 2}px`, top: `${backgroundHeight}px` });
|
||||
|
||||
|
||||
const bbox = (self.backgroundDOM as HTMLElement).getBoundingClientRect();
|
||||
if (bbox.left < 0) {
|
||||
modifyCSS(self.backgroundDOM, { marginLeft: `${-backgroundWidth / 2 - bbox.left}px` })
|
||||
} else if (bbox.right > self.parentWidth) {
|
||||
modifyCSS(self.backgroundDOM, { marginLeft: `${-backgroundWidth / 2 - bbox.right + self.parentWidth + 12}px` })
|
||||
}
|
||||
}
|
||||
public hide() {
|
||||
const self = this;
|
||||
//modifyCSS(self.container as HTMLElement, { top: 0, left: 0, visibility: 'hidden', });
|
||||
}
|
||||
}
|
123
stories/Plugins/component/timebar-slice.tsx
Normal file
123
stories/Plugins/component/timebar-slice.tsx
Normal file
@ -0,0 +1,123 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import G6, { Graph } from '../../../src';
|
||||
import { IGraph } from '../../../src/interface/graph';
|
||||
import TimeBarSlice from '../../../src/plugins/timeBar/timeBarSlice';
|
||||
|
||||
let graph: IGraph = null;
|
||||
|
||||
const TimeBarS = () => {
|
||||
const container = React.useRef();
|
||||
|
||||
const data = {
|
||||
nodes: [],
|
||||
edges: [],
|
||||
};
|
||||
|
||||
for (let i = 1; i < 60; i++) {
|
||||
const id = `node-${i}`;
|
||||
const month = i < 30 ? '01' : '02';
|
||||
const day = i % 30 < 10 ? `0${i % 30}` : `${i % 30}`;
|
||||
data.nodes.push({
|
||||
id,
|
||||
date: parseInt(`2020${month}${day}`),
|
||||
value: Math.round(Math.random() * 300),
|
||||
label: parseInt(`2020${i}`),
|
||||
});
|
||||
|
||||
data.edges.push({
|
||||
source: `node-${Math.round(Math.random() * 90)}`,
|
||||
target: `node-${Math.round(Math.random() * 90)}`,
|
||||
});
|
||||
data.edges.push({
|
||||
source: `node-${Math.round(Math.random() * 90)}`,
|
||||
target: `node-${Math.round(Math.random() * 90)}`,
|
||||
});
|
||||
}
|
||||
|
||||
const timeBarData = [];
|
||||
|
||||
const nodeSize = 20;
|
||||
|
||||
for (let i = 1; i < 60; i++) {
|
||||
const month = i < 30 ? '01' : '02';
|
||||
const day = i % 30 < 10 ? `0${i % 30}` : `${i % 30}`;
|
||||
timeBarData.push({
|
||||
date: parseInt(`2020${month}${day}`),
|
||||
value: Math.round(Math.random() * 300),
|
||||
});
|
||||
}
|
||||
|
||||
console.log('timeBarData', timeBarData)
|
||||
|
||||
let count = 0;
|
||||
const timebar = new TimeBarSlice({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 500,
|
||||
height: 42,
|
||||
padding: 2,
|
||||
trend: {
|
||||
data: timeBarData,
|
||||
isArea: true,
|
||||
lineStyle: {
|
||||
stroke: 'green',
|
||||
lineWidth: 3
|
||||
}
|
||||
},
|
||||
slider: {
|
||||
textStyle: {
|
||||
fill: 'red',
|
||||
fontSize: 16,
|
||||
},
|
||||
},
|
||||
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)}`;
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!graph) {
|
||||
const toolbar = new G6.ToolBar();
|
||||
graph = new Graph({
|
||||
container: container.current as string | HTMLElement,
|
||||
width: 500,
|
||||
height: 500,
|
||||
plugins: [timebar],
|
||||
layout: {
|
||||
type: 'force',
|
||||
preventOverlap: true,
|
||||
},
|
||||
defaultNode: {
|
||||
size: nodeSize,
|
||||
type: 'circle',
|
||||
style: {
|
||||
fill: '#DEE9FF',
|
||||
stroke: '#5B8FF9',
|
||||
},
|
||||
},
|
||||
modes: {
|
||||
default: ['drag-node'],
|
||||
},
|
||||
});
|
||||
graph.data(data);
|
||||
graph.render();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ left: '100px' }} ref={container}></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TimeBarS;
|
90
stories/Plugins/component/timebar.tsx
Normal file
90
stories/Plugins/component/timebar.tsx
Normal file
@ -0,0 +1,90 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import G6, { Graph } from '../../../src';
|
||||
import { IGraph } from '../../../src/interface/graph';
|
||||
|
||||
let graph: IGraph = null;
|
||||
|
||||
const TimeBar = () => {
|
||||
const container = React.useRef();
|
||||
|
||||
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 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({
|
||||
width: 600,
|
||||
timebar: {
|
||||
width: 550,
|
||||
trend: {
|
||||
data: timeBarData,
|
||||
isArea: false,
|
||||
smooth: true,
|
||||
},
|
||||
start: 0.4,
|
||||
end: 0.5,
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!graph) {
|
||||
const toolbar = new G6.ToolBar();
|
||||
graph = new Graph({
|
||||
container: container.current as string | HTMLElement,
|
||||
width: 500,
|
||||
height: 500,
|
||||
plugins: [timebar],
|
||||
layout: {
|
||||
type: 'force',
|
||||
preventOverlap: true,
|
||||
},
|
||||
defaultNode: {
|
||||
size: nodeSize,
|
||||
type: 'circle',
|
||||
style: {
|
||||
fill: '#DEE9FF',
|
||||
stroke: '#5B8FF9',
|
||||
},
|
||||
},
|
||||
modes: {
|
||||
default: ['drag-node'],
|
||||
},
|
||||
});
|
||||
graph.data(data);
|
||||
graph.render();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ left: '100px' }} ref={container}></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TimeBar;
|
@ -10,6 +10,8 @@ import Tooltip from './component/tooltip';
|
||||
import ContextMenu from './component/comtextMenu';
|
||||
import ToolBar from './component/toolbar';
|
||||
import EdgeFilter from './component/edge-filter';
|
||||
import TimeBar from './component/timebar';
|
||||
import TimeBarS from './component/timebar-slice';
|
||||
|
||||
export default { title: 'Plugins' };
|
||||
|
||||
@ -24,4 +26,6 @@ storiesOf('Plugins', module)
|
||||
.add('tooltip', () => <Tooltip />)
|
||||
.add('context menu', () => <ContextMenu />)
|
||||
.add('tool bar', () => <ToolBar />)
|
||||
.add('edge filter', () => <EdgeFilter />);
|
||||
.add('edge filter', () => <EdgeFilter />)
|
||||
.add('time bar', () => <TimeBar />)
|
||||
.add('time bar slice', () => <TimeBarS />);
|
||||
|
Loading…
Reference in New Issue
Block a user