d c. js Dynami c Диаграмма водопада - PullRequest
2 голосов
/ 22 апреля 2020

Vistas имеет хороший пример на github с настройкой того, как сделать водопад в d c. js. Он использует второй набор данных для создания нижней части гистограммы с накоплением. Однако, если вы отфильтруете в первом наборе данных, он будет работать неправильно, так как фиксированное нижнее значение диаграммы с накоплением.

Поэтому мой вопрос заключается в том, можно ли рассчитать значение d.value на основе этой формулы, поэтому второй набор данных (dummy_data) не требуется:

Dummy value of current column = previous dummy value + previous real data value

, в результате чего значение первого и последний столбец установлен в 0

JSFiddle

код

<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='utf-8'>
  <meta content='width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0' name='viewport'>

  <title>Waterfall Chart with DC.js</title>

  <script src='js/d3.js' type='text/javascript'></script>
  <script src='js/crossfilter.js' type='text/javascript'></script>
  <script src='js/reductio.js' type='text/javascript'></script>
  <script src='js/dc.js' type='text/javascript'></script>
  <link href='css/dc.css' rel='stylesheet' type='text/css'>
</head>
<body>
  <div class='pie-graph span6' id='dc-waterfall-chart'></div>
<script>
  var waterfallChart = dc.barChart("#dc-waterfall-chart");
  var original_data = [];
  var dummy_data = [];

  //creating example data - could easily be any data reading process from sources like CSV or JSON
  original_data.push({item: "x0", value: 10});
  original_data.push({item: "x1", value: 2});
  original_data.push({item: "x2", value: -1});
  original_data.push({item: "x3", value: -3});
  original_data.push({item: "x4", value: 8});

  //creating the dummy data, the invisible columns supporting the waterfall chart. 
  //This is going to be the first stack whereas the real data (original_data) is the 
  //second stack
  dummy_data.push({item: "x0", value: 0});
  dummy_data.push({item: "x1", value: 10});
  dummy_data.push({item: "x2", value: 12});
  dummy_data.push({item: "x3", value: 11});
  dummy_data.push({item: "x4", value: 0});

  //creating crossfilter based off of the real data. Again, you can have your own crossfilter creation process here.
  var ndx = crossfilter(original_data);
  var itemDimension = ndx.dimension(function (d) { return d.item; });
  var reducerValue = reductio().count(true).sum(function(d) { return d.value; }).avg(true); 
  var itemGroup = itemDimension.group();
  var grp = reducerValue(itemGroup);

  // we should also have a similar cross filter on the dummy data
  var ndx_dummy = crossfilter(dummy_data);
  var itemDimension_dummy = ndx_dummy.dimension(function (d) { return d.item; });
  var reducerValue_dummy = reductio().count(true).sum(function(d) { return d.value; }).avg(true); 
  var itemGroup_dummy = itemDimension_dummy.group();
  var dummy_grp = reducerValue_dummy(itemGroup_dummy);

  waterfallChart.width(600)
  .height(400)
  .margins({top: 5, right: 40, bottom: 80, left: 40})
  .dimension(itemDimension)
  .group(dummy_grp)
  .valueAccessor(function (d) { // specific to reductio
    return d.value.sum; 
    })
  .title(function(d){ 
    return (d.key + "  (" + d.value.sum+ ")" );
  })
  .transitionDuration(1000)
  .centerBar(false) 
  .gap(7)                    
  .x(d3.scaleBand())
  .xUnits(dc.units.ordinal)
    .controlsUseVisibility(true)
    .addFilterHandler(function(filters, filter) {return [filter];})
  .elasticY(true)
  .xAxis().tickFormat(function(v) {return v;});

  waterfallChart.stack(grp,"x")

  waterfallChart.on("pretransition",function (chart) {
    //coloring the bars
    chart.selectAll("rect.bar").style("fill", function(d){return "white";});
    chart.selectAll("rect.bar").style("stroke", "#ccc");//change the color to white if you want a clean waterfall without dashed boundaries
    chart.selectAll("rect.bar").style("stroke-dasharray", "1,0,2,0,1");

    // stack._1 is your real data, whereas stack._0 is the dummy data. You want to treat the styling of these stacks differently
    chart.selectAll("svg g g.chart-body g.stack._1 rect.bar").style("fill", function(d){console.log(d.data.value.sum);if (d.data.value.sum >0) return '#ff7c19'; else return '#7c7c7c';});
    chart.selectAll("svg g g.chart-body g.stack._1 rect.bar").style("stroke", "white");
    chart.selectAll("svg g g.chart-body g.stack._1 rect.bar").style("stroke-dasharray", "1");
    // chose the color of deselected bars, but only for the real data.
    chart.selectAll("svg g g.chart-body g.stack._1 rect.deselected").style("fill", function (d) {return '#ccc';});
    chart.selectAll('g.x text').style('fill', '#8e8e8e');
    chart.selectAll('g.y text').style('fill', '#777777');
    chart.selectAll('g.x text').style('font-size', '10.5px');
    chart.selectAll('g.y.axis g.tick line').style("stroke", "#f46542");
    chart.selectAll('.domain').style("stroke","#c6c6c6");
    chart.selectAll('rect.bar').on("contextmenu",function(d){d3.event.preventDefault();});
  });
  dc.renderAll();
</script>
</body>
</html>

1 Ответ

2 голосов
/ 29 апреля 2020

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

  function waterfall_group(group, endkey, acc) {
    acc = acc || (x => x);
    return {
      all: () => {
        let cumulate = 0;
        let all = group.all().map(({key,value}) => {
          value = acc(value)
            const kv = {
            key,
            value: {
                baseline: cumulate,
              data: value
            }
            };
          cumulate += value;
          return kv;
        });
        return all.concat([{key: endkey, value: {baseline: 0, data: cumulate}}]);
      }
    };
  }

Эта функция берет ключ для итоговой "итоговой суммы" "bar и функция доступа, необходимая здесь, потому что reductio оборачивает значения в дополнительный объект.

Возвращает группу со значениями {baseline,data}, где baseline - фиктивное значение, необходимое для невидимого стека, и data - это значение для бара.

Создайте поддельную группу, такую ​​как

var waterfall_group = waterfall_group(grp, 'x5', x => x.sum);

и передайте ее .group() и .stack() с аксессорами, чтобы получить вспомогательные значения:

waterfallChart
  .group(waterfall_group, 'baseline', kv => kv.value.baseline)
  .stack(waterfall_group, 'data', kv => kv.value.data)

Я также изменил код раскраски, чтобы получить новый формат данных:

chart.selectAll("svg g g.chart-body g.stack._1 rect.bar")
  .style("fill", function(d){if (d.data.value.data >0) return '#ff7c19'; else return '#7c7c7c';});

Чтобы проверить это, я добавил еще одно поле "категория" и диаграмму ap ie. Обратите внимание, что диаграмма водопада может попасть в некоторые странные состояния с отрицательными и нулевыми значениями (например, нажмите «C»), но они выглядят правильно.

waterfall shot

Вилка вашей скрипки .

Обратите внимание, что поскольку последний элемент (x5 здесь) является чисто синтетическим c и не связан с какими-либо базовыми данными, фильтрация при нажатии на этот элемент вызовет другие графики, чтобы убрать. Я не уверен, как отключить клики на одном конкретном элементе.

...