nvd3 scatter plot with ordinal scale
Asked Answered
I

3

6

Im relatively new to d3 and nvd3 and wanted to create a simple scatterplot, just like the example but with an ordinal y-axis. So y axis values are categorical strings. This is what I thought I needed to do:

var xfun = function (d) { return d.Pos }    //simple ints
  , yfun = function (d) { return d.Title }  //the ordinal values

var chart = nv.models.scatterChart()
    .showDistX(true)
    .showDistY(true)
    .color(d3.scale.category10().range())
    .margin({ top: 30, right: 20, bottom: 50, left: 130 })
    .tooltips(false)
    .x(xfun)   
    .y(yfun);

// create an ordinal scale with some test values
var ys = d3.scale.ordinal()
    .domain(["this","is","an","ordinal","scale"])
    .range(5);

// tell nvd3 to use it
chart.yAxis.scale(ys);

// add to the page
nv.addGraph(function () {
    d3.select(selector).datum(data).transition().duration(500).call(chart);
    nv.utils.windowResize(chart.update);
    return chart;
});

However, no luck:

Error: Invalid value for <circle> attribute cy="NaN" d3.js:690
Error: Invalid value for <line> attribute y1="NaN" d3.js:690
Error: Invalid value for <line> attribute y2="NaN" d3.js:690
Error: Invalid value for <circle> attribute cy="NaN" d3.js:7532
Uncaught TypeError: Cannot read property '0' of undefined 
..

And the y-axis simply shows a linear axis from -1 to 1. Iterestingly there are some circles plotted at y=-1 and y=1 (the extremes).

To manually force correct values for cy I tried adding after call(chart):

d3.selectAll("#mychart circle").attr("cy", function(d){
        return = ys(yfun(d));
        });

But still the same error. How do I get the ordinal scale to work properly? Note I also need it to update correctly when I use the nvd3 legend to switch between dataseries (which will contain different x/y data).

There is a related question on github, but no solution.


Update: after some debugging I tried replacing chart.yAxis.scale(ys) with chart.scatter.y(ys) and this gets rid of the errors. I can also drop the manual selectAll.

However, the y-axis still shows a linear scale from 0.99-1.01 and all points are plotted at y=1. So a step closer but no ordinal scale yet.

Insider answered 25/6, 2013 at 12:8 Comment(2)
Have u inspected the original scaterrplot... The circles that u see are not circles. they are paths drwan in a circular shape :)Syncarpous
@Dom: may be so but its still complaining about svg <circle> elements and I can definitely seem them in the dom (but all with a NaN value for cy)Insider
C
6

In case somebody else stumbles upon this, here's what worked for me: instead of trying to force an ordinal scale on the axis (X, in my case), I used a linear scale, but provided a custom tickFormat function that returned the desired label.

chart.xAxis.tickFormat(function(d){
            return labelValues[d];
        });

Where labelValues maps between the numerical value and the desired label.

Calcify answered 10/1, 2014 at 2:28 Comment(4)
Yes this works. Now I need to figure out how to rotate the labels to be vertical.Wolfson
chart.xAxis.rotateLabels(90) does rotate the labels as desired, however only every nth label is rendered, depending it appears, on the library's label hiding algorithm that avoids overlaps. In my case every 7th label is rendered.Wolfson
could you provide an example of such a "map between numerical value and the desired label"?Manolo
@MiguelVazq try my one may be it will help youCentrifuge
C
2

There is an easy solution by @robinfhu.

Don't use an ordinal scale!

Instead, set your x function to return the index of the element:

chart.x(function(d,i){ return i;})

And set your tickFormat function to read the proper label:

tickFormat(function(d) {return that.data[0].values[d].x;})

Working example: Plunker

Copy and paste:

  nv.models.lineChart()
    .x(function(d, i) {
      return i;
    }).xAxis
    .tickFormat(function(d) {
      return that.data[0].values[d].x;
    });
Centrifuge answered 27/1, 2016 at 11:37 Comment(0)
L
1

I haven't used nvd3.js, but from my d3 experience, the error seems to be the range on your scale. For an ordinal scale, you need to define an array of corresponding values for the domain to map to. For example, I could set the following scale:

var myScale = d3.scale.ordinal()
    .domain(['this','is','an','ordinal','scale'])
    .range([1,2,3,4,5])

This scale would map my domain to numbers in the range 1:5, with 'this' -> 1, 'is' -> 2, etc. If I didn't want to set each point individually, I could also define my range with rangePoints, as follows:

var myScale = d3.scale.ordinal()
    .domain(['this','is','an','ordinal','scale'])
    .rangePoints([1,5])

rangePoints gives me an evenly spaced range of points from the minimum specified value to the max specified value, so this would result in the same range being generated.

I've made some circles that illustrate how to do this here.

When you change to a different data series, you'll have to update your domain. Since range corresponds to where on the page your points are mapping to, you won't need to update it, unless nvd3.js applies a secondary mapping and does ordinalDomain -> integers -> pointLocation in two steps.

Another option is to apply the scale in the function definition.

yfun = function (d) { return ys(d.Title) } 
Leastways answered 25/6, 2013 at 15:18 Comment(3)
thanks. I had tried that as well but no luck. See the edit above. It seems that the problem is earlier than this, i.e., getting nvd3 to acually use the ordinal scale in the first place. As for dynamic series switching, nvd3 provides this out of the box (also with ordinal data) in the multibar example. So I would imagine it should also work with the scatter chart here.Insider
I see what you mean with nvd3. One possibility would be to use ys in your original function to change the values before nvd3 sees them. I've added how to do that to my above post.Leastways
Indeed, in that case I hide from nvd3 that its an ordinal scale and just return some integers. This indeed works and thats what I have now. I could then try to manually override the tick marks but it all feels like a massive hack :) Have created a github issue on the nvd3 side as well github.com/novus/nvd3/issues/177Insider

© 2022 - 2024 — McMap. All rights reserved.