Skip to content

Commit 28bbd9c

Browse files
authored
Chart: reduce flicking when async templates are used for label and series visibility is changed at runtime (T1232664, partial fix) (DevExpress#28878) (DevExpress#28940)
1 parent fb5775f commit 28bbd9c

File tree

3 files changed

+112
-7
lines changed

3 files changed

+112
-7
lines changed

packages/devextreme/js/__internal/viz/core/m_base_widget.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,11 @@ const baseWidget = isServerSide ? getEmptyComponent() : (DOMComponent as any).in
271271

272272
let syncRendering = true;
273273
when.apply(this, items).done(() => {
274+
const isGroupInDom = !groups[0]?.element || !!$(groups[0].element.closest('svg')).length;
275+
if (!isGroupInDom) {
276+
return;
277+
}
278+
274279
if (syncRendering) {
275280
this._setGroupsVisibility(groups, 'visible');
276281
return;

packages/devextreme/js/viz/axes/tick.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { isDefined } from '../../core/utils/type';
22
import { extend } from '../../core/utils/extend';
3-
import { Deferred } from '../../core/utils/deferred';
3+
import { Deferred, when } from '../../core/utils/deferred';
44

55
function getPathStyle(options) {
66
return { stroke: options.color, 'stroke-width': options.width, 'stroke-opacity': options.opacity, opacity: 1 };
@@ -49,12 +49,14 @@ function createTick(axis, renderer, tickOptions, gridOptions, skippedCategory, s
4949
this.labelCoords = axis._getTranslatedValue(value);
5050
},
5151
saveCoords() {
52-
this._lastStoredCoordinates = {
53-
coords: this._storedCoords,
54-
labelCoords: this._storedLabelsCoords
55-
};
56-
this._storedCoords = this.coords;
57-
this._storedLabelsCoords = this.templateContainer ? this._getTemplateCoords() : this.labelCoords;
52+
when(this._templateDef).done(() => {
53+
this._lastStoredCoordinates = {
54+
coords: this._storedCoords,
55+
labelCoords: this._storedLabelsCoords
56+
};
57+
this._storedCoords = this.coords;
58+
this._storedLabelsCoords = this.templateContainer ? this._getTemplateCoords() : this.labelCoords;
59+
});
5860
},
5961
resetCoordinates() {
6062
if(this._lastStoredCoordinates) {

packages/devextreme/testing/tests/DevExpress.viz.charts/chart.integration.tests.js

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4866,3 +4866,101 @@ QUnit.test('Set bar width (via interval) for each pane (T1000672)', function(ass
48664866
assert.ok(chart.series[0].getPoints()[0].width > 30);
48674867
assert.ok(chart.series[2].getPoints()[0].width < 10);
48684868
});
4869+
4870+
QUnit.module('React async templates rendering', {
4871+
beforeEach: function() {
4872+
this.clock = sinon.useFakeTimers();
4873+
this.templateTimeout = 1000;
4874+
this.integrationOptions = {
4875+
templates: {
4876+
labelTemplate: {
4877+
render: ({ model, container, onRendered }) => {
4878+
const deferred = $.Deferred();
4879+
setTimeout(() => {
4880+
const content = $(`<svg overflow="visible"><text fill="#767676" x="30" y="59" text-anchor="middle">${model.valueText}</text></svg>`);
4881+
container.append(content);
4882+
onRendered();
4883+
deferred.resolve();
4884+
}, this.templateTimeout);
4885+
return deferred.promise();
4886+
}
4887+
},
4888+
}
4889+
};
4890+
},
4891+
afterEach: function() {
4892+
this.clock.restore();
4893+
}
4894+
}, () => {
4895+
QUnit.test('no visual slide animation should be shown when label templates are rendered asynchroniously (T1232664)', function(assert) {
4896+
const chart = $('#chartContainer').dxChart({
4897+
dataSource: [{ arg: 1, val: 10 }],
4898+
templatesRenderAsynchronously: true,
4899+
animation: { enabled: true },
4900+
commonSeriesSettings: {
4901+
type: 'bar',
4902+
argumentField: 'arg',
4903+
},
4904+
series: [{
4905+
valueField: 'val',
4906+
}],
4907+
integrationOptions: this.integrationOptions,
4908+
argumentAxis: {
4909+
label: {
4910+
template: 'labelTemplate',
4911+
},
4912+
},
4913+
}).dxChart('instance');
4914+
4915+
const axis = chart.getArgumentAxis();
4916+
const animationSpy = sinon.spy(axis._majorTicks[0].templateContainer, 'animate');
4917+
4918+
chart.render({ animate: true });
4919+
4920+
this.clock.tick(this.templateTimeout);
4921+
4922+
assert.strictEqual(animationSpy.callCount, 0, 'slide animation is not run because no label coordinates were saved before template is rendered');
4923+
});
4924+
4925+
QUnit.test('label templates should be visible after rendering even if additional render interrupted first render, async templates in React 17 (T1232664)', function(assert) {
4926+
const chart = $('#chartContainer').dxChart({
4927+
dataSource: [{ arg: 1, val: 10 }],
4928+
templatesRenderAsynchronously: true,
4929+
animation: { enabled: true },
4930+
commonSeriesSettings: {
4931+
type: 'bar',
4932+
argumentField: 'arg',
4933+
},
4934+
series: [{
4935+
valueField: 'val',
4936+
}],
4937+
integrationOptions: this.integrationOptions,
4938+
argumentAxis: {
4939+
label: {
4940+
template: 'labelTemplate',
4941+
},
4942+
},
4943+
}).dxChart('instance');
4944+
4945+
this.clock.tick(this.templateTimeout);
4946+
4947+
const axis = chart.getArgumentAxis();
4948+
const stub = sinon
4949+
.stub(axis, 'getTemplatesGroups')
4950+
.onCall(0)
4951+
.returns([{ element: $(document.createElementNS('http://www.w3.org/2000/svg', 'g')), attr: () => {} }]);
4952+
4953+
chart.render({ force: true });
4954+
stub.callThrough();
4955+
4956+
4957+
chart._applyingChanges = true;
4958+
this.clock.tick(this.templateTimeout);
4959+
chart._applyingChanges = false;
4960+
4961+
chart.render({ force: true });
4962+
this.clock.tick(this.templateTimeout);
4963+
4964+
assert.strictEqual(axis._majorTicks[0].templateContainer.attr('visibility'), 'visible', 'label is visible');
4965+
});
4966+
});

0 commit comments

Comments
 (0)