【问题标题】:dc.js Dynamic Waterfall Chartdc.js 动态瀑布图
【发布时间】:2020-04-22 14:21:26
【问题描述】:

Vistas 在github 上有一个很好的示例,其中包含有关如何在 dc.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>

【问题讨论】:

  • 这个例子看起来不错,但它显然只适用于静态数据。这么多年过去了,我仍然不明白为什么有人会使用 dc.js 来制作没有过滤的静态图表。 (或者也许 repo 是在寻求帮助?)看起来应该有更好的解决方案使用假组,希望在下周找到时间。
  • 再次感谢您的帮助,非常感谢。事实上,我真的很喜欢 DC.js 与 crossfilter 的结合。但是没有过滤的独立图表可能 d3.js 是更好的解决方案。希望您能再次帮助我进行过滤。
  • 嗨,戈登,您是否已经抽出时间来看看这个例子?很抱歉匆忙,但我需要知道这个例子是否可以工作。再次感谢您的帮助。
  • 我看了看,我看到了一个可能的解决方案,因为这是accumulation 的一种形式,在某些方面类似于Pareto chart,但我还没有编写任何代码。我还有很多其他的责任,这对我来说只是一个爱好,但我会尽快完成。
  • 嗨@KeesdeJager,想知道你是否有机会看看我的回答。您的赏金即将到期。 :-)

标签: dc.js crossfilter


【解决方案1】:

我们可以使用fake group 以基线和最终值所需的方式累积值:

  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}}]);
      }
    };
  }

此函数获取最终“总和”栏的键和一个访问器函数,这里需要,因为 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';});

为了测试它,我添加了另一个“类别”字段和一个饼图。请注意,瀑布图可能会进入一些带有负值和零值的奇怪状态(例如单击“C”),但它们看起来是正确的。

Fork of your fiddle.

请注意,由于最后一项(此处为 x5)是纯合成的,并且与任何基础数据无关,因此通过单击该项进行过滤会导致其他图表空白。我不确定如何禁用对某个特定项目的点击。

【讨论】:

  • 哇,我对 dc.js 的可能性印象深刻。尤其是为第一个和最后一个柱赋予不同值的基线解决方案非常聪明。我不明白的是,圆形图过滤了瀑布,而不是相反。我还为项目添加了一个圆形图,它确实过滤了两种方式。你知道这可能是什么原因吗?再次感谢您的全面而强大的回应。带有两个圆圈的瀑布示例:jsfiddle.net/Pension007/5mynsojh
  • 这只是数据集的一个神器——为了增加另一个维度,我将每一行拆分为A、B、C。饼图统计了每个类别的数量。由于每个 x 值对每个类别的贡献相同,因此对饼图没有影响。如果类别为shuffled a little bit,则单击瀑布图会影响类别饼图。
  • 另外,我不知道为什么,但在此示例中,瀑布图设置为一次仅选择一项:.addFilterHandler(function(filters, filter) {return [filter];})
猜你喜欢
  • 2020-01-14
  • 2012-06-27
  • 2021-12-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多