tui-charts-column.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  1. <template>
  2. <view class="tui-charts__column-wrap">
  3. <view class="tui-column__legend" v-if="legend.show">
  4. <view class="tui-column__legend-item" v-for="(item,index) in dataset" :key="index">
  5. <view class="tui-legend__circle" :style="{backgroundColor:item.color}"></view>
  6. <text
  7. :style="{fontSize:(legend.size || 24)+'rpx',lineHeight:(legend.size || 24)+'rpx',color:legend.color || '#333'}">{{item.name}}</text>
  8. </view>
  9. </view>
  10. <view class="tui-charts__column-box" v-if="xAxis.length>0 && dataset.length>0">
  11. <scroll-view :scroll-x="scrollable" class="tui-column__scroll-view" :style="{height:scrollViewH+'rpx'}">
  12. <view :style="{height:(xAxisVal.height || 2) +'rpx'}" v-if="xAxisVal.show"></view>
  13. <view class="tui-charts__column" :style="{height:height+'rpx'}">
  14. <view class="tui-column__item"
  15. :class="{'tui-column__flex-1':!scrollable,'tui-column__flex-column':isStack,'tui-column__item-active':activeIndex===index && clickEffect==1,'tui-column__bar-opacity':clickEffect==2,'tui-column__bar-active':clickEffect==2 && activeIndex==index}"
  16. :style="{padding:scrollable? (xAxisLine.itemPadding ||'0 30rpx'):'0' }"
  17. v-for="(item,index) in xAxis" :key="index">
  18. <view class="tui-column__val"
  19. v-if="(xAxisVal.show && clickEffect!=2 ) || (xAxisVal.show && clickEffect==2 && activeIndex===index)"
  20. :style="{color:xAxisVal.color,fontSize:(xAxisVal.size || 24)+'rpx',whiteSpace: xAxisVal.nowrap?'nowrap':'normal'}">
  21. {{getYAxisVal(index)}}
  22. </view>
  23. <view class="tui-column__bar" :class="{'tui-column__bar-round':columnCap==='round'}"
  24. v-for="(bar,idx) in dataset" :key="idx"
  25. :style="{width:columnBarWidth+'rpx',borderTopColor:getBarColor(bar.source[index],bar.color,bar.colorFormatter),background:getBarColor(bar.source[index],bar.color,bar.colorFormatter),height:((bar.source[index]-(isStack?(min/dataset.length):min))/splitNumber)*(yAxisLine.itemGap || 60) +'rpx'}"
  26. @tap.stop="onBarTap(index,idx)">
  27. </view>
  28. <view class="tui-column__xAxis-text"
  29. :style="{color:xAxisLabel.color || '#333',fontSize:(xAxisLabel.size || 24)+'rpx' }">
  30. {{item}}
  31. </view>
  32. <view class="tui-xAxis__tickmarks"
  33. :style="{height:xAxisTick.height || '12rpx',backgroundColor:xAxisTick.color || '#e3e3e3'}">
  34. </view>
  35. </view>
  36. </view>
  37. </scroll-view>
  38. <view class="tui-column__border-left"
  39. :style="{height:height+(xAxisVal.show?(xAxisVal.height || 2):0)+'rpx',backgroundColor:yAxisLine.color || '#e3e3e3'}">
  40. </view>
  41. <view class="tui-xAxis__line" :class="{'tui-line__first':index===0}"
  42. :style="{bottom:index*(yAxisLine.itemGap || 60)+(xAxisLabel.height || 60)+'rpx',borderTopStyle:index===0?'solid':splitLine.type,borderTopColor:index===0?xAxisLine.color:splitLine.color}"
  43. v-for="(item,index) in yAxisData" :key="index">
  44. <text class="tui-yAxis__val"
  45. :style="{color:item.color || yAxisLabel.color,fontSize:(yAxisLabel.size||24)+'rpx'}"
  46. v-if="yAxisLabel.show">{{item.value}}</text>
  47. </view>
  48. </view>
  49. <view class="tui-column__tooltip" v-if="tooltip" :class="{'tui-column__tooltip-show':tooltipShow}">
  50. <view class="tui-tooltip__title">{{xAxis[activeIndex] || ''}}</view>
  51. <view class="tui-column__tooltip-item" v-for="(item,index) in tooltips" :key="index">
  52. <view class="tui-legend__circle" :style="{backgroundColor:item.color}"></view>
  53. <text class="tui-tooltip__val">{{item.name}}</text>
  54. <text class="tui-tooltip__val tui-tooltip__val-ml">{{item.val}}</text>
  55. </view>
  56. </view>
  57. </view>
  58. </template>
  59. <script>
  60. export default {
  61. name: "tui-charts-column",
  62. emits: ['click'],
  63. props: {
  64. //图例,说明
  65. legend: {
  66. type: Object,
  67. default () {
  68. return {
  69. show: false,
  70. size: 24,
  71. color: '#333'
  72. }
  73. }
  74. },
  75. tooltip: {
  76. type: Boolean,
  77. default: false
  78. },
  79. xAxis: {
  80. type: Array,
  81. default () {
  82. return []
  83. }
  84. },
  85. //默认选中x轴索引
  86. currentIndex: {
  87. type: Number,
  88. default: -1
  89. },
  90. //柱状条宽度
  91. columnBarWidth: {
  92. type: [Number, String],
  93. default: 32
  94. },
  95. //分割线
  96. splitLine: {
  97. type: Object,
  98. default () {
  99. return {
  100. //分割线颜色,不显示则将颜色设置为transparent
  101. color: "#e3e3e3",
  102. type: "dashed"
  103. }
  104. }
  105. },
  106. //x轴刻度线
  107. xAxisTick: {
  108. type: Object,
  109. default () {
  110. return {
  111. height: '12rpx',
  112. //不显示则将颜色设置为transparent
  113. color: '#e3e3e3'
  114. }
  115. }
  116. },
  117. //x轴线条
  118. xAxisLine: {
  119. type: Object,
  120. default () {
  121. return {
  122. color: '#e3e3e3',
  123. //x轴item的padding值
  124. itemPadding: '0 30rpx'
  125. }
  126. }
  127. },
  128. xAxisLabel: {
  129. type: Object,
  130. default () {
  131. return {
  132. color: "#333",
  133. size: 24,
  134. height: 60
  135. }
  136. }
  137. },
  138. xAxisVal: {
  139. type: Object,
  140. default () {
  141. return {
  142. show: false,
  143. color: "#333",
  144. size: 24,
  145. //如果show为true且val显示的时候,height需要设置一定的值保证val能显示完整 rpx
  146. height: 60
  147. }
  148. }
  149. },
  150. //y轴数据,如果不传则默认使用min,max值计算
  151. // {
  152. // value: 0,
  153. // color: "#333"
  154. // }
  155. yAxis: {
  156. type: Array,
  157. default () {
  158. return []
  159. }
  160. },
  161. //y轴最小值
  162. min: {
  163. type: Number,
  164. default: 0
  165. },
  166. //y轴最大值
  167. max: {
  168. type: Number,
  169. default: 100
  170. },
  171. //y轴分段递增数值
  172. splitNumber: {
  173. type: Number,
  174. default: 20
  175. },
  176. yAxisLine: {
  177. type: Object,
  178. default () {
  179. return {
  180. //不显示则将颜色设置为transparent
  181. color: '#e3e3e3',
  182. //y轴item间距 rpx
  183. itemGap: 60
  184. }
  185. }
  186. },
  187. yAxisLabel: {
  188. type: Object,
  189. default () {
  190. return {
  191. show: true,
  192. color: "#333",
  193. size: 24
  194. }
  195. }
  196. },
  197. //是否可滚动
  198. scrollable: {
  199. type: Boolean,
  200. default: false
  201. },
  202. //是否堆叠展示
  203. isStack: {
  204. type: Boolean,
  205. default: false
  206. },
  207. //柱状条点击效果:1-出现背景,2-高亮显示,其他变暗 3-无效果
  208. clickEffect: {
  209. type: Number,
  210. default: 1
  211. },
  212. /*
  213. 柱状条的端点样式
  214. round 向线条的每个末端添加圆形线帽
  215. square 向线条的每个末端添加正方形线帽
  216. */
  217. columnCap: {
  218. type: String,
  219. default: 'square'
  220. }
  221. },
  222. data() {
  223. return {
  224. height: 0,
  225. scrollViewH: 0,
  226. sections: 0,
  227. yAxisData: [],
  228. activeIndex: -1,
  229. activeIdx: -1,
  230. tooltips: [],
  231. tooltipShow: false,
  232. timer: null,
  233. /*========options============*/
  234. /*
  235. name: '',
  236. color: '',
  237. source: []
  238. colorFormatter:Function
  239. */
  240. dataset: [],
  241. xAxisValFormatter: null
  242. }
  243. },
  244. watch: {
  245. yAxis(newVal) {
  246. this.init()
  247. },
  248. currentIndex(newVal) {
  249. if (newVal != this.activeIndex) {
  250. this.activeIndex = newVal
  251. }
  252. }
  253. },
  254. created() {
  255. this.init()
  256. this.activeIndex = this.currentIndex;
  257. },
  258. // #ifndef VUE3
  259. beforeDestroy() {
  260. this.clearTimer()
  261. },
  262. // #endif
  263. // #ifdef VUE3
  264. beforeUnmount() {
  265. this.clearTimer()
  266. },
  267. // #endif
  268. methods: {
  269. generateArray(start, end) {
  270. return Array.from(new Array(end + 1).keys()).slice(start);
  271. },
  272. getYAxisVal(index) {
  273. let showVal = '';
  274. let val = 0;
  275. if (this.dataset.length === 1) {
  276. val = this.dataset[0].source[index]
  277. showVal = val;
  278. } else if (this.dataset.length > 1) {
  279. let arr = []
  280. this.dataset.forEach(item => {
  281. arr.push(item.source[index])
  282. })
  283. val = arr
  284. }
  285. if (this.xAxisVal.formatter && typeof this.xAxisVal.formatter === 'function') {
  286. showVal = this.xAxisVal.formatter(val)
  287. } else if (this.xAxisValFormatter && typeof this.xAxisValFormatter === 'function') {
  288. showVal = this.xAxisValFormatter(val)
  289. }
  290. return showVal
  291. },
  292. getBarColor(val, color, colorFormatter) {
  293. let bgColor = color;
  294. if (colorFormatter && typeof colorFormatter === 'function') {
  295. let formatColor = colorFormatter(val)
  296. if (formatColor) {
  297. bgColor = formatColor
  298. }
  299. }
  300. return bgColor
  301. },
  302. init() {
  303. let itemGap = this.yAxisLine.itemGap || 60;
  304. let sections = this.yAxis.length - 1;
  305. let yAxis = this.yAxis;
  306. if (sections <= 0) {
  307. sections = Math.ceil((this.max - this.min) / this.splitNumber)
  308. let sectionsArr = this.generateArray(0, sections)
  309. yAxis = sectionsArr.map(item => {
  310. return {
  311. value: item * this.splitNumber + this.min
  312. }
  313. })
  314. }
  315. this.yAxisData = yAxis;
  316. this.sections = sections + 1;
  317. this.height = itemGap * sections;
  318. const valH = this.xAxisVal.show ? (this.xAxisVal.height || 2) : 0;
  319. this.scrollViewH = this.height + (this.xAxisLabel.height || 60) + valH;
  320. },
  321. clearTimer() {
  322. clearTimeout(this.timer)
  323. this.timer = null;
  324. },
  325. tooltipHandle(index) {
  326. let data = [...this.dataset]
  327. let tooltips = []
  328. data.forEach(item => {
  329. let color = item.color;
  330. if (item.colorFormatter && typeof item.colorFormatter === 'function') {
  331. color = item.colorFormatter(item.source[index])
  332. }
  333. tooltips.push({
  334. color: color,
  335. name: item.name,
  336. val: item.source[index]
  337. })
  338. })
  339. this.tooltips = tooltips;
  340. this.clearTimer()
  341. this.tooltipShow = true;
  342. this.timer = setTimeout(() => {
  343. this.tooltipShow = false
  344. }, 5000)
  345. },
  346. onBarTap(index, idx) {
  347. this.activeIndex = index;
  348. this.activeIdx = idx;
  349. this.tooltipHandle(index);
  350. this.$emit('click', {
  351. datasetIndex: idx,
  352. sourceIndex: index,
  353. ...this.dataset[idx]
  354. })
  355. },
  356. /*
  357. dataset:柱状图表数据
  358. xAxisValFormatter :格式化柱状条顶部value值(此处传值是为了做兼容处理)
  359. */
  360. draw(dataset, xAxisValFormatter) {
  361. this.xAxisValFormatter = xAxisValFormatter || null;
  362. this.dataset = dataset || [];
  363. this.init();
  364. }
  365. }
  366. }
  367. </script>
  368. <style scoped>
  369. .tui-charts__column-wrap {
  370. position: relative;
  371. overflow: visible;
  372. }
  373. .tui-charts__column-box {
  374. position: relative;
  375. padding-left: 1px;
  376. box-sizing: border-box;
  377. }
  378. .tui-column__scroll-view {
  379. position: relative;
  380. z-index: 10;
  381. box-sizing: border-box;
  382. }
  383. .tui-charts__column {
  384. width: 100%;
  385. position: relative;
  386. display: flex;
  387. align-items: flex-end;
  388. position: relative;
  389. }
  390. .tui-column__between {
  391. justify-content: space-between;
  392. }
  393. .tui-column__item {
  394. display: flex;
  395. align-items: flex-end;
  396. justify-content: center;
  397. position: relative;
  398. text-align: center;
  399. box-sizing: border-box;
  400. z-index: 10;
  401. transition: all 0.3s;
  402. -webkit-backface-visibility: hidden;
  403. backface-visibility: hidden;
  404. -webkit-font-smoothing: antialiased;
  405. }
  406. .tui-column__bar-opacity {
  407. opacity: 0.6;
  408. }
  409. .tui-column__bar-active {
  410. /*这里请勿随意将值改为1*/
  411. opacity: 0.9999;
  412. }
  413. .tui-column__flex-1 {
  414. flex: 1;
  415. }
  416. .tui-column__item-active {
  417. background-color: rgba(0, 0, 0, .1);
  418. padding-top: 20rpx !important;
  419. }
  420. .tui-column__flex-column {
  421. flex-direction: column;
  422. justify-content: flex-end;
  423. align-items: center;
  424. }
  425. .tui-xAxis__tickmarks {
  426. position: absolute;
  427. right: 0;
  428. width: 1px;
  429. transform: translateY(100%);
  430. bottom: 0;
  431. }
  432. .tui-column__bar {
  433. transition: all 0.3s;
  434. flex-shrink: 0;
  435. text-align: center;
  436. /* #ifdef H5 */
  437. cursor: pointer;
  438. /* #endif */
  439. /* border-top: 1px solid; */
  440. -webkit-backface-visibility: hidden;
  441. backface-visibility: hidden;
  442. -webkit-font-smoothing: antialiased;
  443. }
  444. .tui-column__bar-round {
  445. border-top-left-radius: 100px;
  446. border-top-right-radius: 100px;
  447. }
  448. .tui-column__val {
  449. width: 100%;
  450. position: absolute;
  451. top: 0;
  452. left: 50%;
  453. padding-bottom: 12rpx;
  454. transform: translate(-50%, -100%);
  455. -webkit-backface-visibility: hidden;
  456. backface-visibility: hidden;
  457. -webkit-font-smoothing: antialiased;
  458. word-break: break-all;
  459. }
  460. .tui-column__xAxis-text {
  461. width: 100%;
  462. position: absolute;
  463. left: 50%;
  464. bottom: 0;
  465. flex: 1;
  466. transform: translate(-50%, 100%);
  467. padding-top: 8rpx;
  468. word-break: break-all;
  469. -webkit-backface-visibility: hidden;
  470. backface-visibility: hidden;
  471. -webkit-font-smoothing: antialiased;
  472. }
  473. .tui-column__border-left {
  474. position: absolute;
  475. left: 0;
  476. top: 0;
  477. width: 1px;
  478. z-index: 11;
  479. }
  480. .tui-xAxis__line {
  481. width: 100%;
  482. height: 0;
  483. border-top-width: 1px;
  484. position: absolute;
  485. left: 0;
  486. display: flex;
  487. align-items: center;
  488. }
  489. .tui-line__first {
  490. z-index: 12;
  491. }
  492. .tui-yAxis__val {
  493. transform: translateX(-100%);
  494. padding-right: 12rpx;
  495. -webkit-backface-visibility: hidden;
  496. backface-visibility: hidden;
  497. -webkit-font-smoothing: antialiased;
  498. }
  499. .tui-column__legend {
  500. display: flex;
  501. align-items: center;
  502. flex-wrap: wrap;
  503. }
  504. .tui-column__legend-item {
  505. display: flex;
  506. align-items: center;
  507. margin-left: 24rpx;
  508. margin-bottom: 30rpx;
  509. }
  510. .tui-legend__circle {
  511. height: 20rpx;
  512. width: 20rpx;
  513. border-radius: 50%;
  514. margin-right: 8rpx;
  515. flex-shrink: 0;
  516. }
  517. .tui-column__tooltip {
  518. padding: 30rpx;
  519. border-radius: 12rpx;
  520. background-color: rgba(0, 0, 0, .6);
  521. display: inline-block;
  522. position: absolute;
  523. top: 50%;
  524. left: 50%;
  525. transform: translate(-50%, -50%);
  526. z-index: 20;
  527. visibility: hidden;
  528. opacity: 0;
  529. transition: all 0.3s;
  530. }
  531. .tui-column__tooltip-show {
  532. visibility: visible;
  533. opacity: 1;
  534. }
  535. .tui-tooltip__title {
  536. font-size: 30rpx;
  537. color: #fff;
  538. line-height: 30rpx;
  539. }
  540. .tui-column__tooltip-item {
  541. display: flex;
  542. align-items: center;
  543. padding-top: 24rpx;
  544. white-space: nowrap;
  545. }
  546. .tui-tooltip__val {
  547. font-size: 24rpx;
  548. line-height: 24rpx;
  549. color: #fff;
  550. margin-left: 6rpx;
  551. }
  552. .tui-tooltip__val-ml {
  553. margin-left: 20rpx;
  554. }
  555. </style>