【问题标题】:How add legend to a bar-chart based on data in an object dynamically (d3)如何根据对象中的数据动态地将图例添加到条形图中(d3)
【发布时间】:2019-04-03 20:58:28
【问题描述】:

我是 D3 领域的新手,根据“Web 的交互式数据可视化”一书,我设法创建了一个条形图,该条形图主要基于以下 link. 中的代码

问题是我无法根据对象动态地将图例添加到我的条形图中。

我曾尝试咨询与“向条形图添加图例”相关的 youtube 视频和其他 stackoverflow 问题,但在我看来,我找不到有关如何从对象数组中检索键的问题并使用数据作为图例添加到条形图中。 现在我所有的条也有相同的颜色,见下面的第二个代码。

请参阅下面的代码,了解嵌入在数组中的对象的格式。 名称“key”和“value”是固定的,而对象的数量及其对应的名称和值在用户单击事件后(这决定了对象中将包含哪些变量)有所不同。

以下example 能够创建图例,但是在这种情况下,对象的格式与我的情况有所不同,而且我目前对 D3 的了解有限,所以我不知道我必须通过哪些方式修改代码。

2: {key: "bedrijfsvestigingen_Sbi2008_BedrijfsvestigingenTotaal", value: 490}
3: {key: "bedrijfsvestigingen_Sbi2008_BedrijfsvestigingenNaarActiviteit_M_nZakelijkeDienstverlening", value: 165}
4: {key: "bedrijfsvestigingen_Sbi2008_BedrijfsvestigingenNaarActiviteit_R_uCultuur_Recreatie_OverigeDiensten", value: 120}
5: {key: "bedrijfsvestigingen_Sbi2008_BedrijfsvestigingenNaarActiviteit_K_lFinancieleDiensten_OnroerendGoed", value: 15}
6: {key: "bedrijfsvestigingen_Sbi2008_BedrijfsvestigingenNaarActiviteit_ALandbouw_BosbouwEnVisserij", value: 0}
7: {key: "bedrijfsvestigingen_Sbi2008_BedrijfsvestigingenNaarActiviteit_H_p_JVervoer_InformatieEnCommunicatie", value: 85}];

基于书中的代码并考虑其他变量,我目前有以下用于可视化条形图的代码,其中值(见上面的对象)显示在条形图中,条形图的颜色是都是蓝色的。但是,我当前的代码中还没有包含图例。因此,我想知道如何能够根据对象中的“键”(在我的情况下)动态创建一个图例,并表示绑定到条形的相应颜色。我想达到我画草图的最低图像。

                var svg = d3.select("#barchart")
                    .select("svg")
                    .remove("svg");

                //Width and height
                var w = 600;
                var h = 250;
                var padding=20;



                var xScale = d3.scaleBand()
                    .domain(d3.range(dataset.length))
                    .rangeRound([w - padding,padding ])
                    .paddingInner(0.05);

                var yScale = d3.scaleLinear()
                    .domain([0, d3.max(dataset, function (d) {
                        return d.value;
                    })])
                    .range([padding,h - padding]);
                    console.log("yscale",yScale);

                //Define key function, to be used when binding data
                var key = function (d) {
                    console.log("key", d);
                    return d.key;
                };


                // d3.select("svg").remove();
                //Create SVG element
                var svg = d3.select("#barchart")
                    .append("svg")
                    .attr("width", w)
                    .attr("height", h);
                console.log("svg", svg);

                //Create bars
                svg.selectAll("rect")
                    .data(dataset, key)     //Bind data with custom key function
                    .enter()
                    .append("rect")
                    .attr("x", function (d, i) {
                        return xScale(i);
                    })
                    .attr("y", function (d) {
                        return h - yScale(d.value);
                    })
                    .attr("width", xScale.bandwidth())
                    .attr("height", function (d) {
                        return yScale(d.value);
                    })
                    // .attr("data-legend", function (d) { return d.key })
                    .attr("fill", function (d) {
                        return "rgb(0, 0, " + (d.value * 10) + ")";
                    });
                //Create labels
                svg.selectAll("text")
                    .data(dataset, key)     //Bind data with custom key function
                    .enter()
                    .append("text")
                    .text(function (d) {
                        return d.value;
                    })
                    .attr("text-anchor", "middle")
                    .attr("x", function (d, i) {
                        return xScale(i) + xScale.bandwidth() / 2;
                    })
                    .attr("y", function (d) {
                        return h - yScale(d.value) + 14;
                    })
                    .attr("font-family", "sans-serif")
                    .attr("font-size", "11px")
                    .attr("fill", "white");

【问题讨论】:

  • 你能举例说明你想要达到的结果吗?
  • @torresomar,我在第一篇文章中包含了我想要实现的粗略想法。

标签: d3.js bar-chart


【解决方案1】:

如果我理解正确,这就是您应该需要的。 Plunker with working code.

首先,我鼓励使用边距对象,这样在处理图表时可以提供更好的灵活性

var margin = {
  top: 20,
  right: 20,
  bottom: 20,
  left: 20
};

我们希望使用您提供的数据和示例中的odinal scale 显示数据。

{key: "bedrijfsvestigingen_Sbi2008_BedrijfsvestigingenTotaal", value: 490}
{key: "bedrijfsvestigingen_Sbi2008_BedrijfsvestigingenNaarActiviteit_M_nZakelijkeDienstverlening", value: 165}
{key: "bedrijfsvestigingen_Sbi2008_BedrijfsvestigingenNaarActiviteit_R_uCultuur_Recreatie_OverigeDiensten", value: 120}
{key: "bedrijfsvestigingen_Sbi2008_BedrijfsvestigingenNaarActiviteit_K_lFinancieleDiensten_OnroerendGoed", value: 15}
{key: "bedrijfsvestigingen_Sbi2008_BedrijfsvestigingenNaarActiviteit_ALandbouw_BosbouwEnVisserij", value: 0}
{key: "bedrijfsvestigingen_Sbi2008_BedrijfsvestigingenNaarActiviteit_H_p_JVervoer_InformatieEnCommunicatie", value: 85}];

考虑到第一个元素可能是各种数据集的总和,我认为它不应该包含在图表中,因为它是我们要显示的元素的聚合。 (如果您需要将其显示为元素,您应该能够在查看答案后快速完成)

您的数据集中的元素结构如下:

{
  key: "bedrijfsvestigingen_Sbi2008_BedrijfsvestigingenNaarActiviteit_H_p_JVervoer_InformatieEnCommunicatie", 
  value: 85
}

xScale 的域应该是我们数据集中的所有键值,因为键是一个巨大的字符串,所以我在每个元素中创建了一个名为 label 的自定义属性

{
  key:
    "bedrijfsvestigingen_Sbi2008_BedrijfsvestigingenNaarActiviteit_M_nZakelijkeDienstverlening",
  label: "Business Services",
  value: 165
}

让我们用正确的域和范围创建我们的规模:

var xScale = d3
  .scaleBand()
  .domain(dataset.map(d => d.label)) // All our label properties
  .rangeRound([0, w - margin.left - margin.right]) // This scale will map our values from [0, width - margin.left - margin.right]
  .paddingInner(0.05);

yScale 几乎是正确的,我们只需要稍微改变它来使用我们的边距对象并使用正确的范围 范围必须从 0 开始,如果我们使用 padding 作为起点,我们的值将有一个偏移量,因为我们的值将从 [padding, h - padding] 映射。如果我们想显示零,则该值将映射到填充值,如果这是您希望显示信息的方式,请保持这种方式。在这种情况下,我们将修改比例。

var yScale = d3
  .scaleLinear()
  .domain([
    0,
    d3.max(dataset, function(d) {
      return d.value;
    })
  ])
  .range([0, h - margin.top - margin.bottom]);

接下来我们将创建一个函数来从我们的元素中获取所需的值

var xKey = function(d) {
  return d.label;
};

为我们的 svg 添加一些视觉提示,以帮助可视化元素的布局方式:

var svg = d3
  .select("#barchart")
  .append("svg")
  .style("background", "rgb(243, 243, 243)")
  .style("border", "1px dashed #b4b4b4")
  .attr("width", w)
  .attr("height", h);

我们想使用边距,所以让我们使用组标签来实现这一点,我们可以单独设置所需的每个组/元素的边距,但我发现这种方式更简单、更清晰

var g = svg
  .append("g")
  .attr("transform", `translate(${margin.left}, ${margin.top})`);

我们需要考虑到边距的图表的宽度和高度,让我们快速定义它们:

const customWidth = w - margin.left - margin.right;
const customHeight = h - margin.top - margin.bottom;

让我们添加一个矩形来显示我们的矩形将显示在哪里:

g.append("rect")
  .attr("fill", "#e3e3e3")
  .attr("width", customWidth)
  .attr("height", customHeight);

让我们来处理 rect 创建,在您的代码中,您有一个自定义填充函数,它修改了 RGB 颜色值中的 b 值。在这种情况下,由于我们处理的是分类数据,我们将使用一组颜色作为矩形。

g.append("g")
  .attr("class", "rect__container")
  .selectAll("rect")
  .data(dataset, xKey) //Bind data with custom key function
  .enter()
  .append("rect")
  .attr("x", function(d, i) {
    return xScale(xKey(d)); // use our key function
  })
  .attr("y", function(d) {
    return customHeight - yScale(d.value); // use our custom size values
  })
  .attr("width", xScale.bandwidth())
  .attr("height", function(d) {
    return yScale(d.value);
  })
  .attr("fill", function(d, i) {
    return d3.schemeCategory10[i]; // use an array of colors and use the index to decide which color to use
  });

我们有两个选项来显示图表的标签:

我们可以创建 x 轴或所需的图例。我们将两者都做,因为它不会影响图表的结果,并且可以删除其中任何一个。

var margin = {
  top: 20,
  right: 300, // modifiy our margin to have space to display the legends
  bottom: 50,
  left: 20
};

var legendElement = g
  .append("g")
  .attr("class", "legend__container")
  .attr("transform", `translate(${customWidth}, ${margin.top})`) // set our group position to the end of the chart
  .selectAll("g.legend__element")
  .data(xScale.domain()) // use the scale domain as data
  .enter()
  .append("g")
  .attr("transform", function(d, i) {
    return `translate(${10}, ${i * 30})`; // provide an offset for each element found in the domain
  });

legendElement
  .append("text")
  .attr("x", 30)
  .attr("font-size", "14px")
  .text(d => d);

legendElement
  .append("rect")
  .attr("x", 0)
  .attr("y", -15)
  .attr("width", 20)
  .attr("height", 20)
  .attr("fill", function(d, i) {
    return d3.schemeCategory10[i]; // use the same category color that we previously used in rects
  });

现在让我们使用轴方法:

// create axis
var x_axis = d3.axisBottom().scale(xScale);
//Append group and insert axis
g.append("g")
  .attr("transform", `translate(${0}, ${customHeight})`)
  .call(x_axis);
g.append("g")
  .attr("transform", `translate(${customWidth / 2}, ${customHeight + 40})`)
  .append("text")
  .text("Activities")
  .attr("font-family", "sans-serif")
  .attr("font-size", "14px")
  .attr("font-weight", "bold")
  .style("text-transform", "uppercase")
  .attr("text-anchor", "middle");

最后为我们数据中的值创建标签:

//Create labels
g.append("g")
  .attr("class", "text__container")
  .selectAll("text")
  .data(dataset, xKey) //Bind data with custom key function
  .enter()
  .append("text")
  .text(function(d) {
    return d.value;
  })
  .attr("text-anchor", "middle")
  .attr("x", function(d, i) {
    return xScale(xKey(d)) + xScale.bandwidth() / 2;
  })
  .attr("y", function(d) {
    return customHeight - yScale(d.value) + 14;
  })
  .attr("font-family", "sans-serif")
  .attr("font-size", "11px")
  .attr("fill", "white");

【讨论】:

  • 感谢您的详细解释。老实说,我并不完全理解代码,但它可以工作:)。我想我需要更多经验才能了解 d3 的工作原理。尽管如此,还是非常感谢你。我还有一个问题,我发现条形图有点偏大。所以我想知道我应该如何改变宽度、高度、边距、内边距,这样我就可以得到我目前拥有的一半。
  • 如果你想让图表整体更小,你可以在 plunker 代码中更改宽度和高度值,如果你只想让条形宽度更小,你可以使用@987654339 @,例如在 plunker 代码中尝试.paddingInner(0.5),看看这是否是您的目标
猜你喜欢
  • 2018-05-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-27
  • 2020-03-09
  • 2015-02-14
相关资源
最近更新 更多