-
Notifications
You must be signed in to change notification settings - Fork 187
/
Copy pathdimensions.js
148 lines (132 loc) · 5.27 KB
/
dimensions.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import {extent} from "d3";
import {projectionAspectRatio} from "./projection.js";
import {isOrdinalScale} from "./scales.js";
import {offset} from "./style.js";
export function createDimensions(scales, marks, options = {}) {
// Compute the default margins: the maximum of the marks’ margins. While not
// always used, they may be needed to compute the default height of the plot.
let marginTopDefault = 0.5 - offset,
marginRightDefault = 0.5 + offset,
marginBottomDefault = 0.5 + offset,
marginLeftDefault = 0.5 - offset;
for (const {marginTop, marginRight, marginBottom, marginLeft} of marks) {
if (marginTop > marginTopDefault) marginTopDefault = marginTop;
if (marginRight > marginRightDefault) marginRightDefault = marginRight;
if (marginBottom > marginBottomDefault) marginBottomDefault = marginBottom;
if (marginLeft > marginLeftDefault) marginLeftDefault = marginLeft;
}
// 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 : 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, options, {
width,
marginTopDefault,
marginRightDefault,
marginBottomDefault,
marginLeftDefault
}) + Math.max(0, marginTop - marginTopDefault + marginBottom - marginBottomDefault)
} = options;
// Coerce the width and height.
width = +width;
height = +height;
const dimensions = {
width,
height,
marginTop,
marginRight,
marginBottom,
marginLeft
};
// Compute the facet margins.
if (scales.fx || scales.fy) {
let {
margin: facetMargin,
marginTop: facetMarginTop = facetMargin !== undefined ? facetMargin : marginTop,
marginRight: facetMarginRight = facetMargin !== undefined ? facetMargin : marginRight,
marginBottom: facetMarginBottom = facetMargin !== undefined ? facetMargin : marginBottom,
marginLeft: facetMarginLeft = facetMargin !== undefined ? facetMargin : marginLeft
} = options.facet ?? {};
// Coerce the facet margin options to numbers.
facetMarginTop = +facetMarginTop;
facetMarginRight = +facetMarginRight;
facetMarginBottom = +facetMarginBottom;
facetMarginLeft = +facetMarginLeft;
dimensions.facet = {
marginTop: facetMarginTop,
marginRight: facetMarginRight,
marginBottom: facetMarginBottom,
marginLeft: facetMarginLeft
};
}
return dimensions;
}
function autoHeight(
{x, y, fy, fx},
{projection, aspectRatio},
{width, marginTopDefault, marginRightDefault, marginBottomDefault, marginLeftDefault}
) {
const nfy = fy ? fy.scale.domain().length || 1 : 1;
// If a projection is specified, compute an aspect ratio based on the domain,
// defaulting to the projection’s natural aspect ratio (if known).
const ar = projectionAspectRatio(projection);
if (ar) {
const nfx = fx ? fx.scale.domain().length : 1;
const far = ((1.1 * nfy - 0.1) / (1.1 * nfx - 0.1)) * ar; // 0.1 is default facet padding
const lar = Math.max(0.1, Math.min(10, far)); // clamp the aspect ratio to a “reasonable” value
return Math.round((width - marginLeftDefault - marginRightDefault) * lar + marginTopDefault + marginBottomDefault);
}
const ny = y ? (isOrdinalScale(y) ? y.scale.domain().length || 1 : Math.max(7, 17 / nfy)) : 1;
// If a desired aspect ratio is given, compute a default height to match.
if (aspectRatio != null) {
aspectRatio = +aspectRatio;
if (!(isFinite(aspectRatio) && aspectRatio > 0)) throw new Error(`invalid aspectRatio: ${aspectRatio}`);
const ratio = aspectRatioLength("y", y) / (aspectRatioLength("x", x) * aspectRatio);
const fxb = fx ? fx.scale.bandwidth() : 1;
const fyb = fy ? fy.scale.bandwidth() : 1;
const w = fxb * (width - marginLeftDefault - marginRightDefault) - x.insetLeft - x.insetRight;
return (ratio * w + y.insetTop + y.insetBottom) / fyb + marginTopDefault + marginBottomDefault;
}
return !!(y || fy) * Math.max(1, Math.min(60, ny * nfy)) * 20 + !!fx * 30 + 60;
}
function aspectRatioLength(k, scale) {
if (!scale) throw new Error(`aspectRatio requires ${k} scale`);
const {type, domain} = scale;
let transform;
switch (type) {
case "linear":
case "utc":
case "time":
transform = Number;
break;
case "pow": {
const exponent = scale.scale.exponent();
transform = (x) => Math.pow(x, exponent);
break;
}
case "log":
transform = Math.log;
break;
case "point":
case "band":
return domain.length;
default:
throw new Error(`unsupported ${k} scale for aspectRatio: ${type}`);
}
const [min, max] = extent(domain);
return Math.abs(transform(max) - transform(min));
}