【问题标题】:Multi-series bar chart in DC-jsDC-js 中的多系列条形图
【发布时间】:2014-03-24 13:38:38
【问题描述】:

我正在使用DC.js(D3 之上的 lib),并且有一个很好的单系列条形图示例:

 var xf = crossfilter(data);
 var dim = xf.dimension(function (d) { return d["EmployeeName"]; });
 var group = dim.group().reduceSum(function (d) { return d["AverageSale"]; });

 var chart = dc.barChart(elm);
 chart.barPadding(0.1)
 chart.outerPadding(0.05)
 chart.brushOn(false)
 chart.x(d3.scale.ordinal());
 chart.xUnits(dc.units.ordinal);
 chart.elasticY(true);

 chart.dimension(dim).group(group);
 chart.render();

但我想知道是否可以使用此库创建多维条形图。例如:按商店名称分组,然后按员工分组,然后 y 轴显示平均销售额(已由我的后端计算)。

数据如下:

 [{
    "EmployeeName": "Heather",
    "StoreName" : "Plaza",
    "AverageSaleValue": 200
 }{
    "EmployeeName": "Mellisa",
    "StoreName" : "Plaza",
    "AverageSaleValue": 240
 }, {
    "EmployeeName": "Sarah",
    "StoreName" : "Oak Park",
    "AverageSaleValue": 300
 } ... ]

【问题讨论】:

    标签: javascript d3.js crossfilter dc.js


    【解决方案1】:

    在 dc.js 中立即想到的最接近您要求的内容是堆积条形图 (example)。但我认为您可能更喜欢grouped bar chart。我不确定 dc.js 当前是否支持这种图表类型。也许其他人知道。

    【讨论】:

    • 分组条形图是我一直在寻找的,但不敢相信 dc 没有这个!
    • 可以通过d3的transform函数实现
    • @Goutam 请解释一下
    【解决方案2】:

    如果您要绘制静态组数,则可以使用复合图表实现所需的效果。

    在下面的示例中,我对条形图之间的间隔进行了硬编码 - 我可以这样做,因为我知道要显示 12 个月。

                var actuals = dc.barChart(compositeChart)
                        .gap(65)
                        .group(group)
                        .valueAccessor(function (d) {
                            return d.value.Actual;
                        });
    
                var budgets = dc.barChart(compositeChart)
                        .gap(65)
                        .group(group)
                        .valueAccessor(function (d) {
                            return d.value.Budget;
                        });
    

    我将这些条形图传递给复合图表的 compose 方法:

                    compositeChart
                        .width(1000)
                        .height(300)
                        .dimension(monthDimension)
                        .group(group)
                        .elasticY(true)
                        .x(d3.time.scale().domain(timeExtent))
                        .xUnits(d3.time.months)
                        .round(d3.time.month.round)
                        .renderHorizontalGridLines(true)
                        .compose([budgets, actuals])
                        .brushOn(true);
    

    最后,我添加了一个渲染器,将其中一个图表向右移动几个像素:

                  compositeChart
                        .renderlet(function (chart) {
                            chart.selectAll("g._1").attr("transform", "translate(" + 20 + ", 0)");
                            chart.selectAll("g._0").attr("transform", "translate(" + 1 + ", 0)");
                        });
    

    我知道这不是最干净的方法,但它可以在紧要关头工作。

    我希望这会有所帮助。

    【讨论】:

    • 谢谢,问题是我的图表是完全动态的......我确实有“组”的数量,所以也许我可以根据那个来计算宽度......嗯......
    【解决方案3】:

    我可以通过以下链接中的 renderlet 技术来做到这一点 renderlet function for coloring

    我的代码如下

    .renderlet(function (chart) {
        chart.selectAll('rect.bar').each(function(d){                           
        d3.select(this).attr("fill",                            
            (function(d){
                var colorcode ="grey"
                if(d.x[1] === "Zone1")   
                    colorcode ="#ff7373";
                else if(d.x[1] === "Zone2")
                    colorcode ="#b0e0e6";
                else if(d.x[1] === "Zone3")
                    colorcode ="#c0c0c0";
                else if(d.x[1] === "Zone4")
                    colorcode ="#003366";
                else if(d.x[1] === "Zone5")
                    colorcode ="#ffa500";
                else if(d.x[1] === "Zone6")
                    colorcode ="#468499";
                else if(d.x[1] === "Zone7")
                    colorcode ="#660066";   
                return colorcode;
            }))
        }); 
    
    });  
    

    注意:我为系列使用了一个具有 2 个值的维度。

    【讨论】:

      【解决方案4】:

      我知道我迟到了,但这可能对其他人有所帮助。

      要在 dc.js 中创建分组条形图而不覆盖原始 dc 代码,您可以利用“pretransition”事件并拆分条形以创建组。

      I've created an example (jsfiddle)

      奇迹发生在这里:

      let scaleSubChartBarWidth = chart => {
        let subs = chart.selectAll(".sub");
      
        if (typeof barPadding === 'undefined') { // first draw only
          // to percentage
          barPadding = BAR_PADDING / subs.size() * 100;
          // each bar gets half the padding
          barPadding = barPadding / 2;
        }
      
        let startAt, endAt,
            subScale = d3.scale.linear().domain([0, subs.size()]).range([0, 100]);
      
        subs.each(function (d, i) {
      
          startAt = subScale(i + 1) - subScale(1);
          endAt = subScale(i + 1);
      
          startAt += barPadding;
          endAt -= barPadding;
      
          // polygon(
          //  top-left-vertical top-left-horizontal,
          //  top-right-vertical top-right-horizontal,
          //  bottom-right-vertical bottom-right-horizontal,
          //  bottom-left-vertical bottom-left-horizontal,
          // )
      
          d3.select(this)
            .selectAll('rect')
            .attr("clip-path", `polygon(${startAt}% 0, ${endAt}% 0, ${endAt}% 100%, ${startAt}% 100%)`);
      
        });
      }; 
      

      ...

      .on("pretransition", chart => {
          scaleSubChartBarWidth(chart);
      })
      

      完整代码:

      标记

      <div id="chart-container"></div>
      

      Js

      //'use strict';
      let compositeChart = dc.compositeChart("#chart-container");
      
      const BAR_PADDING = .1; // percentage the padding will take from the bar
      const RANGE_BAND_PADDING = .5; // padding between 'groups'
      const OUTER_RANGE_BAND_PADDING = 0.5; // padding from each side of the chart
      
      let sizing = chart => {
        chart
          .width(window.innerWidth)
          .height(window.innerHeight)
          .redraw();
      };
      
      let resizing = chart => window.onresize = () => sizing(chart);
      
      let barPadding;
      let scaleSubChartBarWidth = chart => {
        let subs = chart.selectAll(".sub");
      
        if (typeof barPadding === 'undefined') { // first draw only
          // to percentage
          barPadding = BAR_PADDING / subs.size() * 100;
          // each bar gets half the padding
          barPadding = barPadding / 2;
        }
      
        let startAt, endAt,
            subScale = d3.scale.linear().domain([0, subs.size()]).range([0, 100]);
      
        subs.each(function (d, i) {
      
          startAt = subScale(i + 1) - subScale(1);
          endAt = subScale(i + 1);
      
          startAt += barPadding;
          endAt -= barPadding;
      
          // polygon(
          //  top-left-vertical top-left-horizontal,
          //  top-right-vertical top-right-horizontal,
          //  bottom-right-vertical bottom-right-horizontal,
          //  bottom-left-vertical bottom-left-horizontal,
          // )
      
          d3.select(this)
            .selectAll('rect')
            .attr("clip-path", `polygon(${startAt}% 0, ${endAt}% 0, ${endAt}% 100%, ${startAt}% 100%)`);
      
        });
      };
      
      let data = [
        {
          key: "First",
          value: [
            {key: 1, value: 0.18},
            {key: 2, value: 0.28},
            {key: 3, value: 0.68}
          ]
        },
        {
          key: "Second",
          value: [
            {key: 1, value: 0.72},
            {key: 2, value: 0.32},
            {key: 3, value: 0.82}
          ]
        },
        {
          key: "Third",
          value: [
            {key: 1, value: 0.3},
            {key: 2, value: 0.22},
            {key: 3, value: 0.7}
          ]
        },
        {
          key: "Fourth",
          value: [
            {key: 1, value: 0.18},
            {key: 2, value: 0.58},
            {key: 3, value: 0.48}
          ]
        }
      ];
      
      let ndx = crossfilter(data),
          dimension = ndx.dimension(d => d.key),
          group = {all: () => data}; // for simplicity sake (take a look at crossfilter group().reduce())
      
      let barChart1 = dc.barChart(compositeChart)
          .barPadding(0)
          .valueAccessor(d => d.value[0].value)
          .title(d => d.key + `[${d.value[0].key}]: ` + d.value[0].value)
          .colors(['#000']);
      
      let barChart2 = dc.barChart(compositeChart)
          .barPadding(0)
          .valueAccessor(d => d.value[1].value)
          .title(d => d.key + `[${d.value[1].key}]: ` + d.value[1].value)
          .colors(['#57B4F0']);
      
      let barChart3 = dc.barChart(compositeChart)
          .barPadding(0)
          .valueAccessor(d => d.value[2].value)
          .title(d => d.key + `[${d.value[2].key}]: ` + d.value[2].value)
          .colors(['#47a64a']);
      
      compositeChart
        .shareTitle(false)
        .dimension(dimension)
        .group(group)
        ._rangeBandPadding(RANGE_BAND_PADDING)
        ._outerRangeBandPadding(OUTER_RANGE_BAND_PADDING)
        .x(d3.scale.ordinal())
        .y(d3.scale.linear().domain([0, 1]))
        .xUnits(dc.units.ordinal)
        .compose([barChart1, barChart2, barChart3])
        .on("pretransition", chart => {
        scaleSubChartBarWidth(chart)
      })
        .on("filtered", (chart, filter) => {
        console.log(chart, filter);
      })
        .on("preRedraw", chart => {
        chart.rescale();
      })
        .on("preRender", chart => {
        chart.rescale();
      })
        .render();
      
      sizing(compositeChart);
      resizing(compositeChart);
      

      它并不完美,但它可以提供一个起点。

      【讨论】:

      • 如果我想创建一个类似于瀑布图的图表,在这种情况下将条形图移动到 y 轴上的特定位置。
      猜你喜欢
      • 2014-09-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多