580 lines
13 KiB
Vue
580 lines
13 KiB
Vue
<template>
|
||
<view class="tui-charts__bar-wrap" :style="{width:width+'rpx'}">
|
||
<view class="tui-bar__legend" v-if="legend.show">
|
||
<view class="tui-bar__legend-item" v-for="(item,index) in dataset" :key="index">
|
||
<view class="tui-legend__circle" :style="{backgroundColor:item.color}"></view>
|
||
<text
|
||
:style="{fontSize:(legend.size || 24)+'rpx',lineHeight:(legend.size || 24)+'rpx',color:legend.color || '#333'}">{{item.name}}</text>
|
||
</view>
|
||
</view>
|
||
<view class="tui-charts__bar-box" :style="{borderLeftColor:yAxisLine.color,borderBottomColor:xAxisLineColor}"
|
||
v-if="yAxis.length>0 && dataset.length>0">
|
||
<view class="tui-charts__bar-item"
|
||
:class="{'tui-bar__item-stack':isStack,'tui-column__item-active':activeIndex===index && clickEffect==1,'tui-column__bar-opacity':clickEffect==2,'tui-column__bar-active':clickEffect==2 && activeIndex==index}"
|
||
v-for="(item,index) in yAxis" :key="index" :style="{padding:yAxisLine.itemPadding ||'30rpx 0'}">
|
||
<view class="tui-charts__bar" :class="{'tui-charts__bar-round':columnCap==='round'}"
|
||
v-for="(bar,idx) in dataset" :key="idx"
|
||
:style="{height:columnBarHeight+'rpx',borderRightColor:getBarColor(bar.source[index],bar.color,bar.colorFormatter),background:getBarColor(bar.source[index],bar.color,bar.colorFormatter),width:getBarWidth(bar.source[index],dataset),marginLeft:getMarginLeft(bar.source[index])}"
|
||
@tap.stop="onBarTap(index,idx)">
|
||
</view>
|
||
<view class="tui-bar__val"
|
||
v-if="(yAxisVal.show && clickEffect!=2 ) || (yAxisVal.show && clickEffect==2 && activeIndex===index)"
|
||
:style="{color:yAxisVal.color,fontSize:(yAxisVal.size || 24)+'rpx',whiteSpace: yAxisVal.nowrap?'nowrap':'normal',left:getLeft(index)}">
|
||
{{getXAxisVal(index)}}
|
||
</view>
|
||
<view class="tui-bar__yAxis-text"
|
||
:style="{color:yAxisLabel.color || '#333',fontSize:(yAxisLabel.size || 24)+'rpx' }">
|
||
{{item}}
|
||
</view>
|
||
<view class="tui-yAxis__tickmarks"
|
||
:style="{width:yAxisTick.width || '12rpx',backgroundColor:yAxisTick.color || '#e3e3e3'}"></view>
|
||
</view>
|
||
<view class="tui-bar__yAxis-linebox">
|
||
<view class="tui-bar__yAxis-line" :class="{'tui-yAxis__line-first':idx===0}"
|
||
v-for="(item,idx) in xAxisData" :key="idx"
|
||
:style="{borderLeftStyle:splitLine.type,borderLeftColor:splitLine.color}">
|
||
<text class="tui-xAxis__val"
|
||
:style="{color:item.color || xAxisLabel.color,fontSize:(xAxisLabel.size||24)+'rpx'}"
|
||
v-if="xAxisLabel.show">{{item.value}}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="tui-column__tooltip" v-if="tooltip" :class="{'tui-column__tooltip-show':tooltipShow}">
|
||
<view class="tui-tooltip__title">{{yAxis[activeIndex] || ''}}</view>
|
||
<view class="tui-column__tooltip-item" v-for="(item,index) in tooltips" :key="index">
|
||
<view class="tui-legend__circle" :style="{backgroundColor:item.color}"></view>
|
||
<text class="tui-tooltip__val">{{item.name}}</text>
|
||
<text class="tui-tooltip__val tui-tooltip__val-ml">{{item.val}}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
name: "tui-charts-bar",
|
||
emits: ['click'],
|
||
props: {
|
||
//图表宽度 rpx
|
||
width: {
|
||
type: [Number, String],
|
||
default: 600
|
||
},
|
||
//图例,说明
|
||
legend: {
|
||
type: Object,
|
||
default () {
|
||
return {
|
||
show: false,
|
||
size: 24,
|
||
color: '#333'
|
||
}
|
||
}
|
||
},
|
||
tooltip: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
//x轴数据,如果不传则默认使用min,max值计算
|
||
// {
|
||
// value: 0,
|
||
// color: "#a0a0a0"
|
||
// }
|
||
xAxis: {
|
||
type: Array,
|
||
default () {
|
||
return []
|
||
}
|
||
},
|
||
//x轴最小值
|
||
min: {
|
||
type: Number,
|
||
default: 0
|
||
},
|
||
//x轴最大值
|
||
max: {
|
||
type: Number,
|
||
default: 100
|
||
},
|
||
//x轴分段递增数值
|
||
splitNumber: {
|
||
type: Number,
|
||
default: 20
|
||
},
|
||
//分割线
|
||
splitLine: {
|
||
type: Object,
|
||
default () {
|
||
return {
|
||
//分割线颜色,不显示则将颜色设置为transparent
|
||
color: "#e3e3e3",
|
||
type: "dashed"
|
||
}
|
||
}
|
||
},
|
||
xAxisLineColor: {
|
||
type: String,
|
||
//不显示则将颜色设置为transparent
|
||
default: '#e3e3e3'
|
||
},
|
||
xAxisLabel: {
|
||
type: Object,
|
||
default () {
|
||
return {
|
||
show: true,
|
||
color: "#333",
|
||
size: 24
|
||
}
|
||
}
|
||
},
|
||
yAxis: {
|
||
type: Array,
|
||
default () {
|
||
return []
|
||
}
|
||
},
|
||
//柱状条高度
|
||
columnBarHeight: {
|
||
type: [Number, String],
|
||
default: 32
|
||
},
|
||
//y轴刻度线
|
||
yAxisTick: {
|
||
type: Object,
|
||
default () {
|
||
return {
|
||
width: '12rpx',
|
||
//不显示则将颜色设置为transparent
|
||
color: '#e3e3e3'
|
||
}
|
||
}
|
||
},
|
||
//y轴线条
|
||
yAxisLine: {
|
||
type: Object,
|
||
default () {
|
||
return {
|
||
color: '#e3e3e3',
|
||
//y轴item的padding值
|
||
itemPadding: '30rpx 0'
|
||
}
|
||
}
|
||
},
|
||
yAxisLabel: {
|
||
type: Object,
|
||
default () {
|
||
return {
|
||
show: true,
|
||
color: "#333",
|
||
size: 24
|
||
}
|
||
}
|
||
},
|
||
yAxisVal: {
|
||
type: Object,
|
||
default () {
|
||
return {
|
||
show: true,
|
||
color: "#333",
|
||
size: 24
|
||
}
|
||
}
|
||
},
|
||
//默认选中y轴索引
|
||
currentIndex: {
|
||
type: Number,
|
||
default: -1
|
||
},
|
||
//是否堆叠展示
|
||
isStack: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
//柱状条点击效果:1-出现背景,2-高亮显示,其他变暗 3-无效果
|
||
clickEffect: {
|
||
type: Number,
|
||
default: 1
|
||
},
|
||
/*
|
||
柱状条的端点样式
|
||
round 向线条的每个末端添加圆形线帽
|
||
square 向线条的每个末端添加正方形线帽
|
||
*/
|
||
columnCap: {
|
||
type: String,
|
||
default: 'square'
|
||
},
|
||
isMinus: {
|
||
type: Boolean,
|
||
default: true
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
sections: 0,
|
||
xAxisData: [],
|
||
activeIndex: -1,
|
||
activeIdx: -1,
|
||
tooltips: [],
|
||
tooltipShow: false,
|
||
timer: null,
|
||
/*========options============*/
|
||
/*
|
||
name: '',
|
||
color: '',
|
||
source: []
|
||
colorFormatter:Function
|
||
*/
|
||
dataset: [],
|
||
yAxisValFormatter: null,
|
||
maxValue: 1
|
||
}
|
||
},
|
||
watch: {
|
||
xAxis(newVal) {
|
||
this.init()
|
||
},
|
||
currentIndex(newVal) {
|
||
if (newVal != this.activeIndex) {
|
||
this.activeIndex = newVal
|
||
}
|
||
}
|
||
},
|
||
// #ifndef VUE3
|
||
beforeDestroy() {
|
||
this.clearTimer()
|
||
},
|
||
// #endif
|
||
// #ifdef VUE3
|
||
beforeUnmount() {
|
||
this.clearTimer()
|
||
},
|
||
// #endif
|
||
created() {
|
||
// this.maxValue = this.max;
|
||
this.init()
|
||
this.activeIndex = this.currentIndex;
|
||
},
|
||
methods: {
|
||
getBarWidth(value, dataset) {
|
||
// ((bar.source[index]-(isStack?(min/dataset.length):min))/(maxValue-min))*width +'rpx'
|
||
let min = this.min
|
||
if (this.isMinus && !this.isStack) {
|
||
value = Math.abs(value)
|
||
min = 0
|
||
}
|
||
return ((value - (this.isStack ? (min / dataset.length) : min)) / (this.maxValue - this.min)) * this.width + 'rpx'
|
||
},
|
||
getMarginLeft(value) {
|
||
let ml = 0
|
||
if (this.isMinus && !this.isStack && Number(this.min) < 0) {
|
||
const min = value > 0 ? Math.abs(this.min) : (value - this.min)
|
||
ml = min / (this.maxValue - this.min) * this.width
|
||
}
|
||
return ml + 'rpx'
|
||
},
|
||
generateArray(start, end) {
|
||
return Array.from(new Array(end + 1).keys()).slice(start);
|
||
},
|
||
getXAxisVal(index) {
|
||
let showVal = '';
|
||
let val = 0;
|
||
if (this.dataset.length === 1) {
|
||
val = this.dataset[0].source[index]
|
||
showVal = val;
|
||
} else if (this.dataset.length > 1) {
|
||
let arr = []
|
||
this.dataset.forEach(item => {
|
||
arr.push(item.source[index])
|
||
})
|
||
val = arr
|
||
}
|
||
if (this.yAxisVal.formatter && typeof this.yAxisVal.formatter === 'function') {
|
||
showVal = this.yAxisVal.formatter(val)
|
||
} else if (this.yAxisValFormatter && typeof this.yAxisValFormatter === 'function') {
|
||
showVal = this.yAxisValFormatter(val)
|
||
}
|
||
return showVal
|
||
},
|
||
getBarColor(val, color, colorFormatter) {
|
||
let bgColor = color;
|
||
if (colorFormatter && typeof colorFormatter === 'function') {
|
||
let formatColor = colorFormatter(val)
|
||
if (formatColor) {
|
||
bgColor = formatColor
|
||
}
|
||
}
|
||
return bgColor
|
||
},
|
||
getLeft(index) {
|
||
let arr = [0]
|
||
let total = 0
|
||
this.dataset.forEach(item => {
|
||
arr.push(item.source[index])
|
||
total += item.source[index]
|
||
})
|
||
return (((this.isStack ? total : Math.max(...arr)) - this.min) / (this.maxValue - this.min)) * this.width +
|
||
'rpx'
|
||
},
|
||
init() {
|
||
let sections = this.xAxis.length - 1;
|
||
let xAxis = this.xAxis;
|
||
this.maxValue = this.max;
|
||
if (sections <= 0) {
|
||
sections = Math.ceil((this.max - this.min) / this.splitNumber)
|
||
let sectionsArr = this.generateArray(0, sections)
|
||
|
||
xAxis = sectionsArr.map(item => {
|
||
return {
|
||
value: item * this.splitNumber + this.min
|
||
}
|
||
})
|
||
this.maxValue = xAxis[xAxis.length - 1].value
|
||
}
|
||
this.xAxisData = xAxis;
|
||
this.sections = sections + 1;
|
||
},
|
||
clearTimer() {
|
||
clearTimeout(this.timer)
|
||
this.timer = null;
|
||
},
|
||
tooltipHandle(index) {
|
||
let data = [...this.dataset]
|
||
let tooltips = []
|
||
data.forEach(item => {
|
||
let color = item.color;
|
||
if (item.colorFormatter && typeof item.colorFormatter === 'function') {
|
||
color = item.colorFormatter(item.source[index])
|
||
}
|
||
tooltips.push({
|
||
color: color,
|
||
name: item.name,
|
||
val: item.source[index]
|
||
})
|
||
})
|
||
this.tooltips = tooltips;
|
||
this.clearTimer()
|
||
this.tooltipShow = true;
|
||
this.timer = setTimeout(() => {
|
||
this.tooltipShow = false
|
||
}, 5000)
|
||
},
|
||
onBarTap(index, idx) {
|
||
this.activeIndex = index;
|
||
this.activeIdx = idx;
|
||
this.tooltipHandle(index);
|
||
this.$emit('click', {
|
||
datasetIndex: idx,
|
||
sourceIndex: index,
|
||
...this.dataset[idx]
|
||
})
|
||
},
|
||
/*
|
||
dataset:柱状图表数据
|
||
yAxisValFormatter :格式化柱状条顶部value值(此处传值是为了做兼容处理)
|
||
*/
|
||
draw(dataset, yAxisValFormatter) {
|
||
this.yAxisValFormatter = yAxisValFormatter || null;
|
||
this.dataset = dataset || [];
|
||
this.init();
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.tui-charts__bar-wrap {
|
||
position: relative;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.tui-bar__legend {
|
||
display: flex;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.tui-bar__legend-item {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-left: 24rpx;
|
||
margin-bottom: 30rpx;
|
||
}
|
||
|
||
.tui-legend__circle {
|
||
height: 20rpx;
|
||
width: 20rpx;
|
||
border-radius: 50%;
|
||
margin-right: 8rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.tui-charts__bar-box {
|
||
position: relative;
|
||
width: 100%;
|
||
border-left: 1px solid;
|
||
border-bottom: 1px solid;
|
||
display: inline-flex;
|
||
justify-content: space-between;
|
||
flex-direction: column;
|
||
z-index: 8;
|
||
}
|
||
|
||
.tui-charts__bar-item {
|
||
position: relative;
|
||
flex: 1;
|
||
flex-shrink: 0;
|
||
padding: 30rpx 0;
|
||
display: inline-flex;
|
||
flex-direction: column;
|
||
z-index: 10;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.tui-bar__item-stack {
|
||
flex-direction: row !important;
|
||
}
|
||
|
||
.tui-yAxis__tickmarks {
|
||
position: absolute;
|
||
width: 16rpx;
|
||
height: 1px;
|
||
background-color: #e3e3e3;
|
||
left: 0;
|
||
top: 0;
|
||
transform: translateX(-100%);
|
||
z-index: 10;
|
||
}
|
||
|
||
.tui-charts__bar {
|
||
position: relative;
|
||
transition: all 0.3s;
|
||
text-align: center;
|
||
/* #ifdef H5 */
|
||
cursor: pointer;
|
||
/* #endif */
|
||
/* border-right: 1px solid; */
|
||
-webkit-backface-visibility: hidden;
|
||
backface-visibility: hidden;
|
||
-webkit-font-smoothing: antialiased;
|
||
/* position: relative; */
|
||
flex-shrink: 0;
|
||
|
||
}
|
||
|
||
.tui-column__bar-opacity {
|
||
opacity: 0.6;
|
||
}
|
||
|
||
.tui-column__bar-active {
|
||
opacity: 0.9999;
|
||
}
|
||
|
||
.tui-column__item-active {
|
||
background-color: rgba(0, 0, 0, .1);
|
||
}
|
||
|
||
.tui-charts__bar-round {
|
||
border-top-right-radius: 100px;
|
||
border-bottom-right-radius: 100px;
|
||
}
|
||
|
||
.tui-bar__val {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 0;
|
||
transform: translateY(-50%);
|
||
padding-left: 12rpx;
|
||
word-break: break-all;
|
||
flex-shrink: 0;
|
||
transition: left 0.3s;
|
||
}
|
||
|
||
.tui-bar__yAxis-text {
|
||
display: inline-block;
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 0;
|
||
flex: 1;
|
||
transform: translate(-100%, -50%);
|
||
padding-right: 20rpx;
|
||
word-break: break-all;
|
||
|
||
}
|
||
|
||
.tui-bar__yAxis-linebox {
|
||
width: 100%;
|
||
height: 100%;
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
box-sizing: border-box;
|
||
z-index: 6;
|
||
}
|
||
|
||
.tui-bar__yAxis-line {
|
||
height: 100%;
|
||
width: 0;
|
||
border-left: 1px;
|
||
display: flex;
|
||
align-items: flex-end;
|
||
justify-content: center;
|
||
overflow: visible;
|
||
}
|
||
|
||
.tui-yAxis__line-first {
|
||
border-left: 0 !important;
|
||
}
|
||
|
||
.tui-xAxis__val {
|
||
transform: translateY(100%);
|
||
padding-top: 12rpx;
|
||
}
|
||
|
||
.tui-column__tooltip {
|
||
padding: 30rpx;
|
||
border-radius: 12rpx;
|
||
background-color: rgba(0, 0, 0, .6);
|
||
display: inline-block;
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
z-index: 20;
|
||
visibility: hidden;
|
||
opacity: 0;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.tui-column__tooltip-show {
|
||
visibility: visible;
|
||
opacity: 1;
|
||
}
|
||
|
||
.tui-tooltip__title {
|
||
font-size: 30rpx;
|
||
color: #fff;
|
||
line-height: 30rpx;
|
||
}
|
||
|
||
.tui-column__tooltip-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding-top: 24rpx;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.tui-tooltip__val {
|
||
font-size: 24rpx;
|
||
line-height: 24rpx;
|
||
color: #fff;
|
||
margin-left: 6rpx;
|
||
}
|
||
|
||
.tui-tooltip__val-ml {
|
||
margin-left: 20rpx;
|
||
}
|
||
</style> |