From 6a16d12436d3f7e59e8e28f68c282ef5bb4d7c24 Mon Sep 17 00:00:00 2001 From: wangwenrui Date: Tue, 28 Jul 2020 19:12:32 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=96=B0=E5=A2=9Ed3=20line=20chart=20?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=EF=BC=88test=E9=A1=B5=E9=9D=A2=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/charts/d3-line-chart2.vue | 130 ++++ .../src/components/charts/d3-line.js | 630 ++++++++++++++++++ .../src/components/charts/d3-line.scss | 72 ++ .../src/components/common/language/en.js | 1 + nezha-fronted/src/router/index.js | 2 +- 5 files changed, 834 insertions(+), 1 deletion(-) create mode 100644 nezha-fronted/src/components/charts/d3-line-chart2.vue create mode 100644 nezha-fronted/src/components/charts/d3-line.js create mode 100644 nezha-fronted/src/components/charts/d3-line.scss diff --git a/nezha-fronted/src/components/charts/d3-line-chart2.vue b/nezha-fronted/src/components/charts/d3-line-chart2.vue new file mode 100644 index 000000000..d70cb36de --- /dev/null +++ b/nezha-fronted/src/components/charts/d3-line-chart2.vue @@ -0,0 +1,130 @@ + + + + + diff --git a/nezha-fronted/src/components/charts/d3-line.js b/nezha-fronted/src/components/charts/d3-line.js new file mode 100644 index 000000000..0456834a7 --- /dev/null +++ b/nezha-fronted/src/components/charts/d3-line.js @@ -0,0 +1,630 @@ +import * as d3 from "d3"; +import './d3-line.scss' +import {randomcolor} from "../common/js/radomcolor/randomcolor"; + +export function D3LineChart(selector,option){ + return { + selector:selector, //选择器 + width:option.width, + height:option.height, + timeFormat:option.timeFormat?option.timeFormat:'%Y-%m-%d', + _timeFormat:null, + datas:option.datas, + legends:option.legends, + title:option.title?option.title:'', + subTitle:option.subTitle?option.subTitle:'', + colors:option.colors, + padding:option.padding?option.padding:{top:40,left:40,bottom:40,right:20}, + duration:option.duration?option.duration:800, + _head_height:0, + _foot_height:0, + currentLineNum:0, + showXAxisTick:option.showXAxisTick?option.showXAxisTick:false, + showYAxisTick:option.showYAxisTick?option.showYAxisTick:false, + tooltipFormatter:option.tooltipFormatter, + + init:function(){ + //定义画布 + this.svg=d3.select(this.selector) + .append('svg') + .attr('width',this.width) + .attr('height',this.height) + .on('mousemove', drawTooltip) + .on('mouseout', removeTooltip); + + this.initOption(); + this.constomAction(); + + this.drawTitle(); + this.creatScale(); + this.createDefs(); + this.createXInnerBar(); + this.createYInnderBar(); + this.createXAxis(); + this.createYAxis(); + + // this.createLegends(); + this.drawLines(); + + this.createZoom() + + let $self=this; + let oldToolVal=null; + function removeTooltip() { + if ($self.tooltip) $self.tooltip.style('display', 'none'); + if ($self.tooltipLine) $self.tooltipLine.attr('stroke', 'none'); + } + function drawTipLine(time){ + if ($self.currentTransform) + $self.tooltipLine + .attr('stroke', 'black') + .attr("clip-path", "url(#clip)") + .attr('x1', $self.currentTransform.applyX($self.xScale(time))) + .attr('x2', $self.currentTransform.applyX($self.xScale(time))) + .attr('y1', $self._head_height) + .attr('y2', $self.height - $self._foot_height); + else + + $self.tooltipLine + .attr('stroke', 'black') + .attr("clip-path", "url(#clip)") + .attr('x1', $self.xScale(time)) + .attr('x2', $self.xScale(time)) + .attr('y1', $self._head_height) + .attr('y2', $self.height - $self._foot_height); + } + function drawTooltip(){ + var x=d3.mouse($self.svg.node())[0]; + if(x<$self.padding.left||x>$self.width-$self.padding.right){ + return false; + } + if ($self.currentTransform) + var time = $self.currentTransform.rescaleX($self.xScale).invert(d3.mouse($self.svg.node())[0]); + else + var time = $self.xScale.invert(d3.mouse($self.svg.node())[0]); + + // drawTipLine(time) + + $self.tooltip.html(d3.timeFormat('%Y-%m-%d %H:%M:%S')(time)) + .style('display', 'block') + .style('left', d3.event.pageX + 20 + 'px') + .style('top', d3.event.pageY - 20 + 'px') + .selectAll() + .data($self.datas).enter() + .append('div') + .html(function(d, i,g) { + let toolVal=d[0]; + let min=Math.abs(+toolVal[0] - +time ) + d.forEach(item=>{ + let temp=Math.abs(+item[0] - +time) + if(temp - min < 0){ + min=temp; + toolVal=item; + } + }) + if(toolVal){ + oldToolVal=toolVal; + let legend=$self.legends[i]; + if(!legend.isGray){ + return `
+
${legend.alias?legend.alias:legend.name}:
+
${toolVal[1]}
+
` + } + } + }); + } + }, + initOption:function(){ + this._timeFormat=d3.timeFormat(this.timeFormat) + this._head_height=this.padding.top + this._foot_height=this.padding.bottom + this.currentLineNum=this.datas.length; + this.tooltip = d3.select('body') + .append('div') + .attr('style',"position: absolute; background-color: rgba(221,228,237,1);border-color:rgba(221,228,237,1); padding: 5px; display: none; left: 983px; top: 89px;") + this.tooltipLine = this.svg.append('line'); + + this.minMax=getMinMax(this.datas); + + if(this.padding.left < computeDistance(this.minMax.max+'')){ + this.padding.left = computeDistance(this.minMax.max+'') + } + + if(!this.colors || this.colors.length{return d[1]})]) + min=d3.min([min,d3.min(arr[i],d=>{return d[1]})]) + } + return{min:min,max:max} +} +function computeDistance(str){ + var width = 0; + var html = document.createElement('span'); + html.innerText = str; + html.className = 'getTextWidth'; + document.querySelector('body').appendChild(html); + width = document.querySelector('.getTextWidth').offsetWidth; + document.querySelector('.getTextWidth').remove(); + return Number((width+5)); +} + +function CrystalLineObject(chart) { + this.group = null; + this.path = null; + this.oldData = []; + let dataset=chart.datas; + let svg=chart.svg; + let xScale=chart.xScale; + let yScale=chart.yScale; + let lineColor=chart.colors; + const dispatch=chart.dispatch; + + this.init = function(id) { + var arr = dataset[id]; + let legend=chart.legends[id]; + this.group = svg.append("g"); + let $self=this; + var line = d3.line() + .x(function(d, i) { + return xScale(d[0]); + }) + .y(function(d) { + return yScale(d[1]); + }) + .curve(d3.curveCatmullRom.alpha(0.3)); //折线曲度 + + //添加折线 + this.path = this.group.append("path") + .attr("d", line(arr)) + .attr('class','chart-line') + .style("fill", "none") + .style("stroke-width", 1) + .attr("clip-path", "url(#clip)") + .style("stroke", lineColor[id]) + .style("stroke-opacity", 0.9) + .on('line-single-show',function(d,i,group){ + let event=d3.event; + let name=event.detail?event.detail.name:"" + if(legend.name != name){ + $self.group + .transition() + .duration(chart.duration) + .style('opacity','0') + legend.isGray=true; + }else{ + $self.group.transition() + .duration(chart.duration).style('opacity','1') + legend.isGray=false; + } + }) + .on('line-all-show',function(){ + $self.group.transition() + .duration(chart.duration).style('opacity','1') + chart.legends.forEach(item=>{ + item.isGray=false; + }) + }) + + //添加系列的小圆点 + /* this.group.selectAll("circle") + .data(arr) + .enter() + .append("circle") + .attr("clip-path", "url(#clip)") + .attr("cx", function(d, i) { + return xScale(d[0]); + }) + .attr("cy", function(d) { + return yScale(d[1]); + }) + .attr("r", 5) + .attr("fill", lineColor[id]);*/ + this.oldData = arr; + }; + + this.scale = function(id, _duration, transform) { + var arr = dataset[id]; + + var line = d3.line() + .x(function(d, i) { + + return transform.applyX(xScale(d[0])) + }) + .y(function(d) { + return yScale(d[1]); + }) + + //添加折线 + this.group.select("path") + .attr("d", line(arr)) + .style("fill", "none") + .style("stroke-width", 1) + .style("stroke", lineColor[id]) + .style("stroke-opacity", 0.9); + + this.group.selectAll("circle") + .attr("cx", function(d, i) { + return transform.applyX(xScale(d[0])); + }) + .attr("cy", function(d) { + return yScale(d[1]); + }) + + } + + //动画初始化方法 + this.movieBegin = function(id) { + var arr = dataset[id]; + //补足/删除路径 + var olddata = this.oldData; + var line = d3.line() + .x(function(d, i) { + if (i >= olddata.length) return chart.width - chart.padding.left; + else return xScale(d[0]); + }) + .y(function(d, i) { + if (i >= olddata.length) return chart.height - chart._foot_height; + else return yScale(olddata[i].value); + }); + + //路径初始化 + this.path.attr("d", line(arr)); + + //截断旧数据 + var tempData = olddata.slice(0, arr.length); + /*var circle = this.group.selectAll("circle").data(tempData); + + //删除多余的圆点 + circle.exit().remove();*/ + + //圆点初始化,添加圆点,多出来的到右侧底部 + /*this.group.selectAll("circle") + .data(arr) + .enter() + .append("circle") + .attr("cx", function(d, i) { + if (i >= olddata.length) return chart.width - chart.padding; + else return xScale(d[0]); + }) + .attr("cy", function(d, i) { + if (i >= olddata.length) return chart.height - chart._foot_height; + else return yScale(d[1]); + }) + .attr("r", 5) + .attr("fill", lineColor[id]);*/ + + this.oldData = arr; + }; + + //重绘加动画效果 + this.reDraw = function(id, _duration) { + var arr = dataset[id]; + var line = d3.line() + .x(function(d, i) { + return xScale(d[0]); + }) + .y(function(d) { + return yScale(d[1]); + }); + + //路径动画 + this.path.transition().duration(_duration).attr("d", line(arr)); + + //圆点动画 + /* this.group.selectAll("circle") + .transition() + .duration(_duration) + .attr("cx", function(d, i) { + return xScale(d[0]); + }) + .attr("cy", function(d) { + return yScale(d[1]); + })*/ + }; + + //从画布删除折线 + this.remove = function() { + this.group.remove(); + }; + +} diff --git a/nezha-fronted/src/components/charts/d3-line.scss b/nezha-fronted/src/components/charts/d3-line.scss new file mode 100644 index 000000000..c2b5c5283 --- /dev/null +++ b/nezha-fronted/src/components/charts/d3-line.scss @@ -0,0 +1,72 @@ +.chart{ + position: relative; +} +.title { + font-family: Arial, 微软雅黑; + font-size: 18px; + text-anchor: middle; +} + +.subTitle { + font-family: Arial, 宋体; + font-size: 12px; + text-anchor: middle; + fill: #666 +} + +.axis path, +.axis line { + fill: none; + stroke: black; + shape-rendering: crispEdges; +} + +.axis text { + font-family: sans-serif; + font-size: 11px; + fill: #999; +} + +.inner_line path, +.inner_line line { + fill: none; + stroke: #ccc; + shape-rendering: crispEdges; + opacity: .5; +} + +.legend { + font-size: 12px; + font-family: Arial, Helvetica, sans-serif; + text-align:left; + max-height:80px; + min-height:25px; + left: 10px; + line-height: 18px; + position: absolute; + padding-bottom:3px; +} + +.legend-shape{ + display:inline-block; + margin-right:5px; + border-radius:10px; + width:15px; + height:5px; + vertical-align: middle; +} +.ft-gr{ + color:lightgray; +} + +.legend-item{ + text-overflow:ellipsis; + white-space:nowrap; + /*width:100%;*/ + margin-right:10px; + overflow-x:hidden; + cursor:pointer; + display:inline-block; + float:left; + line-height: 20px; +} diff --git a/nezha-fronted/src/components/common/language/en.js b/nezha-fronted/src/components/common/language/en.js index 51a4e207c..13d038c06 100644 --- a/nezha-fronted/src/components/common/language/en.js +++ b/nezha-fronted/src/components/common/language/en.js @@ -418,6 +418,7 @@ const en = { vendor:'Vendor', ping:'Ping', }, + featureTitle:'Attribute', /*createAsset:{ title:'New asset',//'新增资产' sn:'SN',//SN diff --git a/nezha-fronted/src/router/index.js b/nezha-fronted/src/router/index.js index 97a3aebb2..928f45c14 100644 --- a/nezha-fronted/src/router/index.js +++ b/nezha-fronted/src/router/index.js @@ -50,7 +50,7 @@ export default new Router({ }, { path: 'test', - component: resolve => require(['../components/page/dashboard/explore/editor.vue'],resolve) + component: resolve => require(['../components/charts/d3-line-chart2'],resolve) }, { path: '/project',