| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382 |
- <template>
- <div class="echart_content">
- <div class="is_show_all" v-if="props.showCheckBox">
- <el-checkbox
- v-model="isShowAll"
- :indeterminate="isIndeterminate"
- @change="ToggleChangeAll"
- >
- 显示全部
- </el-checkbox>
- </div>
- <!-- 竖向堆叠条形图 -->
- <div ref="barStackChart" class="chart_box"></div>
- </div>
- </template>
- <script setup lang="ts">
- import { ref, watch, onMounted, onBeforeUnmount, nextTick } from "vue";
- import * as echarts from "echarts";
- import throttle from "lodash/throttle";
- import { getScorePerformanceAnalysis } from "@/utils/common";
- // ================= Props & Emits =================
- interface Props {
- showCheckBox?: boolean;
- data?: (string | number)[][];
- color?: string[];
- isClick?: boolean;
- legendList?: string[];
- tooltipData?: any[];
- }
- const props = withDefaults(defineProps<Props>(), {
- showCheckBox: true,
- data: () => [],
- color: () => [],
- isClick: false,
- legendList: () => [],
- tooltipData: () => [],
- });
- const emit = defineEmits<{
- (e: "HandleChartClick", index: number, xName: string): void;
- }>();
- // ================= Refs & State =================
- const barStackChart = ref<HTMLElement | null>(null);
- const myChart = ref<echarts.ECharts | null>(null);
- const isShowAll = ref(true);
- const isIndeterminate = ref(false);
- const legendSelected = ref<Record<string, boolean>>({});
- const legenSelectList = ref<string[]>([]);
- const legenAllList = ref<string[]>([]);
- const defaultColors = getScorePerformanceAnalysis();
- // ================= Helper Methods =================
- const initLegenSelectList = () => {
- if (props.legendList.length > 0) {
- legenSelectList.value = props.legendList;
- } else {
- legenSelectList.value = (props.data[0]?.slice(1) as string[]) || [];
- }
- };
- // ================= Core Methods =================
- const LoadEchart = () => {
- if (myChart.value) {
- myChart.value.dispose();
- }
- if (!barStackChart.value) return;
- myChart.value = echarts.init(barStackChart.value, null, {
- devicePixelRatio: 2,
- });
- if (props.data.length === 0) {
- return;
- }
- const dataset = {
- source: props.data,
- };
- const dataCount = props.data.length;
- legenAllList.value = (props.data[0]?.slice(1) as string[]) || [];
- legendSelected.value = (dataset.source[0] as string[])
- .slice(1)
- .reduce((acc: Record<string, boolean>, dim) => {
- acc[dim] = false;
- return acc;
- }, {});
- legenSelectList.value.forEach((item) => {
- legendSelected.value[item] = true;
- });
- isShowAll.value = legenSelectList.value.length === legenAllList.value.length;
- isIndeterminate.value =
- legenSelectList.value.length > 0 &&
- legenSelectList.value.length < legenAllList.value.length;
- let totalWidth = barStackChart.value.clientWidth;
- let singleSeriesWidth = Math.ceil((totalWidth - 140) / dataCount);
- const barMinWidth = 30;
- const dataZoomNum = Math.floor((totalWidth - 140) / (barMinWidth * 1));
- const dataZoomEnd = Math.floor((100 / dataCount) * dataZoomNum);
- const dataZoom = {
- start: 0,
- end: dataZoomEnd,
- type: "slider",
- show: true,
- borderColor: "transparent",
- borderCap: "round",
- xAxisIndex: [0],
- height: 8,
- left: 20,
- right: 20,
- bottom: 0,
- fillerColor: "transparent",
- zoomLock: true,
- handleSize: "0",
- handleStyle: { color: "#b8b8b8", borderWidth: 2 },
- backgroundColor: "transparent",
- showDataShadow: false,
- showDetail: false,
- filterMode: "filter",
- };
- const chartColors = props.color?.length > 0 ? props.color : defaultColors;
- const option: echarts.EChartsOption = {
- dataset: dataset,
- tooltip: {
- triggerOn: "mousemove",
- confine: true,
- extraCssText:
- "border-radius: 4px;padding:5px 0px 5px 5px;white-space:normal;word-warp:break-word;max-width: 400px;",
- enterable: true,
- formatter: (params: any) => {
- const seriesName = params.name;
- let tooltip = `<div class='tooltip_content'>`;
- let title = seriesName;
- tooltip += `<div class='tooltip_title'>${title}</div>`;
- let rateLabel = (dataset.source[0] as string[]).slice(1);
- for (let i = 0; i < rateLabel.length; i++) {
- let labelName = rateLabel[i];
- let rate = params.value[i + 1];
- let list: any[] = [],
- rateNum = "",
- isShowTip = props.tooltipData?.length > 0;
- if (props.tooltipData?.length > 0) {
- const dataIndex = params.dataIndex;
- const itemTooltip = props.tooltipData?.[dataIndex]?.[i] ?? "";
- list = itemTooltip?.list ?? [];
- rateNum = itemTooltip?.rateNum ?? "-";
- }
- if (isShowTip) {
- tooltip += `<div class='tooltip_student'><span class='tooltip_rect_icon' style='background:${chartColors[i]}'></span>${labelName}:${rate == "-" ? "-" : rate + "%"},${rateNum}人</div>`;
- if (i == rateLabel.length - 1) {
- for (let j = 0; j < list.length; j++) {
- tooltip += `<div class='tooltip_student'><span class='tooltip_rect_icon' style='background:#CCCCCC'></span>${list[j].label}:${list[j].value}</div>`;
- }
- }
- } else {
- tooltip += `<div class='tooltip_student'><span class='tooltip_rect_icon' style='background:${chartColors[i]}'></span>${labelName}:${rate == "-" ? "-" : rate + "%"}</div>`;
- }
- }
- tooltip += `</div>`;
- return tooltip;
- },
- },
- legend: {
- show: true,
- top: '6px',
- left: props.showCheckBox ? "110px" : "0px",
- itemGap: 20,
- itemHeight: 10,
- itemWidth: 20,
- textStyle: { fontSize: 12, color: "#333" },
- selectedMode: true,
- selected: legendSelected.value,
- formatter: (name: string) => name,
- },
- grid: {
- top: 50,
- left: 40,
- right: 60,
- bottom: 0,
- containLabel: true,
- },
- dataZoom: singleSeriesWidth < barMinWidth ? dataZoom : null,
- yAxis: {
- type: "value",
- axisLabel: {
- formatter: "{value}%",
- fontSize: 14,
- color: "#666",
- fontWeight: 400,
- },
- axisTick: {
- lineStyle: { color: "red", type: "dashed", width: 1 },
- },
- },
- xAxis: {
- type: "category",
- axisLabel: {
- fontSize: 14,
- color: "#666",
- fontWeight: 400,
- interval: 0,
- rotate: singleSeriesWidth < 80 ? 45 : 0,
- formatter: function (value: string) {
- const valueWidth = value.length * 14;
- if (valueWidth > singleSeriesWidth) {
- if (singleSeriesWidth < 100) {
- if (valueWidth > 80) {
- return value.slice(0, 2) + "..." + value.slice(-3);
- } else {
- return value;
- }
- } else {
- let maxLength = Math.floor(singleSeriesWidth / 14) - 1;
- return value.slice(0, maxLength) + "...";
- }
- } else {
- return value;
- }
- },
- },
- tooltip: {
- show: true,
- formatter: function (params: any) {
- return params.value;
- },
- },
- },
- series: (dataset.source[0] as string[]).slice(1).map(function (dim, index) {
- const color = chartColors[index];
- return {
- type: "bar",
- stack: "total",
- barMaxWidth: 50,
- label: {
- show: singleSeriesWidth > 22,
- position: "inside",
- color: "#fff",
- fontSize: 12,
- formatter: function (params: any) {
- if (params.value[params.seriesIndex + 1] == 0) {
- return "";
- } else {
- return params.value[params.seriesIndex + 1] + "%";
- }
- },
- },
- itemStyle: { color: color },
- };
- }),
- };
- myChart.value.setOption(option);
- myChart.value.on("legendselectchanged", HandleLegendSelectChanged);
- if (props.isClick) {
- // const containerWidth = barStackChart.value.offsetWidth;
- const xAxisDataLength = props.data.length - 1;
- const gridRect = myChart.value
- .getModel()
- .getComponent("grid")
- .coordinateSystem.getRect();
- const singleLabelWidth = gridRect.width / xAxisDataLength;
- myChart.value.on("click", (params: any) => {
- if (params.seriesType === "bar") {
- let pixelPosition = myChart.value!.convertToPixel(
- { xAxisIndex: 0 },
- params.name,
- );
- myChart.value!.setOption({
- graphic: {
- id: "highlight-box",
- type: "rect",
- shape: {
- x: pixelPosition - singleLabelWidth / 2,
- y: gridRect.y,
- width: singleLabelWidth,
- height: gridRect.height,
- },
- style: { fill: params.color + "30" },
- },
- });
- emit("HandleChartClick", params.dataIndex, params.name);
- }
- });
- let defaultPixelPosition = myChart.value.convertToPixel(
- { xAxisIndex: 0 },
- props.data[1][0],
- );
- myChart.value.setOption({
- graphic: {
- id: "highlight-box",
- type: "rect",
- shape: {
- x: defaultPixelPosition - singleLabelWidth / 2,
- y: gridRect.y,
- width: singleLabelWidth,
- height: gridRect.height,
- },
- style: { fill: "rgba(84,112,198,0.1)" },
- },
- });
- }
- };
- const HandleLegendSelectChanged = (params: any) => {
- const selected = params.selected;
- legenSelectList.value = [];
- for (let i = 0; i < legenAllList.value.length; i++) {
- if (selected[legenAllList.value[i]] === true) {
- legenSelectList.value.push(legenAllList.value[i]);
- }
- }
- isShowAll.value = legenSelectList.value.length === legenAllList.value.length;
- isIndeterminate.value =
- legenSelectList.value.length > 0 &&
- legenSelectList.value.length < legenAllList.value.length;
- };
- const handleResize = throttle(() => {
- nextTick(() => {
- myChart.value?.resize();
- });
- }, 500);
- const ToggleChangeAll = (value: boolean | string | number) => {
- const isChecked = value === true;
- if (isChecked) {
- legenSelectList.value = [...legenAllList.value];
- isIndeterminate.value = false;
- } else {
- legenSelectList.value = [];
- isIndeterminate.value = false;
- }
- LoadEchart();
- };
- // ================= Watch =================
- watch(
- () => props.data,
- () => {
- initLegenSelectList();
- LoadEchart();
- },
- { deep: true },
- );
- // ================= Lifecycle =================
- onMounted(() => {
- window.addEventListener("resize", handleResize);
- initLegenSelectList();
- LoadEchart();
- });
- onBeforeUnmount(() => {
- window.removeEventListener("resize", handleResize);
- if (myChart.value) {
- myChart.value.dispose();
- }
- });
- </script>
- <style lang="scss" scoped>
- </style>
|