Public
Edited
May 7, 2023
1 fork
9 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
selectedEventTitle = events[id]["title"]
Insert cell
selectedEventDetails = events[id]["details"]
Insert cell
flowmapBlock = html`
<h2>Field movements</h2>
<p>${flowchart}</p>
`
Insert cell
survivorSummary = function(id) {
if (id < 2)
{return"Most soldiers have survived.";}
else
return `About 1 in ${events[id]["oneIn"]} soldiers have survived.`;
}
Insert cell
unitBlock = html`
<h2>Count of French troops</h2>
<br>
<p>${survivorSummary(id)}</p>
<p>${unitChart}</p>
<p>Each figure represents 1,055 soldiers.</p>`
Insert cell
timelineBlock = html`<div><h2>${formatTime(parseDate(events[id]["date"]))}</h2>
<p>${timeline}</p></div>
`
Insert cell
eventBlock = html`<div><h2>${selectedEventTitle}</h2>
<p>${selectedEventDetails}</p></div>
`
Insert cell
sources = html`<p>Data: <a href="https://www.napoleon.org/en/magazine/publications/1812-napoleons-fatal-march-on-moscow/">1812: Napoleon’s Fatal March on Moscow</a> from Adam Zamoyski, <a href="http://mbostock.github.io/protovis/ex/napoleon.html">Minard's Napoleon</a> from Mike Bostock, <a href="https://www.datavis.ca/gallery/re-minard.php">Re-Visions of Minard</a> from Michael Friendly</p>`
Insert cell
icons = html`<p>Icons: <a href = "https://thenounproject.com/icon/man-1034124/">Man</a> and <a href="https://thenounproject.com/icon/cross-3066973/">Cross</a> from Adrien Coquet from the <a href = "https://thenounproject.com/">Noun Project</a></p>`
Insert cell
Insert cell
formatTime = d3.timeFormat("%B %d, %Y")
Insert cell
parseDate = d3.timeParse("%Y-%m-%d")
Insert cell
timeline = drawTimeline(events)
Insert cell
xAxis = g => g
.attr("transform", `translate(0,${heigthTimeline - 25})`)
.call(d3.axisBottom(x))
Insert cell
// draw cities (circles) and field movements (lines) on a map
function drawTimeline(data) {

let y = heigthTimeline/2;
let r = 5;
let backgroundFill = "#d0c3ab";
let strokeColor = smalt;
let strokeWidth = 3;
const svg = d3.create("svg")
.attr("width", width)
.attr("height", heigthTimeline)
.attr("viewBox", [0, 0, width, heigthTimeline])

// all dates in the background
const allDates = svg.append("g")
.selectAll("circle")
.data(data)
.join("circle")
.attr("class", "allDates")
.attr("transform", d => `translate(${x(parseDate(d.date))},${y})`)
.attr("r", r)//heigthTimeline/10)
.attr("fill", backgroundFill)//"none")//"#7f7f7f") //d=> colorCircles(d.r))
.style("stroke","none")// "grey")
.style("stroke-width", strokeWidth);

// draw a line to display the period duration
const duration = svg.append("g")
.append("line")
.attr("class", "duration")
.attr("x1", x(startDate)+r)
.attr("x2", x(parseDate(period[id]))-r)
.attr("y1", y)
.attr("y2", y)
.style("stroke", strokeColor)
.style("stroke-width", strokeWidth);
// start date and selected date appear darker
const selectedPeriod = svg.append("g")
.selectAll("circle")
.data([startDate,parseDate(period[id])])
.join("circle")
.attr("class", "selectedPeriod")
.attr("transform", d => `translate(${x(d)},${y})`)
.attr("r", r)//heigthTimeline/10)
.attr("fill", "white")//"#7f7f7f") //d=> colorCircles(d.r))
.style("stroke", strokeColor)
.style("stroke-width", strokeWidth);

svg.append("g")
.call(xAxis);
return svg.node();
}
Insert cell
x = d3.scaleTime()
.domain([startDate, endDate])
.range([15, width * 0.5]);//300])//width])//
Insert cell
startDate = parseDate(period[0])
Insert cell
endDate = parseDate(period[period.length-1])
Insert cell
period = events.map(d=>d.date)
Insert cell
Insert cell
Insert cell
selectedSurvivorShare = events[id]["total_survivors"]/422000
Insert cell
// draw the grid chart
unitChart = gridModulo(gridItems, selectedSurvivorShare, {
columnLength: numOfColumns,
squareSize: squareSize
})
Insert cell
Insert cell
Insert cell
Insert cell
// function to place custom shapes
function gridModulo (data, value, {
dataLength = data.length,
columnLength = 0,
squareSize = 50,
squareCol = smalt,//"#3b4b69",// used in shape fill
squareBgd = lightGrey,//inverted,//"#CACACA",//"#e0e5db",
strokeCol = smalt,//"#de3d83",// used in shape stroke
strokeWidth = 2,
textFill = "#fff",
showValues = true
} = {}) {

const size = (squareSize + (strokeWidth * 2));
const columns = (columnLength !== null && columnLength > 1 ) ? columnLength : Math.round(width / size) ;
const height = (dataLength / columns) * size;

console.log('columns', columns)
const svg = DOM.svg(width, height+ size);
const sel = d3.select(svg);

const scaleCol = d3.scaleLinear()
.domain([0, columns])
.range([0, columns * size]);

const scaleRow = d3.scaleLinear()
.domain([0, dataLength / columns])
//.range([height, 0]); // to display "latest" numbers on top
.range([0, height]);

// /!\ ? change here to add additional space every 5 rows (to display block of 100 custom shapes)
const join = sel.selectAll('g')
.data(data)
.join('g').append('g')
.attr('transform', (d, i) => {
const x = i % columns
const y = Math.floor(i / columns)
return `translate(${scaleCol(x)}, ${scaleRow(y)})`
});
join.append('path')
.attr("d", (d, i) => (value < (i+1)/dataLength) ? crossPath : manPath)
.attr('width', squareSize)
.attr('height', squareSize)
.attr('stroke-width', strokeWidth)
.attr('stroke', (d, i) => (value < (i+1)/dataLength) ? squareBgd : squareCol)//strokeCol)
.attr('fill', (d, i) => (value < (i+1)/dataLength) ? squareBgd : squareCol) // (i+1) to start counting from 1 (and not 0)
.attr('transform', (d, i) => "scale("+shapeScale+")"); // reduce the size of the custom shape
return svg;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// the selected step is based on the id (row number) (/!\ to review)
stepSelection = id +1
Insert cell
flowchart = drawMap(projectPoints(cities),projectedTroops.filter(d=>d.step<= stepSelection))
Insert cell
//selectedTroops = troops//.filter(d => d.group ===1)
Insert cell
// draw cities (circles) and field movements (lines) on a map
function drawMap(data1, data2, {
} ={}){
const svg = d3.create("svg")
.attr("width", width)
.attr("height", heightFlowChart)
//.attr("style","background-color: lightgrey")
.attr("viewBox", [0, 0, width, heightFlowChart])
//.attr("style", "width: 100%; height: auto; height: intrinsic;");

// draw troop movements
const troops = svg.append("g")
.selectAll("line")
.data(data2)
.join("line")
.attr("x1",d=> d.x1)
.attr("y1",d=> d.y1)
.attr("y2",d=> d.y2)
.attr("x2",d=> d.x2)
.style("stroke-width",d=>widthScale(d.survivors))
.style("stroke",d=>color(d.group))//"blue")
.style("opacity",d=>d.step/stepSelection)
.attr("class", "troops")
.style("stroke-linecap","round")
/*
const labelTroops = svg.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("stroke-linejoin", "round")
.selectAll("g")
.data(data2)
.join("g")
.attr("transform", (d=> `translate(${d.x1},${d.y1})`));

labelTroops.append("text")
.text(d => `(${d.long}, ${d.survivors})`)//`(${d.long}, ${d.lat}, ${d.survivors})`)
.attr("fill", d=>color(d.group))//"#7f7f7f")//"red")
.attr("text-anchor", "middle").attr("dy", "-0.7em");
*/
// place cities
const cities = svg.append("g")
.selectAll("circle")
.data(data1)
.join("circle")
.attr("class", "cities")
.attr("transform", d => `translate(${d.x},${d.y})`)
.attr("r", (d,i) => heightFlowChart/200)
.attr("fill", "black");//test);//inverted)//"#7f7f7f")//"red"); //d=> colorCircles(d.r))
//.style("stroke-width", strokeWitdthSymbol)
//.style("stroke", strokeColorSymbol) ;
//.attr("fill", "blue")

const labelCities = svg.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("stroke-linejoin", "round")
.selectAll("g")
.data(data1)
.join("g")
.attr("transform", (d=> `translate(${d.x},${d.y})`));

labelCities.append("text")
.text(d => d.city)
.style("background-color","black")
.attr("fill", "black")//test)//inverted)//"#7f7f7f")//"red")
.attr("text-anchor", "middle").attr("dy", "-0.7em");

return svg.node();
}
Insert cell
Insert cell
// used to distinguish the groups of tropps
color = d3.scaleOrdinal([smalt, nBlue,petrolBlue])
Insert cell
widthScale = d3.scaleLinear()
.domain([0,340000])
.range([1,selectedStrokeWidth])//30])
Insert cell
// check if the parameters are correct: .center = ?
projection = d3.geoMercator().translate([width/2,heightFlowChart*(1/2)]).scale(4000).center([31,55])
Insert cell
longs = d3.extent(cities.map(d=>d.long))
Insert cell
projectedCities = projectPoints(cities)
Insert cell
function projectPoints(data) {data.map(d=>{
let point = projection([d.long, d.lat]);
d.x = point[0];
d.y = point[1];
return d;
})
return data;
}
Insert cell
projectedTroops = project2Points(troopsLines)
Insert cell
function project2Points(data) {data.map(d=>{
let point1 = projection([d.long, d.lat]);
let point2 = projection([d.long2, d.lat2]);
d.x1 = point1[0];
d.y1 = point1[1];
d.x2 = point2[0];
d.y2 = point2[1];
return d;
})
return data;
}
Insert cell
Insert cell
Insert cell
html`<style>
article {
justify-content: space-around;
}
.title {
#background-color: red;
width: 90%;
position: relative;
left: 1%;
padding: 10px;
justify-content: space-around;
font-size : 22px;
}
.col1 {
display: inline-flex;
width: 33%;
#background-color: green;
align-items: center;
position: relative;
left: 5%;
padding: ${padding}px;
}
.col2 {
display: inline-flex;
flex-direction: column;
width: 50%;
#background-color: blue;
position: relative;
left: 1%;
padding: ${padding}px;
}
.column {
flex-flow: column;
}
.movements {
display: inline-flex;
#background-color: teal;
width: 95%;
position: relative;
left: 1%;
padding: ${padding}px;
}
.credits {
align-self: flex-start;
#background-color: magenta;
width: 95%;
position: relative;
left: 1%;
font-size: 12px;
}
.sources {
display: inline-flex;
width: 54%;
#background-color: green;
}
.icons {
align-self: flex-end;
display: inline-flex;
width: 40%;
#background-color: green;
position: relative;
left: 4%;
}
.legend {
}
</style>`
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
events
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
Insert cell
Insert cell
Insert cell
troopsLines
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
Insert cell
Insert cell
Insert cell
cities
Type Table, then Shift-Enter. Ctrl-space for more options.

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