d3标有标签和动画的水平图表

时间:2018-11-19 09:40:36

标签: d3.js

我正在制作d3js水平图-设计师特别采用这种方式制作标签。

我已经建立了以下内容-但想在具有动画属性的旧代码上进行更多建模。

//当前图表 https://codepen.io/anon/pen/ZmJzXZ

//静态垂直图http://jsfiddle.net/pg886/201/

//动画垂直图表http://jsfiddle.net/Qh9X5/12073/

-d3js代码

   var data = [{
            "name": "Apples",
            "value": 20,
    },
        {
            "name": "Bananas",
            "value": 12,
    },
        {
            "name": "Grapes",
            "value": 19,
    },
        {
            "name": "Lemons",
            "value": 5,
    },
        {
            "name": "Limes",
            "value": 16,
    },
        {
            "name": "Oranges",
            "value": 26,
    },
        {
            "name": "Pears",
            "value": 30,
    }];

    //sort bars based on value
    data = data.sort(function (a, b) {
        return d3.ascending(a.value, b.value);
    })

    //set up svg using margin conventions - we'll need plenty of room on the left for labels
    var margin = {
        top: 15,
        right: 25,
        bottom: 15,
        left: 60
    };

    var width = 960 - margin.left - margin.right,
        height = 500 - margin.top - margin.bottom;

    var svg = d3.select("#graphic").append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    var x = d3.scale.linear()
        .range([0, width])
        .domain([0, d3.max(data, function (d) {
            return d.value;
        })]);

    var y = d3.scale.ordinal()
        .rangeRoundBands([height, 0], .3)
        .domain(data.map(function (d) {
            return d.name;
        }));

    //make y axis to show bar names
    var yAxis = d3.svg.axis()
        .scale(y)
        //no tick marks
        .tickSize(0)
        .orient("right");

    var gy = svg.append("g")
        .attr("class", "y axis")
        .call(yAxis)

    var bars = svg.selectAll(".bar")
        .data(data)
        .enter()
        .append("g")
        .attr("class", "bars")


    //append rects
    bars.append("rect")
        .attr("class", "bar")
        .attr("y", function (d) {
            return y(d.name);
        })
        .attr("height", y.rangeBand())
        .attr("x", 0)
        .attr("width", function (d) {
            return x(d.value);
        });

    //add a value label to the right of each bar
    bars.append("text")
        .attr("class", "label")
        //y position of the label is halfway down the bar
        .attr("y", function (d) {
            return y(d.name) + y.rangeBand() / 2 + 4;
        })
        //x position is 3 pixels to the right of the bar
        .attr("x", function (d) {
            return x(d.value) + 3;
        })
        .text(function (d) {
            return d.value;
        });

    var labels =         
        bars.append("text")
        .attr("class", "labels")
    .attr("y", function (d) {
            return y(d.name) + y.rangeBand() / 2 - 30;
        })
    .attr("x", 0)
        .text(function (d) {
            return d.name;
        });

enter image description here

1 个答案:

答案 0 :(得分:0)

我有一个类似的图表,对此我做了一些修改,可能会满足您的要求,所以我将以我自己的代码为基础。

我将讨论问题中最相关的部分,您可以看一下代码,并希望自己弄清楚它是如何工作的。

初始动画的工作方式与您发布的第三个链接完全相同:

.transition().duration(speed)
    .delay((_, i) => delay * i)

我们设置了一个延迟,以便每个小节一次出现一个。

我还进行了设置,以便您可以使用d3js更新模式更改数据。

    var bar = svg.selectAll(".bar")
        .data(data, d => d.name)

    bar.exit().remove();

    bar.enter().insert("g", ".y-axis").append("rect")
        .attr("class", "bar")
        .attr("fill", "#ccc")
        .attr("x", x(0))
        .attr("y", d => y(d.name))
        .attr("height", y.bandwidth())
        .merge(bar)
    .transition().duration(speed)
        .delay((_, i) => delay * i)
        .attr("y", d => y(d.name))
        .attr("width", d => x(d.value) - x(0));

由于您未指定要更新新数据的方式,因此目前仅一年。

这是所有代码:

var init = [
    {"year": "2017", "name": "Apples", "value": 20},
    {"year": "2017", "name": "Bananas","value": 12},
    {"year": "2017", "name": "Grapes", "value": 19},
    {"year": "2017", "name": "Lemons", "value": 5},
    {"year": "2017", "name": "Limes", "value": 16},
    {"year": "2017", "name": "Oranges", "value": 26},
    {"year": "2017", "name": "Pears","value": 30},
    {"year": "2018", "name": "Apples", "value": 10},
    {"year": "2018", "name": "Bananas","value": 42},
    {"year": "2018", "name": "Grapes", "value": 69},
    {"year": "2018", "name": "Lemons", "value": 15},
    {"year": "2018", "name": "Limes", "value": 26},
    {"year": "2018", "name": "Oranges", "value": 36},
    {"year": "2018", "name": "Pears","value": 20}
];

chart(init)

function chart(result) {

	var format = d3.format(",.0f")

	var years = [...new Set(result.map(d => d.year))]
	var fruit = [...new Set(result.map(d => d.name))]

	var options = d3.select("#year").selectAll("option")
		.data(years)
	.enter().append("option")
		.text(d => d)

	var svg = d3.select("#graphic"),
		margin = {top: 25, bottom: 10, left: 50, right: 45},
		width = +svg.attr("width") - margin.left - margin.right,
		height = +svg.attr("height") - margin.top - margin.bottom;

	var x = d3.scaleLinear()
		.range([margin.left, width - margin.right])
	
	var y = d3.scaleBand()
		.range([margin.top, height - margin.bottom])
		.padding(0.1)
		.paddingOuter(0.5)
		.paddingInner(0.5)

	var xAxis = svg.append("g")
		.attr("class", "x-axis")
		.attr("transform", `translate(0,${margin.top})`)

	var yAxis = svg.append("g")
		.attr("class", "y-axis")
		.attr("transform", `translate(${margin.left},0)`)

	update(d3.select("#year").property("value"), 750, 250)

	function update(input, speed, delay) {

		var data = result.filter(f => f.year == input)

		var sum = d3.sum(data, d => d.value)

		x.domain([0, d3.max(data, d => d.value)]).nice()

		svg.selectAll(".x-axis").transition().duration(speed)
			.call(d3.axisTop(x).tickSizeOuter(0));
			
		data.sort((a, b) => b.value - a.value)

		y.domain(data.map(d => d.name))

		svg.selectAll(".y-axis").transition().duration(speed)
			.call(d3.axisLeft(y));

		yAxis.selectAll("text").remove()
		yAxis.selectAll("line").remove()

		var bar = svg.selectAll(".bar")
			.data(data, d => d.name)

		bar.exit().remove();

		bar.enter().insert("g", ".y-axis").append("rect")
			.attr("class", "bar")
			.attr("fill", "#ccc")
			.attr("x", x(0))
			.attr("y", d => y(d.name))
			.attr("height", y.bandwidth())
			.merge(bar)
		.transition().duration(speed)
			.delay((_, i) => delay * i)
			.attr("y", d => y(d.name))
			.attr("width", d => x(d.value) - x(0));

		var value = svg.selectAll(".value")
			.data(data, d => d.name)

		value.exit().remove();

		value.enter().append("text")
			.attr("class", "value")
			.attr("opacity", 0)
			.attr("dy", 4)
			.attr("y", d => y(d.name) + y.bandwidth() / 2)
			.merge(value)
		.transition().duration(speed)
			.delay((_, i) => delay * i)
			.attr("opacity", 1)
			.attr("y", d => y(d.name) + y.bandwidth() / 2)
			.attr("x", d =>  x(d.value) + 5)
			.text(d => format((d.value / sum) * 100) + " %")

		var name = svg.selectAll(".name")
			.data(data, d => d.name)

		name.exit().remove();

		name.enter().append("text")
			.attr("class", "name")
			.attr("opacity", 0)
			.attr("dy", -5)
			.attr("y", d => y(d.name))
			.merge(name)
		.transition().duration(speed)
			.delay((_, i) => delay * i)
			.attr("opacity", 1)
			.attr("y", d => y(d.name))
			.attr("x", d =>  x(0) + 5)
			.text(d => d.name)
	}

	var select = d3.select("#year")
		.style("border-radius", "5px")
		.on("change", function() {
			update(this.value, 750, 0)
		})
}
body {
		margin: auto;
		width: 650px;
		font: 12px arial;
	}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg id="graphic" width="600" height="380"></svg><br>

Choose year: 
<select id="year"></select>