Реализация графика аккордов D3.js с заменой дуг в виде сложенной радиальной гистограммы - PullRequest
0 голосов
/ 07 ноября 2018

Я в основном программист Python, работающий в области биоинформатики. Я новичок в D3.js и очень хорошо разбираюсь в своем JavaScript. Первоначально я пытался решить свою проблему в Python, используя пакеты Holoviews и Bokeh, но нашел, что в настоящее время они ограничивают то, чего я пытаюсь достичь.

Я пытаюсь реализовать аккордовую диаграмму, но вместо дуг я добавлю радиальную линейчатую диаграмму. Я написал несколько примеров из Bl.ocks.org, которые я поместил в код и ссылки на jsfiddle ниже. Я взломал это несколько недель назад. Первоначально я попытался изменить размеры дуг на графике аккордов, что я смог сделать, но безуспешно с добавлением дополнительных сложенных дуг сверху. Затем я наткнулся на радиальный столбчатый график, который работал бы намного лучше. Цель состоит в том, чтобы получить аккордовый график, похожий на рисунок 2А из этой статьи: https://www.sciencedirect.com/science/article/pii/S1357272518301377,, но намного лучше выглядящий и интерактивный при наведении мыши. В приведенном мною примере радиального столбчатого столбца есть только 2 значения для каждого индекса. Окончательный код должен работать с несколькими значениями, возможно, что-то вроде этого: https://bl.ocks.org/mbostock/8d2112a115ad95f4a6848001389182fb или это: https://bl.ocks.org/mbostock/6fead6d1378d6df5ae77bb6a719afcb2.

Буду очень признателен за любую помощь в том, как бы я добавил сложенный линейчатый график поверх аккордового графика вместо дуг. Спасибо.

Аккордовый сюжет:

var visual = document.getElementById("visual");

// shared ad blocks between 7-Up, A&W, Coke, Dr Pepper, Pepsi brands
var matrix = [
  [5, 0, 1, 1, 3],
  [0, 0, 0, 1, 0],
  [1, 0, 1, 1, 0],
  [1, 1, 1, 2, 0],
  [3, 0, 0, 0, 3]
];

var array = ["7-Up", "A&W", "Coke", "Dr Pepper", "Pepsi"];

var rotation = -0.7;

var chord_options = {
  "gnames": array,
  "rotation": rotation,
  "colors": ["#034e7b", "#feb24c", "#b10026", "#238443", "#fdbb84", "#ffffb2", "#fed976"]
};

//function Chord(container, options, matrix) {

// initialize the chord configuration variables
var config = {
  width: 640,
  height: 560,
  rotation: 0,
  textgap: 26,
  colors: ["#7fc97f", "#beaed4", "#fdc086", "#ffff99", "#386cb0", "#f0027f", "#bf5b17", "#666666"]
};

// add options to the chord configuration object
if (chord_options) {
  extend(config, chord_options);
}

// set chord visualization variables from the configuration object
var offset = Math.PI * config.rotation,
  width = config.width,
  height = config.height,
  textgap = config.textgap,
  colors = config.colors;

// set viewBox and aspect ratio to enable a resize of the visual dimensions 
var viewBoxDimensions = "0 0 " + width + " " + height,
  aspect = width / height;

if (config.gnames) {
  gnames = config.gnames;
} else {
  // make a list of names
  gnames = [];
  for (var i = 97; i < matrix.length; i++) {
    gnames.push(String.fromCharCode(i));
  }
}

// start the d3 magic
var chord = d3.layout.chord()
  .padding(0.05)
  .sortSubgroups(d3.descending)
  .matrix(matrix);

//var innerRadius = Math.min.apply(null, scaled_size_data) - 5//(width, height) * 0.31;

var innerRadius = Math.min(width, height) * 0.31;
var outerRadius = innerRadius * 1.1;

var fill = d3.scale.ordinal()
  .domain(d3.range(matrix.length - 1))
  .range(colors);

var svg = d3.select("body").append("svg")
  .attr("id", "visual")
  .attr("viewBox", viewBoxDimensions)
  .attr("preserveAspectRatio", "xMinYMid") // add viewBox and preserveAspectRatio
  .attr("width", width)
  .attr("height", height)
  .append("g")
  .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

var g = svg.selectAll("g.group")
  .data(chord.groups)
  .enter().append("svg:g")
  .attr("class", "group");

var arc = d3.svg.arc()
  .innerRadius(innerRadius)
  .outerRadius(outerRadius)
  .startAngle(startAngle)
  .endAngle(endAngle)

g.append("svg:path")
  .style("fill", function(d) {
    return fill(d.index);
  })
  .style("stroke", function(d) {
    return fill(d.index);
  })
  .attr("id", function(d, i) {
    return "group" + d.index;
  })
  .attr("d", arc)
  .on("mouseover", fade(0.1))
  .on("mouseout", fade(1));

function layers(d, i){
    return d3.select(this)
  //d.outerRadius = outerRadius * scaled_size_data[0][i]
  .attr("transform", "scale(1." + i + ")");
  //.attr("d", 
  //    d.outerRadius = outerRadius * scaled_size_data[0][i];
  //})
}

g.append("svg:text")
  .each(function(d) {
    d.angle = ((d.startAngle + d.endAngle) / 2) + offset;
  })
  .attr("dy", ".35em")
  .attr("text-anchor", function(d) {
    return d.angle > Math.PI ? "end" : null;
  })
  .attr("transform", function(d) {
    return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")" + "translate(" + (outerRadius + textgap) + ")" + (d.angle > Math.PI ? "rotate(180)" : "");
  })
  .text(function(d) {
    return gnames[d.index];
  });

svg.append("g")
  .attr("class", "chord")
  .selectAll("path")
  .data(chord.chords)
  .enter().append("path")
  .attr("d", d3.svg.chord().radius(innerRadius).startAngle(startAngle).endAngle(endAngle))
  .style("fill", function(d) {
    return fill(d.source.index);
  })
  .style("opacity", 1)
  .append("svg:title")
  .text(function(d) {
    return d.source.value + "  " + gnames[d.source.index] + " shared with " + gnames[d.target.index];
  });

function startAngle(d) {
  return d.startAngle + offset;
}

function endAngle(d) {
  return d.endAngle + offset;
}

function extend(a, b) {
  for (var i in b) {
    a[i] = b[i];
  }
}

// Returns an event handler for fading a given chord group.
function fade(opacity) {
  return function(g, i) {
    svg.selectAll(".chord path")
      .filter(function(d) {
        return d.source.index != i && d.target.index != i;
      })
      .transition()
      .style("opacity", opacity);
  };
}

window.onresize = function() {
  var targetWidth = (window.innerWidth < width) ? window.innerWidth : width;

  var svg = d3.select("#visual")
    .attr("width", targetWidth)
    .attr("height", targetWidth / aspect);
}

Радиальный столбчатый график:

var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height"),
    innerRadius = 180,
    outerRadius = Math.min(width, height) / 2.5,
    g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");


  var xScaleOffset = Math.PI * 75 / 180;
  var x = d3.scaleBand()
    .range([xScaleOffset, 2 * Math.PI + xScaleOffset])
    .align(0);

  var y = d3.scaleLinear()
    .range([innerRadius, outerRadius]);

  var z = d3.scaleOrdinal()
    .range(["#a1d76a", "#91bfdb"]);

  var zClasses = ['внутренняя сторона', 'внешняя сторона'];

  var data = d3.csvParse(d3.select("pre#data").text());

  var keys = data.columns.slice(1);
  var meanAccidents = d3.mean(data, function(d) {
    return d3.sum(keys, function(key) {
      return d[key];
    });
  })

  x.domain(data.map(function(d) {
    return d.km;
  }));
  y.domain([0, d3.max(data, function(d) {
    return (d.left_lane + d.right_lane);
  })]);
  z.domain(data.columns.slice(1));

  // Accidents
  g.append('g')
    .selectAll("g")
    .data(d3.stack().keys(data.columns.slice(1))(data))
    .enter().append("g")
    .attr("fill", function(d) {
      return z(d.key);
    })
    .selectAll("path")
    .data(function(d) {
      return d;
    })
    .enter().append("path")
    .attr("d", d3.arc()
      .innerRadius(function(d) {
        return y(d[0]);
      })
      .outerRadius(function(d) {
        return y(d[1]);
      })
      .startAngle(function(d) {
        return x(d.data.km);
      })
      .endAngle(function(d) {
        return x(d.data.km) + x.bandwidth();
      })
      .padAngle(0.01)
      .padRadius(innerRadius));

  //yAxis and Mean

  var yAxis = g.append("g")
    .attr("text-anchor", "middle");

  var yTicksValues = d3.ticks(0, 40, 4);

  console.log('Среднее: ', meanAccidents);

  // Mean value line
  var yMeanTick = yAxis
    .append("g")
    .datum([meanAccidents]);

  yMeanTick.append("circle")
    .attr("fill", "none")
    .attr("stroke", "#C0625E")
    .attr("stroke-dasharray", "5 3")
    .attr("r", y);

  var yTick = yAxis
    .selectAll("g")
    .data(yTicksValues)
    .enter().append("g");

  yTick.append("circle")
    .attr("fill", "none")
    .attr("stroke", "#ccdcea")
    .attr("r", y);

  yTick.append("text")
    .attr("y", function(d) {
      return -y(d);
    })
    .attr("dy", "0.35em")
    .attr("fill", "none")
    .attr("stroke", "#fff")
    .attr("stroke-width", 5)
    .text(y.tickFormat(5, "s"));

  yTick.append("text")
    .attr("y", function(d) {
      return -y(d);
    })
    .attr("dy", "0.35em")
    .text(y.tickFormat(5, "s"));

  yAxis.append("text")
    .attr("y", function(d) {
      return -y(yTicksValues.pop());
    })
    .attr("dy", "-2em")
    .text("МКАД, аварийность");

  // Labels for xAxis

  var label = g.append("g")
    .selectAll("g")
    .data(data)
    .enter().append("g")
    .attr("text-anchor", "middle")
    .attr("transform", function(d) {
      return "rotate(" + ((x(d.km) + x.bandwidth() / 2) * 180 / Math.PI - 90) + ")translate(" + innerRadius + ",0)";
    });

  label.append("line")
    .attr("x2", function(d) {
      return (((d.km % 5) == 0) | (d.km == '1')) ? -7 : -4
    })
    .attr("stroke", "#000");

  label.append("text")
    .attr("transform", function(d) {
      return (x(d.km) + x.bandwidth() / 2 + Math.PI / 2) % (2 * Math.PI) < Math.PI ? "rotate(90)translate(0,16)" : "rotate(-90)translate(0,-9)";
    })
    .text(function(d) {
      var xlabel = (((d.km % 5) == 0) | (d.km == '1')) ? d.km : '';
      return xlabel;
    });

  // Legend
  var legend = g.append("g")
    .selectAll("g")
    .data(zClasses)
    .enter().append("g")
    .attr("transform", function(d, i) {
      return "translate(-50," + (i - (zClasses.length - 1) / 2) * 25 + ")";
    });

  legend.append("circle")
    .attr("r", 8)
    .attr("fill", z);

  legend.append("text")
    .attr("x", 15)
    .attr("y", 0)
    .attr("dy", "0.35em")
    .text(function(d) {
      return d;
    });

http://jsfiddle.net/transcend2030/snyp297a/

https://jsfiddle.net/transcend2030/0u8Le1do/

...