Public
Edited
Jul 5, 2023
Insert cell
Insert cell
Insert cell
chart = {

const height = 500;
const width = 1000;
const colorLegendWidth = 30;
const gridWidth = width / 2;
const margin = 40;
const marginWithAxis = 50;
const marginOfAxis = 40;
const graphViewBox = '-150 -150 300 300';
const strokeSelectionColor = 'IndianRed';
const font = 'Sans-serif'
const fontSize = '20px'
const ticksFontSize = '16px'
const fontStyle = `${fontSize} ${font}`;
const ticksFontStyle = `${ticksFontSize} ${font}`;

let selectedInd = null;
function getXGridPosition(nx) {
return generationsScale(nx)
}
function getYGridPosition (ny) {
return height - individualsScale(ny)
}
function getIndividualColor(ind) {
return fitnessScale(ind.fitness)
}
function selectIndividual(ind) {
// Paint the circles
if (!!selectedInd) {
const circleOld = svgGrid.select(`#ind_${selectedInd.id}`)
.attr('fill', (ind) => `url(#graph_fill_pattern_ind_${selectedInd.id})`)
// .transition()
// .duration(250)
// .attr('stroke', getIndividualColor(selectedInd));
}
const circleNew = svgGrid.select(`#ind_${ind.id}`)
.attr('fill', strokeSelectionColor)
// .attr('fill', getIndividualColor(ind))
// .transition()
// .duration(250)
// .attr('stroke', strokeSelectionColor);
selectedInd = ind;
// Draw the big graph
const graph = d3.select('#big_graph')
.transition()
.duration(250)
.attr('opacity', 0)
.transition()
.duration(150)
.attr('href', `#graph_svg_ind_${String(ind.id)}`)
.transition()
.duration(250)
.attr('opacity', 1);
svgGrid.select('#currentFitnessTick')
.transition()
.ease(d3.easeCubicOut)
.attr('visibility', 'visible')
.attr('transform', `translate(10, ${colorLegendScale(ind.fitness)})`)
.selectAll('text')
.text(ind.fitness)
}
function drawGraphInner(graph) {
return drawGraph(graph).node();
// return ForceGraph(graph, {
// nodeId: d => d.id,
// // nodeGroup: d => d.group,
// nodeStroke: 'green',
// nodeStrokeWidth: 3, // node stroke width, in pixels
// nodeFill: "white", // node stroke color
// nodeTitle: d => d.name,
// linkStrokeWidth: 3,
// nodeRadius: 15, // node radius, in pixels
// width: 300,
// height: 300,
// linkStrength: 0.1,
// invalidation // a promise to stop the simulation when the cell is re-run
// })
}

// Create main SVG & inner SVGs
const svg = d3.create('svg').attr('width', width).attr('height', height);
const svgGrid = svg.insert('svg').attr('width', '50%').attr('height', '100%');
const svgGraph = svg.insert('svg').attr('width', '50%').attr('height', '100%').attr('x', '50%');
// Add borders
svg.selectAll('svg').insert('rect').attr('height', '100%').attr('width', '100%').attr('fill', 'none').attr('stroke', 'black').attr('stroke-width', 3);
// Add titles
svg.selectAll('svg')
.insert('text')
.attr("text-anchor", "middle")
.attr("x", '50%')
.attr("y", margin)
.style('font', fontStyle)
.data(['Individuals', 'Graph'])
.text((d) => d)
// Draw big graph
svgGraph.insert('use').attr('width', '100%').attr('height', '100%').attr('id', 'big_graph');

const dataLinks = dataReal.links.slice()
const dataIndividuals = dataReal.nodes.slice()
.sort((a, b) =>
d3.ascending(a.lifespan[0], b.lifespan[0])
|| d3.descending(a.lifespan[1], b.lifespan[1]));
const dataIndividualsPerGeneration = Object.fromEntries(
d3.group(dataIndividuals, ind => ind.lifespan[0]));
// return {indsPerGen: dataIndividualsPerGeneration, uniqueGenerations: [...new Set(dataIndividuals.map((ind) => ind.lifespan[0]))]};
for (let ind of dataIndividuals) {
let individualsInGeneration = dataIndividualsPerGeneration[ind.lifespan[0]];
// console.log(Array.isArray(individualsInGeneration));
// if (individualsInGeneration == null) console.log(ind.lifespan[0], individualsInGeneration);
ind.posY = individualsInGeneration.findIndex(gen_ind => gen_ind.id == ind.id);
}

for (let ind of dataIndividuals) {
ind.hasVisibleLifespan = false;
let gen = ind.lifespan[0];
ind.class = `gen_${gen}`;
ind.posX = ind.lifespan[1];
let lifespanLength = (ind.lifespan[1] - ind.lifespan[0]) + 1;
if (lifespanLength <= 1) continue;
ind.hasVisibleLifespan = true;
for (let current_gen of d3.range(gen + 1, gen + lifespanLength)) {
ind.class = ind.class + ` gen_${current_gen}`
if (current_gen > dataIndividualsPerGeneration.length - 1) continue;
let genIndividuals = dataIndividualsPerGeneration[current_gen];
if (genIndividuals == null) continue;
let index = genIndividuals.findIndex((ind_2) => ind_2.posY == ind.posY);
for (let ind_2 of dataIndividualsPerGeneration[current_gen].slice(index)) {
// tmp.push([ind.id, ind_2.id, ind.posY, ind_2.posY]);
ind_2.posY = ind_2.posY + 1;
}
}
}
// return data_visible_lifespans

// Define scales
const maxPosX = d3.max(d3.merge(dataIndividuals.map(n => n.lifespan)))
const maxPosY = d3.max(dataIndividuals.map((ind) => ind.posY))

const gridLengthX = maxPosX + 1
const gridLengthY = maxPosY + 1

const r = d3.min([
(gridWidth - margin - marginWithAxis - colorLegendWidth) / (3 * gridLengthX + 1),
(height - margin - marginWithAxis) / (3 * gridLengthY + 1)
]);
// return r;
const ind_stroke_width = r / 5;

const generationsScale = d3.scaleLinear()
// .range([marginWithAxis, gridWidth - (margin)])
.range([marginWithAxis + 2 * r, marginWithAxis + (r * (3 * gridLengthX - 1))])
.domain([0, maxPosX]);
// return generationsScale.range();

const individualsScale = d3.scaleLinear()
// .range([marginWithAxis, height - (margin)])
.range([marginWithAxis + 2 * r, marginWithAxis + (r * (3 * gridLengthY - 1))])
.domain([0, maxPosY]);

const fitnessDomain = d3.extent(dataIndividuals.map((ind) => ind.fitness)).reverse()
const fitnessScale = d3.scaleSequential()
.domain(fitnessDomain)
.interpolator(d3.interpolateViridis);

// Define visible lifespans data
const dataIndividualsWithLifespan = [dataIndividuals.filter((ind) => ind.hasVisibleLifespan),
(ind) => ind.id]

// Append the link to the svg element
svgGrid.selectAll()
.data(dataLinks)
.join('path')
.attr('d', (link) => {
const ind_1 = dataIndividuals.find((ind) => ind.id == link.source)
const ind_2 = dataIndividuals.find((ind) => ind.id == link.target)
return d3.linkHorizontal()({
source: [getXGridPosition(ind_2.lifespan[0] - 1), getYGridPosition(ind_1.posY)],
target: [getXGridPosition(ind_2.lifespan[0]), getYGridPosition(ind_2.posY)],
})
})
.attr('stroke', 'black')
.attr('fill', 'none');

const individualGroups = svgGrid.selectAll()
.data(dataIndividuals, (ind) => ind.id)
.join('g')
.attr('id', (ind) => `g_ind_${ind.id}`)
.attr('style', (ind) => `stroke:${getIndividualColor(ind)}`)

// Transparent lifespans for actions
individualGroups.data(...dataIndividualsWithLifespan)
.insert('line')
// .attr('stroke', 'black')
.attr('stroke-width', r * 2)
.attr('visible', 'none')
.attr('stroke-opacity', 0)
.attr('x1', (ind) => getXGridPosition(ind.lifespan[0]) - r)
.attr('x2', (ind) => getXGridPosition(ind.lifespan[1]) + r)
.attr('y1', (ind) => getYGridPosition(ind.posY))
.attr('y2', (ind) => getYGridPosition(ind.posY))

// Lifespan lines
individualGroups.data(...dataIndividualsWithLifespan)
.insert('g')
.insert('line')
// .attr('stroke', 'black')
.attr('stroke-width', ind_stroke_width)
.attr('stroke-linecap', 'round')
.attr('x1', (ind) => getXGridPosition(ind.lifespan[0]))
.attr('x2', (ind) => getXGridPosition(ind.lifespan[1]))
.attr('y1', (ind) => getYGridPosition(ind.posY))
.attr('y2', (ind) => getYGridPosition(ind.posY))
// .select()
// .data(function (ind) {
// console.log(ind);
// const positions = [];
// for (let x of d3.range(ind.lifespan[0], ind.lifespan[1])) positions.push([x, ind.posY]);
// return positions;
// })
// .clone()
// .attr('x1', (d) => getXGridPosition(d[0]))
// .attr('x2', (d) => getXGridPosition(d[0]))
// .attr('y1', (d) => getYGridPosition(d[1]) + r/4)
// .attr('y2', (d) => getYGridPosition(d[1]) - + r/4)
.clone()
.attr('x1', (ind) => getXGridPosition(ind.lifespan[0]))
.attr('x2', (ind) => getXGridPosition(ind.lifespan[0]))
.attr('y1', (ind) => getYGridPosition(ind.posY) + r/4)
.attr('y2', (ind) => getYGridPosition(ind.posY) - r/4)
.clone()
.attr('x1', (ind) => getXGridPosition(ind.lifespan[1]))
.attr('x2', (ind) => getXGridPosition(ind.lifespan[1]))
.attr('y1', (ind) => getYGridPosition(ind.posY) + r/4)
.attr('y2', (ind) => getYGridPosition(ind.posY) - r/4)



// Circles of individuals
individualGroups.data(dataIndividuals, (ind) => ind.id)
.insert('g')
.attr('id', (ind) => `ind_linear_transform_${ind.id}`)
.attr('class', (ind) => 'ind_linear_transform ' + ind.class)
.attr('transform', (ind) => `translate(${getXGridPosition(ind.posX)}, ${getYGridPosition(ind.posY)})`)
.attr('pos_x', (ind) => ind.posX)
.insert('g')
.attr('id', (ind) => `ind_scale_transform_${ind.id}`)
.attr('class', (ind) => 'ind_scale_transform ' + ind.class)
.attr('transform', 'scale(1)')
.insert('circle')
// .attr('stroke', 'black')
.attr('stroke-width', ind_stroke_width)
.attr('r', r)
.attr('fill', (ind) => `url(#graph_fill_pattern_ind_${ind.id})`)
.attr('id', (ind) => `ind_${ind.id}`)
.on('mouseover', function(event, ind) {
d3.select(this)
.transition()
// .attr('stroke', strokeSelectionColor)
d3.select(`#ind_scale_transform_${ind.id}`)
.transition()
.ease(d3.easeCubicOut)
.attr('transform', 'scale(2)')
})
.on('mouseout', function(event, ind) {
d3.select(this)
.transition()
// .attr('stroke', null)
d3.select(`#ind_scale_transform_${ind.id}`)
.transition()
.attr('transform', 'scale(1)')
})
.on('click', function(event, ind) {
// const bg_graph = d3.select('#big_graph_background')
// .attr('href', `#graph_svg_ind_${ind.id}`);
selectIndividual(ind);
})
;

// Graph images
const individualDefs = individualGroups
.data(dataIndividuals, (ind) => ind.id)
.insert('defs')
.attr('id', (ind) => `defs_ind_${ind.id}`)
for (let ind of dataIndividuals) {
// console.log(ind)
let graphSVG = drawGraphInner(ind.graph)
d3.select(individualGroups.select(`#defs_ind_${ind.id}`).node().appendChild(graphSVG))
.attr('width', '100%')
.attr('height', 'auto')
// .attr('width', r * 2)
// .attr('height', r * 2)
.attr('id', `graph_svg_ind_${ind.id}`)
// .attr('patternContentUnits', 'userSpaceOnUse')
// individualFillPattern.nodes().se(`#graph_fill_pattern_ind_${ind.id}`).node().appendChild(graphSVG)
// console.log(i)
// let graphSVG = drawGraph(dataIndividuals[i].graph)
// individualFillPattern.node().appendChild(graphSVG)
}

const individualFillPattern = individualDefs
.insert('pattern')
// .attr('patternContentUnits', 'userSpaceOnUse')
.attr('id', (ind) => `graph_fill_pattern_ind_${ind.id}`)
// .attr('viewBox', (ind) => grah_view_box)
.attr('x', '0%')
.attr('y', '0%')
.attr('width', '100%')
.attr('height', '100%')

individualFillPattern.insert('rect') // white background instead of transparent
.attr('width', '100%')
.attr('height', '100%')
.attr('fill', 'white');

individualFillPattern.insert('use')
.attr('width', r * 2)
.attr('height', r * 2)
.attr('href', (ind) => `#graph_svg_ind_${ind.id}`);

// individualFillPattern.append('svg')
// .html((ind) => drawGraph(ind.graph).getInnerHTML())
// // .html(() => svg_graph_placeholders[d3.randomInt(svg_graph_placeholders.length)()].getInnerHTML()) // TODO: malke specific for an individual
// // .attr('patternContentUnits', 'userSpaceOnUse')
// .attr('width', r * 2)
// .attr('height', r * 2)
// // .attr('width', '100%')
// // .attr('height', '100%')
// .attr('id', (ind) => `graph_svg_ind_${ind.id}`)
// .attr('viewBox', graphViewBox) // TODO: fit to my individual graphs
// .attr('overflow', 'visible')
// ;


individualGroups.data(...dataIndividualsWithLifespan)
.on("mousemove", function(event, ind) {
// var element = d3.select(this)
// .select('circle')
var xy = d3.pointer(event, svgGrid.node());
var lifespan_start = getXGridPosition(ind.lifespan[0])
var lifespan_end = getXGridPosition(ind.lifespan[1])
var pos_x = d3.max([d3.min([xy[0], lifespan_end]), lifespan_start])

// d3.select(this)
// .transition()
// .ease(d3.easeCubicOut)
// .attr('style', `stroke:${strokeSelectionColor}`);
d3.select(this)
.select('.ind_linear_transform')
.transition()
.ease(d3.easeCubicOut)
.attr('transform', `translate(${pos_x}, ${getYGridPosition(ind.posY)})`);
d3.select(this)
.select('circle')
.transition()
.ease(d3.easeCubicOut)
// .attr('stroke', 'orange');
})
.on("mouseleave", function(event, ind) {
// d3.select(this)
// .transition()
// .ease(d3.easeCubicOut)
// .attr('style', `stroke:${getIndividualColor(ind)}`);
d3.select(this)
.select('.ind_linear_transform')
.transition()
.attr('transform', `translate(${getXGridPosition(ind.posX)}, ${getYGridPosition(ind.posY)})`)
d3.select(this)
.select('circle')
.transition()
.ease(d3.easeCubicOut)
// .attr('stroke', null);
});
const axisGenerations = svgGrid.append('g')
.attr("transform", `translate(${0},${height - marginOfAxis})`)
.style("font", ticksFontStyle)
.style("stroke-width", 3);

axisGenerations.call(
d3.axisTop(generationsScale)
.ticks(maxPosX)
.tickSize(0)
).select('.domain').remove();
svgGrid.append("text")
.attr("text-anchor", "middle")
.attr("x", gridWidth / 2 + margin - colorLegendWidth)
.attr("y", height - marginOfAxis / 2)
.style('font', fontStyle)
.text('generation');

axisGenerations.selectAll('.tick')
.on('mouseover', function(event) {
d3.select(this).attr('style', `color: ${strokeSelectionColor}`)
var genNum = d3.select(this).text()
d3.selectAll(`.gen_${genNum}.ind_scale_transform`)
.transition()
.ease(d3.easeCubicOut)
.attr('transform', 'scale(1.5)')
d3.selectAll(`.gen_${genNum}.ind_linear_transform`)
.transition()
.attr('transform', (ind) => `translate(${getXGridPosition(genNum)}, ${getYGridPosition(ind.posY)})`);
})
.on('mouseout', function(event) {
d3.select(this).attr('style', 'color: black')
var genNum = d3.select(this).text()
d3.selectAll(`.gen_${genNum}.ind_scale_transform`)
.transition()
.ease(d3.easeCubicOut)
.attr('transform', 'scale(1)')
d3.selectAll(`.gen_${genNum}.ind_linear_transform`)
.transition()
.attr('transform', (ind) => `translate(${getXGridPosition(ind.posX)}, ${getYGridPosition(ind.posY)})`);
})

const colorLegend = legend({
color: fitnessScale,
title: 'fitness',
tickSize: 5,
width: colorLegendWidth,
height: height - margin - marginWithAxis, // 250 //
});
d3.select(colorLegend).selectAll('g').attr('font-family', null);
d3.select(colorLegend).selectAll('text').attr('font-weight', null);
d3.select(colorLegend).select('text.title').style('font', fontStyle)
d3.select(colorLegend).selectAll('g.tick').style('font', ticksFontStyle)
const visibleTick = d3.select(colorLegend)
.insert('g')
.attr('id', 'currentFitnessTick')
.attr('visibility', 'hidden');
visibleTick.insert('line')
.attr('stroke', strokeSelectionColor)
.attr('stroke-width', 3)
.attr('x1', -10)
.attr('x2', 5);
// visibleTick.insert('text')
// .attr('fill', strokeSelectionColor)
// .style('font', fontStyle)
// // .style('backgroundColor', 'white')
// .attr('x', 13)
// .attr('dy', '0.32em')
// .text('test');
visibleTick.insert('text')
// .attr('fill', strokeSelectionColor)
.style('font', ticksFontStyle)
.style('stroke', 'white')
.style('stroke-width', '0.3em')
// .style('backgroundColor', 'white')
.attr('x', -17)
.attr("text-anchor", "end")
.attr('dy', '0.32em')
.text('test')
.clone()
.attr('fill', strokeSelectionColor)
.style('stroke', null)
.style('stroke-width', null)
;

svgGrid.insert('svg').html(colorLegend.getInnerHTML())
.attr('id', 'colorLegend')
.attr('height', '50%')
.attr('width', colorLegendWidth)
.attr('x', gridWidth - colorLegendWidth - margin)
.attr('y', margin)
// .attr('y', '50%')
.attr('overflow', 'visible');

const colorLegendScale = d3.scaleLinear()
.range([
Number(d3.select(colorLegend).select('image').attr('y')) + Number(d3.select(colorLegend).select('image').attr('height')),
d3.select(colorLegend).select('image').attr('y')
])
.domain(fitnessDomain)

return svg.node();

}
Insert cell
// chart_demo_2 = {

// const height = 500;
// const width = 1000;
// const colorLegendWidth = 30;
// const gridWidth = width / 2;
// const margin = 40;
// const marginWithAxis = 50;
// const marginOfAxis = 40;
// const graphViewBox = '-150 -150 300 300';
// const strokeSelectionColor = 'DarkSalmon';
// const font = 'Sans-serif'
// const fontSize = '20px'
// const ticksFontSize = '16px'
// const fontStyle = `${fontSize} ${font}`;
// const ticksFontStyle = `${ticksFontSize} ${font}`;

// let selectedInd = null;
// function getXGridPosition(nx) {
// return generationsScale(nx)
// }
// function getYGridPosition (ny) {
// return height - individualsScale(ny)
// }
// function getIndividualColor(ind) {
// return fitnessScale(ind.fitness)
// }
// function selectIndividual(ind) {
// // Paint the circles
// if (!!selectedInd) {
// const circleOld = svgGrid.select(`#ind_${selectedInd.id}`)
// .attr('fill', (ind) => `url(#graph_fill_pattern_ind_${selectedInd.id})`)
// // .transition()
// // .duration(250)
// // .attr('stroke', getIndividualColor(selectedInd));
// }
// const circleNew = svgGrid.select(`#ind_${ind.id}`)
// .attr('fill', strokeSelectionColor)
// // .attr('fill', getIndividualColor(ind))
// // .transition()
// // .duration(250)
// // .attr('stroke', strokeSelectionColor);
// selectedInd = ind;
// // Draw the big graph
// const graph = d3.select('#big_graph')
// .transition()
// .duration(250)
// .attr('opacity', 0)
// .transition()
// .duration(150)
// .attr('href', `#graph_svg_ind_${String(ind.id)}`)
// .transition()
// .duration(250)
// .attr('opacity', 1);
// svgGrid.select('#currentFitnessTick')
// .transition()
// .ease(d3.easeCubicOut)
// .attr('visibility', 'visible')
// .attr('transform', `translate(10, ${colorLegendScale(ind.fitness)})`)
// .selectAll('text')
// .text(ind.fitness)
// }
// function drawGraphInner(graph) {
// // return drawGraph(graph).node();
// return ForceGraph(graph, {
// nodeId: d => d.id,
// // nodeGroup: d => d.group,
// nodeStroke: 'green',
// nodeStrokeWidth: 3, // node stroke width, in pixels
// nodeFill: "white", // node stroke color
// nodeTitle: d => d.name,
// linkStrokeWidth: 3,
// nodeRadius: 15, // node radius, in pixels
// width: 300,
// height: 300,
// linkStrength: 0.1,
// invalidation // a promise to stop the simulation when the cell is re-run
// })
// }

// // Create main SVG & inner SVGs
// const svg = d3.create('svg').attr('width', width).attr('height', height);
// const svgGrid = svg.insert('svg').attr('width', '50%').attr('height', '100%');
// const svgGraph = svg.insert('svg').attr('width', '50%').attr('height', '100%').attr('x', '50%');
// // Add borders
// svg.selectAll('svg').insert('rect').attr('height', '100%').attr('width', '100%').attr('fill', 'none').attr('stroke', 'black').attr('stroke-width', 3);
// // Add titles
// svg.selectAll('svg')
// .insert('text')
// .attr("text-anchor", "middle")
// .attr("x", '50%')
// .attr("y", margin)
// .style('font', fontStyle)
// .data(['Individuals', 'Graph'])
// .text((d) => d)
// // Draw big graph
// svgGraph.insert('use').attr('width', '100%').attr('height', '100%').attr('id', 'big_graph');

// const dataLinks = dataReal.links.slice()
// const dataIndividuals = dataReal.nodes.slice()
// .sort((a, b) =>
// d3.ascending(a.lifespan[0], b.lifespan[0])
// || d3.descending(a.lifespan[1], b.lifespan[1]));
// const dataIndividualsPerGeneration = Array.from(
// d3.group(dataIndividuals, ind => ind.lifespan[0]),
// ([, value]) => value);

// for (let ind of dataIndividuals) {
// ind.posY = dataIndividualsPerGeneration[ind.lifespan[0]].findIndex(gen_ind => gen_ind.id == ind.id);
// }

// for (let ind of dataIndividuals) {
// ind.hasVisibleLifespan = false;
// let gen = ind.lifespan[0];
// ind.class = `gen_${gen}`;
// ind.posX = ind.lifespan[1];
// let lifespanLength = (ind.lifespan[1] - ind.lifespan[0]) + 1;
// if (lifespanLength <= 1) continue;
// ind.hasVisibleLifespan = true;
// for (let current_gen of d3.range(gen + 1, gen + lifespanLength)) {
// ind.class = ind.class + ` gen_${current_gen}`
// if (current_gen > dataIndividualsPerGeneration.length - 1) continue;
// let index = dataIndividualsPerGeneration[current_gen].findIndex((ind_2) => ind_2.posY == ind.posY);
// for (let ind_2 of dataIndividualsPerGeneration[current_gen].slice(index)) {
// // tmp.push([ind.id, ind_2.id, ind.posY, ind_2.posY]);
// ind_2.posY = ind_2.posY + 1;
// }
// }
// }
// // return data_visible_lifespans

// // Define scales
// const maxPosX = d3.max(d3.merge(dataIndividuals.map(n => n.lifespan)))
// const maxPosY = d3.max(dataIndividuals.map((ind) => ind.posY))

// const gridLengthX = maxPosX + 1
// const gridLengthY = maxPosY + 1

// const r = d3.min([
// (gridWidth - margin - marginWithAxis - colorLegendWidth) / (3 * gridLengthX + 1),
// (height - margin - marginWithAxis) / (3 * gridLengthY + 1)
// ]);
// // return r;
// const ind_stroke_width = r / 5;

// const generationsScale = d3.scaleLinear()
// // .range([marginWithAxis, gridWidth - (margin)])
// .range([marginWithAxis + 2 * r, marginWithAxis + (r * (3 * gridLengthX - 1))])
// .domain([0, maxPosX]);
// // return generationsScale.range();

// const individualsScale = d3.scaleLinear()
// // .range([marginWithAxis, height - (margin)])
// .range([marginWithAxis + 2 * r, marginWithAxis + (r * (3 * gridLengthY - 1))])
// .domain([0, maxPosY]);

// const fitnessDomain = d3.extent(dataIndividuals.map((ind) => ind.fitness)).reverse()
// const fitnessScale = d3.scaleSequential()
// .domain(fitnessDomain)
// .interpolator(d3.interpolateViridis);

// // Define visible lifespans data
// const dataIndividualsWithLifespan = [dataIndividuals.filter((ind) => ind.hasVisibleLifespan),
// (ind) => ind.id]

// // Append the link to the svg element
// svgGrid.selectAll()
// .data(dataLinks)
// .join('path')
// .attr('d', (link) => {
// const ind_1 = dataIndividuals.find((ind) => ind.id == link.source)
// const ind_2 = dataIndividuals.find((ind) => ind.id == link.target)
// return d3.linkHorizontal()({
// source: [getXGridPosition(ind_2.lifespan[0] - 1), getYGridPosition(ind_1.posY)],
// target: [getXGridPosition(ind_2.lifespan[0]), getYGridPosition(ind_2.posY)],
// })
// })
// .attr('stroke', 'black')
// .attr('fill', 'none');

// const individualGroups = svgGrid.selectAll()
// .data(dataIndividuals, (ind) => ind.id)
// .join('g')
// .attr('id', (ind) => `g_ind_${ind.id}`)
// .attr('style', (ind) => `stroke:${getIndividualColor(ind)}`)

// // Transparent lifespans for actions
// individualGroups.data(...dataIndividualsWithLifespan)
// .insert('line')
// // .attr('stroke', 'black')
// .attr('stroke-width', r * 2)
// .attr('visible', 'none')
// .attr('stroke-opacity', 0)
// .attr('x1', (ind) => getXGridPosition(ind.lifespan[0]) - r)
// .attr('x2', (ind) => getXGridPosition(ind.lifespan[1]) + r)
// .attr('y1', (ind) => getYGridPosition(ind.posY))
// .attr('y2', (ind) => getYGridPosition(ind.posY))

// // Lifespan lines
// individualGroups.data(...dataIndividualsWithLifespan)
// .insert('g')
// .insert('line')
// // .attr('stroke', 'black')
// .attr('stroke-width', ind_stroke_width)
// .attr('stroke-linecap', 'round')
// .attr('x1', (ind) => getXGridPosition(ind.lifespan[0]))
// .attr('x2', (ind) => getXGridPosition(ind.lifespan[1]))
// .attr('y1', (ind) => getYGridPosition(ind.posY))
// .attr('y2', (ind) => getYGridPosition(ind.posY))
// // .select()
// // .data(function (ind) {
// // console.log(ind);
// // const positions = [];
// // for (let x of d3.range(ind.lifespan[0], ind.lifespan[1])) positions.push([x, ind.posY]);
// // return positions;
// // })
// // .clone()
// // .attr('x1', (d) => getXGridPosition(d[0]))
// // .attr('x2', (d) => getXGridPosition(d[0]))
// // .attr('y1', (d) => getYGridPosition(d[1]) + r/4)
// // .attr('y2', (d) => getYGridPosition(d[1]) - + r/4)
// .clone()
// .attr('x1', (ind) => getXGridPosition(ind.lifespan[0]))
// .attr('x2', (ind) => getXGridPosition(ind.lifespan[0]))
// .attr('y1', (ind) => getYGridPosition(ind.posY) + r/4)
// .attr('y2', (ind) => getYGridPosition(ind.posY) - r/4)
// .clone()
// .attr('x1', (ind) => getXGridPosition(ind.lifespan[1]))
// .attr('x2', (ind) => getXGridPosition(ind.lifespan[1]))
// .attr('y1', (ind) => getYGridPosition(ind.posY) + r/4)
// .attr('y2', (ind) => getYGridPosition(ind.posY) - r/4)



// // Circles of individuals
// individualGroups.data(dataIndividuals, (ind) => ind.id)
// .insert('g')
// .attr('id', (ind) => `ind_linear_transform_${ind.id}`)
// .attr('class', (ind) => 'ind_linear_transform ' + ind.class)
// .attr('transform', (ind) => `translate(${getXGridPosition(ind.posX)}, ${getYGridPosition(ind.posY)})`)
// .attr('pos_x', (ind) => ind.posX)
// .insert('g')
// .attr('id', (ind) => `ind_scale_transform_${ind.id}`)
// .attr('class', (ind) => 'ind_scale_transform ' + ind.class)
// .attr('transform', 'scale(1)')
// .insert('circle')
// // .attr('stroke', 'black')
// .attr('stroke-width', ind_stroke_width)
// .attr('r', r)
// .attr('fill', (ind) => `url(#graph_fill_pattern_ind_${ind.id})`)
// .attr('id', (ind) => `ind_${ind.id}`)
// .on('mouseover', function(event, ind) {
// d3.select(this)
// .transition()
// // .attr('stroke', strokeSelectionColor)
// d3.select(`#ind_scale_transform_${ind.id}`)
// .transition()
// .ease(d3.easeCubicOut)
// .attr('transform', 'scale(2)')
// })
// .on('mouseout', function(event, ind) {
// d3.select(this)
// .transition()
// // .attr('stroke', null)
// d3.select(`#ind_scale_transform_${ind.id}`)
// .transition()
// .attr('transform', 'scale(1)')
// })
// .on('click', function(event, ind) {
// // const bg_graph = d3.select('#big_graph_background')
// // .attr('href', `#graph_svg_ind_${ind.id}`);
// selectIndividual(ind);
// })
// ;

// // Graph images
// const individualDefs = individualGroups
// .data(dataIndividuals, (ind) => ind.id)
// .insert('defs')
// .attr('id', (ind) => `defs_ind_${ind.id}`)
// for (let ind of dataIndividuals) {
// console.log(ind)
// let graphSVG = drawGraphInner(ind.graph)
// d3.select(individualGroups.select(`#defs_ind_${ind.id}`).node().appendChild(graphSVG))
// .attr('width', '100%')
// .attr('height', 'auto')
// // .attr('width', r * 2)
// // .attr('height', r * 2)
// .attr('id', `graph_svg_ind_${ind.id}`)
// // .attr('patternContentUnits', 'userSpaceOnUse')
// // individualFillPattern.nodes().se(`#graph_fill_pattern_ind_${ind.id}`).node().appendChild(graphSVG)
// // console.log(i)
// // let graphSVG = drawGraph(dataIndividuals[i].graph)
// // individualFillPattern.node().appendChild(graphSVG)
// }

// const individualFillPattern = individualDefs
// .insert('pattern')
// // .attr('patternContentUnits', 'userSpaceOnUse')
// .attr('id', (ind) => `graph_fill_pattern_ind_${ind.id}`)
// // .attr('viewBox', (ind) => grah_view_box)
// .attr('x', '0%')
// .attr('y', '0%')
// .attr('width', '100%')
// .attr('height', '100%')

// individualFillPattern.insert('rect') // white background instead of transparent
// .attr('width', '100%')
// .attr('height', '100%')
// .attr('fill', 'white');

// individualFillPattern.insert('use')
// .attr('width', r * 2)
// .attr('height', r * 2)
// .attr('href', (ind) => `#graph_svg_ind_${ind.id}`);

// // individualFillPattern.append('svg')
// // .html((ind) => drawGraph(ind.graph).getInnerHTML())
// // // .html(() => svg_graph_placeholders[d3.randomInt(svg_graph_placeholders.length)()].getInnerHTML()) // TODO: malke specific for an individual
// // // .attr('patternContentUnits', 'userSpaceOnUse')
// // .attr('width', r * 2)
// // .attr('height', r * 2)
// // // .attr('width', '100%')
// // // .attr('height', '100%')
// // .attr('id', (ind) => `graph_svg_ind_${ind.id}`)
// // .attr('viewBox', graphViewBox) // TODO: fit to my individual graphs
// // .attr('overflow', 'visible')
// // ;


// individualGroups.data(...dataIndividualsWithLifespan)
// .on("mousemove", function(event, ind) {
// // var element = d3.select(this)
// // .select('circle')
// var xy = d3.pointer(event, svgGrid.node());
// var lifespan_start = getXGridPosition(ind.lifespan[0])
// var lifespan_end = getXGridPosition(ind.lifespan[1])
// var pos_x = d3.max([d3.min([xy[0], lifespan_end]), lifespan_start])

// // d3.select(this)
// // .transition()
// // .ease(d3.easeCubicOut)
// // .attr('style', `stroke:${strokeSelectionColor}`);
// d3.select(this)
// .select('.ind_linear_transform')
// .transition()
// .ease(d3.easeCubicOut)
// .attr('transform', `translate(${pos_x}, ${getYGridPosition(ind.posY)})`);
// d3.select(this)
// .select('circle')
// .transition()
// .ease(d3.easeCubicOut)
// // .attr('stroke', 'orange');
// })
// .on("mouseleave", function(event, ind) {
// // d3.select(this)
// // .transition()
// // .ease(d3.easeCubicOut)
// // .attr('style', `stroke:${getIndividualColor(ind)}`);
// d3.select(this)
// .select('.ind_linear_transform')
// .transition()
// .attr('transform', `translate(${getXGridPosition(ind.posX)}, ${getYGridPosition(ind.posY)})`)
// d3.select(this)
// .select('circle')
// .transition()
// .ease(d3.easeCubicOut)
// // .attr('stroke', null);
// });
// const axisGenerations = svgGrid.append('g')
// .attr("transform", `translate(${0},${height - marginOfAxis})`)
// .style("font", ticksFontStyle)
// .style("stroke-width", 3);

// axisGenerations.call(
// d3.axisTop(generationsScale)
// .ticks(maxPosX)
// .tickSize(0)
// ).select('.domain').remove();
// svgGrid.append("text")
// .attr("text-anchor", "middle")
// .attr("x", gridWidth / 2 + margin - colorLegendWidth)
// .attr("y", height - marginOfAxis / 2)
// .style('font', fontStyle)
// .text('generation');

// axisGenerations.selectAll('.tick')
// .on('mouseover', function(event) {
// d3.select(this).attr('style', `color: ${strokeSelectionColor}`)
// var genNum = d3.select(this).text()
// d3.selectAll(`.gen_${genNum}.ind_scale_transform`)
// .transition()
// .ease(d3.easeCubicOut)
// .attr('transform', 'scale(1.5)')
// d3.selectAll(`.gen_${genNum}.ind_linear_transform`)
// .transition()
// .attr('transform', (ind) => `translate(${getXGridPosition(genNum)}, ${getYGridPosition(ind.posY)})`);
// })
// .on('mouseout', function(event) {
// d3.select(this).attr('style', 'color: black')
// var genNum = d3.select(this).text()
// d3.selectAll(`.gen_${genNum}.ind_scale_transform`)
// .transition()
// .ease(d3.easeCubicOut)
// .attr('transform', 'scale(1)')
// d3.selectAll(`.gen_${genNum}.ind_linear_transform`)
// .transition()
// .attr('transform', (ind) => `translate(${getXGridPosition(ind.posX)}, ${getYGridPosition(ind.posY)})`);
// })

// const colorLegend = legend({
// color: fitnessScale,
// title: 'fitness',
// tickSize: 5,
// width: colorLegendWidth,
// height: height - margin - marginWithAxis,
// });
// d3.select(colorLegend).selectAll('g').attr('font-family', null);
// d3.select(colorLegend).selectAll('text').attr('font-weight', null);
// d3.select(colorLegend).select('text.title').style('font', fontStyle)
// d3.select(colorLegend).selectAll('g.tick').style('font', ticksFontStyle)
// const visibleTick = d3.select(colorLegend)
// .insert('g')
// .attr('id', 'currentFitnessTick')
// .attr('visibility', 'hidden');
// visibleTick.insert('line')
// .attr('stroke', strokeSelectionColor)
// .attr('stroke-width', 3)
// .attr('x1', -10)
// .attr('x2', 5);
// // visibleTick.insert('text')
// // .attr('fill', strokeSelectionColor)
// // .style('font', fontStyle)
// // // .style('backgroundColor', 'white')
// // .attr('x', 13)
// // .attr('dy', '0.32em')
// // .text('test');
// visibleTick.insert('text')
// // .attr('fill', strokeSelectionColor)
// .style('font', ticksFontStyle)
// .style('stroke', 'white')
// .style('stroke-width', '0.3em')
// // .style('backgroundColor', 'white')
// .attr('x', -17)
// .attr("text-anchor", "end")
// .attr('dy', '0.32em')
// .text('test')
// .clone()
// .attr('fill', strokeSelectionColor)
// .style('stroke', null)
// .style('stroke-width', null)
// ;

// svgGrid.insert('svg').html(colorLegend.getInnerHTML())
// .attr('id', 'colorLegend')
// .attr('height', '50%')
// .attr('width', colorLegendWidth)
// .attr('x', gridWidth - colorLegendWidth - margin)
// .attr('y', margin)
// .attr('overflow', 'visible');

// const colorLegendScale = d3.scaleLinear()
// .range([
// Number(d3.select(colorLegend).select('image').attr('y')) + Number(d3.select(colorLegend).select('image').attr('height')),
// d3.select(colorLegend).select('image').attr('y')
// ])
// .domain(fitnessDomain)

// return svg.node();

// }
Insert cell
function drawGraph (graph) {
// nodeId: d => d.id,
// // nodeGroup: d => d.group,
// nodeStroke: 'green',
// nodeStrokeWidth: 3, // node stroke width, in pixels
// nodeFill: "white", // node stroke color
// nodeTitle: d => d.name,
// linkStrokeWidth: 3,
// nodeRadius: 15, // node radius, in pixels
// width: 300,
// height: 300,
// linkStrength: 0.1,
// invalidation // a promise to stop the simulation when the cell is re-run

// Specify the dimensions of the chart.
const width = 250;
const height = 250;
// const width = '100%';
// const height = '100%';
const r = 10;
// Specify the color scale.
const color = d3.scaleOrdinal(d3.schemeCategory10);

// The force simulation mutates links and nodes, so create a copy
// so that re-evaluating this cell produces the same result.
const links = data.links.map(d => ({...d}));
const nodes = data.nodes.map(d => ({...d}));

// Create a simulation with several forces.
const simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).id(d => d.id))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2))
.on("tick", ticked);

// Create the SVG container.
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto;");

// Add a line for each link, and a circle for each node.
const link = svg.append("g")
.attr("stroke", "#999")
.attr("stroke-opacity", 0.6)
.selectAll("line")
.data(links)
.join("line")
.attr("stroke-width", d => Math.sqrt(d.value));

const node = svg.append("g")
.attr("stroke", "#fff")
.attr("stroke-width", 1.5)
.selectAll("circle")
.data(nodes)
.join("circle")
.attr("r", r)
.attr("fill", d => color(d.group));

node.append("title")
.text(d => d.id);

// Add a drag behavior.
node.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));

// Set the position attributes of links and nodes each time the simulation ticks.
function ticked() {
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);

node
.attr("cx", d => d.x)
.attr("cy", d => d.y);
}

// Reheat the simulation when drag starts, and fix the subject position.
function dragstarted(event) {
if (!event.active) simulation.alphaTarget(0.3).restart();
event.subject.fx = event.subject.x;
event.subject.fy = event.subject.y;
}

// Update the subject (dragged node) position during drag.
function dragged(event) {
event.subject.fx = event.x;
event.subject.fy = event.y;
}

// Restore the target alpha so the simulation cools after dragging ends.
// Unfix the subject position now that it’s no longer being dragged.
function dragended(event) {
if (!event.active) simulation.alphaTarget(0);
event.subject.fx = null;
event.subject.fy = null;
}

// When this cell is re-run, stop the previous simulation. (This doesn’t
// really matter since the target alpha is zero and the simulation will
// stop naturally, but it’s a good practice.)
invalidation.then(() => simulation.stop());

return svg;
}
Insert cell
Insert cell
chart_demo = {
return null;
const height = 500;
const width = 1000;
const colorLegendWidth = 60;
const gridWidth = width / 2;
const margin = 30;
const marginWithAxis = 50;
const marginOfAxis = 40;
const graphViewBox = '-150 -150 300 300';
const strokeSelectionColor = 'DarkRed';
const fontStyle = '20px times';
let selectedInd = null;
function getXGridPosition(nx) {
return generationsScale(nx)
}
function getYGridPosition (ny) {
return height - individualsScale(ny)
}
function getIndividualColor(ind) {
return fitnessScale(ind.fitness)
}
function selectIndividual(ind) {
// Paint the circles
if (!!selectedInd) {
const circleOld = svgGrid.select(`#ind_${selectedInd.id}`)
.attr('fill', (ind) => `url(#graph_fill_pattern_ind_${selectedInd.id})`)
// .transition()
// .duration(250)
// .attr('stroke', getIndividualColor(selectedInd));
}
const circleNew = svgGrid.select(`#ind_${ind.id}`)
.attr('fill', strokeSelectionColor)
// .attr('fill', getIndividualColor(ind))
// .transition()
// .duration(250)
// .attr('stroke', strokeSelectionColor);
selectedInd = ind;
// Draw the big graph
const graph = d3.select('#big_graph')
.transition()
.duration(250)
.attr('opacity', 0)
.transition()
.duration(150)
.attr('href', `#graph_svg_ind_${String(ind.id)}`)
.transition()
.duration(250)
.attr('opacity', 1);
svgGrid.select('#currentFitnessTick')
.transition()
.ease(d3.easeCubicOut)
.attr('visibility', 'visible')
.attr('transform', `translate(10, ${colorLegendScale(ind.fitness)})`)
.select('text')
.text(ind.fitness)
}

const svg = d3.create('svg').attr('width', width).attr('height', height); //.attr('style', "border:1px solid black");
const svgGrid = svg.insert('svg').attr('width', '50%').attr('height', '100%');
svgGrid.insert('rect').attr('height', '100%').attr('width', '100%').attr('fill', 'none').attr('stroke', 'black').attr('stroke-width', 3);
const svgGraph = svg.insert('svg').attr('width', '50%').attr('height', '100%').attr('x', '50%');
svgGraph.insert('rect').attr('height', '100%').attr('width', '100%').attr('fill', 'none').attr('stroke', 'black').attr('stroke-width', 3);
svgGraph.insert('use').attr('width', '100%').attr('height', '100%').attr('id', 'big_graph');

const dataLinks = dataReal.links.slice()
const dataIndividuals = dataReal.nodes.slice()
.sort((a, b) =>
d3.ascending(a.lifespan[0], b.lifespan[0])
|| d3.descending(a.lifespan[1], b.lifespan[1]));
const dataIndividualsPerGeneration = Array.from(
d3.group(dataIndividuals, ind => ind.lifespan[0]),
([, value]) => value);

for (let ind of dataIndividuals) {
ind.posY = dataIndividualsPerGeneration[ind.lifespan[0]].findIndex(gen_ind => gen_ind.id == ind.id);
}

for (let ind of dataIndividuals) {
ind.hasVisibleLifespan = false;
let gen = ind.lifespan[0]
ind.class = `gen_${gen}`
let lifespanLength = d3.range(...ind.lifespan).length + 1;
if (lifespanLength <= 1) continue;
ind.hasVisibleLifespan = true;
for (let current_gen of d3.range(gen + 1, gen + lifespanLength)) {
ind.class = ind.class + ` gen_${current_gen}`
if (current_gen > dataIndividualsPerGeneration.length - 1) continue;
let index = dataIndividualsPerGeneration[current_gen].findIndex((ind_2) => ind_2.posY == ind.posY);
for (let ind_2 of dataIndividualsPerGeneration[current_gen].slice(index)) {
// tmp.push([ind.id, ind_2.id, ind.posY, ind_2.posY]);
ind_2.posY = ind_2.posY + 1;
}
}
}
// return data_visible_lifespans

// Define scales
const maxPosX = d3.max(d3.merge(dataIndividuals.map(n => n.lifespan)))
const gridLengthY = d3.max(dataIndividualsPerGeneration.map(gen => gen.length))
const maxPosY = d3.max(dataIndividuals.map((ind) => ind.posY))

const r = d3.min([
(gridWidth - margin - marginWithAxis - colorLegendWidth) / (3 * (maxPosX + 1) + 1),
(height - margin - marginWithAxis) / (3 * (maxPosY + 1) + 1)
]);
// return r;
const ind_stroke_width = r / 5;

const generationsScale = d3.scaleLinear()
// .range([marginWithAxis, gridWidth - (margin)])
.range([marginWithAxis + 2 * r, marginWithAxis + (r * (3 * (maxPosX + 1) - 1))])
.domain([0, maxPosX]);
// return generationsScale.range();

const individualsScale = d3.scaleLinear()
// .range([marginWithAxis, height - (margin)])
.range([marginWithAxis + 2 * r, marginWithAxis + (r * (3 * (maxPosY + 1) - 1))])
.domain([0, maxPosY]);

const fitnessDomain = d3.extent(dataIndividuals.map((ind) => ind.fitness))
const fitnessScale = d3.scaleSequential()
.domain(fitnessDomain)
.interpolator(d3.interpolateViridis);

// Define visible lifespans data
const dataIndividualsWithLifespan = [dataIndividuals.filter((ind) => ind.hasVisibleLifespan),
(ind) => ind.id]

// Append the link to the svg element
svgGrid.selectAll()
.data(dataLinks)
.join('path')
.attr('d', (link) => {
const ind_1 = dataIndividuals.find((ind) => ind.id == link.source)
const ind_2 = dataIndividuals.find((ind) => ind.id == link.target)
return d3.linkHorizontal()({
source: [getXGridPosition(ind_2.lifespan[0] - 1), getYGridPosition(ind_1.posY)],
target: [getXGridPosition(ind_2.lifespan[0]), getYGridPosition(ind_2.posY)],
})
})
.attr('stroke', 'black')
.attr('fill', 'none');

const individualGroups = svgGrid.selectAll()
.data(dataIndividuals, (ind) => ind.id)
.join('g')
.attr('id', (ind) => `g_ind_${ind.id}`)
.attr('style', (ind) => `stroke:${getIndividualColor(ind)}`)

// Transparent lifespans for actions
individualGroups.data(...dataIndividualsWithLifespan)
.insert('line')
// .attr('stroke', 'black')
.attr('stroke-width', r * 2)
.attr('visible', 'none')
.attr('stroke-opacity', 0)
.attr('x1', (ind) => getXGridPosition(ind.lifespan[0]))
.attr('x2', (ind) => getXGridPosition(ind.lifespan[1]) + r)
.attr('y1', (ind) => getYGridPosition(ind.posY))
.attr('y2', (ind) => getYGridPosition(ind.posY))

// Lifespan lines
individualGroups.data(...dataIndividualsWithLifespan)
.insert('line')
// .attr('stroke', 'black')
.attr('stroke-width', ind_stroke_width)
.attr('stroke-linecap', 'round')
.attr('x1', (ind) => getXGridPosition(ind.lifespan[0]))
.attr('x2', (ind) => getXGridPosition(ind.lifespan[1]))
.attr('y1', (ind) => getYGridPosition(ind.posY))
.attr('y2', (ind) => getYGridPosition(ind.posY));

// Circles of individuals
individualGroups.data(dataIndividuals, (ind) => ind.id)
.insert('g')
.attr('id', (ind) => `ind_linear_transform_${ind.id}`)
.attr('class', (ind) => 'ind_linear_transform ' + ind.class)
.attr('transform', (ind) => `translate(${getXGridPosition(ind.lifespan[0])}, ${getYGridPosition(ind.posY)})`)
.attr('pos_x', (ind) => ind.lifespan[0])
.insert('g')
.attr('id', (ind) => `ind_scale_transform_${ind.id}`)
.attr('class', (ind) => 'ind_scale_transform ' + ind.class)
.attr('transform', 'scale(1)')
.insert('circle')
// .attr('stroke', 'black')
.attr('stroke-width', ind_stroke_width)
.attr('r', r)
.attr('fill', (ind) => `url(#graph_fill_pattern_ind_${ind.id})`)
.attr('id', (ind) => `ind_${ind.id}`)
.on('mouseover', function(event, ind) {
d3.select(this)
.transition()
// .attr('stroke', strokeSelectionColor)
d3.select(`#ind_scale_transform_${ind.id}`)
.transition()
.ease(d3.easeCubicOut)
.attr('transform', 'scale(2)')
})
.on('mouseout', function(event, ind) {
d3.select(this)
.transition()
// .attr('stroke', null)
d3.select(`#ind_scale_transform_${ind.id}`)
.transition()
.attr('transform', 'scale(1)')
})
.on('click', function(event, ind) {
// const bg_graph = d3.select('#big_graph_background')
// .attr('href', `#graph_svg_ind_${ind.id}`);
selectIndividual(ind);
})
;

// Graph images
const individualFillPattern = individualGroups
.data(dataIndividuals, (ind) => ind.id)
.insert('defs')
.insert('pattern')
.attr('patternContentUnits', 'userSpaceOnUse')
.attr('id', (ind) => `graph_fill_pattern_ind_${ind.id}`)
// .attr('viewBox', (ind) => grah_view_box)
.attr('x', '0%')
.attr('y', '0%')
.attr('width', '100%')
.attr('height', '100%');

individualFillPattern.insert('rect') // white background instead of transparent
.attr('width', '100%')
.attr('height', '100%')
.attr('fill', 'white');
individualFillPattern.append('svg')
.html(() => svg_graph_placeholders[d3.randomInt(svg_graph_placeholders.length)()].getInnerHTML()) // TODO: malke specific for an individual
// .attr('patternContentUnits', 'userSpaceOnUse')
.attr('width', r * 2)
.attr('height', r * 2)
// .attr('width', '100%')
// .attr('height', '100%')
.attr('id', (ind) => `graph_svg_ind_${ind.id}`)
.attr('viewBox', graphViewBox) // TODO: fit to my individual graphs
.attr('overflow', 'visible')
;
// .attr('style', 'background-color:white')
// .attr('x', (ind) => getXGridPosition(ind.lifespan[0]) - r)
// .attr('y', (ind) => getYGridPosition(ind.posY) - r)
// .attr('clip-path', (ind) => `url(#clipPath${ind.id})`)



individualGroups.data(...dataIndividualsWithLifespan)
.on("mousemove", function(event, ind) {
// var element = d3.select(this)
// .select('circle')
var xy = d3.pointer(event, svgGrid.node());
var lifespan_start = getXGridPosition(ind.lifespan[0])
var lifespan_end = getXGridPosition(ind.lifespan[1])
var pos_x = d3.max([d3.min([xy[0], lifespan_end]), lifespan_start])

// d3.select(this)
// .transition()
// .ease(d3.easeCubicOut)
// .attr('style', `stroke:${strokeSelectionColor}`);
d3.select(this)
.select('.ind_linear_transform')
.transition()
.ease(d3.easeCubicOut)
.attr('transform', `translate(${pos_x}, ${getYGridPosition(ind.posY)})`);
d3.select(this)
.select('circle')
.transition()
.ease(d3.easeCubicOut)
// .attr('stroke', 'orange');
})
.on("mouseleave", function(event, ind) {
// d3.select(this)
// .transition()
// .ease(d3.easeCubicOut)
// .attr('style', `stroke:${getIndividualColor(ind)}`);
d3.select(this)
.select('.ind_linear_transform')
.transition()
.attr('transform', `translate(${getXGridPosition(ind.lifespan[0])}, ${getYGridPosition(ind.posY)})`)
d3.select(this)
.select('circle')
.transition()
.ease(d3.easeCubicOut)
// .attr('stroke', null);
});
const axisGenerations = svgGrid.append('g')
.attr("transform", `translate(${0},${height - marginOfAxis})`)
.style("font", fontStyle)
.style("stroke-width", 3);

axisGenerations.call(
d3.axisTop(generationsScale)
.ticks(maxPosX)
.tickSize(0)
).select('.domain').remove();
svgGrid.append("text")
.attr("text-anchor", "middle")
.attr("x", gridWidth / 2 + margin - colorLegendWidth)
.attr("y", height - marginOfAxis / 2)
.text("generation");

axisGenerations.selectAll('.tick')
.on('mouseover', function(event) {
d3.select(this).attr('style', `color: ${strokeSelectionColor}`)
var genNum = d3.select(this).text()
d3.selectAll(`.gen_${genNum}.ind_scale_transform`)
.transition()
.ease(d3.easeCubicOut)
.attr('transform', 'scale(1.5)')
d3.selectAll(`.gen_${genNum}.ind_linear_transform`)
.transition()
.attr('transform', (ind) => `translate(${getXGridPosition(genNum)}, ${getYGridPosition(ind.posY)})`);
})
.on('mouseout', function(event) {
d3.select(this).attr('style', 'color: black')
var genNum = d3.select(this).text()
d3.selectAll(`.gen_${genNum}.ind_scale_transform`)
.transition()
.ease(d3.easeCubicOut)
.attr('transform', 'scale(1)')
d3.selectAll(`.gen_${genNum}.ind_linear_transform`)
.transition()
.attr('transform', (ind) => `translate(${getXGridPosition(ind.lifespan[0])}, ${getYGridPosition(ind.posY)})`);
})

const colorLegend = legend({
color: fitnessScale,
title: 'fitness',
tickSize: 5,
width: 30,
height: height - margin - marginWithAxis,
});
d3.select(colorLegend).selectAll('g').attr('font-family', null).style("font", fontStyle);
d3.select(colorLegend).selectAll('text').attr('font-weight', null);
// d3.select(colorLegend).insert('circle')
// .attr('id', 'currentFitnessTick')
// .attr('visibility', 'hidden')
// .attr('r', '5')
// .attr('fill', strokeSelectionColor);
const visibleTick = d3.select(colorLegend)
.insert('g')
.attr('id', 'currentFitnessTick')
.attr('visibility', 'hidden');
visibleTick.insert('line')
.attr('stroke', strokeSelectionColor)
.attr('stroke-width', 3)
.attr('x1', -5)
.attr('x2', 10);
visibleTick.insert('text')
.attr('fill', strokeSelectionColor)
.style('font', fontStyle)
// .style('backgroundColor', 'white')
.attr('x', 13)
.attr('dy', '0.32em')
.text('test');

svgGrid.insert('svg').html(colorLegend.getInnerHTML())
.attr('id', 'colorLegend')
.attr('height', '50%')
.attr('width', 'colorLegendWidth')
.attr('x', gridWidth - colorLegendWidth - margin)
.attr('y', margin)
.attr('overflow', 'visible');

const colorLegendScale = d3.scaleLinear()
.range([
Number(d3.select(colorLegend).select('image').attr('y')) + Number(d3.select(colorLegend).select('image').attr('height')),
d3.select(colorLegend).select('image').attr('y')
])
.domain(fitnessDomain)

return svg.node();

}
Insert cell
function ramp(color, n = 256) {
const canvas = DOM.canvas(1, n);
const context = canvas.getContext("2d");
for (let i = 0; i < n; ++i) {
context.fillStyle = color(i / (n - 1));
context.fillRect(0, n-i, 1, 1);
}
return canvas;
}

Insert cell
Insert cell
d3Fisheye = require("d3-fisheye@latest")
Insert cell
import {legend} from "@slowkow/vertical-color-legend"
Insert cell
import {ForceGraph} from "@d3/force-directed-graph"
Insert cell
svg_graph_placeholder = ForceGraph(graph_placeholder, {
nodeId: d => d.id,
// nodeGroup: d => d.group,
nodeStroke: 'green',
nodeStrokeWidth: 3, // node stroke width, in pixels
nodeFill: "white", // node stroke color
nodeTitle: d => d.name,
linkStrokeWidth: 3,
nodeRadius: 15, // node radius, in pixels
width: 300,
height: 300,
linkStrength: 0.1,
invalidation // a promise to stop the simulation when the cell is re-run
})
Insert cell
svg_graph_placeholder_2 = ForceGraph(graph_placeholder, {
nodeId: d => d.id,
// nodeGroup: d => d.group,
nodeStroke: 'blue',
nodeStrokeWidth: 3, // node stroke width, in pixels
nodeFill: "white", // node stroke color
nodeTitle: d => d.name,
linkStrokeWidth: 3,
nodeRadius: 15, // node radius, in pixels
width: 300,
height: 300,
linkStrength: 0.1,
invalidation // a promise to stop the simulation when the cell is re-run
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof dataRealFile = Inputs.file({label: "JSON file", accept: ".json", required: true})

Insert cell
// dataReal = dataRealFile.json()
Insert cell
url = 'https://drive.google.com/uc?id=1kUQO_h8EAtg5xvZnRI7WLdrmfkogN9k7'
Insert cell
Insert cell
// dataReal = JSON.parse('')
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more