dc-js / dc.js

Multi-Dimensional charting built to work natively with crossfilter rendered with d3.js
Apache License 2.0
7.42k stars 1.81k forks source link

TreeMap support #129

Open xhkong opened 11 years ago

xhkong commented 11 years ago

Nick,

Can we add TreeMap support as single (multiple when supported) selection chart? This can be really useful in some cases. And I assume this will not be too hard to achieve, but I might be wrong.

Thanks a lot!

Javy

NickQiZhu commented 11 years ago

TreeMap is a good way to visualize hierarchical data however crossfilter currently does not support hierarchical data (drill down/drill up). I am doing some related hierarchical data visualization in some other project and hopefully will be able to incorporate some ideas into dc.js eventually. As of now I don’t think it is possible with crossfilter.

Will keep this in the backlog as a future feature request, cheers.

marcrichter commented 11 years ago

Nick,

Treemap uses are not limited to hierarchical data, especially the Marimekko / Mosaic variant is highly useful to assess interdependencies in data comprising several categorical dimensions. I like to think of it as the 2D+ variant of (normalised) stacked bars, and it's apparently well-implemented in D3 v3 (treemap.mode("slice-dice"), see https://github.com/mbostock/d3/pull/169).

For some additional background check the following slide decks: http://www.interactivegraphics.org/Slides_files/Chapter31.pdf http://www.interactivegraphics.org/Slides_files/Chapter41.pdf

Cheers Marc

NickQiZhu commented 11 years ago

Agree, though not sure I have enough bandwidth to tackle this anytime soon (just had a new baby this week :) Will slot it in v1.5 for now.

marcrichter commented 11 years ago

Congrats on the baby, and good luck! :-) I only hope it won't make you lose interest in this other baby of yours entirely! ;-)

jrideout commented 10 years ago

Two approaches:

http://www.jasondavies.com/parallel-sets/ http://bl.ocks.org/mbostock/1005090

ghost commented 10 years ago

I implemented one for my report, here is how I added the treemap to dc:

dc.treemapChart = function (parent, chartGroup) {

  var _chart = dc.baseMixin({});
  var _treemap;

  _chart._doRedraw = function() {
    var _cellData = {
      name:'tree',
      children: _chart.data()
    };
    _chart.root()
      .selectAll('.node')
      .data(_treemap.nodes)
      .transition().duration(1000)
      .call(_updateCell);

    _highlightFilters();
    return _chart;
  };

  _chart._doRender = function () {

    var color = d3.scale.category20c();

    _treemap = d3.layout.treemap()
      .size([_chart.width(), _chart.height()])
      .sticky(true)
      .value(function (d) { return d.value; });

    var _cellData = {
      name:'tree',
      children: _chart.data()
    };

    _chart.root()
      .classed('treemap', true)
      .style('position', 'relative')
      .style('height', _chart.height() + 'px')
      .style('width', _chart.width() + 'px');

    var _node = _chart.root()
      .datum(_cellData)
      .selectAll('.node')
      .data(_treemap.nodes)
      .enter()
      .append('div')
      .attr('class', 'node')
      .call(_updateCell)
      .style('background', function(d) { return color(d.key); })
      .style('position', 'absolute')
      .text(function(d) { return d.key; })
      .attr('title', _chart.title())
      .on('click', onClick);

    return _chart;
  };

  function _updateCell() {
    this.style('left', function (d) { return d.x + 'px'; })
    .style('top', function (d) { return d.y + 'px'; })
    .style('width', function (d) { return (d.dx - 1) + 'px'; })
    .style('height', function (d) { return (d.dy - 1) + 'px'; })
    .style('font-size', function (d) { return d.value > 0 ? 0.1 * Math.sqrt(d.dx * d.dy) +'px' : 0; });
  }

  function onClick(d, i) {
    _chart.onClick(d, i);
  }

  function _highlightFilters() {
    if (_chart.hasFilter()) {
      _chart.root().selectAll('.node').each(function (d) {
        if (_chart.hasFilter(d.key)) {
          _chart.highlightSelected(this);
        }
        else {
          _chart.fadeDeselected(this);
        }
      });
    }
    else {
      _chart.root().selectAll('.node').each(function (d) {
        _chart.resetHighlight(this);
      });
    }
  }

  return _chart.anchor(parent, chartGroup);
};
marcrichter commented 10 years ago

That's excellent! Any change to post this as a pull request for core?

bwinchester commented 10 years ago

pierco's example causes a call stack size limit error. Needs more work to incorporate the .margins method other box charts have in DC.

ghost commented 10 years ago

Could you give me an example of this method implementation or point me to the right direction ? Thanks

bwinchester commented 10 years ago

There is a margins mixin for DC.js that takes top left right bottom. Also, can you send an example of the data object you used and the crossfilter dimension and group functions you used to get yours to work?

Margins mixin https://github.com/dc-js/dc.js/blob/master/src/margin-mixin.js

BTW The margins is a suggestion to help place the chart and the call stack error was due to data input. I figured that out, but would like to see how you did the crossfilter dimensions and group to get this chart to display. I was using a demension from a pie chart with 5-6 groups with .group().reduceCount() as the group object, and the chart was displaying but caused a call stack exceeded error.

On Thursday, March 20, 2014, pierco notifications@github.com wrote:

Could you give me an example of this method implementation or point me to the right direction ? Thanks

Reply to this email directly or view it on GitHubhttps://github.com/dc-js/dc.js/issues/129#issuecomment-38214264 .

awjreynolds commented 9 years ago

I was wondering if any further work had been done on this?

jannah commented 9 years ago

Hi, I made a minor modification to allow caps/valueAccessor/KeyAccessor.

dc.treemapChart = function (parent, chartGroup) {

  var _chart = dc.capMixin(dc.marginMixin(dc.baseMixin({})));
  var _treemap;
  _chart.rowsCap = _chart.cap;
  _chart._doRedraw = function() {
    var _cellData = {
      name:'tree',
      children: _chart.data()
    };
    _chart.root()
      .selectAll('.node')
      .data(_treemap.nodes)
      .transition().duration(1000)
      .call(_updateCell);

    _highlightFilters();
    return _chart;
  };

  _chart._doRender = function () {

    var color = d3.scale.category20c();

    _treemap = d3.layout.treemap()
      .size([_chart.width(), _chart.height()])
      .sticky(true)
      .value(function (d) { console.log('treemap value', d); return _chart.valueAccessor()(d); });

    var _cellData = {
      name:'tree',
      children: _chart.data()
    };

    _chart.root()
      .classed('treemap', true)
      .style('position', 'relative')
      .style('height', _chart.height() + 'px')
      .style('width', _chart.width() + 'px');

    var _node = _chart.root()
      .datum(_cellData)
      .selectAll('.node')
      .data(_treemap.nodes)
      .enter()
      .append('div')
      .attr('class', 'node')
      .call(_updateCell)
      .style('background', function(d) { return color(d.key); })
      .style('position', 'absolute')
      .text(function(d) { return _chart.keyAccessor()(d); })
      .attr('title', _chart.title())
      .on('click', onClick);

    return _chart;
  };

  function _updateCell() {
    this.style('left', function (d) { return d.x + 'px'; })
    .style('top', function (d) { return d.y + 'px'; })
    .style('width', function (d) { return (d.dx - 1) + 'px'; })
    .style('height', function (d) { return (d.dy - 1) + 'px'; })
    .style('font-size', function (d) { return d.value > 0 ? 0.1 * Math.sqrt(d.dx * d.dy) +'px' : 0; });
  }

  function onClick(d, i) {
    _chart.onClick(d, i);
  }

  function _highlightFilters() {
    if (_chart.hasFilter()) {
      _chart.root().selectAll('.node').each(function (d) {
        if (_chart.hasFilter(d.key)) {
          _chart.highlightSelected(this);
        }
        else {
          _chart.fadeDeselected(this);
        }
      });
    }
    else {
      _chart.root().selectAll('.node').each(function (d) {
        _chart.resetHighlight(this);
      });
    }
  }

  return _chart.anchor(parent, chartGroup);
};
KatiRG commented 9 years ago

I tried both versions of dc.treemapChart and I got this error:

c.utils.GroupStack is not a constructor

Any ideas?

gordonwoodhull commented 9 years ago

@KatiRG, I haven't tried the code myself, but I don't understand that error since GroupStack is not a symbol in either this code or dc.js

KatiRG commented 9 years ago

ok that's weird! I'm trying the sunburst now, as you suggested. I'll let you know what happens, thanks!

dderiso commented 8 years ago

@jannah @ghost Thanks for adding treemap support. Did you submit a pull request?

oakley808 commented 8 years ago

Wow! This is exactly what I need. I don't see a PR. Is there something I could do to contribute, @jannah @ghost ?

gordonwoodhull commented 8 years ago

Since no one has submitted a PR, I think the best thing you can do is try this code out on your own @oakley808 and let us know how it works.

Then if you feel ambitious and neighborly, submit a PR yourself!

You should be able to just add this code in another script loaded after dc, as long as module gunk doesn't get in the way. dc is very extensible.

rafaneri commented 6 years ago

@jannah I did some tests, but the filters aren't working properly. In https://codepen.io/rafaneri/pen/dKjqeJ is possible to see, that "Empresa 0143" has one data where gender is "M", but when I filter on pie chart for gender "M", the slot "Empresa 0143" is hidden.