0

I'm a newbie both in JSON and js. I'm trying to load json data based on this radarchart. If I add the data just after var data = MY DATA, it works but if I load json file, it doesn't. For loading data, I follow the last suggestion entitled “What does d3.json return?” (v5) of this post--in fact the others didn't worked too--; thus, I have added <script src="https://d3js.org/d3.v5.min.js"></script> in addition to <script src="https://d3js.org/d3.v4.min.js"></script> which I used for other barplots and piecharts in the same webpage.

It works

<head>
  <!-- Load d3.js -->
  <script src="https://d3js.org/d3.v4.min.js"></script>
  <script src="https://d3js.org/d3-path.v1.min.js" charset="utf-8"></script>
  <!-- source: https://gist.github.com/mthh/7e17b680b35b83b49f1c22a3613bd89f -->
  <script src="../other/radarChart.js" charset="utf-8"></script>
</head>

<body>

<!-- a few barplots and piecharts -->
<!-- afterwards the following radarChart -->

<table>
               <tr>
                   <td style="radarChart">
                       <div class="radarChart" style="display: inline-flex;"></div>
                       <script>
                           //////////////////////////////////////////////////////////////
                           //////////////////////// Set-Up //////////////////////////////
                           //////////////////////////////////////////////////////////////
                           
                           var marginRadar = { top: 150, right: 80, bottom: 50, left: 80 },
                           widthRadar = Math.min(700, window.innerWidth / 4) - marginRadar.left - marginRadar.right,
                           heightRadar = Math.min(width, window.innerHeight - marginRadar.top - marginRadar.bottom);
                           
                           //////////////////////////////////////////////////////////////
                           ////////////////////////// Data //////////////////////////////
                           //////////////////////////////////////////////////////////////
                          
                           
                           var data = [
                           {
                           "name": "Clan A",
                           "axes": [
                           {"axis": "cognition", "value": 3.12}, 
                           {"axis": "communication","value": 9.38},
                           {"axis": "competition","value": 18.75},
                           {"axis": "consumption","value": 6.25},
                           {"axis": "contact","value": 21.88},
                           {"axis": "emotion","value": 6.25},
                           {"axis": "motion","value": 18.75},
                           {"axis": "perception","value": 3.12},
                           {"axis": "possession","value": 3.12},
                           {"axis": "social","value": 3.12}
                           ]
                           },
                           {"name": "clan B",
                           "axes": [
                           {"axis": "cognition","value": 3.12},
                           {"axis": "communication","value": 0.00},
                           {"axis": "competition","value": 0.00},
                           {"axis": "consumption","value": 0.00},
                           {"axis": "contact","value": 9.38},
                           {"axis": "emotion","value": 0.00},
                           {"axis": "motion","value": 0.00},
                           {"axis": "perception","value": 0.00},
                           {"axis": "possession","value": 0.00},
                           {"axis": "social","value": 0.00}
                           ]
                           }
                           ];
                           
                          
                           var radarChartOptions = {
                           w: 290,
                           h: 350,
                           margin: marginRadar,
                           levels: 6,
                           roundStrokes: false,
                           color: d3.scaleOrdinal().range(["#AFC52F", "#ff6600"]),
                           format: '.0f',
                           legend: { title: 'Legend', translateX: 150, translateY: 100 },
                           unit: '%'
                           };
                           
                           // Draw the chart, get a reference the created svg element :
                           let svg_radar = RadarChart(".radarChart", data, radarChartOptions);
                           
                       </script>
                   </td>
               </tr>
           </table>
</body>

It doesn't work: loading json file [note: everything is the same before and after the following suggestion, except that I have added <script src="https://d3js.org/d3.v5.min.js"></script> between <td> and <div class="radarChart"..> so that there is no conflict with barplots and piecharts which need v4 instead of v5]:

<table>
               <tr>
                   <td style="radarChart">
                     <!-- add v5 here -->
                     <script src="https://d3js.org/d3.v5.min.js"></script>
                       <div class="radarChart" style="display: inline-flex;"></div>
                     <!-- [same data ] -->
                     [...]

                     var data = d3.json("../other/radarChart-data.json");
                           console.log(data)
                    
                    <!-- [same data after] -->
                    [...]

Thus my question is: how to load JSON file for this radarchart? I prefer to load a file since "value: XX" are not fixed, but changed according to new values adding in related xml files.

In advance, thanks so much for your kind help.

Vanessa
  • 121
  • 12

2 Answers2

1

Important: Both imports of d3 edit the global scope, so one will override the other. I recommend to just choose v4 for the radar chart.

My answer is very long, because I had to include the RadarChart code to make it work. Just scroll down to the very end to see what I did. d3.json needs a function as a second argument, and will call that function with the result:

/////////////////////////////////////////////////////////
/////////////// The Radar Chart Function ////////////////
/// mthh - 2017 /////////////////////////////////////////
// Inspired by the code of alangrafu and Nadieh Bremer //
// (VisualCinnamon.com) and modified for d3 v4 //////////
/////////////////////////////////////////////////////////

const max = Math.max;
const sin = Math.sin;
const cos = Math.cos;
const HALF_PI = Math.PI / 2;

const RadarChart = function RadarChart(parent_selector, data, options) {
  //Wraps SVG text - Taken from http://bl.ocks.org/mbostock/7555321
  const wrap = (text, width) => {
    text.each(function() {
      var text = d3.select(this),
        words = text.text().split(/\s+/).reverse(),
        word,
        line = [],
        lineNumber = 0,
        lineHeight = 1.4, // ems
        y = text.attr("y"),
        x = text.attr("x"),
        dy = parseFloat(text.attr("dy")),
        tspan = text.text(null).append("tspan").attr("x", x).attr("y", y).attr("dy", dy + "em");

      while (word = words.pop()) {
        line.push(word);
        tspan.text(line.join(" "));
        if (tspan.node().getComputedTextLength() > width) {
          line.pop();
          tspan.text(line.join(" "));
          line = [word];
          tspan = text.append("tspan").attr("x", x).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
        }
      }
    });
  } //wrap

  const cfg = {
    w: 600, //Width of the circle
    h: 600, //Height of the circle
    margin: {
      top: 20,
      right: 20,
      bottom: 20,
      left: 20
    }, //The margins of the SVG
    levels: 3, //How many levels or inner circles should there be drawn
    maxValue: 0, //What is the value that the biggest circle will represent
    labelFactor: 1.25, //How much farther than the radius of the outer circle should the labels be placed
    wrapWidth: 60, //The number of pixels after which a label needs to be given a new line
    opacityArea: 0.35, //The opacity of the area of the blob
    dotRadius: 4, //The size of the colored circles of each blog
    opacityCircles: 0.1, //The opacity of the circles of each blob
    strokeWidth: 2, //The width of the stroke around each blob
    roundStrokes: false, //If true the area and stroke will follow a round path (cardinal-closed)
    color: d3.scaleOrdinal(d3.schemeCategory10), //Color function,
    format: '.2%',
    unit: '',
    legend: false
  };

  //Put all of the options into a variable called cfg
  if ('undefined' !== typeof options) {
    for (var i in options) {
      if ('undefined' !== typeof options[i]) {
        cfg[i] = options[i];
      }
    } //for i
  } //if

  //If the supplied maxValue is smaller than the actual one, replace by the max in the data
  // var maxValue = max(cfg.maxValue, d3.max(data, function(i){return d3.max(i.map(function(o){return o.value;}))}));
  let maxValue = 0;
  for (let j = 0; j < data.length; j++) {
    for (let i = 0; i < data[j].axes.length; i++) {
      data[j].axes[i]['id'] = data[j].name;
      if (data[j].axes[i]['value'] > maxValue) {
        maxValue = data[j].axes[i]['value'];
      }
    }
  }
  maxValue = max(cfg.maxValue, maxValue);

  const allAxis = data[0].axes.map((i, j) => i.axis), //Names of each axis
    total = allAxis.length, //The number of different axes
    radius = Math.min(cfg.w / 2, cfg.h / 2), //Radius of the outermost circle
    Format = d3.format(cfg.format), //Formatting
    angleSlice = Math.PI * 2 / total; //The width in radians of each "slice"

  //Scale for the radius
  const rScale = d3.scaleLinear()
    .range([0, radius])
    .domain([0, maxValue]);

  /////////////////////////////////////////////////////////
  //////////// Create the container SVG and g /////////////
  /////////////////////////////////////////////////////////
  const parent = d3.select(parent_selector);

  //Remove whatever chart with the same id/class was present before
  parent.select("svg").remove();

  //Initiate the radar chart SVG
  let svg = parent.append("svg")
    .attr("width", cfg.w + cfg.margin.left + cfg.margin.right)
    .attr("height", cfg.h + cfg.margin.top + cfg.margin.bottom)
    .attr("class", "radar");

  //Append a g element
  let g = svg.append("g")
    .attr("transform", "translate(" + (cfg.w / 2 + cfg.margin.left) + "," + (cfg.h / 2 + cfg.margin.top) + ")");

  /////////////////////////////////////////////////////////
  ////////// Glow filter for some extra pizzazz ///////////
  /////////////////////////////////////////////////////////

  //Filter for the outside glow
  let filter = g.append('defs').append('filter').attr('id', 'glow'),
    feGaussianBlur = filter.append('feGaussianBlur').attr('stdDeviation', '2.5').attr('result', 'coloredBlur'),
    feMerge = filter.append('feMerge'),
    feMergeNode_1 = feMerge.append('feMergeNode').attr('in', 'coloredBlur'),
    feMergeNode_2 = feMerge.append('feMergeNode').attr('in', 'SourceGraphic');

  /////////////////////////////////////////////////////////
  /////////////// Draw the Circular grid //////////////////
  /////////////////////////////////////////////////////////

  //Wrapper for the grid & axes
  let axisGrid = g.append("g").attr("class", "axisWrapper");

  //Draw the background circles
  axisGrid.selectAll(".levels")
    .data(d3.range(1, (cfg.levels + 1)).reverse())
    .enter()
    .append("circle")
    .attr("class", "gridCircle")
    .attr("r", d => radius / cfg.levels * d)
    .style("fill", "#CDCDCD")
    .style("stroke", "#CDCDCD")
    .style("fill-opacity", cfg.opacityCircles)
    .style("filter", "url(#glow)");

  //Text indicating at what % each level is
  axisGrid.selectAll(".axisLabel")
    .data(d3.range(1, (cfg.levels + 1)).reverse())
    .enter().append("text")
    .attr("class", "axisLabel")
    .attr("x", 4)
    .attr("y", d => -d * radius / cfg.levels)
    .attr("dy", "0.4em")
    .style("font-size", "10px")
    .attr("fill", "#737373")
    .text(d => Format(maxValue * d / cfg.levels) + cfg.unit);

  /////////////////////////////////////////////////////////
  //////////////////// Draw the axes //////////////////////
  /////////////////////////////////////////////////////////

  //Create the straight lines radiating outward from the center
  var axis = axisGrid.selectAll(".axis")
    .data(allAxis)
    .enter()
    .append("g")
    .attr("class", "axis");
  //Append the lines
  axis.append("line")
    .attr("x1", 0)
    .attr("y1", 0)
    .attr("x2", (d, i) => rScale(maxValue * 1.1) * cos(angleSlice * i - HALF_PI))
    .attr("y2", (d, i) => rScale(maxValue * 1.1) * sin(angleSlice * i - HALF_PI))
    .attr("class", "line")
    .style("stroke", "white")
    .style("stroke-width", "2px");

  //Append the labels at each axis
  axis.append("text")
    .attr("class", "legend")
    .style("font-size", "11px")
    .attr("text-anchor", "middle")
    .attr("dy", "0.35em")
    .attr("x", (d, i) => rScale(maxValue * cfg.labelFactor) * cos(angleSlice * i - HALF_PI))
    .attr("y", (d, i) => rScale(maxValue * cfg.labelFactor) * sin(angleSlice * i - HALF_PI))
    .text(d => d)
    .call(wrap, cfg.wrapWidth);

  /////////////////////////////////////////////////////////
  ///////////// Draw the radar chart blobs ////////////////
  /////////////////////////////////////////////////////////

  //The radial line function
  const radarLine = d3.radialLine()
    .curve(d3.curveLinearClosed)
    .radius(d => rScale(d.value))
    .angle((d, i) => i * angleSlice);

  if (cfg.roundStrokes) {
    radarLine.curve(d3.curveCardinalClosed)
  }

  //Create a wrapper for the blobs
  const blobWrapper = g.selectAll(".radarWrapper")
    .data(data)
    .enter().append("g")
    .attr("class", "radarWrapper");

  //Append the backgrounds
  blobWrapper
    .append("path")
    .attr("class", "radarArea")
    .attr("d", d => radarLine(d.axes))
    .style("fill", (d, i) => cfg.color(i))
    .style("fill-opacity", cfg.opacityArea)
    .on('mouseover', function(d, i) {
      //Dim all blobs
      parent.selectAll(".radarArea")
        .transition().duration(200)
        .style("fill-opacity", 0.1);
      //Bring back the hovered over blob
      d3.select(this)
        .transition().duration(200)
        .style("fill-opacity", 0.7);
    })
    .on('mouseout', () => {
      //Bring back all blobs
      parent.selectAll(".radarArea")
        .transition().duration(200)
        .style("fill-opacity", cfg.opacityArea);
    });

  //Create the outlines
  blobWrapper.append("path")
    .attr("class", "radarStroke")
    .attr("d", function(d, i) {
      return radarLine(d.axes);
    })
    .style("stroke-width", cfg.strokeWidth + "px")
    .style("stroke", (d, i) => cfg.color(i))
    .style("fill", "none")
    .style("filter", "url(#glow)");

  //Append the circles
  blobWrapper.selectAll(".radarCircle")
    .data(d => d.axes)
    .enter()
    .append("circle")
    .attr("class", "radarCircle")
    .attr("r", cfg.dotRadius)
    .attr("cx", (d, i) => rScale(d.value) * cos(angleSlice * i - HALF_PI))
    .attr("cy", (d, i) => rScale(d.value) * sin(angleSlice * i - HALF_PI))
    .style("fill", (d) => cfg.color(d.id))
    .style("fill-opacity", 0.8);

  /////////////////////////////////////////////////////////
  //////// Append invisible circles for tooltip ///////////
  /////////////////////////////////////////////////////////

  //Wrapper for the invisible circles on top
  const blobCircleWrapper = g.selectAll(".radarCircleWrapper")
    .data(data)
    .enter().append("g")
    .attr("class", "radarCircleWrapper");

  //Append a set of invisible circles on top for the mouseover pop-up
  blobCircleWrapper.selectAll(".radarInvisibleCircle")
    .data(d => d.axes)
    .enter().append("circle")
    .attr("class", "radarInvisibleCircle")
    .attr("r", cfg.dotRadius * 1.5)
    .attr("cx", (d, i) => rScale(d.value) * cos(angleSlice * i - HALF_PI))
    .attr("cy", (d, i) => rScale(d.value) * sin(angleSlice * i - HALF_PI))
    .style("fill", "none")
    .style("pointer-events", "all")
    .on("mouseover", function(d, i) {
      tooltip
        .attr('x', this.cx.baseVal.value - 10)
        .attr('y', this.cy.baseVal.value - 10)
        .transition()
        .style('display', 'block')
        .text(Format(d.value) + cfg.unit);
    })
    .on("mouseout", function() {
      tooltip.transition()
        .style('display', 'none').text('');
    });

  const tooltip = g.append("text")
    .attr("class", "tooltip")
    .attr('x', 0)
    .attr('y', 0)
    .style("font-size", "12px")
    .style('display', 'none')
    .attr("text-anchor", "middle")
    .attr("dy", "0.35em");

  if (cfg.legend !== false && typeof cfg.legend === "object") {
    let legendZone = svg.append('g');
    let names = data.map(el => el.name);
    if (cfg.legend.title) {
      let title = legendZone.append("text")
        .attr("class", "title")
        .attr('transform', `translate(${cfg.legend.translateX},${cfg.legend.translateY})`)
        .attr("x", cfg.w - 70)
        .attr("y", 10)
        .attr("font-size", "12px")
        .attr("fill", "#404040")
        .text(cfg.legend.title);
    }
    let legend = legendZone.append("g")
      .attr("class", "legend")
      .attr("height", 100)
      .attr("width", 200)
      .attr('transform', `translate(${cfg.legend.translateX},${cfg.legend.translateY + 20})`);
    // Create rectangles markers
    legend.selectAll('rect')
      .data(names)
      .enter()
      .append("rect")
      .attr("x", cfg.w - 65)
      .attr("y", (d, i) => i * 20)
      .attr("width", 10)
      .attr("height", 10)
      .style("fill", (d, i) => cfg.color(i));
    // Create labels
    legend.selectAll('text')
      .data(names)
      .enter()
      .append("text")
      .attr("x", cfg.w - 52)
      .attr("y", (d, i) => i * 20 + 9)
      .attr("font-size", "11px")
      .attr("fill", "#737373")
      .text(d => d);
  }
  return svg;
}

//////////////////////////////////////////////////////////////
//////////////////////// Set-Up //////////////////////////////
//////////////////////////////////////////////////////////////

var marginRadar = {
    top: 150,
    right: 80,
    bottom: 50,
    left: 80
  },
  widthRadar = Math.min(700, window.innerWidth / 4) - marginRadar.left - marginRadar.right,
  heightRadar = Math.min(widthRadar, window.innerHeight - marginRadar.top - marginRadar.bottom);

//////////////////////////////////////////////////////////////
////////////////////////// Data //////////////////////////////
//////////////////////////////////////////////////////////////


var radarChartOptions = {
  w: 290,
  h: 350,
  margin: marginRadar,
  levels: 6,
  roundStrokes: false,
  color: d3.scaleOrdinal().range(["#AFC52F", "#ff6600"]),
  format: '.0f',
  legend: {
    title: 'Legend',
    translateX: 150,
    translateY: 100
  },
  unit: '%'
};

// Just some example JSON data I found at https://support.oneskyapp.com/hc/en-us/articles/208047697-JSON-sample-files
d3.json('https://support.oneskyapp.com/hc/en-us/article_attachments/202761727/example_2.json', function(fakeData) {
  // Which I then map into your format - you don't need to do this
  const data = Object.entries(fakeData['quiz']).map(([topic, questionObject]) => ({
    name: topic,
    axes: [
      { axis: 'difficulty', value: Object.keys(questionObject).length },
      { axis: 'depth', value: Object.keys(questionObject).length },
      { axis: 'duration', value: Object.keys(questionObject).length },
      { axis: 'delicacy', value: Object.keys(questionObject).length }
    ]
  }));

  // Draw the chart, get a reference the created svg element :
  let svg_radar = RadarChart(".radarChart", data, radarChartOptions);
});
<head>
  <!-- Load d3.js -->
  <script src="https://d3js.org/d3.v4.min.js"></script>
  <script src="https://d3js.org/d3-path.v1.min.js" charset="utf-8"></script>
  <!-- source: https://gist.github.com/mthh/7e17b680b35b83b49f1c22a3613bd89f -->
</head>

<body>
  <table>
    <tr>
      <td style="radarChart">
        <div class="radarChart" style="display: inline-flex;"></div>
      </td>
    </tr>
  </table>
Ruben Helsloot
  • 12,582
  • 6
  • 26
  • 49
  • Thanks Ruben. Indeed, I agree with you regarding v4--the fact is that I was trying to make it work when loading .json. Anyway, your suggestion works with "your" json data, but once I add my data, it doesn't. Anyway, Marjorie has suggested another solution which works very good, and very easy for me. Once again, thank you for your time. – Vanessa Jul 29 '20 at 14:51
1

The solution suggested here works:

https://www.quora.com/How-can-I-load-data-from-a-JSON-file-into-a-variable-in-JavaScript-without-using-Ajax?fbclid=IwAR1ilYFGKBaNsUupYOwIBSVMHBDP24o7j87WP5GzZSrlwIsyLK1riU5JCRQ

It bypasses the constraints of Ajax in a beautifully simple way!

  • move the declaration of your var data into a separate file
  • load this file as a javascript
  • use the variable as you need to!
Marjorie
  • 71
  • 3