LineChart.vue 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. <template>
  2. <div v-bind="$attrs" class="chart-wrapper full-width full-height">
  3. <q-resize-observer @resize="onResize" />
  4. <div v-if="hasData" class="chart-container">
  5. <Line
  6. ref="chart_ref"
  7. :options="chartLineOptions"
  8. :data="chartLineData"
  9. :plugins="[ChartDataLabels]"
  10. />
  11. </div>
  12. <div v-else class="no-data-container">
  13. <span :class="textColor">{{ $t("http.errors.no_records_found") }}</span>
  14. </div>
  15. </div>
  16. </template>
  17. <script setup>
  18. import { ref, computed } from "vue";
  19. import { Line } from "vue-chartjs";
  20. import {
  21. Chart as ChartJS,
  22. Title,
  23. Tooltip,
  24. Legend,
  25. LineElement,
  26. PointElement,
  27. CategoryScale,
  28. LinearScale,
  29. } from "chart.js";
  30. import ChartDataLabels from "chartjs-plugin-datalabels";
  31. import { base64ToJPEG } from "src/helpers/convertBase64Image";
  32. import { useQuasar, getCssVar, colors } from "quasar";
  33. ChartJS.register(
  34. Title,
  35. Tooltip,
  36. Legend,
  37. LineElement,
  38. PointElement,
  39. CategoryScale,
  40. LinearScale,
  41. );
  42. const $q = useQuasar();
  43. const { lighten } = colors;
  44. const chart_ref = ref(null);
  45. const props = defineProps({
  46. data: {
  47. type: Object,
  48. default: () => ({
  49. chart_data: [],
  50. }),
  51. },
  52. title: {
  53. type: String,
  54. default: "",
  55. },
  56. dataSetLabel: {
  57. type: String,
  58. default: "Valores",
  59. },
  60. labelX: {
  61. type: String,
  62. default: "Categorias",
  63. },
  64. labelY: {
  65. type: String,
  66. default: "Valores",
  67. },
  68. backgroundColors: {
  69. type: Array,
  70. default: null,
  71. },
  72. });
  73. const onResize = () => {
  74. if (chart_ref.value?.chart) {
  75. setTimeout(() => {
  76. chart_ref.value.chart.resize();
  77. }, 50);
  78. }
  79. };
  80. const textColor = computed(() => {
  81. return $q.dark.isActive ? "text-white" : "text-black";
  82. });
  83. const labelColor = computed(() => {
  84. return $q.dark.isActive ? "#ffffff" : "#000000";
  85. });
  86. const gridColor = computed(() => {
  87. return $q.dark.isActive ? "rgba(255, 255, 255, 0.1)" : "rgba(0, 0, 0, 0.1)";
  88. });
  89. const dataLabelColor = computed(() => {
  90. return $q.dark.isActive ? "#ffffff" : "#000000";
  91. });
  92. const hasData = computed(() => {
  93. return props.data?.chart_data && props.data.chart_data.length > 0;
  94. });
  95. const chartLabels = computed(() => {
  96. return props.data?.chart_data?.map((item) => item.label) || [];
  97. });
  98. const chartValues = computed(() => {
  99. return props.data?.chart_data?.map((item) => item.value) || [];
  100. });
  101. const chartThemeColors = computed(() => {
  102. if (props.backgroundColors) {
  103. return props.backgroundColors;
  104. }
  105. const primaryColor = getCssVar("primary");
  106. if (!primaryColor) return [];
  107. const numColors = chartValues.value.length;
  108. const step = numColors > 0 ? 50 / numColors : 0;
  109. return Array.from({ length: numColors }, (_, i) =>
  110. lighten(primaryColor, i * step),
  111. );
  112. });
  113. const chartLineData = computed(() => ({
  114. labels: chartLabels.value,
  115. datasets: [
  116. {
  117. label: props.dataSetLabel,
  118. data: chartValues.value,
  119. borderColor: chartThemeColors.value[0],
  120. backgroundColor: chartThemeColors.value,
  121. fill: false,
  122. cubicInterpolationMode: "monotone",
  123. tension: 0.4,
  124. },
  125. ],
  126. }));
  127. const chartLineOptions = computed(() => ({
  128. responsive: true,
  129. maintainAspectRatio: false,
  130. plugins: {
  131. legend: {
  132. display: false,
  133. },
  134. title: {
  135. display: !!props.title,
  136. text: props.title,
  137. color: labelColor.value,
  138. font: {
  139. size: 16,
  140. },
  141. },
  142. datalabels: {
  143. color: dataLabelColor.value,
  144. anchor: "end",
  145. align: "top",
  146. offset: 4,
  147. font: {
  148. size: 12,
  149. weight: "bold",
  150. },
  151. formatter: (value) => {
  152. return value > 0 ? value : "";
  153. },
  154. },
  155. tooltip: {
  156. backgroundColor: $q.dark.isActive
  157. ? "rgba(0, 0, 0, 0.8)"
  158. : "rgba(255, 255, 255, 0.9)",
  159. titleColor: labelColor.value,
  160. bodyColor: labelColor.value,
  161. borderColor: labelColor.value,
  162. borderWidth: 1,
  163. },
  164. },
  165. interaction: {
  166. intersect: false,
  167. },
  168. scales: {
  169. x: {
  170. display: true,
  171. title: {
  172. display: !!props.labelX,
  173. text: props.labelX,
  174. color: labelColor.value,
  175. font: {
  176. size: 14,
  177. },
  178. },
  179. grid: {
  180. color: gridColor.value,
  181. tickColor: gridColor.value,
  182. },
  183. ticks: {
  184. color: labelColor.value,
  185. },
  186. },
  187. y: {
  188. display: true,
  189. title: {
  190. display: !!props.labelY,
  191. text: props.labelY,
  192. color: labelColor.value,
  193. font: {
  194. size: 14,
  195. },
  196. },
  197. suggestedMin: 0,
  198. grid: {
  199. color: gridColor.value,
  200. tickColor: gridColor.value,
  201. },
  202. ticks: {
  203. color: labelColor.value,
  204. stepSize: 1,
  205. },
  206. },
  207. },
  208. }));
  209. const downloadImage = () => {
  210. if (!chart_ref.value?.chart) return;
  211. const image = chart_ref.value.chart.toBase64Image("image/jpeg", 1);
  212. base64ToJPEG(image, props.title || "line-chart");
  213. };
  214. defineExpose({
  215. downloadImage,
  216. chart_ref,
  217. });
  218. </script>
  219. <style scoped>
  220. .chart-wrapper {
  221. position: relative;
  222. }
  223. .chart-container {
  224. position: absolute;
  225. top: 10px;
  226. left: 0;
  227. right: 0;
  228. bottom: 0;
  229. width: 100%;
  230. height: 100%;
  231. }
  232. .no-data-container {
  233. position: absolute;
  234. top: 0;
  235. left: 0;
  236. right: 0;
  237. bottom: 0;
  238. display: flex;
  239. align-items: center;
  240. justify-content: center;
  241. padding: 1.5rem;
  242. }
  243. </style>