Skip to content

Instantly share code, notes, and snippets.

@daohodac
Last active July 11, 2019 18:35
Show Gist options
  • Save daohodac/0ca6e95b1a9185355c54a27713c676d0 to your computer and use it in GitHub Desktop.
Save daohodac/0ca6e95b1a9185355c54a27713c676d0 to your computer and use it in GitHub Desktop.
ToulouseisAI ecosystem visualization
<!doctype html>
<html>
<meta charset="UTF-8">
<head>
<title>Toulouse Is AI</title>
<script src="https://d3js.org/d3-array.v2.min.js"></script>
<script src="https://d3js.org/d3.v5.min.js"></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class='content'>
<div class='pack'>
<svg class='timeline-svg'>
</svg>
<svg class='pack-svg'>
<g class='g-circle'></g>
<g class='g-text'></g>
<text x="20" y="100" class="year-big">2020</text>
</svg>
</div>
<div class='details'>
<div class='details-help'>
<p>
Survolez l'échelle de temps ci-contre pour visualiser la cartographie Toulouse AI dans le temps
</p>
<p>
Survolez chacun des cerles pour avoir des informations sur la structure
</p>
</div>
<div class='details-logo'><img src="" alt="logo"></div>
<div class='details-name'></div>
<div class='details-contact'></div>
<div class='details-description'></div>
<div class='details-tags'></div>
</div>
</div>
<script src="main-forces.js"></script>
</body>
</html>
const height = 700;
const width = 932;
const bgColor = "#ABB4BB"
// //we create a color scale for the categories
// const colorScale = d3.scaleLinear()
// .domain(d3.range(3))
// .range(["#ABB4BB", "#314B5F"])
// .interpolate(d3.interpolateHcl)
//color the circles
const categorieColorScale = d3.scaleOrdinal(
//d3.schemeCategory10
// [/*'#61550e', '#7e6f14', */'#9d8a1b', '#bda621', '#dec328']
['#dec328', '#9d8a1b', '#61550e']
)
//compute the radius of the circles
const radiusScale = d3.scaleLog().domain([1,500]).range([10,50]).clamp(true);
//used to patition the categories horisontally
var yCenter = d3.scaleOrdinal([height/2-100, height/2, height/2+100])
const showDetails = function(d) {
//we have a company
d3.select(".details-help").style('display', "none");
d3.select(".details");
d3.select(".details > .details-name").style('display', "block").html(`${d.name} (${d.categorie})`);
d3.select(".details > .details-description").style('display', "block").html(d.descr);
d3.select(".details > .details-tags").style('display', "block").html(d.tags.join(','));
d3.select(".details > .details-contact").style('display', "block").html(d.contact);
d3.select(".details > .details-logo > img").style('display', "block").attr('src',d.logo);
}
const hideDetails = function() {
d3.select(".details-help").style('display', "block");
d3.select(".details > .details-name").style('display', "none");
d3.select(".details > .details-description").style('display', "none");
d3.select(".details > .details-tags").style('display', "none");
d3.select(".details > .details-contact").style('display', "none");
d3.select(".details > .details-logo > img").style('display', "none");
}
const createTimeAxis = function(extent, callback) {
const yearScale = d3.scaleLinear()
.domain(extent)
.range([0, width-30]);
// Add scales to axis
var x_axis = d3.axisBottom()
.scale(yearScale)
.tickFormat(d3.format("d"));
//describe the SVG container
d3.select(".timeline-svg")
.attr("viewBox", `0 0 ${width+30} 30`)
.style("padding", "0 14px")
.style("display", "block")
.style("background", '#294B5C')
.style("cursor", "pointer")
.append("g")
//append the timeline axis
.attr("transform", "translate(15,0)")
.call(x_axis)
//append a rect that will be a mouse event catcher
.append('rect')
.attr('x', -30).attr('y', 0).attr('width', width).attr('height', 30)
.attr('fill', 'none')
.attr("pointer-events", "all")
.on("touchmove mousemove", function(d, i) {
callback(yearScale.invert(d3.mouse(this)[0]))
})
d3.select(".timeline-svg").selectAll('text')
.style('font', 'bold 1.5em sans-serif')
.attr('fill', 'beige')
d3.select(".timeline-svg").selectAll('line')
.attr('stroke', 'beige')
d3.select(".timeline-svg").selectAll('path')
.attr('stroke', 'beige')
d3.select('.year-big')
.style('font', 'bold 7em sans-serif')
.attr('fill', 'white')
}
const svg = d3.select(".pack-svg")
.attr("viewBox", `0 0 ${width} ${height}`)
.style("display", "block")
.style("margin", "0 -14px")
.style("background", bgColor);
// .style("cursor", "pointer")
// .on("click", () => zoom(root));
function draw() {
const nodes = this.nodes();
var u = d3.select('.g-circle')
.selectAll('circle')
.data(nodes, d=>d.name);
u.enter()
.append('circle')
.attr('cx', width/2)
.attr('cy', d=>yCenter(d.categorie))
.attr('fill', d=>categorieColorScale(d.categorie))
.attr('stroke', 'black')
.on("mouseover", showDetails)
.on("mouseout", hideDetails)
// .on("click", function(d, i) { d3.select(this).attr('stroke', 'red')})
.merge(u)
.attr('r', d=>d.radius)
.attr('cx', d=>d.x)
.attr('cy', d=>d.y)
u.exit().remove();
}
const layout = function(nodes, year){
const simulation = d3.forceSimulation(nodes)
.force('charge', d3.forceManyBody().strength(1))
.force('center', d3.forceCenter(width / 2, height/2))
.force('y', d3.forceY().y(function(d) {
return yCenter(d.categorie);
}))
.force('x', d3.forceX().x(function(d) {
return width / 2;
}))
.force('collision', d3.forceCollide().radius(function(d) {
return d.radius;
}))
.on('tick', draw);
return simulation;
}
const computeNodes = function(rows, year) {
return rows.reduce((acc,n)=>{
n.age = Math.max(0,year-n.start);
n.people = n.computePeople(year);
if (n.age>0 && n.people>0) {
n.radius = radiusScale(n.people);
acc.push(n);
}
return acc;
}, [])
}
//d3.csv("https://gist.githubusercontent.com/daohodac/52f35c22963943a39fd7f45f1614d721/raw/0fe05a988d1e2cf725bffbc7092904a8a9c99b7e/Mapping%2520Toulouse%2520is%2520AI%2520Data%2520-%2520Copie%2520de%2520Base%2520de%2520donn%25C3%25A9es.csv")
d3.csv("https://docs.google.com/spreadsheets/d/18jOVISX87ITsTzKlE4OFNV3433v-ZDyZyjbD_xO9GWQ/export?format=csv")
.then(raws=>{
const rows = raws.map((r,i)=>{
return {
categorie: r.CATEGORIE.toLowerCase(),
tags: r.TAG.split(','),
size: r.EFFECTIF,
name: r.NOM,
computePeople: d3.scaleLinear().domain([+r["DATE CREATION"], 2019]).range([0,+r.EFFECTIF.split('-')[1]]).clamp(true),
contact: r["Contact IA "],
logo: r["lien logo"],
descr: r["descriptif"],
start: +r["DATE CREATION"],
idx: i
}
})
const uniqCategories = ['startup', 'tpe', 'pme'];//new Set(rows.map(r=>r.categorie).filter(r=>r !== undefined));
categorieColorScale.domain([...uniqCategories]);
yCenter.domain([...uniqCategories]);
// const uniqTag = rows.reduce((uniqTag, r)=>{
// r.tags.forEach(t=>{
// uniqTag.push({...r, ...{tag:t}});
// });
// return uniqTag;
// }, []);
const nodes = computeNodes(rows, 2020)
const simulation = layout(nodes, 2020);
hideDetails();
createTimeAxis([2004, d3.max(rows, r=>r.start)+1], year=>{
svg.select('.year-big').text(Math.round(year))
const nodes = computeNodes(rows, year);
//simulation.stop();
simulation.nodes(nodes);
simulation.alpha(1).restart();
});
});
body {
font-family: helvetica;
background-color: #294B5C;
}
.content{
display: flex;
flex-direction: row;
}
.pack {
flex: 2;
}
.details {
flex: 1;
background-color: lightgrey;
}
.details-logo {
height: 10em;
}
.details-logo >img {
width: 100%;
}
.details-name {
font-size: 1.2em;
padding-bottom: 1em;
}
.details-description {
font-size: 1em;
padding-bottom: 1em;
}
.details-tags {
font-size: 1em;
font-style: italic;
padding-bottom: 1em;
}
.details-contact {
font-size: 1em;
text-align: right;
padding-bottom: 1em;
}
.timeline-svg {
padding: 0 14px;
display:block;
background-color: darkgrey;
cursor: pointer;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment