function make_propensity_matrix({
num_clusters = 3,
target,
width,
height,
mode = 'random',
title = 'SBM cluster connection propensities',
title_font_size = 20,
}){
const margin = {top: 50, sides: 50};
const count_range = [0, 100];
const h = height - margin.top - margin.sides;
const w = width - 2*margin.sides;
const edge_length = Math.min(w, h);
const title_color = d3.hsl("#252525");
const secondary_text_color = title_color.brighter();
const legend_r = margin.sides/4;
const svg = d3.select(target).html('');
const chart = svg.node();
const g = svg.append('g')
.attr("transform", `translate(${margin.sides + w/2 - edge_length/2}, ${margin.top}) `);
const props = random_edge_propensities(num_clusters, () => gen_discrete_unif(...count_range), mode);
chart.value = props;
const clusters = unique([...props.map(d => d.clust_a), ...props.map(d => d.clust_b) ]);
const cell_pos = d3.scaleBand()
.padding(0.05)
.domain(clusters)
.rangeRound([0, edge_length]);
const cell_width = cell_pos.bandwidth();
const cell_color = d3.scaleLinear()
.domain(count_range)
.range(['steelblue', 'orangered']);
// Start drawing things
const cells = g.selectAll('g.cells')
.data(props)
.join('g')
.attr("transform", d => `translate(${cell_pos(d.clust_a)},${cell_pos(d.clust_b)})`);
cells.append('rect')
.attr('width', cell_width)
.attr('height', cell_width)
.attr('stroke', 'black')
.attr('stroke-width', 0)
.attr('rx', 5)
.attr('ry', 5)
.attr('fill', d => cell_color(d.avg_edges));
cells.append('text')
.text(d => d.avg_edges)
.attr('x', cell_width/2)
.attr('y', cell_width/2)
.attr('fill', 'white')
.attr('font-size', '1.5rem')
.attr('text-anchor', 'middle')
.attr('dominant-baseline', 'middle');
// Draw axes
const x_axis = g.append('g')
.attr("transform", `translate(0, ${edge_length + legend_r})`);
const y_axis = g.append('g')
.attr("transform", `translate(${-legend_r}, 0)`);
// Draw the points colored by their cluster id
x_axis.selectAll('circle.legend_point')
.data(clusters)
.join('circle')
.attr('class', 'legend_point')
.attr('cx', d => cell_pos(d) + cell_width/2);
y_axis.selectAll('circle.legend_point')
.data(clusters)
.join('circle')
.attr('class', 'legend_point')
.attr('cy', d => cell_pos(d) + cell_width/2);
g.selectAll('.legend_point')
.attr('r', legend_r)
.attr('fill', d => color({cluster: d}));
// Write out the axis labels
x_axis.append('text')
.attr('class', 'axis_text')
.attr('x', edge_length/2)
.attr('y', margin.sides*0.5)
.text('To cluster');
y_axis.append('text')
.attr('class', 'axis_text')
.attr('x', -edge_length/2)
.attr('y', -margin.sides*0.5)
.attr('transform', 'rotate(-90)')
.text('From cluster');
g.selectAll('.axis_text')
.attr('text-anchor', 'middle')
.attr('font-size', '0.85em')
.attr('dominant-baseline', 'middle')
.attr('fill', secondary_text_color);
// title
svg.append('text')
.text(title)
.attr('x', width - 10)
.attr('y', title_font_size)
.attr('text-anchor', 'end')
.attr('fill', title_color)
.attr('font-size', title_font_size);
// instructions
svg.append('text')
.text('drag cell to change average edge counts')
.attr('x', width - 10)
.attr('y', title_font_size*2)
.attr('text-anchor', 'end')
.attr('font-size', title_font_size/1.7)
.attr('fill', secondary_text_color);
cells.call(
d3.drag()
.on("start", function(){ embolden(this) })
.on("drag", function(d){
const new_avg_edges = clamp(d.avg_edges + sign(d3.event.dx), ...count_range);
d.avg_edges = new_avg_edges;
// Update color
d3.select(this).select('rect').attr('fill', d => cell_color(d.avg_edges));
// Update text
d3.select(this).select('text').text(d => d.avg_edges);
})
.on("end", function(d){
chart.value = props;
chart.dispatchEvent(new CustomEvent("input"));
embolden(this);
})
);
function embolden(el){
// Give (or remove) border to emphasize selection
const bold_stroke = 4;
const square = d3.select(el).select('rect');
const currently_bold = +square.attr('stroke-width') === bold_stroke;
square.attr('stroke-width', currently_bold ? 0 : bold_stroke);
}
return chart;
}