VTrendline.mjs 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. import { createVNode as _createVNode } from "vue";
  2. // Utilities
  3. import { computed, nextTick, ref, watch } from 'vue';
  4. import { makeLineProps } from "./util/line.mjs";
  5. import { genPath as _genPath } from "./util/path.mjs";
  6. import { genericComponent, getPropertyFromItem, getUid, propsFactory, useRender } from "../../util/index.mjs"; // Types
  7. export const makeVTrendlineProps = propsFactory({
  8. fill: Boolean,
  9. ...makeLineProps()
  10. }, 'VTrendline');
  11. export const VTrendline = genericComponent()({
  12. name: 'VTrendline',
  13. props: makeVTrendlineProps(),
  14. setup(props, _ref) {
  15. let {
  16. slots
  17. } = _ref;
  18. const uid = getUid();
  19. const id = computed(() => props.id || `trendline-${uid}`);
  20. const autoDrawDuration = computed(() => Number(props.autoDrawDuration) || (props.fill ? 500 : 2000));
  21. const lastLength = ref(0);
  22. const path = ref(null);
  23. function genPoints(values, boundary) {
  24. const {
  25. minX,
  26. maxX,
  27. minY,
  28. maxY
  29. } = boundary;
  30. const totalValues = values.length;
  31. const maxValue = props.max != null ? Number(props.max) : Math.max(...values);
  32. const minValue = props.min != null ? Number(props.min) : Math.min(...values);
  33. const gridX = (maxX - minX) / (totalValues - 1);
  34. const gridY = (maxY - minY) / (maxValue - minValue || 1);
  35. return values.map((value, index) => {
  36. return {
  37. x: minX + index * gridX,
  38. y: maxY - (value - minValue) * gridY,
  39. value
  40. };
  41. });
  42. }
  43. const hasLabels = computed(() => {
  44. return Boolean(props.showLabels || props.labels.length > 0 || !!slots?.label);
  45. });
  46. const lineWidth = computed(() => {
  47. return parseFloat(props.lineWidth) || 4;
  48. });
  49. const totalWidth = computed(() => Number(props.width));
  50. const boundary = computed(() => {
  51. const padding = Number(props.padding);
  52. return {
  53. minX: padding,
  54. maxX: totalWidth.value - padding,
  55. minY: padding,
  56. maxY: parseInt(props.height, 10) - padding
  57. };
  58. });
  59. const items = computed(() => props.modelValue.map(item => getPropertyFromItem(item, props.itemValue, item)));
  60. const parsedLabels = computed(() => {
  61. const labels = [];
  62. const points = genPoints(items.value, boundary.value);
  63. const len = points.length;
  64. for (let i = 0; labels.length < len; i++) {
  65. const item = points[i];
  66. let value = props.labels[i];
  67. if (!value) {
  68. value = typeof item === 'object' ? item.value : item;
  69. }
  70. labels.push({
  71. x: item.x,
  72. value: String(value)
  73. });
  74. }
  75. return labels;
  76. });
  77. watch(() => props.modelValue, async () => {
  78. await nextTick();
  79. if (!props.autoDraw || !path.value) return;
  80. const pathRef = path.value;
  81. const length = pathRef.getTotalLength();
  82. if (!props.fill) {
  83. // Initial setup to "hide" the line by using the stroke dash array
  84. pathRef.style.strokeDasharray = `${length}`;
  85. pathRef.style.strokeDashoffset = `${length}`;
  86. // Force reflow to ensure the transition starts from this state
  87. pathRef.getBoundingClientRect();
  88. // Animate the stroke dash offset to "draw" the line
  89. pathRef.style.transition = `stroke-dashoffset ${autoDrawDuration.value}ms ${props.autoDrawEasing}`;
  90. pathRef.style.strokeDashoffset = '0';
  91. } else {
  92. // Your existing logic for filled paths remains the same
  93. pathRef.style.transformOrigin = 'bottom center';
  94. pathRef.style.transition = 'none';
  95. pathRef.style.transform = `scaleY(0)`;
  96. pathRef.getBoundingClientRect();
  97. pathRef.style.transition = `transform ${autoDrawDuration.value}ms ${props.autoDrawEasing}`;
  98. pathRef.style.transform = `scaleY(1)`;
  99. }
  100. lastLength.value = length;
  101. }, {
  102. immediate: true
  103. });
  104. function genPath(fill) {
  105. return _genPath(genPoints(items.value, boundary.value), props.smooth ? 8 : Number(props.smooth), fill, parseInt(props.height, 10));
  106. }
  107. useRender(() => {
  108. const gradientData = !props.gradient.slice().length ? [''] : props.gradient.slice().reverse();
  109. return _createVNode("svg", {
  110. "display": "block",
  111. "stroke-width": parseFloat(props.lineWidth) ?? 4
  112. }, [_createVNode("defs", null, [_createVNode("linearGradient", {
  113. "id": id.value,
  114. "gradientUnits": "userSpaceOnUse",
  115. "x1": props.gradientDirection === 'left' ? '100%' : '0',
  116. "y1": props.gradientDirection === 'top' ? '100%' : '0',
  117. "x2": props.gradientDirection === 'right' ? '100%' : '0',
  118. "y2": props.gradientDirection === 'bottom' ? '100%' : '0'
  119. }, [gradientData.map((color, index) => _createVNode("stop", {
  120. "offset": index / Math.max(gradientData.length - 1, 1),
  121. "stop-color": color || 'currentColor'
  122. }, null))])]), hasLabels.value && _createVNode("g", {
  123. "key": "labels",
  124. "style": {
  125. textAnchor: 'middle',
  126. dominantBaseline: 'mathematical',
  127. fill: 'currentColor'
  128. }
  129. }, [parsedLabels.value.map((item, i) => _createVNode("text", {
  130. "x": item.x + lineWidth.value / 2 + lineWidth.value / 2,
  131. "y": parseInt(props.height, 10) - 4 + (parseInt(props.labelSize, 10) || 7 * 0.75),
  132. "font-size": Number(props.labelSize) || 7
  133. }, [slots.label?.({
  134. index: i,
  135. value: item.value
  136. }) ?? item.value]))]), _createVNode("path", {
  137. "ref": path,
  138. "d": genPath(props.fill),
  139. "fill": props.fill ? `url(#${id.value})` : 'none',
  140. "stroke": props.fill ? 'none' : `url(#${id.value})`
  141. }, null), props.fill && _createVNode("path", {
  142. "d": genPath(false),
  143. "fill": "none",
  144. "stroke": props.color ?? props.gradient?.[0]
  145. }, null)]);
  146. });
  147. }
  148. });
  149. //# sourceMappingURL=VTrendline.mjs.map