数据可视化大屏
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

843 line
21KB

  1. import { Component, OnInit, ViewChild, inject } from '@angular/core';
  2. import { STColumn, STComponent } from '@delon/abc/st';
  3. import { SFSchema } from '@delon/form';
  4. import { ModalHelper, _HttpClient } from '@delon/theme';
  5. import { SHARED_IMPORTS } from '@shared';
  6. import { DataVCardComponent } from '../card/card.component';
  7. import * as echarts from 'echarts';
  8. import { AgGridAngular } from 'ag-grid-angular';
  9. import { DataVTitleComponent } from '../title/title.component';
  10. import { ColDef } from 'ag-grid-community'; // Column Definition Type Interface
  11. import { NzProgressModule } from 'ng-zorro-antd/progress';
  12. import { NzBadgeModule } from 'ng-zorro-antd/badge';
  13. import { Subscription } from 'rxjs';
  14. import { IMqttMessage, MqttService } from 'ngx-mqtt';
  15. import { ChartComponentComponent } from './chart-component/chart-component.component';
  16. @Component({
  17. selector: 'app-data-v-s1',
  18. standalone: true,
  19. templateUrl: './s1.component.html',
  20. styleUrls: ['./s1.component.less'],
  21. imports: [NzBadgeModule, NzProgressModule, AgGridAngular, DataVCardComponent, DataVTitleComponent, ...SHARED_IMPORTS, ChartComponentComponent]
  22. })
  23. export class DataVS1Component implements OnInit {
  24. private readonly http = inject(_HttpClient);
  25. private readonly modal = inject(ModalHelper);
  26. chart_options1 = {}
  27. c1OptionC: echarts.EChartsOption = {
  28. series: [
  29. {
  30. type: 'gauge',
  31. startAngle: 180,
  32. endAngle: 0,
  33. center: ['50%', '75%'],
  34. radius: '90%',
  35. min: 0,
  36. max: 1,
  37. splitNumber: 8,
  38. axisLine: {
  39. lineStyle: {
  40. width: 6,
  41. color: [
  42. [0.25, '#74FAFB'],
  43. [0.5, '#74FAFB'],
  44. [0.75, '#74FAFB'],
  45. [1, '#74FAFB']
  46. ]
  47. }
  48. },
  49. pointer: {
  50. icon: 'path://M12.8,0.7l12,40.1H0.7L12.8,0.7z',
  51. length: '12%',
  52. width: 20,
  53. offsetCenter: [0, '-60%'],
  54. itemStyle: {
  55. color: 'auto'
  56. }
  57. },
  58. axisTick: {
  59. length: 12,
  60. lineStyle: {
  61. color: 'auto',
  62. width: 2
  63. }
  64. },
  65. splitLine: {
  66. length: 20,
  67. lineStyle: {
  68. color: 'auto',
  69. width: 5
  70. }
  71. },
  72. axisLabel: {
  73. color: '#464646',
  74. fontSize: 20,
  75. distance: -60,
  76. rotate: 'tangential',
  77. formatter: function (value: any) {
  78. if (value === 0.875) {
  79. return '';
  80. } else if (value === 0.625) {
  81. return '';
  82. } else if (value === 0.375) {
  83. return '';
  84. } else if (value === 0.125) {
  85. return '';
  86. }
  87. return '';
  88. }
  89. },
  90. title: {
  91. offsetCenter: [0, '-10%'],
  92. fontSize: 20
  93. },
  94. detail: {
  95. fontSize: 30,
  96. offsetCenter: [0, '-5%'],
  97. valueAnimation: true,
  98. formatter: function (value: any) {
  99. return Math.round(value * 100) + '';
  100. },
  101. color: 'inherit'
  102. },
  103. data: [
  104. {
  105. value: 0.9,
  106. name: ''
  107. }
  108. ]
  109. }
  110. ]
  111. };
  112. private c1Chart: any;
  113. rowData = [
  114. {
  115. rwmc: '任务1',
  116. rwms: '任务描述1',
  117. kssj: '2024-1-1',
  118. jhwcsj: '2024-1-2',
  119. sfcq: '否',
  120. dqzt: '正常',
  121. dqjd: '节点1',
  122. sjwcsj: '2024-1-2',
  123. bjmc: '报警1',
  124. bjms: '报警描述1',
  125. bjsj: '2024-1-1',
  126. gzyy: '-',
  127. clff: '-',
  128. tzsj: '2024-1-2',
  129. zycd: '一般',
  130. tzmc: '系统提示',
  131. tznr: '账户登录',
  132. fj: '-'
  133. },
  134. {
  135. rwmc: '任务2',
  136. rwms: '任务描述2',
  137. kssj: '2024-1-2',
  138. jhwcsj: '2024-1-3',
  139. sfcq: '否',
  140. dqzt: '正常',
  141. dqjd: '节点2',
  142. sjwcsj: '2024-1-2',
  143. bjmc: '报警2',
  144. bjms: '报警描述2',
  145. bjsj: '2024-1-2',
  146. gzyy: '-',
  147. clff: '-',
  148. tzsj: '2024-1-2',
  149. zycd: '一般',
  150. tzmc: '系统提示',
  151. tznr: '账户登录',
  152. fj: '-'
  153. },
  154. {
  155. rwmc: '任务3',
  156. rwms: '任务描述3',
  157. kssj: '2024-1-3',
  158. jhwcsj: '2024-1-4',
  159. sfcq: '否',
  160. dqzt: '正常',
  161. dqjd: '节点3',
  162. sjwcsj: '2024-1-2',
  163. bjmc: '报警3',
  164. bjms: '报警描述3',
  165. bjsj: '2024-1-3',
  166. gzyy: '-',
  167. clff: '-',
  168. tzsj: '2024-1-2',
  169. zycd: '一般',
  170. tzmc: '系统提示',
  171. tznr: '账户登录',
  172. fj: '-'
  173. },
  174. {
  175. rwmc: '任务4',
  176. rwms: '任务描述4',
  177. kssj: '2024-1-4',
  178. jhwcsj: '2024-1-5',
  179. sfcq: '否',
  180. dqzt: '正常',
  181. dqjd: '节点4',
  182. sjwcsj: '2024-1-2',
  183. bjmc: '报警4',
  184. bjms: '报警描述4',
  185. bjsj: '2024-1-4',
  186. gzyy: '-',
  187. clff: '-',
  188. tzsj: '2024-1-2',
  189. zycd: '一般',
  190. tzmc: '系统提示',
  191. tznr: '账户登录',
  192. fj: '-'
  193. },
  194. {
  195. rwmc: '任务5',
  196. rwms: '任务描述5',
  197. kssj: '2024-1-5',
  198. jhwcsj: '2024-1-6',
  199. sfcq: '否',
  200. dqzt: '正常',
  201. dqjd: '节点5',
  202. sjwcsj: '2024-1-2',
  203. bjmc: '报警5',
  204. bjms: '报警描述5',
  205. bjsj: '2024-1-5',
  206. gzyy: '-',
  207. clff: '-',
  208. tzsj: '2024-1-2',
  209. zycd: '一般',
  210. tzmc: '系统提示',
  211. tznr: '账户登录',
  212. fj: '-'
  213. }
  214. ];
  215. colDefs1: ColDef[] = [
  216. { headerName: '时间', field: 'rwmc', flex: 1 },
  217. { headerName: '系统/设备', field: 'rwms', flex: 1 },
  218. { headerName: '操作描述', field: 'kssj', flex: 1 },
  219. { headerName: '操作人员', field: 'jhwcsj', flex: 1 }
  220. ];
  221. colDefs2: ColDef[] = [
  222. { headerName: '序号', field: 'rwmc', flex: 1 },
  223. { headerName: '时间', field: 'kssj', flex: 2 },
  224. { headerName: '任务描述', field: 'rwms', flex: 1 },
  225. { headerName: '等级', field: 'dqzt', flex: 1.5 }
  226. ];
  227. private subscription: Subscription;
  228. constructor(private _mqttService: MqttService) {
  229. this.subscription = this._mqttService.observe('s1').subscribe((message: IMqttMessage) => {
  230. const messagePayload = JSON.parse(message.payload.toString());
  231. console.log('Received message as object: ', messagePayload.msg);
  232. this.c1Chart.setOption(this.c1OptionC);
  233. });
  234. }
  235. ngOnInit(): void {
  236. var c1 = document.getElementById('c1')!;
  237. this.c1Chart = echarts.init(c1);
  238. var c2 = document.getElementById('c2')!;
  239. var c2Chart = echarts.init(c2);
  240. let c2OptionC = {
  241. series: [
  242. {
  243. type: 'gauge',
  244. startAngle: 180,
  245. endAngle: 0,
  246. center: ['50%', '75%'],
  247. radius: '90%',
  248. min: 0,
  249. max: 1,
  250. splitNumber: 8,
  251. axisLine: {
  252. lineStyle: {
  253. width: 6,
  254. color: [
  255. [0.25, '#74FAFB'],
  256. [0.5, '#74FAFB'],
  257. [0.75, '#74FAFB'],
  258. [1, '#74FAFB']
  259. ]
  260. }
  261. },
  262. pointer: {
  263. icon: 'path://M12.8,0.7l12,40.1H0.7L12.8,0.7z',
  264. length: '12%',
  265. width: 20,
  266. offsetCenter: [0, '-60%'],
  267. itemStyle: {
  268. color: 'auto'
  269. }
  270. },
  271. axisTick: {
  272. length: 12,
  273. lineStyle: {
  274. color: 'auto',
  275. width: 2
  276. }
  277. },
  278. splitLine: {
  279. length: 20,
  280. lineStyle: {
  281. color: 'auto',
  282. width: 5
  283. }
  284. },
  285. axisLabel: {
  286. color: '#464646',
  287. fontSize: 20,
  288. distance: -60,
  289. rotate: 'tangential',
  290. formatter: function (value: any) {
  291. if (value === 0.875) {
  292. return '';
  293. } else if (value === 0.625) {
  294. return '';
  295. } else if (value === 0.375) {
  296. return '';
  297. } else if (value === 0.125) {
  298. return '';
  299. }
  300. return '';
  301. }
  302. },
  303. title: {
  304. offsetCenter: [0, '0%'],
  305. fontSize: 20
  306. },
  307. detail: {
  308. fontSize: 30,
  309. offsetCenter: [0, '-5%'],
  310. valueAnimation: true,
  311. formatter: function (value: any) {
  312. return Math.round(value * 100) + '';
  313. },
  314. color: 'inherit'
  315. },
  316. data: [
  317. {
  318. value: 0.97,
  319. name: ''
  320. }
  321. ]
  322. }
  323. ]
  324. };
  325. var d1 = document.getElementById('d1');
  326. var d1Chart = echarts.init(d1, 'dark');
  327. var d2 = document.getElementById('d2');
  328. var d2Chart = echarts.init(d2, 'dark');
  329. // var d3 = document.getElementById('d3');
  330. // var d3Chart = echarts.init(d3, 'dark');
  331. var errorData = [];
  332. var categoryData = [];
  333. var barData = [];
  334. var dataCount = 100;
  335. for (var i = 0; i < dataCount; i++) {
  336. var val = Math.random() * 1000;
  337. categoryData.push('category' + i);
  338. errorData.push([
  339. i,
  340. echarts.number.round(Math.max(0, val - Math.random() * 100)),
  341. echarts.number.round(val + Math.random() * 80)
  342. ]);
  343. barData.push(echarts.number.round(val, 2));
  344. }
  345. var optionD1;
  346. optionD1 = {
  347. title: {
  348. text: '全水',
  349. subtext: '(kg/kg)'
  350. },
  351. tooltip: {
  352. trigger: 'axis'
  353. },
  354. legend: {
  355. data: ['测量值', '上限值', '下限值',]
  356. },
  357. grid: {
  358. left: '3%',
  359. right: '3%',
  360. bottom: '3%',
  361. containLabel: true
  362. },
  363. toolbox: {
  364. show: false,
  365. feature: {
  366. dataView: { show: true, readOnly: false },
  367. magicType: { show: true, type: ['line', 'bar'] },
  368. restore: { show: true },
  369. saveAsImage: { show: true }
  370. }
  371. },
  372. calculable: true,
  373. xAxis: [
  374. {
  375. type: 'category',
  376. data: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12']
  377. }
  378. ],
  379. yAxis: [
  380. {
  381. type: 'value'
  382. }
  383. ],
  384. series: [
  385. {
  386. name: '测量值',
  387. type: 'line',
  388. data: [100, 155, 139, 199, 220, 160, 120, 182.2, 150, 155, 160, 180],
  389. markPoint: {
  390. data: [
  391. { name: 'Max', value: 220, xAxis: 4, yAxis: 220 },
  392. { name: 'Min', value: 100, xAxis: 0, yAxis: 100 }
  393. ]
  394. },
  395. markLine: {
  396. data: [{ type: 'average', name: 'Avg' }]
  397. }
  398. },
  399. {
  400. type: 'custom',
  401. name: 'error',
  402. itemStyle: {
  403. borderWidth: 1.5
  404. },
  405. renderItem: function (_params: any, api: any) {
  406. var xValue = api.value(0);
  407. var highPoint = api.coord([xValue, api.value(1)]);
  408. var lowPoint = api.coord([xValue, api.value(2)]);
  409. var halfWidth = api.size([1, 0])[0] * 0.1;
  410. var style = api.style({
  411. stroke: api.visual('color'),
  412. fill: undefined
  413. });
  414. return {
  415. type: 'group',
  416. children: [
  417. {
  418. type: 'line',
  419. transition: ['shape'],
  420. shape: {
  421. x1: highPoint[0] - halfWidth,
  422. y1: highPoint[1],
  423. x2: highPoint[0] + halfWidth,
  424. y2: highPoint[1]
  425. },
  426. style: style
  427. },
  428. {
  429. type: 'line',
  430. transition: ['shape'],
  431. shape: {
  432. x1: highPoint[0],
  433. y1: highPoint[1],
  434. x2: lowPoint[0],
  435. y2: lowPoint[1]
  436. },
  437. style: style
  438. },
  439. {
  440. type: 'line',
  441. transition: ['shape'],
  442. shape: {
  443. x1: lowPoint[0] - halfWidth,
  444. y1: lowPoint[1],
  445. x2: lowPoint[0] + halfWidth,
  446. y2: lowPoint[1]
  447. },
  448. style: style
  449. }
  450. ]
  451. };
  452. },
  453. encode: {
  454. x: 0,
  455. y: [1, 2]
  456. },
  457. data: errorData,
  458. z: 100
  459. }
  460. ]
  461. };
  462. this.chart_options1 = {
  463. title: {
  464. textStyle: {
  465. color: "#ffffff"
  466. },
  467. text: '全水',
  468. subtext: '(kg/kg)',
  469. },
  470. legend: {
  471. data: ['测量值', '上限值', '下限值',]
  472. },
  473. calculable: true,
  474. xAxis: [
  475. {
  476. axisLine: {
  477. lineStyle: {
  478. color: [
  479. [0.25, '#74FAFB'],
  480. [0.5, '#74FAFB'],
  481. [0.75, '#74FAFB'],
  482. [1, '#74FAFB']
  483. ]
  484. }
  485. },
  486. position: 'bottom',
  487. type: 'category',
  488. data: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12']
  489. }
  490. ],
  491. yAxis: [
  492. {
  493. type: 'value'
  494. }
  495. ],
  496. series: [
  497. {
  498. name: '测量值',
  499. type: 'line',
  500. data: [100, 155, 139, 199, 220, 160, 120, 182.2, 150, 155, 160, 180],
  501. markPoint: {
  502. data: [
  503. { name: '最大值', value: 220, xAxis: 4, yAxis: 220 },
  504. { name: '最小值', value: 100, xAxis: 0, yAxis: 100 }
  505. ]
  506. },
  507. markLine: {
  508. data: [{ type: 'average', name: '平均值' }]
  509. }
  510. },
  511. {
  512. type: 'custom',
  513. name: 'error',
  514. itemStyle: {
  515. borderWidth: 1.5
  516. },
  517. renderItem: function (_params: any, api: any) {
  518. var xValue = api.value(0);
  519. var highPoint = api.coord([xValue, api.value(1)]);
  520. var lowPoint = api.coord([xValue, api.value(2)]);
  521. var halfWidth = api.size([1, 0])[0] * 0.1;
  522. var style = api.style({
  523. stroke: api.visual('color'),
  524. fill: undefined
  525. });
  526. return {
  527. type: 'group',
  528. children: [
  529. {
  530. type: 'line',
  531. transition: ['shape'],
  532. shape: {
  533. x1: highPoint[0] - halfWidth,
  534. y1: highPoint[1],
  535. x2: highPoint[0] + halfWidth,
  536. y2: highPoint[1]
  537. },
  538. style: style
  539. },
  540. {
  541. type: 'line',
  542. transition: ['shape'],
  543. shape: {
  544. x1: highPoint[0],
  545. y1: highPoint[1],
  546. x2: lowPoint[0],
  547. y2: lowPoint[1]
  548. },
  549. style: style
  550. },
  551. {
  552. type: 'line',
  553. transition: ['shape'],
  554. shape: {
  555. x1: lowPoint[0] - halfWidth,
  556. y1: lowPoint[1],
  557. x2: lowPoint[0] + halfWidth,
  558. y2: lowPoint[1]
  559. },
  560. style: style
  561. }
  562. ]
  563. };
  564. },
  565. encode: {
  566. x: 0,
  567. y: [1, 2]
  568. },
  569. data: errorData,
  570. z: 100
  571. }
  572. ]
  573. };
  574. var optionD2;
  575. optionD2 = {
  576. title: {
  577. text: '热值',
  578. subtext: '(kg/kg)'
  579. },
  580. grid: {
  581. left: '3%',
  582. right: '3%',
  583. bottom: '3%',
  584. containLabel: true
  585. },
  586. tooltip: {
  587. trigger: 'axis'
  588. },
  589. legend: {
  590. data: ['超差样数量', '不合格样数量']
  591. },
  592. toolbox: {
  593. show: false,
  594. feature: {
  595. dataView: { show: true, readOnly: false },
  596. magicType: { show: true, type: ['line', 'bar'] },
  597. restore: { show: true },
  598. saveAsImage: { show: true }
  599. }
  600. },
  601. calculable: true,
  602. xAxis: [
  603. {
  604. type: 'category',
  605. // prettier-ignore
  606. data: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12']
  607. }
  608. ],
  609. yAxis: [
  610. {
  611. type: 'value'
  612. }
  613. ],
  614. series: [
  615. {
  616. name: '超差样数量',
  617. type: 'line',
  618. data: [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3],
  619. markPoint: {
  620. data: [
  621. { type: 'max', name: 'Max' },
  622. { type: 'min', name: 'Min' }
  623. ]
  624. }
  625. },
  626. {
  627. name: '不合格样数量',
  628. type: 'bar',
  629. data: [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3],
  630. markPoint: {
  631. data: [
  632. { name: 'Max', value: 182.2, xAxis: 7, yAxis: 183 },
  633. { name: 'Min', value: 2.3, xAxis: 11, yAxis: 3 }
  634. ]
  635. },
  636. markLine: {
  637. data: [{ type: 'average', name: 'Avg' }]
  638. }
  639. }
  640. ]
  641. };
  642. var optionD3;
  643. optionD3 = {
  644. title: {
  645. text: '全硫',
  646. subtext: '(kg/kg)'
  647. },
  648. tooltip: {
  649. trigger: 'axis'
  650. },
  651. legend: {
  652. data: ['超差样数量', '不合格样数量']
  653. },
  654. toolbox: {
  655. show: false,
  656. feature: {
  657. dataView: { show: true, readOnly: false },
  658. magicType: { show: true, type: ['line', 'bar'] },
  659. restore: { show: true },
  660. saveAsImage: { show: true }
  661. }
  662. },
  663. calculable: true,
  664. xAxis: [
  665. {
  666. type: 'category',
  667. // prettier-ignore
  668. data: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12']
  669. }
  670. ],
  671. yAxis: [
  672. {
  673. type: 'value'
  674. }
  675. ],
  676. series: [
  677. {
  678. name: '超差样数量',
  679. type: 'line',
  680. data: [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3],
  681. markPoint: {
  682. data: [
  683. { type: 'max', name: 'Max' },
  684. { type: 'min', name: 'Min' }
  685. ]
  686. }
  687. },
  688. {
  689. name: '不合格样数量',
  690. type: 'bar',
  691. data: [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3],
  692. markPoint: {
  693. data: [
  694. { name: 'Max', value: 182.2, xAxis: 7, yAxis: 183 },
  695. { name: 'Min', value: 2.3, xAxis: 11, yAxis: 3 }
  696. ]
  697. },
  698. markLine: {
  699. data: [{ type: 'average', name: 'Avg' }]
  700. }
  701. }
  702. ]
  703. };
  704. optionD1 && d1Chart.setOption(optionD1);
  705. optionD2 && d2Chart.setOption(optionD2);
  706. this.c1Chart.setOption(this.c1OptionC);
  707. c2Chart.setOption(c2OptionC);
  708. var r1 = document.getElementById('r1');
  709. var r1Chart = echarts.init(r1, 'dark');
  710. var r2 = document.getElementById('r2');
  711. var r2Chart = echarts.init(r2, 'dark');
  712. var r1SuperCount = 3; // 超差样量
  713. var r1TotalCount = 16; // 总样量
  714. var r2SuperCount = 15; // 合格样量
  715. var r2TotalCount = 16; // 总样量
  716. // 计算超差率
  717. var r1SuperRate = (r1SuperCount / r1TotalCount * 100).toFixed(2); // 保留两位小数
  718. // 计算合格率
  719. var r2SuperRate = (r2SuperCount / r2TotalCount * 100).toFixed(2); // 保留两位小数
  720. // Echarts配置项
  721. var r1Option = {
  722. title: {
  723. text: '煤样超差率',
  724. subtext: '超差率:' + r1SuperRate + '%'
  725. },
  726. tooltip: {
  727. trigger: 'axis',
  728. axisPointer: {
  729. type: 'shadow'
  730. }
  731. },
  732. grid: {
  733. top: '10%',
  734. left: '3%',
  735. right: '4%',
  736. bottom: '3%',
  737. containLabel: true
  738. },
  739. xAxis: {
  740. type: 'category',
  741. data: ['超差样量', '总样量']
  742. },
  743. yAxis: {
  744. type: 'value'
  745. },
  746. series: [{
  747. data: [r1SuperCount, r1TotalCount],
  748. type: 'bar'
  749. }]
  750. };
  751. // Echarts配置项
  752. var r2Option = {
  753. title: {
  754. text: '煤样合格率',
  755. subtext: '合格率:' + r2SuperRate + '%'
  756. },
  757. tooltip: {
  758. trigger: 'axis',
  759. axisPointer: {
  760. type: 'shadow'
  761. }
  762. },
  763. grid: {
  764. top: '10%',
  765. left: '3%',
  766. right: '4%',
  767. bottom: '3%',
  768. containLabel: true
  769. },
  770. xAxis: {
  771. type: 'category',
  772. data: ['超差样量', '总样量']
  773. },
  774. yAxis: {
  775. type: 'value'
  776. },
  777. series: [{
  778. data: [r2SuperCount, r2TotalCount],
  779. type: 'bar'
  780. }]
  781. };
  782. r1Chart.setOption(r1Option);
  783. r2Chart.setOption(r2Option);
  784. // myChart.setOption({
  785. // title: {
  786. // text: 'ECharts 入门示例'
  787. // },
  788. // tooltip: {},
  789. // xAxis: {
  790. // data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
  791. // },
  792. // yAxis: {},
  793. // series: [
  794. // {
  795. // name: '销量',
  796. // type: 'bar',
  797. // data: [5, 20, 36, 10, 10, 20]
  798. // }
  799. // ]
  800. // });
  801. }
  802. add(): void { }
  803. public unsafePublish(topic: string, message: string): void {
  804. this._mqttService.unsafePublish(topic, message, { qos: 1, retain: true });
  805. }
  806. ngOnDestroy(): void {
  807. this.subscription.unsubscribe();
  808. }
  809. }