UNPKG

radar-chart

Version:
345 lines (335 loc) 13.2 kB
(function(){ var Radar = { //regexLine : /<line.*?x1="([\d\.]*?)".*?y1="([\d\.]*?)".*?x2="([\d\.]*?)".*?y2="([\d\.]*?)".*?>/gi; //newline : '<line x1="$1" y1="$2" x2="$3" y2="$4"/>'; options : { viewBox : '0,0,610,610', preserveAspectRatio : "none", chart : {width:'100%', height:'100%'}, isDonut : true, //中心點是否摟空 centerRadius : 20, //中心點空白偏移量 levels:5, //橫軸層數 verticalAxis : {show:true, css:'vertical-axis'}, //縱軸線 horizontalAxis : {show:true, css: 'horizontal-axis'}, //橫軸線 verticalZoom : 0.8, //縱軸縮放 (縱軸padding功用) maxValue : 100, //縱軸上的最大值 minValue : 0, //縱軸上的最小值 scale : 10, //縱軸刻度數 scaleText : '', scaleShow : true, scaleColor : 'black', scaleWidth : 2, scaleFontSize : 14, showTitle: true, //項目標題 placement:{show:true, type:'circle' , radius: 7}, //項目放置點 {type:'rect', width:10, height:10} //panel infoPanel : {show:true, width:70, height:24}, //資訊板 infoPanelRadius: {rx:5, ry:5}, //圓角 infoPanelGap : {h:10,v:10}, infoPadding : {left:3 , top: 4}, // arc : 2 * Math.PI, //圓弧 areaShow : true, //是否顯示一個項目的區塊 color : d3.scale.category10(), horizontalDashEnable : true , //是否呈現虛線 verticalDashEnable : true, itemTitleGap : 6, //縱軸上的標題間隔 surmiseFontGap: 3, //假設的font gap isDebug : false //除錯模式才會把 例外丟出 }, mixOptions : function(options){ var opt = Object.create(this.options); if(options!=null){ for(var attr in options){ opt[attr] = options[attr]; } } return opt; }, reset : function(id){ d3.select(id).select('svg').remove(); }, //利用三角函式計算坐標點 getPoint : function(radius, radians, offset){ return { x : radius * -Math.sin(radians) + offset, y : radius * -Math.cos(radians) + offset }; }, //繪製刻度表 drawScaleLine : function(svg, verticalLength, onePiece, minLength, opt, self){ if(opt.scaleShow){ var p1 = self.getPoint(verticalLength, 0 , minLength); var p2 = self.getPoint(opt.centerRadius, 0 , minLength); var basicLength = (p2.y - p1.y) / opt.levels; var basicValue = (opt.maxValue - opt.minValue) / opt.levels; /* svg.append('g').attr('class','scale-group') .append('line') .attr('x1', p1.x) .attr('y1', p1.y) .attr('x2', p2.x) .attr('y2', p2.y) .attr('stroke', opt.scaleColor ) .attr('stroke-width', opt.scaleWidth); */ var g = svg.append('g').attr('class','scale-group'); for(var j=0 , count = opt.levels ; j <= count ; j++){ var textY = p2.y - j * basicLength; g.append('text') .attr('x', p1.x) .attr('y', textY) .attr('fill', opt.scaleColor) .attr('font-size', opt.scaleFontSize + 'px') .text(j * basicValue + opt.scaleText); } } }, //繪製橫軸 drawHorizontalAxis : function(svg, index, onePiece, minLength, radius, group, self){ var p1 = self.getPoint(radius, onePiece * (index) , minLength); var p2 = self.getPoint(radius, onePiece * (index+1) , minLength); var line = group.append('line'); var css = (self.options.horizontalDashEnable) ? 'horizontal-axis dash' : 'horizontal-axis' ; line.attr('class', css).attr('x1', p1.x).attr('y1', p1.y).attr('x2', p2.x).attr('y2', p2.y); }, //繪製橫軸網 drawHorizontalWeb : function(svg, show, onePiece, minLength, verticalLength, count, opt, self){ if(show){ var horizontalAxisGap = verticalLength / opt.levels; for(var outIndex=0; outIndex <= opt.levels ; outIndex++){ var group = svg.append('g').attr('class', 'horizontal-web'); var radius = outIndex * horizontalAxisGap + opt.centerRadius; for(var index=0; index < count ; index++){ svg.call(self.drawHorizontalAxis, index, onePiece, minLength, radius, group, self); } } } }, //繪製縱軸 drawVerticalAxis : function(svg, p1, p2, group, self){ var line = group.append('line'); var css = (self.options.verticalDashEnable) ? ' vertical-axis dash' : 'vertical-axis'; line.attr('class', css).attr('x1', p1.x).attr('y1', p1.y).attr('x2', p2.x).attr('y2', p2.y); }, //繪製軸上的標題 drawItemTitle : function(svg, p1, p2, title, group, minLength, opt, self){ //minLength 就是矩行的中心點位置 var x = Math.ceil(p1.x); var y = Math.ceil(p1.y); var text = group.append('text').attr('class', 'item-title').text(title); var fontSize = text.style("font-size").replace('pt','').replace('px','') | 0; var gapTotal = opt.surmiseFontGap * (title.length - 1); var offsetW = (fontSize * title.length + gapTotal) ; // var offsetH = fontSize / 2 | 0; //x is rigth if(x > minLength){ text.attr('x', p1.x + opt.itemTitleGap ); }else if(x < minLength){ text.attr('x', p1.x - opt.itemTitleGap - offsetW); }else{ //x is center text.attr('x', p1.x - offsetW / 2 ); } if(y > minLength){ text.attr('y', p1.y + offsetH + opt.itemTitleGap + 5); }else if(y < minLength){ text.attr('y', p1.y - offsetH - opt.itemTitleGap); }else{ text.attr('y', p1.y); } }, //繪製縱軸網 drawVerticalWeb : function(svg, show, onePiece, minLength, verticalLength, count, opt, self){ if(show){ var axixGroup = svg.append('g').attr('class', 'vertical-web'); var titleGroup = svg.append('g').attr('class', 'title-group'); var titleList = svg.datum()[0]; for(var index=0; index < count ; index++){ var currentRadians = onePiece * index; //當前縱軸的弧度 var p1 = self.getPoint(verticalLength, currentRadians , minLength); var p2 = self.getPoint(opt.centerRadius, currentRadians , minLength); var title = titleList[index].title; svg.call(self.drawVerticalAxis, p1, p2, axixGroup, self); svg.call(self.drawItemTitle, p1, p2, title, titleGroup, minLength, opt, self); } } }, //計算出所有值得落點 getPoinst : function(svg, onePiece, minLength, centerRadius, opt, self){ var pointList = { areaPoints : [], pointPoints : [] }; var areaCount = svg.datum().length; for(var outIndex=0; outIndex < areaCount; outIndex++ ){ pointList.pointPoints[outIndex] = []; var areaData = svg.datum()[outIndex]; var points=''; for(var index=0, valueCount=areaData.length ; index < valueCount ; index++){ var val = areaData[index].value; //var radius = (minLength-centerRadius )/ (opt.maxValue - opt.minValue) * val + centerRadius; var radius = (minLength*opt.verticalZoom )/ (opt.maxValue - opt.minValue) * val + centerRadius; var point = self.getPoint(radius, onePiece * index , minLength ); points += point.x + ' ' + point.y + ','; pointList.pointPoints[outIndex].push(point); } points = points.substr(0, points.length-1); pointList.areaPoints.push(points); } return pointList; }, areaMouseOver : function(data){ var areaGroup = d3.select('.area-box').selectAll('.area-group'); var areas = areaGroup.selectAll('.area'); //var marks = areaGroup.selectAll('.mark'); var currentArea = d3.select(this).select('.area'); //var currentMarks = d3.select(this).selectAll('.mark'); areas.classed('areaFade', true); //marks.classed('areaFade', true); currentArea.classed('areaFade', false).classed('areaHover', true); //currentMarks.classed('areaFade', false);//.classed('areaHover', true); }, areaMouseOut : function(data){ d3.select('.area-box').selectAll('.area-group').selectAll('.area').classed('areaFade', false); d3.select(this).select('.area').classed('areaHover', false); //d3.select('.area-box').selectAll('.area-group').selectAll('.mark').classed('areaFade', false); }, //繪製區域 drawArea : function(svg, show, areaPoints, opt, self){ var areaBox = svg.append('g').attr('class', 'area-box'); if(show){ for(var i=0, l=areaPoints.length; i < l ; i++){ var areaGrup = areaBox.append('g').attr('class', 'area-group'); var area = areaGrup.append('polygon'); area.attr('class', 'area') .attr('points',areaPoints[i]) .style('fill', opt.color(i)) .style('stroke', opt.color(i)); areaGrup.on('mouseover', self.areaMouseOver); areaGrup.on('mouseout' , self.areaMouseOut); } } }, markMouseOver : function(data){ var self = this.self; var target = d3.select(this); var x = Number(target.attr('cx')); var y = Number(target.attr('cy')); var gap = self.options.infoPanelGap; var padding = self.options.infoPadding; var viewBoxList = self.options.viewBox.split(','); var w = viewBoxList[2]; var h = viewBoxList[3]; var panelX = 0; if(x + gap.h + self.options.infoPanel.width > w ){ panelX = x - gap.h - self.options.infoPanel.width; panelTitleX = x - gap.h + padding.top - self.options.infoPanel.width; }else{ panelX = x + gap.h; panelTitleX = x + gap.h + padding.top; } self.panel .classed('panel-show', true) .attr('x', panelX) .attr('y', y + gap.v); self.panelTitle .classed('panel-show', true) .attr('x', panelTitleX) .attr('y', y + gap.v + 14 + padding.left) .text(data.title + ':' + data.showValue); }, markMouseOut : function(data){ var self = this.self; self.panel.classed('panel-show', false); self.panelTitle.classed('panel-show', false); self.panelTitle.text(''); }, //標記分數 drawMark : function(svg, points, opt, self){ var gulps = svg.selectAll('.area-group')[0]; var dataList = svg.datum(); for(var groupIndex=0, count=points.length ; groupIndex < count ; groupIndex++){ var gulp = d3.select(gulps[groupIndex]); //這是跟區域同一圖層 var markPoints = points[groupIndex]; var dataItem = dataList[groupIndex]; for(var markIndex=0 , l = markPoints.length ; markIndex < l ; markIndex++){ var obj = dataItem[markIndex]; var p = markPoints[markIndex]; var mark = gulp.append(opt.placement.type); mark.attr('class', 'mark'); mark.attr('cx', p.x).attr('cy', p.y).attr('r', opt.placement.radius); mark.style('fill', opt.color(groupIndex)); mark.datum(obj); //bind data mark[0][0].self = self; //bind self mark.on('mouseover', self.markMouseOver); mark.on('mouseout' , self.markMouseOut); } } }, panel : null, panelTitle : null, //panelText : null, drawInfoPanel : function(svg, opt, self){ var w = opt.infoPanel.width; var h = opt.infoPanel.height; var panelGroup = svg.append('g').attr('class','infoPanel-group'); self.panel = panelGroup.append('rect') .attr('class','infoPanel') .attr('width', w) .attr('height', h) .attr('x', 0) .attr('y', 0) .attr('rx', opt.infoPanelRadius.rx) .attr('ry', opt.infoPanelRadius.ry); self.panelTitle = panelGroup.append('text').attr('class', 'panel-title'); // self.panelText = panelGroup.append('text').attr('class', 'panel-text'); }, renderRadar : function(svg, opt, self){ var total = svg.datum()[0].length; var onePiece = opt.arc / total; //一條直軸所占的弧 var viewBoxList = opt.viewBox.split(','); var w = viewBoxList[2]; var h = viewBoxList[3]; //var minLength = Math.min(svg.property('clientHeight') / 2, svg.property('clientWidth') / 2); //矩形的最小邊 / 2 var minLength = Math.min(h / 2, w / 2); var verticalLength = opt.verticalZoom * minLength;// - opt.centerRadius; //縱軸長度 var pointsList = self.getPoinst(svg, onePiece, minLength, opt.centerRadius, opt, self); //計算出落點坐標 //reander horizontal axis svg.call(self.drawHorizontalWeb, opt.horizontalAxis.show, onePiece, minLength, verticalLength, total, opt, self); //render vertical axis svg.call(self.drawVerticalWeb, opt.verticalAxis.show, onePiece, minLength, verticalLength+opt.centerRadius, total, opt, self); //繪製刻度 svg.call(self.drawScaleLine, verticalLength + opt.centerRadius, onePiece, minLength, opt, self); //reander area svg.call(self.drawArea, opt.areaShow, pointsList.areaPoints, opt, self ); //rander mark svg.call(self.drawMark, pointsList.pointPoints, opt, self); //rander info panel svg.call(self.drawInfoPanel, opt, self); }, draw : function(id, data, options){ try{ this.reset(id); var opt = (options) ? this.mixOptions(options) : this.mixOptions(null) ; var svg = d3.select(id).append("svg"); svg.attr('class', 'radar'); svg.attr("width", opt.chart.width); svg.attr("height", opt.chart.height); svg.attr("viewBox",opt.viewBox); svg.attr("preserveAspectRatio", opt.preserveAspectRatio); svg.datum(data); //塞入數據 svg.call(this.renderRadar, opt, this); //要呼叫的函數 (會先執行) }catch(e){ if(opt.isDebug) throw e; } } }; if(!window.Radar){ window.Radar = Radar; } if(typeof(module)!= "undefined"){ module.exports = Radar; } }).call(this);