«Галантные товары» используют Vue + Echarts для создания эксклюзивного визуального интерфейса (ниже)

JavaScript
«Галантные товары» используют Vue + Echarts для создания эксклюзивного визуального интерфейса (ниже)

предисловие

Продолжение предыдущей статьи"Dry Goods" Используйте Vue + Echarts для создания своего эксклюзивного визуального интерфейса (часть 1)", сегодня речь пойдет о标记использования для достижения эффекта, показанного на рисунке ниже.

Используемый номер версии Echarts: v4.3. Номер версии v-диаграмм: v1.19.0.

Существует много вариантов использования разметки, сценарии, которые будут представлены сегодня: линейная диаграмма, столбчатая диаграмма, линейная диаграмма + столбчатая диаграмма.

Маркеры линейной диаграммы - символ

На картинке выше в точке перегиба полилинии некоторые «точки» заменены маленькими значками.

Чтобы добиться такого эффекта, вам нужно сначала понять исходные требования:

  • Каждый маркер представляет тип деятельности.
  • Некоторые действия будут иметь место в определенные моменты времени или периоды времени, и тип действия должен быть отмечен датой действия.
  • При наличии нескольких событий в один и тот же день используется составной значок, а при отображении всплывающей подсказки отображается информация о каждом событии дня.
  • Макет всплывающей подсказки выглядит следующим образом: сначала отображается текущая дата, значок и название действия каждого действия отображаются посередине, и, наконец, отображаются имя индикатора и соответствующее значение.
  • Без активной даты точка перегиба и всплывающая подсказка отображаются как обычно. Таким образом, его полный эффект должен быть таким:

Для достижения такого эффекта необходимо учитывать следующие моменты:

  • Как установить активную иконку в точке перегиба полилинии в виде «точки» через направленное совпадение даты.
  • Как установить размер иконки? Его необходимо отличать от обычной идентификации точки перегиба.
  • Как переписать стиль всплывающей подсказки? Также должна быть совместима со стилями даты, у которых нет активности.

Анализ идей:

Во-первых, для направленного сопоставления дат необходимо разработать следующую структуру данных:

data: [
    {
        id: '1, 1, 3, 2',
        date: '2019-10-10',
        name: 'test-name1, test-name2, test-name3, test-name4'
    },
    ...
]

Следующее основное свойство:symbolэто ключ, он на самом деле находится на полилинии拐点.

Типы меток, поддерживаемые символом: круг, прямоугольник, круглый прямоугольник, треугольник, ромб, булавка, стрелка, нет. Значение по умолчанию — emptyCircle, то есть пустой круг.

Он также поддерживает формат ссылок:'image://http://xxx.xxx.xxx/a/b.png'. Кроме того, если вам нужны разные графики для разных данных, вы можете установить функцию обратного вызова в следующем формате:

(value: Array|number, params: Object) => string

Значение первого параметра — это значение данных в data. Второй параметр params — это другие параметры элемента данных.

Это именно то, что нам нужно. Шагните на яму, чтобы проверить: указанная выше функция обратного вызова доступна только в последней версии.V4.3можно использовать как обычно, в противном случае будет сообщено об ошибке. Вот почему в начале я подчеркивал проблему версии Echarts. Конкретная реализация выглядит следующим образом:

<ve-line ... :extend="chartExtend"></ve-line>
...

// mock 包含标注的数据结构
dataList: [
    {
        id: '1, 1, 3, 2',
        date: '2019-10-10',
        name: 'test-name1, test-name2, test-name3, test-name4'
    },
    ...
    {
        id: '1',
        date: '2019-10-17',
        name: 'test-name1'
    }
],
...

setChartExtend () {
    this.chartExtend = {
        series: (v) => {
            Array.from(v).forEach((e, idx) => {
                e.symbol = (value, params) => {
                    return getSymbolIcon(params.name, dataList);
                };
                e.symbolSize = (value, params) => {
                    return getSymbolSize(params.name, dataList);
                };
            });
            return v;
        },
        ...
    };
},

getSymbolIcon (date, dataList) {
    const defaultSymbol = 'circle';

    if (!dataList || dataList.length === 0) {
        return defaultSymbol;
    }

    // 通过日期匹配,找到对应的标注对象
    const dataItem = dataList.find(item => item.date === date);

    const iconUrl = getSymbolUrl(dataItem.id);
    return iconUrl ? iconUrl : defaultSymbol;
},

getSymbolSize (date, dataList) {
    if (!dataList || dataList.length === 0) {
        return 4;
    }

    // 通过日期匹配,找到对应的标注对象
    const dataItem = dataList.find(item => item.date === date);

    return dataItem ? 15 : 4;
},

getSymbolUrl (id) {
    // 这里需要额外先做一层准备工作:将图标按 id 对应图标进行命名,然后传到自家的cdn上
    // 命名可以像这样:symbol-icon-1.jpg、symbol-icon-2.jpg 等等
    // 这里拿到标注的id,拼上链接返回即可
    // 形如:image://http://xxx.xxx.com/symbol-icon-1.jpg
    // 遇到多个 id 的情况,可以多加一个复合图标来处理,id 可以定为 0
}

Последний вопрос, как переписать стиль всплывающей подсказки, чтобы он был совместимым?

Как упоминалось ранее, макет всплывающей подсказки разделен на три части: дата, информация о метке и конкретное значение. Затем мы используем это, чтобы перерисовать всплывающую подсказку.

всплывающая подсказка поддерживает функцию обратного вызова форматирования, и ее тип возвращаемого значения — Sting.

// 回调函数格式
(params: Object|Array, ticket: string, callback: (ticket: string, html: string)) => string

Информация о дате, которую можно получить через params[0].axisValue.

Метод получения информации об аннотации аналогичен идее получения значка выше, за исключением того, что здесь необходимо отобразить конкретный тип и имя аннотации.

Конкретное значение можно получить с помощью маркера, имени серии, значения и других атрибутов в параметрах. Конкретная реализация выглядит следующим образом:

setChartExtend () {
    this.chartExtend = {
        series: (v) => {
            ...
        },
        tooltip: {
            formatter: (params) => {
                return getTooltipResult(params, dataList);
            }
        }
    };
},

getTooltipResult (params, dataList) {
    const dateResult = params[0].axisValue;
    // 获取原版 tooltip 的渲染结构
    const originalResultObj = getOriginalTooltipResult(params);

    if (!dataList || dataList.length === 0) {
        return dateResult + originalResultObj.strResult;
    }

    const dataItem = dataList.find(item => item.date === date);

    if (dataItem) {
        return dateResult +  getSymbolResult(dataItem, originalResultObj.strResult);
    }

    return dateResult + originalResultObj.strResult;
},

getOriginalTooltipResult (params) {
    let result = '';

    params.forEach((param, idx) => {
        // value 会因为 seriesType 的不同,类型也会有不同
        let value = Object.prototype.toString.call(param.value) === '[object Array]' ? param.value[1] : param.value;

        const str = `${param.marker}${param.seriesName}: ${ value }<br>`;
        result += str;
    });

    return {
        strResult: result
    };
},

getSymbolResult (dataItem, originalResult) {
    // 将 dataItem 的 id 转为数组的形式,循环渲染输出图标与名称的组合
    const dataIds = dataItem.id.split(',');
    const dataNames = dataItem.name.split(',');

    dataIds.forEach ((id, idx) => {
        // 通过 id 换取 图标的链接
        const iconUrl = ...;

        // 仿照 param.marker 的 style 写法,渲染图标样式
        const str = `<img src="${iconUrl}" width="11" height="11" style="display: inline-block; margin-right: 4px; margin-left: -1px;">${dataNames[idx]}<br>`;

        result += str;
    });

    return result + originalResult;
}

Возможно, некоторые студенты спросят, какое значение возвращает метод getOriginalTooltipResult, ведь есть только один strResult, зачем его оформлять как объект?

На самом деле, это для удобства расширения. Например, за датой может следовать сумма значений конкретных данных ниже. Затем вам нужно передать цикл params в методе getOriginalTooltipResult, вычислить итог и сопоставить стиль для создания strTotal.

getOriginalTooltipResult (params) {
    ...

    return {
        strTotal: strTotal,
        strResult: result
    };
}

Кроме того, в реальном бизнесе значение некоторых строк также может быть выражено в процентах. Затем вам нужно расширить метод getOriginalTooltipResult, например, передав объект параметров:

getOriginalTooltipResult = (params, options = { isLinePercent: false, isShowTotal: false }) {
    ...
}

На этом этапе рендеринг маркеров линейного графика может быть полностью отрендерен.

Маркер гистограммы - markPoint

Очень неприятный момент: гистограмма не имеет свойства символа. Это означает, что набор из приведенной выше линейной диаграммы нельзя использовать в столбчатой ​​диаграмме.

Нет другого пути, кроме как проверять документ с самого начала и продолжать находить информацию. После некоторого «движения и катания» я наконец нашел атрибут markPoint. В объекте markPoint можно установить символ, в таком случае сделанная вами установка не будет напрасной? ! ?

Следует отметить, что символом markPoint по умолчанию является «булавка», представляющая собой значок пузырька. Кроме того, для отображения метки markPoint необходимо установить его свойство данных. Нам нужно установить следующие свойства в данных:

data: [
    {
        symbol: '...', // 设置标记的图标链接
        symbolSize: 15, // 设置标记的大小
        coord: [index, 0], // x 轴的第 index 个上,打标记
        symbolOffset: [0, 0] // 将标记定位在 x 轴上
    },
    ...
]

Конкретный код реализации выглядит следующим образом:

<ve-histogram :data="chartData" :extend="chartExtend"></ve-histogram>
...

// mock 包含标注的数据结构
dataList: [
    {
        id: '1, 1, 3, 2',
        date: '2019-10-10',
        name: 'test-name1, test-name2, test-name3, test-name4'
    },
    ...
    {
        id: '1',
        date: '2019-10-17',
        name: 'test-name1'
    }
],
...

setChartExtend () {
    this.chartExtend = {
        series: (v) => {
            Array.from(v).forEach((e, idx) => {
                e.markPoint = {
                    data: getMarkPointData(this.chartData.rows, dataList)
                };
            });
        },
        ...
    };
},

getMarkPointData (rows, dataList) {
    const results = [];

    rows.forEach((row, index) => {
        // 通过日期匹配,找到对应的标注对象
        const dataItem = dataList.find(item => item.date === row.date);
        if (dataItem) {
            results.push({
                symbol: getSymbolUrl(dataItem.id),
                symbolSize: 15,
                coord: [index, 0],
                symbolOffset: [0, 0]
            });
        }
    });

    return results;
},

getSymbolUrl (id) {
    // 这里需要额外先做一层准备工作:将图标按 id 对应图标进行命名,然后传到自家的cdn上
    // 命名可以像这样:symbol-icon-1.jpg、symbol-icon-2.jpg 等等
    // 这里拿到标注的id,拼上链接返回即可
    // 形如:image://http://xxx.xxx.com/symbol-icon-1.jpg
    // 遇到多个 id 的情况,可以多加一个复合图标来处理,id 可以定为 0
}

Когда данные приведены в качестве данных MarchPoint, данные не даны, необходимость использования ChartData в строках.

В цикле строк, если совпадение нужно отметить в день, только что заданная структура данных сохраняется в массиве результатов и, наконец, возвращается к данным markPoint для отображения на дисплее. Эффект следующий:

Способ реализации всплывающей подсказки не зависит от типа графика и может использоваться в общем случае, поэтому здесь повторяться не будем.

Кроме того, иногда необходимо иметь дело с эффектом суммирования гистограммы. Это повлияет на позиционирование символаOffset.Для красоты это можно обработать так: Для нештабелированных элементов сместите их вправо на 50%, и отцентрируйте метку, т.е.symbolOffset : ['50%', 0].

Линейная диаграмма + столбчатая диаграмма

прийти последнимкомбо удар, составная структура диаграммы из линейной диаграммы и гистограммы, например следующая:

С точки зрения реализации кода мы, конечно, можем пометить каждый подходящий столбец и точку перегиба полилинии. Но в дизайне интерфейса для красоты мы решили отметить только точку перегиба ломаной и игнорировать точку перегиба пунктирной линии.

Первоначальный замысел этого дизайна таков: метка предназначена только для того, чтобы напомнить людям, сообщить зрителям, что из-за каких-то особых событий в тот день данные претерпели более очевидные изменения. Итак, вывод из этого – достаточно, чтобы в день появлялась только одна метка.

Конкретная реализация на самом деле очень проста, просто нужно судить о типе серии при рендеринге:

setChartExtend () {
    this.chartExtend = {
        series: (v) => {
            Array.from(v).forEach((e, idx) => {
                if (e.type === 'bar') {
                    // 设置柱状图 markPoint 的方法
                    ...
                }

                if (e.type === 'line') {
                    // 设置折线图 symbol、symbolSize 的方法
                    ...
                }
            });
        },
        ...
    };
}

Суммировать

Есть много эффектов, которых можно достичь с помощью Echarts, и в этой статье рассматривается рендеринг «маркеров». В точке перегиба линейного графика символ используется для обработки сопоставления. На гистограмме из-за отсутствия прямого символа вместо него используется markPoint, а метка располагается в определенном измерении. Эффекты довольно хорошие.

Однако при последующем использовании обнаружилась еще одна неловкая ситуация:在折线图中,当点击图例中的某一项,使其数据隐藏,然后再次点击重新渲染后,发现 symbol 的自定义图标不显示了.

Я долго искал, но так и не нашел полезной информации. Я подозревал, что это была ошибка рендеринга, поэтому я написал о проблеме в Echarts, надеясь, что ее можно будет решить. Вы также можете обсудить связанные вопросы в области сообщений, спасибо!

PS: Добро пожаловать, чтобы обратить внимание на мою общедоступную учетную запись «Super Brother Front-end Small Stack», чтобы обменяться идеями и технологиями.