1.canvas.vue
<template>
<div>
<div style="margin: 50px auto;width: 100%" class="canvasDom">
<div class="legend">
<div v-for="(item,index) in legend" >
<div class="classBar" :style="{background: color[index]}"></div>
<span :style="{color:color[index]}"> {{item}}</span>
</div>
<div>
<div class="classLine" style="background:#FF8C11"></div>
<span style="color: #FF8C11"> 当期资产</span>
</div>
</div>
<canvas :id="id" ref="chart" height="420">
当前浏览器不支持Canvas,请更换浏览器后再试
</canvas>
</div>
</div>
</template>
<script>
export default {
name: "career-simulation-chart",
data() {
return {
color: ['#E8C251', '#68BEBB', '#FF6F72', '#9400d3', '#B22222',
'#006400', '#BDB76B', '#556B2F', '#FF8C00', '#8B0000'],
legend:[],
}
},
props: ['id'],
methods: {
init(obj) {
let canvas = document.getElementById(this.id);
canvas.width = canvas.parentNode.clientWidth
console.log(canvas.parentNode.clientWidth)
console.log(canvas.width)
canvas.height = canvas.height
let ctx = canvas.getContext('2d');
//画布的款高
let cw = canvas.width;
let ch = Math.round(canvas.height / 1.3);
let xz = obj.x;
let yz = obj.y;
//内间距padding
let padding = 80;
//原点,bottomRight:X轴终点,topLeft:Y轴终点
let origin = {x: padding+50, y: ch + padding-10};
let bottomRight = {x: cw - padding, y: ch + padding};
let topLeft = {x: padding+50, y: padding};
let YscaleNum = 9; // Y轴刻度长度
let Ydata = []; // Y轴数据存放数组
ctx.beginPath();
//绘制Y轴
ctx.moveTo(origin.x, origin.y);
ctx.lineTo(topLeft.x, topLeft.y);
//设置字号
ctx.font = '16px SimHei';
ctx.strokeStyle = '#797979';
ctx.fillText('单位:元',
origin.x - 60,
topLeft.y+20);
//绘制Y方向刻度
// let max = 0; //最大刻度max
let step = 0; // 判断负值个数
for (let i = 0; i < yz.length; i++) {
if (yz[i] < 0) {
step++
}
}
step = Math.floor(step / yz.length * YscaleNum);
// let min = Math.min.apply(this, yz);
let sum = yz.reduce((i, n) => {
return i + n
}, 0);
let min = Math.min.apply(this, yz);
let max = Math.max.apply(this, yz);
let avgValue = Math.floor((max - min) / YscaleNum);
console.log('avgValue',avgValue)
console.log((max-min)/YscaleNum)
// step = avgValue < 0 ? step : YscaleNum - step
console.log(step)
let avgHeight = (ch - 50) / YscaleNum;
for (let i = 1; i < YscaleNum + 1; i++) {
// let minus = 0
// if (avgValue < 0) {
// if (i <= step) {
// minus = 1
// } else {
// minus = -1
// }
// } else {
// if (i <= step) {
// minus = -1
// } else {
// minus = 1
// }
// }
// //绘制Y轴文字
// let text = ''
// if (i <= step) {
// console.log(i)
// text = minus * avgValue * (step - i)
// } else {
// text = minus * avgValue * (i - step)
// }
let text = min + avgValue*i
Ydata.push(text)
let txtWidth = ctx.measureText(this.currency(text)).width;
ctx.fillText(this.currency(text),
origin.x - txtWidth - 5,
origin.y - (i) * avgHeight + 6);
}
console.log(Ydata)
ctx.strokeStyle = '#64A1FA';
ctx.lineWidth = 2;
ctx.stroke();
ctx.beginPath();
//绘制X轴
ctx.lineWidth = 1;
ctx.moveTo(origin.x, origin.y+5);
ctx.lineTo(bottomRight.x + 100, origin.y+5);
//计算刻度可使用的总宽度
ctx.stroke();
ctx.beginPath();
let avgWidth = (cw - 2 * padding + 70) / (this.arr_integ(xz)[this.arr_integ(xz).length - 1]);
let s = this.arr_integ(xz)[0]
let e = this.arr_integ(xz)[this.arr_integ(xz).length - 1]
e = isNaN(xz[xz.length - 1]) && (e * 1 + 5);
s = s - s % 5;
for (let i = s; i <= e; i++) {
let jl = i - this.arr_integ(xz)[0]
if (i % 5 == 0) {
let text = i == obj.retireList ? '退休后' : (i + '岁');
if (i > obj.retireList) text = '';
// console.log(jl * avgWidth)
let txtWidth = ctx.measureText(text).width; // 文字的宽度
ctx.fillText(
text,
origin.x + jl * avgWidth - txtWidth / 2 + 100,
origin.y+25);
ctx.stroke();
ctx.beginPath();
ctx.fillText(
'.',
origin.x + jl * avgWidth - txtWidth / 2 + 110,
origin.y+6);
}
}
for (let i = 0; i < obj.series.length; i++) {
this.creatBar(bottomRight, ctx, origin, step, avgWidth, avgHeight, xz, obj.series[i], i);
}
ctx.strokeStyle = '#64A1FA';
ctx.lineWidth = 1;
ctx.stroke();
//绘制折线
ctx.beginPath();
for (let i = 0; i < yz.length; i++) {
let index = this.getArraySection(Ydata, yz[i])
let div = ((yz[i] - Ydata[index]) / Math.abs(avgValue) * avgHeight)
let posY = origin.y - ((index + 1) * avgHeight + Number(div));
let jl = xz[i].replace(/岁/, "") - this.arr_integ(xz)[0];
if (i == 0) {
ctx.moveTo(origin.x + jl * avgWidth + 70, Number(posY.toFixed(2)));
} else {
ctx.lineTo(origin.x + jl * avgWidth + 70, Number(posY.toFixed(2)));
}
}
ctx.strokeStyle = '#FF8C11';
ctx.lineWidth = 2;
ctx.stroke();
//0刻度
let zeroIdx = Ydata.findIndex(ele => {
return ele === 0
}) + 1
if (zeroIdx != 0) {
ctx.beginPath();
ctx.strokeStyle = '#64A1FA';
ctx.lineWidth = 2;
ctx.moveTo(origin.x, origin.y - zeroIdx * avgHeight);
ctx.lineTo(bottomRight.x + 100, origin.y - zeroIdx * avgHeight);
ctx.stroke();
}
},
// 画柱状图
creatBar(bottomRight, ctx, origin, step, avgWidth, avgHeight, xz, bar, hd) {
ctx.fillStyle = bar.color; //柱状图的颜色
let sc = bar.data; //柱状图的长度阈值
let yHeight = origin.x + hd * 26
let rectCoor = (sc[0].replace(/岁/, '') - this.arr_integ(xz)[0]) * avgWidth + 70
let rectWidth = sc[1].replace(/岁/, '') - sc[0].replace(/岁/, '');
rectWidth = rectWidth || (this.arr_integ(xz)[this.arr_integ(xz).length - 1] - sc[0].replace(/岁/, '') + 5)
ctx.fillRect(origin.x + rectCoor, yHeight, rectWidth * avgWidth, 20);
ctx.font = '13px SimHei';
ctx.fillStyle = '#666';
ctx.lineWidth = 1;
ctx.fillText(bar.name,
origin.x + rectCoor + rectWidth * avgWidth + 5,
yHeight + 15
);
// 画竖向标的
if (bar.target) {
for (let i = 0; i < bar.target.length; i++) {
this.target(ctx, origin, step, avgWidth, avgHeight, xz, bar.target[i], yHeight)
}
}
},
// bar 位置定位
// 数组去掉汉字
arr_integ(arr) {
let newArr = [];
arr = arr.slice(0, -1)
newArr = arr.map(item => {
return item.replace(/岁/, "")
})
return newArr
},
target(ctx, origin, step, avgWidth, avgHeight, xz, obj, hd) {
let name = obj.name;
let tar = obj.data;
let x = origin.x + (tar.replace(/岁/, '') - this.arr_integ(xz)[0]) * avgWidth + 70;
// - hd * avgHeight
let sH = origin.y - (step + 1) * avgHeight;
console.log(hd)
ctx.fillStyle = '#64A1FA '
console.log(sH)
if (hd > sH) {
hd += 50;
ctx.moveTo(x, sH)
ctx.lineTo(x, hd);
ctx.fillText(name, x, hd - 10);
} else {
hd -= 30;
ctx.moveTo(x, sH)
ctx.lineTo(x, hd);
ctx.fillText(name, x, hd + 20);
}
},
getInitData() { // 获取数据
let cis = this.$store.state.userInfo.cis;
let planID = this.$store.state.planId;
const params = {
functionName: "wealthmanagementbiz.service.CustomerAchievementService",
methodName: "getPlanCareerSimulation",
data: {
cis: cis,
planId: planID
}
};
this.http(JSON.stringify(params))
.then(res => {
console.log(res)
console.log('lengthrrrrrrrrrrrrrr',res.data.data.list.length)
if (!res.data.data.list.length) {
document.getElementsByClassName('canvasDom')[0].innerHTML = '暂无数据'
return
}
this.Arrangement(res.data)
})
.catch(err => {
document.getElementsByClassName('canvasDom')[0].innerHTML = '暂无数据'
console.log(err)
})
},
getArraySection(arr, num) { // 数据第一次出现的下标
if (arr[arr.length - 1] < num) return arr.length - 1;
if (arr[0] > num) return 0;
for (let i = 0; i < arr.length; i++) {
if (arr[i] > num) {
return i - 1;
}
}
},
Arrangement(data) { // 整理数据
console.log(data)
let x = data.totalNetIncome.map(ele => {
return `${ele.year}岁`
});
let y = data.totalNetIncome.map(ele => {
return ele.totalNetIncome
});
let series = [];
for (let i = 0; i < data.data.list.length; i++) {
// debugger
if(data.data.list[i] !== null) {
if (data.data.list[i].data.length !== 0) {
this.legend.push(data.data.list[i].name)
series.push({
name: data.data.list[i].name,
text: data.data.list[i].name,
data: data.data.list[i].data.map(ele => {
return ele + '岁'
}),
color: this.color[i]
})
}
}
}
this.init({
x: x,
y: y,
retireList: data.data.list[0].name==="养老规划"?data.data.list[0].data[0]:'100',
series: series
})
},
currency (value, currency, decimals){
const digitsRE = /(\d{3})(?=\d)/g
value = parseFloat(value)
if (!isFinite(value) || (!value && value !== 0)) return ''
currency = currency != null ? currency : ''
decimals = decimals != null ? decimals : 0
var stringified = Math.abs(value).toFixed(decimals)
var _int = decimals
? stringified.slice(0, -1 - decimals)
: stringified
var i = _int.length % 3
var head = i > 0
? (_int.slice(0, i) + (_int.length > 3 ? ',' : ''))
: ''
var _float = decimals
? stringified.slice(-1 - decimals)
: ''
var sign = value < 0 ? '-' : ''
return sign + currency + head +
_int.slice(i).replace(digitsRE, '$1,') +
_float
},
},
mounted() {
this.getInitData();
}
}
</script>
<style scoped lang="scss">
.legend{
display: flex;
justify-content: center;
align-items: center;
}
.canvasDom {
text-align: center;
font-size: 24px;
color: crimson;
span{
font-size: 14px;
}
}
.classBar{
width: 20px;
height: 10px;
border-radius: 4px;
display: inline-block;
margin-left: 10px;
}
.classLine{
width: 20px;
height: 2px;
transform: translateY(-4px);
border-radius: 4px;
display: inline-block;
margin-left: 10px;
}
</style>
2.使用
<el-dialog
title="生涯仿真图"
center
:visible.sync="isShowChart"
width="70%"
:before-close="handleClose"
>
<career-chart :id="'career4'"></career-chart>
</el-dialog>
import careerChart from "@/components/modules/CareerSimulationChart/CareerSimulationChart";
components: {careerChart},