Skip to content

Commit

Permalink
Merge pull request #15711 from apache/fix-line-gradient
Browse files Browse the repository at this point in the history
fix(line): soft clipping gradient.
  • Loading branch information
pissang committed Sep 14, 2021
2 parents fde66ec + 344b648 commit bdafcbc
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 27 deletions.
97 changes: 73 additions & 24 deletions src/chart/line/LineView.ts
Expand Up @@ -56,6 +56,7 @@ import {getDefaultLabel, getDefaultInterpolatedLabel} from '../helper/labelHelpe
import { getECData } from '../../util/innerStore';
import { createFloat32Array } from '../../util/vendor';
import { convertToColorString } from '../../util/format';
import { lerp } from 'zrender/src/tool/color';

type PolarArea = ReturnType<Polar['getArea']>;
type Cartesian2DArea = ReturnType<Cartesian2D['getArea']>;
Expand Down Expand Up @@ -191,9 +192,56 @@ function turnPointsIntoStep(
return stepPoints;
}

/**
* Clip color stops to edge. Avoid creating too large gradients.
* Which may lead to blurry when GPU acceleration is enabled. See #15680
*
* The stops has been sorted from small to large.
*/
function clipColorStops(colorStops: ColorStop[], maxSize: number): ColorStop[] {
const newColorStops: ColorStop[] = [];
const len = colorStops.length;
// coord will always < 0 in prevOutOfRangeColorStop.
let prevOutOfRangeColorStop: ColorStop;
let prevInRangeColorStop: ColorStop;

function lerpStop(stop0: ColorStop, stop1: ColorStop, clippedCoord: number) {
const coord0 = stop0.coord;
const p = (clippedCoord - coord0) / (stop1.coord - coord0);
const color = lerp(p, [stop0.color, stop1.color]) as string;
return { coord: clippedCoord, color } as ColorStop;
}

for (let i = 0; i < len; i++) {
const stop = colorStops[i];
const coord = stop.coord;
if (coord < 0) {
prevOutOfRangeColorStop = stop;
}
else if (coord > maxSize) {
if (prevInRangeColorStop) {
newColorStops.push(lerpStop(prevInRangeColorStop, stop, maxSize));
}
// All following stop will be out of range. So just ignore them.
break;
}
else {
if (prevOutOfRangeColorStop) {
newColorStops.push(lerpStop(prevOutOfRangeColorStop, stop, 0));
// Reset
prevOutOfRangeColorStop = null;
}
newColorStops.push(stop);
prevInRangeColorStop = stop;
}
}
return newColorStops;
}

function getVisualGradient(
data: SeriesData,
coordSys: Cartesian2D | Polar
coordSys: Cartesian2D | Polar,
api: ExtensionAPI
) {
const visualMetaList = data.getVisual('visualMeta');
if (!visualMetaList || !visualMetaList.length || !data.count()) {
Expand Down Expand Up @@ -236,19 +284,14 @@ function getVisualGradient(
// LinearGradient to render `outerColors`.

const axis = coordSys.getAxis(coordDim);
const axisScaleExtent = axis.scale.getExtent();

// dataToCoord mapping may not be linear, but must be monotonic.
const colorStops: ColorStop[] = zrUtil.map(visualMeta.stops, function (stop) {
let coord = axis.toGlobalCoord(axis.dataToCoord(stop.value));
// normalize the infinite value
isNaN(coord) || isFinite(coord)
|| (coord = axis.toGlobalCoord(axis.dataToCoord(axisScaleExtent[+(coord < 0)])));
// offset will be calculated later.
return {
offset: 0,
coord,
coord: axis.toGlobalCoord(axis.dataToCoord(stop.value)),
color: stop.color
};
} as ColorStop;
});
const stopLen = colorStops.length;
const outerColors = visualMeta.outerColors.slice();
Expand All @@ -257,34 +300,40 @@ function getVisualGradient(
colorStops.reverse();
outerColors.reverse();
}
const colorStopsInRange = clipColorStops(
colorStops, coordDim === 'x' ? api.getWidth() : api.getHeight()
);
const inRangeStopLen = colorStopsInRange.length;
if (!inRangeStopLen && stopLen) {
// All stops are out of range. All will be the same color.
return colorStops[0].coord < 0
? (outerColors[1] ? outerColors[1] : colorStops[stopLen - 1].color)
: (outerColors[0] ? outerColors[0] : colorStops[0].color);
}

const tinyExtent = 10; // Arbitrary value: 10px
const minCoord = colorStops[0].coord - tinyExtent;
const maxCoord = colorStops[stopLen - 1].coord + tinyExtent;
const tinyExtent = 0; // Arbitrary value: 10px
const minCoord = colorStopsInRange[0].coord - tinyExtent;
const maxCoord = colorStopsInRange[inRangeStopLen - 1].coord + tinyExtent;
const coordSpan = maxCoord - minCoord;

if (coordSpan < 1e-3) {
return 'transparent';
}

zrUtil.each(colorStops, function (stop) {
zrUtil.each(colorStopsInRange, function (stop) {
stop.offset = (stop.coord - minCoord) / coordSpan;
});
colorStops.push({
offset: stopLen ? colorStops[stopLen - 1].offset : 0.5,
colorStopsInRange.push({
// NOTE: inRangeStopLen may still be 0 if stoplen is zero.
offset: inRangeStopLen ? colorStopsInRange[inRangeStopLen - 1].offset : 0.5,
color: outerColors[1] || 'transparent'
});
colorStops.unshift({ // notice colorStops.length have been changed.
offset: stopLen ? colorStops[0].offset : 0.5,
colorStopsInRange.unshift({ // notice newColorStops.length have been changed.
offset: inRangeStopLen ? colorStopsInRange[0].offset : 0.5,
color: outerColors[0] || 'transparent'
});

// zrUtil.each(colorStops, function (colorStop) {
// // Make sure each offset has rounded px to avoid not sharp edge
// colorStop.offset = (Math.round(colorStop.offset * (end - start) + start) - start) / (end - start);
// });

const gradient = new graphic.LinearGradient(0, 0, 0, 0, colorStops, true);
const gradient = new graphic.LinearGradient(0, 0, 0, 0, colorStopsInRange, true);
gradient[coordDim] = minCoord;
gradient[coordDim + '2' as 'x2' | 'y2'] = maxCoord;

Expand Down Expand Up @@ -626,7 +675,7 @@ class LineView extends ChartView {
}
}
this._clipShapeForSymbol = clipShapeForSymbol;
const visualColor = getVisualGradient(data, coordSys)
const visualColor = getVisualGradient(data, coordSys, api)
|| data.getVisual('style')[data.getVisual('drawType')];
// Initialization animation or coordinate system changed
if (
Expand Down
2 changes: 1 addition & 1 deletion src/export/core.ts
Expand Up @@ -24,7 +24,7 @@ export * from './api';
import { use } from '../extension';

// Import label layout by default.
// TODO remove
// TODO will be treeshaked.
import {installLabelLayout} from '../label/installLabelLayout';
use(installLabelLayout);

Expand Down
131 changes: 129 additions & 2 deletions test/linear-gradient.html → test/line-visual2.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/runTest/actions/__meta__.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/runTest/actions/line-visual2.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit bdafcbc

Please sign in to comment.