平時開發中,有時候會碰到下面這種“弧形”樣式,主要分為“內凹”和“外凸”兩種類型,如下
該如何實現呢?或者想一下,有哪些 CSS 屬性和“弧形”有關?下面介紹 3 種方式,一起看看吧
通常情況下,我們用border-radius都是這樣
div{
border-radius: 20px;
}
這樣表示 4 個角都是圓角,并且是標準的正圓
其實,border-radius還支持斜杠的寫法,比如
div{
border-radius: 20px / 10px;
}
這表示,圓角是一個x半徑為20px,y半徑為10px的橢圓,如下
放大來看,其實是這樣的
進一步,我們將x半徑設置成50%,這樣就能得到一個完整的弧形了
div{
border-radius: 50% / 20px;
}
效果如下
這樣就得到了“外凸”的弧形了,是不是很容易?
至于“內凹”弧形,單純的border-radius表示無能為力,可以看接下來的方式
這個其實大多數同學都能想到的方式,一個矩形和一個橢圓組合起來,不就是一個弧形卡片了嗎?原理非常簡單
用代碼實現就是
div{
position: relative;
}
div::after{
content: '';
position: absolute;
width: 100%;
height: 20px;
border-radius: 100%;
background: inherit; /* 繼承父級背景 */
bottom: 0;
left: 50%;
transform: translate(-50%,50%);
}
效果如下(虛線表示偽元素的邊緣)
用偽元素的好處是,可以隨意控制弧度的大小和位置,這個是border-radius所不能比的
通過overflow:hidden裁剪多余部分,可以得到一個邊緣比較“鋒利”的弧形,如下所示
另外,用偽元素還能實現“內凹”的效果,不過這需要反過來思考,什么意思呢?之前是給偽元素加的背景,現在需要加在偽元素的外圍,這里用box-shadow實現,原理如下
用代碼實現就是
div{
background: none;
overflow: hidden;
}
div::after{
content: '';
background: none;
box-shadow: 0 0 0 9999vh #FFE8A3; /*足夠大的陰影*/
z-index: -1;
}
效果如下(虛線表示偽元素的邊緣)
提到圓弧,還可以想到徑向漸變,沒錯,這里通過徑向漸變也能輕易實現弧形卡片效果。
先來看“外凸”的,其實思路和偽元素一樣,先拆分,一個橢圓和一個矩形,對應的就是徑向漸變(radial-gradient)和線性漸變(linear-gradient),如下
用代碼實現就是
div{
background:
radial-gradient(closest-side, #9747FF 100%,transparent 0) center bottom/100% 20px no-repeat,
linear-gradient(#FFE8A3, #FFE8A3) 0 0/100% calc(100% - 10px) no-repeat;
}
效果如下(紫色部分是徑向漸變)
再來看“內凹”的弧形,其實也和偽元素思路類似,只不過這里需要繪制一個足夠大的漸變,從透明到純色的徑向漸變,示意如下
調整好漸變中心點,用代碼實現就是
div{
background: radial-gradient(50% 10px at center bottom, transparent 100%,#FFE8A3 0) center bottom;
}
效果如下(全部都是徑向漸變繪制)
當然你也可以隨意改變徑向的弧度和大小,來實現各種不同的效果。
以上所有demo都可以查看以下在線鏈接
以上就是本文的全部內容了,共介紹了 3 種不同的方式,下面總結一下各自優缺點
作者:XboxYan
鏈接:https://juejin.cn/post/7308434314777788426
您已經熟悉我們使用和組合用于制作數據可視化的常見 SVG 形狀:線條、矩形和圓形。您甚至已經使用矩形從頭開始創建了條形圖。但是我們可以用原始形狀畫出的東西太多了。為了創建更復雜的可視化,我們通常轉向 SVG 路徑。正如我們在第 1 章中所討論的,SVG 路徑是所有 SVG 元素中最靈活的,幾乎可以采用任何形式。我們在 D3 項目中廣泛使用它們,最簡單的例子是在折線圖中繪制線條和曲線或在圓環圖中繪制弧線。
SVG 路徑的形狀由其 d 屬性確定。此屬性由指示路徑的起點和終點、用于更改方向的曲線類型以及路徑是打開還是閉合的命令組成。路徑的 d 屬性可能很快變得又長又復雜。大多數時候,我們不想自己創作它。這就是 D3 的形狀生成器功能的用武之地!
在本章中,我們將構建如圖 4.1 所示的項目:溫度演變的折線圖和一組弧線,可視化 2021 年紐約市降水天數的百分比。您可以在 https://d3js-in-action-third-edition.github.io/new-york-city-weather-2021/ 在線找到此項目。基礎數據來自地下天氣(www.wunderground.com/)。
圖 4.1 我們將在本章中構建的項目:2021 年紐約市溫度演變的折線圖和一組顯示降水天數百分比的弧線圖。
我們將使用 D3 的形狀生成器函數創建這兩個可視化效果。但在開始之前,我們將討論 D3 的邊距約定以及如何向圖表添加軸。
開發數據可視化通常需要提前規劃如何使用 SVG 容器中的可用空間。首先開始玩很酷的東西是非常誘人的,也就是可視化的核心,但相信我們。一點點的準備可以為您節省大量的執行時間。所有編程任務都是如此,在一般生活中也是如此!在此規劃階段,我們不僅要考慮圖表本身,還要考慮使圖表可讀的互補元素,例如軸、標簽和圖例。
在本節中,我們將介紹邊距約定,這是一種便于為這些不同元素分配空間的方法。然后,我們將討論如何向可視化添加軸以及組成 D3 軸的多個 SVG 元素。我們將這些概念應用于圖 4.1 所示的折線圖。
在我們開始之前,請轉到第 4 章的代碼文件。您可以從本書的 Github 存儲庫下載它們(如果您還沒有 (https://github.com/d3js-in-action-third-edition/code-files)。在名為 chapter_04 的文件夾中,代碼文件按節進行組織。要開始本章的練習,請在代碼編輯器中打開 4.1-Margin_convention_and_axes/start 文件夾并啟動本地 Web 服務器。如果您需要有關設置本地開發環境的幫助,請參閱附錄 A。
您可以在位于本章代碼文件根目錄下的自述文件中找到有關項目文件夾結構的更多詳細信息。
使用本章的代碼文件時,在代碼編輯器中僅打開一個開始文件夾或一個結束文件夾。如果一次打開章節的所有文件并使用 Live Server 擴展為項目提供服務,則數據文件的路徑將無法按預期工作。
我們將開始在文件折線圖中工作.js并使用方法 d3.csv() 加載每周溫度數據集。
d3.csv("../data/weekly_temperature.csv");
在第 3 章中,我們解釋了 D3 在加載表格數據集時執行的類型轉換會影響值的類型。例如,原始數據集中的數字變成字符串,我們需要將它們變回數字以方便操作它們。我們已經看到 d3.csv() 的回調函數 ,我們可以逐行訪問數據,是執行此類轉換的好地方。在這里,我們將介紹一個小技巧。我們可以調用方法 d3.autoType ,而不是手動轉換數字。此函數檢測常見的數據類型,如日期和數字,并將它們轉換為相應的 JavaScript 類型。
d3.csv("../data/weekly_temperature.csv", d3.autoType);
請注意,數據類型可能不明確,并且 d3.autoType 有時會選擇錯誤的類型。因此,在數據數組完全加載后仔細檢查數據數組非常重要。在下面的代碼片段中,我們使用 JavaScript Promise 訪問加載的數據集,并將其登錄到控制臺,以確認日期被格式化為 JavaScript 日期,溫度被格式化為 數字。您可以在圖 4.2 中看到結果。
d3.csv("../data/weekly_temperature.csv", d3.autoType).then(data => {
console.log("temperature data", data);
});
圖 4.2 由于 d3.autoType 方法,日期被格式化為 JavaScript 日期,溫度被格式化為數字。
我們使用 JavaScript Promise 來訪問數據集,因為加載數據是一個異步過程(如果您需要復習有關使用 D3 加載和訪問數據的信息,請參閱第 3 章)。但是現在我們知道我們的數據集已完全加載并正確格式化,我們可以開始構建圖表了。
文件折線圖.js已經包含一個名為 drawLineChart() ,我們將在其中創建折線圖。在 JavaScript Promise 的回調函數中,調用函數 drawLineChart() 并將數據集作為參數傳遞。
d3.tsv("../data/weekly_temperature.csv", d3.autoType).then(data => {
console.log("temperature data", data);
drawLineChart(data);
});
我們現在準備討論保證金慣例并將其應用于我們的圖表!
D3 邊距約定旨在以系統和可重用的方式為軸、標簽和圖例保留圖表周圍的空間。該約定使用四個邊距:圖表的上方、右側、下方和左側,如圖 4.3 所示。通過說明這些邊距,我們可以知道圖表核心剩余區域的位置和大小,我們稱之為內部圖表。
圖 4.3 D3 邊距約定設置圖表頂部、右側、底部和左側的邊距值。
邊距值在邊距對象中聲明,該對象由上邊距、右邊距、下邊距和左邊距組成。讓我們為折線圖創建邊距對象。在函數 drawLineChart() 中,聲明一個名為 margin 的常量。如以下代碼片段所示,為上邊距、右邊距、下邊距和左邊距分別指定 40、170、25 和 40px 的值。
const drawLineChart = (partialData) => {
const margin = {top: 40, right: 170, bottom: 25, left: 40};
};
事先確切知道軸和標簽需要多少空間通常是不可能的。我們從一個有根據的猜測開始,如果需要,稍后會進行調整。例如,查看圖 4.1 中的折線圖或托管項目 (https://d3js-in-action-third-edition.github.io/new-york-city-weather-2021/)。您將看到可視化效果右側顯示的標簽相對較長,因此右邊距為 170px。另一方面,軸的標簽不占用太多空間;因此,剩余的邊距可以小得多。
聲明邊距對象后,我們就可以開始考慮 SVG 容器的大小了。知道了 SVG 容器的大小和邊距,我們最終可以計算出兩個新常量,分別名為 innerWidth 和 innerHeight ,它們代表內部圖表的寬度和高度。這些尺寸如圖4.4所示。
圖 4.4 知道 SVG 容器的尺寸和邊距,我們可以計算內部圖表的寬度和高度。
內部圖表的寬度對應于 SVG 容器的寬度減去左側和右側的邊距。如果 SVG 容器的寬度為 1000 像素,每邊的邊距分別為 170 和 40 像素,則內部圖表仍保留 790 像素。同樣,如果 SVG 容器的高度為 500px,我們通過從總高度中減去頂部和底部邊距來計算內部圖表的高度,因此為 435px。通過使常量 innerWidth 和 innerHeight 與邊距成正比,我們確保如果我們以后需要更改邊距,它們會自動調整。
const margin = {top: 40, right: 170, bottom: 25, left: 40};
const width = 1000;
const height = 500;
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
現在讓我們附加折線圖的 SVG 容器。仍然在函數 drawLineChart() 中工作,將一個 SVG 元素附加到 div 中,其 id 為 折線圖,該元素已經存在于文件索引中.html并使用寬度和高度常量設置其 viewBox 屬性。您還可以臨時將邊框應用于 SVG 元素,以幫助您查看正在工作的區域。如果您需要復習如何將元素附加到 DOM 或設置其屬性和樣式,請參閱第 2 章。
const svg = d3.select("#line-chart")
.append("svg")
.attr("viewBox", `0, 0, ${width}, ${height}`);
我們之前已經聲明了邊距,這些邊距將決定為內部圖表保留的區域。知道 SVG 容器的坐標系從其左上角開始,內部圖表的每個元素都必須向保留區域移動。我們可以將內部圖表包裝在 SVG 組中并僅對該組應用平移,而不是將此置換應用于每個元素。如圖 4.5 所示,此策略為內部圖表創建了一個新的坐標系。
圖 4.5 應用于將包含內部圖表的 SVG 組的平移,為內部圖表中包含的元素創建新的坐標系。
為了將此策略付諸實施,我們將一個組附加到 SVG 容器。然后,我們根據左邊距和上邊距對組應用翻譯。最后,我們將 SVG 組保存到名為 innerChart 的常量中,稍后我們將使用該常量構建折線圖。
const innerChart = svg
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);
保證金約定和此處介紹的策略的主要優點是,一旦實施,我們就不再需要考慮它了。我們可以繼續創建軸和圖表,同時知道為標簽、圖例和其他補充信息保留了一個區域。
建立邊距約定后,我們準備向圖表添加軸。軸是數據可視化的重要組成部分。它們可作為查看者理解所代表的數字和類別的參考。
如果您查看圖 4.1 中的折線圖或托管項目 (https://d3js-in-action-third-edition.github.io/new-york-city-weather-2021/),您將看到兩個軸。水平軸,也稱為 x 軸,顯示每個月的位置。垂直軸或 y 軸用作以華氏度為單位的溫度的參考。
在 D3 中,我們使用 axis() 組件生成器創建軸。此生成器將比例作為輸入,并返回組成軸的 SVG 元素作為輸出。如果您還記得我們在第 3 章中關于比例的討論,您就會知道它們將數據值映射到屏幕上。例如,對于我們的折線圖,刻度將為我們計算數據集中每個日期的水平位置或其相關溫度的垂直位置。
創建軸的第一步實際上是聲明其比例。首先,我們需要一個水平定位日期的刻度。這正是 D3 的時間尺度 d3.scaleTime() 的作用(有關選擇 D3 尺度的幫助,請參閱附錄 B)。時間尺度是第3章討論的第一類尺度的一部分。它接受連續輸入并返回連續輸出。時間尺度的行為與第3章中使用的線性尺度非常相似,唯一的區別是它操縱與時間相關的數據并計算它們在空間中的位置。
讓我們聲明我們的時間刻度并將其命名為 xScale,因為它將負責沿 x 軸定位元素。我們規模的范圍從數據集中的第一個日期延伸到最后一個日期。在下面的代碼片段中,我們使用 d3.min() 和 d3.max() 來查找這些值。
刻度所涵蓋的范圍隨著內部圖表中可用的水平空間而擴展(見圖4.5)。在內部圖表的坐標系中,這意味著范圍從零擴展到之前計算的 innerWidth。如果您需要復習聲明 D3 刻度的域和范圍,請參閱第 3 章。
const firstDate = d3.min(data, d => d.date);
const lastDate = d3.max(data, d => d.date);
const xScale = d3.scaleTime()
.domain([firstDate, lastDate])
.range([0, innerWidth]);
沿y軸分布的溫度也需要第一個系列的刻度,具有連續的輸入和輸出。線性刻度在這里將是完美的,因為我們希望溫度和折線圖上的垂直位置是線性比例的。
在下面的代碼片段中,我們聲明了我們的溫標并將其命名為 yScale,因為它將負責沿 y 軸定位元素。在這里,我們希望我們的 y 軸從零開始,因此我們將零作為域的第一個值傳遞。盡管數據集中的最低溫度約為 26°F,但從零開始 y 軸通常是一個好主意,在我們的例子中,這將使我們能夠正確看到溫度的演變。但就像生活中的大多數事情一樣,這不是一個硬性規則,這個圖表沒有正確或錯誤的答案,特別是因為華氏度的零不是絕對的零。
我們將數據集中的最高溫度作為域的第二個值傳遞。我們通過使用函數 d3.max() 查詢數據集中的列max_temp_F來找到此值。
我們的刻度范圍隨著內部圖表的高度而擴展。由于垂直值是在 SVG 坐標系中從上到下計算的,因此范圍從 innerHeight(內圖左下角的位置)開始,到零(對應于其左上角的位置)結束。
const maxTemp = d3.max(data, d => d.max_temp_F);
const yScale = d3.scaleLinear()
.domain([0, maxTemp])
.range([innerHeight, 0]);
初始化刻度后,我們就可以附加軸了。D3 有四個軸生成器:axisTop()、axisRight()、axisBottom() 和 axisLeft() ,它們分別創建頂部、右側、底部和左側軸的組件。它們都是 d3 軸模塊 (https://github.com/d3/d3-axis) 的一部分。
我們提到軸生成器函數將刻度作為輸入。例如,要創建折線圖的底部軸,我們調用生成器 axisBottom() 并將 xScale 作為參數傳遞,因為此刻度負責沿底部軸分布數據。我們將生成器保存在名為 底軸 .
const bottomAxis = d3.axisBottom(xScale);
軸生成器是構造組成軸的元素的函數。為了使這些元素出現在屏幕上,我們需要使用 call() 方法從 D3 選擇中調用軸生成器。在下面的代碼片段中,請注意我們如何在調用軸生成器之前使用 innerChart 選擇并將組元素附加到其中。該組的類名為 axis-x,這將幫助我們稍后定位和設置軸的樣式。
const bottomAxis = d3.axisBottom(xScale);
innerChart
.append("g")
.attr("class", "axis-x")
.call(bottomAxis);
圖 4.6 默認情況下,D3 軸在所選內容的原點生成,此處為內部圖表的左上角。我們需要應用翻譯將它們移動到所需的位置。
在瀏覽器中查看生成的軸。默認情況下,D3 軸顯示在所選內容的原點,在本例中為內部圖表區域的左上角,如圖 4.6 所示。我們可以通過對包含軸的 SVG 組應用平移來將軸移動到圖表底部。請記住,應用于組的轉換由其所有子級繼承。在下面的代碼片段中,我們將包含軸元素的組向下平移一個對應于內部圖表高度的值。
const bottomAxis = d3.axisBottom(xScale);
innerChart
.append("g")
.attr("class", "axis-x")
.attr("transform", `translate(0, ${innerHeight})`)
.call(bottomAxis);
我們要更改的另一件事是軸標簽的格式。默認情況下,D3 調整軸上的時間表示形式,根據域顯示小時、天、月或年標簽。但是這種默認格式并不總是提供我們正在尋找的標簽。幸運的是,D3 提供了多種方法來更改標簽的格式。
首先,我們注意到 x 軸有 3 月至 <> 月的標簽,這很好,但沒有 <> 月的標簽。根據您居住的時區,第一個日期可能不完全是 <> 月 <> 日的午夜,這使 D<> 無法將其識別為我們第一個月的開始。由于我們的數據集不是動態的,因此對 firstDate 變量進行硬編碼是一個合理的解決方案。為此,我們將使用 JavaScript Date() 構造函數。
在下面的代碼片段中,firstDate 成為一個新的 Date() 對象。在括號之間,我們首先聲明年份 ( 2021 年 )、月份 ( 00,因為月份索引為零索引)、日 ( 01 ),并可選擇在它后面跟小時、分鐘和秒 ( 0, 0, 0 )。
const firstDate = new Date(2021, 00, 01, 0, 0, 0);
const lastDate = d3.max(data, d => d.date);
const xScale = d3.scaleTime()
.domain([firstDate, lastDate])
.range([0, innerWidth]);
如果保存項目,你將看到我們現在在 1 月 2021日的位置有一個標簽。但是標簽只給了我們 01 年,這并沒有錯,因為 Fri Jan 2021 00 00:00:2021 對應于 <> 年的開始,但我們更愿意有一個月份標簽。
圖 4.7 默認情況下,D3 調整軸標簽上的時間表示。在我們的例子中,它表示 1 月 2021日作為 <> 年的開始。這沒有錯,但對于可讀性來說并不理想。
我們可以使用方法 axis.tickFormat() 更改軸標簽的格式,該方法在 d3 軸模塊 (https://github.com/d3/d3-axis) 中可用。刻度是您在軸上看到的短垂直線。它們通常(但不一定)附有勾號標簽。
假設我們希望刻度標簽是縮寫的月份名稱。在 D3 中,我們可以使用方法 d3.timeFormat() 格式化與時間相關的值,來自模塊 d3-time-format (https://github.com/d3/d3-time-format)。此方法接受格式作為參數,例如,%b 表示月份名稱的縮寫。您可以在模塊中查看可用格式的完整列表。
在下面的代碼片段中,我們將 tickFormat() 方法鏈接到之前聲明的底部軸,并將時間格式作為參數傳遞。
const bottomAxis = d3.axisBottom(xScale)
.tickFormat(d3.timeFormat("%b"));
圖 4.8 使用每個月縮寫名稱格式化的底部軸標簽。
我們的標簽現在格式正確!它們標記每個月的開始,這還不錯,但我們可以通過在各自的刻度之間居中月標簽來提高可讀性,以建議每個月從一個刻度延伸到下一個刻度。
要更改刻度標簽的位置,我們首先需要選擇它們。打開瀏覽器的檢查器,仔細查看 D3 為軸生成的 SVG 元素。首先,我們有一個帶有類域的路徑元素,該元素在范圍(或域的表示)上繪制一條水平線。此路徑包括兩個外部刻度,即形狀兩端的短垂直線,如圖 4.9 所示。軸的刻度和標簽由線條和文本元素組成,組織成具有刻度類的 SVG 組。這些 SVG 組沿軸平移以設置其行和文本元素的位置。軸生成器創建的元素的類型和類是 D3 公共 API 的一部分。您可以使用它們來自定義軸外觀。
圖 4.9 組成軸的 SVG 元素
考慮到這種結構,我們可以使用選擇器選擇x軸的所有標簽 “.axis-x 文本” ,這意味著我們使用類軸-x抓取組中的每個文本元素。然后我們執行一些調整。首先,我們使用文本元素的 y 屬性將文本元素向下移動 10px。這種增加的垂直空白將提高可讀性。我們還將他們的字體系列設置為Roboto,這是我們已經在項目中使用的字體。默認情況下,D3 將軸的字體系列設置為無襯線,防止標簽繼承項目的字體系列。最后,我們將它們的字體大小增加到 14px。
出于關注點分離的目的,最后兩個樣式調整最好從CSS文件中處理。但在這里,我們使用 D3 來簡化指令。
d3.selectAll(".axis-x text")
.attr("y", "10px")
.style("font-family", "Roboto, sans-serif")
.style("font-size", "14px");
為了使月份標簽在其相應的刻度之間居中,我們將使用 x 屬性。由于每個月都有不同的長度(在 28 到 31 天之間),我們需要為每個標簽找到該月第一天和下個月第一天之間的中位數位置。請注意,D3 已在 g.axis-x 上將文本錨點屬性設置為“中間”。
我們知道 D3 附加到每個標簽的數據對應于該月的第一天。在下面的代碼片段中,我們通過將 JavaScript 方法 getMonth() 應用于當前月份或附加到標簽的值來查找下個月。此方法返回一個介于 0 和 11 之間的數字,0 表示 11 月,<> 表示 <> 月。然后,我們可以通過將年份、下個月和每月的第一天傳遞給 Date() 對象來創建新的 JavaScript 日期。
最后,我們使用 xScale 計算月初和下個月開始之間的中位數距離。完成后,您的軸應如圖 4.10 所示。
d3.selectAll(".axis-x text")
.attr("x", d => {
const currentMonth = d;
const nextMonth = new Date(2021, currentMonth.getMonth() + 1, 1);
return (xScale(nextMonth) - xScale(currentMonth)) / 2;
})
.attr("y", "10px")
.style("font-family", "Roboto, sans-serif")
.style("font-size", "14px");
圖 4.10 格式化的 x 軸,月份標簽在各自的刻度之間居中。
那是很多操縱!但希望它能讓您了解我們可以自定義 D3 軸的不同方法。
我們現在將添加 y 軸,其步驟將更加簡單。我們使用軸生成器 d3.axisLeft() ,因為我們想將 y 軸定位在圖表的左側。我們將 yScale 作為參數傳遞,并將軸保存在名為 leftAxis 的常量中。
const leftAxis = d3.axisLeft(yScale);
再一次,我們希望將軸附加到內部圖表。我們將一個組附加到內部圖表選擇中,給它一個 axis-y 類并調用 leftAxis 。
const leftAxis = d3.axisLeft(yScale);
innerChart
.append("g")
.attr("class", "axis-y")
.call(leftAxis);
如果保存項目并在瀏覽器中查看,則會看到 y 軸已正確定位。我們所要做的就是更改標簽的字體并增加它們的大小。在下面的代碼片段中,我們使用類軸-y 選擇組內的所有文本元素。我們使用它們的 x 屬性將它們稍微向左移動以獲得更好的可讀性,并設置它們的字體系列和字體大小屬性。
d3.selectAll(".axis-y text")
.attr("x", "-5px")
.style("font-family", "Roboto, sans-serif")
.style("font-size", "14px");
您可能已經注意到,我們必須重復代碼來設置軸標簽的字體系列和字體大小屬性。在學習環境中,這沒什么大不了的,但我們通常會盡量避免在專業項目中出現這種重復。前面提到的更好的解決方案是從CSS文件控制這些樣式。另一種可能是使用組合選擇器應用它們,如下所示。
d3.selectAll(".axis-x text, .axis-y text")
.style("font-family", "Roboto, sans-serif")
.style("font-size", "14px");
圖 4.11 完成的 x 軸和 y 軸。
我們已經完成了我們的軸,但我們仍然應該做一件事來幫助讀者理解我們的圖表。x 軸上的標簽是不言自明的,但 y 軸上的標簽不是。我們知道它們在 0 到 90 之間變化,但我們不知道它們代表什么。
我們可以通過向軸添加標簽來解決此問題。在 D3 項目中,標簽只是文本元素,所以我們所要做的就是將文本元素附加到 SVG 容器中。我們將其內容設置為“溫度(°F)”,并將其垂直位置設置為SVG容器原點下方20px。就是這樣!您的項目現在應如圖 4.12 所示。在下一節中,我們將繪制折線圖。
svg
.append("text")
.text("Temperature (°F)")
.attr("y", 20);
圖 4.12 完成的軸和標簽。
現在,我們已準備好構建最常見的數據可視化之一:折線圖。折線圖由連接數據點的線或插入這些數據點的曲線組成。它們通常用于顯示現象隨時間推移的演變。在 D3 中,這些直線和曲線是使用 SVG 路徑元素構建的,這些元素的形狀由其 d 屬性確定。在第 1 章中,我們討論了 d 屬性是如何由一系列命令組成的,這些命令指示如何繪制形狀。我們還說過,它很快就會變得復雜。值得慶幸的是,d3 形狀模塊 (https://github.com/d3/d3-shape) 提供了為我們計算 d 屬性的線和曲線生成器函數,簡化了折線圖的創建。
在本節中,我們將繪制一條線/曲線,顯示 2021 年紐約市平均溫度的演變,就像您在托管項目 (https://d3js-in-action-third-edition.github.io/new-york-city-weather-2021/) 或圖 4.1 中看到的那樣。但首先,讓我們在屏幕上顯示每個數據點。雖然這一步對于繪制折線圖不是必需的,但它將幫助我們了解 D3 的線生成器函數的工作原理。
在函數 drawLineChart() 中工作,我們使用數據綁定模式為數據集weekly_temperature.csv中的每一行創建一個圓圈。我們將這些圓圈附加到 innerChart 選擇中,并給它們一個 4px 的半徑。然后我們使用x和y尺度計算它們的位置屬性(cx和cy)。
如果你還記得我們從第 3 章開始關于數據綁定的討論,你就知道我們可以使用訪問器函數訪問綁定到每個圓圈的數據。在下面的代碼片段中,d 公開了附加到每個圓的基準面。這些數據是一個 JavaScript 對象,我們可以使用點符號訪問日期或平均溫度。如果您需要查看此概念,請參閱第 3.3.1 節。
請注意我們如何聲明一個名為“茄子”的單獨顏色常量,并使用它來設置圓圈的填充屬性。在這個項目中,我們將重復使用相同的顏色幾次,因此將其放在常量中會很方便。隨意使用您喜歡的任何顏色!
const aubergine = "#75485E";
innerChart
.selectAll("circle") #A
.data(data) #A
.join("circle") #A
.attr("r", 4)
.attr("cx", d => xScale(d.date)) #B
.attr("cy", d => yScale(d.avg_temp_F)) #B
.attr("fill", aubergine);
保存您的項目并查看瀏覽器中的圓圈。它們應位于 29 到 80°F 之間,并形成圓頂狀形狀,如圖 4.13 所示。
圖 4.13 平均溫度隨時間演變的數據點。
在這個階段要指出的一件很酷的事情是,即使沒有注意到它,你現在也知道如何繪制散點圖!散點圖只是一個圖表,顯示沿 x 軸和 y 軸定位的數據點集合,并可視化兩個或多個變量之間的關系。
您知道如何繪制軸,并且知道如何根據其相關數據在屏幕上定位數據點,因此您可以完全構建散點圖!這就是D3的酷之處。您不必學習如何創建特定的圖表。相反,您可以通過生成和組裝構建基塊來構建可視化效果。對于散點圖,這些構建基塊可以像兩個軸和一組圓一樣簡單。在第 7 章中,我們將構建一個散點圖,其中圓的面積根據變量而變化。
散點圖示例
現在我們清楚地看到了每個數據點的位置,引入 D3 的線生成器會更容易。行生成器 d3.line() 是一個函數,它將每個數據點的水平和垂直位置作為輸入,并返回線或折線的 d 屬性,作為輸出傳遞通過這些數據點。我們通常用兩個訪問函數 x() 和 y() 鏈接線生成器,分別將數據點的水平和垂直位置作為參數,如圖 4.14 所示。
圖 4.14 行生成器 d3.line() 與兩個訪問器函數 x() 和 y() 結合使用,它們分別將每個數據點的水平和垂直位置作為參數。
讓我們為折線圖聲明一個線生成器函數。我們首先調用方法 d3.line() 并使用 x() 和 y() 訪問器函數進行鏈接。x() 訪問器函數將每個數據點的水平位置作為參數。如果我們像到目前為止所做的那樣遍歷數據,我們可以使用參數 d 來訪問每個基準面(數據集的每一行)。數據點的水平位置對應于它們表示的日期,并使用 xScale() 計算。同樣,數據點的垂直位置與當天的平均溫度成正比,并由 yScale() 返回。我們將行生成器函數存儲在名為 lineGenerator 的常量中,以便稍后可以調用它。
const lineGenerator = d3.line()
.x(d => xScale(d.date)) #A
.y(d => yScale(d.avg_temp_F)); #B
然后,我們將一個路徑元素附加到內部圖表,并通過調用線生成器并將數據集作為參數傳遞來設置其 d 屬性。
默認情況下,SVG 路徑具有黑色填充。如果我們只想看到一條線,我們需要將填充屬性設置為 none 并將筆觸屬性設置為我們選擇的顏色;這里,顏色存儲在茄子常數中。此筆畫將成為我們的折線圖,如圖 4.15 所示。
innerChart
.append("path")
.attr("d", lineGenerator(data)) #A
.attr("fill", "none")
.attr("stroke", aubergine);
圖 4.15 使用線生成器創建并穿過每個數據點的 SVG 路徑,生成折線圖。
在像我們的折線圖這樣的情況下,離散數據點覆蓋了整個數據范圍,用簡單的線條表示數據點是一個很好的解決方案。但有時,我們需要在點之間插值數據,為此 D3 提供了各種生成曲線的插值函數。
曲線生成器用作 d3.line() 的訪問函數。要將上一節中聲明的線生成器轉換為曲線生成器,我們只需鏈接 curve() 訪問器函數并傳遞 D3 的一個插值器。在下面的代碼片段中,我們使用插值器 d3.curveCatmullRom ,它產生一個三次樣條曲線(通過每個數據點并使用三階多項式函數計算的平滑靈活的形狀)。結果如圖4.16所示。
const curveGenerator = d3.line()
.x(d => xScale(d.year))
.y(d => yScale(d.electoral_democracies))
.curve(d3.curveCatmullRom);
圖 4.16 使用Catmull-Roll樣條進行曲線插值的折線圖。
插值會修改數據表示,不同的插值函數會創建不同的可視化效果。數據可以通過各種方式可視化,從編程的角度來看,所有這些都是正確的。但是,我們有責任確保我們可視化的信息反映了實際現象。
由于數據可視化處理統計原理的可視化表示,因此它受到濫用統計數據的所有危險的影響。線條的插值特別容易被誤用,因為它將一條看起來笨拙的線條變成了一條平滑的“自然”線條。
在圖 4.17 中,您可以看到使用不同曲線插值跟蹤的相同折線圖,并了解它們如何影響視覺表示。選擇適當的插值函數在很大程度上取決于您正在使用的數據。在我們的例子中,d3.curveBasis低估了溫度的突然變化,而d3.curveBundle旨在拉直曲線并減少其變化,這對于我們的數據來說是不夠的。如果我們沒有在圖表上繪制數據點,我們就不知道曲線不能準確地表示它們。這就是為什么仔細選擇和測試曲線插值函數很重要的原因。
另一方面,函數 d3.curveMonotoneX 和 d3.curveCatmullRom 創建緊隨數據點的曲線,類似于原始折線圖。d3.curveStep 還可以在上下文適當時提供對數據的有趣解釋。圖 4.17 中所示的曲線插值列表并不詳盡,其中一些插值器還接受影響最終曲線形狀的參數。有關所有可用選項,請參閱 d3 形狀模塊 (https://github.com/d3/d3-shape)。
圖 4.17 不同的曲線插值及其如何修改數據的表示。
您現在知道如何使用 D3 繪制折線圖了!回顧一下,我們首先需要初始化一個線生成器函數并設置其 x() 和 y() 訪問器函數。這些將負責計算每個數據點的水平和垂直位置。然后,我們可以通過鏈接 curve() 訪問器函數并選擇插值來選擇將直線轉換為曲線。最后,我們將一個 SVG 路徑元素附加到我們的圖表中,并通過調用線條生成器并將數據作為參數傳遞來設置其 d 屬性。在第 7 章中,我們將通過工具提示使此折線圖具有交互性。如果您想立即學習該章節,請隨時直接轉到該章節!
圖 4.18 創建折線圖的步驟
在本節中,我們將在折線圖后面添加一個區域,以顯示每個日期的最低和最高溫度之間的范圍。在 D3 中繪制區域的過程與用于繪制線條的過程非常相似。像線條一樣,區域是使用 SVG 路徑元素創建的,D3 為我們提供了一個方便的區域生成器函數, d3.area() ,用于計算該路徑的 d 屬性。
在開始之前需要注意的一件事是,我們希望顯示折線圖后面的區域。由于元素在屏幕上的繪制順序與它們追加在 SVG 父項中的順序相同,因此應在創建折線圖的代碼之前添加用于繪制區域的代碼。
讓我們首先聲明一個區域生成器函數,并將其存儲在名為 areaGenerator 的常量中。正如您在以下代碼片段中觀察到的那樣,區域生成器至少需要三個訪問器函數。第一個 x() 負責計算數據點的水平位置,與線生成器完全相同。但是現在,我們不僅有一組數據點,而是兩組:一個沿著區域的下邊緣,另一個在其上邊緣,因此訪問器函數 y0() 和 y1() 。請注意,在我們的例子中,區域下邊緣和上邊緣的數據點共享相同的水平位置。
const areaGenerator = d3.area()
.x(d => xScale(d.date))
.y0(d => yScale(d.min_temp_F))
.y1(d => yScale(d.max_temp_F));
圖 4.19 可能有助于可視化區域的下限和上限,以及面積生成器如何計算與該區域相關的數據。
圖 4.19 面積生成器 d3.area() 與三個或更多訪問器函數結合使用。為了繪制最低和最高溫度之間的面積,我們使用 x()、y0() 和 y1()。第一個計算每個數據點的水平位置,第二個計算數據點在下邊界上的垂直位置,這里是最低溫度,第三個是數據點在上邊緣的垂直位置,這里是最高溫度。
正如我們對折線圖所做的那樣,通過將 curve() 訪問器函數鏈接到面積生成器,將區域的邊界插值為曲線。這里我們也使用相同的曲線插值器函數, d3.curveCatmullRom 。
const areaGenerator = d3.area()
.x(d => xScale(d.date))
.y0(d => yScale(d.min_temp_F))
.y1(d => yScale(d.max_temp_F))
.curve(d3.curveCatmullRom);
一旦面積生成器準備就緒,我們需要做的就是將 SVG 路徑元素附加到內部圖表中。為了設置其 d 屬性,我們調用區域生成器并將數據集作為參數傳遞。其余的純粹與美學有關。我們將填充屬性設置為之前聲明的茄子色常數,并將填充不透明度設置為 20%,以確保區域和折線圖之間的對比度足夠。請注意,茄子常數的聲明需要在我們使用它來設置區域的填充之前進行。
innerChart
.append("path")
.attr("d", areaGenerator(data))
.attr("fill", aubergine)
.attr("fill-opacity", 0.2);
圖4.20 平均溫度的折線圖,結合顯示最低溫度和最高溫度之間變化的區域。
如您所見,繪制區域的過程與繪制線條的過程非常相似。主要區別在于,一條線只有一組數據點,在這些數據點之間繪制了這條線,而區域是兩條邊之間的區域,每條邊都有一組數據點。這就是為什么線發生器只需要兩個訪問器函數 x() 和 y() ,而面積生成器至少需要三個,在我們的例子中是 x() 、y0() 和 y1()。
圖 4.21 創建區域的步驟
我們現在有一張 2021 年紐約市平均溫度的折線圖,以及一個顯示最低和最高溫度之間變化的區域。它看起來已經相當不錯了,但我們需要確保看到這張圖表的人很容易理解線條和面積的含義。標簽是一個很好的工具!
在 D3 中,標簽只是我們放置在可視化效果上的 SVG 文本元素。在這里,我們將創建三個標簽,一個用于我們將放置在折線圖末尾的平均溫度,一個用于放置在該區域下方的最低溫度,另一個用于放置在該區域上方的最高溫度。
讓我們從折線圖的標簽開始。我們首先將 SVG 文本元素附加到內部圖表,并使用 text() 方法將其內容設置為“平均溫度”。然后我們計算它的位置,由屬性 x 和 y 控制。
我們希望標簽位于折線圖的末尾或緊靠其最后一個數據點之后。我們可以通過將之前聲明刻度時計算的 lastDate 常量傳遞給 xScale() 來獲取該值。我們還添加了 10px 的額外填充。
對于垂直位置,我們還沒有一個常數來為我們提供最后一個溫度值。盡管如此,我們仍然可以使用 data[data.length - 1] 找到數據集中的最后一行,并使用點符號來訪問平均溫度。我們將這個值傳遞給 yScale() 并獲取標簽的垂直位置。
最后,我們重用顏色常數茄子作為文本的顏色,由其填充屬性控制。
innerChart
.append("text")
.text("Average temperature")
.attr("x", xScale(lastDate) + 10)
.attr("y", yScale(data[data.length - 1].avg_temp_F))
.attr("fill", aubergine);
如果保存項目并在瀏覽器中查看,則會發現標簽的底部與折線圖上最后一個數據點的中心垂直對齊。默認情況下,SVG 文本的基線位于文本底部,如圖 4.22 所示。我們可以使用主導基線屬性來更改此設置。在下面的代碼片段中,我們給此屬性一個值 中間 ,以將基線移動到文本的垂直中心。
圖 4.22 SVG 文本的 y 屬性設置其基線的垂直位置,默認情況下位于文本底部。我們可以使用主導基線屬性來更改它。如果我們給此屬性值“middle”,則文本的基線將移動到其垂直中間,而值“hanging”會將基線移動到文本的頂部。
innerChart
.append("text")
.text("Average temperature")
.attr("x", xScale(lastDate) + 10)
.attr("y", yScale(data[data.length - 1].avg_temp_F))
.attr("dominant-baseline", "middle")
.attr("fill", aubergine);
然后,我們將為該區域的下邊界添加一個標簽,該標簽表示最低溫度的演變。策略非常相似。我們首先附加一個 SVG 文本元素,并為其提供“最低溫度”的內容。
對于它的位置,我們選擇了最后一個向下的突起,它對應于倒數第三個數據點。我們將這些數據點的值傳遞給我們的秤以找到它的位置,并將標簽向下移動 20px,向右移動 13px。這些數字是通過移動標簽找到的,直到我們找到一個看起來合適的位置。瀏覽器的檢查器工具是測試此類微小調整的好地方。請注意,我們已將標簽的主要基線設置為 掛起 。如圖 4.22 所示,這意味著 y 屬性控制文本頂部的位置。
最后,在代碼段中,您將看到我們在標簽中添加了一條線,在該區域的向下突起和標簽之間跟蹤,以闡明標簽代表的內容。您可以在圖 4.23 中看到它的外觀。同樣,我們使用刻度來計算直線的 x1、y1、x2 和 y2 屬性,這些屬性控制其起點和終點的位置。
innerChart
.append("text")
.text("Minimum temperature")
.attr("x", xScale(data[data.length - 3].date) + 13)
.attr("y", yScale(data[data.length - 3].min_temp_F) + 20)
.attr("alignment-baseline", "hanging")
.attr("fill", aubergine);
innerChart
.append("line")
.attr("x1", xScale(data[data.length - 3].date))
.attr("y1", yScale(data[data.length - 3].min_temp_F) + 3)
.attr("x2", xScale(data[data.length - 3].date) + 10)
.attr("y2", yScale(data[data.length - 3].min_temp_F) + 20)
.attr("stroke", aubergine)
.attr("stroke-width", 2);
我們使用非常相似的過程為該區域的上邊界附加一個標簽,該標簽表示最高溫度的演變。我們選擇將此標簽放置在與倒數第四個數據點相對應的向上突起附近。同樣,我們在標簽和突起之間畫了一條線。完成后,折線圖就完成了!
innerChart
.append("text")
.text("Maximum temperature")
.attr("x", xScale(data[data.length - 4].date) + 13)
.attr("y", yScale(data[data.length - 4].max_temp_F) - 20)
.attr("fill", aubergine);
innerChart
.append("line")
.attr("x1", xScale(data[data.length - 4].date))
.attr("y1", yScale(data[data.length - 4].max_temp_F) - 3)
.attr("x2", xScale(data[data.length - 4].date) + 10)
.attr("y2", yScale(data[data.length - 4].max_temp_F) - 20)
.attr("stroke", aubergine)
.attr("stroke-width", 2);
圖 4.23 2021年紐約市溫度演變的完整折線圖。
在最后一節中,我們將討論如何使用 D3 繪制弧線。弧形是數據可視化中的常見形狀。它們用于餅圖、旭日圖和南丁格爾圖,以可視化金額與總數的關系,我們經常在自定義徑向可視化中使用它們。
像直線和面積一樣,弧是用 SVG 路徑繪制的,而且,正如你現在可能已經猜到的那樣,D3 提供了一個方便的弧發生器函數,可以為我們計算弧路徑的 d 屬性。
在詳細討論電弧發生器之前,讓我們準備我們的項目。在這里,我們將繪制構成徑向圖的弧線,您可以在圖 4.1 中的“有降水的日子”或托管項目 (https://d3js-in-action-third-edition.github.io/new-york-city-weather-2021/) 中看到。藍色弧線表示 2021 年紐約市有降水的天數百分比 (35%),而灰色弧線表示其余天數。
首先,打開文件弧.js .這就是我們將在本章其余部分工作的地方。像往常一樣,我們需要加載一個數據集,在本例中為 daily_precipitations.csv ,它包含在數據文件夾中。如果您查看 CSV 文件,您會發現它只包含兩列:日期列列出了 2021 年的每一天,而total_precip_in列則提供了每天的總降水量(以英寸為單位)。
在下面的代碼片段中,我們使用 d3.csv() 獲取數據集,使用 d3.autoType 正確格式化日期和數字,并使用 Promise 將其鏈接,我們將數據記錄到控制臺中。我們不會在這里討論如何使用 d3.csv() 的細節。有關更多說明,請參閱第 3 章,有關 d4.autoType 的討論,請參閱本章的第 1.3 節。
d3.csv("./data/daily_precipitations.csv", d3.autoType).then(data => {
console.log("precipitations data", data);
});
如果您在控制臺中查看數據,您會發現日期和數字的格式都正確。偉大!我們可以獲取格式化的數據集并將其傳遞給函數 drawArc() ,它已經存在于 arcs 中.js .
d3.csv("./data/daily_precipitations.csv", d3.autoType).then(data => {
console.log("precipitations data", data);
drawArc(data);
});
在 drawArc() 中,我們現在可以附加一個新的 SVG 容器。正如您在以下代碼片段中看到的,我們為 SVG 容器提供了 300px 的寬度和高度,并將其附加到 div 中,其中包含索引中已經存在的 arc id.html 。我們使用第 1 章中解釋的策略使 SVG 響應:將 viewBox 屬性的最后兩個值設置為其寬度和高度,并完全省略寬度和高度屬性。這樣,SVG 容器將適應其父容器的大小,同時保留其縱橫比。請注意,我們將 SVG 容器選擇保存在名為 svg 的常量中。
const pieChartWidth = 300;
const pieChartHeight = 300;
const svg = d3.select("#arc")
.append("svg")
.attr("viewBox", [0, 0, pieChartWidth, pieChartHeight]);
如第 4.1 節所述,我們將圖表包裝在 SVG 組中,并將該組轉換為所需位置。不過,這次的策略有點不同。我們不需要為軸或標簽保留空間,因此我們可以省略邊距約定。但是,與迄今為止構建的所有可視化相反,弧位于極坐標系中,而不是笛卡爾坐標系中,后者的行為略有不同。
如圖 4.24 所示,SVG 容器的坐標系是笛卡爾坐標系。它使用兩個垂直維度 x 和 y 來描述 2D 空間中的位置。我們在第 1 章中討論過,SVG 元素的坐標系有點特殊,因為它的原點位于 SVG 容器的左上角,使 y 維在從上到下的方向上為正。
2D 極坐標系還使用兩個維度:半徑和角度。半徑是原點與空間中點之間的距離,而角度是從 12 點鐘方向沿順時針方向計算的。這種描述空間位置的方法在處理圓弧時特別有用。
圖 4.24 笛卡爾坐標的尺寸彼此垂直,而極坐標系統使用半徑和角度尺寸來描述空間中的位置。
由于元素位于極坐標系中的原點周圍,因此我們可以說我們將要構建的弧可視化的原點位于 SVG 容器的中心,如圖 4.25 所示。
圖 4.25 通過將弧包裝成 SVG 組并將該組轉換為 SVG 容器的中心,我們簡化了一組弧的創建。當我們向組追加弧時,它們的位置將自動相對于圖表的中心,這對應于其極坐標系的原點。
在下一個代碼片段中,我們選擇SVG容器并在其中附加一個組,我們將該組轉換為SVG容器的中心,并將其保存在常量innerChart中。
const innerChart = svg
.append("g")
.attr("transform", `translate(${pieChartWidth/2},
? ${pieChartHeight/2})`);
在創建弧線之前,我們需要做最后一件事:計算圖表上有降水的日子所采用的角度。使用 D3 創建餅圖或圓環圖時,我們通常使用餅圖布局生成器處理此類計算,我們將在下一章中介紹。但是由于我們在這里只畫兩個弧線,所以數學很容易。
首先,我們可以使用數據集的 length 屬性知道 2021 年的總天數,即 365。然后,我們通過過濾數據集來查找有降水的天數,以僅保留降水量大于零的天數,即 126 天。最后,我們將降水的天數除以總天數(得到 35%),將降水天數轉換為百分比。
const numberOfDays = data.length;
const numberOfDaysWithPrecipitations = data.filter(d =>
? d.total_precip_in > 0).length;
const percentageDaysWithPrecipitations =
? Math.round(numberOfDaysWithPrecipitations / numberOfDays * 100);
然后,我們可以通過將這個數字乘以 360 度(一個完整圓的度數)來計算對應于降水天數的角度,得到 126 度。我們從度開始,因為它往往更直觀,但我們還需要將此值轉換為弧度。為此,我們將降水天數百分比(以度為單位)所覆蓋的角度乘以數字 pi (3.1416),然后將其除以 180,得到大約 2.2 弧度的角度,我們將其保存在常數angleDaysWithPrecipitations_rad中。
我們執行此轉換是因為我們將在一會兒使用的電弧發生器期望角度以弧度而不是度為單位。作為處理角度的經驗法則,JavaScript 通常希望它們以弧度為單位,而 CSS 使用度數。
const angleDaysWithPrecipitations_deg = percentageDaysWithPrecipitations *
? 360 / 100;
const angleDaysWithPrecipitations_rad = angleDaysWithPrecipitations_deg *
? Math.PI / 180;
我們終于到了有趣的部分,生成弧線!首先,我們需要聲明一個電弧發生器,就像我們對線和區域所做的那樣。弧發生器 d3.arc() 是模塊 d3-shape (https://github.com/d3/d3-shape) 的一部分,在我們的例子中,需要兩個主要的訪問器函數:弧的內半徑和外半徑,分別由 innerRadius() 和 outerRadius() 處理,并給定值為 80 和 120px。請注意,如果內半徑為零,我們會得到一個類似于餅圖中的弧線,并從原點開始。
const arcGenerator = d3.arc()
.innerRadius(80)
.outerRadius(120);
我們可以通過使用訪問器函數在弧形之間添加填充來個性化我們的弧線 padAngle() ,它接受以弧度為單位的角度。這里我們使用 0.02 弧度,對應于略多于 1 度。我們也可以用 角半徑() ,它接受一個以像素為單位的值。此訪問器函數與 CSS 邊框半徑屬性具有類似的效果。
const arcGenerator = d3.arc()
.innerRadius(80)
.outerRadius(120)
.padAngle(0.02)
.cornerRadius(6);
圖 4.26 電弧發生器使用多個訪問器函數來計算電弧的 d 屬性。在這里,我們在生成器聲明期間設置其內半徑、外半徑、填充角度和角半徑。我們將在將路徑元素附加到圖表時傳遞每個弧的開始和結束角度。
此時,您可能想知道為什么我們不使用處理弧線覆蓋的角度的訪問器函數。在我們的例子中,由于我們已經手動計算了角度,因此當我們附加路徑時,將這些值傳遞給電弧發生器會更簡單。但我們將在下一章中看到,情況并非總是如此。
因此,讓我們附加第一個弧線,即顯示降水天數的弧線。在下面的代碼片段中,我們首先將一個 path 元素附加到內部圖表選擇中。然后,我們通過調用最后一個代碼段中聲明的 arc 生成器來設置其 d 屬性。
觀察我們如何將開始和結束角度作為對象傳遞給生成器。起始角度的值為零,對應于 12 點鐘位置,而結束角度的值是之前計算的降水天數所覆蓋的角度。最后,我們將弧線的填充設置為顏色 #6EB7C2,青藍色。
innerChart
.append("path")
.attr("d", () => {
return arcGenerator({
startAngle: 0,
endAngle: angleDaysWithPrecipitations_rad
});
})
.attr("fill", "#6EB7C2");
我們以類似的方式附加第二個弧線。這一次,弧從前一個弧線結束的地方開始,到圓圈完成時結束,對應于弧度中的角度 2*Pi。我們給弧線一個 #DCE2E2,一種更接近灰色的顏色,以表明這些日子沒有降水。
innerChart
.append("path")
.attr("d", () => {
return arcGenerator({
startAngle: angleDaysWithPrecipitations_rad,
endAngle: 2 * Math.PI
});
})
.attr("fill", "#DCE2E2");
保存項目后,弧應如圖 4.27 所示。我們鼓勵您使用傳遞給生成器的訪問器函數的值(如半徑或角半徑),以了解它們如何修改弧的外觀。
圖 4.27 弧線顯示有降水天數和無降水天數之間的比率。
如您所見,繪制圓弧的過程類似于繪制線條和區域的過程。主要區別在于弧在空間中的位置是用極坐標而不是笛卡爾來處理的,這反映在弧發生器的訪問器函數中。
圖 4.28 繪制弧線的步驟。
餅圖和圓環圖最近在數據可視化社區中得到了很多負面報道,主要是因為我們意識到人眼不太擅長估計弧線所代表的比率。但是,這些圖表并不總是一個糟糕的選擇,尤其是當它們包含少量類別時。但我們絕對可以通過標簽幫助他們提高可讀性,這就是我們在這里要做的!
在表示有降水天數的弧線上,我們將添加標簽“35%”,即之前計算的有降水的天數百分比。放置此標簽的好地方是弧的質心,也稱為其質心。此值可由電弧發生器提供。
在下面的代碼片段中,我們在前面初始化的弧發生器函數上調用方法。這一次,我們將它與 startAngle() 和 endAngle() 訪問器函數鏈接起來,分別將它們傳遞代表有降水的日子的弧的開始角和結束角的值。最后,我們鏈接方法centroid(),它將計算弧的中點。
const centroid = arcGenerator
.startAngle(0)
.endAngle(angleDaysWithPrecipitations_rad)
.centroid();
將質心記錄到控制臺中。您將看到它由兩個值的數組組成:質心的水平和垂直位置,在我們的例子中是 [89, -45] ,從內部圖表的原點計算得出。
在下一個代碼段中,我們通過向內部圖表追加文本元素來創建標簽。為了使標簽包含“%”符號,我們使用方法 d3.format(“.0%”) ,后跟括號中的值。此方法便于以特定方式(如貨幣、百分比和指數)格式化數字,或為這些數字添加特定后綴,如“M”表示百萬或“μ”表示微型。您可以在模塊 d3 格式 (https://github.com/d3/d3-format) 中找到所有可用格式的詳細列表。
然后,我們使用質心數組中返回的第一個和第二個值設置 x 和 y 屬性。請注意我們如何設置文本錨點和主要基線屬性,以確保標簽在水平和垂直方向上以 x 和 y 屬性為中心。
最后,我們給標簽一個白色和500的字體粗細,以提高其易讀性。保存后,帶有標簽的弧應如圖 4.29 所示。
innerChart
.append("text")
.text(d => d3.format(".0%")(percentageDaysWithPrecipitations/100))
.attr("x", centroid[0])
.attr("y", centroid[1])
.attr("text-anchor", "middle")
.attr("dominant-baseline", "middle")
.attr("fill", "white")
.style("font-weight", 500);
圖 4.29 帶有標簽的已完成弧。
您現在知道如何使用 D3 繪制線條、面積和弧線了!在下一章中,我們將使用布局生成器將這些形狀提升到另一個層次。
css3之前,并沒有直接的設置元素圓角的屬性,那如何實現設計圖中的圓角設定呢?
如:表單demo中的文本框圓角。
一般通過在PS 軟件中截取上左、上右、下左、下右四個邊角的圓角圖片,通過background-position背景圖屬性設定位置,從而實現圓角的設定。
或是將四個圓角圖片當做四個img標簽,通過position定位屬性找尋設定的位置,如:
通過圖片設定圓角
在IE6、7、8還比較盛行的時候,如果要兼容到IE瀏覽器,需要使用CSS hack兼容IE瀏覽器版本,高級瀏覽器可以直接使用border-radius屬性設定圓角,對低版本瀏覽器使用圖片定位的方式實現設計圖呈現。但隨著近幾年瀏覽器版本的不斷更新迭代,我們需要做的就是,如果需要兼容低版本瀏覽器,只需保證呈現正常即可,無需過分的追求展示效果與設計圖的還原度。但實現的思想在web布局中是很重要的。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。