-
Notifications
You must be signed in to change notification settings - Fork 185
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
geometries and projections #1111
Conversation
@@ -206,7 +207,7 @@ export function ScaleQuantize( | |||
if (min instanceof Date) thresholds = thresholds.map((x) => new Date(x)); // preserve date types | |||
} | |||
if (order(arrayify(domain)) < 0) thresholds.reverse(); // preserve descending domain | |||
return ScaleThreshold(key, channels, {domain: thresholds, range, reverse}); | |||
return ScaleThreshold(key, channels, {domain: thresholds, range, reverse, unknown}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fixes an (unfiled) bug where the quantize scale didn’t respect the unknown option.
@@ -140,7 +141,7 @@ function applyMultilineText(selection, {monospace, lineAnchor, lineHeight, lineW | |||
selection.each(function (i) { | |||
const lines = linesof(formatDefault(T[i])); | |||
const n = lines.length; | |||
const y = lineAnchor === "top" ? 0.71 : lineAnchor === "bottom" ? -0.29 - n : (164 - n * 100) / 200; | |||
const y = lineAnchor === "top" ? 0.71 : lineAnchor === "bottom" ? 1 - n : (164 - n * 100) / 200; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This reverts #1061.
const {x: X, y: Y, rotate: R, text: T, fontSize: FS} = channels; | ||
const {rotate} = this; | ||
const [cx, cy] = applyFrameAnchor(this, dimensions); | ||
return create("svg:g", context) | ||
.call(applyIndirectStyles, this, scales, dimensions) | ||
.call(applyIndirectTextStyles, this, T, dimensions) | ||
.call(applyTransform, this, scales) | ||
.call(applyTransform, this, {x: X && x, y: Y && y}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fixes #1043.
Would it make sense to call it Plot.geo rather than Plot.geometries? Shorter and more evocative of maps. |
That’d be fine, especially since it’s inherently tied to GeoJSON. |
Example using normal marks (dot, text) with a projection: Plot.plot({
width: 960,
height: 600,
projection: "albers-usa",
marks: [
Plot.geometry(states, {fill: "#ccc"}),
Plot.geometry(statemesh, {stroke: "white"}),
Plot.dot(capitals, {x: "longitude", y: "latitude", fill: "currentColor"}),
Plot.text(capitals, {
x: "longitude",
y: "latitude",
frameAnchor: "bottom",
text: (d) => `${d.capital}\n${d.state}`,
dy: -6
})
]
}) |
Example using hexbin initializer with a projection: Plot.plot({
width: 960,
height: 600,
projection: "albers-usa",
color: {
legend: true,
label: "First year opened",
scheme: "spectral"
},
r: {
range: [0, 20]
},
marks: [
Plot.geometry(statemesh, {strokeOpacity: 0.25, strokeWidth: 1}),
Plot.dot(
walmarts,
Plot.hexbin(
{r: "count", fill: "min", title: "min"},
{x: "longitude", y: "latitude", fill: "date", stroke: "white", title: "date"}
)
)
]
}) |
One thing that comes to mind is we'll want to expose the projection definition so we can share it between different maps, and make it easier to augment the current map. Maybe this could follow the same pattern as scales: chart.scale("projection") would expose the projection's parameters, and apply and invert functions. |
Yeah, we could do that. I’m not sure if we should call the projection a scale but they are certainly similar. For now I don’t think this is particularly urgent because there is little internal configuration of the projection (the only thing that affects it is the width and height of the chart); as long as you specify the same projection type you’ll get the same behavior across charts. |
I've tried to port all my maps from the Plot.carto 0.5 notebook to this branch, and took notes. It works generally very well, and is a painless transition: see https://observablehq.com/@fil/plot-geometries-1111 Only two things didn't work:
Beyond these, porting the maps was for the most part a no-brainer. One issue was slowing me down consistently though: scaling. In general adjusting the scale to fit the map to the frame is hard—and sometimes it might be impossible to compute in advance (for example when the frame's size depends on the number of facets);
Usage (As I already mentioned), repeatedly having to type Plot.geometry was tripping me up. I hesitated with Plot.geometries (since we sometimes have one feature, or several features to draw). Having Plot.geo instead would be a no-brainer. I was very happy with the fact that sending a single feature as data, instead of an array, worked. But I'm not sure if this should be done on all the marks? i.e. Plot.mark(Object, options)? Using the x and y channels for longitude and latitude in Plot.dot is a bit disconcerting at first (in particular if your map is not "horizontal"). However it's better than any alternative I've considered, in particular because it makes transitioning a chart for "rectangular" to "projected" a one-line business. Ideas for future iterations: More projections. The default list of projections is a bit restricted. I'm not advocating to embed all of d3-geo-projections and d3-geo-polygon… but I'd like it to be easier to extend… maybe with some kind of plugin that would register projection names. (Maybe related to theming-extending color schemes #630.) Exposing a projection as a scale. (As already mentioned,) we'll have to think of a way to expose/share a scale's definition. If we push people to define projections with D3, it might be hard to create the object that allows to recreate the same projection somewhere else. If we create a small DSL for projections, it should cover more than the handful that is known to d3-geo (see also https://vega.github.io/vega-lite/docs/projection.html#projection-types). More graticule options. Add graticule (step) options in Plot.graticule. Random remarks Default dimensions. Why do |
Addendum: I like the idea of string accessors for properties, but I'm not sure how we would disambiguate between "Population" as an accessor for d => d.properties.Population and "id" for d => d.id. |
Co-authored-by: Philippe Rivière <[email protected]>
Co-authored-by: Philippe Rivière <[email protected]>
I implemented the error on marks requiring band scales in d0e41cb. We could throw a similar error for marks that define channels bound to either the x or y scale, but don’t provide exactly the x and y channels. |
Added another error for non-point marks in cf8476e. |
So, the only thing left is whether we call it Plot.geo or Plot.geometry? 😅 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
I'm with geo, but it's your call ultimately :)
Do you have an opinion on the channel name being “geo” or “geometry”? |
Most of the time we won't have to use it, so I don't mind if it's longer to type or look up. Having a geo mark and a geometry channel might help disambiguate? |
What do you think of calling it “geojson” (for both the mark and the channel)? Is that better or worse than “geometry”? E.g., Plot.geojson(statemesh, {strokeOpacity: 0.3}) |
yes, geojson would be fine. |
Amazing development! |
I love how f63a8eb skips invisible points (e.g. on the other side of the orthographic projection). |
TODO:
Potential future enhancements (but not now):
"name"
becomesd => d.properties["name"]
)?Fixes #1043.