實現的效果以下圖所示app
1.定義data數據,實現組件的可定製化svg
panel組件能定製的參數包括如下幾項函數
config: { size: 200, //panel的大小 label: '採油量', //panel的名稱 min: 0, //量程的最小值 max: 100, //量程的最大值 majorTicks: 6, //主刻度的個數 minorTicks: 4, //次刻度的個數 greenColor: '#109618', //區域1顏色 yellowColor:'#FF9900', //區域2顏色 redColor:'#DC3912', //區域3顏色 duration: 500, //轉動一次動畫須要的時間 value: 10} //panel顯示的值
2.處理數據函數setConfig()字體
處理config數據動畫
return { value: config.value, size: config.size, cx: config.size / 2, //圓心的x座標 cy: config.size / 2, //圓心的y座標 label: config.label, max: config.max, min: config.min, range: config.max - config.min, //量程 radius: config.size * 0.97 / 2, //半徑(稍許留白) minorTicks: config.minorTicks, majorTicks: config.majorTicks, duration: config.duration, greenColor: config.greenColor, yellowColor : config.yellowColor, greenZones: {from: config.min, to: config.min + (config.max - config.min) * 0.75}, //區域1範圍 yellowZones: { from: config.min + (config.max - config.min)*0.75, to: config.min +(config.max - config.min)*0.9 } , redColor:config.redColor, //區域2範圍 redZones: { from: config.min +(config.max - config.min)*0.9, to: config.max } //區域3範圍 }
3.render函數ui
3.1獲取數據this
let {size, cx, cy, radius, range, min, max, label, minorTicks, majorTicks, duration, value,greenZones,greenColor,yellowZones,yellowColor,redZones,redColor} = this.setConfig(config);
3.2添加svgspa
let self = this; //添加上下文 let panel = d3.select("#panel").append("svg:svg") .attr("class", "gauge") //添加類名gauge .attr("width", size) //設置svg寬度 .attr("height", size); //設置svg高度
3.3畫外圓指針
panel.append("svg:circle") //添加circle,圓 .attr("cx", cx) //設置circle的座標 .attr("cy", cy) .attr("r", radius) //設置circle的半徑 .style("fill", "#ccc") //設置circle的填充顏色 .style("stroke", "#000") //設置circle的輪廓顏色 .style("stroke-width", "0.5px");//設置circle的輪廓寬度
3.4畫內圓code
panel.append("svg:circle") .attr("cx", cx) .attr("cy", cy) .attr("r", 0.9 * radius) //半徑不同 .style("fill", "#fff") .style("stroke", "#e0e0e0") .style("stroke-width", "2px");
3.5畫顏色區域
黃色和綠色同下 panel.append("svg:path") //添加路徑 .style("fill", greenColor) .attr("d", d3.svg.arc() //添加圓弧 .startAngle(this.valueToRadians(greenZones.from, config)) //開始弧度 .endAngle(this.valueToRadians(greenZones.to, config)) //結束弧度 .innerRadius(0.65 * radius) //內圈半徑 .outerRadius(0.85 * radius)) //外圈半徑 .attr("transform", function () {//移動+旋轉 return "translate(" + cx + ", " + cy + ") rotate(270)" });
3.6設置label
let fontSize = Math.round(size / 9); //字體大小 panel.append("svg:text") //添加文本 .attr("x", cx) //文本位置 .attr("y", cy / 2 + fontSize / 2) .attr("dy", fontSize / 2) .attr("text-anchor", "middle")//文字角度 .text(label) //文字內容 .style("font-size", fontSize + "px")//字體大小 .style("fill", "#333") //顏色 .style("stroke-width", "0px");
3.7設置大小刻度線
let deltaSize = Math.round(size / 16); // let majorDelta = range / (majorTicks - 1);//大刻度之間的距離 { for (let major = min; major <= max; major += majorDelta) { //循環每一個大刻度 let minorDelta = majorDelta / minorTicks;//小刻度之間的距離 for (let minor = major + minorDelta; minor < Math.min(major + majorDelta, max); minor += minorDelta) { //循環每一個小刻度 let point1 = this.valueToPoint(minor, config, 0.75); let point2 = this.valueToPoint(minor, config, 0.85); //獲取小刻度線的(x1,y1)(x2,y2)位置 //添加小刻度線 panel.append("svg:line") .attr("x1", point1.x) //小刻度線的兩點座標 .attr("y1", point1.y) .attr("x2", point2.x) .attr("y2", point2.y) .style("stroke", "#666")//小刻度線顏色 .style("stroke-width", "1px");//小刻度的寬度 } //獲取大刻度線的(x1,y1)(x2,y2)位置 let point3 = this.valueToPoint(major, config, 0.7); let point4 = this.valueToPoint(major, config, 0.85); //添加大刻度線 panel.append("svg:line") .attr("x1", point3.x) .attr("y1", point3.y) .attr("x2", point4.x) .attr("y2", point4.y) .style("stroke", "#333") .style("stroke-width", "2px"); //標記最大值和最小值 if (major == min || major == max) { let point = this.valueToPoint(major, config, 0.63); panel.append("svg:text") .attr("x", point.x) .attr("y", point.y) .attr("dy", fontSize / 3) .attr("text-anchor", major == min ? "state" : "end") .text(major) .style("font-size", deltaSize + "px") .style("fill", "#333") .style("stroke-width", "0px") } } }
3.8設置指針
pointerContainer = panel.append("svg:g").attr("class", "pointerContainer");
//中間值 let midValue = (min + max) / 2; //指針的點軌跡 let pointerPath = this.buildPointerPath(midValue, config); //曲線生成器 let pointerLine = d3.svg.line() .x(function (d) { return d.x }) .y(function (d) { return d.y }) .interpolate("basis"); //貝斯曲線 //畫指針 pointerContainer.selectAll("path") .data([pointerPath]) .enter() .append("svg:path") .attr("d", pointerLine) .style("fill", "#dc3912") //填充的顏色 .style("stroke", "#c63310") //輪廓的顏色 .style("fill-opacity", 0.7); //填充的透明度
3.9畫指針中心圓
pointerContainer.append("svg:circle") .attr("cx", cx) .attr("cy", cy) .attr("r", 0.12 * radius) .style("fill", "#4684EE") .style("stroke", "#666") .style("opacity", 1);
3.10設置value
let valueSize = Math.round(size / 10); pointerContainer.append("svg:text") .attr("x", cx) .attr("y", size - (cy / 4) - valueSize) .text(midValue) .attr("dy", valueSize / 2) .attr("text-anchor", "middle") .style("font-size", valueSize + "px") .style("fill", "#000") .style("stroke-width", "0px"); this.redraw(value,duration,min,max,range,config,cy,cx)//指針渲染函數
4.設置指針渲染函數
渲染指針
redraw(value,duration,min,max,range,config,cy,cx){ let self =this; let panel = d3.select("#panel").select(".gauge"); //指針旋轉 let pointContainer = panel.select(".pointerContainer"); //設置value值 pointContainer.selectAll('text').text(value); let pointer = pointContainer.selectAll("path"); //指針移動 pointer.transition() .duration(duration)//持續時間 .attrTween("transform", function () { let pointerValue = value; //判斷超出量程的問題 if (value > max) pointerValue = max + 0.02 * range; else if (value < min) pointerValue = min - 0.02 * range; //目標旋轉角度:將數值轉化爲角度,並減去一個直角, let targetRotation = (self.valueToDegrees(pointerValue, config) - 90); //如今的角度 let currentRotation = self._currentRotation || targetRotation; self._currentRotation = targetRotation; return function (step) { let rotation = currentRotation + (targetRotation - currentRotation) * step; return " rotate(" + rotation + ", " + cx + ", " + cy + ")"; //定義旋轉 } }) }
5.公共函數
轉化爲弧度
valueToRadians(value, config) { return this.valueToDegrees(value, config) * Math.PI / 180 }
轉化爲角度
valueToDegrees(value, config) { let {range, min} = this.setConfig(config); return value / range * 270 - (min / range * 270 + 45) }
生成指針點數據
valueToPoint(value, config, factor){ let {cx, cy, radius} = this.setConfig(config); return { x: cx - radius * factor * Math.cos(this.valueToRadians(value, config)), y: cy - radius * factor * Math.sin(this.valueToRadians(value, config)) } }
生成指針
buildPointerPath(value, config){ let {range} = this.setConfig(config); let delta = range / 13, head = this.valueToPoint(value, config, 0.85), head1 = this.valueToPoint(value - delta, config, 0.12), head2 = this.valueToPoint(value + delta, config, 0.12), tailValue = value - (range * (1 / (270 / 360)) / 2), tail = this.valueToPoint(tailValue, config, 0.28), tail1 = this.valueToPoint(tailValue - delta, config, 0.12), tail2 = this.valueToPoint(tailValue + delta, config, 0.12); return [head, head1, tail2, tail, tail1, head2, head]; }