1

The attached code should produce a few coloured circles, and indeed it does when using a hard-coded dataset of [1,4,2,3,5].

However, I don't understand why the code below isn't working using my JSON data array.

The console in the browser is showing the JSON query is successfully running and pulling the data into the page - just nothing seems to happen with it.

The JSON query is as follows:

[{"Id":1,"UserID":"JNP","HCRef":"ProjSetup","AnswerString":"","Answerlist":1},
{"Id":2,"UserID":"JNP","HCRef":"ProjSetup","AnswerString":null,"Answerlist":2},
{"Id":3,"UserID":"JNP","HCRef":"ProjSetup","AnswerString":null,"Answerlist":5},
{"Id":4,"UserID":"JNP","HCRef":"ProjSetup","AnswerString":null,"Answerlist":6},
{"Id":5,"UserID":"JNP","HCRef":"ProjSetup","AnswerString":null,"Answerlist":3},
{"Id":6,"UserID":"JNP","HCRef":null,"AnswerString":null,"Answerlist":4}]

What I want to happen, is use the Answerlist variable to be returned in the returncolour section of the code and drive the colour of the circles. The data is otherwise only used to trigger a circle being drawn at all (using the 'i' parameter).

As I say, the code works fine doing this with a hardcoded dataset.

Code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>D3 Test</title>
    <script src="https://code.jquery.com/jquery-1.10.2.js"></script>
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <style type="text/css">
    </style>
</head>
<body>
<script type="text/javascript">
    var dataset = d3.json('/api/answers/', function (d) {
        console.log(d);
    });

    var w = 1000;
    var h = 1000;
    // var dataset = [1,4,2,3,5];
    var svg = d3.select("body")
        .append("svg")
        .attr("width", w)
        .attr("height", h);
    var circles = svg.selectAll("circle")
             .data(dataset)
             .enter()
             .append("circle");

    circles.attr("cx", function (d, i) 
    {
        var Xaxis;
        if (i === 0) { Xaxis = "500"; }
        else if (i === 1) { Xaxis = "400"; }
        else if (i === 2) { Xaxis = "420"; }
        else if (i === 3) { Xaxis = "452.5"; }
        else if (i === 4) { Xaxis = "485"; }
        else if (i === 5) { Xaxis = "515"; }
        else if (i === 6) { Xaxis = "547.5"; }
        else if (i === 7) { Xaxis = "580"; }
        else if (i === 8) { Xaxis = "600"; }
        else if (i === 9) { Xaxis = "600"; }
        else if (i === 10) { Xaxis = "650"; }
        else if (i === 11) { Xaxis = "700"; }
        else if (i === 12) { Xaxis = "750"; }
        else if (i === 13) { Xaxis = "750"; }
        else if (i === 14) { Xaxis = "750"; }
        else if (i === 15) { Xaxis = "750"; }
        else if (i === 16) { Xaxis = "750"; }
        return Xaxis;
    })
    circles.attr("cy", function (d, i) {
        var Yaxis;
        if (i === 0) { Yaxis = "500"; }
        else if (i === 1) { Yaxis = "500"; }
        else if (i === 2) { Yaxis = "535"; }
        else if (i === 3) { Yaxis = "560"; }
        else if (i === 4) { Yaxis = "585"; }
        else if (i === 5) { Yaxis = "585"; }
        else if (i === 6) { Yaxis = "560"; }
        else if (i === 7) { Yaxis = "535"; }
        else if (i === 8) { Yaxis = "500"; }
        else if (i === 9) { Yaxis = "600"; }
        else if (i === 10) { Yaxis = "550"; }
        else if (i === 11) { Yaxis = "500"; }
        else if (i === 12) { Yaxis = "450"; }
        else if (i === 13) { Yaxis = "600"; }
        else if (i === 14) { Yaxis = "550"; }
        else if (i === 15) { Yaxis = "500"; }
        else if (i === 16) { Yaxis = "450"; }
        return Yaxis;
    })
   .attr("r", function (d, i) {
       var size;
       if (i === 0) { size = "30"; }
       else if (i > 0) { size = "20"; }
       return size;
   })
   .attr("fill", function (d) {
       return d.Answerlist;
       var returnColor;
       if (d.Answerlist === 1) { returnColor = "green"; }
       else if (d.Answerlist === 2) { returnColor = "lightgreen"; }
       else if (d.Answerlist === 3) { returnColor = "gold"; }
       else if (d.Answerlist === 4) { returnColor = "darkorange"; }
       else if (d.Answerlist === 5) { returnColor = "red"; }
       else if (d.Answerlist === 6) { returnColor = "lightgrey"; }
        return returnColor;
    });
</script>
</body>
</html>

2 Answers 2

3

This is related to asynchronous code.

Move your processing code inside the callback, where you currently only have:

console.log(d);

The code you will put there is executed later than any of the non-callback code you have, because the script first executes to completion and only then (asynchronously) the callback will be fired -- and thus, only then d (dataset) will have the desired value:

d3.json('/api/answers/', function (dataset) {
    var w = 1000;
    var h = 1000;
    var svg = d3.select("body")
        .append("svg")
        .attr("width", w)
        .attr("height", h);
    var circles = svg.selectAll("circle")
             .data(dataset)
             .enter()
             .append("circle");
    // etc....
    // ...
});

NB: your enormous if statements can be reduced to 5% of the code, if you would use arrays for your possible values.

For instance, this:

    var Xaxis;
    if (i === 0) { Xaxis = "500"; }
    else if (i === 1) { Xaxis = "400"; }
    else if (i === 2) { Xaxis = "420"; }
    else if (i === 3) { Xaxis = "452.5"; }
    else if (i === 4) { Xaxis = "485"; }
    else if (i === 5) { Xaxis = "515"; }
    else if (i === 6) { Xaxis = "547.5"; }
    else if (i === 7) { Xaxis = "580"; }
    else if (i === 8) { Xaxis = "600"; }
    else if (i === 9) { Xaxis = "600"; }
    else if (i === 10) { Xaxis = "650"; }
    else if (i === 11) { Xaxis = "700"; }
    else if (i === 12) { Xaxis = "750"; }
    else if (i === 13) { Xaxis = "750"; }
    else if (i === 14) { Xaxis = "750"; }
    else if (i === 15) { Xaxis = "750"; }
    else if (i === 16) { Xaxis = "750"; }
    return Xaxis;

... can be written as:

    var Xaxis = [500, 400, 420, 452.5, 485, 515, 547.5, 580, 600, 600,
                 650, 700, 750, 750,   750,  750, 750][i];

It seems though you want to position the smaller circles nicely around the bigger circle. In that case you can calculate the coordinates using trigonometric functions (sine and cosine) based on the value of i. It could look like this for the smaller circles (i > 0) -- the larger is positioned at [x,y] = [500,500]:

    x = 500-Math.cos((i-1)/2)*100
    y = 500+Math.sin((i-1)/2)*100

Also note that you have dead code in your attr callback. The return statement will exit that function before determining a color and returning that. So you should delete that return statement.

Finally, there is a color missing, since your sample data has AnswerList values going up to 6, while you only specify colors for up to index 5.

Here is how the code could look with these changes. Note that I have used ES6 arrow functions. If you don't have ES6 support, just change them to standard functions:

var dataset = [
    {"Id":1,"UserID":"JNP","HCRef":"ProjSetup","AnswerString":"","Answerlist":1},
    {"Id":2,"UserID":"JNP","HCRef":"ProjSetup","AnswerString":null,"Answerlist":2},
    {"Id":3,"UserID":"JNP","HCRef":"ProjSetup","AnswerString":null,"Answerlist":5},
    {"Id":4,"UserID":"JNP","HCRef":"ProjSetup","AnswerString":null,"Answerlist":6},
    {"Id":5,"UserID":"JNP","HCRef":"ProjSetup","AnswerString":null,"Answerlist":3},
    {"Id":6,"UserID":"JNP","HCRef":null,"AnswerString":null,"Answerlist":4}];
var svg = d3.select("body")
    .append("svg")
    .attr("width", 1000)
    .attr("height", 1000)
    .selectAll("circle")
    .data(dataset)
    .enter()
    .append("circle")
    .attr("cx", (d, i) => i ? 500-Math.cos((i-1)/2)*100 : 500)
    .attr("cy", (d, i) => i ? 500+Math.sin((i-1)/2)*100 : 500)
    .attr("r", (d, i) => i === 0 ? 30 : 20)
    .attr("fill", (d) => ['', 'green', 'gold', 'darkorange', 'red', 'lightgrey', 'purple']
                         [d.Answerlist]);
<script src="https://d3js.org/d3.v4.min.js"></script>
Scroll down to see the colored circles.

Sign up to request clarification or add additional context in comments.

9 Comments

Thanks for the response. I have given this a go and still no joy. The Console still shows the object values being loaded, but the code doesn't seem to be doing anything with the data. I am suspecting that something is wrong with how I am calling the right part of the Json string to be used. Should I be doing something different to call the Answerlist section of the Json string, as that is where the Int values sit that will determine the colour.
Well, I don't know why you put a return statement at the start of the fill callback (near the end), but if you want to see colors, then you might want to enable the code you have below that return: just remove it, so the color is being read from the dataset. I don't know what else you expect to happen.
I added a working snippet for your convenience. I also added a better solution for placing the smaller circles nicely around the bigger one.
I have spent some time looking at this - firstly, yes I can now see the mistake on the premature return statement! Silly indeed. However, I have tried running your code on my build and no joy. I have also tried removing the return and tweaking etc, I still just get a blank unless I use hardcoded data rather than the JSON. I will keep looking at it, but cannot see why the JSON string isn't playing ball.
Can you provide a fiddle that demonstrates the problem? For better debugging, write the JSON representation to the console: console.log(JSON.stringify(dataset)); and carefully check it has the structure you expect.
|
1

I'm late to the party, and the problem with the asynchronous code was already solved by trincot. But I'd like to talk about your huge and unnecessary if else statements. It was also addressed in trincot's answer, but there is an even better D3 way of dealing with it: using scales.

So, if you want to positioning your circles evenly from the position 200 to the position 400 in your x axis, here is a simple solution:

var xScale = d3.scalePoint()
    .range([200, 400])//the start and end position

Then, you use the scale to set the circles' cx position:

.attr("cx", function(d,i){
    return xScale(i)
});

See? You don't need to manually set the x positions in a huge array ([400, 420, 450...]) and even less in a cumbersome bunch of if statements, d3 does that for you.

Check this demo, using 10 circles:

var svg = d3.select("body")
  .append("svg")
  .attr("width", 500)
  .attr("height", 100);

var dataset = d3.range(10);

var xScale = d3.scalePoint()
    .range([200, 400])
    .domain(dataset);

var circles = svg.selectAll(".circle")
  .data(dataset)
  .enter()
  .append("circle");

circles.attr("cy", 50)
  .attr("r", 5)
  .attr("fill", "teal")
  .attr("cx", function(d,i){return xScale(i)});
<script src="https://d3js.org/d3.v4.min.js"></script>

2 Comments

Thanks for the response, but per your previous help, this code will not use IF statements like this in the final version, I am simply used this code to test the connection to the database and use the Json string - so I appreciate the messy IF statements aren't good. Would you be able to look at the other question I have posted, which is using your previous advice with regard to generating the circles? What I will then do, is use the working Json array code from this question with the smarter circle solution. I didn't want to mix up the questions here etc.
@JNPer Did you see my last comment in your first question?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.