Public
Edited
Apr 10, 2023
1 star
Insert cell
Insert cell
Insert cell
// making a selective view for color schemes

viewof colors = {
const form = html`<form><select name=i>${schemes.map(c => Object.assign(html`<option>`, {textContent: c.name}))}</select> <span style="font-size:smaller;font-style:oblique;">color scheme</output>`;
form.i.selectedIndex = 1;
form.oninput = () => form.value = schemes[form.i.selectedIndex].colors;
form.oninput();
return form;
}
Insert cell
chart = {
//making canvas with a predefined width and height
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);

//append the legend, define the proper location for the legend. the legend colors based on the filling colors in the map are defined in "legend" function.
svg.append(legend)
//define the proper location for the legend based on the size of canvas. like finding the location of the center of the canvas and based on that find the bottom or top corner location for the legend
.attr("transform", "translate(200,290)")
//define the legend properties for NAN values
.append(() =>
legend({
color: nullcolorforlegend,
title: "No Data",
width: 50,
tickFormat: ".1f"
})
);

//append the map and graphics to the canvas
svg.append("g")
.selectAll("path")
//append the Florida map from polygons features, define the attribute for the map (stroke color and thickness)
.data(topojson.feature(polygons, polygons.objects.Floda_WGS84).features)
.join("path")
.attr("stroke", "#636363")
//join the counties with values of two variables using the data dictionary and fill in the colors of the counties based on the values. In the data dictionary, each county (key) has two values (air quality and % urban population); using the "get" function, the values for each county are extracted
.attr("fill", d => color(data.get(d.properties[idAttribute])))
.attr("d", path)
.append("title")
//define the text for Hover over on the map. so from the data dictionary, the name of counties (idAttribute) and values and their classes are extracted and shown on the map
.text(d => `${d.properties[idAttribute]}, ${format(data.get(d.properties[idAttribute]))}`);

return svg.node();
}
Insert cell
Insert cell
// http://www.joshuastevens.net/cartography/make-a-bivariate-choropleth-map/
// defining four different color schemes for the map as a selective bar, so the users can choose different color schemes; each color scheme has nine colors as the number of classes for each variable is three and totally we have nine classes

schemes = [
{
name: "RdBu",
colors: [
"#e8e8e8", "#e4acac", "#c85a5a",
"#b0d5df", "#ad9ea5", "#985356",
"#64acbe", "#627f8c", "#574249"
]
},
{
name: "BuPu",
colors: [
"#e8e8e8", "#ace4e4", "#5ac8c8",
"#dfb0d6", "#a5add3", "#5698b9",
"#be64ac", "#8c62aa", "#3b4994"
]
},
{
name: "GnBu",
colors: [
"#e8e8e8", "#b5c0da", "#6c83b5",
"#b8d6be", "#90b2b3", "#567994",
"#73ae80", "#5a9178", "#2a5a5b"
]
},
{
name: "PuOr",
colors: [
"#e8e8e8", "#e4d9ac", "#c8b35a",
"#cbb8d7", "#c8ada0", "#af8e53",
"#9972af", "#976b82", "#804d36"
]
}
]
Insert cell
//The legend color for NAN values
nullcolorforlegend = d3.scaleOrdinal()
.domain([NaN])
.range([nancolor])
Insert cell
//color for NAN values
nancolor = d3.color("white")
Insert cell
// lables for each class
labels = ["low", "", "high"]
Insert cell
//the number of classes for each variable
n = Math.floor(Math.sqrt(colors.length))
Insert cell
//classify variable x based on quantile classification method
x = d3.scaleQuantile(Array.from(data.values(), d => d[0]), d3.range(n))
Insert cell
//classify variable y based on quantile classification method
y = d3.scaleQuantile(Array.from(data.values(), d => d[1]), d3.range(n))
Insert cell
path = d3.geoPath().projection(projection)
Insert cell
//define the Projection: transverse Mercator projection is suggested for Florida [http://www.radicalcartography.net/projections/state%20planes.gif]
//Rotate the map sets the longitude of origin for our UTM projection.
projection = d3.geoTransverseMercator().rotate([87,0]).fitExtent([[10, 10], [width, height]], polygon_features);
Insert cell
// reading the data as .csv file and making a data dictionary that assigns each county to the values of two variables
//Statrdatdize the urban population and use average value for the second variable

data = Object.assign(new Map(d3.csvParse(await FileAttachment("Floridadata@1.csv").text(), ({County, AverageDailyPM, UrbanPop, TotalPop}) => [County, [+AverageDailyPM, (+UrbanPop/+TotalPop)*100]])), {title: ["Air Quality", "%Urban population"]})
Insert cell
// The join attribute
idAttribute = "COUNTY"
Insert cell
//defining a variable that stores the features of polygons' objects in a nested array

polygon_features = topojson.feature(polygons, polygons.objects.Floda_WGS84)
Insert cell
//add the map of Florida as the polygons. This map should be in topojson format and in WGS84 coordinate system. In polygons, the geometry and attributes of each object are saved

polygons = FileAttachment("Floda_WGS84.json").json()
Insert cell
height = 410
Insert cell
width = 570
Insert cell
legend = () => {
// defining the size, rotation, orientation, font, and properties of labels for the legend
// K is the size of the legend and n is the number of classes for each variable (here=3)
//This parts of the code also define the filling colors in the legend
//Define the label for each variable in the legend based on variable's titles
const k = 20;
const arrow = DOM.uid();
return svg`<g font-family=sans-serif font-size=8>
<g transform="translate(-${k * n / 2},-${k * n / 2}) rotate(-45 ${k * n / 2},${k * n / 2})">
<marker id="${arrow.id}" markerHeight=10 markerWidth=10 refX=6 refY=3 orient=auto>
<path d="M0,0L9,3L0,6Z" />
</marker>
${d3.cross(d3.range(n), d3.range(n)).map(([i, j]) => svg`<rect width=${k} height=${k} x=${i * k} y=${(n - 1 - j) * k} fill=${colors[j * n + i]}>
<title>${data.title[0]}${labels[j] && ` (${labels[j]})`}
${data.title[1]}${labels[i] && ` (${labels[i]})`}</title>
</rect>`)}
<line marker-end="${arrow}" x1=0 x2=${n * k} y1=${n * k} y2=${n * k} stroke=black stroke-width=1 />
<line marker-end="${arrow}" y2=0 y1=${n * k} stroke=black stroke-width=1 />
<text font-weight="bold" dy="0.71em" transform="rotate(90) translate(${n / 2 * k},6)" text-anchor="middle">${data.title[0]}</text>
<text font-weight="bold" dy="0.71em" transform="translate(${n / 2 * k},${n * k + 6})" text-anchor="middle">${data.title[1]}</text>
</g>
</g>`;
}
Insert cell
//defining color function that assigns color into unit based on values of the variable
color = {
return value => {
if (!value) return "#ccc";
let [a, b] = value;
return colors[y(b) + x(a) * n];
};
}
Insert cell
//defining format for values shown on the map

formatNum = d3.format(".1f")
Insert cell
//defining the format function that assigns data labels (x and y labels)
// x and y represent the variables, and a and b are the values of each variable, respectively

format = (value) => {
if (!value) return "N/A";
let [a, b] = value;
return `${a} ${data.title[0]}${labels[x(a)] && ` (${labels[x(a)]})`}
${formatNum(b)} ${data.title[1]}${labels[y(b)] && ` (${labels[y(b)]})`}`;
}
Insert cell
topojson = require("topojson-client@3")
Insert cell
d3 = require("d3@5")
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more