Skip to content

Commit d6aa201

Browse files
authored
fix(color): Fix color update on gradient
Update gradient color when .data.colors() is called Fix #4048
1 parent aa71068 commit d6aa201

File tree

3 files changed

+297
-5
lines changed

3 files changed

+297
-5
lines changed

src/Chart/api/data.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ extend(data, {
119119

120120
/**
121121
* Get and set colors of the data loaded in the chart.
122+
* - **NOTE:** If gradient option is set, the color update will affect only gradient stops have the same color.
122123
* @function data․colors
123124
* @instance
124125
* @memberof Chart

src/ChartInternal/internals/color.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ export default {
9090
const $$ = this;
9191
const {$el, config} = $$;
9292
const ids: string[] = [];
93+
const hasGradient = config.area_linearGradient || config.bar_linearGradient ||
94+
config.point_radialGradient;
9395

9496
let pattern = notEmpty(config.color_pattern) ?
9597
config.color_pattern :
@@ -125,13 +127,11 @@ export default {
125127
// if callback function is provided
126128
if (isFunction(colors[id])) {
127129
color = colors[id].bind($$.api)(d);
128-
129-
// if specified, choose that color
130130
} else if (colors[id]) {
131+
// if specified, choose that color
131132
color = colors[id];
132-
133-
// if not specified, choose from pattern
134133
} else {
134+
// if not specified, choose from pattern
135135
if (ids.indexOf(id) < 0) {
136136
ids.push(id);
137137
}
@@ -143,7 +143,24 @@ export default {
143143
colors[id] = color;
144144
}
145145

146-
return isFunction(callback) ? callback.bind($$.api)(color, d) : color;
146+
color = isFunction(callback) ? callback.call($$.api, color, d) : color;
147+
148+
if (hasGradient) {
149+
const stop = $$.$el.defs.selectAll(
150+
`[id$='-gradient${$$.getTargetSelectorSuffix(id)}'] stop`
151+
);
152+
let hasSameColor;
153+
154+
stop.each(function(d, i) {
155+
hasSameColor = i === 0 ?
156+
this.style.stopColor :
157+
this.style.stopColor === hasSameColor;
158+
});
159+
160+
hasSameColor === true && stop.attr("stop-color", color);
161+
}
162+
163+
return color;
147164
};
148165
},
149166

test/api/data-spec.ts

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,4 +280,278 @@ describe("API data", function() {
280280
});
281281
});
282282
});
283+
284+
describe("data.colors() with gradient", () => {
285+
let gradientChart;
286+
287+
it("should update gradient stop colors for area_linearGradient", () => new Promise(done => {
288+
gradientChart = util.generate({
289+
data: {
290+
columns: [
291+
["data1", 30, 200, 100, 400, 150, 250],
292+
["data2", 50, 20, 10, 40, 15, 25]
293+
],
294+
type: "area",
295+
colors: {
296+
data1: "#ff0000",
297+
data2: "#00ff00"
298+
}
299+
},
300+
area: {
301+
linearGradient: {
302+
x: [0, 0],
303+
y: [0, 1],
304+
stops: [
305+
[0, "#ff0000", 1],
306+
[1, "#ff0000", 0]
307+
]
308+
}
309+
},
310+
onrendered: function() {
311+
const defs = this.$.defs;
312+
313+
// Check initial gradient stop colors
314+
const gradientData1 = defs.select("[id$='-gradient-data1'] stop:first-child");
315+
expect(toHex(gradientData1.attr("stop-color"))).to.be.equal("#ff0000");
316+
317+
// Update colors
318+
this.data.colors({
319+
data1: "#0000ff",
320+
data2: "#ffff00"
321+
});
322+
323+
setTimeout(() => {
324+
// Check updated gradient stop colors
325+
const updatedGradientData1 = defs.select("[id$='-gradient-data1'] stop:first-child");
326+
expect(toHex(updatedGradientData1.attr("stop-color"))).to.be.equal("#0000ff");
327+
328+
// Check that both stops have been updated
329+
const stops = defs.selectAll("[id$='-gradient-data1'] stop");
330+
stops.each(function() {
331+
expect(toHex(this.getAttribute("stop-color"))).to.be.equal("#0000ff");
332+
});
333+
334+
gradientChart.destroy();
335+
done(1);
336+
}, 350);
337+
}
338+
});
339+
}));
340+
341+
it("should update gradient stop colors for bar_linearGradient", () => new Promise(done => {
342+
gradientChart = util.generate({
343+
data: {
344+
columns: [
345+
["data1", 30, 200, 100, 400, 150, 250]
346+
],
347+
type: "bar",
348+
colors: {
349+
data1: "#ff0000"
350+
}
351+
},
352+
bar: {
353+
linearGradient: {
354+
x: [0, 0],
355+
y: [0, 1],
356+
stops: [
357+
[0, "#ff0000", 1],
358+
[1, "#ff0000", 0]
359+
]
360+
}
361+
},
362+
onrendered: function() {
363+
const defs = this.$.defs;
364+
365+
// Check initial gradient stop colors
366+
const gradientData1 = defs.select("[id$='-gradient-data1'] stop:first-child");
367+
expect(toHex(gradientData1.attr("stop-color"))).to.be.equal("#ff0000");
368+
369+
// Update colors
370+
this.data.colors({
371+
data1: "#00ff00"
372+
});
373+
374+
setTimeout(() => {
375+
// Check updated gradient stop colors
376+
const updatedGradientData1 = defs.select("[id$='-gradient-data1'] stop:first-child");
377+
expect(toHex(updatedGradientData1.attr("stop-color"))).to.be.equal("#00ff00");
378+
379+
// Check that all stops have been updated
380+
const stops = defs.selectAll("[id$='-gradient-data1'] stop");
381+
stops.each(function() {
382+
expect(toHex(this.getAttribute("stop-color"))).to.be.equal("#00ff00");
383+
});
384+
385+
gradientChart.destroy();
386+
done(1);
387+
}, 350);
388+
}
389+
});
390+
}));
391+
392+
it("should update gradient stop colors for point_radialGradient", () => new Promise(done => {
393+
gradientChart = util.generate({
394+
data: {
395+
columns: [
396+
["data1", 30, 200, 100, 400, 150, 250]
397+
],
398+
type: "scatter",
399+
colors: {
400+
data1: "#ff0000"
401+
}
402+
},
403+
point: {
404+
radialGradient: {
405+
cx: 0.5,
406+
cy: 0.5,
407+
r: 0.5,
408+
stops: [
409+
[0, "#ff0000", 1],
410+
[1, "#ff0000", 0]
411+
]
412+
}
413+
},
414+
onrendered: function() {
415+
const defs = this.$.defs;
416+
417+
// Check initial gradient stop colors
418+
const gradientData1 = defs.select("[id$='-gradient-data1'] stop:first-child");
419+
expect(toHex(gradientData1.attr("stop-color"))).to.be.equal("#ff0000");
420+
421+
// Update colors
422+
this.data.colors({
423+
data1: "#ffff00"
424+
});
425+
426+
setTimeout(() => {
427+
// Check updated gradient stop colors
428+
const updatedGradientData1 = defs.select("[id$='-gradient-data1'] stop:first-child");
429+
expect(toHex(updatedGradientData1.attr("stop-color"))).to.be.equal("#ffff00");
430+
431+
// Check that all stops have been updated
432+
const stops = defs.selectAll("[id$='-gradient-data1'] stop");
433+
stops.each(function() {
434+
expect(toHex(this.getAttribute("stop-color"))).to.be.equal("#ffff00");
435+
});
436+
437+
gradientChart.destroy();
438+
done(1);
439+
}, 350);
440+
}
441+
});
442+
}));
443+
444+
it("should not update gradient stop colors when stops have different colors", () => new Promise(done => {
445+
// Manually modify gradient after creation to have different colors
446+
gradientChart = util.generate({
447+
data: {
448+
columns: [
449+
["data1", 30, 200, 100, 400, 150, 250]
450+
],
451+
type: "area",
452+
colors: {
453+
data1: "#ff0000"
454+
}
455+
},
456+
area: {
457+
linearGradient: {
458+
x: [0, 0],
459+
y: [0, 1],
460+
stops: [
461+
[0, "#ff0000", 1],
462+
[1, "#ff0000", 0]
463+
]
464+
}
465+
},
466+
onrendered: function() {
467+
const defs = this.$.defs;
468+
469+
// Manually set different colors for stops to simulate mixed gradient
470+
// Using style instead of setAttribute since the code checks style.stopColor
471+
const stops = defs.selectAll("[id$='-gradient-data1'] stop");
472+
stops.nodes()[0].style.stopColor = "#ff0000";
473+
stops.nodes()[1].style.stopColor = "#0000ff";
474+
475+
// Verify initial colors
476+
const initialFirstColor = toHex(stops.nodes()[0].style.stopColor);
477+
const initialLastColor = toHex(stops.nodes()[1].style.stopColor);
478+
479+
expect(initialFirstColor).to.be.equal("#ff0000");
480+
expect(initialLastColor).to.be.equal("#0000ff");
481+
482+
// Update colors
483+
this.data.colors({
484+
data1: "#00ff00"
485+
});
486+
487+
setTimeout(() => {
488+
// Check that gradient stops remain unchanged (different colors)
489+
const updatedStops = defs.selectAll("[id$='-gradient-data1'] stop");
490+
const updatedFirstColor = toHex(updatedStops.nodes()[0].style.stopColor);
491+
const updatedLastColor = toHex(updatedStops.nodes()[1].style.stopColor);
492+
493+
expect(updatedFirstColor).to.be.equal("#ff0000");
494+
expect(updatedLastColor).to.be.equal("#0000ff");
495+
496+
gradientChart.destroy();
497+
done(1);
498+
}, 350);
499+
}
500+
});
501+
}));
502+
503+
it("should update gradient stop colors for data id with spaces", () => new Promise(done => {
504+
gradientChart = util.generate({
505+
data: {
506+
columns: [
507+
["data 1", 30, 200, 100, 400, 150, 250],
508+
["data 2", 50, 20, 10, 40, 15, 25]
509+
],
510+
type: "area",
511+
colors: {
512+
"data 1": "#ff0000",
513+
"data 2": "#00ff00"
514+
}
515+
},
516+
area: {
517+
linearGradient: {
518+
x: [0, 0],
519+
y: [0, 1],
520+
stops: [
521+
[0, "#ff0000", 1],
522+
[1, "#ff0000", 0]
523+
]
524+
}
525+
},
526+
onrendered: function() {
527+
const defs = this.$.defs;
528+
529+
// Check initial gradient stop colors for data id with spaces
530+
const gradientData1 = defs.select("[id$='-gradient-data-1'] stop:first-child");
531+
expect(toHex(gradientData1.attr("stop-color"))).to.be.equal("#ff0000");
532+
533+
// Update colors
534+
this.data.colors({
535+
"data 1": "#0000ff",
536+
"data 2": "#ffff00"
537+
});
538+
539+
setTimeout(() => {
540+
// Check updated gradient stop colors
541+
const updatedGradientData1 = defs.select("[id$='-gradient-data-1'] stop:first-child");
542+
expect(toHex(updatedGradientData1.attr("stop-color"))).to.be.equal("#0000ff");
543+
544+
// Check that both stops have been updated
545+
const stops = defs.selectAll("[id$='-gradient-data-1'] stop");
546+
stops.each(function() {
547+
expect(toHex(this.getAttribute("stop-color"))).to.be.equal("#0000ff");
548+
});
549+
550+
gradientChart.destroy();
551+
done(1);
552+
}, 350);
553+
}
554+
});
555+
}));
556+
});
283557
});

0 commit comments

Comments
 (0)