diff --git a/src/dimensions.js b/src/dimensions.js index c6ae8fe263..c90be5279a 100644 --- a/src/dimensions.js +++ b/src/dimensions.js @@ -1,37 +1,65 @@ -import {isProjection} from "./projection.js"; +import {hasProjection} from "./projection.js"; import {isOrdinalScale} from "./scales.js"; import {offset} from "./style.js"; -export function Dimensions( - scales, - geometry, - {x: {axis: xAxis} = {}, y: {axis: yAxis} = {}, fx: {axis: fxAxis} = {}, fy: {axis: fyAxis} = {}}, - { - projection, - width = 640, - height = autoHeight(scales, geometry || isProjection(projection)), +export function Dimensions(scales, geometry, axes, options = {}) { + // The default margins depend on the presence and orientation of axes. If the + // corresponding scale is not present, the axis is necessarily null. + const {x: {axis: x} = {}, y: {axis: y} = {}, fx: {axis: fx} = {}, fy: {axis: fy} = {}} = axes; + + // Compute the default facet margins. When faceting is not present (and hence + // the fx and fy axis are null), these will all be zero. + let { facet: { margin: facetMargin, - marginTop: facetMarginTop = facetMargin !== undefined ? facetMargin : fxAxis === "top" ? 30 : 0, - marginRight: facetMarginRight = facetMargin !== undefined ? facetMargin : fyAxis === "right" ? 40 : 0, - marginBottom: facetMarginBottom = facetMargin !== undefined ? facetMargin : fxAxis === "bottom" ? 30 : 0, - marginLeft: facetMarginLeft = facetMargin !== undefined ? facetMargin : fyAxis === "left" ? 40 : 0 - } = {}, + marginTop: facetMarginTop = facetMargin !== undefined ? facetMargin : fx === "top" ? 30 : 0, + marginRight: facetMarginRight = facetMargin !== undefined ? facetMargin : fy === "right" ? 40 : 0, + marginBottom: facetMarginBottom = facetMargin !== undefined ? facetMargin : fx === "bottom" ? 30 : 0, + marginLeft: facetMarginLeft = facetMargin !== undefined ? facetMargin : fy === "left" ? 40 : 0 + } = {} + } = options; + + // Coerce the facet margin options to numbers. + facetMarginTop = +facetMarginTop; + facetMarginRight = +facetMarginRight; + facetMarginBottom = +facetMarginBottom; + facetMarginLeft = +facetMarginLeft; + + // Compute the default margins; while not always used, they may be needed to + // compute the default height of the plot. + const marginTopDefault = Math.max((x === "top" ? 30 : 0) + facetMarginTop, y || fy ? 20 : 0.5 - offset); + const marginBottomDefault = Math.max((x === "bottom" ? 30 : 0) + facetMarginBottom, y || fy ? 20 : 0.5 + offset); + const marginRightDefault = Math.max((y === "right" ? 40 : 0) + facetMarginRight, x || fx ? 20 : 0.5 + offset); + const marginLeftDefault = Math.max((y === "left" ? 40 : 0) + facetMarginLeft, x || fx ? 20 : 0.5 - offset); + + // Compute the actual margins. The order of precedence is: the side-specific + // margin options, then the global margin option, then the defaults. + let { margin, - marginTop = margin !== undefined - ? margin - : Math.max((xAxis === "top" ? 30 : 0) + facetMarginTop, yAxis || fyAxis ? 20 : 0.5 - offset), - marginRight = margin !== undefined - ? margin - : Math.max((yAxis === "right" ? 40 : 0) + facetMarginRight, xAxis || fxAxis ? 20 : 0.5 + offset), - marginBottom = margin !== undefined - ? margin - : Math.max((xAxis === "bottom" ? 30 : 0) + facetMarginBottom, yAxis || fyAxis ? 20 : 0.5 + offset), - marginLeft = margin !== undefined - ? margin - : Math.max((yAxis === "left" ? 40 : 0) + facetMarginLeft, xAxis || fxAxis ? 20 : 0.5 - offset) - } = {} -) { + marginTop = margin !== undefined ? margin : marginTopDefault, + marginRight = margin !== undefined ? margin : marginRightDefault, + marginBottom = margin !== undefined ? margin : marginBottomDefault, + marginLeft = margin !== undefined ? margin : marginLeftDefault + } = options; + + // Coerce the margin options to numbers. + marginTop = +marginTop; + marginRight = +marginRight; + marginBottom = +marginBottom; + marginLeft = +marginLeft; + + // Compute the outer dimensions of the plot. If the top and bottom margins are + // specified explicitly, adjust the automatic height accordingly. + let { + width = 640, + height = autoHeight(scales, geometry || hasProjection(options)) + + Math.max(0, marginTop - marginTopDefault + marginBottom - marginBottomDefault) + } = options; + + // Coerce the width and height. + width = +width; + height = +height; + return { width, height, diff --git a/src/projection.js b/src/projection.js index cde9c81f0d..b8dfa386df 100644 --- a/src/projection.js +++ b/src/projection.js @@ -26,7 +26,7 @@ export function maybeProjection(projection, dimensions) { return projection?.({...dimensions, ...options}); } -export function isProjection(projection) { +export function hasProjection({projection} = {}) { if (projection == null) return false; if (typeof projection.stream === "function") return true; // d3 projection if (isObject(projection)) ({type: projection} = projection); diff --git a/test/output/downloadsOrdinal.svg b/test/output/downloadsOrdinal.svg index 3cfe7b0905..c9be7271f4 100644 --- a/test/output/downloadsOrdinal.svg +++ b/test/output/downloadsOrdinal.svg @@ -1,4 +1,4 @@ - + - + 0 - + 2 - + 4 - + 6 - + 8 - + 10 - + 12 - + 14 - + 16 - + 18 - + 20 22 ↑ downloads - + Jan 01 @@ -192,64 +192,64 @@ date - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - + \ No newline at end of file diff --git a/test/output/ibmTrading.svg b/test/output/ibmTrading.svg index 4c50d57c47..2f6b391498 100644 --- a/test/output/ibmTrading.svg +++ b/test/output/ibmTrading.svg @@ -1,4 +1,4 @@ - + - + 0 - + 2 - + 4 - + 6 - + 8 - + 10 - + 12 - + 14 - + 16 - + 18 - + 20 ↑ Volume (USD, millions) - + 2018-04-16 @@ -140,25 +140,25 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plots/dot-sort.js b/test/plots/dot-sort.js index b6f0cc30e0..f9d64dccd4 100644 --- a/test/plots/dot-sort.js +++ b/test/plots/dot-sort.js @@ -5,7 +5,7 @@ export default async function () { const x = [..."ABDCEFGH"]; const r = [30, 60, 20, 20, 35, 22, 20, 28]; const options = {x, r, stroke: "black", fill: x, fillOpacity: 0.8}; - const p = {width: 300, margin: 50, axis: null, r: {type: "identity"}}; + const p = {width: 300, axis: null, r: {type: "identity"}, x: {inset: 50}, margin: 0}; return html` ${Plot.dot(x, options).plot({...p, caption: "default sort (r desc)"})}