d3 javascript series chart

家住魔仙堡 提交于 2019-12-23 06:40:13

问题


I am trying to create this particular d3 application where a series of data can be dynamically displayed like this. Each segment contains two pieces of data.

The first step is to print the circles so there is sufficient space between the series but also the largest circle is always under the smaller circle.

//version 3 -- with correct labels and legend-- http://jsfiddle.net/0ht35rpb/33/

//******version 2 fiddle****** http://jsfiddle.net/1oka61mL/10/

-- How to set the diagonal labels properly - same angles, aligned properly? -- Add legend? -- Mask the bottom pointers in an opposite color then continue the line in a different color?

//******Latest Jsfiddle****** http://jsfiddle.net/0ht35rpb/26/

var width = 600;
var height = 400;
var svg = d3.select('svg').attr("width", width).attr("height", height);

//Count
//Checkins
//Popularity

var data = [{
  "name": "Twitter",
  "items": [{
    "id": 0,
    "label": "Count",
    "value": 200
  }, {
    "id": 1,
    "label": "Checkins",
    "value": 1000
  }, {
    "id": 2,
    "label": "Popularity",
    "value": 30
  }]
}, {
  "name": "Facebook",
  "items": [{
    "id": 0,
    "label": "Count",
    "value": 500
  }, {
    "id": 1,
    "label": "Checkins",
    "value": 300
  }, {
    "id": 2,
    "label": "Popularity",
    "value": 740
  }]
}, {
  "name": "Ebay",
  "items": [{
    "id": 0,
    "label": "Count",
    "value": 4000
  }, {
    "id": 1,
    "label": "Checkins",
    "value": 1000
  }, {
    "id": 2,
    "label": "Popularity",
    "value": 40
  }]
}, {
  "name": "Foursquare",
  "items": [{
    "id": 0,
    "label": "Count",
    "value": 2000
  }, {
    "id": 1,
    "label": "Checkins",
    "value": 3000
  }, {
    "id": 2,
    "label": "Popularity",
    "value": 4500
  }]
}];


var outerRadius = [];
// organise the data. 
// Insert indices and sort items in each series
// keep a running total of max circle size in each series
// for later positioning
var x = 0;
var totalWidth = d3.sum(
  data.map(function(series) {
    series.items.forEach(function(item, i) {
      item.index = i;
    });
    series.items.sort(function(a, b) {
      return b.value - a.value;
    });
    var maxr = Math.sqrt(series.items[0].value);
    outerRadius.push(maxr);
    x += maxr;
    series.xcentre = x;
    x += maxr;
    return maxr * 2;
  })
);

// make scales for position and colour
var scale = d3.scale.linear().domain([0, totalWidth]).range([0, width]);
//var colScale = d3.scale.category10();

function colores_google(n) {
  var colores_g = ["#f7b363", "#448875", "#c12f39", "#2b2d39", "#f8dd2f"];
  return colores_g[n % colores_g.length];
}


// add a group per series, position the group according to the values and position scale  we calculated above
var groups = svg.selectAll("g").data(data);
groups.enter().append("g");
groups.attr("transform", function(d) {
  return ("translate(" + d.xcentre + ",0)");
});


// then add circles per series, biggest first as items are sorted
// colour according to index (the property we inserted previously so we can
// keep track of their original position in the series)
var circles = groups.selectAll("circle").data(function(d) {
  return d.items;
}, function(d) {
  return d.index;
});
circles.enter().append("circle").attr("cy", height / 2).attr("cx", 0);

circles
  .attr("r", function(d) {
    return Math.sqrt(d.value);
  })
  .style("fill", function(d) {
    return colores_google(d.index);
  });



var labelsgroups = svg.selectAll("text").data(data);
labelsgroups.enter().append("text");
labelsgroups
  .attr("y", function(d, i) {
    d.y = 300;
    d.cy = 200;
    return 300;
  })
  .attr("x", function(d) {
    d.x = d.xcentre;
    d.cx = d.xcentre;
    return d.xcentre;
  })
  .text(function(d) {
    return d.name;
  })
  .each(function(d) {
    var bbox = this.getBBox();
    d.sx = d.x - bbox.width / 2 - 2;
    d.ox = d.x + bbox.width / 2 + 2;
    d.sy = d.oy = d.y + 5;
  })
  .attr("text-anchor", "middle");



var pointersgroups = svg.selectAll("path.pointer").data(data);
pointersgroups.enter().append("path");
pointersgroups
  .attr("class", "pointer")
  .attr("marker-end", "url(#circ)");

pointersgroups
  .attr("d", function(d) {
    return "M" + (d.xcentre) + "," + (d.oy - 25) + "L" + (d.xcentre) + "," + (d.sy - 25) + " " + d.xcentre + "," + (d.cy);
  })



function fetchValue(items, label) {
  for (i = 0; i <= items.length; i++) {
    if (items[i].label == label) {
      return items[i].value;
    }
  }
}


function fetchRadius(items, label) {
  for (i = 0; i <= items.length; i++) {
    if (items[i].label == label) {
      return Math.sqrt(items[i].value);
    }
  }
}

/*
var labels1groups = svg.selectAll(".label1").data(data);
labels1groups.enter().append("text");
labels1groups
  .attr("class", "label1")
  .attr("y", function(d, i) {
    d.y = 100;
    d.cy = 100;
    return 100;
  })
  .attr("x", function(d) {
    d.x = d.xcentre;
    d.cx = d.xcentre+50;
    return d.xcentre+50;
  })
  .text(function(d) {
    return fetchValue(d.items, "Count");
  })
  .attr("transform", function(d, i) {
            return "translate(" + (15 * i) + "," + (i * 45) + ") rotate(-45)";

  })
  .each(function(d) {
    var bbox = this.getBBox();
    d.sx = d.x - bbox.width / 2 - 2;
    d.ox = d.x + bbox.width / 2 + 2;
    d.sy = d.oy = d.y ;
  })
  .attr("text-anchor", "left");

*/


var gridSize = 100;

var labels1groups = svg.selectAll(".label2")
  .data(data)
  .enter().append("text")
  .text(function(d) {
    return fetchValue(d.items, "Count");
    //return d; 
  })
  .attr("x", function(d, i) {

    d.x = i * gridSize + 50;
    d.cx = i * gridSize + 50;

    return i * gridSize;
  })
  .attr("y", function(d, i) {
    d.y = 105;
    d.cy = 50;
    return 0;
  })
  .attr("transform", function(d, i) {
    return "translate(" + gridSize / 2 + ", -6)" +
      "rotate(-45 " + ((i + 0.5) * gridSize) + " " + (-6) + ")";
  })
  .each(function(d) {
    var bbox = this.getBBox();
    d.sx = d.x - bbox.width / 2 - 2;
    d.ox = d.x + bbox.width / 2 + 2;
    d.sy = d.oy = d.y;
  })
  .style("text-anchor", "end")
  .attr("class", function(d, i) {
    return ((i >= 8 && i <= 16) ?
      "timeLabel mono axis axis-worktime" :
      "timeLabel mono axis");
  });




var pointers1groups = svg.selectAll("path.pointer1").data(data);
pointers1groups.enter().append("path");
pointers1groups
  .attr("class", "pointer1")
  .attr("marker-end", "url(#circ)");

pointers1groups
  .attr("d", function(d, i) {

    //d.y = outerRadius[i];
    //d.y = d.oy - d.cy;
    //fetchRadius(d.items, "Count");    

    //(d.xcentre+100)
    // + " " + d.cx + "," + d.cy

    //return "M "+ (d.xcentre) +" 25 ,L "+ dist +" 75";

    return "M" + (d.xcentre) + "," + (d.y + d.oy - fetchRadius(d.items, "Count") - 10) + "L" + (d.xcentre + 80) + "," + d.cy;


  })

//Older Jsfiddle http://jsfiddle.net/59bunh8u/51/

var rawData = [{
    "name": "Twitter",
    "items" : [
        {
            "label" : "15 billion",
            "unit" : "per day",
            "value" : 1500
        },
        {
            "label" : "450 checkins",
            "unit" : "per day",
            "value" : 450
        }
    ]
}, 
{               
    "name": "Facebook",
    "items" : [
        {
            "label" : "5 billion",
            "unit" : "per day",
            "value" : 5000
        },
        {
            "label" : "2000 checkins",
            "unit" : "per day",
            "value" : 2000
        }
    ]
}];


$.each(rawData, function(index, value) {
    var total = 0;
    var layerSet = [];
    var ratios = [25, 100];

    $.each(value["items"], function(i, v) {
        total += v["value"];
    });

    value["total"] = total;
});



var w = $this.data("width");
var h = $this.data("height");

var el = $this;

var margin = {
    top: 65,
    right: 90,
    bottom: 5,
    left: 150
};

var svg = d3.select(el[0]).append("svg")
    .attr("class", "series")
    .attr("width", w + margin.left + margin.right)
    .attr("height", h + margin.top + margin.bottom)
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

var defs = svg.append("svg:defs");


$.each(rawData, function(i, v) {
    circleDraw(i, v["items"]);
});



//calculates where each element should be placed
function calculateDistance (d, i, items) {
    var dcx = 0;
    for (var k = 0; k < i; k++) {
      dcx += Math.sqrt(items[k].value);
    }
    return dcx + 10 * i;
}

function getPercentage(value, total) {
    return ((value / total) * 100);
}

function circleDraw(index, data){
    data.sort(function(a, b) {
        return parseFloat(b.value) - parseFloat(a.value);
    });

    var circlelayer = svg.append("g")
      .attr("class", "circlelayer");

    var circle = circlelayer.selectAll("circle")
      .data(data);

    circle.enter().append("circle")
        .attr("class", function(d, i) {
            if (i == 0) {
              return "blue";
            }
            return "gold";
        })
        .attr("cy", 60)
        .attr("cx", function(d, i) {
            return calculateDistance(d, index, data);
        })
        .attr("r", function(d, i) {
            return Math.sqrt(d.value);
        });

    circle.exit().remove();
}

回答1:


Here is how you could drawn the lines:

groups.append("line")
  .attr("class", "pointer1")
  .attr("marker-end", "url(#circ)")
  .attr("x1", 0)
  .attr("y1", height / 2)
  .attr("x2", 0)
  .attr("y2",height - 10);

  var linesG = svg.selectAll(".slines").data(data)
                            .enter().append("g")
              .attr("transform", function(d) {
                return ("translate(" + d.xcentre + "," + height/2 +") rotate(-45)");
              });

  linesG.append("line")
    .attr("class", "pointer1")
    .attr("marker-end", "url(#circ)")
    .attr("x1", 0)
    .attr("y1", 0)
    .attr("x2", 150)
    .attr("y2", 0);

  linesG.append("text")
        .text(function(d) {
        return fetchValue(d.items, "Count");
      })
      .attr("text-anchor", "end")
     .attr("y", -5)
     .attr("x", 150);

Updated jsfiddle




回答2:


I've managed to get the diagonal markers and pointers in alignment, pointing to the correct circle colors to represent that set. I am keen to fine tune this chart and have more control over the padding and chart width/height parameters. The chart looks stable but would be keen to test it with different values and sized data sets.

/LATEST/ http://jsfiddle.net/0ht35rpb/33/

var width = 760;
var height = 400;

var svg = d3.select('#serieschart')
  .append("svg:svg")
  .attr("width", width)
  .attr("height", height);

//Count
//Checkins
//Popularity

var data = [{
  "name": "Twitter",
  "items": [{
    "id": 0,
    "label": "Count",
    "value": 200
  }, {
    "id": 1,
    "label": "Checkins",
    "value": 1000
  }, {
    "id": 2,
    "label": "Popularity",
    "value": 30
  }]
}, {
  "name": "Facebook",
  "items": [{
    "id": 0,
    "label": "Count",
    "value": 500
  }, {
    "id": 1,
    "label": "Checkins",
    "value": 300
  }, {
    "id": 2,
    "label": "Popularity",
    "value": 740
  }]
}, {
  "name": "Ebay",
  "items": [{
    "id": 0,
    "label": "Count",
    "value": 4000
  }, {
    "id": 1,
    "label": "Checkins",
    "value": 1000
  }, {
    "id": 2,
    "label": "Popularity",
    "value": 40
  }]
}, {
  "name": "Foursquare",
  "items": [{
    "id": 0,
    "label": "Count",
    "value": 2000
  }, {
    "id": 1,
    "label": "Checkins",
    "value": 3000
  }, {
    "id": 2,
    "label": "Popularity",
    "value": 4500
  }]
}];



var legend_group = svg.append("g")
  .attr("class", "legend")
  .attr("width", 80)
  .attr("height", 100)
  .append("svg:g")
  .attr("class", "legendsection")
  .attr("transform", "translate(0,30)");

var legend = legend_group.selectAll("circle").data(data[0].items);

legend.enter().append("circle")
  .attr("cx", 70)
  .attr("cy", function(d, i) {
    return 15 * i;
  })
  .attr("r", 7)
  .attr("width", 18)
  .attr("height", 18)
  .style("fill", function(d, i) {
    return colores_google(i);
  });

legend.exit().remove();





var legendtext = legend_group.selectAll("text").data(data[0].items);

legendtext.enter().append("text")
  .attr("class", "labels")
  .attr("dy", function(d, i) {
    return 15 * i;
  })
  .attr("text-anchor", function(d) {
    return "start";
  })
  .text(function(d) {
    return d.label;
  });

legendtext.exit().remove();

var m = [80, 20, 20, 10];
var w =+ width - m[0];
var h =+ height - m[1];

var chart = svg.append("g")
  .attr("class", "serieschart")
  .attr("width", w)
  .attr("height", h);

var outerRadius = [];
// organise the data. 
// Insert indices and sort items in each series
// keep a running total of max circle size in each series
// for later positioning
var x = 0;
var totalWidth = d3.sum(
  data.map(function(series) {
    series.items.forEach(function(item, i) {
      item.index = i;
    });
    series.items.sort(function(a, b) {
      return b.value - a.value;
    });
    var maxr = Math.sqrt(series.items[0].value);
    outerRadius.push(maxr);
    x += maxr;
    series.xcentre = x;
    x += maxr;
    return maxr * 2;
  })
);

// make scales for position and colour
var scale = d3.scale.linear().domain([0, totalWidth]).range([0, w]);
//var colScale = d3.scale.category10();

function colores_google(n) {
  var colores_g = ["#f7b363", "#448875", "#c12f39", "#2b2d39", "#f8dd2f"];
  return colores_g[n % colores_g.length];
}

function fetchValue(items, label) {
  for (i = 0; i <= items.length; i++) {
    if (items[i].label == label) {
      return items[i].value;
    }
  }
}

function fetchRadius(items, label) {
  for (i = 0; i <= items.length; i++) {
    if (items[i].label == label) {
      return Math.sqrt(items[i].value);
    }
  }
}


// add a group per series, position the group according to the values and position scale  we calculated above
var groups = chart.selectAll("g.seriesGroup").data(data);
var newGroups = groups.enter().append("g").attr("class", "seriesGroup");
newGroups.append("text")
  .attr("class", "seriesName")
  .attr("text-anchor", "middle");
newGroups.append("line")
  .attr("class", "seriesName")
  .attr("y1", h - 40)
  .attr("y2", h / 2);
newGroups.append("text")
  .attr("class", "datumValue")
  .attr("y", 10)
  //.attr("transform", "rotate(-45)")
;
newGroups.append("g").attr("class", "circleGroup");
newGroups.append("g").attr("class", "datumLine")
  .append("line")
  .attr("class", "datumValue")
  .attr("y2", 40);

var focus = "Count";
groups.attr("transform", function(d) {
  return "translate(" + scale(d.xcentre) + ",0)";
});

groups.select("text.seriesName")
  .text(function(d) {
    return d.name;
  })
  .attr("y", h - 20);

groups.select("text.datumValue")
  .text(function(d) {
    return fetchValue(d.items, focus);
  })
  .attr("transform", function(d) {
    return "translate(" + ((h / 2) - 20 - scale(fetchRadius(d.items, focus))) + ",20) rotate(-45)";
  });

groups.select("line.datumValue")
  .attr("y1", function(d) {
    return (h / 2) - scale(fetchRadius(d.items, focus));
  })
  .attr("x2", function(d) {
    return (h / 2) - scale(fetchRadius(d.items, focus) + 20);
  });

// then add circles per series, biggest first as items are sorted
// colour according to index (the property we inserted previously so we can
// keep track of their original position in the series)
var circles = groups
  .select(".circleGroup")
  .selectAll("circle").data(function(d) {
    return d.items;
  }, function(d) {
    return d.index;
  });
circles.enter().append("circle").attr("cy", h / 2).attr("cx", 0);

circles
  .attr("r", function(d) {
    return scale(Math.sqrt(d.value));
  })
  .style("fill", function(d) {
    return colores_google(d.index);
  });


来源:https://stackoverflow.com/questions/43059639/d3-javascript-series-chart

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!