apache / echarts

Apache ECharts is a powerful, interactive charting and data visualization library for browser
https://echarts.apache.org
Apache License 2.0
60k stars 19.59k forks source link

[Bug] 自定义图表系列,添加绘制文本件组后,通过dataZoom平移X轴,会导致渲染错乱 #20135

Open JbfGod opened 1 month ago

JbfGod commented 1 month ago

Version

5.5.1

Link to Minimal Reproduction

https://codesandbox.io/p/sandbox/echart-custom-vpz6xs?file=%2Fpackage.json

Steps to Reproduce

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
</head>
<body style="height: 800px; margin: 0">
<div id="container" style="height: 100%"></div>

<script type="text/javascript" src="https://registry.npmmirror.com/echarts/5.5.1/files/dist/echarts.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dayjs/dayjs.min.js"></script>
<script type="text/javascript">

    var dom = document.getElementById('container');
    var myChart = echarts.init(dom, null, {
        renderer: 'canvas',
        useDirtyRect: false
    });
    const now = dayjs()
    const startDayOfMonth = dayjs(`${now.format("YYYY-MM-01")}`)
    const daysOfMonth = Array(now.daysInMonth()).fill(0).map((_, idx) => {
        return startDayOfMonth.add(idx, "day").format("YYYY-MM-DD")
    })
    const randomGet = (arr) => {
        const randomIdx = Math.floor(Math.random() * arr.length)
        return arr[randomIdx]
    }
    const randomWaybills = () => {
        const waybills = []
        const len = Math.ceil(Math.random() * 10)
        for (let i = 0; i < len; i++) {
            const t1 = randomGet(Array(31).fill(0).map((_, idx) => dayjs("2024-07-01").add(idx % 31, "day")))
            const t2 = randomGet(Array(31).fill(0).map((_, idx) => dayjs("2024-07-01").add(idx % 31, "day")))
            const loadTime = (t1.isBefore(t2) ? t1 : t2).format("YYYY-MM-DD")
            const unloadTime = (t1.isBefore(t2) ? t2 : t1).format("YYYY-MM-DD")
            waybills.push({
                id: Math.ceil(Math.random() * 100000000),
                // 不重复
                from: randomGet(["泰州", "宿迁", "镇江", "盐城", "扬州", "盐城", "盐城", "盐城", "盐城"]),
                to: randomGet([ "无锡", "常州", "南通", "扬州", "盐城", "扬州", "盐城", "盐城", "盐城", "盐城"]),
                driverName: randomGet(["王麻子", "李四", "王五", "赵六", "孙七", "周八", "吴九", "王麻子"]),
                escortName: randomGet(["李四", "王五", "赵六", "孙七", "周八", "吴九", "郑十", "王麻子"]),
                // 时间随机在同一个月份
                loadTime,
                unloadTime
            })
        }
        return waybills
    }
    const mockDatas = [
        {
            plateNo: "湘A8899590",
            waybills: randomWaybills()
        },
        {
            plateNo: "湘A8899568",
            waybills: randomWaybills()
        },
        {
            plateNo: "湘A8899569",
            waybills: randomWaybills()
        },
        {
            plateNo: "湘A8899560",
            waybills: randomWaybills()
        },
    ]
    const plateNos = mockDatas.map(item => item.plateNo)

    var option;

    const DataType = {
        Waybill: 1,
        Blank: 2,
        Virtual: 3
    }
    const serialData = [];
    mockDatas.forEach(function (d, idx) {
        const {waybills} = d
        const placeholderDay = new Set()
        for (let i = 0; i < waybills.length; i++) {
            const waybill = waybills[i]
            serialData.push({
                value: [idx, waybill.loadTime, waybill.unloadTime, DataType.Waybill, waybill],
                itemStyle: {
                    normal: {
                        color: "#ece2f5"
                    }
                }
            })
            const end = dayjs(waybill.unloadTime)
            for (let start = dayjs(waybill.loadTime); start.isBefore(end) || start.isSame(end); start = start.add(1, "day")) {
                placeholderDay.add(start.format("YYYY-MM-DD"))
            }
        }
        // 构造虚拟数据点,确保xAxis类目每个点位都有数据,防止xAxis出现点位缺失
        daysOfMonth.forEach(d => {
            serialData.push({
                value: [idx, d, d, DataType.Virtual],
                itemStyle: {
                    silent: false,
                    normal: {
                        color: "transparent"
                    }
                }
            })
        })
        // 构造空白区域,实现点击新增效果
        let start, end
        const blankDays = daysOfMonth.map(d => placeholderDay.has(d) ? null : d)
        blankDays.forEach((curr, i) => {
            if (!start) {
                if (curr) {
                    start = curr
                    end = curr
                }
                return
            } else if (curr) {
                end = curr
            }
            if (curr == null || i === blankDays.length - 1) {
                serialData.push({
                    value: [idx, start, end, DataType.Blank, d],
                    itemStyle: {
                        normal: {
                            color: "transparent"
                        }
                    }
                })
                start = null
                end = null
            }
        })
    })

    function renderItem(params, api) {
        if (api.value(3) === DataType.Virtual) {
            return
        }
        const truckIndex = api.value(0)
        const drawStart = api.coord([api.value(1), truckIndex])
        const drawEnd = api.coord([api.value(2), truckIndex])
        const drawHeight = api.size([0, 1])[1] * 0.85
        const oneTickWidth = api.size([1, 0])[0]
        if (api.value(3) === DataType.Blank) {
            return {
                type: 'rect',
                shape: echarts.graphic.clipRectByRect(
                    {
                        x: drawStart[0] - oneTickWidth / 2,
                        y: drawStart[1] - drawHeight / 2,
                        width: drawEnd[0] - drawStart[0] + oneTickWidth,
                        height: drawHeight,
                    },
                    {
                        x: params.coordSys.x,
                        y: params.coordSys.y,
                        width: params.coordSys.width,
                        height: params.coordSys.height
                    }
                ),
                style: {
                    fill: "transparent"
                },
            }
        }
        let rectHead = echarts.graphic.clipRectByRect(
            {
                x: drawStart[0] - oneTickWidth / 2,
                y: drawStart[1] - drawHeight / 2,
                width: 6,
                height: drawHeight,
            },
            {
                x: params.coordSys.x,
                y: params.coordSys.y,
                width: params.coordSys.width,
                height: params.coordSys.height
            }
        );
        const rectShape = echarts.graphic.clipRectByRect(
            {
                x: drawStart[0] - oneTickWidth / 2 + 6,
                y: drawStart[1] - drawHeight / 2,
                width: drawEnd[0] - drawStart[0] + oneTickWidth - 6,
                height: drawHeight,
            },
            {
                x: params.coordSys.x,
                y: params.coordSys.y,
                width: params.coordSys.width,
                height: params.coordSys.height
            }
        );
        const children = []
        if (rectHead) {
            children.push({
                type: 'rect',
                shape: {...rectHead, r: [2, 0, 0, 2]},
                style: {
                    fill: "#7c3bb4"
                },
                z2: 99
            })
        }
        if (rectShape) {
            children.push(...[
                {
                    type: 'text',
                    style: {
                        text: "上海~广州"
                    },
                    x: drawStart[0] - oneTickWidth / 2 + 6 + 8,
                    y: drawStart[1] - drawHeight / 2 + 8,
                    z2: 100,
                },
                {
                    type: 'rect',
                    shape: {...rectShape, r: [0, 2, 2, 0]},
                    style: {
                        fill: "#ece2f5"
                    },
                }
            ])
        }
        if (children.length > 0) {
            return {
                type: "group",
                children: children
            }
        }
    }

    option = {
        tooltip: {
            formatter: function (params) {
                if (params.value[3] === DataType.Waybill) {
                    const waybill = params.value[4]
                    return params.marker + waybill.from + "~" + waybill.to + "<br/>"
                        + "司机: " + waybill.driverName + "&nbsp;&nbsp;"
                        + "押运员: " + waybill.escortName + "<br/>";
                } else if (params.value[3] === DataType.Blank) {
                    return "新增运单";
                }

            }
        },
        title: {
            text: 'Test',
            left: 'center'
        },
        dataZoom: [
            {
                type: 'inside',
                filterMode: 'weakFilter',
                minValueSpan: 1,
                maxValueSpan: 7,
                xAxisIndex: 0,
            }
        ],
        grid: {
            height: 500
        },
        xAxis: {
            value: daysOfMonth,
            position: "top",
            type: "category"
        },

        yAxis: {
            data: plateNos
        },
        series: [
            {
                type: 'custom',
                renderItem,
                encode: {
                    x: [1, 2],
                    y: 0
                },
                data: serialData.sort((a, b) => a.value[1].localeCompare(b.value[1])),
                barWidth: "100%"
            }
        ]
    };

    myChart.setOption(option);
</script>
</body>
</html>

Current Behavior

当我在自定义组件中,添加了文本组件,初始化的时候图表的渲染是正常的,但是当我尝试移动x轴,图表渲染会出现混乱。 如下图: 【初始化的时候,正常显示】

image

【当我移动x轴时,原本应该绘制在坐标轴内的矩形,出现在了外面】

image

【当我注释掉绘制的文本组件的代码,图表会正常的渲染,绘制的矩形不会出现在坐标轴外】

image

Expected Behavior

我不明白为什么我添加一个简单的文本组件后,它会让原本正常绘制的矩形出现在坐标轴外

Environment

- OS:macOS 14.3.1
- Browser: Chrome 126.0.6478.127(正式版本) (arm64)
- Framework: HTML+JavaScript

Any additional comments?

No response

echarts-bot[bot] commented 1 month ago

@JbfGod It seems you are not using English, I've helped translate the content automatically. To make your issue understood by more people and get helped, we'd like to suggest using English next time. 🤗

TRANSLATED
**TITLE** [Bug] After adding a drawing text group, the X axis will be translated via dataZoom
helgasoft commented 1 month ago

first - your Minimal Reproduction code is too large, not minimal. Please try to simplify. second - the sample pictures do not correspond to the code output, they show more data. third - the code creates already the bottom two rectangles, they are just not well visible

🚩 Please follow Official posting guidelines (below). Note that questions about usage, "how to" or extensive debugging shouldn't be addressed here.


The issue list is reserved exclusively for bug reports and feature requests. For usage questions, please use the following resources:

Another good resource is website makeapie.cn with many code examples.

JbfGod commented 1 month ago

first - your Minimal Reproduction code is too large, not minimal. Please try to simplify. second - the sample pictures do not correspond to the code output, they show more data. third - the code creates already the bottom two rectangles, they are just not well visible

🚩 Please follow Official posting guidelines (below). Note that questions about usage, "how to" or extensive debugging shouldn't be addressed here.

The issue list is reserved exclusively for bug reports and feature requests. For usage questions, please use the following resources:

Another good resource is website makeapie.cn with many code examples.

我已按照你的要求,尝试简化了代码和问题的叙述,你可以在codesandbox中复现我所说的问题