VChart
VChart copied to clipboard
[Bug] 条形图动画更新后,出现空白渲染
Version
2.0.1
Link to Minimal Reproduction
vscreen
Steps to Reproduce
const spec = {
type: 'bar',
theme: {},
xField: ['10002'],
yField: ['OCxMNY4j86cy', '10001'],
direction: 'horizontal',
sortDataByAxis: true,
seriesField: '30001',
padding: {
left: 6,
right: 6,
top: 6,
bottom: 6
},
labelLayout: 'region',
data: [
{
id: 'data',
values: [
{
'10001': 'Profit',
'10002': 20,
'10003': 'XU0wVG43cGL7',
'30001': '2023',
OCxMNY4j86cy: 'Labels',
XU0wVG43cGL7: 20,
vGTOlXqnCrc3: '2023'
},
{
'10001': 'Profit',
'10002': 44,
'10003': 'XU0wVG43cGL7',
'30001': '2022',
OCxMNY4j86cy: 'Labels',
XU0wVG43cGL7: 44,
vGTOlXqnCrc3: '2022'
},
{
'10001': 'Profit',
'10002': 15,
'10003': 'XU0wVG43cGL7',
'30001': '2023',
OCxMNY4j86cy: 'Tables',
XU0wVG43cGL7: 15,
vGTOlXqnCrc3: '2023'
},
{
'10001': 'Profit',
'10002': 20,
'10003': 'XU0wVG43cGL7',
'30001': '2022',
OCxMNY4j86cy: 'Tables',
XU0wVG43cGL7: 20,
vGTOlXqnCrc3: '2022'
},
{
'10001': 'Profit',
'10002': 50,
'10003': 'XU0wVG43cGL7',
'30001': '2023',
OCxMNY4j86cy: 'Storage',
XU0wVG43cGL7: 50,
vGTOlXqnCrc3: '2023'
},
{
'10001': 'Profit',
'10002': 65,
'10003': 'XU0wVG43cGL7',
'30001': '2022',
OCxMNY4j86cy: 'Storage',
XU0wVG43cGL7: 65,
vGTOlXqnCrc3: '2022'
},
{
'10001': 'Profit',
'10002': 15,
'10003': 'XU0wVG43cGL7',
'30001': '2023',
OCxMNY4j86cy: 'Furn',
XU0wVG43cGL7: 15,
vGTOlXqnCrc3: '2023'
},
{
'10001': 'Profit',
'10002': 40,
'10003': 'XU0wVG43cGL7',
'30001': '2022',
OCxMNY4j86cy: 'Furn',
XU0wVG43cGL7: 40,
vGTOlXqnCrc3: '2022'
},
{
'10001': 'Profit',
'10002': 57,
'10003': 'XU0wVG43cGL7',
'30001': '2023',
OCxMNY4j86cy: 'Art',
XU0wVG43cGL7: 57,
vGTOlXqnCrc3: '2023'
},
{
'10001': 'Profit',
'10002': 35,
'10003': 'XU0wVG43cGL7',
'30001': '2022',
OCxMNY4j86cy: 'Art',
XU0wVG43cGL7: 35,
vGTOlXqnCrc3: '2022'
}
],
fields: {
'10001': {
alias: '指标名称'
},
'10002': {
alias: '指标值'
},
'30001': {
alias: '图例项',
domain: null,
sortIndex: null,
lockStatisticsByDomain: true
},
OCxMNY4j86cy: {
alias: 'Product',
domain: ['Art', 'Furn', 'Labels', 'Storage', 'Tables'],
sortIndex: 0,
lockStatisticsByDomain: true
},
vGTOlXqnCrc3: {
alias: 'Year'
},
XU0wVG43cGL7: {
alias: 'Profit'
}
}
}
],
stackInverse: true,
axes: [
{
type: 'band',
tick: {
style: {
strokeOpacity: 0.2
}
},
grid: {
visible: false,
style: {
zIndex: 150,
stroke: '#FFFFFF',
lineWidth: 1,
lineDash: []
}
},
orient: 'left',
visible: true,
domainLine: {
visible: false,
style: {
lineWidth: 1,
stroke: '#d5d7e2'
}
},
title: {
visible: false,
space: 5,
text: '',
style: {
fontSize: 12,
fill: 'rgba(255,255,255,0.5)',
fontFamily: 'D-DIN',
fontWeight: 'normal'
}
},
maxHeight: null,
autoIndent: false,
sampling: false,
zIndex: 200,
label: {
visible: true,
space: 8,
style: {
fontSize: 12,
fill: 'rgba(255,255,255,0.7)',
angle: 0,
fontFamily: 'D-DIN',
fontWeight: 'normal',
direction: 'horizontal',
maxLineWidth: 174
},
autoHide: true,
autoHideMethod: 'greedy',
flush: true,
lastVisible: true,
autoHideSeparation: 4
},
hover: true,
background: {
visible: true,
state: {
hover: {
fillOpacity: 0.08,
fill: '#141414'
},
hover_reverse: {
fillOpacity: 0.08,
fill: '#141414'
}
}
},
paddingInner: 0.13,
paddingOuter: 0.13,
ticks: true,
maxWidth: 180
},
{
type: 'linear',
tick: {
size: 4,
visible: true
},
niceType: 'rough',
zIndex: 200,
grid: {
visible: true
},
orient: 'bottom',
visible: true,
domainLine: {
visible: false,
style: {
lineWidth: 1,
stroke: '#d5d7e2'
}
},
title: {
visible: false,
text: '',
space: 8,
style: {
fontSize: 12,
fill: 'rgba(255,255,255,0.5)',
fontFamily: 'D-DIN',
fontWeight: 'normal'
}
},
autoIndent: false,
sampling: false,
label: {
visible: true,
space: 4,
flush: true,
padding: 0,
style: {
fontSize: 12,
maxLineWidth: 174,
fill: 'rgba(255,255,255,0.7)',
angle: 0,
fontFamily: 'D-DIN',
fontWeight: 'normal',
dy: 0,
direction: 'horizontal'
},
autoHide: true,
autoHideMethod: 'greedy',
autoHideSeparation: 4,
rotateAngle: [null],
labelOverlap: 'custom',
tighten: false
},
background: {
visible: true,
state: {
hover: {
fillOpacity: 0.08,
fill: '#141414'
},
hover_reverse: {
fillOpacity: 0.08,
fill: '#141414'
}
}
},
innerOffset: {
top: 0
},
zero: true,
nice: true,
paddingInner: 0.13,
paddingOuter: 0.13,
ticks: true
},
{
type: 'linear',
tick: {
size: 4,
visible: true
},
niceType: 'rough',
zIndex: 200,
grid: {
visible: false
},
orient: 'top',
visible: true,
domainLine: {
visible: false
},
title: {
visible: false
},
autoIndent: false,
sampling: false,
label: {
visible: true,
space: 4,
flush: true,
padding: 0,
style: {
visible: false,
fontSize: 12,
maxLineWidth: 174,
fill: 'rgba(255,255,255,0.7)',
angle: 0,
fontFamily: 'D-DIN',
fontWeight: 'normal',
dy: 0,
direction: 'horizontal'
},
autoHide: true,
autoHideMethod: 'greedy',
autoHideSeparation: 4,
rotateAngle: [null],
labelOverlap: 'custom',
tighten: false
},
background: {
visible: true,
state: {
hover: {
fillOpacity: 0.08,
fill: '#141414'
},
hover_reverse: {
fillOpacity: 0.08,
fill: '#141414'
}
}
},
innerOffset: {
top: 0
},
zero: true,
nice: true,
paddingInner: 0.13,
paddingOuter: 0.13,
ticks: true
}
],
color: {
field: '30001',
type: 'ordinal',
range: ['rgb(0,110,255)', 'rgb(0,229,229)'],
specified: {},
domain: ['2023', '2022']
},
colorGradient: {
type: 'linear',
x0: {
field: '30001',
type: 'ordinal',
range: [0, 0]
},
y0: {
field: '30001',
type: 'ordinal',
range: [1, 1]
},
x1: {
field: '30001',
type: 'ordinal',
range: [1, 1]
},
y1: {
field: '30001',
type: 'ordinal',
range: [1, 1]
},
stops: [
{
offset: 0,
color: {
field: '30001',
type: 'ordinal',
range: ['rgba(0,110,255,0.2)', 'rgba(0,229,229,0.2)']
}
},
{
offset: 1,
color: {
field: '30001',
type: 'ordinal',
range: ['rgb(0,110,255)', 'rgb(0,229,229)']
}
}
]
},
legends: [
{
type: 'discrete',
visible: true,
id: 'legend-discrete',
orient: 'top',
position: 'end',
layoutType: 'normal',
maxRow: 1,
title: {
textStyle: {
fontSize: 12,
fill: 'rgba(255,255,255,0.7)'
}
},
layoutLevel: 70,
item: {
focus: false,
focusIconStyle: {
size: 14
},
maxWidth: 400,
spaceRow: 0,
spaceCol: 0,
padding: {
left: 10,
right: 0,
top: 0,
bottom: 5
},
background: {
visible: false,
style: {
fillOpacity: 0.001
}
},
label: {
style: {
fontSize: 12,
fill: 'rgba(255,255,255,0.7)',
fontFamily: 'D-DIN',
fontWeight: 'normal'
},
state: {
unSelected: {
fillOpacity: 0.2
}
}
},
shape: {
style: {
lineWidth: 0,
symbolType: 'square',
size: 12,
fillOpacity: 1,
width: 12,
height: 7.416
}
}
},
pager: {
layout: 'horizontal',
padding: 0,
textStyle: {
fill: 'rgba(255,255,255,0.7)'
},
space: 0,
handler: {
preShape: 'triangleLeft',
nextShape: 'triangleRight',
style: {
fill: 'rgba(255,255,255,0.7)',
cursor: 'pointer',
fillOpaity: 1
},
state: {
disable: {
fill: 'rgba(255,255,255,0.7)',
cursor: 'not-allowed',
fillOpacity: 0.45
}
}
}
},
alignSelf: 'end',
padding: {
left: 10,
right: 0,
top: 0,
bottom: 12
}
}
],
label: {
visible: false,
offset: 3,
overlap: {
hideOnHit: true,
avoidBaseMark: false,
strategy: [
{
type: 'position',
position: []
}
],
clampForce: true
},
style: {
fontSize: 12,
fontFamily: 'D-DIN',
fontWeight: 'normal',
zIndex: 400,
lineHeight: '100%',
boundsPadding: [1, 0, 0, 0],
fill: 'rgba(255,255,255,1)',
strokeOpacity: 0
},
position: 'outside',
smartInvert: false,
fontWeight: 'normal',
animation: false,
avoidBaseMark: true
},
tooltip: {
visible: true,
renderMode: 'canvas',
mark: {
visible: true
},
style: {
panel: {
padding: {
top: 5,
bottom: 10,
left: 10,
right: 10
},
backgroundColor: 'rgba(8, 28, 48, 0.95)',
border: {
color: '#CFCFCF',
width: 0,
radius: 2
},
shadow: {
x: 0,
y: 4,
blur: 12,
spread: 0,
color: 'rgba(0, 0, 0, 0.2)'
}
},
titleLabel: {
fontSize: 14,
fontColor: '#FFF',
fontWeight: 'bold',
fontFamily: 'D-DIN',
align: 'left',
lineHeight: 18
},
keyLabel: {
fontSize: 12,
fontColor: 'rgba(255,255,255,0.65)',
fontWeight: 'normal',
fontFamily: 'SourceHanSansCN-Normal',
align: 'left',
lineHeight: 18
},
valueLabel: {
fontSize: 12,
fontColor: '#FFF',
fontWeight: 'normal',
fontFamily: 'D-DIN',
align: 'right',
lineHeight: 18
},
shape: {
size: 10,
spacing: 10,
shapeLineWidth: 0
},
spaceRow: 10
},
dimension: {
visible: true
}
},
hover: {
enable: true
},
select: {
enable: true
},
bar: {
state: {
hover: {
cursor: 'pointer',
fillOpacity: 0.8,
stroke: '#58595B',
lineWidth: 1,
zIndex: 500
},
selected: {
cursor: 'pointer',
fillOpacity: 1,
stroke: '#58595B',
lineWidth: 1
},
selected_reverse: {
fillOpacity: 0.3,
lineWidth: 0.3
}
},
style: {
cornerRadius: 0,
fill: {
gradient: 'linear',
stops: [
{
offset: 0
},
{
offset: 1
}
]
},
lineWidth: 2,
stroke: {
gradient: 'linear',
stops: [
{
offset: 0
},
{
offset: 1
}
]
}
}
},
region: [
{
clip: true
}
],
background: 'rgba(0, 0, 0, 1)',
markLine: [],
animation: true,
crosshair: {
yField: {
visible: true,
line: {
type: 'rect',
style: {
fillOpacity: 1,
fill: 'rgba(80,156,255,0.1)'
}
}
},
gridZIndex: 100,
xField: {
line: {
style: {
fillOpacity: 1,
fill: 'rgba(80,156,255,0.1)'
}
},
visible: false
}
},
morph: {
enable: false
},
fillOpacity: {
fillOpacity: 1
},
axesPadding: true,
plotLayout: {
clip: false
},
scales: [
{
id: 'gradientFillStop0_data',
type: 'ordinal',
range: [
'rgba(0,110,255,0.2)',
'rgba(0,229,229,0.2)',
'rgba(46,85,234,0.2)',
'rgba(184,231,254,0.2)',
'rgba(0,214,137,0.2)',
'rgba(183,249,245,0.2)',
'rgba(251,204,113,0.2)',
'rgba(244,110,80,0.2)'
],
domain: ['2023', '2022']
},
{
id: 'gradientFillStop1_data',
type: 'ordinal',
range: [
'rgb(0,110,255)',
'rgb(0,229,229)',
'rgb(46,85,234)',
'rgb(184,231,254)',
'rgb(0,214,137)',
'rgb(183,249,245)',
'rgb(251,204,113)',
'rgb(244,110,80)'
],
domain: ['2023', '2022']
},
{
id: 'gradientStrokeStop0_data',
type: 'ordinal',
range: [
'rgba(51, 139, 255, 0.2)',
'rgba(25, 255, 255, 0.2)',
'rgba(92, 123, 239, 0.2)',
'rgba(234, 248, 255, 0.2)',
'rgba(10, 255, 167, 0.2)',
'rgba(230, 253, 252, 0.2)',
'rgba(252, 222, 163, 0.2)',
'rgba(247, 150, 128, 0.2)'
],
domain: ['2023', '2022']
},
{
id: 'gradientStrokeStop1_data',
type: 'ordinal',
range: [
'rgba(51, 139, 255, 1)',
'rgba(25, 255, 255, 1)',
'rgba(92, 123, 239, 1)',
'rgba(234, 248, 255, 1)',
'rgba(10, 255, 167, 1)',
'rgba(230, 253, 252, 1)',
'rgba(252, 222, 163, 1)',
'rgba(247, 150, 128, 1)'
],
domain: ['2023', '2022']
},
{
id: 'gradientX1_data',
type: 'ordinal',
range: [1, 1, 1, 1, 1, 1, 1, 1],
domain: ['2023', '2022']
},
{
id: 'gradientY1_data',
type: 'ordinal',
range: [0, 0, 0, 0, 0, 0, 0, 0],
domain: ['2023', '2022']
}
],
barWidth: '60%',
barBackground: {
fieldLevel: 1,
visible: false,
interactive: false,
style: {
cornerRadius: 0,
fill: 'rgba(255,255,255,1)',
fillOpacity: 0.1
}
},
animationAppear: {
bar: {
type: 'growWidthIn',
oneByOne: false,
controlOptions: {
immediatelyApply: true
},
easing: 'circInOut',
duration: 1000
}
},
animationNormal: {
bar: [
{
type: 'growWidthIn',
oneByOne: false,
controlOptions: {
immediatelyApply: false
},
startTime: 5000,
easing: 'circInOut',
duration: 1000,
delayAfter: 6000,
loop: true
},
{
loop: true,
startTime: 5000,
delay: 1000,
delayAfter: 5000,
duration: 1000,
easing: 'circInOut',
customParameters: {
isHorizontal: true,
attribute: {
fill: {
gradient: 'linear',
x0: 0,
x1: 1,
y1: 0,
y0: 0,
stops: [
{
offset: 0,
color: 'rgba(255,255,255,0)'
},
{
offset: 1,
color: 'rgba(255,255,255,0.3)'
}
]
},
blur: 0,
shadowColor: 'rgba(0,0,0,0)'
}
}
}
]
},
animationEnter: {
bar: {
easing: 'circInOut',
duration: 1000
}
},
animationUpdate: {
bar: {
easing: 'circInOut',
duration: 1000
}
},
animationExit: {},
hash: 'e89bf72b22f7068b85e9ce618e06ce66',
line: {
style: {
stroke: {
gradient: 'linear',
stops: [
{
offset: 0
},
{
offset: 1
}
]
}
}
},
width: 400,
height: 225
};
const getAnimationSpec = spec => {
if (!spec) {
return {};
}
return {
animation: spec.animation,
animationAppear: spec.animationAppear,
animationUpdate: spec.animationUpdate,
animationEnter: spec.animationEnter,
animationExit: spec.animationExit
};
};
const chartUpdate = (chartInstance, preChartSpec, curSpec) => {
const preAnimationSpec = getAnimationSpec(preChartSpec);
const curAnimationSpec = getAnimationSpec(curSpec);
const reAnimate = JSON.stringify(preAnimationSpec) !== JSON.stringify(curAnimationSpec);
// VChart图表分批updateSpec, 流程待图表库底层优化
// step1. update除data外的spec
// step2. updateData
const specWithoutDataChange = {
...curSpec,
data: preChartSpec?.data ?? [
{
id: 'data',
values: []
}
]
};
const curData = curSpec.data;
chartInstance.updateSpec(specWithoutDataChange, false, undefined); // remake, 无动画
chartInstance.updateFullDataSync(curData, true);
};
const vchart = new VChart(spec, { dom: CONTAINER_ID });
vchart.renderSync();
vchart.updateSpec(spec);
vchart.reRunNormalAnimation();
setTimeout(() => {
chartUpdate(vchart, spec, spec);
vchart.reRunNormalAnimation()
}, 2000);
// Just for the convenience of console debugging, DO NOT COPY!
window['vchart'] = vchart;
Current Behavior
Expected Behavior
两次数据更新前,不出现空白帧
Environment
- OS:
- Browser:
- Framework:
Any additional comments?
No response