1

I have two elements I need to render and a context of the big picture I am trying to achieve (a complete dashboard).

enter image description here

One is a chart that renders fine.

 $scope.riskChart = new dc.pieChart('#risk-chart');
 $scope.riskChart
       .width(width)
       .height(height)
       .radius(Math.round(height/2.0))
       .innerRadius(Math.round(height/4.0))
       .dimension($scope.quarter)
       .group($scope.quarterGroup)
       .transitionDuration(250);

The other is a triangle, to be used for a more complex shape

 $scope.openChart = d3.select("#risk-chart svg g")
        .enter()
        .attr("width", 55)
        .attr("height", 55)
        .append('path')
        .attr("d", d3.symbol('triangle-up'))
        .attr("transform", function(d) { return "translate(" + 100 + "," + 100 + ")"; })
        .style("fill", fill);

On invocation of render functions, the dc.js render function is recognized and the chart is seen, but the d3.js render() function is not recognized.

How do I add this shape to my dc.js canvas (an svg element).

 $scope.riskChart.render();   <--------------Works!
 $scope.openChart.render();   <--------------Doesn't work (d3.js)!

How do I make this work?

EDIT:

I modified dc.js to include my custom chart, it is a work in progress.

dc.starChart =  function(parent, fill) {
    var _chart = {};
    var _count = null, _category = null;
    var _width, _height;
    var _root = null, _svg = null, _g = null;
    var _region;
    var _minHeight = 20;
    var _dispatch = d3.dispatch('jump');

    _chart.count = function(count) {
        if(!arguments.length)
            return _count;
        _count = count;
        return _chart;
    };

    _chart.category = function(category) {
        if(!arguments.length)
            return _category
        _category = category;
        return _chart;
    };


function count() {
    return _count;
}

function category() {
    return _category;
}

function y(height) {
    return isNaN(height) ? 3 : _y(0) - _y(height);
}



_chart.redraw = function(fill) {
    var color = fill;  
    var triangle = d3.symbol('triangle-up');
    this._g.attr("width", 55)
        .attr("height", 55)
        .append('path')
        .attr("d", triangle)
        .attr("transform", function(d) { return "translate(" + 25 + "," + 25 + ")"; })
        .style("fill", fill);
    return _chart;

};     

_chart.render = function() {
    _g = _svg
        .append('g');

    _svg.on('click', function() {
        if(_x)
            _dispatch.jump(_x.invert(d3.mouse(this)[0]));
    });
    if (_root.select('svg'))
        _chart.redraw();
    else{
        resetSvg();                    
        generateSvg();
    }
    return _chart;
};

_chart.on = function(event, callback) {
    _dispatch.on(event, callback);
    return _chart;
};

_chart.width = function(w) {
    if(!arguments.length)
        return this._width;
    this._width = w;
    return _chart;
};

_chart.height = function(h) {
    if(!arguments.length)
        return this._height;
    this._height = h;
    return _chart;
};

_chart.select = function(s) {
    return this._root.select(s);
};

_chart.selectAll = function(s) {
    return this._root.selectAll(s);
};

function resetSvg() {
    if (_root.select('svg'))
        _chart.select('svg').remove();
    generateSvg();
}

function generateSvg() {
    this._svg = _root.append('svg')
        .attr({width: _chart.width(),
               height: _chart.height()});
}

_root = d3.select(parent);
return _chart;

}

enter image description here

Vahe
  • 1,699
  • 3
  • 25
  • 76
  • `.render()` is a method on dc.js charts. D3 is much lower level - more a graphics library without any concept of charts or rendering/redrawing. Although it’s not an exact duplicate I’m going to vote to close with another question which I hope will explain what you’re missing. – Gordon Jan 06 '20 at 05:54
  • Does this answer your question? [dc.js - Listening for chart group render](https://stackoverflow.com/questions/25336528/dc-js-listening-for-chart-group-render) – Gordon Jan 06 '20 at 05:58
  • I see this confusion between d3.js and dc.js frequently - please lmk if there is anything I can add to the dc.js documentation to make the relationship between the libraries more clear. – Gordon Jan 06 '20 at 12:31
  • Thanks Gordon, unfortunately there is no active link for dc.js source or the link to the line on github mentioned in the accepted answer (https://github.com/dc-js/dc.js/blob/master/src/core.js#L91) returns 404. However https://github.com/dc-js/dc.js works. Please update answer to reflect new path of core.js. The correct link has core.js under a core subdirectory (BUT, there is no line 91 to be found as seen in the link of the answer) https://github.com/dc-js/dc.js/blob/master/src/core/core.js#L91 – Vahe Jan 06 '20 at 17:24
  • 1
    Yes, that was way out of date - thanks for the heads up! I have updated it. Please comment on that answer if you run into other problems. – Gordon Jan 06 '20 at 19:17
  • 1
    This tab was still open on my screen and I realized you are probably not looking to add another chart at all, but just want to annotate the pie chart with some other content. For that, you'd want to look at [.on('pretransition',...) or .on('renderlet', ...)](https://dc-js.github.io/dc.js/docs/html/BaseMixin.html#on). If you search around you will find many examples on the web. – Gordon Jan 06 '20 at 19:29
  • Gordon, not sure if this is possible but can you provide a snippet example based on my code provided that solves my issue, I am unfamiliar with the inner workings of dc.js. At the very least some coherent stand alone answer that shows the process of implemenation. For example where (what js file) do I implement the functions, what functions should I implement? – Vahe Jan 09 '20 at 00:14
  • If not, I will ask a follow up question, the link provided in comment #2 of this answer, namely, https://stackoverflow.com/questions/25336528/dc-js-listening-for-chart-group-render shows a sample code in the example, do I simply create a new js file and create the relevant implmentations for my custom object type (wrapper) and then pass the object into `dc.reigsterChart(obj)`? – Vahe Jan 09 '20 at 00:28

2 Answers2

1

Because the triangle is not bound to any data array, .enter() should not be called.

Try this way:

 $scope.openChart = d3.select("#risk-chart svg g")
        .attr("width", 55)
        .attr("height", 55)
        .append('path')
        .attr("d", d3.symbol('triangle-up'))
        .attr("transform", function(d) { return "translate(" + 100 + "," + 100 + ")"; })
        .style("fill", fill);
Mehdi
  • 7,204
  • 1
  • 32
  • 44
  • Removal of the chained enter() function produced a small glitch (small shape appearing then dissappearing) How do I keep it visible? Should I be grouping all shapes in dc.js and d3.js, and if so, how do I do it? – Vahe Jan 05 '20 at 17:30
  • I haven't, I will now, but am unsure what its function is? – Vahe Jan 05 '20 at 21:47
  • Application of the suggestion produces `TypeError: this.setAttribute is not a function` reported back by d3.js, right above line in my code for $scope.openChart and right above angular.js in the call stack – Vahe Jan 05 '20 at 21:51
  • Moving $scope.riskChart.render(); after the successful dc.js chart function and before the unsuccessful d3.js removes the error. But still no chart appears... `dc.redrawAll('group');` succeeds the unsuccessful d3.js function call to redraw everything. Unclear how to `render()` on d3.js, as `render()` works only on dc.js – Vahe Jan 05 '20 at 22:02
1

I think I confused matters by talking about how to create a new chart, when really you just want to add a symbol to an existing chart.

In order to add things to an existing chart, the easiest thing to do is put an event handler on its pretransition or renderlet event. The pretransition event fires immediately once a chart is rendered or redrawn; the renderlet event fires after its animated transitions are complete.

Adapting your code to D3v4/5 and sticking it in a pretransition handler might look like this:

yearRingChart.on('pretransition', chart => {
  let tri = chart.select('svg g') // 1
    .selectAll('path.triangle') // 2
    .data([0]); // 1
  tri = tri.enter()
    .append('path')
    .attr('class', 'triangle')
    .merge(tri);
  tri
    .attr("d", d3.symbol().type(d3.symbolTriangle).size(200))
    .style("fill", 'darkgreen'); // 5
})

Some notes:

  1. Use chart.select to select items within the chart. It's no different from using D3 directly, but it's a little safer. We select the containing <g> here, which is where we want to add the triangle.
  2. Whether or not the triangle is already there, select it.
  3. .data([0]) is a trick to add an element once, only if it doesn't exist - any array of size 1 will do
  4. If there is no triangle, append one and merge it into the selection. Now tri will contain exactly one old or new triangle.
  5. Define any attributes on the triangle, here using d3.symbol to define a triangle of area 200.

ring chart with a triangle in the center Example fiddle.

Gordon
  • 19,811
  • 4
  • 36
  • 74
  • I am posting the entire mockup of the dashboard including the star of the dashboard I want rendered, made up of primitives. In the mean time, thank you for this helpful and comprehensive response. I will award the answer. Feel free to comment to the answer related to my question image I will post – Vahe Jan 09 '20 at 20:15
  • Triangles are the basic building block of the 12 point star with a white circle inside (text content) This star is separate from the year ring chart that would fit into the first grid of the dashboard. – Vahe Jan 09 '20 at 20:18
  • It is actually the case that I want to build separate charts in one grid because they are not just a pie chart and triangle but a pie chart and a star. – Vahe Jan 09 '20 at 20:23
  • I see - the picture helps explain what you are trying to do. It looks like the 12-point star is basically static but the color will change based on the number which is displayed. Hard to say if it would be easier to create a new chart class, or just what I showed above, except selecting elements outside of the chart instead of inside it. I guess the choice would depend on whether you want the star to be reusable. If you want the numbers to "flip" from one value to the other, you could use the [number display widget](https://dc-js.github.io/dc.js/docs/html/NumberDisplay.html) for that part. – Gordon Jan 09 '20 at 20:30
  • The colors are going to be static for all charts, I just chose those different shades for differentiation. – Vahe Jan 09 '20 at 20:33
  • If the color isn't changing, maybe just embed a static SVG image and put a number display widget on top of it? – Gordon Jan 09 '20 at 20:40
  • I used https://github.com/jColeChanged/svg2d3js to convert my MS PowerPoint star saved as an SVG natively, then copied the result into the example fiddle (From Gordon's accepted answer above) After modifying variables and renaming attrs to attr multiple times I ended up with a working prototype https://jsfiddle.net/fyv41kaL/7/ – Vahe Jan 09 '20 at 23:50
  • Glad you got it working. There isn't any need to convert the SVG to D3, because web browsers display SVG natively with the `` tag. You wouldn't need to use my code either. It would just be HTML layout + embedded SVG + a number display widget. – Gordon Jan 09 '20 at 23:55