Реализация графика аккордов D3.js с заменой дуг в виде столбчатой радиальной диаграммы
Я в основном программист 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;
});