UNPKG

datavizstocklib

Version:

测试一下呗

723 lines (659 loc) 27.5 kB
import * as d3 from "d3"; let config = { /** 控件名 动态瀑布图 */ name: "DynamicWaterfall", /** svg 对象 */ svg: {}, /** 展示类型 Dynamic Fixed Custom */ scaleType: "Dynamic", /** 数据 */ data: [], /** 内部使用 */ jsonData: {}, /** 内部使用 当前展示的数据 */ currentData: [], /** 表头名集合 */ columnName: [], /** 图表默认高度 */ clientHeight: 500, /** 图表默认宽度 */ clientWidth: 0, /** 升序或降序 asc desc */ sort: "desc", /** Y轴比例尺 */ yScale: {}, /** X轴比例尺 */ xScale: {}, /** 边距设置 上,右,下,左 */ margin: [60, 100, 60, 100], /** colors */ colors: ["#5E4FA2", "#3288BD", "#66C2A5", "#ABDDA4", "#E6F598", "#FFFFBF", "#FEE08B", "#FDAE61", "#F46D43", "#D53E4F", "#9E0142"], /** 最大文字的字号 */ fontsize: 14, //字号 /**Y轴文字字号 */ YfontSize: 14, /**X轴文字字号 */ XfontSize: 14, /** 字体 */ fontfamilyBase: "SourceHanSansK-Regular-plotly,Helvetica Neue,PingFang SC,Microsoft YaHei,Helvetica,Hiragino Sans GB,Arial,sans-serif", /** 文字颜色 */ rectTextColor: "#A3A3A3", /** 条数 */ barNumber: 5, /** 柱高度自适应 */ rectHeightFixed: false, /** 柱高度和 柱间距的比例 */ rectRate: 5, /** 内部使用 */ rectSpace: 0, /** X轴坐标位置 bottom top */ xAxisPos: "top", /** 动画时长 */ durationTime: 200, /** 最大值 */ maxValue: 0, /** 延时播放 */ delay: 2000, height: 35, /** 内部使用 */ isRun: false, replay: false, showRanking: true, /** 矩形圆角 */ rx: 2, index: 0, dataIndex: 0, timeout: null, rankingFontsize: 16, xSicks: 5, showThousands: true, pointFormat: 'auto', showPercent: false, el: '_dataviz', d3Ease:'' } String.prototype.replaceAll = function(s1, s2) { return this.replace(new RegExp(s1, "gm"), s2); } export class DynamicWaterfall { /** 构造器 */ constructor() { let newconfig = JSON.parse(JSON.stringify(config)); Object.assign(this, newconfig) } /** 更新图表 */ update(obj) { // 复制新配置 Object.assign(this, obj) this.getEase(); this.durationTime = 200; } /** 重新播放 */ replay() {} /** 播放 */ play() {} /** 暂停 */ pause() {} /** 初始化 */ init(obj, callback) { let that = this; Object.assign(this, obj); this.getEase(); this.index = 1; that.arrayToJson(); that.svg = d3.select("#" + this.el).append("svg"); //添加竖线 that.svg.append("g") .attr("class", "grid") .style("color", "#A3A3A3") .style("font-size", "14") that.svg.append("g") .attr("class", "xaxis") .attr("font-size", that.XfontSize) .attr("font-family", that.fontfamilyBase) .attr("fill", that.fontColor) if (that.clientWidth == 0) { that.clientWidth = document.getElementById(this.el).clientWidth; } that.svg.attr("width", that.clientWidth).attr("height", that.clientHeight); let currentData = that.getData(); let maxValue = that.max(currentData) * 1.1; let minValue = that.min(currentData); let width = that.clientWidth - this.margin[1] - this.margin[3]; that.yScale = d3.scaleLinear().range([width, that.margin[3]]); // 界面像素范围 that.yScale.domain([minValue, maxValue]); //数据范围 that.bar = that.svg.append("g"); that.svg.append("text").attr("class", "topText") .attr("x", that.svg.attr("width") - that.margin[1] + 80) .attr("y", that.margin[0] - that.rectRate * 5) .text('') .attr("text-anchor", "end") .attr("font-size", that.rankingFontsize) .attr("fill", that.fontColor); this.dataIndex = this.jsonData.length + 1 clearTimeout(this.timeout); if (this.timeout != null) this.timeout.stop(); // 获取x轴中的最大值,和2判断找到最大值是因为最后会添加“累计”的信息 that.maxXSize = Math.max(2, d3.max(that.jsonData, (d)=>{ return ""+d[that.columnName[0]].length })) that.updateChart(callback); } getEase(){ this.d3Ease=d3; this.animateType.split(".").forEach(item=>{ if(item.indexOf("(")>-1){ let arr=item.split("(") this.d3Ease=this.d3Ease[arr[0]](arr[1].replace(")","")) }else{ this.d3Ease=this.d3Ease[item] } }) } /** * 页面渲染 * @param {*} year */ updateChart(callback, lastflag) { let that = this; that.arrayToJson(); let currentData = that.getData(); if (currentData.length == 0) { return; } if (lastflag) { currentData = that.jsonData; let tmpObj = currentData.slice(currentData.length - 1); var objString = JSON.stringify(tmpObj[0]); var obj2 = JSON.parse(objString); obj2['_inner_color'] = that.colors[0]; obj2[that.columnName[0]]='累计' currentData.push(obj2); that.barNumber = that.barNumber + 1; } that.maxValue = that.max(currentData); that.svg.attr("width", that.clientWidth).attr("height", that.clientHeight); let uirange = []; uirange.push(that.margin[3]); uirange.push(that.clientWidth - that.margin[1]); let startDate = that.index > that.barNumber ? that.index - that.barNumber + 1 : 1; // 如果是最后一次计算,会绘制所有柱子,此时开始日期应当是第二个数据(去除表头) if(lastflag){ startDate=1 } // that.xScale = d3.scaleLinear().range(uirange); // 界面像素范围 // that.xScale.domain([-0.5, that.barNumber]); //数据范围 let xScaleData = that.data .slice(startDate, that.barNumber + startDate) .map(d=>d[0]) if(lastflag){ xScaleData = currentData .map(d=>d[that.columnName[0]]) } that.xScale = d3.scaleBand() .domain(xScaleData) .range(uirange) .padding(0.3); // 界面像素范围 let width = that.clientHeight - this.margin[0] - this.margin[2]; that.yScale = d3.scaleLinear().range([width, that.margin[2]]); // 界面像素范围 let minValue = 0; //that.min(currentData); that.yScale.domain([minValue, that.maxValue * 1.1]); //数据范围 let yScale = that.yScale; let xScale = that.xScale; let zoomTimes=1 while( that.maxXSize * that.XfontSize > xScale.step() * zoomTimes){ zoomTimes *= 2 } //添加 x 轴坐标,在tickValues中计算是否绘制 let gridXScale = d3.axisBottom(that.xScale) .tickValues(xScaleData.filter(function(d,i,data){ // 根据字体大小和文本长度,计算x轴显示与否,第一个第二个永远都会显示出来 let index = that.index - 1 if(lastflag){ index = that.jsonData.length - 1 } // 如果在zoomTime不是1的情况下与最后一个发生冲突的节点不会显示x轴文字 if(zoomTimes>1){ // 向左查找可能会重叠的节点文字,设置为不显示 let tempZoomTimes=zoomTimes let tempIndex=index while(tempIndex-1>=0 && tempZoomTimes > 1){ if(d == that.jsonData[tempIndex-1][that.columnName[0]]){ return false } tempZoomTimes-- tempIndex-- } // 向右查找可能会重叠的节点文字,设置为不显示 tempZoomTimes=zoomTimes tempIndex=index while(tempIndex+1< that.jsonData.length-1 && tempZoomTimes > 1){ if(d == that.jsonData[tempIndex+1][that.columnName[0]]){ return false } tempZoomTimes-- tempIndex++ } } // 常规情况下每隔zoomTimes个取一个,或者若为最后一个则一定显示 return !(i%zoomTimes) || d == that.jsonData[index>that.jsonData.length-1?that.jsonData.length-1:index][that.columnName[0]] })) that.svg.select(".xaxis") .attr("transform", function(){ return `translate(0,${-that.margin[2]+that.clientHeight})` }) .html(); that.svg.select(".xaxis") .transition() .duration(that.durationTime) .ease(that.d3Ease) .call(gridXScale); that.svg.select(".xaxis") .selectAll("line").remove() that.svg.select(".xaxis") .selectAll("path").remove() that.svg.select(".xaxis") .selectAll("text") .attr("font-size", that.XfontSize) .attr("font-family", that.fontfamilyBase) .attr("fill", that.fontColor) that.height = (that.clientHeight - that.margin[0] - that.margin[2]) * 0.95 / that.barNumber * that.rectRate let gridHeight = yScale(that.maxValue) - yScale(0) - that.clientHeight; let svgWidth = that.clientWidth - this.margin[1] - this.margin[3]; let yTickSize = svgWidth + that.margin[1]*1/3 that.gridscale = d3.axisLeft() .scale(that.yScale) .ticks(that.xSicks) .tickSize(-yTickSize, 0, 0) .tickFormat(d => { if (d < 0) { return ""; } return that.format(d); }) that.svg.select(".grid") .attr('transform', 'translate(' + that.margin[3] + ',0)') .transition().duration(that.durationTime) .call(that.gridscale).ease(that.d3Ease) that.svg.select(".grid").selectAll("Text") .attr("fill", that.fontColor).attr("fill-opacity", 1).attr("font-size", that.YfontSize);; that.svg.select(".grid").selectAll("line").attr("opacity", 0.4); that.svg.select(".grid").select("path").remove(); var binding = that.bar.selectAll('g').data(currentData, function(d) { return d[that.columnName[0]] + d['_inner_color'] }); binding.attr("class", "binding"); binding.exit().transition() .duration(that.durationTime) .attr("transform", function(d, i) { return "translate(" + (xScale(xScaleData[0]) - xScale.step()) + "," + (yScale(0)) + ")"; }).ease(that.d3Ease).attr("opacity",0.0001).remove(); let barWidthTmp = xScale.bandwidth() * that.rectRate; //补充新的元素 let enterG = binding.enter().append("g").attr("opacity", 1); // 新元素初始化 enterG.attr("opacity",0.0001) .attr("transform", function(d, i) { if (currentData.length == that.barNumber) { return "translate(" + width + "," + (yScale(0)) + ")"; } else { return "translate(" + width + "," + (yScale(0)) + ")"; } }) .transition().duration(0) .attr("transform", function(d, i) { return "translate(" + (xScale(d[that.columnName[0]]) + xScale.bandwidth() * (1 - that.rectRate)/2 ) + "," + (yScale(0)) + ")"; }).attr("opacity", 1) .ease(that.d3Ease); // let barWidthTmp = xScale.bandwidth() * that.rectRate//(xScale(1) - xScale(0)) * that.rectRate; enterG.append("rect") .attr("width", barWidthTmp) .attr("rx", that.rx) .attr("height", 0) .attr("y", function(d, i) { if (i == 0) { return 0; } if (lastflag && (i == that.barNumber - 1)) { return -(yScale(0) - yScale(d[that.key])); } let preData = currentData[i - 1][that.key]; if (that.index > that.barNumber) { if (parseFloat(preData) < parseFloat(d[that.key])) { return -(yScale(0) - yScale(d[that.key])) + Math.abs((yScale(0) - yScale(preData - d[that.key]))); } else { return -(yScale(0) - yScale(preData)); } } else { return -(yScale(0) - yScale(preData)); } }) .attr("fill", function(d, i) { return d['_inner_color']; }); // enterG.append("text") // .text(function(d, i) { // if (lastflag) { // if (i % 3 == 0) { // return d[that.columnName[0]]; // } else { // return ""; // } // } else { // return d[that.columnName[0]]; // } // }) // .attr("font-size", that.XfontSize) // .attr("font-family", that.fontfamilyBase) // .attr("fill", that.fontColor) // .attr("class", 'yText') // .attr("text-anchor", "middle") // .attr("transform", function(d, i) { // return "translate(" + barWidthTmp / 2 + "," + 30 + ")"; // }) // .transition().duration(that.durationTime) // .ease(that.d3Ease); // binding.selectAll('.yText').attr("fill", that.fontColor).attr("font-size", that.XfontSize).attr("transform", function(d, i) { // return "translate(" + barWidthTmp / 2 + "," + 30 + ")"; // }) // if (lastflag) { // that.svg.selectAll(".yText").text(function(d, i) { // if (lastflag) { // if (i == that.barNumber - 1) { // return "累计"; // } // } // if (that.jsonData.length > that.originBarNumber*1.8){ // if ((that.barNumber - 3 - i) % 3 == 0) { // return d[that.columnName[0]]; // } else { // return ""; // } // }else{ // return d[that.columnName[0]]; // } // }); // } var storage = window.localStorage; enterG.append("text").attr("class", "tips") .attr("font-size", that.fontsize) .attr("fill", function(d, i) { return d['_inner_color']; }) .text(function(d, i) { return that.format(Math.abs(d.min)); }) .attr("transform", function(d, i) { if (d.min < 0) { return storage["tipsTempTransFormAppend"]; } else { that.tipsTempTransFormAppend = "translate(" + barWidthTmp / 2 + "," + (yScale(d[that.key]) - yScale(minValue) - 20) + ")"; storage["tipsTempTransFormAppend"] = that.tipsTempTransFormAppend; return that.tipsTempTransFormAppend; } }) .attr("text-anchor", "middle") .transition() .duration(that.durationTime) .ease(that.d3Ease); let tipsTempTransForm = ""; binding.selectAll(".tips") .attr("text-anchor", "middle") .attr("font-size", that.fontsize) .text(function(d, i) { return that.format(Math.abs(d.min)); }) .transition() .duration(that.durationTime) .attr("transform", function(d, i, data) { if (d.min < 0) { tipsTempTransForm = "translate(" + barWidthTmp / 2 + "," + (yScale(d[that.key]-d.min) - yScale(minValue) - 20) + ")"; return tipsTempTransForm; } else { tipsTempTransForm = "translate(" + barWidthTmp / 2 + "," + (yScale(d[that.key]) - yScale(minValue) - 20) + ")"; return tipsTempTransForm; } }) .ease(that.d3Ease); if (lastflag) { that.svg.selectAll(".tips").text(function(d, i) { if (i == that.barNumber - 1) { return that.format(d[that.key]); } else { return ""; } }); } that.bar.selectAll("rect") .transition() .duration(that.durationTime) .attr("width", barWidthTmp) .attr("height", function(d, i) { let theight = 0; if (lastflag && (i == that.barNumber - 1)) { return Math.abs(-(yScale(0) - yScale(d[that.key]))); } if (i == 0) { theight = yScale(0) - yScale(d[that.key]); } else { let preData = 0; if (that.index > that.barNumber) { if (i == 1) { preData = that.jsonData[that.index - 1 - that.barNumber][that.key]; } else { if (currentData.length < (i - 1)) { return; } else { preData = currentData[i - 2][that.key]; } } } else { if (currentData.length < i) { return; } else { preData = currentData[i - 1][that.key]; } } if (lastflag) { preData = currentData[i - 1][that.key]; } let min = parseFloat(preData) - parseFloat(d[that.key]); theight = Math.abs(yScale(0) - yScale(min)); // console.log("that.index:" + that.index + " i:" + i + " min:" + min + ":" + parseFloat(preData) + ":" + parseFloat(d[key]) + " : " + theight); } if(theight<0)theight = 0 return theight; }) .attr("y", function(d, i, data) { //向上增长 y height 发生变化 向下增长 y 为前值的y height 发生变化 if (i == 0) { // console.log(startDate) return -(yScale(0) - yScale(d[that.key])); } let preData = 0; if (that.index > that.barNumber) { if (i == 1) { if(startDate > 1) { preData = parseFloat(that.data[startDate - 1][1]) } } else { if (currentData.length < (i - 1)) { return; } else { preData = currentData[i - 2][that.key]; } } } else { if (currentData.length < i) { return; } else { preData = currentData[i - 1][that.key]; } } if (lastflag) { preData = currentData[i - 1][that.key]; } let min = parseFloat(preData) - parseFloat(d[that.key]); if (min > 0) { //递减 // 以前值的 y 为起点 减去 两值差的高度 return -yScale(0) + yScale(preData); } else { // 递增 ok // 以本值的高度 let y = -yScale(0) + yScale(d[that.key]); return y; } }) .attr("rx", that.rx) .attr("fill", function(d, i) { return d['_inner_color']; }) .ease(that.d3Ease); binding.transition().duration(that.durationTime).attr("transform", function(d, i) { return "translate(" + (xScale(d[that.columnName[0]])+ xScale.bandwidth() * (1 - that.rectRate)/2 ) + "," + yScale(0) + ")"; }).attr("fill", function(d, i) { return d['_inner_color']; }).ease(that.d3Ease); that.svg.selectAll("g").attr('opacity', '1') // 排名显示 // that.svg.select('.topText').attr('visibility', that.showRanking ? 'visible' : 'hidden').attr("x", that.svg.attr("width") - that.margin[1] + 80) // .attr("y", that.margin[0] - that.rectRate * 5).attr("font-size", that.rankingFontsize).text(function (d, i) { // if (that.index - 1 < 0) return '' // return '第' + (that.jsonData.length - that.index + 1) + '名 ' + that.jsonData[that.index - 1][that.columnName[0]] // }) if (lastflag) { callback(that.index, true) that.index = 0 return; } this.timeout = d3.timeout(function() { if (that.index > that.jsonData.length) { // console.log("do if" + that.index); that.originBarNumber = that.barNumber that.barNumber = that.jsonData.length; //return; that.updateChart(callback, true); } else { if (that.isPlay) { callback(that.index, false) that.updateChart(callback); that.index++; that.dataIndex-- } } }, that.index == 1?that.durationTime/2:that.durationTime); } updateObj(obj, callback) { clearTimeout(this.timeout); this.timeout.stop(); Object.assign(this, obj) if (this.replay) { this.dataIndex = this.jsonData.length + 1 this.index = 0 d3.select("#" + this.el).html('') this.init(obj, callback) } else { this.updateChart(callback) } } destroy() { clearTimeout(this.timeout); if (this.timeout != null) this.timeout.stop(); Object.assign(this, null) } min(currentData) { let that = this; return d3.min(currentData, d => d[that.key]) * 0.9; } getData() { let that = this; let currentData = []; if (that.index <= that.barNumber) { currentData = that.jsonData.slice(0, that.index); } else { currentData = that.jsonData.slice(that.index - that.barNumber, that.index); } return currentData; } /** 千分位格式化 */ format(num) { if (this.pointFormat == 0) { num = parseInt(num) } else if (this.pointFormat == 1) { num = parseFloat(num).toFixed(1) } else if (this.pointFormat == 2) { num = parseFloat(num).toFixed(2) } else { num = num.toString() } let result = ''; if (this.showThousands) { num = (num || 0).toString(); let a = num.split('.')[0] || num let b = num.split('.')[1] || "" while (a.length > 3) { result = ',' + a.slice(-3) + result; a = a.slice(0, a.length - 3); } if (a) { result = a + result; } if (b) { result = result + "." + b } } else { result = num } if (this.showPercent) result = result + '%' return result } /** 计算最大值 */ max(currentData) { let that = this; return d3.max(currentData, function(d) { return parseFloat(d[that.key]); }); } sortJsonData() { let that = this; that.jsonData.sort(function(a, b) { let aValue = Number(a[that.key]); let bValue = Number(b[that.key]); return d3.descending(bValue, aValue); }); } arrayToJson() { //添加配色 let that = this; that.data = that.removeNull(that.data) that.columnName = that.data[0]; let json = []; that.key = that.columnName[1]; for (let i = 1; i < that.data.length; i++) { let res = {}; for (let j = 0; j < that.columnName.length; j++) { res[that.columnName[j]] = that.data[i][j]; } if (i == 1) { res['_inner_color'] = parseFloat(res[that.key]) > 0 ? that.colors[1] : that.colors[2]; res['min'] = res[that.key]; } else if (i == (that.data.length - 1)) { res['_inner_color'] = (parseFloat(res[that.key]) - json[i - 2][that.key]) > 0 ? that.colors[1] : that.colors[2]; res['min'] = (res[that.key] - json[i - 2][that.key]).toFixed(2); } else { let min = (res[that.key] - json[i - 2][that.key]).toFixed(2); res['min'] = min; if (min > 0) { res['_inner_color'] = that.colors[1]; } else { res['_inner_color'] = that.colors[2]; } } json.push(res); } that.jsonData = json; // that.sortJsonData(); } removeNull(data) { let array = [] for (let i = 0; i < data.length; i++) { let a = []; for (let j = 0; j < data[i].length; j++) { if (data[i][j] != null && data[i][j] != "" && data[i][j] != " ") { data[i][j] = (data[i][j] + '').replace(/,/g, ''); a.push(data[i][j]); } } if (a.length > 0) array.push(a) } return array } } export default DynamicWaterfall;