Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom series enhancement (for-next) #12775

Merged
merged 18 commits into from
Jun 23, 2020
Merged

Custom series enhancement (for-next) #12775

merged 18 commits into from
Jun 23, 2020

Conversation

100pah
Copy link
Member

@100pah 100pah commented Jun 9, 2020

Brief Information

This pull request is in the type of:

  • bug fixing
  • new feature
  • others

What does this PR do?

  • Support the new APIs about attached text and transform (follow zr-next).
  • Modify emphasis setting.
  • Provide backward compat for custom series since zr-next change the APIs about attached text and transform.
  • Support "clipPath" defined on element.
  • Support transition animation related setting in custom series, including transition attrs, shape, style animation, clipPath, and during, and "enter"/"leave" animation.
  • Fix and publish "$mergeChildren" (by name or by index) for "group element".

textContent and textConfig

renderItem(params, api) {
    return {
        type: 'rect',
        textContent: {
            // type: 'text' can be ignored
            style: {
                text: 'xxxx',
                fill: 'red'
            }
        },
        textConfig: {
            ...
        }
    }
}

emphasis config

renderItem(params, api) {
    return {
        type: 'rect',
        textContent: {
            // type: 'text' can be ignored
            style: {
                text: 'xxxx',
                fill: 'red'
            },
            emphasis: {
                style: {
                    ...
                }
            }
        },
        textConfig: {
            ...
        },
        emphasis: {
            style: {
                ...
            },
            textConfig: {
                ...
            },
            // z2 can also be specified in emphasis.
            z2: 100
        }
    }
}
renderItem(params, api) {
    return {
        type: 'rect',
        emphasis: {
            // Set false to disable default emphasis behavior.
            style: false,
        }
    }
}

Transform attr

renderItem(params, api) {
    return {
        type: 'rect',
        x: 12,
        y: 11,
        scaleX: 2,
        scaleY: 2,
        originX: 10,
        originY: 32,
        rotation: 1.57
    }
}

clipPath

renderItem(params, api) {
    return {
        type: 'group',
        clipPath: {
            type: 'rect',
            shape: { ... }
        }
    }
}

during

renderItem(params, api) {
    return {
        type: 'rect',
        shape: {
            myAttr: 123,
            $transition: 'myAttr'
        },
        during: function (apiDuring) {
            var x = apiDuring.getAttr('x');
            var myVal = apiDuring.getShape('myAttr');
            apiDuring.setStyle('opacity', 0.5);
            ...
        }
    }
}

transition config

renderItem(params, api) {
    return {
        type: 'rect',
        scaleX: 123,
        scaleY: 123,
        // Means `scaleX` and `scaleY` will be interpolated and
        // have transition animation if series animation related
        // option configured.
        // By default `x` and `y` (and the legacy prop `position`) will
        // be transitioned. But if `$transition` specified, only props
        // in `$transition` will be transitioned.
        $transition: ['x', 'y'],
        shape: {
            myAttr1: 123,
            myAttr2: 123,
            // Means `myAttr1` and `myAttr2` will be interpolated and
            // have transition animation if series animation related
            // option configured.
            $transition: ['myAttr1', 'myAttr2']
        },
        style: {
            // `opacity` will be interpolate and transitioned.
            // for "Image" and "Text", `x`, `y` can also be specified in
            // style, so style transition might be needed.
            $transition: 'opacity'
        },
        clipPath: {
            type: 'circle',
            x: 100
            $transition: 'x'
        }
    }
}

enterFrom laveTo animation config

renderItem(params, api) {
    return {
        type: 'rect',
        rotation: 2 * Math.PI,
        // Means `rotation` will have enter animation from value `0`.
        $enterFrom: {
            rotation: 0
        },
        // Have leave animation to value `0.4`.
        $leaveTo: {
            rotation: 0.4
        },
        shape: {
            myAttr1: 123,
            // Have enter animation from value `10`.
            $enterFrom: {
                myAttr1: 10
            },
            // Have leave animation to value `5`.
            $leaveTo: {
                myAttr1: 5
            },
        },
        style: {
            // Have enter animation from value `0`.
            $enterFrom: {
                opacity: 0
            },
            // Have leave animation to value `0`.
            $leaveTo: {
                opacity: 0
            }
        }
    }
}

$mergeChildren

By default, group children will be merged by index.
Users can also specify $mergeChildren: 'byName' to merge children by name on each child element.
Merge children will happen when each time renderItem called. For example, if there is some condition
variables in renderItem which results in different returned children.

renderItem(params, api) {
    var condition = getCondition();
    var children;
    if (condition === 'm') {
        children = [{
            type: 'circle',
            name: 'name_1',
            ...
        }, {
            type: 'circle',
            name: 'name_2',
            ...
        }, {
            type: 'circle',
            name: 'name_3',
            ...
        }];
    }
    else {
        children = [{
            type: 'circle',
            name: 'name_3',
            ...
        }, {
            type: 'circle',
            name: 'name_1',
            ...
        }]
    }

    return {
        type: 'group',
        $mergeChildren: 'byName',
        children: children
    }
}

Deprecated and break change

  • api.style and api.styleEmphasis are deprecated.
  • The behavior of api.style and api.styleEmphasis are slightly different (impossible to totally compat).
  • position, scale, origin are deprecated.
  • diffChildrenByName is deprecated.

Others

Related test cases or examples to use the new APIs

<test/custom-text-content.html>
<test/custom-text-transition.html>

Related issues

#5988

@echarts-bot
Copy link

echarts-bot bot commented Jun 9, 2020

Thanks for your contribution!
The community will review it ASAP. In the meanwhile, please checkout the coding standard and Wiki about How to make a pull request.

The pull request is marked to be PR: author is committer because you are a committer of this project.

@100pah
Copy link
Member Author

100pah commented Jun 20, 2020

Somthing more discussion about during callback

The proposed during callback may enable some new power customzation with animation.
For example, there are some exmaple shown in test/custom-transition.html "spiral-dynamic-extent" and "texture-bar-by-clipPath":

  • Functional interpolation
    • If intending to make the element move according to a special customized path instead of a linear path, the built-in interpolate is not enough to do that.
    • If intending to make animation in customzied polygon points (or event morph), the built-in interpolate diff of number[][] is not enough.
    • To resolve it, we can make a special prop in el.shape (say, el.shape.abc) and use the built-in interpolate on that. In each during callback, calculate the transition prop or polygon pionts final by that el.shape.abc. That is so called "functional interpolation".
  • Text animation in custom series.

Note that:

For users, in each frame, users usually need to:

  • read the current props value from the current graphic element.
  • calculate based on those values.
  • write the result to the that graphic element.

For echarts, we need to:

  • restrict the "power" of that new API for forward compatibiliy (think of the potential changes or other related features in future).
  • think of the reasonability of the API.
  • think of implementation complexity and performance of the API.

The current API is implemented in the follow way:

renderItem(params, api) {
    var abcVal = makeNextAbcValue(params, api);
    return {
        type: 'polygon',
        shape: {
            abc: abcVal,
            points: calculatePolygonPoints(abcVal)
            // Make the built-in interpolate to be adapted on "abc".
            $transition: 'abc'
        },
        during: function (apiDuring) {
            var abcVal = apiDuring.getShape('abc');
            var points = calculatePolygonPoints(abcVal);
            apiDuring.setShape('points', points);
        }
    }
}

Note that it provides those "method" to for users to read and write the values in the "during callback":

interface DuringAPI {

    // currently only transform props  
    // (x, y, scaleX, scaleY, originX, orignY, rotation) 
    // are available here. Other attributes are not supported 
    // until really needed one day.
    setAttr(key: TransformProps, val: unknown): void;
    getAttr(key: TransformProps): unknown;

    // get and set and any props in "shape".
    // No need to make a travel since it does not support 
    // to input a dictionary object.
    // We do not restrict the shape props types because it 
    // should be able to customized by users and the result
    // of  `renderItem` also does not restrict the shape props.
    setShape(key: string, val: unknown): void;
    getShape(key: string): unknown;

    // get and set and any props in "style".
    // No need to make a travel since it does not support 
    // to input a dictionary object.
    // We do not restrict the style props types because it 
    // The result of  `renderItem` also does not restrict the  
    // style props.
    setStyle(key: string, val: unknown): void;
    getStyle(key: string): unknown;

    // If `setStyle` called, echarts will auto call `durty()` 
    // internally for this element.
    // Otherwise of only `setAttr` or `setShape` called, 
    // echarts will only call `markRedraw()` internally 
    // for this element.
}

Now could we think of that:

  1. Is it appropriate to implement those features via exposing a "during callback" to users?
  2. Is it appropriate to implement "read & write" in the way of the DuringAPI above?
  3. Is there any potential flaw I missed or any other better solution?

@pissang
Copy link
Contributor

pissang commented Jun 22, 2020

// currently only transform props
// (x, y, scaleX, scaleY, originX, orignY, rotation)
// are available here. Other attributes are not supported
// until really needed one day.
setAttr(key: TransformProps, val: unknown): void;
getAttr(key: TransformProps): unknown;

I will prefer setTransform, getTransform over setAttr, getAttr here.

Also, can parameter be an object? In the case want to update multiple multiple transform properties:

setTransform({ x: Math.sin(angle), y: Math.cos(angle), rotation: angle });

It will be simpler than:

setTransform('x', Math.sin(angle));
setTransform('y', Math.cos(angle));
setTransform('rotation', angle);

@100pah
Copy link
Member Author

100pah commented Jun 22, 2020

@pissang

I think is OK to change setAttr to setTransform. I will try to modify it soon.

But I am not sure about support "object input" in those setter API. The reason is:

  • When we call "getter", we also need to fetch one prop one calling.
  • I guess in most cases only one or few attributes need to be modified in during. So may be "object input" helps little for "API simple to use".
during: function (apiDuring) {
    var points = makeShapePoints(
        api, valOnRadius, apiDuring.getShape('valOnAngle')
    );
    apiDuring.setShape('points', points);
}
during: function (apiDuring) {
    var points = makePionterPoints(params, apiDuring.getShape('polarEndRadian'));
    apiDuring.setShape('points', points);
}
during: function (apiDuring) {
    apiDuring.setStyle('text', makeText(apiDuring.getShape('valOnRadian')));
}
during: function (apiDuring) {
    var currContentColor = getColorInTransition(apiDuring, 'content');
    apiDuring.setStyle('fill', currContentColor);
}
during: function (apiDuring) {
    var points = makeShapePoints(
        params,
        apiDuring.getShape('widthRadius'),
        apiDuring.getShape('startRadius'),
        apiDuring.getShape('endRadian')
    );
    apiDuring.setShape('points', points);
}

In these cases above, "object input" does not help

during: function (apiDuring) {
    var iValOnAngle = apiDuring.getShape('valOnAngle');
    var point = makeLabelPosition(api, valOnRadius, iValOnAngle);
    apiDuring
        .setTransform('x', point[0])
        .setTransform('y', point[1])
        .setStyle('text', getText(iValOnAngle));
}
during: function (apiDuring) {
    var endRadian = apiDuring.getShape('endRadian');
    var point = makeLabelPosition(
        params,
        apiDuring.getShape('widthRadius'),
        apiDuring.getShape('startRadius'),
        endRadian
    );
    apiDuring.setTransform('x', point[0]).setTransform('y', point[1]);
    apiDuring.setStyle('text', makeText(endRadian));
}

In these cases above, "object input" helps, but not a significant improve:

during: function (apiDuring) {
    var endRadian = apiDuring.getShape('endRadian');
    var point = makeLabelPosition(
        params,
        apiDuring.getShape('widthRadius'),
        apiDuring.getShape('startRadius'),
        endRadian
    );
    apiDuring.setTransform({ x: point[0], y: point[1] });
    apiDuring.setStyle('text', makeText(endRadian));
}
  • Support "object input" make the API to use a loop internally. This is a simple performance comparison, where the "object input" is slower than the "plain way" 1/3. Not sure whether the time cost is not significant enough to worry about. I do not test them in low-end mobile device.
  • The "object input" need to make "overload" in those method setTransform setShape setStyle. Little tedious in code.

@100pah 100pah merged commit 9963aa4 into next Jun 23, 2020
@echarts-bot
Copy link

echarts-bot bot commented Jun 23, 2020

Congratulations! Your PR has been merged. Thanks for your contribution! 👍

@jianqi-jin
Copy link

jianqi-jin commented Sep 20, 2022

emphasis config

renderItem(params, api) {
    return {
        type: 'rect',
        textContent: {
            // type: 'text' can be ignored
            style: {
                text: 'xxxx',
                fill: 'red'
            },
            emphasis: {
                style: {
                    ...
                }
            }
        },
        textConfig: {
            ...
        },
        emphasis: {
            style: {
                ...
            },
            textConfig: {
                ...
            },
            // z2 can also be specified in emphasis.
            z2: 100
        }
    }
}

Hello, may I ask that if I just want to emphasis the text 'xxx' without emphasizing its parent rect, how can I do?
And also, when I emphasis the parent rect, I just don't want to emphasis its child, how can I do? Thanks :)

Waiting for your reply with excited ~

@100pah

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants