【问题标题】:Interactive spider or radar chart using d3使用 d3 的交互式蜘蛛图或雷达图
【发布时间】:2014-06-20 12:45:08
【问题描述】:

我正在寻找 HTML/javascript 的蜘蛛图/雷达图,它也是交互式的。我希望用户移动所有端点并存储最终结果。

我已经搜索了一段时间,虽然我发现了一些不错的图表组件,它们都是静态的,只能使用代码进行更新。

【问题讨论】:

  • 我不知道有什么东西可以开箱即用。但是,就目前而言,这对 SO 来说不是一个好问题。如果遇到任何特定问题,我建议您尝试编写自己的解决方案并发布更具体的问题。另外,欢迎来到 SO! :)
  • 我希望*的一些专家可能知道一些我找不到的项目。如果没有,我必须自己尝试一下
  • 不幸的是,SO 不是这样的列表存储库的好地方。这些列表通常会变得固执己见并且链接失效。

标签: javascript html d3.js radar-chart


【解决方案1】:

看看this

这是使用这个alangrafu的代码可以实现的:

该示例在您描述的意义上确实不是交互式的,但它在其他方面是交互式的,没有什么能阻止您实现所需的交互性,以示例中的代码作为一个很好的起点。

【讨论】:

    【解决方案2】:

    交互式 D3 雷达图示例:

    index.html

    <!DOCTYPE html>
    <html>
      <head>
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
      <title>Radar chart</title>
        <script src="http://d3js.org/d3.v3.min.js"></script>
        <script src="RadarChart.js"></script>
        <style>
            body {
              overflow: hidden;
              margin: 0;
              font-size: 14px;
              font-family: "Helvetica Neue", Helvetica;
            }
    
            #chart {
              position: absolute;
              top: 50px;
              left: 100px;
            }   
        </style>
      </head>
      <body>
        <div id="body">
          <div id="chart"></div>
        </div>
    
        <script type="text/javascript" src="script.js"></script>
    
      </body>
    </html>
    

    RadarChart.js

    var RadarChart = {
      draw: function(id, d, options){
      var cfg = {
         radius: 5,
         w: 600,
         h: 600,
         factor: 1,
         factorLegend: .85,
         levels: 3,
         maxValue: 0,
         radians: 2 * Math.PI,
         opacityArea: 0.5,
         ToRight: 5,
         TranslateX: 80,
         TranslateY: 30,
         ExtraWidthX: 100,
         ExtraWidthY: 100,
         color: d3.scale.category10()
        };
    
        if('undefined' !== typeof options){
          for(var i in options){
            if('undefined' !== typeof options[i]){
              cfg[i] = options[i];
            }
          }
        }
        cfg.maxValue = Math.max(cfg.maxValue, d3.max(d, function(i){return d3.max(i.map(function(o){return o.value;}))}));
        var allAxis = (d[0].map(function(i, j){return i.axis}));
        var total = allAxis.length;
        var radius = cfg.factor*Math.min(cfg.w/2, cfg.h/2);
        var Format = d3.format('%');
        d3.select(id).select("svg").remove();
    
        var g = d3.select(id)
                .append("svg")
                .attr("width", cfg.w+cfg.ExtraWidthX)
                .attr("height", cfg.h+cfg.ExtraWidthY)
                .append("g")
                .attr("transform", "translate(" + cfg.TranslateX + "," + cfg.TranslateY + ")");
                ;
    
        var tooltip;
    
        //Circular segments
        for(var j=0; j<cfg.levels-1; j++){
          var levelFactor = cfg.factor*radius*((j+1)/cfg.levels);
          g.selectAll(".levels")
           .data(allAxis)
           .enter()
           .append("svg:line")
           .attr("x1", function(d, i){return levelFactor*(1-cfg.factor*Math.sin(i*cfg.radians/total));})
           .attr("y1", function(d, i){return levelFactor*(1-cfg.factor*Math.cos(i*cfg.radians/total));})
           .attr("x2", function(d, i){return levelFactor*(1-cfg.factor*Math.sin((i+1)*cfg.radians/total));})
           .attr("y2", function(d, i){return levelFactor*(1-cfg.factor*Math.cos((i+1)*cfg.radians/total));})
           .attr("class", "line")
           .style("stroke", "grey")
           .style("stroke-opacity", "0.75")
           .style("stroke-width", "0.3px")
           .attr("transform", "translate(" + (cfg.w/2-levelFactor) + ", " + (cfg.h/2-levelFactor) + ")");
        }
    
        //Text indicating at what % each level is
        for(var j=0; j<cfg.levels; j++){
          var levelFactor = cfg.factor*radius*((j+1)/cfg.levels);
          g.selectAll(".levels")
           .data([1]) //dummy data
           .enter()
           .append("svg:text")
           .attr("x", function(d){return levelFactor*(1-cfg.factor*Math.sin(0));})
           .attr("y", function(d){return levelFactor*(1-cfg.factor*Math.cos(0));})
           .attr("class", "legend")
           .style("font-family", "sans-serif")
           .style("font-size", "10px")
           .attr("transform", "translate(" + (cfg.w/2-levelFactor + cfg.ToRight) + ", " + (cfg.h/2-levelFactor) + ")")
           .attr("fill", "#737373")
           .text(Format((j+1)*cfg.maxValue/cfg.levels));
        }
    
        series = 0;
    
        var axis = g.selectAll(".axis")
                .data(allAxis)
                .enter()
                .append("g")
                .attr("class", "axis");
    
        axis.append("line")
            .attr("x1", cfg.w/2)
            .attr("y1", cfg.h/2)
            .attr("x2", function(d, i){return cfg.w/2*(1-cfg.factor*Math.sin(i*cfg.radians/total));})
            .attr("y2", function(d, i){return cfg.h/2*(1-cfg.factor*Math.cos(i*cfg.radians/total));})
            .attr("class", "line")
            .style("stroke", "grey")
            .style("stroke-width", "1px");
    
        axis.append("text")
            .attr("class", "legend")
            .text(function(d){return d})
            .style("font-family", "sans-serif")
            .style("font-size", "11px")
            .attr("text-anchor", "middle")
            .attr("dy", "1.5em")
            .attr("transform", function(d, i){return "translate(0, -10)"})
            .attr("x", function(d, i){return cfg.w/2*(1-cfg.factorLegend*Math.sin(i*cfg.radians/total))-60*Math.sin(i*cfg.radians/total);})
            .attr("y", function(d, i){return cfg.h/2*(1-Math.cos(i*cfg.radians/total))-20*Math.cos(i*cfg.radians/total);});
    
    
        d.forEach(function(y, x){
          dataValues = [];
          g.selectAll(".nodes")
            .data(y, function(j, i){
              dataValues.push([
                cfg.w/2*(1-(parseFloat(Math.max(j.value, 0))/cfg.maxValue)*cfg.factor*Math.sin(i*cfg.radians/total)), 
                cfg.h/2*(1-(parseFloat(Math.max(j.value, 0))/cfg.maxValue)*cfg.factor*Math.cos(i*cfg.radians/total))
              ]);
            });
          dataValues.push(dataValues[0]);
          g.selectAll(".area")
                         .data([dataValues])
                         .enter()
                         .append("polygon")
                         .attr("class", "radar-chart-serie"+series)
                         .style("stroke-width", "2px")
                         .style("stroke", cfg.color(series))
                         .attr("points",function(d) {
                             var str="";
                             for(var pti=0;pti<d.length;pti++){
                                 str=str+d[pti][0]+","+d[pti][2]+" ";
                             }
                             return str;
                          })
                         .style("fill", function(j, i){return cfg.color(series)})
                         .style("fill-opacity", cfg.opacityArea)
                         .on('mouseover', function (d){
                                            z = "polygon."+d3.select(this).attr("class");
                                            g.selectAll("polygon")
                                             .transition(200)
                                             .style("fill-opacity", 0.1); 
                                            g.selectAll(z)
                                             .transition(200)
                                             .style("fill-opacity", .7);
                                          })
                         .on('mouseout', function(){
                                            g.selectAll("polygon")
                                             .transition(200)
                                             .style("fill-opacity", cfg.opacityArea);
                         });
          series++;
        });
        series=0;
    
    
        d.forEach(function(y, x){
          g.selectAll(".nodes")
            .data(y).enter()
            .append("svg:circle")
            .attr("class", "radar-chart-serie"+series)
            .attr('r', cfg.radius)
            .attr("alt", function(j){return Math.max(j.value, 0)})
            .attr("cx", function(j, i){
              dataValues.push([
                cfg.w/2*(1-(parseFloat(Math.max(j.value, 0))/cfg.maxValue)*cfg.factor*Math.sin(i*cfg.radians/total)), 
                cfg.h/2*(1-(parseFloat(Math.max(j.value, 0))/cfg.maxValue)*cfg.factor*Math.cos(i*cfg.radians/total))
            ]);
            return cfg.w/2*(1-(Math.max(j.value, 0)/cfg.maxValue)*cfg.factor*Math.sin(i*cfg.radians/total));
            })
            .attr("cy", function(j, i){
              return cfg.h/2*(1-(Math.max(j.value, 0)/cfg.maxValue)*cfg.factor*Math.cos(i*cfg.radians/total));
            })
            .attr("data-id", function(j){return j.axis})
            .style("fill", cfg.color(series)).style("fill-opacity", .9)
            .on('mouseover', function (d){
                        newX =  parseFloat(d3.select(this).attr('cx')) - 10;
                        newY =  parseFloat(d3.select(this).attr('cy')) - 5;
    
                        tooltip
                            .attr('x', newX)
                            .attr('y', newY)
                            .text(Format(d.value))
                            .transition(200)
                            .style('opacity', 1);
    
                        z = "polygon."+d3.select(this).attr("class");
                        g.selectAll("polygon")
                            .transition(200)
                            .style("fill-opacity", 0.1); 
                        g.selectAll(z)
                            .transition(200)
                            .style("fill-opacity", .7);
                      })
            .on('mouseout', function(){
                        tooltip
                            .transition(200)
                            .style('opacity', 0);
                        g.selectAll("polygon")
                            .transition(200)
                            .style("fill-opacity", cfg.opacityArea);
                      })
            .append("svg:title")
            .text(function(j){return Math.max(j.value, 0)});
    
          series++;
        });
        //Tooltip
        tooltip = g.append('text')
                   .style('opacity', 0)
                   .style('font-family', 'sans-serif')
                   .style('font-size', '13px');
      }
    };
    

    Script.js

    var w = 500,
        h = 500;
    
    var colorscale = d3.scale.category10();
    
    //Legend titles
    var LegendOptions = ['Smartphone','Tablet'];
    
    //Data
    var d = [
              [
                {axis:"Email",value:0.59},
                {axis:"Social Networks",value:0.56},
                {axis:"Internet Banking",value:0.42},
                {axis:"News Sportsites",value:0.34},
                {axis:"Search Engine",value:0.48},
                {axis:"View Shopping sites",value:0.14},
                {axis:"Paying Online",value:0.11},
                {axis:"Buy Online",value:0.05},
                {axis:"Stream Music",value:0.07},
                {axis:"Online Gaming",value:0.12},
                {axis:"Navigation",value:0.27},
                {axis:"App connected to TV program",value:0.03},
                {axis:"Offline Gaming",value:0.12},
                {axis:"Photo Video",value:0.4},
                {axis:"Reading",value:0.03},
                {axis:"Listen Music",value:0.22},
                {axis:"Watch TV",value:0.03},
                {axis:"TV Movies Streaming",value:0.03},
                {axis:"Listen Radio",value:0.07},
                {axis:"Sending Money",value:0.18},
                {axis:"Other",value:0.07},
                {axis:"Use less Once week",value:0.08}
              ],[
                {axis:"Email",value:0.48},
                {axis:"Social Networks",value:0.41},
                {axis:"Internet Banking",value:0.27},
                {axis:"News Sportsites",value:0.28},
                {axis:"Search Engine",value:0.46},
                {axis:"View Shopping sites",value:0.29},
                {axis:"Paying Online",value:0.11},
                {axis:"Buy Online",value:0.14},
                {axis:"Stream Music",value:0.05},
                {axis:"Online Gaming",value:0.19},
                {axis:"Navigation",value:0.14},
                {axis:"App connected to TV program",value:0.06},
                {axis:"Offline Gaming",value:0.24},
                {axis:"Photo Video",value:0.17},
                {axis:"Reading",value:0.15},
                {axis:"Listen Music",value:0.12},
                {axis:"Watch TV",value:0.1},
                {axis:"TV Movies Streaming",value:0.14},
                {axis:"Listen Radio",value:0.06},
                {axis:"Sending Money",value:0.16},
                {axis:"Other",value:0.07},
                {axis:"Use less Once week",value:0.17}
              ]
            ];
    
    //Options for the Radar chart, other than default
    var mycfg = {
      w: w,
      h: h,
      maxValue: 0.6,
      levels: 6,
      ExtraWidthX: 300
    }
    
    //Call function to draw the Radar chart
    //Will expect that data is in %'s
    RadarChart.draw("#chart", d, mycfg);
    
    ////////////////////////////////////////////
    /////////// Initiate legend ////////////////
    ////////////////////////////////////////////
    
    var svg = d3.select('#body')
        .selectAll('svg')
        .append('svg')
        .attr("width", w+300)
        .attr("height", h)
    
    //Create the title for the legend
    var text = svg.append("text")
        .attr("class", "title")
        .attr('transform', 'translate(90,0)') 
        .attr("x", w - 70)
        .attr("y", 10)
        .attr("font-size", "12px")
        .attr("fill", "#404040")
        .text("What % of owners use a specific service in a week");
    
    //Initiate Legend   
    var legend = svg.append("g")
        .attr("class", "legend")
        .attr("height", 100)
        .attr("width", 200)
        .attr('transform', 'translate(90,20)') 
        ;
        //Create colour squares
        legend.selectAll('rect')
          .data(LegendOptions)
          .enter()
          .append("rect")
          .attr("x", w - 65)
          .attr("y", function(d, i){ return i * 20;})
          .attr("width", 10)
          .attr("height", 10)
          .style("fill", function(d, i){ return colorscale(i);})
          ;
        //Create text next to squares
        legend.selectAll('text')
          .data(LegendOptions)
          .enter()
          .append("text")
          .attr("x", w - 52)
          .attr("y", function(d, i){ return i * 20 + 9;})
          .attr("font-size", "11px")
          .attr("fill", "#737373")
          .text(function(d) { return d; })
          ; 
    

    实时示例:http://bl.ocks.org/nbremer/6506614

    【讨论】:

    • 有没有简单的方法来支持用户可以移动点?
    【解决方案3】:

    还在寻找?在 github 上查看这个项目,我认为这正是您正在寻找的:

    https://github.com/jmstriegel/jquery.spidergraph

    演示:http://www.jqueryrain.com/?jhRGLHlE

    我自己找了这么久的图书馆,偶然发现了你的帖子。您是否也找到了另一种解决方案?

    【讨论】:

    • 这是唯一真正互动的答案,在移动点的意义上,但它只是蜘蛛图。雷达图有吗?
    • 仅供参考的链接不再对演示有效并且是垃圾邮件