裝弧形門,也是我們現(xiàn)在常說的拱形門,防盜門很少做成這樣的形狀,常見于室內(nèi)門的設(shè)計(jì)上。這種門源于古羅馬時(shí)期,在室內(nèi)空間做出拱形形狀的門,像自然界的洞穴,在當(dāng)時(shí)主要起到了裝飾的作用。很多地中海風(fēng)格的室內(nèi)設(shè)計(jì),就巧妙的將這種形狀的門與藍(lán)白色或?qū)嵞窘Y(jié)合在一起,門關(guān)閉時(shí)能夠分割空間,打開時(shí)又能延伸視覺,將思緒放空到一望無際的地中海。關(guān)于家裝弧形門安裝,可以先從以下兩個(gè)方面來了解。
一、家裝弧形門的風(fēng)水
家裝弧形門在歐美的室內(nèi)設(shè)計(jì)中比較常見,鮮少出現(xiàn)在我國的室內(nèi)裝修中,這在風(fēng)水上是有一定講究的。
1.家裝門包括大門和室內(nèi)門。大門是進(jìn)出往來的唯一通道,接納外界的氣息,是家居運(yùn)氣的咽喉要道,也是風(fēng)水上常說的氣口。大門做成弧形,相當(dāng)于橫梁壓門,進(jìn)入家居就要被壓制一下,整個(gè)家庭的運(yùn)勢也會(huì)因此而受到削弱;
2.家居宜靜不宜動(dòng),而圓形代表動(dòng),且弧形門形似墳門,易招陰邪之氣,被認(rèn)為是不吉利的。
關(guān)于家裝弧形門的風(fēng)水的說法有很多,但也可以通過其他方法比如玄關(guān)的設(shè)計(jì)等進(jìn)行化解。
二、安裝方法
制作安裝家裝弧形門,首先是要加弧形的門框門套,即先做出弧形的門洞。關(guān)于弧形門洞的制作,現(xiàn)在常見的有兩種方法:一種是木工做框架,外面做石膏板,最后刷漆;一種是用混凝土澆注,兩邊做成圓拐。第一種是比較便捷、快速的方法,下面簡單介紹一下木質(zhì)框架的做法。
1. 測量圓弧,根據(jù)設(shè)計(jì)師提供的尺寸在細(xì)木工板切出相應(yīng)的形狀,測量要做到精確。加工模板,最好從細(xì)木工板的邊角開始開料,減少板材的浪費(fèi)。
2.找到合適的位置開窗洞,接著在兩側(cè)將之前做好的細(xì)木工板各加一塊兒上去;
3.做出等長的木方(木方的長度要根據(jù)兩側(cè)細(xì)木工板的水平距離來確定,一般以5-6cm為宜)。用木方將兩塊細(xì)木工板連接;
4. 接著就可以在表面封石膏板了,用木工專門切圓的工具把石膏板加工出窗戶的樣子;封號(hào)石膏板后,用飾面板將摟在外面的木方封起來,并用槍釘固定;
5.貼木線條收口。
以上介紹了家裝弧形門安裝方法,希望對(duì)大家有所幫助。
本文首發(fā)于土撥鼠裝修網(wǎng)(www.tobosu.com),轉(zhuǎn)載請(qǐng)注明原文地址:http://www.tobosu.com/article/xcjy/730.html
在上一章中,我們討論了 D3 如何使用其形狀生成器函數(shù)計(jì)算復(fù)雜形狀(如曲線、面積和弧)的 d 屬性。在本章中,我們將通過布局將這些形狀提升到另一個(gè)層次。在 D3 中,布局是將數(shù)據(jù)集作為輸入并生成新的批注數(shù)據(jù)集作為輸出的函數(shù),其中包含繪制特定可視化效果所需的屬性。例如,餅圖布局計(jì)算餅圖每個(gè)扇區(qū)的角度,并使用這些角度批注數(shù)據(jù)集。同樣,堆棧布局計(jì)算堆積形狀在堆積條形圖或流圖中的位置。
布局不會(huì)繪制可視化效果,也不會(huì)像組件一樣調(diào)用它們,也不會(huì)像形狀生成器那樣在繪圖代碼中引用。相反,它們是一個(gè)預(yù)處理步驟,用于設(shè)置數(shù)據(jù)的格式,以便準(zhǔn)備好以您選擇的形式顯示。
圖5.1 布局功能是用于計(jì)算繪制特定圖表所需信息的數(shù)據(jù)預(yù)處理步驟。
在本章中,我們將餅圖和堆棧布局與第 4 章中討論的弧形和面積形狀生成器相結(jié)合,以創(chuàng)建圖 5.2 所示的項(xiàng)目。您也可以在 https://d3js-in-action-third-edition.github.io/visualizing-40-years-of-music-industry-sales/ 在線找到它。該項(xiàng)目可視化了 1973 年至 2019 年間音樂行業(yè)每種格式的銷售情況。它的靈感來自2020年MakeoverMonday(www.makeovermonday.co.uk/week-21-2020/)舉辦的挑戰(zhàn)。
圖 5.2 1973 年至 2019 年音樂行業(yè)銷售的可視化。這是我們將在本章中構(gòu)建的項(xiàng)目。
雖然本章只介紹了餅圖和堆棧布局,但其他布局,如和弦布局和更奇特的布局,遵循相同的原則,看完這些應(yīng)該很容易理解。
在開始之前,請(qǐng)轉(zhuǎn)到第 5 章的代碼文件。您可以從本書的 Github 存儲(chǔ)庫下載它們(https://github.com/d3js-in-action-third-edition/code-files)。在名為 chapter_05 的文件夾中,代碼文件按節(jié)進(jìn)行組織。要開始本章的練習(xí),請(qǐng)?jiān)诖a編輯器中打開 5.1-Pie_layout/start 文件夾并啟動(dòng)本地 Web 服務(wù)器。如果您需要有關(guān)設(shè)置本地開發(fā)環(huán)境的幫助,請(qǐng)參閱附錄 A。您可以在位于本章代碼文件根目錄下的自述文件中找到有關(guān)項(xiàng)目文件夾結(jié)構(gòu)的更多詳細(xì)信息。
我們將在本章中構(gòu)建的三個(gè)可視化(圓環(huán)圖、堆積條形圖和流圖)共享相同的數(shù)據(jù)、維度和比例。為了避免重復(fù),該項(xiàng)目被分解為多個(gè) JavaScript 文件,其中一個(gè)用于可視化共享的常量,另一個(gè)專門用于比例。這種方法將使我們的代碼更易于閱讀和修改。在生產(chǎn)代碼中,我們可能會(huì)使用 JavaScript 導(dǎo)入和導(dǎo)出來訪問不同的函數(shù),并結(jié)合 Node 和捆綁器。在討論前端框架時(shí),我們將到達(dá)那里,但現(xiàn)在,我們將堅(jiān)持一個(gè)類似遺留的項(xiàng)目結(jié)構(gòu),以保持對(duì) D3 的關(guān)注。請(qǐng)注意,D3 庫和所有 JavaScript 文件都已加載到 index.html 中。
使用本章的代碼文件時(shí),在代碼編輯器中僅打開一個(gè)開始文件夾或一個(gè)結(jié)束文件夾。如果一次打開章節(jié)的所有文件并使用 Live Server 擴(kuò)展為項(xiàng)目提供服務(wù),則數(shù)據(jù)文件的路徑將無法按預(yù)期工作。
在本節(jié)中,我們將使用 D3 的餅圖布局來創(chuàng)建圓環(huán)圖,您可以在圖 5.2 頂部和托管項(xiàng)目 (https://d3js-in-action-third-edition.github.io/visualizing-40-years-of-music-industry-sales/) 上看到該圓環(huán)圖。更具體地說,我們將可視化 1975 年、1995 年和 2013 年每種音樂格式的銷售額細(xì)分。每個(gè)圓環(huán)圖的中心將對(duì)應(yīng)于相應(yīng)年份在流圖和下面堆疊條形圖的 x 軸上的位置。
讓我們花點(diǎn)時(shí)間建立一個(gè)策略,以確保每個(gè)圖表根據(jù) x 軸上的年份正確水平對(duì)齊。一個(gè)簡單的方法是使用第4章中描述的保證金約定。隨著本章的進(jìn)展,我們將使用三個(gè) SVG 容器:一個(gè)用于圓環(huán)圖,一個(gè)用于流圖,一個(gè)用于堆積條形圖。這些容器中的每一個(gè)都具有相同的尺寸并共享相同的邊距。為內(nèi)部圖表保留的區(qū)域(沒有軸和標(biāo)簽的可視化效果)也將具有相同的維度并水平對(duì)齊,如圖 5.3 所示。文件 js/shared-constant.js 已包含可視化共享的邊距對(duì)象和維度常量。
我們還在 js/load-data 中為您加載了 CSV 數(shù)據(jù)文件.js .有關(guān)如何將數(shù)據(jù)加載到 D3 項(xiàng)目中的更多信息,請(qǐng)參閱第 4 章和第 3 章。加載數(shù)據(jù)后,我們調(diào)用函數(shù) defineScales() 和 drawDonutCharts() ,我們將在本節(jié)中使用它們。
首先,讓我們?yōu)閳A環(huán)圖追加一個(gè) SVG 容器和一個(gè)定義為內(nèi)部圖表保留區(qū)域的 SVG 組。為此,我們轉(zhuǎn)到 js/donut-charts.js并在函數(shù) drawDonutCharts() 中,我們創(chuàng)建 SVG 容器和一個(gè) SVG 組。在下面的代碼片段中,您將看到我們?cè)?div 內(nèi)附加了 SVG 容器,ID 為 donut 。請(qǐng)注意,我們通過根據(jù)圖表的左邊距和上邊距平移組來應(yīng)用邊距約定。
const svg=d3.select("#donut")
.append("svg") #A
.attr("viewBox", `0 0 ${width} ${height}`); #A
const donutContainers=svg
.append("g") #B
.attr("transform", `translate(${margin.left}, ${margin.top})`); #B
您可能想知道為什么我們需要將邊距約定應(yīng)用于圓環(huán)圖,因?yàn)闆]有軸和標(biāo)簽的帳戶空間。這是因?yàn)槊總€(gè)圓環(huán)圖將根據(jù)其所代表的年份水平定位。由于我們希望這些年份的水平位置與下面的流線圖和堆疊條形圖中相同,因此我們需要考慮邊際慣例。
在第 4 章中,我們討論了極坐標(biāo)以及如何通過將弧包含在 SVG 組中并將該組轉(zhuǎn)換為圖表中心的位置來促進(jìn)餅圖或圓環(huán)圖的創(chuàng)建。通過以這種方式進(jìn)行,弧線將自動(dòng)圍繞該中心繪制。
我們將在這里應(yīng)用相同的策略,唯一的區(qū)別是我們需要考慮三個(gè)圓環(huán)圖,并且它們的中心水平位置對(duì)應(yīng)于它們所代表的年份,如圖 5.4 所示。
圖 5.4 組成圓環(huán)圖的每組弧都包含在 SVG 組中。這些組根據(jù)它們所代表的年份進(jìn)行水平翻譯。該位置是使用 D3 刻度計(jì)算的。
要計(jì)算每個(gè)甜甜圈中心的水平位置,我們需要一個(gè)刻度。如您所知,我們使用 D3 刻度將數(shù)據(jù)(此處為年份)轉(zhuǎn)換為屏幕屬性,此處為水平位置。線性或時(shí)間刻度對(duì)于我們的目的來說效果很好,但我們選擇波段刻度,因?yàn)槲覀冎牢覀兩院髸?huì)繪制一個(gè)堆疊條形圖,它將共享相同的刻度。有關(guān)頻段刻度工作原理的更多說明,請(qǐng)參閱第 3 章。
在文件中 js/scale.js ,我們首先使用函數(shù) d3.scaleBand() 初始化波段刻度,并將其存儲(chǔ)在名為 xScale 的常量中。請(qǐng)注意我們?nèi)绾卧诤瘮?shù) defineScales() 中聲明刻度的域和范圍。這種方法讓我們等到數(shù)據(jù)加載完成,然后再嘗試使用它來設(shè)置域(一旦數(shù)據(jù)準(zhǔn)備就緒,函數(shù) defineScales() 從加載數(shù)據(jù)調(diào)用.js)。我們?cè)诤瘮?shù)外部聲明常量 xScale,使其可以從其他 js 文件訪問。
const xScale=d3.scaleBand(); #A
const defineScales=(data)=> {
xScale
.domain(data.map(d=> d.year)) #B
.range([0, innerWidth]); #B
};
帶狀刻度接受離散輸入作為域,并返回該范圍的連續(xù)輸出。在清單 5.1 中,我們使用 JavaScript map() 方法,通過每年從數(shù)據(jù)集創(chuàng)建一個(gè)數(shù)組來設(shè)置域。對(duì)于范圍,我們傳遞一個(gè)數(shù)組,其中包含可用水平空間的最小值(零)和最大值(對(duì)應(yīng)于內(nèi)部圖表的 innerWidth)。
我們回到函數(shù) drawDonutCharts() ,正如你在清單 5.2 中看到的,我們首先聲明一個(gè)名為 years 的數(shù)組,它列出了我們感興趣的年份,這里是 1975、1995 和 2013。然后,使用 forEach() 循環(huán),我們?yōu)楦信d趣的每一年附加一個(gè) SVG 組,并將其保存在名為 donutContainer 的常量中。最后,我們通過設(shè)置組的轉(zhuǎn)換屬性來翻譯組。水平平移是通過調(diào)用計(jì)算的 xScale ,我們將當(dāng)前年份傳遞到該平移,而垂直平移對(duì)應(yīng)于內(nèi)部圖表的半高。
const years=[1975, 1995, 2013];
years.forEach(year=> {
const donutContainer=donutContainers
.append("g")
.attr("transform", `translate(${xScale(year)}, ${innerHeight/2})`);
});
完成準(zhǔn)備步驟后,我們現(xiàn)在可以專注于圓環(huán)圖。餅圖和圓環(huán)圖可視化部分與整體的關(guān)系或每個(gè)扇區(qū)相對(duì)于總量表示的數(shù)量。D3 餅圖布局生成器通過根據(jù)每個(gè)切片所代表的百分比計(jì)算每個(gè)切片的開始和結(jié)束角度來幫助我們。
D3 的餅圖生成器希望輸入數(shù)據(jù)格式化為數(shù)字?jǐn)?shù)組。例如,對(duì)于 1975 年,我們可以有一個(gè)數(shù)組,其中包含與每種音樂格式對(duì)應(yīng)的銷售額,如下所示:
const sales1975=[8061.8, 2770.4, 469.5, 0, 0, 0, 48.5];
雖然這樣一個(gè)簡單的數(shù)組足以生成餅圖,但它會(huì)阻止我們以后根據(jù)它所代表的音樂格式為每個(gè)切片分配顏色。為了隨身攜帶這些信息,我們可以使用一個(gè)對(duì)象數(shù)組,其中包含音樂格式的 ID 和感興趣年份的相關(guān)銷售額。
在示例 5.3 中,我們首先從加載數(shù)據(jù)集的 columns 屬性中提取格式。獲取數(shù)據(jù)時(shí),例如,使用 d3.csv() 方法,D3 將一個(gè)數(shù)組附加到數(shù)據(jù)集,其中包含原始 CSV 數(shù)據(jù)集中每列的標(biāo)題,并使用鍵 data.columns 進(jìn)行訪問。如果將提取的數(shù)據(jù)記錄到控制臺(tái)中,則會(huì)在數(shù)據(jù)數(shù)組的末尾看到它,如圖 5.5 所示。
由于我們只對(duì)音樂格式感興趣,因此我們可以過濾列數(shù)組以刪除“year”標(biāo)簽。
圖 5.5 從 CSV 文件獲取數(shù)據(jù)時(shí),D3 將數(shù)組附加到數(shù)據(jù)集,其中包含原始數(shù)據(jù)集中列的標(biāo)題。可以使用鍵 data.columns 訪問此數(shù)組。
為了準(zhǔn)備餅圖生成器的數(shù)據(jù),我們還需要提取感興趣的年份的數(shù)據(jù)。我們使用 JavaScript 方法 find() 隔離這些數(shù)據(jù),并將其存儲(chǔ)在名為 yearData 的常量中。
我們遍歷格式數(shù)組,對(duì)于每種格式,我們創(chuàng)建一個(gè)對(duì)象,其中包含格式 id 及其感興趣年份的相關(guān)銷售額。最后,我們將這個(gè)對(duì)象推入 數(shù)組格式化數(shù)據(jù) ,之前聲明。
const years=[1975, 1995, 2013];
const formats=data.columns.filter(format=> format !=="year"); #A
years.forEach(year=> {
...
const yearData=data.find(d=> d.year===year); #B
const formattedData=[]; #C
formats.forEach(format=> { #D
formattedData.push({ format: format, sales: yearData[format] }); #D
}); #D
});
準(zhǔn)備就緒后,格式化數(shù)據(jù)是一個(gè)對(duì)象數(shù)組,每個(gè)對(duì)象都包含格式的 id 及其感興趣年份的相關(guān)銷售額。
//=> formattedData=[
{ format: "vinyl", sales: 8061.8 },
{ format: "eight_track", sales: 2770.4 },
{ format: "cassette", sales: 469.5 },
{ format: "cd", sales: 0 },
{ format: "download", sales: 0 },
{ format: "streaming", sales: 0 },
{ format: "other", sales: 48.5 }
];
現(xiàn)在數(shù)據(jù)格式正確,我們可以初始化餅圖布局生成器。我們用方法 d3.pie() 構(gòu)造一個(gè)新的餅圖生成器,它是 d3 形狀模塊 (https://github.com/d3/d3-shape#pies) 的一部分。由于格式化數(shù)據(jù)是一個(gè)對(duì)象數(shù)組,我們需要告訴餅圖生成器哪個(gè)鍵包含將決定切片大小的值。我們通過設(shè)置 value() 訪問器函數(shù)來做到這一點(diǎn),如以下代碼片段所示。我們還將 pie 生成器存儲(chǔ)在一個(gè)名為 pieGenerator 的常量中,以便我們可以像調(diào)用任何其他函數(shù)一樣調(diào)用它。
const pieGenerator=d3.pie()
.value(d=> d.sales);
要生成餅圖布局的數(shù)據(jù),我們只需調(diào)用餅圖生成器函數(shù),將格式化的數(shù)據(jù)作為參數(shù)傳遞,并將結(jié)果存儲(chǔ)在名為 注釋數(shù)據(jù) .
const pieGenerator=d3.pie()
.value(d=> d.sales);
const annotatedData=pieGenerator(formattedData);
餅圖生成器返回一個(gè)新的帶批注的數(shù)據(jù)集,其中包含對(duì)原始數(shù)據(jù)集的引用,但也包括新屬性:每個(gè)切片的值、其索引及其開始和結(jié)束角度(以弧度為單位)。請(qǐng)注意,每個(gè)切片之間的填充也包括 padAngle 并且當(dāng)前設(shè)置為零。我們稍后會(huì)改變這一點(diǎn)。
//=> annotatedData=[
{
data: { format: "vinyl", sales: 8061.8 },
value: 8061.8,
index: 0,
startAngle: 0,
endAngle: 4.5,
padAngle: 0,
},
...
];
請(qǐng)務(wù)必了解餅圖布局生成器不直接參與繪制餅圖。這是一個(gè)預(yù)處理步驟,用于計(jì)算餅圖扇區(qū)的角度。如圖5.1和5.6所述,此過程通常包括三個(gè)步驟:
圖 5.6 餅圖布局生成器是一個(gè)預(yù)處理步驟,用于生成一個(gè)帶注釋的數(shù)據(jù)集,其中包含餅圖每個(gè)切片的開始和結(jié)束角度。該過程通常涉及格式化我們的數(shù)據(jù),初始化餅圖生成器函數(shù),并調(diào)用該函數(shù)以獲取帶注釋的數(shù)據(jù)。
準(zhǔn)備好帶注釋的數(shù)據(jù)集后,是時(shí)候生成弧線了!您將看到以下步驟與上一章中創(chuàng)建弧的方式非常相似。出于這個(gè)原因,我們不會(huì)解釋每一個(gè)細(xì)節(jié)。如果您需要更深入的討論,請(qǐng)參閱第 4 章。
在示例 5.4 中,我們首先通過調(diào)用 d3.arc() 方法及其負(fù)責(zé)設(shè)置圖表內(nèi)外半徑、切片之間的填充以及切片角半徑的各種訪問器函數(shù)來初始化 arc 生成器。如果內(nèi)半徑設(shè)置為零,我們將獲得一個(gè)餅圖,而如果它大于零,我們將得到一個(gè)圓環(huán)圖。
與第 4 章中使用的策略的唯一區(qū)別是,這次我們可以在聲明電弧發(fā)生器的同時(shí)設(shè)置 startAngle() 和 endAngle() 訪問器函數(shù)。這是因?yàn)楝F(xiàn)在,這些值包含在帶注釋的數(shù)據(jù)集中,我們可以告訴這些訪問器函數(shù)如何通過 d.startAngle 和 d.endAngle .
要使弧出現(xiàn)在屏幕上,我們需要做的最后一件事是使用數(shù)據(jù)綁定模式為注釋數(shù)據(jù)集中的每個(gè)對(duì)象生成一個(gè)路徑元素(每個(gè)弧或切片都有一個(gè)對(duì)象)。請(qǐng)注意,在清單 5.4 中,我們?nèi)绾螢槊總€(gè)甜甜圈的弧指定一個(gè)特定的類名 ( 'arc-${year}' ),并將該類名用作數(shù)據(jù)綁定模式中的選擇器。由于我們正在循環(huán)中創(chuàng)建甜甜圈,這將防止 D3 在制作新甜甜圈時(shí)覆蓋每個(gè)甜甜圈。
最后,我們調(diào)用弧發(fā)生器函數(shù)來計(jì)算每條路徑的 d 屬性。
const arcGenerator=d3.arc()
.startAngle(d=> d.startAngle) #A
.endAngle(d=> d.endAngle) #A
.innerRadius(60)
.outerRadius(100)
.padAngle(0.02)
.cornerRadius(3);
const arcs=donutContainer
.selectAll(`.arc-${year}`) #B
.data(annotatedData) #B
.join("path") #B
.attr("class", `arc-${year}`)
.attr("d", arcGenerator); #C
如果您保存項(xiàng)目并在瀏覽器中查看圓環(huán)圖,您會(huì)發(fā)現(xiàn)它們的形狀是正確的,但每個(gè)弧線都是漆黑的。這是正常的,黑色是 SVG 路徑的默認(rèn)填充屬性。為了提高可讀性,我們將根據(jù)每個(gè)弧線所代表的音樂格式對(duì)它們應(yīng)用不同的顏色。
將正確的顏色應(yīng)用于每個(gè)弧的一種簡單且可重用的方法是聲明色階。在 D3 中,色階通常使用 d3.scaleOrdinal() (https://github.com/d3/d3-scale#scaleOrdinal) 創(chuàng)建。序數(shù)刻度將離散域映射到離散范圍。在我們的例子中,域是音樂格式的數(shù)組,范圍是包含與每種格式關(guān)聯(lián)的顏色的數(shù)組。
在文件比例中.js ,我們首先聲明一個(gè)序數(shù)比例并將其保存在常量色階中。然后,我們通過將 formatInfo 數(shù)組(在共享常量中可用.js的每個(gè)格式 id 映射到數(shù)組中來設(shè)置其域。我們對(duì)顏色做同樣的事情,您可以根據(jù)自己的喜好進(jìn)行個(gè)性化設(shè)置。在本章中,我們將重用此色階來創(chuàng)建構(gòu)成我們項(xiàng)目的所有圖表。
const colorScale=d3.scaleOrdinal();
const defineScales=(data)=> {
colorScale
.domain(formatsInfo.map(f=> f.id))
.range(formatsInfo.map(f=> f.color));
};
回到圓環(huán)圖.js我們可以通過將綁定到每個(gè)弧的音樂格式 id 傳遞給色階來設(shè)置弧的填充屬性。
const arcs=donutContainer
.selectAll(`.arc-${year}`)
.data(annotatedData)
.join("path")
.attr("class", `arc-${year}`)
.attr("d", arcGenerator)
.attr("fill", d=> colorScale(d.data.format));
保存您的項(xiàng)目并在瀏覽器中查看。看起來還不錯(cuò)!弧線已按降序顯示,從最大到最小,這有助于提高可讀性。我們已經(jīng)可以看到音樂的面貌在 1975 年、1995 年和 2013 年間發(fā)生了怎樣的變化,主導(dǎo)格式完全不同。
圖 5.7 1975年、1995年和2013年的圓環(huán)圖
在第4章中,我們提到餅圖有時(shí)很難解釋,因?yàn)槿四X不太擅長將角度轉(zhuǎn)換為比率。我們可以通過在圓環(huán)圖的質(zhì)心上添加一個(gè)標(biāo)簽來提高圓環(huán)圖的可讀性,該標(biāo)簽以百分比表示每個(gè)弧的值,就像我們?cè)谏弦徽轮兴龅哪菢印?/span>
在示例 5.5 中,我們稍微修改了用于創(chuàng)建圓弧的代碼(來自示例 5.4)。首先,我們使用數(shù)據(jù)綁定模式來追加 SVG 組而不是路徑元素。然后,我們將路徑元素(用于圓弧)和 SVG 文本元素(用于標(biāo)簽)附加到這些組中。由于父母將綁定數(shù)據(jù)傳遞給孩子,因此我們將在塑造弧線和標(biāo)簽時(shí)訪問數(shù)據(jù)。
我們通過調(diào)用電弧發(fā)生器來繪制電弧,就像我們之前所做的那樣。要設(shè)置標(biāo)簽的文本,我們需要計(jì)算每個(gè)弧線表示的比率或百分比。我們通過從弧的結(jié)束角度中減去弧的起始角并將結(jié)果除以 2π(以弧度為單位的完整圓覆蓋的角度)來執(zhí)行此計(jì)算。請(qǐng)注意我們?nèi)绾问褂美ㄌ?hào)表示法(d[“百分比”])將百分比值存儲(chǔ)到綁定數(shù)據(jù)中。當(dāng)我們需要對(duì)不同的屬性進(jìn)行相同的計(jì)算時(shí),這個(gè)技巧很有用。它可以防止您多次重復(fù)計(jì)算。為了返回標(biāo)簽的文本,我們將計(jì)算出的百分比傳遞給方法 d3.format(“.0%”) ,該方法生成一個(gè)舍入百分比并在標(biāo)簽?zāi)┪蔡砑右粋€(gè)百分比符號(hào)。
我們應(yīng)用相同的策略來計(jì)算每個(gè)弧的質(zhì)心,這是我們想要放置標(biāo)簽的位置。當(dāng)設(shè)置標(biāo)簽的 x 屬性時(shí),我們計(jì)算相關(guān)弧的質(zhì)心(使用第 4 章中討論的技術(shù))并將其存儲(chǔ)在綁定數(shù)據(jù)中( d[“質(zhì)心”])。然后,在設(shè)置 y 屬性時(shí),質(zhì)心數(shù)組已經(jīng)可以通過 d.centroid 訪問。
為了使標(biāo)簽水平和垂直地以質(zhì)心居中,我們需要將其文本錨點(diǎn)和主要基線屬性設(shè)置為中間。我們還使用fill屬性將它們的顏色設(shè)置為白色,將其字體大小增加到16px,將其字體粗細(xì)增加到500以提高可讀性。
如果您保存項(xiàng)目并在瀏覽器中查看圓環(huán)圖,您會(huì)發(fā)現(xiàn)標(biāo)簽在大弧上工作良好,但在較小的弧線上幾乎無法讀取。在專業(yè)項(xiàng)目中,我們可以通過將小弧的標(biāo)簽移動(dòng)到圓環(huán)圖之外來解決這個(gè)問題。對(duì)于此項(xiàng)目,當(dāng)百分比小于 5% 時(shí),我們根本不會(huì)通過將其填充不透明度屬性設(shè)置為零來顯示這些標(biāo)簽。
const arcs=donutContainer
.selectAll(`.arc-${year}`)
.data(annotatedData)
.join("g") #A
.attr("class", `arc-${year}`);
arcs #B
.append("path") #B
.attr("d", arcGenerator) #B
.attr("fill", d=> colorScale(d.data.format)); #B
arcs
.append("text") #C
.text(d=> {
d["percentage"]=(d.endAngle - d.startAngle) / (2 * Math.PI); #D
return d3.format(".0%")(d.percentage); #D
})
.attr("x", d=> { #E
d["centroid"]=arcGenerator #E
.startAngle(d.startAngle) #E
.endAngle(d.endAngle) #E
.centroid(); #E
return d.centroid[0]; #E
}) #E
.attr("y", d=> d.centroid[1]) #E
.attr("text-anchor", "middle")
.attr("alignment-baseline", "middle")
.attr("fill", "#f6fafc")
.attr("fill-opacity", d=> d.percentage < 0.05 ? 0 : 1) #F
.style("font-size", "16px")
.style("font-weight", 500);
圖 5.8 帶百分比標(biāo)簽的圓環(huán)圖
作為最后一步,我們將指示圓環(huán)圖所代表的年份,標(biāo)簽位于其中心。我們通過在每個(gè)甜甜圈容器中附加一個(gè)文本元素來做到這一點(diǎn)。因?yàn)槲覀冞€在循環(huán)往復(fù)年份,所以我們可以直接應(yīng)用當(dāng)前年份作為標(biāo)簽的文本。此外,由于圓環(huán)容器位于圖表的中心,因此文本元素會(huì)自動(dòng)正確定位。我們所要做的就是設(shè)置其文本錨點(diǎn)和主要基線屬性,使其水平和垂直居中。
donutContainer
.append("text")
.text(year)
.attr("text-anchor", "middle")
.attr("dominant-baseline", "middle")
.style("font-size", "24px")
.style("font-weight", 500);
瞧!我們的圓環(huán)圖是完整的。
圖 5.9 帶有年份標(biāo)簽的完整圓環(huán)圖
圖 5.10 回顧了創(chuàng)建餅圖或圓環(huán)圖的步驟。在第一步中,我們使用布局函數(shù) d3.pie() 預(yù)處理數(shù)據(jù),以獲得帶有注釋的數(shù)據(jù)集,其中包含每個(gè)切片的角度。然后,我們使用弧發(fā)生器函數(shù)繪制弧線,該函數(shù)從注釋數(shù)據(jù)集中獲取角度并返回每個(gè)路徑的 d 屬性。最后,我們使用 SVG 文本元素添加標(biāo)簽以提高圖表的可讀性。
圖 5.10 創(chuàng)建餅圖或圓環(huán)圖所涉及的主要步驟。
到目前為止,我們已經(jīng)處理了可以在任何傳統(tǒng)電子表格中輕松創(chuàng)建的信息可視化的簡單示例。但是你進(jìn)入這個(gè)行業(yè)并不是為了制作類似Excel的圖表。您可能希望用漂亮的數(shù)據(jù)讓您的觀眾驚嘆不已,為您的美學(xué) je ne sais quoi 贏得獎(jiǎng)項(xiàng),并通過您隨著時(shí)間的推移而變化的表現(xiàn)喚起深刻的情感反應(yīng)。
流圖是代表變化和變化的崇高信息可視化。在你開始把這些部分放在一起之前,創(chuàng)作似乎具有挑戰(zhàn)性。歸根結(jié)底,流圖是所謂的堆積面積圖的變體。這些層相互累積,并根據(jù)靠近中心的組件所占用的空間來調(diào)整上方和下方元素的面積。它似乎是有機(jī)的,因?yàn)檫@種吸積性模仿了許多生物的生長方式,似乎暗示了控制生物生長和衰敗的各種涌現(xiàn)特性。我們稍后會(huì)解釋它的外觀,但首先,讓我們弄清楚如何構(gòu)建它。
我們?cè)诒緯牡谝徊糠挚戳艘粋€(gè)流線圖,因?yàn)樗鼘?shí)際上并沒有那么奇特。流圖是一種堆積圖,這意味著它與堆積條形圖基本相似,如圖 5.11 所示。流線圖也類似于我們?cè)谏弦徽轮袠?gòu)建的折線圖后面的區(qū)域,只是這些區(qū)域相互堆疊。在本節(jié)中,我們將使用 D3 的堆棧和面積生成器來創(chuàng)建堆疊條形圖,然后創(chuàng)建流線圖。
圖 5.11 流圖與堆積條形圖基本相似。在 D3 中,兩者都是使用堆棧布局生成器創(chuàng)建的。
在 D3 中,創(chuàng)建堆積條形圖或流圖的步驟類似,如圖 5.12 所示。首先,我們初始化一個(gè)堆棧布局生成器并設(shè)置堆棧的參數(shù)。然后,我們將原始數(shù)據(jù)集傳遞給堆棧生成器,堆棧生成器將返回一個(gè)新的注釋數(shù)據(jù)集,指示每個(gè)數(shù)據(jù)點(diǎn)的下限和上限。如果我們制作一個(gè)流線圖,我們還必須初始化一個(gè)面積生成器,類似于上一章中討論的直線和曲線生成器。最后,我們將帶注釋的數(shù)據(jù)集綁定到制作圖表所需的 SVG 形狀、堆疊條形圖的矩形或流圖的路徑。在流圖的情況下,調(diào)用面積生成器來計(jì)算路徑的 d 屬性。我們將在以下小節(jié)中更詳細(xì)地介紹這些步驟。
圖 5.12 使用 D3 創(chuàng)建堆積圖的步驟。
堆棧布局生成器是一個(gè) D3 函數(shù),它將具有多個(gè)類別的數(shù)據(jù)集作為輸入。本章示例中使用的數(shù)據(jù)集包含 1973 年至 2019 年間每年不同音樂格式的總銷售額。每種音樂格式將成為堆疊圖表中的一個(gè)系列。
與前面討論的餅圖布局生成器一樣,堆棧布局函數(shù)返回一個(gè)新的注釋數(shù)據(jù)集,其中包含不同序列在“堆疊”到另一個(gè)時(shí)的位置。堆棧生成器是 d3 形狀模塊 (https://github.com/d3/d3-shape#stacks) 的一部分。
讓我們將堆棧布局付諸行動(dòng),并開始在位于堆疊條形圖中的函數(shù) drawStackedBars() 中工作.js 。請(qǐng)注意,此函數(shù)已經(jīng)包含將 SVG 容器附加到 div 的代碼,ID 為 “bars”,以及內(nèi)部圖表的組容器。這與我們?cè)诘?章中使用的策略相同,與保證金慣例并行。
在下面的代碼片段中,我們首先使用方法 d3.stack() 聲明一個(gè)堆棧生成器,并將其存儲(chǔ)在一個(gè)名為 stackGenerator 的常量中。然后,我們需要告訴生成器數(shù)據(jù)集中的哪些鍵包含我們要堆疊的值(將成為序列)。我們使用 keys() 訪問器函數(shù)來做到這一點(diǎn),我們將類別 id 數(shù)組傳遞給該函數(shù),這里是每種音樂格式的標(biāo)識(shí)符。我們通過映射 formatInfo 常量的 id 來創(chuàng)建這個(gè)數(shù)組。我們還可以使用附加到數(shù)據(jù)集的列鍵并過濾掉年份,就像我們?cè)?5.1.2 節(jié)中所做的那樣。
最后,我們調(diào)用堆棧生成器并將數(shù)據(jù)作為參數(shù)傳遞,以獲得帶注釋的數(shù)據(jù)集。我們將新數(shù)據(jù)集存儲(chǔ)在名為 注釋數(shù)據(jù) .
const stackGenerator=d3.stack() #A
.keys(formatsInfo.map(f=> f.id)); #B
const annotatedData=stackGenerator(data); #C
如果將帶注釋的數(shù)據(jù)集記錄到控制臺(tái)中,您將看到它由多維數(shù)組組成。我們首先為每個(gè)系列提供一個(gè)數(shù)組,如圖 5.13 所示,序列的 id 可通過 key 屬性獲得。然后,序列數(shù)組包含另一組數(shù)組,數(shù)據(jù)集中每年一個(gè)數(shù)組。最后這些數(shù)組包括相關(guān)年份類別的下限和上限以及該年份的原始數(shù)據(jù)。下限和上限分別由索引 d[0] 和 d[1] 訪問,如果 d 對(duì)應(yīng)于數(shù)組。
格式“乙烯基”是堆棧布局處理的第一個(gè)鍵。請(qǐng)注意,它的下限始終為零,而其上邊界對(duì)應(yīng)于該格式的當(dāng)年銷售額。然后,以下類別是“8 軌”。8 軌的下邊界對(duì)應(yīng)于黑膠唱片的上邊界,我們將 8 軌的銷量相加以獲得其上限,從而創(chuàng)建一個(gè)堆棧。
圖 5.13 堆棧布局生成器返回的帶注釋的數(shù)據(jù)集。
如果“堆棧”的概念還不清楚,下圖可能會(huì)有所幫助。如果我們從原始數(shù)據(jù)集中仔細(xì)觀察 1986 年,我們將看到音樂主要通過三種格式提供:黑膠唱片的銷量為 2,825M$,盒式磁帶為 5,830M$,CD 為 2,170M$。我們?cè)趫D5.14的左側(cè)顯示了這些數(shù)據(jù)點(diǎn),獨(dú)立繪制。
當(dāng)我們使用堆棧布局時(shí),我們創(chuàng)建所謂的“數(shù)據(jù)列”而不是“數(shù)據(jù)點(diǎn)”,每列都有下限和上限。如果我們的堆棧從黑膠唱片開始,則下限為零,上邊界對(duì)應(yīng)于 1986 年黑膠唱片的銷售額:2,825M$。然后,我們將盒式磁帶的銷售疊加在其上:下邊界對(duì)應(yīng)于黑膠唱片的上限(2,825M$),上邊界是黑膠唱片和盒式磁帶(8,655M$)的銷售量。這個(gè)上邊界成為CD銷售的下限,其上邊界對(duì)應(yīng)于三種格式(10,825M$)的銷售量相加。這些邊界在帶注釋的數(shù)據(jù)集中通過索引(d[0]和d[1])訪問。
圖 5.14 堆棧布局生成器將數(shù)據(jù)點(diǎn)轉(zhuǎn)換為堆疊數(shù)據(jù)列,并返回包含每個(gè)數(shù)據(jù)列的下限和上限的帶注釋的數(shù)據(jù)集。在這里,我們看到 1986 年的一個(gè)例子。
在本節(jié)中,我們將創(chuàng)建您在圖 5.11 底部看到的堆積條形圖。堆積條形圖類似于我們?cè)诘?2 章和第 3 章中已經(jīng)制作的條形圖,只是條形圖分為多個(gè)類別或系列。堆積條形圖和一般的堆積可視化通常用于顯示趨勢隨時(shí)間推移的演變。
就像我們對(duì)圓環(huán)圖所做的那樣,我們將使用堆棧布局返回的帶注釋的數(shù)據(jù)集來繪制對(duì)應(yīng)于每個(gè)類別的條形。但首先,我們需要一個(gè)垂直軸的比例,將每個(gè)矩形的下邊界和上邊界轉(zhuǎn)換為垂直位置。我們希望條形的高度與銷售額成線性比例,因此我們將使用線性刻度。由于此刻度需要訪問帶注釋的數(shù)據(jù),因此我們將在函數(shù) drawStackedBars() 中聲明它。
刻度域從零到注釋數(shù)據(jù)中可用的最大上限。我們知道,這個(gè)最大值必須存在于最后一個(gè)帶注釋的數(shù)據(jù)系列中,這些數(shù)據(jù)將位于圖表的頂部。我們可以使用 length 屬性訪問這個(gè)系列( annotatedData[annotatedData.length - 1])。然后,我們使用方法 d3.max() 檢索屬性 d[1] 下的最大值,該值對(duì)應(yīng)于上限。
垂直刻度的范圍從內(nèi)部圖表底部的innerHeight到內(nèi)部圖表頂部的零(請(qǐng)記住,SVG垂直軸在向下方向上為正)。最后,我們將 scale 聲明與方法 .nice() 鏈接起來,這將確保域以“nice”舍入值結(jié)束,而不是注釋數(shù)據(jù)集中的實(shí)際最大值。
const maxUpperBoundary=d3.max(annotatedData[annotatedData.length - 1], d
?=> d[1]);
const yScale=d3.scaleLinear()
.domain([0, maxUpperBoundary])
.range([innerHeight, 0])
.nice();
我們現(xiàn)在已準(zhǔn)備好附加條形圖。為此,我們遍歷帶注釋的數(shù)據(jù),并逐個(gè)附加序列,如清單 5.7 中所述。我們從數(shù)據(jù)綁定模式開始,為系列數(shù)組中的每個(gè)項(xiàng)目或年份附加一個(gè)矩形元素(每種音樂格式都有一個(gè)系列)。請(qǐng)注意我們?nèi)绾螌⑴c當(dāng)前系列相關(guān)的類名應(yīng)用于矩形并將其用作選擇器。如果我們簡單地使用“rect”元素作為選擇器,每次執(zhí)行循環(huán)時(shí),先前創(chuàng)建的矩形都將被刪除并替換為新矩形。
然后,我們通過調(diào)用帶刻度的帶寬屬性來設(shè)置矩形的 x 屬性,通過將當(dāng)前年份傳遞給 xScale 來設(shè)置它們的寬度屬性。y 屬性對(duì)應(yīng)于矩形左上角的垂直位置,由前面聲明的垂直刻度返回,我們將矩形的上邊界 (d[1] ) 傳遞到該刻度。
同樣,矩形的高度是其上邊界和下邊界位置之間的差異。這里有一點(diǎn)問題。因?yàn)?SVG 垂直軸在向下方向上是正的,所以 yScale(d[0]) 返回的值高于 yScale(d[1])。我們需要從前者中減去后者,以避免為 y 屬性提供負(fù)值,這會(huì)引發(fā)錯(cuò)誤。
最后,我們通過將當(dāng)前音樂格式傳遞給色階來設(shè)置 fill 屬性,該色階可在每個(gè)系列的 key 屬性下訪問,如前面圖 5.13 所示。
annotatedData.forEach(serie=> { #A
innerChart
.selectAll(`.bar-${serie.key}`) #B
.data(serie) #B
.join("rect") #B
.attr("class", d=> `bar-${serie.key}`) #B
.attr("x", d=> xScale(d.data.year)) #C
.attr("y", d=> yScale(d[1])) #C
.attr("width", xScale.bandwidth()) #C
.attr("height", d=> yScale(d[0]) - yScale(d[1])) #C
.attr("fill", colorScale(serie.key)); #C
});
如果保存項(xiàng)目,您將看到條形之間沒有水平空間。我們可以通過回到 xScale 的聲明來解決這個(gè)問題,并將其 paddingInner() 訪問器函數(shù)設(shè)置為值 20%,就像我們?cè)诘?3 章中所做的那樣。
xScale
.domain(data.map(d=> d.year))
.range([0, innerWidth])
.paddingInner(0.2);
為了完成我們的堆積條形圖,我們需要添加軸。在清單 5.9 中,我們首先使用方法 d3.axisBottom() 聲明一個(gè)底部軸,并將 xScale 作為引用傳遞。
我們將軸聲明與方法鏈接起來, .tickValues() ,它允許我們陳述我們希望在圖表上看到的確切刻度和標(biāo)簽。否則,D3 將每年提供一對(duì)刻度和標(biāo)簽,看起來會(huì)很局促且難以閱讀。方法.tickValues()將值數(shù)組作為參數(shù)。我們使用方法 d3.range() 生成這個(gè)數(shù)組,并聲明我們想要從 1975 年到 2020 年的每個(gè)整數(shù),步長為 5。
我們還使用方法 .tickSizeOuter() 隱藏底部軸兩端的刻度,我們向其傳遞值為零。方法tickValues()和tickSizeOuter()都可以在d3軸模塊(https://github.com/d3/d3-axis)中找到,而d3.range()是d3-array模塊(https://github.com/d3/d3-array)的一部分。
最后,我們使用 call() 方法將底部軸附加到圖表中,在轉(zhuǎn)換為底部的組中,并對(duì)左軸執(zhí)行相同的操作。
const bottomAxis=d3.axisBottom(xScale) #A
.tickValues(d3.range(1975, 2020, 5)) #A
.tickSizeOuter(0); #A
innerChart #B
.append("g") #B
.attr("transform", `translate(0, ${innerHeight})`) #B
.call(bottomAxis); #B
const leftAxis=d3.axisLeft(yScale); #C
innerChart #C
.append("g") #C
.call(leftAxis); #C
如果保存項(xiàng)目并在瀏覽器中查看它,您可能會(huì)發(fā)現(xiàn)軸標(biāo)簽有點(diǎn)太小。此外,如第 4 章所述,D3 將字體族“sans-serif”應(yīng)用于包含軸元素的 SVG 組,這意味著項(xiàng)目的字體系列不會(huì)被繼承。從 CSS 文件可視化中.css ,我們可以使用選擇器 .tick 文本定位軸標(biāo)簽并修改其樣式屬性。在下面的代碼片段中,我們更改了它們的字體系列、字體大小和字體粗細(xì)屬性。
.tick text {
font-family: 'Roboto', sans-serif;
font-size: 14px;
font-weight: 500;
}
完成后,堆積條形圖將類似于圖 5.15 中的條形圖,但看起來還不像圖 5.2 中的條形圖或托管項(xiàng)目 (https://d3js-in-action-third-edition.github.io/visualizing-40-years-of-music-industry-sales/) 中的條形圖。我們一會(huì)兒就到那里。
圖5.15 第一版堆積條形圖
在上一小節(jié)中,我們使用堆棧布局函數(shù)生成一個(gè)帶注釋的數(shù)據(jù)集,從中繪制堆疊條形圖的矩形。現(xiàn)在,我們將應(yīng)用類似的策略來繪制流圖。盡管流圖看起來比堆積條形圖更復(fù)雜,但它們很容易在 D3 中創(chuàng)建。主要區(qū)別在于,對(duì)于流圖,我們使用帶注釋的數(shù)據(jù)集來追加區(qū)域,而為堆疊條形圖附加矩形。
在本小節(jié)中,我們將使用函數(shù) drawStreamGraph() ,您可以在文件流圖中找到它.js 。此函數(shù)已包含將 SVG 容器附加到 div 的代碼,ID 為 “streamgraph”,以及內(nèi)部圖表的組容器。這與我們?cè)诘?章中使用的策略相同,與保證金慣例并行。
在示例 5.10 中,我們初始化堆棧生成器并調(diào)用它來獲取帶注釋的數(shù)據(jù)。我們還聲明了一個(gè)線性刻度來計(jì)算垂直邊界的位置。這與我們用于堆積條形圖的代碼完全相同。現(xiàn)在,不要擔(dān)心我們正在復(fù)制代碼。我們將在下一小節(jié)中回到它。
const stackGenerator=d3.stack() #A
.keys(formatsInfo.map(f=> f.id)); #A
const annotatedData=stackGenerator(data); #A
const maxUpperBoundary=d3.max(annotatedData[annotatedData.length - 1], d
?=> d[1]);
const yScale=d3.scaleLinear() #B
.domain([0, maxUpperBoundary]) #B
.range([innerHeight, 0]) #B
.nice(); #B
為了繪制堆疊區(qū)域,我們需要一個(gè)區(qū)域生成器函數(shù),該函數(shù)將負(fù)責(zé)計(jì)算用于繪制序列的每個(gè)路徑元素的 d 屬性。如第4章所述,面積生成器至少使用三個(gè)訪問器函數(shù),在我們的例子中,一個(gè)用于檢索每個(gè)數(shù)據(jù)點(diǎn)的水平位置,一個(gè)用于堆疊區(qū)域的下邊界,另一個(gè)用于它們的上邊界。圖 5.16 說明了面積生成器如何應(yīng)用于堆疊區(qū)域。
圖 5.16 面積生成器 d3.area() 與三個(gè)或更多訪問器函數(shù)組合在一起。當(dāng)與流圖的堆棧布局結(jié)合使用時(shí),它使用每個(gè)數(shù)據(jù)點(diǎn)的下限和上限(y0 和 y1)來計(jì)算區(qū)域的 d 屬性。
在下面的代碼片段中,我們初始化了區(qū)域生成器 d3.area() 。首先,我們使用 x() 訪問器函數(shù)來計(jì)算每個(gè)數(shù)據(jù)點(diǎn)的水平位置。由于 xScale 是波段刻度,因此它返回相關(guān)年份的每個(gè)波段開頭的位置,該位置可在每個(gè)數(shù)據(jù)點(diǎn)的數(shù)據(jù)對(duì)象中的注釋數(shù)據(jù)集中訪問 ( d.data.year)。如果我們希望數(shù)據(jù)點(diǎn)與下面堆疊條形圖的條形中心水平對(duì)齊,我們需要將數(shù)據(jù)點(diǎn)向右平移,寬度為條形寬度的一半,我們可以用帶寬()屬性計(jì)算帶刻度。
然后,我們使用 y0() 和 y(1) 訪問器函數(shù)來確定數(shù)據(jù)點(diǎn)沿每個(gè)序列的下邊界和上邊界的垂直位置。這個(gè)位置是用 yScale 計(jì)算的,之前聲明了,我們將邊界的值傳遞給邊界值,可以通過邊界數(shù)據(jù)中的數(shù)組索引訪問:d[0] 表示下邊界,d[1] 表示上限邊界。
最后,如果我們想沿每個(gè)邊界插值數(shù)據(jù)點(diǎn)以獲得曲線而不是直線,我們使用 curve() 訪問器函數(shù)。這里我們選擇了曲線插值函數(shù)d3.curveCatmullRom。如前所述,曲線插值會(huì)修改數(shù)據(jù)的表示,必須謹(jǐn)慎選擇。有關(guān)討論和演示,請(qǐng)參閱第 4.2.2 節(jié)。
const areaGenerator=d3.area()
.x(d=> xScale(d.data.year) + xScale.bandwidth()/2)
.y0(d=> yScale(d[0]))
.y1(d=> yScale(d[1]))
.curve(d3.curveCatmullRom);
現(xiàn)在,我們已準(zhǔn)備好繪制堆疊區(qū)域!首先,我們使用數(shù)據(jù)綁定模式為注釋數(shù)據(jù)集中的每個(gè)序列生成一個(gè) SVG 路徑元素。我們調(diào)用面積生成器函數(shù)來獲取每個(gè)路徑的 d 屬性,以及它們的填充屬性的色階。
請(qǐng)注意我們?nèi)绾卧?SVG 組中附加路徑以保持標(biāo)記井井有條且易于檢查。這也將有助于在以后保持區(qū)域和垂直網(wǎng)格的適當(dāng)并置。
innerChart
.append("g")
.attr("class", "areas-container")
.selectAll("path")
.data(annotatedData)
.join("path")
.attr("d", areaGenerator)
.attr("fill", d=> colorScale(d.key));
在本節(jié)中,我們要做的最后一件事是向流圖添加軸和標(biāo)簽。我們開始聲明軸生成器 d3.axisLeft() 并將 yScale 作為引用傳遞。然后,我們使用 .call() 方法將軸元素附加到 SVG 組中。
const leftAxis=d3.axisLeft(yScale);
innerChart
.append("g")
.call(leftAxis);
我們可能會(huì)省略 x 軸,因?yàn)榱鲌D與下面的堆疊條形圖水平對(duì)齊,并且此圖表具有相同的 x 軸。但我們將利用這個(gè)機(jī)會(huì)討論如何擴(kuò)展軸上的刻度以在圖表后面創(chuàng)建網(wǎng)格。
首先,我們需要記住,SVG 元素是按照它們?cè)?SVG 容器中出現(xiàn)的順序繪制的。因此,如果我們希望網(wǎng)格出現(xiàn)在流線圖后面,我們需要先繪制它。這就是為什么以下代碼片段應(yīng)位于追加流圖路徑的代碼片段之前的原因。
到目前為止,生成底部軸的代碼與用于堆疊條形圖的代碼相同,包括 tickValues() 和 tickSizeOuter() 方法的使用。
const bottomAxis=d3.axisBottom(xScale)
.tickValues(d3.range(1975, 2020, 5))
.tickSizeOuter(0);
innerChart
.append("g")
.attr("class", "x-axis-streamgraph")
.attr("transform", `translate(0, ${innerHeight})`)
.call(bottomAxis);
要將即時(shí)報(bào)價(jià)轉(zhuǎn)換為網(wǎng)格,我們所要做的就是使用 tickSize() 方法擴(kuò)展它們的長度。通過這種方法,我們給即時(shí)報(bào)價(jià)一個(gè)對(duì)應(yīng)于內(nèi)部圖表高度的長度,乘以 -1 使它們向上增長。請(qǐng)注意,我們還可以首先避免平移軸,并將此長度設(shè)置為正值,以使刻度線從上到下的方向增長。每當(dāng)需要水平網(wǎng)格時(shí),此方法也可以應(yīng)用于左軸或右軸。
const bottomAxis=d3.axisBottom(xScale)
.tickValues(d3.range(1975, 2020, 5))
.tickSizeOuter(0)
.tickSize(innerHeight * -1);
最后,我們可以選擇隱藏軸底部的水平線和年份標(biāo)簽,方法是將它們的不透明度定為零。為此,我們使用之前賦予 x 軸容器的類名( x-axis-streamgraph ),并將其用作 CSS 文件可視化中的選擇器.css .正如您在下面的代碼片段中看到的,通過“ .x-axis-streamgraph path”訪問的水平線的不透明度是用stroke-opacity屬性管理的,而我們需要使用填充不透明度來隱藏年份標(biāo)簽(“ .x-axis-streamgraph文本”)。我們還可以使用 D3 style() 方法來處理流圖內(nèi)的不透明度.js .
.x-axis-streamgraph path {
stroke-opacity: 0;
}
.x-axis-streamgraph text {
fill-opacity: 0;
}
最后,我們將在左側(cè)軸上方添加一個(gè)標(biāo)簽,以指示此軸所代表的內(nèi)容。如圖 5.2 所示,或者在托管項(xiàng)目 (https://d3js-in-action-third-edition.github.io/visualizing-40-years-of-music-industry-sales/) 上,流圖的標(biāo)簽分為兩行,第一行帶有文本“總收入(百萬美元)”,第二行提到“根據(jù)通貨膨脹進(jìn)行調(diào)整”。
我們將使用 SVG 文本構(gòu)建此標(biāo)簽。關(guān)于 SVG 文本,需要了解的一件事是它的行為不像 HTML 文本。例如,如果我們?cè)?HTML 元素中添加文本,文本將根據(jù)水平可用空間自動(dòng)換行或重排。SVG 文本不會(huì)這樣做,每個(gè)文本元素的位置需要單獨(dú)處理。
要操作 SVG 文本中的潛臺(tái)詞,我們可以使用 tspan 元素。將文本分解為多個(gè) tspan,允許使用其 x、y、dx 和 dy 屬性分別調(diào)整其樣式和位置,前兩個(gè)用于參考 SVG 容器的坐標(biāo)系,后兩個(gè)用于參考前一個(gè)文本元素。
在上述所有定義中,請(qǐng)務(wù)必記住,文本基線由其文本錨點(diǎn)屬性水平控制,垂直由其主基線屬性控制。
為了創(chuàng)建我們的標(biāo)簽,我們可以使用位于 SVG 文本中的三個(gè) tspan 元素,如圖 5.17 所示。如果文本元素的主基線屬性設(shè)置為 hang ,則文本將顯示在 SVG 容器原點(diǎn)的正下方和右側(cè)。使用 dx 和 dy,我們可以根據(jù)圖 5.17 分別將第二個(gè)和第三個(gè)跨度移動(dòng)到它們的正確位置。
圖 5.17 tspan 元素允許分別操作副詞項(xiàng)的樣式和位置。我們使用屬性 dx 和 dy 來設(shè)置相對(duì)于前一個(gè)文本元素的位置。
在下面的代碼片段中,我們將該策略付諸行動(dòng)。首先,我們將一個(gè)文本元素附加到我們的 SVG 容器中,并將其主基線屬性設(shè)置為值 hang ,這意味著文本及其子文本的基線將位于它們的正上方。
我們將文本選擇保存到常量 leftAxisLabel 中,并重復(fù)使用它將三個(gè) tspan 元素附加到文本容器中。我們將第一個(gè)tspan的文本設(shè)置為“總收入”,第二個(gè)tspan設(shè)置為“(百萬美元)”,第三個(gè)tspan設(shè)置為“經(jīng)通貨膨脹調(diào)整”。
默認(rèn)情況下,tspan 元素一個(gè)接一個(gè)地顯示在同一水平線上。保存您的項(xiàng)目并查看標(biāo)簽進(jìn)行確認(rèn)。
const leftAxisLabel=svg
.append("text")
.attr("dominant-baseline", "hanging");
leftAxisLabel
.append("tspan")
.text("Total revenue");
leftAxisLabel
.append("tspan")
.text("(million USD)");
leftAxisLabel
.append("tspan")
.text("Adjusted for inflation");
要將第二個(gè) tspan 稍微向右移動(dòng),我們可以設(shè)置其 dx 屬性并為其指定值 5。要將第三個(gè) tspan 移動(dòng)到第一個(gè)和第二個(gè) tspan 下方,我們可以使用 y 或 dy 屬性并為其指定值“20”。在這種特殊情況下,這兩個(gè)屬性將具有相同的效果。最后,如果我們希望第三個(gè) tspan 的左側(cè)與 SVG 容器的左邊框?qū)R,最好使用 x 屬性并將其設(shè)置為零。
const leftAxisLabel=svg
.append("text")
.attr("dominant-baseline", "hanging");
leftAxisLabel
.append("tspan")
.text("Total revenue");
leftAxisLabel
.append("tspan")
.text("(million USD)")
.attr("dx", 5);
leftAxisLabel
.append("tspan")
.text("Adjusted for inflation")
.attr("x", 0)
.attr("dy", 20);
通常,tspan 元素用于將不同的樣式應(yīng)用于文本的一部分。例如,我們可以降低第二個(gè)和第三個(gè) tspan 元素的不透明度,使它們呈灰色,并減小第三個(gè) tspan 的字體大小,因?yàn)榕c標(biāo)簽的其余部分相比,它傳達(dá)了次要信息。
const leftAxisLabel=svg
.append("text")
.attr("dominant-baseline", "hanging");
leftAxisLabel
.append("tspan")
.text("Total revenue");
leftAxisLabel
.append("tspan")
.text("(million USD)")
.attr("dx", 5)
.attr("fill-opacity", 0.7);
leftAxisLabel
.append("tspan")
.text("Adjusted for inflation")
.attr("x", 0)
.attr("dy", 20)
.attr("fill-opacity", 0.7)
.style("font-size", "14px");
我們的流圖的第一次迭代現(xiàn)在已經(jīng)完成,如圖 5.18 所示。當(dāng)此類圖表的垂直基線位于零時(shí),我們通常將其命名為堆積面積圖,而流線圖的面積往往位于中心基線周圍。在下一小節(jié)中,我們將討論如何更改圖表的基線。但在我們到達(dá)那里之前,觀察堆疊條形圖和堆疊面積圖在這一點(diǎn)上的相似之處很有趣。
圖 5.18 我們流線圖的第一次迭代,也可以命名為堆積面積圖。
通過控制序列的堆疊順序以及它們?cè)诹慊€周圍的垂直定位方式,我們可以將堆積條形圖和堆積面積圖更進(jìn)一步。此級(jí)別的控制是通過 order() 和 offset() 訪問器函數(shù)實(shí)現(xiàn)的,這兩個(gè)函數(shù)都應(yīng)用于堆棧布局生成器。
讓我們首先看一下 order() 訪問器函數(shù),它控制形狀垂直堆疊的順序。D3 有六個(gè)內(nèi)置訂單,可以作為參數(shù)傳遞,如圖 5.19 所示。
d3.stackOrderNone 是默認(rèn)順序,這意味著如果未設(shè)置 order() 訪問器函數(shù),則應(yīng)用該順序。它按與 keys 數(shù)組中列出的順序相同的順序堆疊對(duì)應(yīng)于每個(gè)系列的形狀,從下到上。d3.stackOrderReverse顛倒了這個(gè)順序,從底部的最后一個(gè)鍵開始,到頂部的第一個(gè)鍵結(jié)束。
d3.stackOrderAscending 計(jì)算每個(gè)序列的總和。總和最小的序列位于底部,其他序列按升序堆疊。同樣,d3.stackOrder降序?qū)⒖偤妥畲蟮男蛄蟹旁诘撞浚唇敌蚨询B序列。
最后兩個(gè)訂單計(jì)算每個(gè)序列達(dá)到其最大值的指數(shù)。d3.stackOrderAppearance 按序列達(dá)到峰值的順序堆疊序列,這對(duì)于可讀性非常有用,尤其是對(duì)于基線為零的堆棧。另一方面,d3.stackOrderInsideOut 將峰值最早的序列定位在圖表的中間,將最新峰值的序列放在外面。此順序非常適合形狀圍繞中心基線分布的流線圖。
圖 5.19 D3 允許使用 order() 訪問器函數(shù)控制形狀堆疊的順序。在這里,我們看到堆積區(qū)域的示例,但相同的原則適用于堆積條形圖。
堆棧布局的另一個(gè)訪問器函數(shù)稱為 offset() ,控制圖表零基線的位置以及形狀在其周圍的分布方式。D3 有五個(gè)內(nèi)置偏移量,如圖 5.20 所示。
d3.stackOffsetNone 將所有形狀定位在零基線上方。它是默認(rèn)偏移量。
以下三個(gè)偏移分布基線上方和下方的形狀。d3.stackOffsetDiverging 將正值定位在基線上方,負(fù)值定位在基線下方。此偏移最適合堆積條形圖。d3.stackOffsetSilhouette 將基線移動(dòng)到圖表的中心。d3.stackOffsetWiggle的作用類似,但優(yōu)化了基線的位置,以最小化擺動(dòng)或序列的交替上下移動(dòng)。這三個(gè)偏移需要調(diào)整垂直刻度的域以適應(yīng)基線的位置。
最后,d3.stackOffsetExpand 規(guī)范化 0 到 1 之間的數(shù)據(jù)值,使每個(gè)索引的總和為 100%。歸一化值時(shí),垂直刻度的域也在 0 和 1 之間變化。
在創(chuàng)建堆疊布局時(shí),我們通常會(huì)組合順序和偏移量以達(dá)到所需的結(jié)果。雖然對(duì)于我們何時(shí)應(yīng)該使用順序或偏移量沒有嚴(yán)格的規(guī)定,但目標(biāo)應(yīng)始終是提高可視化的可讀性和/或?qū)⒆⒁饬性谖覀兿胍獜?qiáng)調(diào)的故事上。
對(duì)于本章的項(xiàng)目,我們將使用 order() 和 offset() 訪問器函數(shù)將堆積面積圖轉(zhuǎn)換為具有中心基線和堆積條形圖以表示相對(duì)值(介于 0 和 100% 之間)的流圖。
在我們開始之前需要注意的一件事是,order() 和 offset() 訪問器函數(shù)可以顯著更改注釋數(shù)據(jù)集中攜帶的值。例如,通過將堆積面積圖轉(zhuǎn)換為流圖,所表示的銷售價(jià)值將不再在 24 到 000,12 之間變化,而是在 -000,12 和 000,3 之間變化。同樣,如果我們使用 d0.stackOffsetExpand 來規(guī)范堆疊條形圖顯示的銷售額,則注釋數(shù)據(jù)將包含在 1 到 <> 之間。在設(shè)置垂直刻度的域時(shí),必須考慮這些不同的值。
考慮不同 offset() 訪問器函數(shù)帶來的域變化的一種簡單方法是確保我們始終計(jì)算注釋數(shù)據(jù)集中的最小值和最大值,并相應(yīng)地設(shè)置域。
在示例 5.11 中,我們首先聲明兩個(gè)空數(shù)組,一個(gè)存儲(chǔ)每個(gè)序列的最小值,另一個(gè)存儲(chǔ)最大值。然后我們遍歷帶注釋的數(shù)據(jù)集,使用 d3.min() 和 d3.max() 找到每個(gè)序列的最小值和最大值,并將它們推送到相應(yīng)的數(shù)組中。最后,我們從每個(gè)數(shù)組中提取最小值和最大值,并使用它們來設(shè)置域。
此策略可應(yīng)用于流圖和堆積條形圖。對(duì)于堆積條形圖,您可能希望從比例聲明中刪除 nice() 方法,以僅顯示介于 0 和 1 之間的值。
const minLowerBoundaries=[]; #A
const maxUpperBoundaries=[]; #A
annotatedData.forEach(series=> { #B
minLowerBoundaries.push(d3.min(series, d=> d[0])); #B
maxUpperBoundaries.push(d3.max(series, d=> d[1])); #B
}); #B
const minDomain=d3.min(minLowerBoundaries); #C
const maxDomain=d3.max(maxUpperBoundaries); #C
const yScale=d3.scaleLinear()
.domain([minDomain, maxDomain]) #D
.range([innerHeight, 0])
.nice();
完成此修改后,您可以自由測試偏移值的任何順序,并且 yScale 的域?qū)⒆詣?dòng)調(diào)整。
現(xiàn)在,要將堆疊面積圖轉(zhuǎn)換為流圖,我們所要做的就是將 order() 和 offset() 訪問器函數(shù)鏈接到之前聲明的堆棧生成器。在這里,我們使用訂單 d3.stackOrderInsideOut 與偏移量 d3.stackOffsetSilhouette 結(jié)合使用。我們鼓勵(lì)您測試一些組合,以了解它們?nèi)绾斡绊憯?shù)據(jù)表示。
const stackGenerator=d3.stack()
.keys(formatsInfo.map(f=> f.id))
.order(d3.stackOrderInsideOut)
.offset(d3.stackOffsetSilhouette);
流線圖在美學(xué)上令人愉悅,它們肯定會(huì)吸引注意力。但它們也更難閱讀。當(dāng)您想要概述現(xiàn)象隨時(shí)間推移的演變時(shí),流圖是一個(gè)很好的選擇。但是,如果您希望讀者能夠精確地測量和比較值,堆疊條形圖或成對(duì)條形圖是更好的選擇。工具提示還可以幫助提高流圖的可讀性。我們將在第 7 章中構(gòu)建一個(gè)。
同樣,我們通過將其偏移量設(shè)置為 d3.stackOffsetExpand 來修改堆積條形圖,這將規(guī)范化 0 到 1 之間的銷售值。我們還將順序設(shè)置為 d3.stackOrderDescending,以強(qiáng)調(diào) CD 格式在 2000 年左右如何主導(dǎo)市場。再次嘗試一些組合,看看它如何改變圖表傳達(dá)的故事焦點(diǎn)。
const stackGenerator=d3.stack()
.keys(formatsInfo.map(f=> f.id))
.order(d3.stackOrderDescending)
.offset(d3.stackOffsetExpand);
在最后一節(jié)中,我們將討論如何使用傳統(tǒng)的 HTML 元素輕松構(gòu)建圖例,并將通過在堆疊條形圖下方放置顏色圖例來將其付諸實(shí)踐。圖例是數(shù)據(jù)可視化的重要組成部分,可幫助讀者解釋他們所看到的內(nèi)容。
通常,圖例涉及文本,我們知道 SVG 文本并不總是便于操作。如果您查看我們將在圖 5.21 中構(gòu)建的顏色圖例,您會(huì)發(fā)現(xiàn)它由一系列彩色方塊和標(biāo)簽組成,與堆疊條形圖水平居中。使用 SVG 元素構(gòu)建此圖例將涉及計(jì)算每個(gè)矩形和文本元素的確切位置。這是可能的,但有一種更簡單的方法。
圖 5.21 我們將在本節(jié)中構(gòu)建的顏色圖例,位于堆積條形圖下方。
D3 不僅用于控制 SVG 元素。它可以創(chuàng)建和操作任何 DOM 元素。這意味著我們可以使用傳統(tǒng)的HTML元素構(gòu)建圖例,并使用CSS來定位它們。有很多方法可以繼續(xù),但這樣的圖例要求結(jié)構(gòu)化為 HTML 無序列表 ( <ul></ul> )。帶有標(biāo)簽的每個(gè)顏色組合都可以存儲(chǔ)在 <li></li> 元素中,其中一個(gè) <span></span> 元素保存顏色,另一個(gè)元素包含標(biāo)簽,如以下示例所示。
<ul>
<li>
<span> color 1 </span>
<span> label 1 </span>
</li>
<li>
<span> color 2 </span>
<span> label 2 </span>
</li>
...
</ul>
要使用 D3 構(gòu)建此 HTML 結(jié)構(gòu),我們轉(zhuǎn)到文件圖例.js并開始在函數(shù) addLegend() 中工作。在下面的代碼片段中,我們選擇帶有一類 legend-container 的 div,該類已存在于索引中.html .我們將一個(gè) ul 元素附加到這個(gè) div 中,并給它一類顏色圖例。
然后,我們使用數(shù)據(jù)綁定模式為 formatInfo 數(shù)組中包含的每種格式附加一個(gè) li 元素,該數(shù)組在共享常量中可用.js .我們將此選擇保存到一個(gè)常量中 命名 圖例項(xiàng) .
我們調(diào)用 legendItems 選擇并將 span 元素附加到其中,并根據(jù)相關(guān)的音樂格式設(shè)置跨度的背景顏色屬性。為此,我們可以直接從 格式信息 或調(diào)用色標(biāo)。最后,我們附加另一個(gè) span 元素并將其文本設(shè)置為當(dāng)前格式的標(biāo)簽鍵。
const legendItems=d3.select(".legend-container")
.append("ul") #A
.attr("class", "color-legend") #A
.selectAll(".color-legend-item") #A
.data(formatsInfo) #A
.join("li") #A
.attr("class", "color-legend-item");
legendItems #B
.append("span") #B
.attr("class", "color-legend-item-color") #B
.style("background-color", d=> d.color); #B
legendItems #C
.append("span") #C
.attr("class", "color-legend-item-label") #C
.text(d=> d.label); #C
如果您應(yīng)用的類名與上一代碼段中使用的類名相同,則圖例應(yīng)自動(dòng)如圖 5.21 所示。這是因?yàn)橐韵聵邮揭言?base 中設(shè)置.css .請(qǐng)注意我們?nèi)绾问褂?CSS flexbox 屬性 (https://css-tricks.com/snippets/css/a-guide-to-flexbox/) 來處理圖例的布局。我們不會(huì)花時(shí)間解釋這個(gè)樣式片段,因?yàn)槟赡苁煜SS,這不是本書的重點(diǎn)。這里的主要要點(diǎn)是,有時(shí)傳統(tǒng)的HTML元素和CSS樣式比SVG更容易操作,我們可以使用D3來綁定數(shù)據(jù)和操作任何DOM元素。
.color-legend {
display: flex;
justify-content: center;
flex-wrap: wrap;
margin: 0;
padding-left: 0;
}
.color-legend-item {
margin: 5px 12px;
font-size: 1.4rem;
}
.color-legend span {
display: inline-block;
}
.color-legend-item-color {
position: relative;
top: 2px;
width: 14px;
height: 14px;
margin-right: 5px;
border-radius: 3px;
}
您現(xiàn)在知道如何使用 D3 布局,如餅圖和堆棧布局。在第7章中,我們將把這個(gè)項(xiàng)目變成一個(gè)交互式可視化。如果您接下來想這樣做,請(qǐng)隨時(shí)直接去那里。
在現(xiàn)代網(wǎng)頁設(shè)計(jì)中,個(gè)人主頁是一個(gè)展示個(gè)人信息、技能、事件等的重要載體。為了吸引訪客的注意力并提供良好的用戶體驗(yàn),設(shè)計(jì)師通常會(huì)運(yùn)用各種技巧和效果來增加頁面的吸引力。本文將介紹如何使用CSS創(chuàng)建一個(gè)驚嘆的個(gè)人主頁介紹卡片,展示獨(dú)特魅力;
首先,需要定義基本的HTML結(jié)構(gòu)來容納個(gè)人主頁介紹卡片;
這里外層使用一個(gè)div包裹,里面使用三個(gè)<div>元素作為包裹容器布局,并在其中添加所需的圖像、內(nèi)容和按鈕等:
<div class="card">
<div class="box">
<div class="img_box">
<video
src="./assets/video.mp4"
muted
autoplay
loop
/>
</div>
</div>
<div class="box">
<div class="content">
<h2>
Alexa
<br>
<span>
Professional Artist
</span>
</h2>
<ul>
<li>
Posts
<span>22</span>
</li>
<li>
Followers
<span>999+</span>
</li>
<li>
Following
<span>7</span>
</li>
</ul>
<button>Follow</button>
</div>
</div>
<div class="circle">
<div class="img_box">
<img src="./assets/user.jpg" alt="">
</div>
</div>
</div>
外層是card容器,視頻和文本內(nèi)容區(qū)域是上下布局的,分別使用box容器包裹,最后是circle容器包裹頭像在定位在中間左邊超出;
注:
video設(shè)置屬性:靜音(muted)可實(shí)現(xiàn)自動(dòng)播放(autoplay),接著設(shè)置循環(huán)播放(loop);
img>和video>的父容器是一個(gè)類名img_box;
接下來,我們將使用CSS來為個(gè)人主頁介紹卡片添加樣式。以下是一些關(guān)鍵的樣式屬性和技巧,可以使卡片看起來更加漂亮和吸引人;
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--clr: #083d41
}
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: var(--clr);
}
.card {
background-color: var(--clr);
position: relative;
width: 320px;
height: 430px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
/* 先把容器基本樣式調(diào)整一下 */
.card .box {
background-color: tomato;
position: relative;
width: 110%;
height: 200px;
/* 文本內(nèi)容區(qū)域圓角 */
border-radius: 20px;
}
/* 頭像容器則使用定位布局 */
.card .circle {
width: 180px;
height: 180px;
position: absolute;
left: -70px;
top: 50%;
transform: translateY(-50%);
border-radius: 50%;
border: 10px solid var(--clr);
}
/* 調(diào)整img和video共有的父容器樣式 */
.card .box .img_box,
.card .circle .img_box {
position: absolute;
inset: 0;
overflow: hidden;
/* img的圓角 */
border-radius: 50%;
}
.card .box .img_box {
/* video的圓角 */
border-radius: 15px;
}
/* 調(diào)整圖片和視頻的樣式 */
.card .box .img_box video,
.card .circle .img_box img {
position: absolute;
width: 100%;
height: 100%;
object-fit: cover;
}
調(diào)整card下的第一個(gè)box容器樣式,也就是包裹視頻的容器:
.card .box:nth-child(1)::before {
content: "";
width: 20px;
height: 20px;
background-color: transparent;
position: absolute;
z-index: 10;
top: 106px;
left: -1px;
border-bottom-left-radius: 20px;
box-shadow: -6px 6px var(--clr);
}
/* 樣式同before類似,注意定位樣式 */
.card .box:nth-child(1)::after {
content: "";
width: 20px;
height: 20px;
background-color: transparent;
position: absolute;
z-index: 10;
bottom: -1px;
left: 105px;
border-bottom-left-radius: 20px;
box-shadow: -6px 6px var(--clr);
}
目前添加樣式效果圖,可以在調(diào)試階段更改明顯色彩用于調(diào)整距離、位置等;
調(diào)整card下的第二個(gè)box容器樣式,也就是包含文字信息的容器:
.card .box:nth-child(2) {
background-color: #fff;
width: 100%;
height: 220px;
}
.card .box:nth-child(2)::before {
content: "";
width: 20px;
height: 20px;
background-color: transparent;
position: absolute;
z-index: 10;
bottom: 106px;
left: -1px;
border-top-left-radius: 20px;
box-shadow: -6px -6px var(--clr);
}
.card .box:nth-child(2)::after {
content: "";
width: 20px;
height: 20px;
background-color: transparent;
position: absolute;
z-index: 10;
top: -1px;
left: 109px;
border-top-left-radius: 20px;
box-shadow: -6px -6px var(--clr);
}
.card .box .content {
position: absolute;
inset: 0;
padding: 30px 10px 20px;
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
}
/* 姓名和Title樣式 */
.card .box .content h2 {
width: 100%;
padding-left: 120px;
text-transform: uppercase;
letter-spacing: 0.1em;
line-height: 1.1em;
font-size: 1.15em;
font-weight: 600;
color: #333;
}
.card .box .content h2 span {
letter-spacing: 0.05em;
font-size: 0.75em;
font-weight: 400;
color: tomato;
text-transform: initial;
}
/* 列表樣式 */
.card .box .content ul {
position: relative;
top: 15px;
width: 100%;
padding: 0 10px;
display: grid;
grid-template-columns: repeat(3, 1fr);
}
.card .box .content ul li {
list-style: none;
display: flex;
flex-direction: column;
align-items: center;
padding: 0 10px;
font-size: 0.85em;
font-weight: 500;
color: #999;
}
.card .box .content ul li:not(:last-child)
{
border-right: 1px solid #ccc;
}
.card .box .content ul li span{
font-size: 1.65em;
color: #333;
}
/* 按鈕樣式 */
.card .box .content button {
position: relative;
top: 25px;
padding: 8px 30px;
border: none;
outline: none;
background-color: #03a9f4;
border-radius: 30px;
color: #fff;
font-size: 1em;
letter-spacing: 0.2em;
text-transform: uppercase;
font-weight: 500;
cursor: pointer;
border: 5px solid var(--clr);
box-shadow: 0 0 0 10px #fff;
transition: .5s;
}
.card .box .content button:hover {
letter-spacing: 0.5em;
background-color: #ff3d7f;
}
由于按鈕的圓角與文本內(nèi)容卡片的交界處看上去顯得有些過于突兀了; 所以現(xiàn)在把它們的交界處優(yōu)化成弧形,樣式類似box的偽元素,這里也給按鈕創(chuàng)建兩個(gè)偽元素,用于優(yōu)化兩邊的交界處:
.card .box .content button::before {
content: "";
width: 20px;
height: 20px;
background-color: transparent;
position: absolute;
top: 23px;
left: -29px;
border-top-right-radius: 20px;
box-shadow: 5px -7px #fff;
}
.card .box .content button::after {
content: "";
width: 20px;
height: 20px;
background-color: transparent;
position: absolute;
top: 23px;
right: -29px;
border-top-left-radius: 20px;
box-shadow: -5px -7px #fff;
}
除了基本樣式之外,還進(jìn)一步優(yōu)化個(gè)人主頁介紹卡片的細(xì)節(jié)。一些可選的技巧包括:
通過運(yùn)用CSS的各種樣式屬性和技巧,我們可以輕松地創(chuàng)建漂亮的個(gè)人主頁介紹卡片。這些卡片不僅能夠有效地展示個(gè)人信息和技能,還能夠吸引訪客的注意力并提供良好的用戶體驗(yàn)。記得嘗試不同的樣式和效果來定制你自己獨(dú)特的個(gè)人主頁卡片!
CSS創(chuàng)作個(gè)人主頁介紹卡片,展示獨(dú)特魅力
原文鏈接:https://juejin.cn/post/7260709771870060603
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。