SpeedometerChart.vue 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. <template>
  2. <q-card v-bind="$attrs" class="q-px-md full-height column justify-between">
  3. <q-card-section class="q-pa-none">
  4. <div class="row justify-between no-wrap items-start q-py-md q-px-sm">
  5. <div>
  6. <div class="text-bold text-description q-mb-sm">
  7. {{ String(props.title).toLocaleUpperCase() }}
  8. </div>
  9. <span>{{ props.subTitle }}</span>
  10. </div>
  11. <q-btn
  12. icon="mdi-tray-arrow-down"
  13. class="q-ml-md"
  14. dense
  15. flat
  16. @click="downloadImage"
  17. />
  18. </div>
  19. <q-separator dark />
  20. </q-card-section>
  21. <div class="graph-container">
  22. <Doughnut
  23. ref="chart_ref"
  24. :options="chartOptions"
  25. :data="chartData"
  26. :plugins="[ChartDataLabels, gaugeNeedle]"
  27. />
  28. </div>
  29. </q-card>
  30. </template>
  31. <script setup>
  32. import { ref } from "vue";
  33. import { Chart as ChartJS, ArcElement, Tooltip, Legend } from "chart.js";
  34. import { Doughnut } from "vue-chartjs";
  35. import ChartDataLabels from "chartjs-plugin-datalabels";
  36. import { base64ToJPEG } from "src/helpers/convertBase64Image";
  37. const props = defineProps({
  38. title: {
  39. type: String,
  40. required: true,
  41. },
  42. valorAgulha: {
  43. type: Number,
  44. required: true,
  45. },
  46. subTitle: {
  47. type: String,
  48. default: null,
  49. },
  50. });
  51. const chart_ref = ref(null);
  52. const titulo = ref(`Valor: ${props.valorAgulha}`);
  53. const gaugeNeedle = {
  54. id: "gaugeNeedle",
  55. afterDatasetsDraw(chart) {
  56. const { ctx, data } = chart;
  57. ctx.save();
  58. const needleValue = data.datasets[0].needleValue;
  59. const xCenter = chart.getDatasetMeta(0).data[0].x;
  60. const yCenter = chart.getDatasetMeta(0).data[0].y;
  61. const outerRadius = chart.getDatasetMeta(0).data[0].outerRadius - 40;
  62. const angle = Math.PI;
  63. let circumference =
  64. (chart.getDatasetMeta(0).data[0].circumference /
  65. Math.PI /
  66. data.datasets[0].data[0]) *
  67. needleValue;
  68. const needleAngleValue = circumference + 1.5;
  69. ctx.translate(xCenter, yCenter);
  70. ctx.rotate(angle * needleAngleValue);
  71. // Draw the needle
  72. ctx.beginPath();
  73. ctx.strokeStyle = "grey";
  74. ctx.fillStyle = "grey";
  75. ctx.moveTo(0 - 4, 0);
  76. ctx.lineTo(0, -outerRadius);
  77. ctx.lineTo(0 + 4, 0);
  78. ctx.stroke();
  79. ctx.fill();
  80. ctx.beginPath();
  81. ctx.arc(0, 0, 8, 0, 2 * Math.PI);
  82. ctx.fillStyle = "grey";
  83. ctx.fill();
  84. ctx.restore();
  85. },
  86. };
  87. ChartJS.register(ArcElement, Tooltip, Legend);
  88. const chartData = ref({
  89. datasets: [
  90. {
  91. backgroundColor: [
  92. "#00a550",
  93. "#4dbb7e",
  94. "#9ad2ad",
  95. "#cce156",
  96. "#fff100",
  97. "#ffbe00",
  98. "#ff8c00",
  99. "#FC3D23",
  100. "#D01616",
  101. "#8A0000",
  102. ],
  103. data: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
  104. needleValue: props.valorAgulha,
  105. borderColor: "transparent",
  106. },
  107. ],
  108. });
  109. const chartOptions = ref({
  110. rotation: 270,
  111. circumference: 180,
  112. cutout: "50%",
  113. plugins: {
  114. tooltip: {
  115. enabled: false,
  116. },
  117. title: {
  118. display: true,
  119. text: titulo.value,
  120. color: "#ffffff",
  121. position: "bottom",
  122. },
  123. datalabels: {
  124. color: "black",
  125. font: {
  126. size: 14,
  127. weight: "bold",
  128. },
  129. formatter: (value, ctx) => {
  130. const valor = ctx.dataIndex;
  131. return valor;
  132. },
  133. },
  134. },
  135. });
  136. addEventListener("resize", () => {
  137. if (chart_ref.value) {
  138. chart_ref.value.chart.update();
  139. }
  140. });
  141. const downloadImage = () => {
  142. const image = chart_ref.value.chart?.toBase64Image("image/jpeg", 1);
  143. base64ToJPEG(image, props.title);
  144. };
  145. </script>
  146. <style scoped>
  147. .graph-container {
  148. display: flex;
  149. align-items: center;
  150. justify-content: center;
  151. width: 100%;
  152. max-height: 300px;
  153. }
  154. @media screen and (max-width: 1600px) {
  155. .graph-container {
  156. max-height: 250px;
  157. }
  158. }
  159. @media screen and (max-width: 1360px) {
  160. .graph-container {
  161. max-height: 200px;
  162. }
  163. }
  164. @media screen and (max-width: 1130px) {
  165. .graph-container {
  166. max-height: 175px;
  167. }
  168. }
  169. @media screen and (max-width: 888px) {
  170. .graph-container {
  171. max-height: 250px;
  172. }
  173. }
  174. @media screen and (max-width: 650px) {
  175. .graph-container {
  176. max-height: 300px;
  177. }
  178. }
  179. </style>