Last active
July 11, 2019 18:35
-
-
Save daohodac/0ca6e95b1a9185355c54a27713c676d0 to your computer and use it in GitHub Desktop.
ToulouseisAI ecosystem visualization
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
}); | |
}); | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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