Shape模組提供各種形狀的生成器,這些形狀的產生是資料驅動的,通過控制輸入資料來形成一種視覺的表現。
Pies
餅圖生成器不直接產生圖形,而是計算出需要的角度資訊,然後傳入d3.arc
中進行繪製。
// 繪製餅圖
function pie() {
var value = identity$1,
sortValues = descending$1,
sort = null,
startAngle = constant$1(0),
endAngle = constant$1(tau$2),
padAngle = constant$1(0);
function pie(data) {
var i,
n = data.length,
j,
k,
//統計data陣列中的資料和
sum = 0,
index = new Array(n),
arcs = new Array(n),
a0 = +startAngle.apply(this, arguments),
//將|endAngle - startAngle|限定在 2 * PI之間
da = Math.min(tau$2, Math.max(-tau$2, endAngle.apply(this, arguments) - a0)),
a1,
//限定padAngle的範圍
p = Math.min(Math.abs(da) / n, padAngle.apply(this, arguments)),
// da<0表示弧線為逆時針方向,pa的值也應進行相應處理
pa = p * (da < 0 ? -1 : 1),
v;
for (i = 0; i < n; ++i) {
if ((v = arcs[index[i] = i] = +value(data[i], i, data)) > 0) {
sum += v;
}
}
// 按照處理後的arcs資料大小對index進行排序,或者直接對data進行排序
if (sortValues != null) index.sort(function(i, j) { return sortValues(arcs[i], arcs[j]); });
else if (sort != null) index.sort(function(i, j) { return sort(data[i], data[j]); });
// 計算arcs,按照排序後的index來逐個計算
for (i = 0, k = sum ? (da - n * pa) / sum : 0; i < n; ++i, a0 = a1) {
j = index[i], v = arcs[j], a1 = a0 + (v > 0 ? v * k : 0) + pa, arcs[j] = {
data: data[j],
index: i,
value: v,
startAngle: a0,
endAngle: a1,
padAngle: p
};
}
return arcs;
}
//設定value函式或數值,value函式會被依次傳入data[i]、i和data。
pie.value = function(_) {
return arguments.length ? (value = typeof _ === "function" ? _ : constant$1(+_), pie) : value;
};
//設定數值的排序方式
pie.sortValues = function(_) {
return arguments.length ? (sortValues = _, sort = null, pie) : sortValues;
};
pie.sort = function(_) {
return arguments.length ? (sort = _, sortValues = null, pie) : sort;
};
pie.startAngle = function(_) {
return arguments.length ? (startAngle = typeof _ === "function" ? _ : constant$1(+_), pie) : startAngle;
};
pie.endAngle = function(_) {
return arguments.length ? (endAngle = typeof _ === "function" ? _ : constant$1(+_), pie) : endAngle;
};
pie.padAngle = function(_) {
return arguments.length ? (padAngle = typeof _ === "function" ? _ : constant$1(+_), pie) : padAngle;
};
return pie;
}
複製程式碼
Lines
可以產生樣條曲線或者多段線。
d3.line
預設的設定是構造多條直線段。
//d3.line(),繪製多條直線段,中間可能斷開
function line() {
var x$$ = x,
y$$ = y,
defined = constant$1(true),
context = null,
curve = curveLinear,
output = null;
function line(data) {
var i,
n = data.length,
d,
defined0 = false,
buffer;
if (context == null) output = curve(buffer = path());
for (i = 0; i <= n; ++i) {
if (!(i < n && defined(d = data[i], i, data)) === defined0) {
if (defined0 = !defined0) output.lineStart();
else output.lineEnd();
}
if (defined0) output.point(+x$$(d, i, data), +y$$(d, i, data));
}
// 返回path的計算結果
if (buffer) return output = null, buffer + "" || null;
}
// 設定獲取x的函式
line.x = function(_) {
return arguments.length ? (x$$ = typeof _ === "function" ? _ : constant$1(+_), line) : x$$;
};
// 設定獲取y的函式
line.y = function(_) {
return arguments.length ? (y$$ = typeof _ === "function" ? _ : constant$1(+_), line) : y$$;
};
// defined函式用於判斷當前點是否已被定義,若為true,則會計算x、y座標和繪製直線;否則會跳過當前點,結束當前直線的繪製。
line.defined = function(_) {
return arguments.length ? (defined = typeof _ === "function" ? _ : constant$1(!!_), line) : defined;
};
// 設定curve函式
line.curve = function(_) {
return arguments.length ? (curve = _, context != null && (output = curve(context)), line) : curve;
};
// 設定context
line.context = function(_) {
return arguments.length ? (_ == null ? context = output = null : output = curve(context = _), line) : context;
};
return line;
}
複製程式碼
d3.radialLine
構造放射線,與上述d3.line
類似,只是將x、y函式替換成角度和半徑函式,並且改放射線總是相對於(0, 0)進行繪製。
// d3.radialLine,在d3.line的基礎上進行修改,主要的差別是curve函式和座標系。
function radialLine$1() {
return radialLine(line().curve(curveRadialLinear));
}
// 構造放射線,分別構造線性和放射狀曲線
var curveRadialLinear = curveRadial(curveLinear);
//線性的curve函式
function curveLinear(context) {
return new Linear(context);
}
//將當前curve函式包裝成放射狀curve
function curveRadial(curve) {
function radial(context) {
return new Radial(curve(context));
}
radial._curve = curve;
return radial;
}
// 將line中的(x, y)座標替換成(angle, radius)
function radialLine(l) {
var c = l.curve;
l.angle = l.x, delete l.x;
l.radius = l.y, delete l.y;
l.curve = function(_) {
return arguments.length ? c(curveRadial(_)) : c()._curve;
};
return l;
}
複製程式碼
Areas
用於產生一塊區域。
d3.area
/* d3.area
* 首先根據x1, y1函式進行繪製,完成後根據x0, y0函式來反方向繪製,整個圖形的繪製過程是順時針方向。
* 預設繪製的是x0 = x1,y0 = 0的一塊區域。
*/
function area$1() {
var x0 = x,
x1 = null,
y0 = constant$1(0),
y1 = y,
defined = constant$1(true),
context = null,
curve = curveLinear,
output = null;
function area(data) {
var i,
j,
k,
n = data.length,
d,
defined0 = false,
buffer,
x0z = new Array(n),
y0z = new Array(n);
if (context == null) output = curve(buffer = path());
for (i = 0; i <= n; ++i) {
if (!(i < n && defined(d = data[i], i, data)) === defined0) {
//defined0由false變為true時,表示繪製開始,相反表示繪製結束
if (defined0 = !defined0) {
j = i;
output.areaStart();
output.lineStart();
} else {
output.lineEnd();
output.lineStart();
//反向繪製x0z, y0z,繪製方向為順時針方向
for (k = i - 1; k >= j; --k) {
output.point(x0z[k], y0z[k]);
}
output.lineEnd();
// 關閉繪製區域
output.areaEnd();
}
}
//defined0為true時可以繪製
if (defined0) {
x0z[i] = +x0(d, i, data), y0z[i] = +y0(d, i, data);
//優先使用x1和y1函式進行計算
output.point(x1 ? +x1(d, i, data) : x0z[i], y1 ? +y1(d, i, data) : y0z[i]);
}
}
if (buffer) return output = null, buffer + "" || null;
}
// 返回與當前area有相同defined、curve和context的line構造器
function arealine() {
return line().defined(defined).curve(curve).context(context);
}
// 設定x函式,將該函式賦值給x0,null賦值給x1
area.x = function(_) {
return arguments.length ? (x0 = typeof _ === "function" ? _ : constant$1(+_), x1 = null, area) : x0;
};
area.x0 = function(_) {
return arguments.length ? (x0 = typeof _ === "function" ? _ : constant$1(+_), area) : x0;
};
area.x1 = function(_) {
return arguments.length ? (x1 = _ == null ? null : typeof _ === "function" ? _ : constant$1(+_), area) : x1;
};
// 設定y函式,將該函式賦值給y0,null賦值給y1
area.y = function(_) {
return arguments.length ? (y0 = typeof _ === "function" ? _ : constant$1(+_), y1 = null, area) : y0;
};
area.y0 = function(_) {
return arguments.length ? (y0 = typeof _ === "function" ? _ : constant$1(+_), area) : y0;
};
area.y1 = function(_) {
return arguments.length ? (y1 = _ == null ? null : typeof _ === "function" ? _ : constant$1(+_), area) : y1;
};
// 分別對line構造器設定x和y函式
area.lineX0 =
area.lineY0 = function() {
return arealine().x(x0).y(y0);
};
area.lineY1 = function() {
return arealine().x(x0).y(y1);
};
area.lineX1 = function() {
return arealine().x(x1).y(y0);
};
// defined函式用來判斷是否繪製當前點。這樣可以生成離散的圖形。
area.defined = function(_) {
return arguments.length ? (defined = typeof _ === "function" ? _ : constant$1(!!_), area) : defined;
};
area.curve = function(_) {
return arguments.length ? (curve = _, context != null && (output = curve(context)), area) : curve;
};
area.context = function(_) {
return arguments.length ? (_ == null ? context = output = null : output = curve(context = _), area) : context;
};
return area;
}
複製程式碼
d3.radialArea
繪製放射狀區域。
/*
* d3.radialArea
* 在area的基礎上修改curve函式,將x,y座標轉換為angle,radius座標,其餘繪製方式不變
*/
function radialArea() {
var a = area$1().curve(curveRadialLinear),
c = a.curve,
x0 = a.lineX0,
x1 = a.lineX1,
y0 = a.lineY0,
y1 = a.lineY1;
// 將d3.area中的(x0, y0)和(x1, y1)轉化成(startAngle, innerRadius)和(endAngle, outerRadius)
a.angle = a.x, delete a.x;
a.startAngle = a.x0, delete a.x0;
a.endAngle = a.x1, delete a.x1;
a.radius = a.y, delete a.y;
a.innerRadius = a.y0, delete a.y0;
a.outerRadius = a.y1, delete a.y1;
a.lineStartAngle = function() { return radialLine(x0()); }, delete a.lineX0;
a.lineEndAngle = function() { return radialLine(x1()); }, delete a.lineX1;
a.lineInnerRadius = function() { return radialLine(y0()); }, delete a.lineY0;
a.lineOuterRadius = function() { return radialLine(y1()); }, delete a.lineY1;
// 對自定義的curve函式進行包裝,防止計算時方法不能使用
a.curve = function(_) {
return arguments.length ? c(curveRadial(_)) : c()._curve;
};
return a;
}
複製程式碼
Curve
curve的功能就是將離散的點進行連線,形成一個連續的圖形,它並不是直接使用,而是傳入上述如d3.line
、d3.area
等函式的curve
函式中來控制這些離散的點的連線方式。
d3.curveBasis
通過特定控制點的貝塞爾曲線將離散的點進行連線。
function point(that, x, y) {
that._context.bezierCurveTo(
(2 * that._x0 + that._x1) / 3,
(2 * that._y0 + that._y1) / 3,
(that._x0 + 2 * that._x1) / 3,
(that._y0 + 2 * that._y1) / 3,
(that._x0 + 4 * that._x1 + x) / 6,
(that._y0 + 4 * that._y1 + y) / 6
);
}
function Basis(context) {
this._context = context;
}
Basis.prototype = {
areaStart: function() {
this._line = 0;
},
areaEnd: function() {
this._line = NaN;
},
lineStart: function() {
this._x0 = this._x1 =
this._y0 = this._y1 = NaN;
this._point = 0;
},
lineEnd: function() {
switch (this._point) {
case 3: point(this, this._x1, this._y1); // proceed
case 2: this._context.lineTo(this._x1, this._y1); break;
}
if (this._line || (this._line !== 0 && this._point === 1)) this._context.closePath();
this._line = 1 - this._line;
},
//首先移動至起點即第一個點,記錄下第一個點和第二個點座標,連線當前點(第一個點)和((5 * x0 + x1) / 6, (5 * y0 + y1) / 6),並繪製改點到((x0 + 4 * x1 + x) / 6, (y0 + 4 * y1 + y) / 6)點的三次貝塞爾曲線
point: function(x, y) {
x = +x, y = +y;
switch (this._point) {
case 0: this._point = 1; this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); break;
case 1: this._point = 2; break;
case 2: this._point = 3; this._context.lineTo((5 * this._x0 + this._x1) / 6, (5 * this._y0 + this._y1) / 6); // proceed
default: point(this, x, y); break;
}
this._x0 = this._x1, this._x1 = x;
this._y0 = this._y1, this._y1 = y;
}
};
// d3.curveBasis
function basis(context) {
return new Basis(context);
}
複製程式碼
d3.curveBasisClosed
通過特定控制點的貝塞爾曲線連線離散的點,並形成一個閉合圖形。
function BasisClosed(context) {
this._context = context;
}
BasisClosed.prototype = {
areaStart: noop,
areaEnd: noop,
//(x1, y1)和(x2, y2)用於記錄第一個和第二個點的座標
lineStart: function() {
this._x0 = this._x1 = this._x2 = this._x3 = this._x4 =
this._y0 = this._y1 = this._y2 = this._y3 = this._y4 = NaN;
this._point = 0;
},
lineEnd: function() {
switch (this._point) {
//如果只有一個點,則移動到第一個點即該點處並關閉圖形
case 1: {
this._context.moveTo(this._x2, this._y2);
this._context.closePath();
break;
}
//如果有兩個點,則按如下方式處理
case 2: {
this._context.moveTo((this._x2 + 2 * this._x3) / 3, (this._y2 + 2 * this._y3) / 3);
this._context.lineTo((this._x3 + 2 * this._x2) / 3, (this._y3 + 2 * this._y2) / 3);
this._context.closePath();
break;
}
//從最後一個點向前繪製,x2,x3,x4分別記錄的是前三個點座標
case 3: {
this.point(this._x2, this._y2);
this.point(this._x3, this._y3);
this.point(this._x4, this._y4);
break;
}
}
},
point: function(x, y) {
x = +x, y = +y;
switch (this._point) {
//記錄最初的三個點的座標
case 0: this._point = 1; this._x2 = x, this._y2 = y; break;
case 1: this._point = 2; this._x3 = x, this._y3 = y; break;
//到最後會繪製一個閉合圖形,與初始點連線
case 2: this._point = 3; this._x4 = x, this._y4 = y; this._context.moveTo((this._x0 + 4 * this._x1 + x) / 6, (this._y0 + 4 * this._y1 + y) / 6); break;
default: point(this, x, y); break;
}
this._x0 = this._x1, this._x1 = x;
this._y0 = this._y1, this._y1 = y;
}
};
//d3.curveBasisClosed
function basisClosed(context) {
return new BasisClosed(context);
}
複製程式碼
d3.curveBasisOpen
function BasisOpen(context) {
this._context = context;
}
BasisOpen.prototype = {
areaStart: function() {
this._line = 0;
},
areaEnd: function() {
this._line = NaN;
},
lineStart: function() {
this._x0 = this._x1 =
this._y0 = this._y1 = NaN;
this._point = 0;
},
lineEnd: function() {
if (this._line || (this._line !== 0 && this._point === 3)) this._context.closePath();
this._line = 1 - this._line;
},
//記錄第一個和第二個點的座標,從第三個點處開始操作,移動至((x0 + 4 * x1 + x) / 6, (y0 + y1 * 4 + y) / 6) 點處
point: function(x, y) {
x = +x, y = +y;
switch (this._point) {
case 0: this._point = 1; break;
case 1: this._point = 2; break;
case 2: this._point = 3; var x0 = (this._x0 + 4 * this._x1 + x) / 6, y0 = (this._y0 + 4 * this._y1 + y) / 6; this._line ? this._context.lineTo(x0, y0) : this._context.moveTo(x0, y0); break;
case 3: this._point = 4; // proceed
default: point(this, x, y); break;
}
this._x0 = this._x1, this._x1 = x;
this._y0 = this._y1, this._y1 = y;
}
};
//d3.curveBasisOpen
function basisOpen(context) {
return new BasisOpen(context);
}
複製程式碼
d3.curveBundle
根據制定的控制點連線離散的點,用於分層級的關係圖中,與d3.line
一起使用,而不能與d3.area
使用。
Bundle.prototype = {
lineStart: function() {
this._x = [];
this._y = [];
this._basis.lineStart();
},
//結束時開始處理資料並繪製圖形
lineEnd: function() {
var x = this._x,
y = this._y,
j = x.length - 1;
if (j > 0) {
var x0 = x[0],
y0 = y[0],
//計算起始點到結束點之間x和y的差值
dx = x[j] - x0,
dy = y[j] - y0,
i = -1,
t;
while (++i <= j) {
t = i / j;
//根據比例確定繪製點的位置,繪製範圍在(x0, y0)和(x[j], y[j])之間
//當x接近0時,結果近似為一條從起始點到結束點的直線;當x接近1時,結果接近d3.curveBasis
this._basis.point(
this._beta * x[i] + (1 - this._beta) * (x0 + t * dx),
this._beta * y[i] + (1 - this._beta) * (y0 + t * dy)
);
}
}
this._x = this._y = null;
this._basis.lineEnd();
},
//該方法不會繪製圖形,只是將資料存入陣列在結束繪製時開始處理資料
point: function(x, y) {
this._x.push(+x);
this._y.push(+y);
}
};
//d3.curveBundle
var bundle = (function custom(beta) {
function bundle(context) {
return beta === 1 ? new Basis(context) : new Bundle(context, beta);
}
bundle.beta = function(beta) {
return custom(+beta);
};
return bundle;
})(0.85);
複製程式碼
Custom Curves
自定義curve函式,需要自定義幾個指定的方法。
- curve.areaStart() 表示一個新的區域的開始,每個區域包含兩條線段,topline是資料的順序繪製,baseline則反向繪製。
- curve.areaEnd() 表示當前區域的結束。
- curve.lineStart() 表示一條新的線段的開始,接下來會繪製多個點。
- curve.lineEnd() 表示當前線段的結束。
- curve.point(x, y) 在當前線段上根據給定的(x, y)座標繪製一個新的點。
Symbols
//d3.symbol,預設繪製面積為64的圓形
function symbol() {
var type = constant$1(circle),
size = constant$1(64),
context = null;
function symbol() {
var buffer;
if (!context) context = buffer = path();
type.apply(this, arguments).draw(context, +size.apply(this, arguments));
if (buffer) return context = null, buffer + "" || null;
}
//設定圖形型別
symbol.type = function(_) {
return arguments.length ? (type = typeof _ === "function" ? _ : constant$1(_), symbol) : type;
};
//設定圖形的面積
symbol.size = function(_) {
return arguments.length ? (size = typeof _ === "function" ? _ : constant$1(+_), symbol) : size;
};
//設定繪製上下文
symbol.context = function(_) {
return arguments.length ? (context = _ == null ? null : _, symbol) : context;
};
return symbol;
}
複製程式碼
以內建的circle
型別為例
var circle = {
//size是該圓形的面積
draw: function(context, size) {
var r = Math.sqrt(size / pi$2);
context.moveTo(r, 0);
context.arc(0, 0, r, 0, tau$2);
}
};
複製程式碼
若自定義type,則應該實現draw
方法。