图表数据转化

发布时间 2023-11-23 15:40:24作者: RookieMaster

图表数据转化

目标:

现在前端所需的图标数据格式大致统一,后端从数据库查询后的数据种类多种多样,希望可以通过常见的转化方法转为工具类,提高业务开发效率。

常见的数据表格式说明

下面是常见前端框架(Vue、React)中使用的图表数据格式的总结:

Vue

Vue Chart.js

  • Line Chart:

    • 数据格式:
      • labels:标签数组,表示 x 轴上的数据点。
      • datasets:数据集数组,每个数据集包含以下属性:
        • label:数据集的标签。
        • data:数据点数组,表示 y 轴上的数据。
        • borderColor:线条颜色。
        • fill:是否填充面积。
    • 示例代码:
    data() {
      return {
        chartData: {
          labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
          datasets: [
            {
              label: 'Data 1',
              data: [65, 59, 80, 81, 56, 55, 40],
              borderColor: 'red',
              fill: false
            },
            {
              label: 'Data 2',
              data: [28, 48, 40, 19, 86, 27, 90],
              borderColor: 'blue',
              fill: false
            }
          ]
        }
      }
    }
    
  • Bar Chart:

    • 数据格式:
      • labels:标签数组,表示 x 轴上的数据点。
      • datasets:数据集数组,每个数据集包含以下属性:
        • label:数据集的标签。
        • data:数据点数组,表示 y 轴上的数据。
        • backgroundColor:柱状图的填充颜色。
    • 示例代码:
    data() {
      return {
        chartData: {
          labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
          datasets: [
            {
              label: 'Data 1',
              data: [12, 19, 3, 5, 2, 3],
              backgroundColor: 'red'
            },
            {
              label: 'Data 2',
              data: [15, 9, 7, 8, 5, 7],
              backgroundColor: 'blue'
            }
          ]
        }
      }
    }
    

Vue ECharts

  • Line Chart:

    • 数据格式:
      • xAxis:x 轴配置,包含以下属性:
        • type:x 轴类型(例如 category)。
        • data:标签数组,表示 x 轴上的数据点。
      • yAxis:y 轴配置,包含以下属性:
        • type:y 轴类型。
      • series:系列数组,每个系列包含以下属性:
        • name:系列的名称。
        • type:图表类型(例如 line)。
        • data:数据点数组,表示 y 轴上的数据。
    • 示例代码:
    data() {
      return {
        chartData: {
          xAxis: {
            type: 'category',
            data: ['January', 'February', 'March', 'April', 'May', 'June', 'July']
          },
          yAxis: {
            type: 'value'
          },
          series: [
            {
              name: 'Data 1',
              type: 'line',
              data: [65, 59, 80, 81, 56, 55, 40]
            },
            {
              name: 'Data 2',
              type: 'line',
              data: [28, 48, 40, 19, 86, 27, 90]
            }
          ]
        }
      }
    }
    
  • Bar Chart:

    • 数据格式:
      • xAxis:x 轴配置,包含以下属性:
        • type:x 轴类型(例如 category)。
        • data:标签数组,表示 x 轴上的数据点。
      • yAxis:y 轴配置,包含以下属性:
        • type:y 轴类型。
      • series:系列数组,每个系列包含以下属性:
        • name:系列的名称。
        • type:图表类型(例如 bar)。
        • data:数据点数组,表示 y 轴上的数据。
    • 示例代码:
    data() {
      return {
        chartData: {
          xAxis: {
            type: 'category',
            data: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange']
          },
          yAxis: {
            type: 'value'
          },
          series: [
            {
              name: 'Data 1',
              type: 'bar',
              data: [12, 19, 3, 5, 2, 3]
            },
            {
              name: 'Data 2',
              type: 'bar',
              data: [15, 9, 7, 8, 5, 7]
            }
          ]
        }
      }
    }
    

React

React Chart.js

  • Line Chart:
    • 数据格式与 Vue Chart.js 中的相同。
  • Bar Chart:
    • 数据格式与 Vue Chart.js 中的相同。

React ECharts

  • Line Chart:
    • 数据格式与 Vue ECharts 中的相同。
  • Bar Chart:
    • 数据格式与 Vue ECharts 中的相同。

后端统一返回数据格式

Json展示

{
    "code": 200,
    "message": "成功",
    "data": {
        "x": [
            "January",
            "February",
            "March"
        ],
        "y": [
            {
                "name": "Data 1",
                "data": [
                    "77.34",
                    "72.65",
                    "74.18"
                ]
            },
            {
                "name": "Data 2",
                "data": [
                    "70.67",
                    "79.54",
                    "74.75"
                ]
            },
            {
                "name": "Data 3",
                "data": [
                    "74.66",
                    "75.39",
                    "74.12"
                ]
            }
        ]
    }
}

实体类封装:

x轴,y轴分组:

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@NoArgsConstructor
public class TableListVo {

    @ApiModelProperty(value = "x轴数据")
    private List<String> x;

    @ApiModelProperty(value = "y轴数据")
    private List<TableYListVo> y;

    public TableListVo(List<String> x, List<TableYListVo> y) {
        this.x = x;
        this.y = y;
    }


}

y轴数据:

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@NoArgsConstructor
public class TableYListVo {


    @ApiModelProperty(value = "名称")
    private String name;

    @ApiModelProperty(value = "数据")
    private List<String> data;

    public TableYListVo(String name, List<String> data) {
        this.name = name;
        this.data = data;
    }
}

抽取常见的转化方式

1.根据查询的数据作为分组展示依据

数据库查询出的数据:

<==    Columns: average, variety, city
<==        Row: 70.67, apple, New York
<==        Row: 74.75, apple, Los Angeles"
<==        Row: 77.34, banana, New York
<==        Row: 74.18, banana, Los Angeles"
<==        Row: 72.65, banana, Chicago
<==        Row: 74.12, orange, Los Angeles"
<==        Row: 75.39, orange, Chicago
<==        Row: 79.54, apple, Chicago
<==        Row: 74.66, orange, New York

需求:现在希望将city作为x轴数据,variety作为y轴的name数据,average为y轴的data属性。达到的效果为,每个城市对应的品种数据以表格的形式展出,若average则赋值为0

效果图:

1700708114307

转化工具类:

 /**
     * 将数据列表转换为表格数据,可以排除指定的 x 字段和 y 字段
     *
     * @param dataList            数据列表
     * @param xFieldExtractor     x 字段值提取函数
     * @param yFieldExtractor     y 字段值提取函数
     * @param valueFieldExtractor 值字段值提取函数
     * @param excludedXFields     要排除的 x 字段值列表
     * @param excludedYFields     要排除的 y 字段值列表
     * @param fieldLabelProvider  字段标签提供函数
     * @param <T>                 数据类型
     * @return 表格数据对象
     */
    public static <T> TableListVo convertToTableDataWithExclusions(List<T> dataList, Function<T, String> xFieldExtractor, Function<T, String> yFieldExtractor, Function<T, String> valueFieldExtractor, List<String> excludedXFields, List<String> excludedYFields, Function<String, String> fieldLabelProvider) {
        try {
            // 创建一个数据映射的嵌套数据结构,用于存储每个字段的数据
            Map<String, Map<String, List<String>>> dataMap = dataList.stream().filter(obj -> excludedXFields == null || !excludedXFields.contains(xFieldExtractor.apply(obj))).collect(Collectors.groupingBy(obj -> xFieldExtractor.apply(obj), Collectors.groupingBy(obj -> yFieldExtractor.apply(obj), Collectors.mapping(obj -> String.valueOf(valueFieldExtractor.apply(obj)), Collectors.toList()))));

            // 提取 x 轴的标签,即 xField 的值列表
            List<String> xLabels = new ArrayList<>(dataMap.keySet());

            // 获取所有可能的 y 字段值
            List<String> allYValues = dataMap.values().stream().flatMap(yData -> yData.keySet().stream()).filter(yValue -> excludedYFields == null || !excludedYFields.contains(yValue)).distinct().collect(Collectors.toList());

            // 构建 y 轴的数据列表
            List<TableYListVo> yData = allYValues.stream().map(yValue -> {
                String mappedYValue = fieldLabelProvider != null ? fieldLabelProvider.apply(yValue) : yValue;
                List<String> yValueData = xLabels.stream().map(xLabel -> dataMap.getOrDefault(xLabel, new HashMap<>()).getOrDefault(yValue, new ArrayList<>())).flatMap(List::stream).collect(Collectors.toList());
                return new TableYListVo(mappedYValue, yValueData);
            }).collect(Collectors.toList());

            return new TableListVo(xLabels, yData);
        } catch (Exception e) {
            // 异常处理,记录日志等
            e.printStackTrace();
            return new TableListVo(new ArrayList<>(), new ArrayList<>());
        }
    }

调用:

List<TobaccoSensoryDto> resultList = tobaccoInfoMapper.selectBySensory();
        TableListVo tableListVo1 = TableUtils.convertToTableDataWithExclusions(resultList,TobaccoSensoryDto::getCity , TobaccoSensoryDto::getVariety, TobaccoSensoryDto::getAverage,
                null,CollectionUtil.newArrayList("红大","政和红大"),null);

2.根据查询的字段作为分组展示依据

数据库查询出的数据:

<==    Columns: aroma_quality, aroma_amount, impurity, mellowness, concentration, strength, irritation, aftertaste, create_year
<==        Row: 6.95, 7.18, 6.77, 6.86, 7.59, 7.23, 6.82, 6.77, 2013
<==        Row: 6.88, 7.42, 6.85, 6.88, 7.73, 6.77, 6.62, 6.73, 2014
<==        Row: 7.42, 7.65, 7.27, 7.27, 7.81, 7.35, 7.27, 7.23, 2015
<==        Row: 7.5, 7.5, 7.38, 7.46, 7.85, 7.69, 7.27, 7.38, 2016
<==        Row: 7.81, 7.92, 7.5, 7.88, 8.04, 7.81, 7.69, 7.69, 2017
<==        Row: 7.5, 7.8, 7.33, 7.3, 8.23, 7.77, 7.2, 7.27, 2018
<==        Row: 7.96, 8.11, 7.79, 7.75, 8.09, 7.96, 7.75, 7.71, 2019
<==        Row: 7.54, 7.58, 7.08, 7.33, 7.42, 7.33, 7.33, 7.08, 2020
<==        Row: 7.25, 7.83, 7.08, 7.21, 8.04, 7.38, 7.25, 7.17, 2021

需求:现在希望将create_year作为x轴数据,查询字段作为作为y轴的name数据,查询的字段值为y轴的data属性。达到的效果为,每年对应的9字段数据以表格的形式展出,若average则赋值为0

效果图:

1700709969695

转化工具类:

/**
     * 将数据列表转换为表格数据
     *
     * @param dataList           数据列表
     * @param xFieldExtractor    提取 x 字段值的函数
     * @param yFields            y 字段的列表
     * @param fieldLabelProvider 获取字段标签值的函数
     * @param <T>                数据类型
     * @return 表格数据对象
     */
    public static <T> TableListVo convertToTableData(List<T> dataList, Function<T, String> xFieldExtractor, List<String> yFields, Function<String, String> fieldLabelProvider) {
        try {
            // 创建一个数据映射的嵌套数据结构,用于存储每个字段的数据
            Map<String, Map<String, List<String>>> dataMap = new HashMap<>();

            // 初始化数据映射结构,为每个字段创建一个子映射
            yFields.forEach(yField -> dataMap.put(yField, new HashMap<>()));

            // 遍历数据列表,将数据按字段存储到数据映射中
            dataList.forEach(data -> {
                String xValue = xFieldExtractor.apply(data);

                yFields.forEach(yField -> {
                    String yValue = getField(data, yField);

                    // 将数据根据 x 值和对应的字段存储到数据映射中
                    if (yValue == null) {
                        yValue = "0"; // 将 null 值替换为 "0"
                    }
                    dataMap.get(yField).computeIfAbsent(xValue, k -> new ArrayList<>()).add(yValue);
                });
            });

            // 提取 x 轴的标签,即 xFieldExtractor 获得的值列表
            List<String> xLabels = new ArrayList<>(dataMap.get(yFields.get(0)).keySet());

            // 判断 x 字段的数据类型,并根据需要进行排序
            if (isNumeric(xLabels.get(0))) {
                xLabels.sort(Comparator.comparingDouble(Double::parseDouble));
            }

            // 构建 y 轴的数据列表
            List<TableYListVo> yData = yFields.stream().map(yField -> {
                String fieldLabel = fieldLabelProvider.apply(yField);

                // 提取每个字段对应的数据值
                List<String> yValueData = dataMap.get(yField).values().stream().flatMap(List::stream).collect(Collectors.toList());

                return new TableYListVo(fieldLabel, yValueData);
            }).collect(Collectors.toList());

            return new TableListVo(xLabels, yData);
        } catch (Exception e) {
            // 异常处理,记录日志等
            e.printStackTrace();
            return new TableListVo(new ArrayList<>(), new ArrayList<>());
        }
    }

    // 判断字符串是否为数字
    private static boolean isNumeric(String str) {
        if (str == null) {
            return false;
        }
        try {
            Double.parseDouble(str);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    /**
     * 获取对象的字段值
     *
     * @param data      数据对象
     * @param fieldName 字段名
     * @param <T>       数据类型
     * @return 字段值
     */
    private static <T> String getField(T data, String fieldName) {
        if (data == null || fieldName == null || fieldName.isEmpty()) {
            return null;
        }

        Class<?> clazz = data.getClass();

        try {
            Field field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
            return field.get(data).toString();
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }

        return null;
    }

调用:

 List<TobaccoSensoryEval> resultList = tobaccoChemicalEvalMapper.getHistorySensory(gradekey.getGrade());
            TableListVo tableListVo = TableUtils.convertToTableData(resultList, TobaccoSensoryEval::getCreateYear, CollectionUtil.newArrayList("aromaQuality", "aromaAmount"), TobaccoSensoryEvalField::getByFieldName);
            System.out.println("tableListVo = " + tableListVo);