@@ -19,154 +19,128 @@ A powerful, lightweight visualization for COM-480 built with Hugo, Tailwind CSS.
1919- [ Languages] ( visualizations/languages/ ) : see the distribution of books by language as a waffle chart.
2020- [ Publishers] ( visualizations/publishers/ ) : compare gender representation across major publishers.
2121- [ Library] ( visualizations/library/ ) : browse and filter books on an interactive shelf.
22-
23- ### Scale countries by amount of books published by author's from these countries
24-
25-
26- ## Cartogram
27-
28- <!-- prettier-ignore-start -->
29- <script src =" https://unpkg.com/topojson@3/dist/topojson.min.js " ></script >
30- <script src =" {{< asset-url " js /cartogram.js" >}}" ></script>
31-
32- {{< d3 >}}
33- const width = container.clientWidth;
34- const height = 500;
35- const svg = d3.select(container).append(" svg" )
36- .attr(" width" , width)
37- .attr(" height" , height);
38-
39- const genderLabels = {
40- " m" : " Male" ,
41- " w" : " Female" ,
42- " w;m" : " Female & Male" ,
43- " m;m" : " Male & Male" ,
44- " m;m;m" : " Male & Male & Male" ,
45- " w;w" : " Female & Female"
46- };
47- const genders = Object.keys(genderLabels);
48- const isDark = document.documentElement.classList.contains(" dark" );
49- const labelColor = isDark ? congoColors.neutral100 : " #000000 " ;
50-
51- const selector = d3.select(container).insert(" div" , " svg" )
52- .style(" margin- bottom" , " 10px " );
53- selector.append(" label" )
54- .text(" Select gender: " )
55- .style(" color" , labelColor);
56- const select = selector.append(" select" )
57- .style(" margin- bottom" , " 8px " )
58- .style(" padding" , " 6px 12px " )
59- .style(" background" , congoColors.primary400)
60- .style(" color" , congoColors.neutral100)
61- .style(" border" , " none" )
62- .style(" border- radius" , " 4px " )
63- .style(" cursor" , " pointer" );
64-
65- Promise.all([
66- d3.json(" https: // cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json"),
67- d3 .csv (" {{< asset-url " data/ nationalities .csv " >}}" )
68- ]).then (([topology , data ]) => {
69- const dataById = new Map (
70- data
71- .filter (d => d[" ID" ] && d[" ID" ] !== " nan" )
72- .map (d => [+ d[" ID" ], { counts: + d[" counts" ], name: d[" nationality" ] }])
73- );
74- const nameById = new Map (
75- topology .objects .countries .geometries .map (d => [+ d .id , d .properties ? .name ?? " Unknown" ])
76- );
77-
78-
79- // Split geometries
80- const problematicIDs = [10 , 643 , 242 ]; // Antarctica, Russia
81- const cartoGeometries = topology .objects .countries .geometries
82- .filter (d => ! problematicIDs .includes (+ d .id ));
83-
84- const staticGeometries = topology .objects .countries .geometries
85- .filter (d => problematicIDs .includes (+ d .id )); // Russia yes, Antarctica no
86-
87-
88- const values = cartoGeometries .map (d => dataById .get (+ d .id )? .counts ?? 0 );
89-
90- const lo = d3 .min (values), hi = d3 .max (values);
91-
92- const scale = d3 .scaleLinear ().domain ([lo, hi]).range ([1 , 20 ]);
93- const colorScale = d3 .scaleSequentialLog (d3 .interpolate (
94- congoColors .neutral100 ,
95- congoColors .primary500
96- )).domain ([1 , hi]);
97-
98- const color = d => {
99- const counts = dataById .get (+ d .id )? .counts ?? 0 ;
100- return counts === 0 ? congoColors .neutral100 : colorScale (counts);
101- };
102-
103- // Tooltip handlers
104- const onMouseover = (event , d ) => {
105- const id = + d .id ;
106- const entry = dataById .get (id);
107- const name = entry? .name ?? nameById .get (id) ?? " Unknown" ;
108- const counts = entry? .counts ?? 0 ;
109- tooltip .style (" opacity" , 1 )
110- .html (` <strong>${ name} </strong><br/>${ counts} book${ counts !== 1 ? " s" : " " } ` );
111- };
112-
113- const onMousemove = (event ) => {
114- const [x , y ] = d3 .pointer (event , container);
115- tooltip
116- .style (" left" , ` ${ x + 12 } px` )
117- .style (" top" , ` ${ y - 28 } px` );
118- };
119-
120- const onMouseout = () => tooltip .style (" opacity" , 0 );
121-
122- carto
123- .properties (d => ({ id: d .ID }))
124- .value (d => scale (dataById .get (+ d .id )? .counts ?? 0 ));
125-
126- const features = carto (topology, cartoGeometries).features ;
127- const cartoFeatures = carto (topology, cartoGeometries).features ;
128- const normalFeatures = cartoGeometries .map (d => topojson .feature (topology, d));
129-
130- // Draw exluded countries because of anti-meridian as static layer first
131- svg .selectAll (" .static-country" )
132- .data (staticGeometries .map (d => topojson .feature (topology, d)))
133- .enter ().append (" path" )
134- .attr (" class" , " static-country" )
135- .attr (" d" , staticPath)
136- .attr (" fill" , color)
137- .attr (" stroke" , congoColors .neutral100 )
138- .attr (" stroke-width" , 0.5 )
139- .on (" mouseover" , onMouseover)
140- .on (" mousemove" , onMousemove)
141- .on (" mouseout" , onMouseout);
142-
143- // Draw cartogram countries
144- const paths = svg .selectAll (" .carto-country" )
145- .data (cartoFeatures)
146- .enter ().append (" path" )
147- .attr (" class" , " carto-country" )
148- .attr (" d" , carto .path )
149- .attr (" fill" , color)
150- .attr (" stroke" , congoColors .neutral100 )
151- .attr (" stroke-width" , 0.5 )
152- .on (" mouseover" , onMouseover)
153- .on (" mousemove" , onMousemove)
154- .on (" mouseout" , onMouseout);
155-
156- // Toggle between distorted and normal
157- button .on (" click" , () => {
158- distorted = ! distorted;
159- button .text (distorted ? " Show Normal Map" : " Show Cartogram" );
160-
161- paths .transition ().duration (750 )
162- .attr (" d" , (d , i ) => distorted
163- ? carto .path (d)
164- : staticPath (normalFeatures[i])
165- );
166- });
167-
168- }).catch (err => {
169- console .error (" Error loading data:" , err);
170- });
171- {{< / d3 > }}
172- <!-- prettier- ignore- end -->
22+ ## Scale countries by amount of books published by author's from these countries
23+ # Cartogram
24+ !-- prettier-ignore-start -->
25+ script src="https://unpkg.com/topojson@3/dist/topojson.min.js"> </script >
26+ script src="{{< asset-url "js/cartogram.js" >}}"></script >
27+ {< d3 >}}
28+ onst width = container.clientWidth;
29+ onst height = 500;
30+ onst svg = d3.select(container).append("svg")
31+ .attr("width", width)
32+ .attr("height", height);
33+ onst genderLabels = {
34+ "m": "Male",
35+ "w": "Female",
36+ "w;m": "Female & Male",
37+ "m;m": "Male & Male",
38+ "m;m;m": "Male & Male & Male",
39+ "w;w": "Female & Female"
40+ ;
41+ onst genders = Object.keys(genderLabels);
42+ onst isDark = document.documentElement.classList.contains("dark");
43+ onst labelColor = isDark ? congoColors.neutral100 : "#000000 ";
44+ onst selector = d3.select(container).insert("div", "svg")
45+ .style("margin-bottom", "10px");
46+ elector.append("label")
47+ .text("Select gender: ")
48+ .style("color", labelColor);
49+ onst select = selector.append("select")
50+ .style("margin-bottom", "8px")
51+ .style("padding", "6px 12px")
52+ .style("background", congoColors.primary400)
53+ .style("color", congoColors.neutral100)
54+ .style("border", "none")
55+ .style("border-radius", "4px")
56+ .style("cursor", "pointer");
57+ romise.all([
58+ d3.json("https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json "),
59+ d3.csv("{{< asset-url "data/nationalities.csv" >}}")
60+ ).then(([ topology, data] ) => {
61+ const dataById = new Map(
62+ data
63+ .filter(d => d[ "ID"] && d[ "ID"] !== "nan")
64+ .map(d => [ +d[ "ID"] , { counts: +d[ "counts"] , name: d[ "nationality"] }] )
65+ );
66+ const nameById = new Map(
67+ topology.objects.countries.geometries.map(d => [ +d.id, d.properties?.name ?? "Unknown"] )
68+ );
69+ // Split geometries
70+ const problematicIDs = [ 10, 643, 242] ; // Antarctica, Russia
71+ const cartoGeometries = topology.objects.countries.geometries
72+ .filter(d => !problematicIDs.includes(+d.id));
73+ const staticGeometries = topology.objects.countries.geometries
74+ .filter(d => problematicIDs.includes(+d.id)); // Russia yes, Antarctica no
75+ const values = cartoGeometries.map(d => dataById.get(+d.id)?.counts ?? 0);
76+ const lo = d3.min(values), hi = d3.max(values);
77+ const scale = d3.scaleLinear().domain([ lo, hi] ).range([ 1, 20] );
78+ const colorScale = d3.scaleSequentialLog(d3.interpolate(
79+ congoColors.neutral100,
80+ congoColors.primary500
81+ )).domain([ 1, hi] );
82+ const color = d => {
83+ const counts = dataById.get(+d.id)?.counts ?? 0;
84+ return counts === 0 ? congoColors.neutral100 : colorScale(counts);
85+ };
86+ // Tooltip handlers
87+ const onMouseover = (event, d) => {
88+ const id = +d.id;
89+ const entry = dataById.get(id);
90+ const name = entry?.name ?? nameById.get(id) ?? "Unknown";
91+ const counts = entry?.counts ?? 0;
92+ tooltip.style("opacity", 1)
93+ .html(` <strong>${name}</strong><br/>${counts} book${counts !== 1 ? "s" : ""} ` );
94+ };
95+ const onMousemove = (event) => {
96+ const [ x, y] = d3.pointer(event, container);
97+ tooltip
98+ .style("left", ` ${x + 12}px ` )
99+ .style("top", ` ${y - 28}px ` );
100+ };
101+ const onMouseout = () => tooltip.style("opacity", 0);
102+ carto
103+ .properties(d => ({ id: d.ID }))
104+ .value(d => scale(dataById.get(+d.id)?.counts ?? 0));
105+ const features = carto(topology, cartoGeometries).features;
106+ const cartoFeatures = carto(topology, cartoGeometries).features;
107+ const normalFeatures = cartoGeometries.map(d => topojson.feature(topology, d));
108+ // Draw exluded countries because of anti-meridian as static layer first
109+ svg.selectAll(".static-country")
110+ .data(staticGeometries.map(d => topojson.feature(topology, d)))
111+ .enter().append("path")
112+ .attr("class", "static-country")
113+ .attr("d", staticPath)
114+ .attr("fill", color)
115+ .attr("stroke", congoColors.neutral100)
116+ .attr("stroke-width", 0.5)
117+ .on("mouseover", onMouseover)
118+ .on("mousemove", onMousemove)
119+ .on("mouseout", onMouseout);
120+ // Draw cartogram countries
121+ const paths = svg.selectAll(".carto-country")
122+ .data(cartoFeatures)
123+ .enter().append("path")
124+ .attr("class", "carto-country")
125+ .attr("d", carto.path)
126+ .attr("fill", color)
127+ .attr("stroke", congoColors.neutral100)
128+ .attr("stroke-width", 0.5)
129+ .on("mouseover", onMouseover)
130+ .on("mousemove", onMousemove)
131+ .on("mouseout", onMouseout);
132+ // Toggle between distorted and normal
133+ button.on("click", () => {
134+ distorted = !distorted;
135+ button.text(distorted ? "Show Normal Map" : "Show Cartogram");
136+ paths.transition().duration(750)
137+ .attr("d", (d, i) => distorted
138+ ? carto.path(d)
139+ : staticPath(normalFeatures[ i] )
140+ );
141+ });
142+ ).catch(err => {
143+ console.error("Error loading data:", err);
144+ );
145+ {< /d3 >}}
146+ !-- prettier-ignore-end -->
0 commit comments