Как создать многосерийный линейный график с использованием данных, отфильтрованных из CSV-файла?

1

У меня есть файл data.csv сделанный таким образом:

CODE,YEAR,MODALITY,VALUE
AB,2000,first,15
AB,2000,second,
AB,2000,third,33
AB,2001,first,20
AB,2001,second,25
AB,2001,third,87
AB,2002,first,6
AB,2002,second,
AB,2002,third,16
AB,2003,first,50
AB,2003,second,50
AB,2003,third,10
AB,2004,first,20
AB,2004,second,55
AB,2004,third,8
AC,2000,first,
AC,2000,second,97
AC,2000,third,77
AC,2001,first,42
AC,2001,second,5
AC,2001,third,81
AC,2002,first,
AC,2002,second,63
AC,2002,third,14
AC,2003,first,5
AC,2003,second,7
AC,2003,third,0
AC,2004,first,5
AC,2004,second,7
AC,2004,third,0
AD,2000,first,11
AD,2000,second,2
AD,2000,third,36
AD,2001,first,95
AD,2001,second,78
AD,2001,third,88
AD,2002,first,89
AD,2002,second,32
AD,2002,third,79
AD,2003,first,5
AD,2003,second,32
AD,2003,third,9
AD,2004,first,7
AD,2004,second,32
AD,2004,third,91
AE,2000,first,15
AE,2000,second,78
AE,2000,third,1
AE,2001,first,5
AE,2001,second,2
AE,2001,third,64
AE,2002,first,44
AE,2002,second,51
AE,2002,third,
AE,2003,first,40
AE,2003,second,52
AE,2003,third,85
AE,2004,first,45
AE,2004,second,50
AE,2004,third,80

Я создал файл index.html состоящий из некоторых выбираемых пользователем элементов (в примере для простоты я выбрал 4 круга с кодами AB, AC, AD или AE) и 3 переключателя (первый, второй и третий).

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

Поэтому у меня есть codes массивов, которые содержат коды выбранных кругов и переменную modalitySelected которая содержит выбранную радиокнопку.

То, что я хотел бы сделать, это линейная диаграмма, представляющая данные на основе выбора, сделанного пользователем.

Пример: Изображение 174551

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

В этом примере есть некоторые недостающие данные, и пунктирная линия именно это. (Пока это не важно представлять).

Это мой код. Вначале управляется выбор окружностей и создается массив codes который содержит коды выбранных элементов. Затем создается линейная диаграмма.

index.html:

<body>
   <div id="circles">
      <svg>
         <circle id="AB" cx="10" cy="10" r="10" fill="purple" />
         <circle id="AC" cx="60" cy="60" r="5" fill="red" />
         <circle id="AD" cx="110" cy="110" r="15" fill="orange" />
         <circle id="AE" cx="90" cy="50" r="7" fill="yellow" />
      </svg>
   </div>
   <button type="button" id="finalSelection">Finish</button>

   <span style="display:block;margin-top: 10px;">Selected codes: <span class="values"></span></span><br>

   <div id="modality-selector-container">
      <form id="modality-selector">
         <input type="radio" name="modality-selector" id="rb-first" value="first" checked />
         <label for="rb-first">First</label>
         <input type="radio" name="modality-selector" id="rb-second" value="second" />
         <label for="rb-second">Second</label>
         <input type="radio" name="modality-selector" id="rb-third" value="third" />
         <label for="rb-third">Third</label>
      </form>
   </div>

   <div id="line-chart-container"></div>
   <script src="./script.js"></script>
</body>

script.js:

var codes = [];
modalitySelected = document.querySelector('input[name=modality-selector]:checked').value; 
var filtered_data = null;

// set the dimensions and margins of the graph
var margin = {top: 20, right: 20, bottom: 30, left: 50};
var width = 600 - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;

// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]); 

// parse the date/time
var parseTime = d3.timeParse("%Y"); 

var svg = null;
var valueline = null;

d3.selectAll('#circles svg circle').on('click', function() {
   var id = d3.select(this).attr('id');

   if(d3.select(this).classed('clicked')) { 
      d3.select(this).classed('clicked', false).style('stroke', null);
      codes.splice(codes.indexOf(id), 1); 
   } 
   else { 
      if(codes.length) { 
         if(d3.event.ctrlKey) { 
            d3.select(this).classed('clicked', true).style('stroke', 'blue');
            codes.push(id); 
         }
         else { 
            d3.selectAll(".clicked").classed('clicked', false).style('stroke', null);
            codes = [];
            d3.select(this).classed('clicked', true).style('stroke', 'blue');
            codes.push(id); 
         }
      } 
      else {
         d3.select(this).classed('clicked', true).style('stroke', 'blue');
         codes.push(id);
      }
   }

   $('span.values').html(codes.join(', '));
});

$('button#finalSelection').click(function() {
   $('span.values').html(codes.join(', '));
   console.log("compare: " + codes);
   compareCodes();
});

function compareCodes() {
   // define the line
   valueline = d3.line()
      .x(function(d) {
         return x(d.YEAR); 
      })
      .y(function(d) {
         return y(d.VALUE);  
      });  

   // append the svg obgect to the body of the page
   // appends a 'group' element to 'svg'
   // moves the 'group' element to the top left margin
   svg = d3.select("#line-chart-container").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 + ")");

   getData(); 
}

function getData() {
   d3.queue()
      .defer(d3.csv, './data.csv') 
      .await(makeLineChart); 
}

function makeLineChart(error, data) {
   if(error) {
      console.log(error);
   }

   // radio button change
   d3.selectAll("input[name='modality-selector']")
      .on("change", function(){
         console.log(this.value);
         modalitySelected = this.value;

         // filter data
         filtered_data = data.filter(function(d) {
            return d.MODALITY == modalitySelected && d.CODE == codes[0];
         });

         // format the data
         filtered_data.forEach(function(d) {
            d.YEAR = parseTime(d.YEAR);
            d.VALUE = +d.VALUE;
         });

         updateGraph(filtered_data);
      }); // end radio on change

   // generate initial line chart - filter data
   filtered_data = data.filter(function(d) {
      return d.MODALITY == modalitySelected && d.CODE == codes[0];
   });
   updateGraph(filtered_data);
}

function updateGraph(data) {
   var numTickXaxis = data.length;

   // scale the range of the data
   x.domain(d3.extent(filtered_data, function(d) { 
      return d.YEAR; 
   }));
   y.domain([0, d3.max(filtered_data, function(d) { 
      return d.VALUE; 
   })]);

   // add the valueline path
   svg.append("path")
      .data([filtered_data])
      .attr("class", "line")
      .attr("d", valueline);

   // add the X Axis
   svg.append("g")
      .attr("class", "axis")
      .attr("transform", "translate(0," + height + ")")
      .call(d3.axisBottom(x)
         .tickFormat(d3.timeFormat("%Y"))
         .ticks(numTickXaxis))
      .selectAll("text")   
         .style("text-anchor", "end")
         .attr("dx", "-.8em")
         .attr("dy", ".15em")
         .attr("transform", "rotate(-65)");

   // add the Y Axis
   svg.append("g")
      .attr("class", "axis")
      .call(d3.axisLeft(y)); 

   var state = svg.selectAll(".line");
   state.exit().remove();
}

Полный код ЗДЕСЬ.

В этом коде есть две проблемы:

  1. при изменении выбора переключателя, график перезаписывается (несмотря на наличие state.exit().remove();)
  2. как вы можете видеть, код работает только для первого элемента в codes. Я не знаю, как обрабатывать случай, когда codes состоят из нескольких элементов и показывают больше строк.

Я могу рассмотреть идею изменения структуры data.csv сохраняющей один и тот же контент. Например, я мог бы редактировать файл следующим образом:

CODE,YEAR,FIRST,SECOND,THIRD
AB,2000,15,50,33
AB,2001,20,25,87
AB,2002,6,,16
AB,2003,50,50,10
AB,2004,20,55,8
AC,2000,,97,77
AC,2001,42,5,81
AC,2002,,63,14
AC,2003,5,7,0
AC,2004,5,7,0
AD,2000,11,2,36
AD,2001,95,78,88
AD,2002,89,32,79
AD,2003,5,32,9
AD,2004,7,32,91
AE,2000,15,78,1
AE,2001,5,2,64
AE,2002,44,51,
AE,2003,40,52,85
AE,2004,45,50,80

Кто-нибудь знает, как мне помочь? Спасибо!


РЕШЕНИЕ

Следуя этому примеру, я смог решить проблему.

ЗДЕСЬ код.

Теперь у меня есть небольшая (я надеюсь) графическая проблема. На этой картине мы можем четко видеть, в чем проблема.

Изображение 174551

Линии начинаются перед осями. Зачем?

  • 0
    У меня есть новое решение, но CSV выглядит это , это нормально?
  • 0
    @RobertAndersson Нет, так что это не хорошо. На самом деле, возможных кодов действительно много (> 500), и файл с такой структурой кажется мне сложным в управлении.
Показать ещё 2 комментария
Теги:
d3.js
linechart
data-visualization

1 ответ

0
Лучший ответ

Проблема заключалась в том, что "transform", "translate(" в вашем createAxis() переместило вашу ось в сторону.

Поэтому я добавил

.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");

Для вашей исходной переменной svg чтобы она совпадала с осью и удалила

  .attr("transform", "translate(" + 0 + "," + 0 + ")");

Из createAxis()

Здесь ссылка: Plunker

Ещё вопросы

Сообщество Overcoder
Наверх
Меню