UNPKG

datavizstocklib

Version:

测试一下呗

552 lines (518 loc) 19.8 kB
import * as d3 from "d3"; let config = { /** 控件名 */ name: "DynamicPackedCircle", /** 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: [], /** 最大文字的字号 */ YfontSize: 25, //字号 /** 字体 */ fontfamilyBase: "SourceHanSansK-Regular-plotly,Helvetica Neue,PingFang SC,Microsoft YaHei,Helvetica,Hiragino Sans GB,Arial,sans-serif", /** 文字颜色 */ rectTextColor: "#A3A3A3", /** 条数 */ barNumber: 5, /** 柱高度自适应 */ rectHeightFixed: false, // 柱子高度 rectHeight: 100, /** 柱高度和 柱间距的比例 */ rectRate: 5, /** 内部使用 */ rectSpace: 0, /** X轴坐标位置 bottom top */ xAxisPos: "top", /** 动画时长 */ durationTime: 1000, /** 最大值 */ maxValue: 0, /** 延时播放 */ delay: 2000, height: 35, /** 内部使用 */ isRun: false, replay: false, /** 矩形圆角 */ rx: 2, index: 0, dataIndex: 0, timeout: null, xSicks: 5, showThousands: true, pointFormat: 'auto', el: '_dataviz', d3Ease: '', // 是否填充渐变色 ifGradient: false, // 是否填显示尖角 rectSharpCorner: false, // 图片 useImages: true, // 名称显示在左边,否则显示在柱子上 nameAtLeft: true, showLabel: true, showRanking: true, rankingFontsize: 30, showPercent: false } var colorRgb = function(sColor){ sColor = sColor.toLowerCase(); //十六进制颜色值的正则表达式 var reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/; // 如果是16进制颜色 if (sColor && reg.test(sColor)) { if (sColor.length === 4) { var sColorNew = "#"; for (var i=1; i<4; i+=1) { sColorNew += sColor.slice(i, i+1).concat(sColor.slice(i, i+1)); } sColor = sColorNew; } //处理六位的颜色值 var sColorChange = []; for (var i=1; i<7; i+=2) { sColorChange.push(parseInt("0x"+sColor.slice(i, i+2))); } return "rgb(" + sColorChange.join(",") + ")"; } return sColor; }; export class DynamicPackedCircle { /** 构造器 */ constructor() { let newconfig = JSON.parse(JSON.stringify(config)); Object.assign(this, newconfig) } /** 更新图表 */ update(obj) { // 复制新配置 Object.assign(this, obj) } /** 初始化 */ init(obj, callback) { let that = this; Object.assign(this, obj); this.index = 1 let c = that.colors.concat() for (let i = 0; i <= that.data.length; i = i + c.length) { that.colors = that.colors.concat(c) } this.getEase(); that.arrayToJson(); that.svg = d3.select("#" + this.el).append("svg"); if (that.clientWidth == 0) { that.clientWidth = document.getElementById(this.el).clientWidth; } if (this.clientHeight > this.clientWidth) { this.margin[1] = this.margin[3] = 40 } that.svg.attr("width", that.clientWidth) .attr("height", that.clientHeight); let width = this.clientWidth - this.margin[1] - this.margin[3]; let height = this.clientHeight - this.margin[0] - this.margin[2]; that.svg.append("circle") .attr("class", "circle-bg") .attr("r", width > height ? height / 2 : width / 2) .attr("fill", colorRgb(this.backgroundColor)) .attr("fill-opacity",.2) .attr('transform', `translate(${width/2+this.margin[3]},${height/2+this.margin[0]})`); //创建y轴比例尺 that.chart = that.svg.append("g") .attr("width", that.clientWidth) .attr("height", that.clientHeight) .attr("transform", `translate(${that.margin[3]},${that.margin[0]})`) that.svg.append("g").attr("class", "timeTextG") .append("text") .attr("class", "topText") .attr("x", that.clientWidth * 0.9) .attr("y", that.margin[0] - that.rectRate - 10) .text('') .attr("text-anchor", "end") .attr("font-size", this.rankingFontsize) .attr("fill", () => { return that.fontColor }); that.updateChart(callback); } updateObj(obj, callback) { Object.assign(this, obj) if (this.clientHeight > this.clientWidth) { this.margin[1] = this.margin[3] = 40 } if (this.replay) { this.dataIndex = this.jsonData.length + 1 this.index = 0 d3.select("#" + this.el).html('') this.init(obj, callback) } else { let c = this.colors.concat() for (let i = 0; i <= this.data.length; i = i + c.length) { this.colors = this.colors.concat(c) } this.updateChart(callback) } } // 页面渲染 updateChart(callback) { let that = this let width = this.clientWidth - this.margin[1] - this.margin[3]; let height = this.clientHeight - this.margin[0] - this.margin[2]; this.treemap = data => d3.pack() // .tile(d3.treemapResquarify) .size([width, height]) .padding(this.treemapBorder) (d3.hierarchy(data, (d) => d.children) .sum(d => d.value) .sort((a, b) => { return b.value - a.value; })) this.chart.attr("transform", `translate(${this.margin[3]},${this.margin[0]})`) this.svg.select(".circle-bg") // .attr("fill",this.colors[0]) // .attr("opacity",.2) .transition() .duration(this.durationTime) .ease(this.d3Ease) .attr("opacity", .2) .attr("r", width > height ? height / 2 : width / 2) .attr('transform', `translate(${width/2+this.margin[3]},${height/2+this.margin[0]})`); this.svg.attr("width", this.clientWidth) .attr("height", this.clientHeight); let { showData, showTime, ifEnd } = this.getData(); this.svg.select(".topText") .attr("x", this.clientWidth * 0.9) .attr("y", this.margin[0] - this.rectRate - 10) .text(this.showRanking ? showTime : "") .attr("fill", () => { return !this.isDark ? "#808080" : this.fontColor }) .transition() .duration(this.durationTime) .ease(this.d3Ease) .attr("font-size", this.rankingFontsize) .on("end", () => { if (!ifEnd) { if (this.isPlay) { setTimeout(function() { callback(that.index, false) that.updateChart(callback); that.index++; }, 300) } } else { callback(this.index, true); this.index = 0; } }) const root = this.treemap({ name: "", children: showData }); this.chart.selectAll("g") .data(root.leaves(), function(d) { //root.descendants() return d.data.name }) .join( enter => { const leaf = enter.append("g"); this.enterRect(leaf) }, update => { this.updateRect(update) }, exit => { this.exitRect(exit) } ) // .attr("transform", d => `translate(${d.x0},${d.y0})`); } exitRect(leaf) { leaf.select("rect") .transition() .duration(this.durationTime) .ease(this.d3Ease) .attr("fill", (d, i) => { return this.colors[i]; }) .attr("r", 0) leaf .transition() .duration(this.durationTime) .ease(this.d3Ease) .attr("opacity", 0.0001) .remove(); } updateRect(leaf) { const self = this leaf .transition() .duration(this.durationTime) .ease(this.d3Ease) .attr("transform", d => { if (this.textAlign == "Center") { let showText = this.showLabel ? [d.data.name].concat(d.value) : [d.data.name].concat('') let maxLength = d3.max(showText, (c) => ("" + c).length) let width = d.r d.fontSize = Math.min(width / maxLength, 30) * self.circlefontsize } return `translate(${d.x},${d.y})` }); leaf.select("circle") .transition() .duration(this.durationTime) .ease(this.d3Ease) .attr("r", d => d.r) .attr("fill", (d, i) => { return this.colors[i]; }) leaf.select("clipPath") .select("use") // .attr("transform",(d)=>{ // let x = this.textAlign=="Center"?(d.x1 - d.x0)/2 : 0 // let y = this.textAlign=="Center"?(d.y1 - d.y0)/2-d.fontSize : 0 // return `translate(${-x},${-y})` // }) leaf.select("text") .transition() .duration(this.durationTime) .ease(this.d3Ease) // .attr("transform",(d)=>{ // let x = this.textAlign=="Center"?(d.x1 - d.x0)/2 : 0 // let y = this.textAlign=="Center"?(d.y1 - d.y0)/2-d.fontSize : 0 // return `translate(${x},${y})` // }) .attr("text-anchor", this.textAlign == "Center" ? "middle" : "start") .attr("font-size", (d) => { if (this.textAlign !== "Center") { return this.textFontSize } else { return d.fontSize } }) leaf.select("text") .selectAll("tspan") .data(d => { //.split(/(?=[A-Z][a-z])|\s+/g) return this.showLabel ? [d.data.name].concat(d.value) : [d.data.name].concat('') }) .attr("x", this.textAlign === "Center" ? 0 : 3) .attr("y", (d, i, nodes) => `${(i === nodes.length - 1) * 0.3 + i * 0.9}em`) .attr("fill-opacity", (d, i, nodes) => i === nodes.length - 1 ? 0.7 : 1) .style("font-family", "regular") .transition() .duration(this.durationTime) .ease(this.d3Ease) .attr("fill", this.fontColor) .tween("text", function(d, i, data) { let that = this if (self.interpolate && i == data.length - 1 && self.showLabel) { let preNum = d3.select(this).text().split("%")[0].split(",").join("") let maxLength = Math.max(self.countDecimals(preNum), self.countDecimals(d)) let interNum = d3.interpolateNumber(Number(preNum), Number(d)) return function(t) { d3.select(that).text(self.format(Number(interNum(t).toFixed(maxLength)))) } } else { if (i == data.length - 1) d3.select(this).text(self.showLabel ? self.format(d) : "") else d3.select(this).text(d) } }); leaf.select("title").text(d => `${d.ancestors().reverse().map(d => d.data.name).join("")}\n${this.format(d.value)}`); } enterRect(leaf) { let self = this leaf // .transition() // .duration(this.durationTime) // .ease(this.d3Ease) .attr("transform", d => { if (this.textAlign == "Center") { let showText = this.showLabel ? [d.data.name].concat(d.value) : [d.data.name].concat('') let maxLength = d3.max(showText, (c) => ("" + c).length) let width = d.r d.fontSize = Math.min(width / maxLength, 30) * self.circlefontsize } return `translate(${d.x},${d.y})` }); leaf.append("title") .text(d => `${d.ancestors().reverse().map(d => d.data.name).join("")}\n${this.format(d.value)}`); leaf.append("circle") .attr("id", (d, i) => { d.leafUid = "leaf" + i return d.leafUid }) .attr("r", 0) .attr("fill", (d, i) => { return this.colors[i]; }) .attr("fill-opacity", 0.8) .transition() .duration(this.durationTime) .ease(this.d3Ease) .attr("r", d => d.r) leaf.append("clipPath") .attr("id", (d, i) => { d.clipUid = "clip" + i return d.clipUid }) .append("use") // .attr("transform",(d)=>{ // let x = this.textAlign=="Center"?(d.x1 - d.x0)/2 : 0 // let y = this.textAlign=="Center"?(d.y1 - d.y0)/2-d.fontSize : 0 // return `translate(${-x},${-y})` // }) .attr("xlink:href", d => "#" + d.leafUid); leaf.append("text") .transition() .duration(this.durationTime) .ease(this.d3Ease) // .attr("transform",(d)=>{ // let x = this.textAlign=="Center"?(d.x1 - d.x0)/2 : 0 // let y = this.textAlign=="Center"?(d.y1 - d.y0)/2-d.fontSize : 0 // return `translate(${x},${y})` // }) .attr("text-anchor", this.textAlign == "Center" ? "middle" : "start") .attr("font-size", (d) => { if (this.textAlign !== "Center") { return this.textFontSize } else { return d.fontSize } }) .attr("clip-path", d => `url(#${d.clipUid})`) // .attr("font-size",this.textFontSize) leaf.select("text") .selectAll("tspan") .data(d => { //.split(/(?=[A-Z][a-z])|\s+/g) return this.showLabel ? [d.data.name].concat(d.value) : [d.data.name].concat('') }) .join("tspan") .attr("x", this.textAlign === "Center" ? 0 : 3) .attr("y", (d, i, nodes) => `${(i === nodes.length - 1) * 0.3 + i * 0.9}em`) .attr("fill-opacity", (d, i, nodes) => i === nodes.length - 1 ? 0.7 : 1) .style("font-family", "regular") .attr("fill", this.fontColor) .transition() .duration(this.durationTime) .ease(this.d3Ease) .tween("text", function(d, i, data) { let that = this if (self.interpolate && i == data.length - 1 && self.showLabel) { let maxLength = Math.max(0, self.countDecimals(d)) let interNum = d3.interpolateNumber(0, Number(d)) return function(t) { d3.select(that).text(self.format(Number(interNum(t).toFixed(maxLength)))) } } else { if (i == data.length - 1) { d3.select(this).text(self.showLabel ? self.format(d) : "") } else { d3.select(this).text(d) } } }); } countDecimals(value) { if (!value) { return 0 } if (Math.floor(value) === Number(value)) return 0; if (!String(value).split(".")[1]) { return 0 } return String(value).split(".")[1].length || 0; } getData() { // 获取当前时间 let showTimeIndex = this.index > this.columnName.length - 1 ? this.columnName.length - 1 : this.index let showTime = this.columnName[showTimeIndex] let showData = this.jsonData.map(d => { return { value: +d[showTime], name: d[this.columnName[0]] } }) return { showData, showTime, ifEnd: this.index > this.columnName.length - 1 ? true : false } } 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] } }) } updataImages() { let that = this; for (var i = 0; i < that.jsonData.length; i++) { that.jsonData[i]["image"] = that.dynamicImages[i] } } destroy() { clearTimeout(this.timeout); if (this.timeout != null) this.timeout.stop(); Object.assign(this, null) } /** 千分位格式化 */ 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 = Number(num) } 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 } arrayToJson() { //添加配色 let that = this; that.columnName = that.data[0]; let json = []; 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]; } json.push(res); } that.jsonData = json; if (that.useImages) this.updataImages(); // that.sortJsonData(); } } export default DynamicPackedCircle;