数据更改的 Highcharts 热图未正确渲染数据。仅渲染 yAxis 上的最后一个类别

问题描述 投票:0回答:1

我正在编写 vue 代码来渲染热图。 y 轴可以根据日历选择进行更改。它在加载时正确渲染。但如果我更改日历上的日期,则只会呈现 y 轴上的最后一个类别。

当我查看

this.chart
时,它拥有所有数据,但只是未渲染所有数据。

我的代码:

components/BaseElements.vue

<template>
  <div id="filters" class="m-2">
    <DateRangePicker
      ref="picker"
      v-model="dateRange"
      opens="inline"
      show-dropdowns="true"
      auto-apply="true"
      single-date-picker="range"
      :date-range="dateRange"
      :ranges="customRanges"
      @select="handleDateRangeChange"
    >
    </DateRangePicker>

  </div>
</template>

<script>
import "bootstrap/dist/css/bootstrap.min.css";
import DateRangePicker from "vue3-daterange-picker";
import moment from "moment";
import Multiselect from "vue-multiselect";
import axios from "axios";
export default {
  components: { DateRangePicker, Multiselect },
  props: {
    startDate: {
      type: String,
      required: true,
    },
    endDate: {
      type: String,
      required: true,
    },
  },
  data() {
    return {
      dateRange: {
        startDate: this.startDate,
        endDate: this.endDate,
      },
    };
  },
  mounted() {
    this.loadData(this.dateRange.startDate, this.dateRange.endDate);
  },
  methods: {
    handleDateRangeChange(value) {
      this.dateRange.startDate = value.startDate;
      this.dateRange.endDate = value.endDate;

      this.$emit("date-range-changed", {
        startDate: this.dateRange.startDate,
        endDate: this.dateRange.endDate,
        divSelected: this.selectedDivisions,
        serviceSelected: this.selectedServices,
      });
    },

    loadData(startDate, endDate) {
      const baseURL ="http://localhost:3001/api";
      if (!baseURL) {
        console.error("VUE_APP_API_BASE_URL is not defined");
        return;
      }
      const url = `${baseURL}/filters?startDate=${moment(startDate).format(
        "YYYY-MM-DD"
      )}&endDate=${moment(endDate).format("YYYY-MM-DD")}`;


    },
 
  },
};
</script>

<style>

/* Add your component-specific styles here */
</style>

components/MainTab.vue

<template>
  <div id="webs" class="m-2 d-inline-flex">
<BaseElements
        :start-date="startDate"
        :end-date="endDate"
        @date-range-changed="updateDateRange"
      />
    <div style="width: 100%" class="d-flex flex-column">
      <HeatMap id="heatMap" style="width: 100%" />
    </div>
  </div>
</template>

<script>
import "bootstrap/dist/css/bootstrap.min.css";
import HeatMap from "./WebTab/HeatMap.vue";

export default {
  components: { HeatMap },
};
</script>

<style>
/* Add your component-specific styles here */
</style>

components/MainHeatMap.vue

<template>
  <div>
    <highcharts
      :constructor-type="'chart'"
      class="hc"
      :options="chartOptions"
      ref="chart"
    ></highcharts>
  </div>
</template>

<script>
import moment from "moment";

export default {
  props: {
    chartData: {
      type: Array,
      default: () => [],
    },
    divisionProp: {
      type: String,
      default: "division",
    },
    serviceProp: {
      type: String,
      default: "service",
    },
    profileNameProp: {
      type: String,
      default: "profile_name",
    },
    reportDateProp: {
      type: String,
      default: "report_date",
    },
    videoViewsProp: {
      type: String,
      default: "insight_video_views",
    },
  },
  data() {
    const vm = this;
    return {
      chartOptions: {
        chart: {
          backgroundColor: "#fbfcf8",
          type: "heatmap",
          plotBorderWidth: 1,
          height: "100%",
          events: {
            redraw: function () {
              if (!vm.initialLoad) {
                console.log(this.series[0].data);
                console.log(vm.seriesData);
              }
            },
            drilldown: function (e) {
              if (e.seriesOptions !== undefined) {
                const drilldowns = [
                  ...new Set(e.seriesOptions.data.map((d) => d.name)),
                ];
                this.xAxis[0].categories = drilldowns;
              }
            },
            drillup: function (e) {
              const drillups = [
                ...new Set(e.seriesOptions.data.map((d) => d.name)),
              ];
              this.xAxis[0].categories = drillups;
            },
          },
        },
        title: {
          text: "Views",
          align: "center",
        },
        exporting: {
          enabled: true,
          buttons: {
            contextButton: {
              menuItems: [
                "viewFullscreen",
                "printChart",
                "separator",
                "downloadPNG",
                "downloadJPEG",
                "separator",
                "downloadPDF",
                "downloadCSV",
              ],
            },
          },
        },
        credits: {
          enabled: false,
        },
        legend: {
          enabled: true,
          itemStyle: {
            color: "#000",
          },
        },
        rangeSelector: {
          enabled: false,
        },
        navigator: {
          enabled: false,
        },
        scrollbar: {
          enabled: false,
        },
        xAxis: {
          categories: [],
        },
        yAxis: {
          title: "",
          categories: [],
          reversed: false,
        },
        accessibility: {
          point: {
            descriptionFormat:
              "{(add index 1)}. " +
              "{series.xAxis.categories.(x)} " +
              "{series.yAxis.categories.(y)}, {value}.",
          },
        },
        colorAxis: {
          min: 0,
          minColor: "#FFFFFF",
          maxColor: "#DA70D6",
        },
        tooltip: {
          style: { color: "#000" },
          formatter() {
            const addCommas = (value) => {
              return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
            };
            return (
              `<b>${this.series.xAxis.categories[this.point.x]}</b> had<br>` +
              `<b>${addCommas(
                this.point.value
              )}</b> video views in the week of <br>` +
              `<b>${this.series.yAxis.categories[this.point.y]}</b>`
            );
          },
        },
        series: [
          {
            name: "Video Views",
            borderWidth: 1,
            data: [],
            dataLabels: {
              enabled: true,
              color: "#000000",
              formatter() {
                const numericSymbols = ["K", "M", "G", "T", "P", "E"];
                if (this.point.value >= 1000) {
                  const symbolIndex = Math.floor(
                    Math.log10(this.point.value) / 3
                  );
                  const scaledValue =
                    this.point.value / Math.pow(1000, symbolIndex);
                  const symbol = numericSymbols[symbolIndex - 1];
                  return `${Math.ceil(scaledValue)}${symbol}`;
                }
                return this.point.value;
              },
            },
          },
        ],
        drilldown: {
          series: [],
        },
        responsive: {
          rules: [
            {
              condition: {
                maxWidth: 500,
              },
              chartOptions: {
                yAxis: {
                  labels: {
                    format: "{substr value 0 1}",
                  },
                },
              },
            },
          ],
        },
      },
      fullData: [],
      seriesData: [],
      initialLoad: true,
    };
  },
  watch: {
    chartData: {
      handler(newChartData, oldChartData) {
        if (newChartData !== oldChartData) {
          this.updateChartData(newChartData);
        }
      },
      deep: true,
    },
  },
  created() {
    this.updateChartData(this.chartData);
  },
  methods: {
    updateChartData(data) {
      this.fullData = data;
      const filteredData = this.fullData.map((item) => ({
        division: item[this.divisionProp],
        service: item[this.serviceProp],
        profile_name: item[this.profileNameProp],
        report_date: item[this.reportDateProp],
        insight_video_views: item[this.videoViewsProp],
      }));

      const generateDateRanges = (fullData, groupBy) => {
        const dates = [...new Set(fullData.map((dt) => dt.report_date))].sort();
        const startDate = moment(dates[0]);
        const endDate = moment(dates[dates.length - 1]);

        const diffDays = endDate.diff(startDate, "days");
        const diffMonths = endDate.diff(startDate, "months");

        const sumInsightVideoViews = (start, end, groupValue) =>
          fullData
            .filter(
              (dt) =>
                dt[groupBy] === groupValue &&
                moment(dt.report_date).isBetween(start, end, null, "[)")
            )
            .reduce((sum, dt) => sum + dt.insight_video_views, 0);

        const uniqueGroupValues = [
          ...new Set(fullData.map((dt) => dt[groupBy])),
        ];
        const ranges = [];

        uniqueGroupValues.forEach((groupValue) => {
          let current = startDate.clone();

          while (current.isBefore(endDate) || current.isSame(endDate, "day")) {
            let next;
            let format;

            if (diffDays <= 14) {
              current.startOf("day");
              next = current.clone().add(1, "day");
              format = "ll";
            } else if (diffMonths <= 6) {
              current.startOf("week");
              next = current.clone().add(1, "week");
              format = "ll";
            } else {
              current.startOf("month");
              next = current.clone().add(1, "month");
              format = "MMMM, YYYY";
            }

            const division = fullData.find(
              (dt) => dt[groupBy] === groupValue
            ).division;

            ranges.push({
              [groupBy]: groupValue,
              division: division,
              date: current.format(format),
              insight_video_views: sumInsightVideoViews(
                current,
                next,
                groupValue
              ),
            });

            current = next;
          }
        });

        return ranges;
      };

      let yAxis = [
        ...new Set(
          generateDateRanges(filteredData, "division").map((d) => d.date)
        ),
      ];

      let xAxis = [
        ...new Set(
          generateDateRanges(filteredData, "division").map((d) => d.division)
        ),
      ];

      this.chartOptions.yAxis.categories = yAxis;
      this.chartOptions.xAxis.categories = xAxis;

      const groupBy = (array, property) => {
        return array.reduce((acc, item) => {
          const key = item[property];
          if (!acc[key]) {
            acc[key] = [];
          }
          acc[key].push(item);
          return acc;
        }, {});
      };
      // Group by division
      const groupedByDivision = groupBy(
        generateDateRanges(filteredData, "division"),
        "division"
      );

      // Group by service
      const groupedByService = groupBy(
        generateDateRanges(filteredData, "service"),
        "service"
      );

      const seriesData = Object.keys(groupedByDivision).flatMap((division) => {
        const divisionIndex = xAxis.indexOf(division);
        return groupedByDivision[division].map((range, dateIndex) => ({
          x: divisionIndex,
          y: dateIndex,
          value: range.insight_video_views,
          name: division,
          id: division,
          drilldown: division,
        }));
      });
      this.seriesData = seriesData;
      this.chartOptions.series[0].data = this.seriesData;

      const transformDrill = (data) => {
        const result = {};
        const serviceIndexMap = {};

        for (const [service, items] of Object.entries(data)) {
          for (let index = 0; index < items.length; index++) {
            const { division, insight_video_views } = items[index];
            if (!result[division]) result[division] = [];
            if (!serviceIndexMap[division]) serviceIndexMap[division] = {};
            if (serviceIndexMap[division][service] === undefined) {
              serviceIndexMap[division][service] = Object.keys(
                serviceIndexMap[division]
              ).length;
            }
            const x = serviceIndexMap[division][service];
            result[division].push({
              x,
              y: index,
              value: insight_video_views,
              name: service,
            });
          }
        }

        return Object.entries(result).map(([id, data]) => ({
          id,
          borderWidth: 1,
          dataLabels: {
            enabled: true,
            color: "#000000",
            formatter() {
              const numericSymbols = ["K", "M", "G", "T", "P", "E"];
              if (this.point.value >= 1000) {
                const symbolIndex = Math.floor(
                  Math.log10(this.point.value) / 3
                );
                const scaledValue =
                  this.point.value / Math.pow(1000, symbolIndex);
                const symbol = numericSymbols[symbolIndex - 1];
                return `${Math.ceil(scaledValue)}${symbol}`;
              }
              return this.point.value;
            },
          },
          data,
        }));
      };
      const drilldownData = transformDrill(groupedByService);
      this.chartOptions.drilldown.series = drilldownData;
      console.log(this.chartOptions);
      console.log(this.initialLoad);
      this.initialLoad = false;
    },
  },
};
</script>
<style>
.highcharts-heatmap-series > .highcharts-drilldown-point {
  cursor: default !important;
}

.highcharts-heatmap-series > .highcharts-drilldown-data-label {
  cursor: default !important;
}

.highcharts-heatmap-series > .highcharts-drilldown-data-label > text {
  text-decoration: none !important;
  color: #000000 !important;
  fill: #000000 !important;
}
</style>

加载时,它加载正确: enter image description here

但是日期变更: enter image description here

问题是

console.log(this.chartOptions);
返回更新后的
yAxis
和更新后的
this.chartOptions.series[0].data
。所以不确定为什么它不能正确渲染。
this.series[0].setData(vm.seriesData);
只是没有在页面上加载任何图表。

javascript vue.js highcharts heatmap
1个回答
0
投票

更新系列数据的最佳方法是使用图表引用更新整个图表对象。以下是包含数据更新的热图系列演示:https://codesandbox.io/p/sandbox/gifted-andras-d7gl3d

© www.soinside.com 2019 - 2024. All rights reserved.