UNPKG

datavizstocklib

Version:

测试一下呗

860 lines (793 loc) 30.9 kB
import * as d3 from "d3"; import $ from "jquery"; import infoPanel from "./infoPanel"; import vizlog from "./vizlog"; let config = { name: "perfit", svg: {}, screenWidth: document.documentElement.clientWidth, //屏幕宽度 screenHeight: document.documentElement.clientHeight, //屏幕高度 comWidth: "", //当前页面宽度 comHeight: "", //当前页面高度 widthHeightRate: 0.7, // 宽高度比率 fillopacity: 1, resData: [], unitnumber: 100000000, dataMaxMin: [], yScale: {}, xScale: {}, margin: [60, 60, 20, 20], //上,右,下,左 colorSet: ["#E8847B", "#2FA2AA", "#3367B6"], // 色系 colors: [['#E8847B', '#D97C7D'], ['#2FA2AA', '#38B5BE'], ['#3C569E', '#3367B6']], baseW: 7, //控件左右间距 comW: 100, //控件宽度 columnnum: 4,//列数 xlength: 0, //X 轴宽度 referenceYaxis: 0, //参考 Y坐标 axisfontsize: [13, 13],//坐标轴字号 先x,再y fontsize: 14, //字号 fontfamilyBase: "SourceHanSansK-Regular-plotly,Helvetica Neue,PingFang SC,Microsoft YaHei,Helvetica,Hiragino Sans GB,Arial,sans-serif", fontfamilyPost: '', //字体 Bold Medium rectTextColor: "#EFEFEF",//文字颜色 el: "", click: {}, ws: 3,// 控白 showAxisY: true, //是否添加Y轴,默认添加 referValue: 0, shadow: true, shadowid: "", type: 0, // 0 带营业外收支, 1 不带营业外收支 /** * 内部使用,对外不要传入 */ index: 0, textHeight: '',//文字高度 numberFixed:2 //数值保留位数 } export class Perfit { constructor() { let newconfig = JSON.parse(JSON.stringify(config)); Object.assign(this, newconfig) } init(obj) { Object.assign(this, obj); this.svg = d3.select("#" + this.el).append("svg"); let textid = infoPanel.randomWord(8); this.svg .append("defs") .append("text") .style("font-size", this.fontsize) .attr("x", 0) .attr("y", 0) .style("color", "transparent") .text("利润") .attr("id", textid); this.textHeight = this.fontsize + this.fontsize / 10;//document.querySelector("#" + textid).getBBox().height; if (this.dataMaxMin.length == 0) { this.unitChange(); } else { let maxtmp = this.dataMaxMin.max; let mintmp = this.dataMaxMin.min; this.unitChange(); this.dataMaxMin.max = maxtmp / this.unitnumber; this.dataMaxMin.min = mintmp / this.unitnumber; } vizlog.i("最大值:" + this.dataMaxMin.max + " 最小值:" + this.dataMaxMin.min); this.getChartType(); this.show(); } show() { this.createAxis(); vizlog.i("创建营业总收入矩形"); this.income(1); vizlog.i("创建总成本矩形"); this.cost(2); //处理营业外收支 this.others(3); vizlog.i("创建利润矩形"); this.perfit(4); vizlog.i("创建净利润矩形"); this.perfitPer(5); infoPanel.initInfoPanel(this.svg, this.el, this.fontfamilyBase); infoPanel.black(); } /** * 是否带营业外收支,同时计算列数 */ getChartType() { this.type = 1; if (this.resData[2].name == '营业外收支') { this.columnnum = 5; this.type = 0; } for (let i = 0; i < this.resData.length; i++) { let records = this.resData[i].records; if (records) { let addFlag = false; let sum = 0; for (let j = 0; j < records.length; j++) { if (records[j].value < 0) { addFlag = true; } else { sum = sum + records[j].value; } } if (addFlag) { this.columnnum = this.columnnum + 1; this.dataMaxMin.max = this.dataMaxMin.max < sum ? sum : this.dataMaxMin.max; } } if (i == 1) { return; } } } /** * 初始化坐标轴 */ createAxis() { this.comWidth = $("#" + this.el).width(); this.comHeight = $("#" + this.el).height(); this.svg.attr("width", this.comWidth).attr("height", this.comHeight); let width = this.comWidth, height = this.comHeight, chartWidth = width - this.margin[1] - this.margin[3], chartHeight = height - this.margin[0] - this.margin[2]; let chart = this.svg.append("g") .attr("transform", `translate(${this.margin[1]}, ${this.margin[0]})`); //创建y轴比例尺 let stepNum = d3.tickStep(Math.floor(this.dataMaxMin["min"]), Math.ceil(this.dataMaxMin["max"]), 10); let max = Math.ceil(this.dataMaxMin["max"] / stepNum) * stepNum; let min = Math.floor(this.dataMaxMin["min"] / stepNum) * stepNum; this.yScale = d3 .scaleLinear() .rangeRound([chartHeight, 0]) // 界面像素范围 .domain([min, max]); //数据范围 if (this.showAxisY) { //生成y轴 chart.append("g") .attr("id", "yyyy" + this.el) .style("font-size", this.axisfontsize[0]) .style("font-family", this.fontfamilyBase) .call(d3.axisLeft(this.yScale)); d3.select("#yyyy" + this.el) .select("path") .remove(); } let endnum = this.columnnum * 2 - 0.1 this.xScale = d3 .scaleLinear() .rangeRound([0, chartWidth - 40]) // 界面像素范围 .domain([0, endnum]); //数据范围 //生成x轴 // chart.append("g") // .attr("id", "xxxx" + this.el) // .attr('transform', 'translate(20,0 )') // .call(d3.axisBottom(this.xScale)); //绘制平行的坐标系轴线,其实是将刻度线反向延长 let t = d3 .axisLeft() .scale(this.yScale) .tickSize(-chartWidth, 0, 0) .tickFormat(""); chart .append("g") .attr("class", "grid") .attr("id", "yxx" + this.el) .call(t) .style("color", this.showAxisY ? "#DCDCDC" : "transparent"); d3.select("#yxx" + this.el) .select("path") .remove(); // 删除Y轴上的多余线 let linenode = d3.select("#yyyy" + this.el).selectAll("line"); linenode.each(function (d, i) { d3.select(this).remove(); }) // x轴加粗 infoPanel.addLineForDataviz(this.svg, 0 + this.margin[1], this.yScale(0) + this.margin[0], chartWidth + this.margin[1], this.yScale(0) + this.margin[0], 1, "black"); vizlog.i("原点位置:" + 0 + " : " + this.yScale(0)); this.xlength = document.getElementById("yxx" + this.el).getBBox().width - this.margin[1]; if (this.resData[2].name == '营业外收支') { this.comW = (this.xlength - this.baseW * 6) / 5; } else { this.comW = (this.xlength / 46) * 10; } this.comW = this.xScale(2) - this.xScale(0) - this.baseW; } /** * 营收情况 */ income(col) { let that = this; let t = that.resData[0].records; let x = that.xScale(that.index) + that.margin[1] + 20; let y = that.yScale(0); // 添加正值 for (var i = t.length - 1; i >= 0; i--) { let p = t[i]; if (t[i].value < 0) { continue; } let h = that.calNumberHeight(t[i].value); if (i == t.length - 1) { h = h - that.ws * (t.length - 1); } y = y - h; var _name = infoPanel.randomWord(8); infoPanel.gradient(that.svg, _name, that.colors[0][0], that.colors[0][1], 1); that.addRect(t[i].name, t[i].value, x, y, that.comW, h, _name, 'lefttop', col, '营业总收入'); y = y - that.ws; that.referValue = p.value; } //添加两行文字 let id = infoPanel.randomWord(); that.addSecondTitle(id, that.comW, 100, x, y + this.margin[0] - 5, "营业总收入", this.colorSet[0], 'start' ); let textWidth = that.getComputedTextLength(id); if (textWidth * 2 < this.comW) { id = infoPanel.randomWord(); that.addSecondTitle(id, that.comW, 100, x + that.comW, y + this.margin[0] - 5, that.resData[0].value.toFixed(that.numberFixed), this.colorSet[0], 'end' ); } // 添加负值 let execflag = false; for (var i = t.length - 1; i >= 0; i--) { let p = t[i]; if (t[i].value >= 0) { continue; } let h = that.calNumberHeight(Math.abs(t[i].value)); if (i == t.length - 1) { h = h - that.ws * (t.length - 1); } y = y + that.ws; var _name = infoPanel.randomWord(8); that.index = that.index + 2; let x = that.xScale(that.index) + that.margin[1] + 20; infoPanel.gradient(that.svg, _name, that.colors[0][0], that.colors[0][1], 1); that.addRect(t[i].name, t[i].value, x, y, that.comW, h, _name, 'lefttop', col, '营业总收入'); y = y + h; that.referValue = p.value; execflag = true; } if (!execflag) { y = y + that.ws; } vizlog.i("营业收入后 Y的位置:" + y); that.referenceYaxis = y; } /** * 成本 */ cost(col) { let that = this; let t = that.resData[1].records; that.index = that.index + 2; let x = that.xScale(that.index) + that.margin[1] + 20; let y = that.referenceYaxis; let h = 0; let color = d3.scaleLinear() .domain([that.dataMaxMin.min, that.dataMaxMin.max]) .range(that.colors[1]); for (var i = 0; i < t.length; i++) { let p = t[i]; if (t[i].value < 0) { continue; } var start = color(that.referValue); that.referValue = parseFloat(p.value) + parseFloat(that.referValue); var end = color(that.referValue); var _name = infoPanel.randomWord(8); infoPanel.gradient(that.svg, _name, start, end, 1); h = that.calNumberHeight(t[i].value); if (i == 0) { h = h - that.ws * (t.length - 1); if (h <= 0) { continue; } } that.addRect(t[i].name, t[i].value, x, y, that.comW, h, _name, 'lefttop', col, '营业总成本'); y = y + h + that.ws; } //添加两行文字 let id = infoPanel.randomWord(); that.addSecondTitle(id, that.comW, 100, x, that.referenceYaxis + this.margin[0] - 5 - that.ws, "营业总成本", this.colorSet[1], 'start' ); let textWidth = this.getComputedTextLength(id); if (textWidth * 2 < this.comW) { id = infoPanel.randomWord(); } // 添加成本负值 let execflag = false; for (var i = t.length - 1; i >= 0; i--) { let p = t[i]; if (parseFloat(p.value) >= 0) { continue; } let h = that.calNumberHeight(Math.abs(t[i].value)); if (h < 3) { h = 3; } if (execflag) { // h = h - that.ws * (t.length - 1); } y = y - that.ws; y = y - h; var _name = infoPanel.randomWord(8); that.index = that.index + 2; let x = that.xScale(that.index) + that.margin[1] + 20; var start = color(that.referValue); that.referValue = parseFloat(p.value) + parseFloat(that.referValue); var end = color(that.referValue); var _name = infoPanel.randomWord(8); infoPanel.gradient(that.svg, _name, start, end, 1); that.addRect(t[i].name, t[i].value, x, y, that.comW, h, _name, 'lefttop', col, '营业总收入'); that.referValue = p.value; execflag = true; } that.referenceYaxis = y; } /** *营业外收支 */ others(col) { let that = this; let t = that.resData[2]; let y = that.referenceYaxis; that.index = that.index + 2; let x = that.xScale(that.index) + that.margin[1] + 20; let h = that.calNumberHeight(Math.abs(t.value)); if (h < 3) { h = 3; } var color = d3.scaleLinear() .domain([that.dataMaxMin.min, that.dataMaxMin.max]) .range(that.colors[1]); let p = t; var _name = infoPanel.randomWord(8); var start = color(that.referValue); that.referValue = parseFloat(that.referValue) - parseFloat(p.value); var end = color(that.referValue); infoPanel.gradient(that.svg, _name, start, end, 1); if (t.value > 0) {// 正值往下走 y = y + h; that.addRect(t.name, t.value, x, y, that.comW, h, _name, 'lefttop', col, '营业外收支'); } else {//负值往上走 y = y - h; that.addRect(t.name, t.value, x, y, that.comW, h, _name, 'lefttop', col, '营业外收支'); } //添加两行文字 let id = infoPanel.randomWord(); that.addSecondTitle( id, that.comW, 100, x, y + that.margin[0] - 4, t.name, this.colorSet[1], 'start' ); let textWidth = this.getComputedTextLength(id); if (textWidth * 2 < this.comW) { id = infoPanel.randomWord(); that.addSecondTitle(id, that.comW, 100, x + that.comW, y + that.margin[0] - 4, t.value, this.colorSet[1], 'end' ); } } /** * 利润表 */ perfit(col) { //判断数据在 x 轴上方还是下方 let that = this; let upFlag = (that.resData[3].value > 0); let y = that.yScale(0); var tmp = 0; that.index = that.index + 2; let x = that.xScale(that.index) + that.margin[1] + 20; var color = d3.scaleLinear() .domain([that.dataMaxMin.min, that.dataMaxMin.max]) .range(that.colors[2]); if (upFlag) { let t = that.resData[3].records; for (var i = 0; i < t.length; i++) { let p = t[i]; var _name = infoPanel.randomWord(8); var start = color(that.referValue); that.referValue = parseFloat(that.referValue) - parseFloat(p.value); var end = color(that.referValue); if (that.referValue < that.dataMaxMin.min) { start = color(that.dataMaxMin.min) end = color(that.dataMaxMin.max) } infoPanel.gradient(that.svg, _name, start, end, 1); let h = that.calNumberHeight(t[i].value); if (i == t.length - 1) { h = h - that.ws * (t.length - 1); } if (h < 3) { h = 3; } //当 y 为负时,矩形块顶到上边距了 y = y - h; that.addRect(t[i].name, t[i].value, x, y, that.comW, h, _name, 'lefttop', col, '利润总额'); y = y - that.ws; } } else { // 在下 let t = that.resData[3].records; //从 X轴 向下画矩形 y = y - that.ws; vizlog.i("从 X轴 向下画矩形"); for (let i = 0; i < t.length; i++) { let p = t[i]; let _name = infoPanel.randomWord(8); let start = color(that.referValue); that.referValue = parseFloat(that.referValue) - parseFloat(p.value); let end = color(that.referValue); infoPanel.gradient(that.svg, _name, start, end, 1); let h = that.calNumberHeight(Math.abs(t[i].value)); if (i == t.length - 1) { if (h > that.ws) { h = h - that.ws; } } //当 y 为负时,矩形块顶到上边距了 that.addRect(t[i].name, t[i].value, x, y, that.comW, h, _name, 'lefttop', col, '利润总额'); if (i == t.length - 1) { break; } y = y + h; y = y + that.ws; } y = that.yScale(0); } let id = infoPanel.randomWord(); //添加两行文字 that.addSecondTitle( id, that.comW, 100, x, y + that.margin[0] - 4, that.resData[3].name, this.colorSet[2], 'start' ); let textWidth = this.getComputedTextLength(id); if (textWidth * 2 < this.comW) { id = infoPanel.randomWord(); that.addSecondTitle( id, that.comW, 100, x + that.comW, y + that.margin[0] - 4, that.resData[3].value, this.colorSet[2], 'end' ); } } /** * 利润归属 */ perfitPer(col) { vizlog.i("利润归属"); let that = this; let upFlag = (that.resData[3].value > 0); let t = that.resData[3].records[0]; that.index = that.index + 2; let x = that.xScale(that.index) + that.margin[1] + 20; var color = d3.scaleLinear() .domain([that.dataMaxMin.min, that.dataMaxMin.max]) .range(that.colors[2]); if (upFlag) { let y = that.yScale(0); //从 X轴 向上画矩形 for (var i = 0; i < t.records.length; i++) { let p = t.records[i]; let start = color(that.referValue); that.referValue = parseFloat(that.referValue) - parseFloat(p.value); let end = color(that.referValue); if (that.referValue < that.dataMaxMin.min) { start = color(that.dataMaxMin.min) end = color(that.dataMaxMin.max) } let _name = infoPanel.randomWord(8); infoPanel.gradient(that.svg, _name, start, end, 1); //当 y 为负时,矩形块顶到上边距了 let h = that.calNumberHeight(t.records[i].value); if (i == 0) { h = h - that.ws * (t.records.length - 1); } if (h < 3) { h = 3; } y = y - h; let w = this.xScale(2) - this.xScale(0); that.addRect(t.records[i].name, t.records[i].value, x, y, w, h, _name, 'lefttop', col, '利润归属'); y = y - that.ws; } } else { let y = that.yScale(0); //从 X轴 向下画矩形 for (let i = 0; i < t.length; i++) { let p = t[i]; let _name = infoPanel.randomWord(8); let start = color(that.referValue); that.referValue = parseFloat(that.referValue) - parseFloat(p.value); let end = color(that.referValue); infoPanel.gradient(that.svg, _name, start, end, 1); //当 y 为负时,矩形块顶到上边距了 let h = that.calNumberHeight(t[i].value); if (i == 0) { h = h - that.ws * (t.length - 1); } let w = this.xScale(2) - this.xScale(0); that.addRect(t[i].name, t[i].value, tmp, y, w, h, _name, 'lefttop', col, '利润归属'); y = y + h; y = y + that.ws; } } } /** * 控件id, 控件值, 矩形 x y 左上角坐标, h 高度, c 色系索引 bel矩形所属哪个项 */ addRect(id, v, x, y, w, h, c, pos, col, bel) { if (w < 0 || h <= 0) { vizlog.e("注意=> w:" + w + " h:" + h); return; } let that = this; let circle = this.comWidth >= 500 ? 3 : 1.5; let rect = that.svg .append("rect") .attr("v", v) .attr("id", infoPanel.randomWord(8) + "_" + that.el) .attr("memo", id) .attr("vt", id) .attr("col", col) .attr("fill-opacity", that.fillopacity) .attr("rx", circle) .attr("ry", circle) .attr("fill", 'url(#' + c + ')') .attr("x", x) .attr("bel", bel) .attr("y", function () { let t = y + that.margin[0]; return t; }) .attr("width", w) .attr("height", function () { return h; }) .on('click', function () { let _that = d3.select(this); _that.attr('transform', 'translate(2,2)'); d3.timeout(function () { _that.attr('transform', 'translate(0,0)') }, 300); var col = _that.attr("col"); var thisId = _that.attr("id"); var thisValue = _that.attr("v"); var thisXY = d3.mouse(this); var bel = _that.attr("bel"); if (typeof that.click === "function") { that.click(thisId, thisValue, thisXY, col, d3.event, bel); } // d3.event.stopPropagation(); }) .on("mouseover", function () { let _that = d3.select(this); var thisId = _that.attr("id"); var thisValue = _that.attr("v"); var thisXY = d3.mouse(this); infoPanel.showInfoPanel(thisId, thisValue, thisXY, that.el); }) .on("mouseout", function () { let _that = d3.select(this); var thisId = _that.attr("id"); infoPanel.hideInfoPanel(thisId, that.el); }) .on("mousemove", function () { let _that = d3.select(this); var thisId = _that.attr("id"); var thisValue = _that.attr("v"); var thisXY = d3.mouse(this); infoPanel.moveInfoPanel(thisId, thisValue, thisXY, that.el); }); if (that.shadow) { rect.style("filter", "url(#drop-shadow)"); } if ("lefttop" == pos) { that.addText(w, h, id, v, x, y, c, 'lefttop'); } else { that.addText(w, h, id, v, x, y, c); } } /** * w 宽度 h高度 t 文本内容 x y */ addText(w, h, t, v, x, y, c, pos) { if (h > this.textHeight && h < 2 * this.textHeight && w < t.length * this.fontsize + 10) { return; //高度只够放一行的且矩形宽度不够的 } let addValueFlag = true; if (h > this.textHeight) { let mx = x + w / 2; let my = y + this.margin[0] + h / 2; let p = "middle"; if ("lefttop" === pos) { my = y + this.margin[0] + this.fontsize; if (h >= this.textHeight * 2 + 10 * 2) { my += 10; } else { my += (h - this.textHeight) / 2; } if (t.length * this.fontsize + 20 > w) { mx = x + (w - t.length * this.fontsize) / 2; } else { mx = x + 10; } p = 'start'; } let id = infoPanel.randomWord(8); this.renderText(id, mx, my, w, h, t, p, p, this.fontsize, this.fontfamilyPost, this.rectTextColor); let textWidth = d3.select("#" + id).node().getComputedTextLength();// 文字宽度 if (textWidth > w) {// 一行显示不下了 // 一行最多个汉字 let maxWord = w / this.fontsize; vizlog.i("maxword:" + maxWord); p = "middle" mx = x + w / 2 d3.select("#" + id).remove(); let tmp = t.substring(0, maxWord); if (h < this.textHeight * 2 + 2 * 10) { if (h > this.textHeight * 2) { my = y + this.margin[0] + this.fontsize + (h - this.textHeight * 2) / 2 - 3; } else { my = y + this.margin[0] + this.fontsize + (h - this.textHeight) / 2 - 3; tmp = t.substring(0, maxWord - 1) + "..."; t = ""; } } this.renderText(id, mx, my, w, h, tmp, p, p, this.fontsize, this.fontfamilyPost, this.rectTextColor); my = my + this.textHeight; tmp = t.substring(maxWord); if (tmp.length > maxWord) { tmp = tmp.substring(0, maxWord - 1) + '...'; } if (my > (y + this.margin[0] + h / 2)) { addValueFlag = false; } this.renderText(infoPanel.randomWord(8), mx, my, w, h, tmp, p, p, this.fontsize, this.fontfamilyPost, this.rectTextColor); } } if (w > 70 && h > 110 && addValueFlag) { let p = 'middle'; let mx = x + w / 2; let my = y + this.margin[0] + h / 2 + this.fontsize / 2; let vtmp = v ? v.toFixed(this.numberFixed) : ''; this.renderText(infoPanel.randomWord(8), mx, my, w, h, vtmp, p, p, this.fontsize + 2, this.fontfamilyPost, this.rectTextColor); } } /** * 计算文本宽度 * @param {String} id */ getComputedTextLength(id) { return d3.select("#" + id).node().getComputedTextLength(); } /** * w h x y */ addSecondTitle(id, w, h, x, y, t, c, pos) { let fs = this.fontsize == 10 ? this.fontsize : this.fontsize + 2; this.renderText(id, x, y, w, h, t, pos, "", fs, this.fontfamilyPost, c); } /** * 文字渲染 * @param {String} id 编号 * @param {number} x X 坐标 * @param {number} y Y 坐标 * @param {number} w 宽度 * @param {number} h 高度 * @param {String} text 文本 * @param {String} textanchor 文本水平位置 start|middle|end * @param {String} baseline 文本垂直位置 start|middle|end * @param {number} fontsize 字号 * @param {String} fontfamily 字体 * @param {String} textcolor 颜色 */ renderText(id, x, y, w, h, text, textanchor, baseline, fontsize, fontfamily, textColor) { this.svg .append("text") .attr("id", id) .text(text) .attr("x", x) .attr("y", y) .attr("style", "text-anchor: " + textanchor) .attr("dominant-baseline", baseline) .attr("width", w) .attr("height", h) .attr("font-size", fontsize) .attr("font-weight", 700) .attr("font-family", fontfamily) .attr("fill", textColor) .attr("pointer-events", "none"); } unitChange() { this.dataMaxMin = { "max": -999999, "min": 0 }; let level = 0; for (var i = 0; i < this.resData.length; i++) { this.unitChangeRecursion(this.resData[i], level); } let min = this.resData[0].value - this.resData[1].value; this.dataMaxMin["min"] = this.dataMaxMin["min"] > min ? min : this.dataMaxMin["min"]; } /** * 递归处理 单位 */ unitChangeRecursion(obj, level) { let that = this; if (obj) { if (obj.value) { obj.value = parseFloat((obj.value / that.unitnumber).toFixed(2)); if (obj.name == '营业外收支') { return; } if (level == 0) { that.dataMaxMin["max"] = that.dataMaxMin["max"] < obj.value ? obj.value : that.dataMaxMin["max"]; that.dataMaxMin["min"] = that.dataMaxMin["min"] > obj.value ? obj.value : that.dataMaxMin["min"]; } } if (obj.records) { let l = level + 1; for (var i = 0; i < obj.records.length; i++) { that.unitChangeRecursion(obj.records[i], l); } } } } /** * 计算数据对应 Y 轴高度 */ calNumberHeight(numberValue) { return this.yScale(0) - this.yScale(numberValue); } //页面大小切换 resize() { let that = this; clearTimeout(that.timer); that.timer = setTimeout(function () { that.svg.selectAll("*").remove(); that.show(); }, 200); } } export default Perfit;