| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839 |
- <template>
- <div v-loading="loading">
- <!-- ECharts 图表容器 -->
- <div class="line-chart" ref="chart"></div>
- <div class="control-panel">
- <!-- 光标 -->
- <div class="panel-block">
- <span class="label">光标</span>
- <div class="btn-group">
- <span
- v-for="item in GBcheckList"
- :key="item.val"
- :class="['btn', checkedGB.includes(item.val) ? 'active' : '']"
- @click="selectCursor(item.val)"
- >
- {{ item.val }}
- </span>
- </div>
- </div>
- <!-- 特征值(多选) -->
- <div class="panel-block full-width">
- <span class="label">特征 </span>
- <el-cascader
- v-model="selectedFeatures"
- :options="cascaderOptions"
- :props="cascaderProps"
- collapse-tags
- clearable
- placeholder="请选择特征"
- size="small"
- @change="handleCascaderChange"
- />
- </div>
- </div>
- </div>
- </template>
- <script>
- import axios from "axios";
- import * as echarts from "echarts"; // 导入 echarts 库
- import cursorReferenceMixin from "./spectrogramcharts/cursorReferenceMixin";
- import Bdgb from "./spectrogramcharts/Bdgb";
- import Xdgb from "./spectrogramcharts/Xdgb";
- import Tjgb from "./spectrogramcharts/Tjgb";
- export default {
- name: "TimedomainCharts", // 组件名称
- mixins: [cursorReferenceMixin, Bdgb, Xdgb, Tjgb],
- props: {
- currentIndex: {
- type: Number,
- default: 0,
- },
- activeIndex: {
- type: Number,
- default: 0,
- },
- loading: {
- type: Boolean,
- default: false,
- },
- ids: {
- type: Array,
- default: () => [],
- },
- timeListTwo: {
- type: Object,
- default: () => ({}),
- },
- currentRow: {
- type: Object,
- default: () => ({}),
- },
- windCode: {
- type: String,
- default: "",
- },
- },
- data() {
- return {
- chartInstance: null,
- option: null,
- TZshow: false,
- timeList: {},
- manualMarks: [],
- // 时域:多单指针
- singlePointerPoints: [],
- activeSinglePointer: null,
- // 时域:边带
- sidebandCursorVisible: false,
- sidebandCursorPoints: [],
- // 光标
- GBcheckList: [
- { val: "单指针" },
- // { val: "谐波光标" },
- // { val: "边带光标" },
- ],
- checkedGB: [],
- featureGroups: [
- {
- label: "时域特征",
- type: "time",
- children: [
- { key: "Xrms", label: "有效值" },
- { key: "mean_value", label: "平均值" },
- { key: "max_value", label: "最大值" },
- { key: "min_value", label: "最小值" },
- { key: "Xp", label: "峰值" },
- { key: "Xpp", label: "峰峰值" },
- ],
- },
- ],
- selectedFeatures: [],
- cascaderProps: {
- // multiple: true, // 多选
- // checkStrictly: false, // 不能选父节点(只选叶子)
- // emitPath: true, // 返回路径(重要)
- multiple: true,
- checkStrictly: false,
- emitPath: true, // 保留(你需要路径)
- value: "value",
- label: "label",
- },
- // 特征值按钮
- TZFeatureList: [
- { key: "Xrms", label: "有效值" },
- { key: "mean_value", label: "平均值" },
- { key: "max_value", label: "最大值" },
- { key: "min_value", label: "最小值" },
- { key: "Xp", label: "峰值" },
- { key: "Xpp", label: "峰峰值" },
- ],
- activeFeatures: [],
- };
- },
- computed: {
- cascaderOptions() {
- return this.featureGroups.map((group) => ({
- label: group.label,
- value: group.type, // ✅ 改这里
- children: group.children.map((item) => ({
- label: item.label,
- value: item.key,
- })),
- }));
- },
- },
- watch: {
- // 监听 chartData 和 chartLabels 的变化,重新绘制图表
- chartData(newData) {
- this.updateChart(newData, this.chartLabels);
- },
- chartLabels(newLabels) {
- this.updateChart(this.chartData, newLabels);
- },
- timeListTwo(newValue) {
- this.timeList = newValue; // 将 timeListTwo 的新值赋给 timeList
- if (this.chartInstance) {
- this.updateChart(this.timeList.y, this.timeList.x); // 更新图表
- }
- },
- // 监听 timeList 的变化
- timeList: {
- handler(newValue) {
- if (this.chartInstance) {
- this.updateChart(newValue.y, newValue.x); // 只在 chartInstance 初始化后更新图表
- }
- },
- deep: true, // 深度监听
- },
- },
- mounted() {
- this.$nextTick(() => {
- this.initializeChart();
- });
- },
- beforeDestroy() {
- if (this.chartInstance) {
- this.chartInstance.getZr().off("click", this.handleSinglePointerClick);
- this.chartInstance.getZr().off("click", this.handleSidebandCursorClick);
- this.chartInstance.dispose();
- this.chartInstance = null;
- }
- },
- methods: {
- getChartXYData() {
- return {
- x: this.timeList?.x || [],
- y: this.timeList?.y || [],
- };
- },
- findClosestIndex(target, xData) {
- if (!Array.isArray(xData) || !xData.length) return -1;
- let closestIndex = 0;
- let minDiff = Math.abs(Number(xData[0]) - target);
- for (let i = 1; i < xData.length; i++) {
- const diff = Math.abs(Number(xData[i]) - target);
- if (diff < minDiff) {
- minDiff = diff;
- closestIndex = i;
- }
- }
- return closestIndex;
- },
- bindSinglePointerClick() {
- if (!this.chartInstance) return;
- this.chartInstance.getZr().off("click", this.handleSinglePointerClick);
- this.chartInstance.getZr().on("click", this.handleSinglePointerClick);
- },
- unbindSinglePointerClick() {
- if (!this.chartInstance) return;
- this.chartInstance.getZr().off("click", this.handleSinglePointerClick);
- },
- enableSinglePointer() {
- this.bindSinglePointerClick();
- },
- disableSinglePointer() {
- this.unbindSinglePointerClick();
- this.singlePointerPoints = [];
- this.activeSinglePointer = null;
- this.removeSinglePointerSeries();
- },
- handleSinglePointerClick(event) {
- if (!this.checkedGB.includes("单指针")) return;
- if (this.checkedGB.includes("边带光标")) return; // 边带模式冻结单指针
- if (!this.chartInstance) return;
- const { x, y } = this.getChartXYData();
- if (!x.length || !y.length) return;
- const pointInGrid = this.chartInstance.convertFromPixel(
- { seriesIndex: 0 },
- [event.offsetX, event.offsetY],
- );
- const xClick = pointInGrid?.[0];
- if (xClick == null || Number.isNaN(xClick)) return;
- const idx = this.findClosestIndex(xClick, x);
- if (idx < 0) return;
- const xAxis = Number(x[idx]);
- const yValue = Number(y[idx]);
- if (Number.isNaN(xAxis) || Number.isNaN(yValue)) return;
- const existed = this.singlePointerPoints.some(
- (p) => Number(p.xAxis) === xAxis,
- );
- if (!existed) {
- this.singlePointerPoints.push({
- xAxis,
- val: yValue.toFixed(6),
- });
- }
- // 默认最后一次点击的单指针作为边带中心(0线)
- this.activeSinglePointer = {
- xAxis,
- val: yValue.toFixed(6),
- };
- this.applySinglePointerSeries();
- },
- applySinglePointerSeries() {
- if (!this.chartInstance) return;
- const option = this.chartInstance.getOption();
- const otherSeries = (option.series || []).filter(
- (s) => s && s.id !== "SINGLE_POINTER_LINE",
- );
- if (!this.singlePointerPoints.length) {
- this.chartInstance.setOption(
- { series: otherSeries },
- { replaceMerge: ["series"] },
- );
- return;
- }
- const seriesConfig = {
- id: "SINGLE_POINTER_LINE",
- type: "line",
- data: [],
- silent: true,
- z: 20,
- markLine: {
- silent: false,
- symbol: ["none", "none"],
- lineStyle: {
- color: "#ff0000",
- type: "dashed",
- width: 2,
- },
- label: {
- show: true,
- position: "end",
- formatter: (params) => params.data?.val || "",
- },
- data: this.singlePointerPoints.map((p) => ({
- xAxis: p.xAxis,
- val: p.val,
- })),
- },
- };
- this.chartInstance.setOption(
- { series: [...otherSeries, seriesConfig] },
- { replaceMerge: ["series"] },
- );
- },
- removeSinglePointerSeries() {
- if (!this.chartInstance) return;
- const option = this.chartInstance.getOption();
- const series = (option.series || []).filter(
- (s) => s && s.id !== "SINGLE_POINTER_LINE",
- );
- this.chartInstance.setOption({ series }, { replaceMerge: ["series"] });
- },
- enableSidebandCursor() {
- this.sidebandCursorVisible = true;
- if (!this.chartInstance) return;
- // 边带模式:只响应边带点击,冻结单指针
- this.unbindSinglePointerClick();
- this.chartInstance.getZr().off("click", this.handleSidebandCursorClick);
- this.chartInstance.getZr().on("click", this.handleSidebandCursorClick);
- },
- disableSidebandCursor() {
- this.sidebandCursorVisible = false;
- if (this.chartInstance) {
- this.chartInstance.getZr().off("click", this.handleSidebandCursorClick);
- }
- this.removeSidebandCursor();
- if (this.checkedGB.includes("单指针")) {
- this.bindSinglePointerClick();
- }
- },
- handleSidebandCursorClick(event) {
- if (!this.sidebandCursorVisible || !this.chartInstance) return;
- if (!this.singlePointerPoints.length) return;
- const pointInGrid = this.chartInstance.convertFromPixel(
- { seriesIndex: 0 },
- [event.offsetX, event.offsetY],
- );
- const clickX = pointInGrid?.[0];
- if (clickX == null || Number.isNaN(clickX)) return;
- // 以离点击最近的单指针来定义边带间隔 delta
- const nearestCenter = this.singlePointerPoints.reduce((prev, cur) => {
- const prevDiff = Math.abs(clickX - Number(prev.xAxis));
- const curDiff = Math.abs(clickX - Number(cur.xAxis));
- return curDiff < prevDiff ? cur : prev;
- });
- const referenceCenterX = Number(nearestCenter.xAxis);
- const delta = Math.abs(clickX - referenceCenterX);
- if (!delta) return;
- const points = [];
- this.singlePointerPoints.forEach((center) => {
- const centerX = Number(center.xAxis);
- for (let n = -5; n <= 5; n++) {
- if (n === 0) continue;
- points.push({
- xAxis: centerX + n * delta,
- val: `${n > 0 ? "+" : ""}${n}`,
- });
- }
- });
- this.sidebandCursorPoints = points;
- this.applySidebandSeries();
- },
- applySidebandSeries() {
- if (!this.chartInstance) return;
- const option = this.chartInstance.getOption();
- const otherSeries = (option.series || []).filter(
- (s) => s && s.id !== "SIDEBAND_CURSOR",
- );
- if (!this.sidebandCursorPoints.length) {
- this.chartInstance.setOption(
- { series: otherSeries },
- { replaceMerge: ["series"] },
- );
- return;
- }
- const sidebandSeries = {
- id: "SIDEBAND_CURSOR",
- type: "line",
- data: [],
- silent: true,
- z: 21,
- markLine: {
- silent: true,
- symbol: ["none", "none"],
- lineStyle: {
- color: "#ff0000",
- type: "dashed",
- width: 1,
- },
- label: {
- show: true,
- position: "start",
- formatter: (params) => params.data?.val || "",
- color: "#ff0000",
- },
- data: this.sidebandCursorPoints.map((p) => ({
- xAxis: p.xAxis,
- val: p.val,
- })),
- },
- };
- this.chartInstance.setOption(
- { series: [...otherSeries, sidebandSeries] },
- { replaceMerge: ["series"] },
- );
- },
- removeSidebandCursor() {
- this.sidebandCursorPoints = [];
- if (!this.chartInstance) return;
- const option = this.chartInstance.getOption();
- const series = (option.series || []).filter(
- (s) => s && s.id !== "SIDEBAND_CURSOR",
- );
- this.chartInstance.setOption({ series }, { replaceMerge: ["series"] });
- },
- initializeChart() {
- const chartDom = this.$refs.chart;
- if (chartDom && !this.chartInstance) {
- this.chartInstance = echarts.init(chartDom);
- }
- if (this.timeList.y && this.timeList.x) {
- this.updateChart(this.timeList.y, this.timeList.x);
- }
- },
- handleCascaderChange(val) {
- if (!val || val.length === 0) {
- this.activeFeatures = [];
- this.renderTZFeatures();
- return;
- }
- // val = [["时域特征", "Xrms"], ["时域特征", "max_value"]]
- this.activeFeatures = val.map((item) => item[item.length - 1]);
- this.renderTZFeatures(); // ✅ 直接用你已有方法
- },
- renderTZFeatures() {
- if (!this.chartInstance) return;
- const marks = this.activeFeatures
- .filter((key) => this.timeList[key] !== undefined)
- .map((key) => ({
- yAxis: this.timeList[key],
- label: {
- // formatter: key,
- formatter: this.timeList[key],
- },
- }));
- this.chartInstance.setOption({
- series: [
- {
- id: "MAIN_SERIES",
- markLine: {
- silent: true,
- symbol: ["none", "none"],
- lineStyle: {
- color: "#00aaff",
- type: "dashed",
- },
- data: marks,
- },
- },
- ],
- });
- },
- selectCursor(val) {
- const isAlreadyChecked = this.checkedGB.includes(val);
- const isSinglePointerVal = val === "单指针";
- const isSidebandVal = val === "边带光标";
- const isHarmonicVal = val === "谐波光标";
- if (isAlreadyChecked) {
- if (isSinglePointerVal) {
- // 单指针取消时,级联取消边带
- this.checkedGB = this.checkedGB.filter(
- (v) => v !== "单指针" && v !== "边带光标",
- );
- } else {
- this.checkedGB = this.checkedGB.filter((v) => v !== val);
- }
- } else if (isSinglePointerVal || isSidebandVal) {
- const next = new Set(this.checkedGB);
- next.add(val);
- next.delete("谐波光标");
- this.checkedGB = Array.from(next);
- } else if (isHarmonicVal) {
- this.checkedGB = [val];
- } else {
- this.checkedGB = [val];
- }
- const isSidebandChecked = this.checkedGB.includes("边带光标");
- const isHarmonicChecked = this.checkedGB.includes("谐波光标");
- const isSinglePointerChecked = this.checkedGB.includes("单指针");
- isSidebandChecked
- ? this.enableSidebandCursor()
- : this.disableSidebandCursor();
- isHarmonicChecked
- ? this.enableHarmonicCursor()
- : this.disableHarmonicCursor();
- if (!isSinglePointerChecked) {
- this.disableSinglePointer();
- } else if (isSidebandChecked) {
- // 保留单指针线,冻结单指针点击
- this.unbindSinglePointerClick();
- } else {
- this.enableSinglePointer();
- }
- },
- // 更新图表数据
- updateChart(data, labels) {
- if (!this.chartInstance) return; // Check if chartInstance is available
- console.log(this.timeList, "updataChart 时域图");
- const option = {
- // title: {
- // text: this.timeList.title,
- // left: "center",
- // },
- grid: {
- left: 60, // 原来是100,适当缩小左右边距
- right: 60,
- // bottom: 90, // 给推拽条和坐标轴腾出空间
- top: 30,
- bottom: 50,
- },
- toolbox: {
- right: 10,
- // top: 55, // 👈 工具栏在最上面
- feature: {
- dataZoom: { yAxisIndex: "none" },
- restore: {},
- saveAsImage: {},
- // myCustomTool3: {
- // show: true,
- // title: "特征值",
- // icon: `image://${require("@/assets/analyse/10.png")}`,
- // onclick: () => this.Show(),
- // },
- },
- },
- xAxis: {
- type: "value",
- name: this.timeList.xaxis,
- nameLocation: "center",
- nameTextStyle: {
- fontSize: 12,
- color: "#333",
- padding: [15, 0, 0, 0], // 增加X轴标题和轴线的距离
- },
- axisLabel: {
- margin: 1, // 增加数值标签和轴线的间距
- formatter: (value) => value,
- },
- axisTick: {
- show: true,
- },
- axisLine: {
- show: true,
- },
- },
- yAxis: {
- type: "value",
- name: this.timeList.yaxis,
- nameTextStyle: {
- fontSize: 12,
- color: "#333",
- padding: [0, 10, 0, 0], // 根据需要微调
- },
- axisLabel: {
- margin: 10,
- formatter: (value) => value,
- },
- axisTick: {
- show: true,
- },
- axisLine: {
- show: true,
- },
- },
- tooltip: {
- trigger: "axis",
- formatter: (params) => {
- const xValue = params[0].value[0];
- const yValue = params[0].value[1];
- return `X: ${xValue}<br/>Y: ${yValue}`;
- },
- axisPointer: {
- type: "line",
- },
- },
- dataZoom: [
- {
- type: "inside",
- xAxisIndex: 0,
- filterMode: "none",
- zoomOnMouseWheel: false,
- moveOnMouseMove: false,
- moveOnMouseWheel: false,
- },
- ],
- // dataZoom: [
- // {
- // type: "inside",
- // start: 0,
- // end: 10,
- // },
- // {
- // type: "slider",
- // start: 0,
- // end: 10,
- // handleSize: "80%",
- // showDataShadow: false,
- // top: 30, // 👈 放到顶部
- // height: 20, // 👈 控制高度(可选)
- // },
- // ],
- series: [
- {
- id: "MAIN_SERIES", // ✅ 必须加
- name: "数据系列",
- type: "line",
- data: labels.map((item, index) => [item, data[index]]),
- // data: (labels || []).map((item, index) => [item, data?.[index]])
- symbol: "none",
- symbolSize: 8,
- lineStyle: {
- color: "#162961",
- width: 1,
- },
- itemStyle: {
- color: "#162961",
- borderColor: "#fff",
- borderWidth: 1,
- },
- large: true,
- progressive: 2000,
- },
- ],
- };
- this.chartInstance.setOption(option, true);
- this.$nextTick(() => {
- this.renderTZFeatures(); // ✅ 防止被覆盖
- this.applySinglePointerSeries();
- this.applySidebandSeries();
- });
- },
- generateSeries(featureLines) {
- const createMarkLine = (dataSource, color) => ({
- type: "line",
- markLine: {
- silent: false,
- lineStyle: { color, type: "dashed", width: 1 },
- symbol: ["none", "none"],
- label: {
- show: true,
- position: "end",
- formatter: ({ data }) => data.val,
- },
- emphasis: {
- symbol: ["none", "none"],
- lineStyle: { color: "#FF6A00", width: 2 },
- label: {
- show: true,
- formatter: ({ value }) => `特征值: ${value}`,
- color: "#000",
- },
- },
- data: dataSource.map(({ Xaxis, val }) => ({ xAxis: Xaxis, val })),
- },
- });
- const markLines = [
- { data: featureLines.Fr, color: "#A633FF" },
- { data: featureLines.BPFI, color: "#23357e" },
- { data: featureLines.BPFO, color: "#42a0ae" },
- { data: featureLines.BSF, color: "#008080" },
- { data: featureLines.FTF, color: "#af254f" },
- { data: featureLines.B3P, color: "#FFD700" },
- ].map(({ data, color }) => createMarkLine(data, color));
- return [
- {
- name: "数据系列",
- type: "line",
- data: this.spectrumList.x.map((x, i) => [x, this.spectrumList.y[i]]),
- symbol: "none",
- lineStyle: { color: "#162961", width: 1 },
- itemStyle: { color: "#162961", borderColor: "#fff", borderWidth: 1 },
- large: true,
- },
- ...markLines,
- ];
- },
- renderFeatureSeries(featureLines) {
- if (!this.chartInstance) return;
- const currentOption = this.chartInstance.getOption();
- // 保留光标
- const cursorLineSeries =
- currentOption.series.find((s) => s && s.id === "CURSOR_LINE_SERIES") ||
- {};
- const cursorPointSeries =
- currentOption.series.find((s) => s && s.id === "CURSOR_POINT_SERIES") ||
- {};
- const cursorHighLineSeries =
- currentOption.series.find((s) => s && s.id === "PEAK_REFERENCE_LINE") ||
- {};
- const featureSeries = this.generateSeries(featureLines);
- this.chartInstance.setOption(
- {
- series: [
- (currentOption.series || []).find(
- (s) => s && (s.id === "MAIN_SERIES" || s.name === "数据系列"),
- ) || (currentOption.series || []).find((s) => s),
- ...featureSeries.slice(1),
- cursorLineSeries,
- cursorPointSeries,
- cursorHighLineSeries,
- ].filter((s) => s && s.type),
- },
- { replaceMerge: ["series"] },
- );
- },
- getTime() {
- this.$emit("handleLoading", {
- id: this.chartId,
- currentRow: this.currentRow,
- loading: true,
- });
- const params = {
- ids: this.ids,
- analysisType: "time",
- windCode: this.windCode,
- };
- axios
- .post("/AnalysisMulti/analysis/time", params)
- .then((res) => {
- this.timeList = JSON.parse(res.data);
- console.log(this.timeList, "timeList");
- })
- .catch((error) => {})
- .finally(() => {
- this.$emit("handleLoading", {
- id: this.chartId,
- currentRow: this.currentRow,
- loading: false,
- });
- });
- },
- Show() {
- this.TZshow = !this.TZshow;
- },
- },
- };
- </script>
- <style lang="scss" scoped>
- .line-chart {
- width: 100%;
- height: 400px;
- }
- .FD {
- width: 100%;
- height: 1px;
- position: relative;
- }
- .eigenvalue {
- position: absolute;
- top: 30px;
- right: 0;
- font-size: 10px;
- width: 100px;
- border: 1px solid black;
- padding: 5px;
- background: #fff;
- z-index: 99;
- h5 {
- line-height: 16px;
- height: 16px;
- }
- }
- .control-panel {
- display: flex;
- flex-wrap: wrap;
- gap: 12px;
- padding: 8px 12px;
- background: #f5f7fa;
- border: 1px solid #ddd;
- border-radius: 6px;
- margin-bottom: 10px;
- }
- .panel-block {
- display: flex;
- align-items: center;
- gap: 6px;
- }
- .label {
- font-size: 12px;
- color: #666;
- }
- .btn-group {
- display: flex;
- gap: 6px;
- }
- .btn {
- padding: 2px 8px;
- font-size: 12px;
- border: 1px solid #ccc;
- border-radius: 3px;
- cursor: pointer;
- }
- .btn.active {
- background: #409eff;
- color: #fff;
- }
- </style>
|