Skip to content

Commit

Permalink
feat: dots cartogram
Browse files Browse the repository at this point in the history
  • Loading branch information
neocarto committed Feb 8, 2022
1 parent b6e1861 commit 49ba03a
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 20 deletions.
67 changes: 66 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ The *bubble* type is used to draw a map by proportional circles. [Source](https:
- <b>strokeWidth</b>: stroke width (default: 0.5)
- <b>fillOpacity</b>: fill opacity (default: 1)
- <b>dorling</b>: a boolean (default:false)
- <b>interation</b> an integer ti define the number of iteration for the Dorling method (default 200)
- <b>interation</b> an integer to define the number of iteration for the Dorling method (default 200)
- <b>tooltip</b> an array of 3 values defing what to display within the tooltip. The two first values indicates the name of a field in the properties. the third value is a string to indicates the unit (default:"")

Parameters of the legend
Expand Down Expand Up @@ -400,6 +400,71 @@ bertin.draw({

</details>


#### Dots cartogram

The *dotscartogram* type is a method of map representation that follows Dorling s cartograms and dot density maps. The data from each territorial unit are dissolved in such a way that a dot represents a constant quantity, the same on the whole map. [Example](https://observablehq.com/@neocartocnrs/bertin-js-dots-cartograms?collection=@neocartocnrs/bertin)

<details><summary>Code</summary>
~~~js

bertin.draw({
params: { projection: d3.geoBertin1953() },
layers: [
{
type: "dotscartogram",
geojson: data,
onedot: 200000000000,
iteration: 200,
values: "gdp",
radius: radius,
span: span,
leg_fill: "none",
leg_stroke: "black",
leg_strokeWidth: 1.5,
leg_x: 800,
leg_y: 450,
leg_title: `GDP by world region`,
leg_txt: "200 billion $",
fill: "red",
tooltip: ["name", "region", ""]
}
]
})
~~~
</details>

<details><summary>Parameters</summary>

- <b>geojson</b>: a geojson (<ins>compulsory<ins>)
- <b>values</b>: a string corresponding to the targeted variable in the properties(<ins>compulsory<ins>)
- <b>radius</b>: radius of dots (defaul:4)
- <b>nbmax</b>: number max of circles on the map (defaul:200)
- <b>onedot</b>: dot value (if onedot is filled, nbmax is useless)
- <b>span></b>: spacing between dots (default 0.5)
<b>fill </b>:
- <b>fill</b>: fill color (default: random color)
- <b>stroke</b>: stroke color (default: "white")
- <b>strokeWidth</b>: stroke width (default: 0.5)
- <b>fillOpacity</b>: fill opacity (default: 1)
- <b>tooltip</b> an array of 3 values defing what to display within the tooltip. The two first values indicates the name of a field in the properties. the third value is a string to indicates the unit (default:"")
- <b>interation</b> an integer to define the number of iteration for the Dorling method (default 200)

Parameters of the legend

- <b>leg_x</b>: position in x (if this value is not filled, the legend is not displayed)
- <b>leg_y</b>: position in y (if this value is not filled, the legend is not displayed)
- <b>leg_fill</b>: color of the circles (default: "none")
- <b>leg_stroke</b>: stroke of the circles (default: "black")
- <b>leg_strokeWidth</b>: stoke-width (default: 0.8)
- <b>leg_txtcol</b>: color of the texte (default: "#363636")
- <b>leg_title</b>: title of the legend (default var_data)
- <b>leg_txt</b>: text in the legend (default onedot value)
- <b>leg_fontSize</b>: title legend font size (default: 14)
- <b>leg_fontSize2</b>: text font size (default: 10)

</details>

#### Mushroom

The *mushroom* type is used to draw a map with 2 supperposed proportional semi-circles. This type of representation can be used when 2 data with the same order of magnitude need to be compressed. [Source](https://github.com/neocarto/bertin/blob/main/src/layer-mushroom.js) [Example](https://observablehq.com/d/3c51f698ba19546c?collection=@neocartocnrs/bertin)
Expand Down
6 changes: 3 additions & 3 deletions src/chorotypo.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as d3scale from "d3-scale";
const d3 = Object.assign({}, d3scalechromatic, d3scale, d3array);
import * as stat from "statsbreaks";

export function chorotypo(geojson, input){
export function chorotypo(features, input){
if (typeof input == "string")
return {
getcol: (d) => input
Expand All @@ -26,7 +26,7 @@ export function chorotypo(geojson, input){
}
if (breaks == null) {
breaks = stat.breaks({
values: geojson.features.map((d) => +d.properties[values]),
values: features.map((d) => +d.properties[values]),
method: method,
nb: nbreaks,
precision: leg_round
Expand Down Expand Up @@ -61,7 +61,7 @@ export function chorotypo(geojson, input){
let col_missing = input.col_missing ? input.col_missing : "#f5f5f5";

let types = Array.from(
new Set(geojson.features.map((d) => d.properties[values]))
new Set(features.map((d) => d.properties[values]))
);

if (colors == null) {
Expand Down
12 changes: 12 additions & 0 deletions src/draw.js
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@ if (layer.type == "label") {
geojson: layer.geojson,
values: layer.values,
radius: layer.radius,
nbmax: layer.nbmax,
onedot: layer.onedot,
span: layer.span,
fill: layer.fill,
Expand All @@ -393,6 +394,17 @@ if (layer.type == "label") {
fillOpacity: layer.fillOpacity,
iteration: layer.iteration,
tooltip: layer.tooltip,
leg_x: layer.leg_x,
leg_y: layer.leg_y,
leg_title: layer.leg_title,
leg_fontSize: layer.leg_fontSize,
leg_fontSize2: layer.leg_fontSize2,
leg_txtcol: layer.leg_txtcol,
leg_stroke: layer.leg_stroke,
leg_strokeWidth: layer.leg_strokeWidth,
leg_fill: layer.leg_fill,
leg_txt: layer.leg_txt

});
}

Expand Down
4 changes: 2 additions & 2 deletions src/layer-bubble.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,10 @@ export function bubble(selection, projection, clipid, options = {}){
)
.join("circle")
.attr("fill", (d) =>
chorotypo(geojson, fill).getcol(d.properties[fill.values] || undefined)
chorotypo(features, fill).getcol(d.properties[fill.values] || undefined)
)
.attr("stroke", (d) =>
chorotypo(geojson, stroke).getcol(d.properties[stroke.values] || undefined)
chorotypo(features, stroke).getcol(d.properties[stroke.values] || undefined)
)
.attr("stroke-width", strokeWidth)
.attr("fill-opacity", fillOpacity)
Expand Down
149 changes: 137 additions & 12 deletions src/layer-dotscartogram.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,18 @@ export function dotscartogram(selection, projection, clipid, options = {}){
"#66c2a5",
"#fc8d62",
"#8da0cb",
"#e78ac3",
"#a6d854",
"#ffd92f",
"#e5c494",
"#b3b3b3"
"#e78ac3"
];

let geojson = options.geojson;
let values = options.values;
let radius = options.radius ?? 4;
let onedot = options.onedot ?? Math.round(d3.sum(geojson.features.map((d) => +d.properties[values])) / 500)
let span = options.span ?? 0
let nbmax = options.nbmax ?? 200
let onedot = options.onedot ?? Math.round(d3.sum(geojson.features.map((d) => +d.properties[values])) / nbmax)
let span = options.span ?? 0.5
let fill = options.fill ?? cols[Math.floor(Math.random() * cols.length)];
let stroke = options.stroke ?? "white";
let strokeWidth = options.strokeWidth ?? 0.5;
let stroke = options.stroke ?? "none";
let strokeWidth = options.strokeWidth ?? 0;
let fillOpacity = options.fillOpacity ?? 1;
let tooltip = options.tooltip ?? "";
let iteration = options.iteration ?? 200;
Expand Down Expand Up @@ -87,9 +84,7 @@ export function dotscartogram(selection, projection, clipid, options = {}){
chorotypo(dots, fill).getcol(d.properties[fill.values] || undefined)
)
.attr("stroke", (d) =>
chorotypo(dots, stroke).getcol(
d.properties[stroke.values] || undefined
)
chorotypo(dots, stroke).getcol(d.properties[stroke.values] || undefined)
)
.attr("stroke-width", strokeWidth)
.attr("fill-opacity", fillOpacity)
Expand Down Expand Up @@ -128,4 +123,134 @@ export function dotscartogram(selection, projection, clipid, options = {}){
.attr("stroke-width", strokeWidth)
.attr("fill-opacity", fillOpacity);
});


// legend

const leg_x = options.leg_x ?? null
const leg_y = options.leg_y ?? null
const leg_title = options.leg_title ?? "leg_title"
const leg_fontSize = options.leg_fontSize ?? 14
const leg_fontSize2 = options.leg_fontSize2 ?? 10
const leg_txtcol = options.leg_txtcol ?? "#363636"
const leg_stroke = options.leg_stroke ?? stroke
const leg_strokeWidth = options.leg_strokeWidth ?? strokeWidth
const leg_fill = typeof fill == "string" ? fill : options.leg_fill
const leg_txt = options.leg_txt ?? onedot

if (leg_x != null && leg_y != null) {
let delta = 0
let leg = selection.append("g");
if (leg_title != null) {
delta = (leg_title.split("\n").length + 1) * leg_fontSize;
leg
.append("g")
.selectAll("text")
.data(leg_title.split("\n"))
.join("text")
.attr("x", leg_x)
.attr("y", leg_y)
.attr("font-size", `${leg_fontSize}px`)
.attr("dy", (d, i) => i * leg_fontSize)
.attr("text-anchor", "start")
.attr("dominant-baseline", "hanging")
.attr("fill", leg_txtcol)
.text((d) => d);
}
leg
.append("circle")
.attr("r", radius)
.attr("fill", leg_fill)
.attr("stroke", leg_stroke)
.attr("stroke-width", leg_strokeWidth)
.attr("cx",leg_x + radius)
.attr("cy", leg_y + radius * 2 + (leg_title.split("\n").length ) * leg_fontSize)


leg
.append("text")
.attr("fill", leg_txtcol)
.attr("font-size", `${leg_fontSize2}px`)
.attr("dominant-baseline", "middle")
.attr("x",leg_x + radius * 2 + leg_fontSize2)
.attr("y", leg_y + radius * 2 + (leg_title.split("\n").length) * leg_fontSize)
.text(leg_txt)

}

// legend (classes)

if (typeof fill == "object" && fill.type == "choro") {
legchoro(selection, {
x: fill.leg_x,
y: fill.leg_y,
w: fill.leg_w,
h: fill.leg_h,
stroke: fill.leg_stroke,
fillOpacity: fill.leg_fillOpacity,
strokeWidth: fill.leg_strokeWidth,
txtcol: fill.leg_txtcol,
title: fill.leg_title ? fill.leg_title : fill.values,
fontSize: fill.leg_fontSize,
fontSize2: fill.leg_fontSize2,
breaks: chorotypo(dots, fill).breaks,
colors: chorotypo(dots, fill).colors
});
}

if (typeof stroke == "object" && stroke.type == "choro") {
legchoro(selection, {
x: stroke.leg_x,
y: stroke.leg_y,
w: stroke.leg_w,
h: stroke.leg_h,
stroke: stroke.leg_stroke,
fillOpacity: stroke.leg_fillOpacity,
strokeWidth: stroke.leg_strokeWidth,
txtcol: stroke.leg_txtcol,
title: stroke.leg_title ? stroke.leg_title : stroke.values,
fontSize: stroke.leg_fontSize,
fontSize2: stroke.leg_fontSize2,
breaks: chorotypo(dots, stroke).breaks,
colors: chorotypo(dots, stroke).colors
});
}

if (typeof fill == "object" && fill.type == "typo") {
legtypo(selection, {
x: fill.leg_x,
y: fill.leg_y,
w: fill.leg_w,
h: fill.leg_h,
stroke: fill.leg_stroke,
fillOpacity: fill.leg_fillOpacity,
strokeWidth: fill.leg_strokeWidth,
txtcol: fill.leg_txtcol,
title: fill.leg_title ? fill.leg_title : fill.values,
fontSize: fill.leg_fontSize,
fontSize2: fill.leg_fontSize2,
types: chorotypo(dots, fill).types,
colors: chorotypo(dots, fill).colors
});
}

if (typeof stroke == "object" && fill.type == "stroke") {
legtypo(selection, {
x: stroke.leg_x,
y: stroke.leg_y,
w: stroke.leg_w,
h: stroke.leg_h,
stroke: stroke.leg_stroke,
fillOpacity: stroke.leg_fillOpacity,
strokeWidth: stroke.leg_strokeWidth,
txtcol: stroke.leg_txtcol,
title: stroke.leg_title ? fill.leg_title : fill.values,
fontSize: stroke.leg_fontSize,
fontSize2: stroke.leg_fontSize2,
types: chorotypo(dots, stroke).types,
colors: chorotypo(dots, stroke).colors
});
}


}
4 changes: 2 additions & 2 deletions src/layer-simple.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,10 @@ export function layersimple(selection, projection, clipid, geojson, options = {}
${symbol_shift ? d.y : projection(d.geometry.coordinates)[1]})`
)
.attr("fill", (d) =>
chorotypo(geojson, fill).getcol(d.properties[fill.values] || undefined)
chorotypo(geojson.features, fill).getcol(d.properties[fill.values] || undefined)
)
.attr("stroke", (d) =>
chorotypo(geojson, stroke).getcol(d.properties[stroke.values] || undefined)
chorotypo(geojson.features, stroke).getcol(d.properties[stroke.values] || undefined)
)
.attr("stroke-width", strokeWidth)
.attr("fill-opacity", fillOpacity)
Expand Down

0 comments on commit 49ba03a

Please sign in to comment.