d3.js混合曲线标签/条形图,饼图

时间:2017-04-03 02:22:59

标签: javascript d3.js charts

enter image description here

我正在尝试重新创建此图表设计。带有dougnut图表,周围是弯曲的数据标签和内部曲线条形图。

//开始演示 http://jsfiddle.net/NYEaX/1753/

// 带内栏的最新演示 // http://jsfiddle.net/NYEaX/1761/

我尝试使用路径绘制外部标签,但标签显示不正确?

PO#  |  Item  |  Qty Ordered  |    Qty Received  |
 1
        123         20            [form Field]
        345         10            [form field]
 5
        232         5             [form field]

2 个答案:

答案 0 :(得分:1)

现场演示: http://jsfiddle.net/zu8m9ckd/8/

var $this = $("#progress");



var data = [{
    "group": "Chinese",
    "value": 22.6,
    "children": [{
        "growth": 1277.4
  }]
}, {
    "group": "Portuguese",
    "value": 4.2,
    "children": [{
        "growth": 989.6
  }]
}, {
    "group": "Spanish",
    "value": 7.8,
    "children": [{
        "growth": 743.2
  }]
}, {
    "group": "Rest",
    "value": 17.8,
    "children": [{
        "growth": 588.5
  }]
}, {
    "group": "French",
    "value": 3.0,
    "children": [{
        "growth": 398.2
  }]
}, {
    "group": "English",
    "value": 27.3,
    "children": [{
        "growth": 281.2
  }]
}, {
    "group": "German",
    "value": 3.8,
    "children": [{
        "growth": 173.1
  }]
}, {
    "group": "Japanese",
    "value": 5.0,
    "children": [{
        "growth": 110.6
  }]
}, {
    "group": "Korean",
    "value": 2.0,
    "children": [{
        "growth": 107.1
  }]
}, {
    "group": "Arabic",
    "value": 3.3,
    "children": [{
        "growth": 2501.2
  }]
}, {
    "group": "Russian",
    "value": 3.0,
    "children": [{
        "growth": 1825.8
  }]
}];


var w = 500;
var h = w;

var radius = Math.min(w, h) / 2 - 50;

var svg = d3.select($this[0])
    .append("svg")
    .attr("width", w)
    .attr("height", h)
    .append("g")


svg.append("g")
    .attr("class", "innerslices");
svg.append("g")
    .attr("class", "slices");
svg.append("g")
    .attr("class", "labels");
svg.append("g")
    .attr("class", "labelsvals");


var pie = d3.layout.pie()
    .sort(null)
    .value(function (d) {
        return d.value;
    });

const outerRadius=0.85;
const innerRadius=0.75;
const earthRadius=0.05;

const arc = d3.svg.arc()
    .outerRadius(radius * outerRadius)
    .innerRadius(radius * innerRadius);



const outerArc = d3.svg.arc()
    .innerRadius(radius - 20)
    .outerRadius(radius - 20);

const innerArc = d3.svg.arc()
    .innerRadius(radius - 55)
    .outerRadius(radius - 55);


svg.attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");


function colores_google(n) {
    var colores_g = ["#e9168a", "#f8dd2f", "#448875", "#c3bd75", "#2b2d39", "#311854", "#553814", "#f7b363", "#89191d", "#c12f34", "#2b2a2c", "#c5b8a6", "#57585b"];
    return colores_g[n % colores_g.length];
}


var totalsArray = [];
$.each(data, function (index, value) {
    value["groupid"] = index;

    var total = 0;

    $.each(value.children, function (i, v) {
        v["groupid"] = index;
        total += v.growth;
    });

    value["total"] = total;
    totalsArray.push(total);
});

var maxTotal = Math.max.apply(Math, totalsArray);




//slices
var slice = svg.select(".slices").selectAll("path.slice")
    .data(pie(data))


slice.enter()
    .insert("path")
    .style("fill", function (d) {
        return "#3b453f"; //colores_google(d.data.groupid);
    })
    .attr("class", "slice");

slice
    .transition().duration(1000)
    .attr("d", function (d) {
        return arc(d);
    })

slice.exit()
    .remove();
//slices



//innerslices

var innerslice = svg.select(".innerslices").selectAll("path.innerslice")
    .data(pie(data));

innerslice.enter()
    .insert("path")
    .style("fill", function (d) {
        return "#8fdfff"; //colores_google(d.data.groupid);
    })
    .attr("class", "innerslice");


innerslice
    .transition().duration(1000)
    .attr("d", function (d) {

        var arc3 = d3.svg.arc()
            .outerRadius(radius * innerRadius)
            .innerRadius(radius * (innerRadius-(innerRadius-earthRadius) * (d.data.children[0].growth / maxTotal)));
        return arc3(d);
    })

innerslice.exit()
    .remove();
//innerslice


var pieData = pie(data);
var pieAngle = pieData.map(function (p) {
    return (p.startAngle + p.endAngle) / 2 / Math.PI * 180;
});



const labels = svg.append('g')
    .classed('labels', true);


//base on angle to change `text-anchor` and `transform(rotate)` to make the position of text correct
labels.selectAll('text')
    .data(data)
    .enter()
    .append('text')
    .style('text-anchor', function (d, i) { //important
        const p = pieData[i];
        const angle = pieAngle[i];
        if (angle > 0 && angle <= 180) { //text-anchor depends on the angle
            return "start"
        }
        return "end"
    })
    .attr("transform", function (d, i) { //important             
        const p = pieData[i];
        let angle = pieAngle[i];
        if (angle > 0 && angle <= 180) { //rotation depends on the angle
            angle = angle - 180;
        }
        return `translate(${outerArc.centroid(p)}) rotate(${angle+90} 0 0) `
    })
    .text(function (d) {
        return d.group;
    });

//similar with outer black text
labels.selectAll('text.inner')
    .data(data)
    .enter()
    .append('text')
    .attr("class","inner")
    .style('text-anchor', function (d, i) { //important
        const p = pieData[i];
        const angle = pieAngle[i];
        if (angle > 0 && angle <= 180) { //text-anchor depends on the angle
            return "end"
        }
        return "start"
    })
    .attr("transform", function (d, i) { //important             
        const p = pieData[i];
        let angle = pieAngle[i];
        if (angle > 0 && angle <= 180) { //rotation depends on the angle
            angle = angle - 180;
        }
        return `translate(${innerArc.centroid(p)}) rotate(${angle+90} 0 0) `
    })
    .text(function (d) {
        return d.children[0].growth+"%";
    });

const labelFontSize = 10;
const labelValRadius = (radius * 0.80 - labelFontSize * 0.35); //calculate correct radius
const labelValRadius1 = (radius * 0.80 + labelFontSize * 0.35); //why 0.35? I don't know. Try to google it.


const labelsVals = svg.append('g')
    .classed('labelsvals', true);

//define two paths to make the direction of labels correct
labelsVals.append('def')
    .append('path')
    .attr('id', 'label-path-1')
    .attr('d', `m0 ${-labelValRadius} a${labelValRadius} ${labelValRadius} 0 1,1 -0.01 0`);
labelsVals.append('def')
    .append('path')
    .attr('id', 'label-path-2')
    .attr('d', `m0 ${-labelValRadius1} a${labelValRadius1} ${labelValRadius1} 0 1,0 0.01 0`);

labelsVals.selectAll('text')
    .data(data)
    .enter()
    .append('text')
    .style('font-size', labelFontSize)
    .style('font-weight', "bold")
    .style('text-anchor', 'middle')
    .append('textPath')
    .attr('href', function (d, i) {
        const p = pieData[i];
        const angle = pieAngle[i];
        if (angle > 90 && angle <= 270) { //based on angle to choose the path
            return '#label-path-2';
        } else {
            return '#label-path-1';
        }
    })
    .attr('startOffset', function (d, i) {
        const p = pieData[i];
        const angle = pieAngle[i];
        let percent = (p.startAngle + p.endAngle) / 2 / 2 / Math.PI * 100;
        if (angle > 90 && angle <= 270) { //calculate the correct percent for each path respectively
            return 100 - percent + "%";
        }
        return percent + "%";
    })
    .text(function (d) {
        if (d.value > 2) {//according to the simple image, the percent less than 3% should only show int part
            return d.value.toFixed(1) + "%";
        } else {
            return d.value.toFixed(0) + "%";
        }

    });
body {
  background: #eeeeee;
}

path {
  stroke-width: 1px;
  stroke: #eeeeee;
}

.small {
  fill: steelblue;
}

.big {
  stroke: #666;
  fill: #ddd;
}

.small:hover {
  stroke: steelblue;
  fill: lightsteelblue;
}

.test {
  padding: 30px
}

#progress {
  position: relative;
  margin-top: 20px
}

.progresschart {
  background: white;
  border-radius: 100px;
  width: 100px;
  height: 100px;
  overflow: hidden;
  border: 1px solid grey;
  margin-top: 5px;
}

.bar {
  fill: steelblue;
}

.bar:hover {
  fill: brown;
}

.progresslabels {
  position: absolute;
  top: 0px;
  left: 0;
}

.labelsvals {
  fill: #ffffff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="progress"></div>

您的代码中有许多部分应该更正。我选择两个主要部分来解释:

白标:

//base on angle to change `text-anchor` and `transform(rotate)` to make the position of text correct
labels.selectAll('text')
    .data(data)
    .enter()
    .append('text')
    .style('text-anchor', function (d, i) { //important
        const p = pieData[i];
        const angle = pieAngle[i];
        if (angle > 0 && angle <= 180) { //text-anchor depends on the angle
            return "start"
        }
        return "end"
    })
    .attr("transform", function (d, i) { //important             
        const p = pieData[i];
        let angle = pieAngle[i];
        if (angle > 0 && angle <= 180) { //rotation depends on the angle
            angle = angle - 180;
        }
        return `translate(${outerArc.centroid(p)}) rotate(${angle+90} 0 0) `
    })
    .text(function (d) {
        return d.group;
    });

外部黑色标签:

const labelFontSize = 10;
const labelValRadius = (radius * 0.80 - labelFontSize * 0.35); //calculate correct radius
const labelValRadius1 = (radius * 0.80 + labelFontSize * 0.35); //why 0.35? I don't know. Try to google it.


const labelsVals = svg.append('g')
    .classed('labelsvals', true);

//define two paths to make the direction of labels correct
labelsVals.append('def')
    .append('path')
    .attr('id', 'label-path-1')
    .attr('d', `m0 ${-labelValRadius} a${labelValRadius} ${labelValRadius} 0 1,1 -0.01 0`);
labelsVals.append('def')
    .append('path')
    .attr('id', 'label-path-2')
    .attr('d', `m0 ${-labelValRadius1} a${labelValRadius1} ${labelValRadius1} 0 1,0 0.01 0`);

labelsVals.selectAll('text')
    .data(data)
    .enter()
    .append('text')
    .style('font-size', labelFontSize)
    .style('font-weight', "bold")
    .style('text-anchor', 'middle')
    .append('textPath')
    .attr('href', function (d, i) {
        const p = pieData[i];
        const angle = pieAngle[i];
        if (angle > 90 && angle <= 270) { //based on angle to choose the path
            return '#label-path-2';
        } else {
            return '#label-path-1';
        }
    })
    .attr('startOffset', function (d, i) {
        const p = pieData[i];
        const angle = pieAngle[i];
        let percent = (p.startAngle + p.endAngle) / 2 / 2 / Math.PI * 100;
        if (angle > 90 && angle <= 270) { //calculate the correct percent for each path respectively
            return 100 - percent + "%";
        }
        return percent + "%";
    })
    .text(function (d) {
        if (d.value > 2) {//according to the simple image, the percent less than 3% should only show int part
            return d.value.toFixed(1) + "%";
        } else {
            return d.value.toFixed(0) + "%";
        }

    });

内部黑色标签

//similar with outer black label
labels.selectAll('text.inner')
    .data(data)
    .enter()
    .append('text')
    .attr("class","inner")
    .style('text-anchor', function (d, i) { //important
        const p = pieData[i];
        const angle = pieAngle[i];
        if (angle > 0 && angle <= 180) { //text-anchor depends on the angle
            return "end"
        }
        return "start"
    })
    .attr("transform", function (d, i) { //important             
        const p = pieData[i];
        let angle = pieAngle[i];
        if (angle > 0 && angle <= 180) { //rotation depends on the angle
            angle = angle - 180;
        }
        return `translate(${innerArc.centroid(p)}) rotate(${angle+90} 0 0) `
    })
    .text(function (d) {
        return d.children[0].growth+"%";
    });

答案 1 :(得分:-1)

enter image description here

在这个小提琴中,我设法得到了甜甜圈,内圈和一些标签。

但我需要让标签以正确的方向,颜色,比例更精确地反射。

//最新代码

http://jsfiddle.net/NYEaX/1761/

//innerslices
var arc2 = d3.svg.arc()
  .outerRadius(radius * 0.75)
  .innerRadius(radius * 0.25);

var innerslice = svg.select(".innerslices").selectAll("path.innerslice")
  .data(pie(data));

innerslice.enter()
  .insert("path")
  .style("fill", function(d) {
    return "#8fdfff";//colores_google(d.data.groupid);
  })
  .attr("class", "innerslice");

innerslice
  .transition().duration(1000)
  .attrTween("d", function(d) {

    var arc3 = d3.svg.arc()
      .outerRadius(radius * 0.75)
      .innerRadius(radius * 0.25 * (d.data.children[0].growth / maxTotal));

    this._current = this._current || d;
    var interpolate = d3.interpolate(this._current, d);
    this._current = interpolate(0);
    return function(t) {
      return arc3(interpolate(t));
    };
  })

innerslice.exit()
  .remove();
//innerslice