Public
Edited
Dec 14, 2023
1 fork
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// googleUsCountyBoundaries = FileAttachment("google://us-county-boundaries.json").json()
Insert cell
road_home_with_lat_lon.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
["Structure Type", "PARISH", "TOTAL_CLOSING_AMOUNT", "Total CG Amount", "Total ACG Amunt", "Total IMM Amount", "Total Elevation Amount", "Current Damage Assessment", "Current Damage Assessment - Type 1", "Current Damage Assessment - Type 2", "Damage Type 1 or 2", "Current PSV", "Current Total DOB Amount (no Legal Fees removed)", "Closing Total DOB Amount"]
Insert cell
LousianaCounties = FileAttachment("us-county-boundaries (3).json").json()
Insert cell
// LousianaCounties = (googleUsCountyBoundaries.filter((d) => d["stusab"] == "LA"))
Insert cell
lousianaCounitiesGeoData = LousianaCounties.map((obj, i) => {
const type = obj["geo_shape"]["type"]
const geometry = obj["geo_shape"]["geometry"]
const properties = obj["geo_shape"]["properties"]
const statefp = obj["statefp"]
const countyfp = obj["countyfp"]
const name = obj["name"]
const stusab = obj["stusab"]
return {type, geometry, properties, statefp, countyfp, name, stusab}
})
Insert cell
geoData = LousianaCounties.map((obj) => obj["geo_shape"]);
Insert cell
lousianaWithCounties = ({ type: "FeatureCollection", features : geoData });
Insert cell
width = 1000
Insert cell
height = 384;
Insert cell
projection = d3
.geoAlbers()
.fitSize([width, height], lousianaWithCounties);
Insert cell
projection([30.035, -89.956])
// first home's lat lng
Insert cell
path = d3.geoPath().projection(projection)
Insert cell
Insert cell
d3_hexbin = require("d3-hexbin@0.2")
Insert cell
d3_hexgrid = require("d3-hexgrid")
Insert cell
hexbin = d3_hexbin.hexbin()
.extent([[0,0],[width, height]])
.radius(8);
Insert cell
hexgrid = d3_hexgrid.hexgrid()
.extent([width, height])
.geography(lousianaWithCounties)
.projection(projection)
.pathGenerator(path)
.hexRadius(4.5)
.gridExtend(0);
Insert cell
dataForHexGrid = (road_home_with_lat_lon.filter(d => d["ARS File (Yes/No)"] === "N")).map((d, i) => {
return {
x: projection([d["INTPTLON00"], d["INTPTLAT00"]])[0],
y: projection([d["INTPTLON00"], d["INTPTLAT00"]])[1],
currentDamageAssesment: d["Current Damage Assessment"],
lat: d["INTPTLAT00"],
lng: d["INTPTLON00"],
...d
};
})
Insert cell
hex = hexgrid(dataForHexGrid, ["currentDamageAssesment","PARISH", "Structure Type", "TOTAL_CLOSING_AMOUNT", "Total CG Amount", "Total ACG Amunt", "Total IMM Amount", "Total Elevation Amount", "Current Damage Assessment", "Current Damage Assessment - Type 1", "Current Damage Assessment - Type 2", "Damage Type 1 or 2", "Current PSV", "Current Total DOB Amount (no Legal Fees removed)", "Closing Total DOB Amount"])
Insert cell
lousianaDamageColours = d3.scaleSequential(t => {
var tNew = Math.pow(t, 10);
return d3.interpolateViridis(tNew);
}).domain(hex.grid.extentPointDensity.reverse())
Insert cell
summedUpData = hex.grid.layout.map(d => {
if (d.length > 0) {
const totalDamage = d.reduce((acc, dd) => acc + dd.currentDamageAssesment, 0);
d["AverageBinDamage"] = totalDamage/ d.length;
d["TotalBinDamage"] = totalDamage
} else {
d["AverageBinDamage"] = 0;
d["TotalBinDamage"] = 0;
}
return d;
});

Insert cell
color = d3.scaleSequential(d3.interpolateReds).domain(d3.extent(summedUpData.map(d => d["AverageBinDamage"])));
Insert cell
d3.extent(summedUpData.map(d => d["AverageBinDamage"]))
Insert cell
filteredData = (hex.grid.layout).filter(d => d.length > 0)
Insert cell
Insert cell
Insert cell
cc = {
const margin = { top: 40, bottom: 40, left: -215, right: 50 };
let filteredData2 = newFilteredData;

function formatNumber(number, decimalPlaces = 2) {
return number.toLocaleString("en-US", {
maximumFractionDigits: decimalPlaces
});
}
// const divElt = htl.html`<div id = "charts"></div>`;
const fullSvg = d3
.create("svg")
.attr("width", 1000)
.attr("height", height + 50);
const svg = fullSvg
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);
const chart2svg = fullSvg
.append("g")
.attr("transform", `translate(${[1000 / 2, margin.top]})`);
Chart2(newFilteredData);

// Creating hexagons
const hexs = svg
.append("g")
.selectAll(".hex")
.data(hex.grid.layout)
.enter()
.append("path")
.attr("class", "hex")
.attr("d", hex.hexagon())
.attr("transform", (d) => `translate(${d.x}, ${d.y})`)
.attr("fill", (d) => (!d.datapoints ? "#fff" : color(d.AverageBinDamage)))
.style("stroke", "#ccc");
// .append("title")
// .text(d => `Total Damage Cost : $${formatNumber(d.TotalBinDamage)} \nAverage Damage Cost : $${formatNumber(d.AverageBinDamage)} \nNumber of Houses : ${d.length}`)

// defining brush
const brush = d3
.brush()
.on("start", brushstart)
// .on("brush", brushmove)
// .on("end", brushend)
.on("brush end", brushmov)
.extent([
[0, 0],
[width, height]
]);

let brushCell;

// brush functions.
function brushstart(p) {
if (brushCell !== this) {
d3.select(brushCell).call(brush.move, null);
brushCell = this;
}
}

function brushmov({ selection }) {
if (selection) {
const [[x0, y0], [x1, y1]] = selection;
const filteredHex = svg
.selectAll(".hex")
.style("fill-opacity", 0.3)
.filter((d) => {
const [hx, hy] = [d.x, d.y];
return hx >= x0 && hx <= x1 && hy >= y0 && hy <= y1;
})
.style("fill", (d) =>
!d.datapoints ? "#fff" : color(d.AverageBinDamage)
)
.style("fill-opacity", 1);
const filteredData1 = hex.grid.layout.filter((d, i) => {
const [hx, hy] = [d.x, d.y];
return hx >= x0 && hx <= x1 && hy >= y0 && hy <= y1;
});
filteredData2 = [].concat(
...filteredData1
.filter((d) => d.length > 0)
.map((d) => d.slice(0, d.length))
);
} else {
filteredData2 = newFilteredData;
d3.selectAll(".hex").style("fill-opacity", 1);
}
Chart2(filteredData2);
}

// function brushmove(p) {
// var e = d3.brushSelection(this);

// if (!p) {
// d3.selectAll(".hex").style("fill-opacity", 0.3);
// filteredData2 = newFilteredData;
// }
// else {
// const [[x0, y0], [x1, y1]] = e;
// const filteredHex = svg
// .selectAll(".hex")
// .style("fill-opacity", 0.3)
// .filter((d) => {
// const [hx, hy] = [d.x, d.y];
// return hx >= x0 && hx <= x1 && hy >= y0 && hy <= y1;
// })
// .style("fill", (d) =>
// !d.datapoints ? "#fff" : color(d.AverageBinDamage)
// )
// .style("fill-opacity", 1);

// const filteredData1 = hex.grid.layout.filter((d, i) => {
// const [hx, hy] = [d.x, d.y];
// return hx >= x0 && hx <= x1 && hy >= y0 && hy <= y1;
// });
// console.log(filteredData1);
// filteredData2 = [].concat(
// ...filteredData1
// .filter((d) => d.length > 0)
// .map((d) => d.slice(0, d.length))
// );
// }
// Chart2(filteredData2);
// }

// function brushend() {
// var e = d3.brushSelection(this);
// if (e === null) {
// d3.selectAll(".hex")
// .style("fill", (d) =>
// !d.datapoints ? "#fff" : color(d.AverageBinDamage)
// )
// .style("fill-opacity", 1);
// }
// }

// interaction
if (interaction == "Parish") {
// appending parish boundries.
svg
.append("g")
.attr("id", "LousianaMap")
.selectAll("path")
.data(lousianaCounitiesGeoData)
.join("path")
.attr("d", path)
.attr("stroke", "grey")
.attr("stroke-width", 1)
.attr("fill", "white")
.attr("fill-opacity", 0)
.on("click", (event, d) => {
Chart2(newFilteredData.filter((dd) => dd["PARISH"] == d.name));
}).append("title")
.html((obj) => `Parish : ${obj["name"]}`);
} else {
svg.call(brush);
}

// Function to create glyph.
function Chart2(data, psvValue = null) {
chart2svg.selectAll("g").remove();
const margin = { top: 40, bottom: 40, left: -215, right: 50 };
const newFilteredData = data;
const filteredDataTotalRebuildDamage = d3.sum(
newFilteredData.map((d) => d["Current Damage Assessment - Type 2"])
);
const filteredDataAvgPSV = d3.mean(
newFilteredData.map((d) => d["Current PSV"])
);
const sc = d3
.scaleLinear()
.domain(d3.extent(newFilteredData.map((d) => d["Current PSV"])))
.range([0.95, 1.5]);
const filteredDataTotalRepairDamage = d3.sum(
newFilteredData.map((d) => d["Current Damage Assessment - Type 1"])
);
const filteredDataTotalDecidedDamage = d3.sum(
newFilteredData.map((d) => d["Current Damage Assessment"])
);
const RepairDamageInDecidedDamage = d3.sum(
newFilteredData
.filter((d) => d["Damage Type 1 or 2"] == 1)
.map((d) => d["Current Damage Assessment"])
);
const RebuildDamageInDecidedDamage = d3.sum(
newFilteredData
.filter((d) => d["Damage Type 1 or 2"] == 2)
.map((d) => d["Current Damage Assessment"])
);
const repairDamageScale = d3.scaleLinear().domain([0, 100]).range([0, 7]);
const filteredDataTotalGrants = d3.sum(
newFilteredData.map((d) => d["TOTAL_CLOSING_AMOUNT"])
);
const filteredDataTotalCG = d3.sum(
newFilteredData.map((d) => d["Total CG Amount"])
);
const filteredDataTotalACG = d3.sum(
newFilteredData.map((d) => d["Total ACG Amunt"])
);
const filteredDataTotalIMM = d3.sum(
newFilteredData.map((d) => d["Total IMM Amount"])
);
const filteredDataTotalClosingDOB = d3.sum(
newFilteredData.map((d) => d["Closing Total DOB Amount"])
);
const svg2 = d3.create("svg").attr("width", 1300).attr("height", 500);
const repairMean = d3.mean(
newFilteredData
.filter((d) => d["Damage Type 1 or 2"] == 1)
.map((d) => d["Current Damage Assessment"])
);
const repairColor = d3
.scaleSequential(d3.interpolateGreens)
.domain(
d3.extent(
newFilteredData
.filter((d) => d["Damage Type 1 or 2"] == 1)
.map((d) => d["Current Damage Assessment"])
)
);

// const repairColor = d3
// .scaleSequential(d3.interpolateReds)
// .domain([
// 0,
// d3.sum(
// newFilteredData1
// .filter((d) => d["Damage Type 1 or 2"] == 1)
// .map((d) => d["Current Damage Assessment"])
// )
// ]);

const rebuildMean = d3.mean(
newFilteredData
.filter((d) => d["Damage Type 1 or 2"] == 2)
.map((d) => d["Current Damage Assessment"])
);
const rebuildColor = d3
.scaleSequential(d3.interpolateBlues)
.domain(
d3.extent(
newFilteredData
.filter((d) => d["Damage Type 1 or 2"] == 2)
.map((d) => d["Current Damage Assessment"])
)
);
const filteredDataCG = d3.sum(
newFilteredData.map((d) => d["Total CG Amount"])
);
const filteredDataACG = d3.sum(
newFilteredData.map((d) => d["Total ACG Amunt"])
);

const filteredDataEGA = d3.sum(
newFilteredData.map((d) => d["Total Elevation Amount"])
);
const ChimneyData1 = [
{ label: "Compensation Grants", value: filteredDataCG },
{ label: "Additional Grants", value: filteredDataACG },
{ label: "Total IMM", value: filteredDataTotalIMM },
{ label: "Elevation Grants", value: filteredDataEGA },
{ label: "Duplication Of Benefits", value: filteredDataTotalClosingDOB }
];

const xScale = d3
.scaleLinear()
.domain([0, d3.sum(ChimneyData1, (d) => d.value)])
.range([0, 150]);

const colors = [
"#4e79a7",
"#f28e2c",
"#e15759",
"#76b7b2",
// "#59a14f",
"#edc949",
"#af7aa1",
"#ff9da7",
"#9c755f",
"#bab0ab"
];
const spacing = 15;
// creating home structure.
const g = chart2svg
.append("g")
.attr(
"transform",
`translate(${70}, ${margin.top + margin.top + 50})scale(1.25,1.25)`
);
g.append("rect")
.attr("x", 20)
.attr("y", 50)
.attr("width", 160)
.attr("height", 100)
.attr("fill", "none")
.attr("stroke", "black");
// Upper left half triangle
g.append("polygon")
.attr("points", "10,50 100,0 190,50")
.attr("fill", "none")
.attr("stroke", "black");
// upper right half triangle (for rebuild homes)
g.append("polygon")
.attr("points", "100,0 100,50 190,50")
.attr("fill", rebuildColor(rebuildMean))
.attr("stroke", "black")
.append("title")
.text(
(d, i) =>
`Total Rebuild Damage: ${d3.format(".2s")(
RebuildDamageInDecidedDamage.toFixed(2)
)}\nRebuild % in whole damage: ${(
(RebuildDamageInDecidedDamage / filteredDataTotalDecidedDamage) *
100
).toFixed(2)}%\nAvg Damage:${d3.format(".2s")(
d3.mean(
newFilteredData
.filter((d) => d["Damage Type 1 or 2"] == 2)
.map((d) => d["Current Damage Assessment"])
)
)}`
);
// upper right half triangel (for repair homes).
g.append("path")
.attr(
"d",
paths[
Math.round(
repairDamageScale(
(RepairDamageInDecidedDamage / filteredDataTotalDecidedDamage) *
100
)
)
]
)
.attr(
"fill",
repairColor(
d3.mean(
newFilteredData
.filter((d) => d["Damage Type 1 or 2"] == 1)
.map((d) => d["Current Damage Assessment"])
)
)
)
.attr("stroke", "black")
.append("title")
.text(
(d, i) =>
`Total repair Damage: ${d3.format(".2s")(
RepairDamageInDecidedDamage.toFixed(2)
)}\nRepair % in whole damages: ${(
(RepairDamageInDecidedDamage / filteredDataTotalDecidedDamage) *
100
).toFixed(2)}%\nAvg Damage: ${d3.format(".2s")(
d3.mean(
newFilteredData
.filter((d) => d["Damage Type 1 or 2"] == 1)
.map((d) => d["Current Damage Assessment"])
)
)}\n`
);

// creating Chimney
g.selectAll("g")
.data(groupDataFunc(ChimneyData1))
.join("g")
.append("rect")
.attr("x", 30)
.attr("y", (d) => xScale(d.cumulative))
.attr("width", 17)
.attr("height", (d) => xScale(d.value))
.style("fill", (d, i) => colors[i])
.append("title")
.text((d) => `${d.label} : $${d3.format(".2s")(d.value.toFixed(2))}`);

// Chimney Legend
chart2svg
.append("g")
.selectAll("g")
.data(groupDataFunc(ChimneyData1))
.join("rect")
.attr("x", 310)
.attr("y", (d, i) => 10 * (i + 1) + spacing * i)
.attr("width", 10)
.attr("height", 10)
.attr("fill", (d, i) => colors[i]);

chart2svg
.append("g")
.selectAll("text")
.data(groupDataFunc(ChimneyData1))
.join("text")
.attr("x", 325)
.attr("y", (d, i) => 10 * (i + 1) + spacing * i + 7)
.style("fill", "black")
.style("font-size", "12px")
.text((d) => `${d.label} (${d.percent.toFixed(1)}%)`);

chart2svg
.append("g")
.attr("transform", "translate(0,0)scale(0.87, 0.87)")
.append(() =>
Legend(repairColor, {
title: "Repair Cost",
tickFormat: (d) => d3.format(".2s")(d)
})
);

chart2svg
.append("g")
.attr("transform", "translate(0,50)scale(0.87,0.87)")
.append(() => Legend(rebuildColor, { title: "Rebuild Cost" }));
}

return fullSvg.node();
}
Insert cell
Insert cell
Insert cell
newFilteredData = [].concat(...(filteredData.filter(d => d.length > 0 )).map(d => d.slice(0,d.length)));
Insert cell
newFilteredData1 = [].concat(...(filteredData.filter(d => d.length > 0 )).map(d => d.slice(0,d.length)));
Insert cell
Insert cell
orleans = road_home_with_lat_lon.filter(d => d["PARISH"] == "Orleans")
Insert cell
p = "M128,208 H128 H208 V128 H224 L167,64 L112,128 H128 V208"
Insert cell
polp = "152,50 175,40 190,50"
// first third same, first is for the upper tip and third is for the straight lines lower tip
// fourth for the upper tip
Insert cell
<svg width="250" height="150">
<rect x="20" y="50" width="160" height="100" fill="none" stroke="black" />
<polygon points="10,50 100,0 190,50" fill="none" stroke="black" />
<rect x="30" y="10" width="20" height="140" fill="gray" />
<polygon id="fundsTriangle" points="100,0 100,50 190,50" fill="steelblue" fill-opacity = 0.3 stroke = "black" />
<path d = "${paths[0]}" stroke = "green" fill = "none"/>
<path d = "${paths[1]}" stroke = "green" fill = "none"/>
<path d = "${paths[2]}" stroke = "green" fill = "none"/>
<path d = "${paths[3]}" stroke = "green" fill = "none"/>
<path d = "${paths[4]}" stroke = "green" fill = "none"/>
<path d = "${paths[5]}" stroke = "green" fill = "none"/>
<path d = "${paths[6]}" stroke = "green" fill = "none"/>
<path d = "${paths[7]}" stroke = "green" fill = "none"/>
</svg>
Insert cell
// paths = [
// "M172,40 V50 H190 L172,40",
// "M154,30 V50 H190 L154,30",
// "M136,20 V50 H190 L136,20",
// "M118,10 V50 H190 L118,10",
// "M100,0 V50 H190 L100,0"
// ]
Insert cell
paths = [
"M163,35 V50 H190 L163,35",
"M154,30 V50 H190 L154,30",
"M145,25 V50 H190 L145,25",
"M136,20 V50 H190 L136,20",
"M127,15 V50 H190 L127,15",
"M118,10 V50 H190 L118,10",
"M109,5 V50 H190 L109,5",
"M100,0 V50 H190 L100,0"
]
Insert cell
Insert cell
filteredDataTotalIMM = d3.sum(
newFilteredData.map((d) => d["Total IMM Amount"])
);
Insert cell
filteredDataTotalGrants = d3.sum(
newFilteredData.map((d) => d["TOTAL_CLOSING_AMOUNT"])
);
Insert cell
filteredDataTotalClosingDOB = d3.sum(
newFilteredData.map((d) => d["Closing Total DOB Amount"])
);

Insert cell
filteredDataCG = d3.sum(
newFilteredData.map((d) => d["Total CG Amount"])
);
Insert cell
filteredDataACG = d3.sum(
newFilteredData.map((d) => d["Total ACG Amunt"])
);

Insert cell
filteredDataEGA = d3.sum(
newFilteredData.map((d) => d["Total Elevation Amount"]))
Insert cell
xScale = d3.scaleLinear()
.domain([0, d3.sum(ChimneyData1, d => d.value)])
.range([0, 150]);
Insert cell
ChimneyData1 = [
{ label: "Compensation Grants", value: filteredDataCG},
{label : "Additional Grants", value: filteredDataACG},
{ label: "Total IMM", value: filteredDataTotalIMM },
{ label : "Elevation Grants", value : filteredDataEGA},
{ label: "Duplication Of Benefits", value: filteredDataTotalClosingDOB },
];
Insert cell
groupDataFunc(ChimneyData1)
Insert cell
function groupDataFunc(data) {
const percent = d3.scaleLinear()
.domain([0, d3.sum(data, d => d.value)])
.range([0, 100])
let cumulative = 0
const _data = data.map(d => {
cumulative += d.value
return {
value: d.value,
cumulative: cumulative - d.value,
label: d.label,
percent: percent(d.value)
}
}).filter(d => d.value > 0)
return _data
};
Insert cell
colors = [
"#4e79a7",
"#f28e2c",
"#e15759",
"#76b7b2",
// "#59a14f",
"#edc949",
"#af7aa1",
"#ff9da7",
"#9c755f",
"#bab0ab"
]
Insert cell
{
const width = 20;
const height = 150;
const svg = d3.create("svg").attr("width", 300).attr("height", 160);

const g = svg.selectAll("g")
.data(groupDataFunc(ChimneyData1))
.join("g");

g.append("rect")
.attr("x", 30)
.attr("y", d => xScale(d.cumulative))
.attr("width", 20)
.attr("height", d => xScale(d.value))
.style("fill", (d, i) => colors[i])
.append("title")
.text(d => `${d.label} : ${d.value}`);

return svg.node()
}
Insert cell
import { Legend, Swatches } from "@d3/color-legend"
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