Как динамически добавить легенду к гистограмме на основе данных в объекте (d3) - PullRequest
0 голосов
/ 02 апреля 2019

Я действительно новичок в области D3 и, основываясь на книге «Интерактивная визуализация данных для Интернета», мне удалось создать гистограмму, которая в основном основана на коде из следующей ссылки .

Проблема в том, что мне не удается добавить легенду на мою гистограмму на основе объекта динамически.

Я пытался проконсультироваться с видео на youtube и другими вопросами о стековом потоке, связанном с «добавлением легенды к гистограмме», однако, по моему мнению, я не смог найти вопрос о том, как можно извлечь ключи из массива объектов. и использовать данные, чтобы добавить в качестве легенды на гистограмму. На данный момент все мои столбцы также имеют одинаковый цвет, см. Второй код ниже.

См. Код ниже для форматирования моего объекта, который встроен в массив. Имя «ключ» и «значение» являются фиксированными, в то время как количество объектов и их соответствующие имя и значение различаются после события щелчка пользователя (которое определяет, какие переменные будут включены в объект).

Следующий пример может создать легенду, однако в этом случае форматирование объекта несколько отличается от моего, и мои текущие знания о 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}];

Основываясь на коде из книги и учете других переменных, в настоящее время у меня есть следующий код для визуализации гистограммы, в котором значения (см. Объект выше) отображаются на гистограммах, а цвет гистограммы: все голубовато. Однако в моем текущем коде пока нет легенды. Поэтому мне интересно, как можно динамически создать легенду на основе «ключей» (в моем случае) в объекте и представить соответствующий цвет, привязанный к полосам. Я хотел бы получить самое низкое изображение, которое я нарисовал эскиз. This is a rough idea of what I would like to achieve with d3

                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");

1 Ответ

2 голосов
/ 03 апреля 2019

Если я правильно понял, это то, что вам нужно. Плункер с рабочим кодом .

Прежде всего я бы рекомендовал использовать маржинальный объект, который обеспечит большую гибкость при работе с диаграммами

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

Мы хотим отобразитьданные с единичной шкалой из приведенных вами данных и примера.

{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, 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);

Позволяет разобраться с созданием прямоугольника, в вашем коде у вас была пользовательская функция заполнения, которая изменила значение b в пределах значений цвета RGB.В этом случае, поскольку мы имеем дело с категориальными данными, мы будем использовать массив цветов для ректов.

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");
...