整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          D3.js實(shí)戰(zhàn)教程:4 繪制直線、曲線和圓弧


          章涵蓋

          • 向圖表添加軸并應(yīng)用邊距約定
          • 使用線條生成器功能繪制折線圖
          • 插值數(shù)據(jù)點(diǎn)以將直線轉(zhuǎn)換為曲線
          • 使用面積生成器繪制面積
          • 使用電弧發(fā)生器創(chuàng)建電弧

          您已經(jīng)熟悉我們使用和組合用于制作數(shù)據(jù)可視化的常見 SVG 形狀:線條、矩形和圓形。您甚至已經(jīng)使用矩形從頭開始創(chuàng)建了條形圖。但是我們可以用原始形狀畫出的東西太多了。為了創(chuàng)建更復(fù)雜的可視化,我們通常轉(zhuǎn)向 SVG 路徑。正如我們在第 1 章中所討論的,SVG 路徑是所有 SVG 元素中最靈活的,幾乎可以采用任何形式。我們在 D3 項(xiàng)目中廣泛使用它們,最簡單的例子是在折線圖中繪制線條和曲線或在圓環(huán)圖中繪制弧線。

          SVG 路徑的形狀由其 d 屬性確定。此屬性由指示路徑的起點(diǎn)和終點(diǎn)、用于更改方向的曲線類型以及路徑是打開還是閉合的命令組成。路徑的 d 屬性可能很快變得又長又復(fù)雜。大多數(shù)時(shí)候,我們不想自己創(chuàng)作它。這就是 D3 的形狀生成器功能的用武之地!

          在本章中,我們將構(gòu)建如圖 4.1 所示的項(xiàng)目:溫度演變的折線圖和一組弧線,可視化 2021 年紐約市降水天數(shù)的百分比。您可以在 https://d3js-in-action-third-edition.github.io/new-york-city-weather-2021/ 在線找到此項(xiàng)目。基礎(chǔ)數(shù)據(jù)來自地下天氣(www.wunderground.com/)。

          圖 4.1 我們將在本章中構(gòu)建的項(xiàng)目:2021 年紐約市溫度演變的折線圖和一組顯示降水天數(shù)百分比的弧線圖。


          我們將使用 D3 的形狀生成器函數(shù)創(chuàng)建這兩個(gè)可視化效果。但在開始之前,我們將討論 D3 的邊距約定以及如何向圖表添加軸。

          4.1 創(chuàng)建軸

          開發(fā)數(shù)據(jù)可視化通常需要提前規(guī)劃如何使用 SVG 容器中的可用空間。首先開始玩很酷的東西是非常誘人的,也就是可視化的核心,但相信我們。一點(diǎn)點(diǎn)的準(zhǔn)備可以為您節(jié)省大量的執(zhí)行時(shí)間。所有編程任務(wù)都是如此,在一般生活中也是如此!在此規(guī)劃階段,我們不僅要考慮圖表本身,還要考慮使圖表可讀的互補(bǔ)元素,例如軸、標(biāo)簽和圖例。

          在本節(jié)中,我們將介紹邊距約定,這是一種便于為這些不同元素分配空間的方法。然后,我們將討論如何向可視化添加軸以及組成 D3 軸的多個(gè) SVG 元素。我們將這些概念應(yīng)用于圖 4.1 所示的折線圖。

          在我們開始之前,請轉(zhuǎn)到第 4 章的代碼文件。您可以從本書的 Github 存儲(chǔ)庫下載它們(如果您還沒有 (https://github.com/d3js-in-action-third-edition/code-files)。在名為 chapter_04 的文件夾中,代碼文件按節(jié)進(jìn)行組織。要開始本章的練習(xí),請?jiān)诖a編輯器中打開 4.1-Margin_convention_and_axes/start 文件夾并啟動(dòng)本地 Web 服務(wù)器。如果您需要有關(guān)設(shè)置本地開發(fā)環(huán)境的幫助,請參閱附錄 A。

          您可以在位于本章代碼文件根目錄下的自述文件中找到有關(guān)項(xiàng)目文件夾結(jié)構(gòu)的更多詳細(xì)信息。

          警告

          使用本章的代碼文件時(shí),在代碼編輯器中僅打開一個(gè)開始文件夾或一個(gè)結(jié)束文件夾。如果一次打開章節(jié)的所有文件并使用 Live Server 擴(kuò)展為項(xiàng)目提供服務(wù),則數(shù)據(jù)文件的路徑將無法按預(yù)期工作。

          我們將開始在文件折線圖中工作.js并使用方法 d3.csv() 加載每周溫度數(shù)據(jù)集。

          d3.csv("../data/weekly_temperature.csv");

          在第 3 章中,我們解釋了 D3 在加載表格數(shù)據(jù)集時(shí)執(zhí)行的類型轉(zhuǎn)換會(huì)影響值的類型。例如,原始數(shù)據(jù)集中的數(shù)字變成字符串,我們需要將它們變回?cái)?shù)字以方便操作它們。我們已經(jīng)看到 d3.csv() 的回調(diào)函數(shù) ,我們可以逐行訪問數(shù)據(jù),是執(zhí)行此類轉(zhuǎn)換的好地方。在這里,我們將介紹一個(gè)小技巧。我們可以調(diào)用方法 d3.autoType ,而不是手動(dòng)轉(zhuǎn)換數(shù)字。此函數(shù)檢測常見的數(shù)據(jù)類型,如日期和數(shù)字,并將它們轉(zhuǎn)換為相應(yīng)的 JavaScript 類型。

          d3.csv("../data/weekly_temperature.csv", d3.autoType);

          請注意,數(shù)據(jù)類型可能不明確,并且 d3.autoType 有時(shí)會(huì)選擇錯(cuò)誤的類型。因此,在數(shù)據(jù)數(shù)組完全加載后仔細(xì)檢查數(shù)據(jù)數(shù)組非常重要。在下面的代碼片段中,我們使用 JavaScript Promise 訪問加載的數(shù)據(jù)集,并將其登錄到控制臺(tái),以確認(rèn)日期被格式化為 JavaScript 日期,溫度被格式化為 數(shù)字。您可以在圖 4.2 中看到結(jié)果。

          d3.csv("../data/weekly_temperature.csv", d3.autoType).then(data => {
            console.log("temperature data", data);
          });

          圖 4.2 由于 d3.autoType 方法,日期被格式化為 JavaScript 日期,溫度被格式化為數(shù)字。


          我們使用 JavaScript Promise 來訪問數(shù)據(jù)集,因?yàn)榧虞d數(shù)據(jù)是一個(gè)異步過程(如果您需要復(fù)習(xí)有關(guān)使用 D3 加載和訪問數(shù)據(jù)的信息,請參閱第 3 章)。但是現(xiàn)在我們知道我們的數(shù)據(jù)集已完全加載并正確格式化,我們可以開始構(gòu)建圖表了。

          文件折線圖.js已經(jīng)包含一個(gè)名為 drawLineChart() ,我們將在其中創(chuàng)建折線圖。在 JavaScript Promise 的回調(diào)函數(shù)中,調(diào)用函數(shù) drawLineChart() 并將數(shù)據(jù)集作為參數(shù)傳遞。

          d3.tsv("../data/weekly_temperature.csv", d3.autoType).then(data => {
            console.log("temperature data", data);
            drawLineChart(data);
          });

          我們現(xiàn)在準(zhǔn)備討論保證金慣例并將其應(yīng)用于我們的圖表!

          4.1.1 邊距約定

          D3 邊距約定旨在以系統(tǒng)和可重用的方式為軸、標(biāo)簽和圖例保留圖表周圍的空間。該約定使用四個(gè)邊距:圖表的上方、右側(cè)、下方和左側(cè),如圖 4.3 所示。通過說明這些邊距,我們可以知道圖表核心剩余區(qū)域的位置和大小,我們稱之為內(nèi)部圖表。

          圖 4.3 D3 邊距約定設(shè)置圖表頂部、右側(cè)、底部和左側(cè)的邊距值。


          邊距值在邊距對象中聲明,該對象由上邊距、右邊距、下邊距和左邊距組成。讓我們?yōu)檎劬€圖創(chuàng)建邊距對象。在函數(shù) drawLineChart() 中,聲明一個(gè)名為 margin 的常量。如以下代碼片段所示,為上邊距、右邊距、下邊距和左邊距分別指定 40、170、25 和 40px 的值。

          const drawLineChart = (partialData) => {
            const margin = {top: 40, right: 170, bottom: 25, left: 40};
          };

          事先確切知道軸和標(biāo)簽需要多少空間通常是不可能的。我們從一個(gè)有根據(jù)的猜測開始,如果需要,稍后會(huì)進(jìn)行調(diào)整。例如,查看圖 4.1 中的折線圖或托管項(xiàng)目 (https://d3js-in-action-third-edition.github.io/new-york-city-weather-2021/)。您將看到可視化效果右側(cè)顯示的標(biāo)簽相對較長,因此右邊距為 170px。另一方面,軸的標(biāo)簽不占用太多空間;因此,剩余的邊距可以小得多。

          聲明邊距對象后,我們就可以開始考慮 SVG 容器的大小了。知道了 SVG 容器的大小和邊距,我們最終可以計(jì)算出兩個(gè)新常量,分別名為 innerWidth 和 innerHeight ,它們代表內(nèi)部圖表的寬度和高度。這些尺寸如圖4.4所示。

          圖 4.4 知道 SVG 容器的尺寸和邊距,我們可以計(jì)算內(nèi)部圖表的寬度和高度。


          內(nèi)部圖表的寬度對應(yīng)于 SVG 容器的寬度減去左側(cè)和右側(cè)的邊距。如果 SVG 容器的寬度為 1000 像素,每邊的邊距分別為 170 和 40 像素,則內(nèi)部圖表仍保留 790 像素。同樣,如果 SVG 容器的高度為 500px,我們通過從總高度中減去頂部和底部邊距來計(jì)算內(nèi)部圖表的高度,因此為 435px。通過使常量 innerWidth 和 innerHeight 與邊距成正比,我們確保如果我們以后需要更改邊距,它們會(huì)自動(dòng)調(diào)整。

          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;

          現(xiàn)在讓我們附加折線圖的 SVG 容器。仍然在函數(shù) drawLineChart() 中工作,將一個(gè) SVG 元素附加到 div 中,其 id 為 折線圖,該元素已經(jīng)存在于文件索引中.html并使用寬度和高度常量設(shè)置其 viewBox 屬性。您還可以臨時(shí)將邊框應(yīng)用于 SVG 元素,以幫助您查看正在工作的區(qū)域。如果您需要復(fù)習(xí)如何將元素附加到 DOM 或設(shè)置其屬性和樣式,請參閱第 2 章。

          const svg = d3.select("#line-chart")
            .append("svg")
            .attr("viewBox", `0, 0, ${width}, ${height}`);

          我們之前已經(jīng)聲明了邊距,這些邊距將決定為內(nèi)部圖表保留的區(qū)域。知道 SVG 容器的坐標(biāo)系從其左上角開始,內(nèi)部圖表的每個(gè)元素都必須向保留區(qū)域移動(dòng)。我們可以將內(nèi)部圖表包裝在 SVG 組中并僅對該組應(yīng)用平移,而不是將此置換應(yīng)用于每個(gè)元素。如圖 4.5 所示,此策略為內(nèi)部圖表創(chuàng)建了一個(gè)新的坐標(biāo)系。

          圖 4.5 應(yīng)用于將包含內(nèi)部圖表的 SVG 組的平移,為內(nèi)部圖表中包含的元素創(chuàng)建新的坐標(biāo)系。


          為了將此策略付諸實(shí)施,我們將一個(gè)組附加到 SVG 容器。然后,我們根據(jù)左邊距和上邊距對組應(yīng)用翻譯。最后,我們將 SVG 組保存到名為 innerChart 的常量中,稍后我們將使用該常量構(gòu)建折線圖。

          const innerChart = svg
            .append("g")
            .attr("transform", `translate(${margin.left}, ${margin.top})`);

          保證金約定和此處介紹的策略的主要優(yōu)點(diǎn)是,一旦實(shí)施,我們就不再需要考慮它了。我們可以繼續(xù)創(chuàng)建軸和圖表,同時(shí)知道為標(biāo)簽、圖例和其他補(bǔ)充信息保留了一個(gè)區(qū)域。

          4.1.2 生成軸

          建立邊距約定后,我們準(zhǔn)備向圖表添加軸。軸是數(shù)據(jù)可視化的重要組成部分。它們可作為查看者理解所代表的數(shù)字和類別的參考。

          如果您查看圖 4.1 中的折線圖或托管項(xiàng)目 (https://d3js-in-action-third-edition.github.io/new-york-city-weather-2021/),您將看到兩個(gè)軸。水平軸,也稱為 x 軸,顯示每個(gè)月的位置。垂直軸或 y 軸用作以華氏度為單位的溫度的參考。

          在 D3 中,我們使用 axis() 組件生成器創(chuàng)建軸。此生成器將比例作為輸入,并返回組成軸的 SVG 元素作為輸出。如果您還記得我們在第 3 章中關(guān)于比例的討論,您就會(huì)知道它們將數(shù)據(jù)值映射到屏幕上。例如,對于我們的折線圖,刻度將為我們計(jì)算數(shù)據(jù)集中每個(gè)日期的水平位置或其相關(guān)溫度的垂直位置。

          聲明秤

          創(chuàng)建軸的第一步實(shí)際上是聲明其比例。首先,我們需要一個(gè)水平定位日期的刻度。這正是 D3 的時(shí)間尺度 d3.scaleTime() 的作用(有關(guān)選擇 D3 尺度的幫助,請參閱附錄 B)。時(shí)間尺度是第3章討論的第一類尺度的一部分。它接受連續(xù)輸入并返回連續(xù)輸出。時(shí)間尺度的行為與第3章中使用的線性尺度非常相似,唯一的區(qū)別是它操縱與時(shí)間相關(guān)的數(shù)據(jù)并計(jì)算它們在空間中的位置。

          讓我們聲明我們的時(shí)間刻度并將其命名為 xScale,因?yàn)樗鼘⒇?fù)責(zé)沿 x 軸定位元素。我們規(guī)模的范圍從數(shù)據(jù)集中的第一個(gè)日期延伸到最后一個(gè)日期。在下面的代碼片段中,我們使用 d3.min() 和 d3.max() 來查找這些值。

          刻度所涵蓋的范圍隨著內(nèi)部圖表中可用的水平空間而擴(kuò)展(見圖4.5)。在內(nèi)部圖表的坐標(biāo)系中,這意味著范圍從零擴(kuò)展到之前計(jì)算的 innerWidth。如果您需要復(fù)習(xí)聲明 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軸分布的溫度也需要第一個(gè)系列的刻度,具有連續(xù)的輸入和輸出。線性刻度在這里將是完美的,因?yàn)槲覀兿M麥囟群驼劬€圖上的垂直位置是線性比例的。

          在下面的代碼片段中,我們聲明了我們的溫標(biāo)并將其命名為 yScale,因?yàn)樗鼘⒇?fù)責(zé)沿 y 軸定位元素。在這里,我們希望我們的 y 軸從零開始,因此我們將零作為域的第一個(gè)值傳遞。盡管數(shù)據(jù)集中的最低溫度約為 26°F,但從零開始 y 軸通常是一個(gè)好主意,在我們的例子中,這將使我們能夠正確看到溫度的演變。但就像生活中的大多數(shù)事情一樣,這不是一個(gè)硬性規(guī)則,這個(gè)圖表沒有正確或錯(cuò)誤的答案,特別是因?yàn)槿A氏度的零不是絕對的零。

          我們將數(shù)據(jù)集中的最高溫度作為域的第二個(gè)值傳遞。我們通過使用函數(shù) d3.max() 查詢數(shù)據(jù)集中的列max_temp_F來找到此值。

          我們的刻度范圍隨著內(nèi)部圖表的高度而擴(kuò)展。由于垂直值是在 SVG 坐標(biāo)系中從上到下計(jì)算的,因此范圍從 innerHeight(內(nèi)圖左下角的位置)開始,到零(對應(yīng)于其左上角的位置)結(jié)束。

          const maxTemp = d3.max(data, d => d.max_temp_F);
          const yScale = d3.scaleLinear()
            .domain([0, maxTemp])
            .range([innerHeight, 0]);

          追加軸

          初始化刻度后,我們就可以附加軸了。D3 有四個(gè)軸生成器:axisTop()、axisRight()、axisBottom() 和 axisLeft() ,它們分別創(chuàng)建頂部、右側(cè)、底部和左側(cè)軸的組件。它們都是 d3 軸模塊 (https://github.com/d3/d3-axis) 的一部分。

          我們提到軸生成器函數(shù)將刻度作為輸入。例如,要?jiǎng)?chuàng)建折線圖的底部軸,我們調(diào)用生成器 axisBottom() 并將 xScale 作為參數(shù)傳遞,因?yàn)榇丝潭蓉?fù)責(zé)沿底部軸分布數(shù)據(jù)。我們將生成器保存在名為 底軸 .

          const bottomAxis = d3.axisBottom(xScale);

          軸生成器是構(gòu)造組成軸的元素的函數(shù)。為了使這些元素出現(xiàn)在屏幕上,我們需要使用 call() 方法從 D3 選擇中調(diào)用軸生成器。在下面的代碼片段中,請注意我們?nèi)绾卧谡{(diào)用軸生成器之前使用 innerChart 選擇并將組元素附加到其中。該組的類名為 axis-x,這將幫助我們稍后定位和設(shè)置軸的樣式。

          const bottomAxis = d3.axisBottom(xScale);
          innerChart
            .append("g")
              .attr("class", "axis-x")
              .call(bottomAxis);

          圖 4.6 默認(rèn)情況下,D3 軸在所選內(nèi)容的原點(diǎn)生成,此處為內(nèi)部圖表的左上角。我們需要應(yīng)用翻譯將它們移動(dòng)到所需的位置。


          在瀏覽器中查看生成的軸。默認(rèn)情況下,D3 軸顯示在所選內(nèi)容的原點(diǎn),在本例中為內(nèi)部圖表區(qū)域的左上角,如圖 4.6 所示。我們可以通過對包含軸的 SVG 組應(yīng)用平移來將軸移動(dòng)到圖表底部。請記住,應(yīng)用于組的轉(zhuǎn)換由其所有子級繼承。在下面的代碼片段中,我們將包含軸元素的組向下平移一個(gè)對應(yīng)于內(nèi)部圖表高度的值。

          const bottomAxis = d3.axisBottom(xScale);
          innerChart
            .append("g")
              .attr("class", "axis-x")
              .attr("transform", `translate(0, ${innerHeight})`)
              .call(bottomAxis);

          我們要更改的另一件事是軸標(biāo)簽的格式。默認(rèn)情況下,D3 調(diào)整軸上的時(shí)間表示形式,根據(jù)域顯示小時(shí)、天、月或年標(biāo)簽。但是這種默認(rèn)格式并不總是提供我們正在尋找的標(biāo)簽。幸運(yùn)的是,D3 提供了多種方法來更改標(biāo)簽的格式。

          首先,我們注意到 x 軸有 3 月至 <> 月的標(biāo)簽,這很好,但沒有 <> 月的標(biāo)簽。根據(jù)您居住的時(shí)區(qū),第一個(gè)日期可能不完全是 <> 月 <> 日的午夜,這使 D<> 無法將其識別為我們第一個(gè)月的開始。由于我們的數(shù)據(jù)集不是動(dòng)態(tài)的,因此對 firstDate 變量進(jìn)行硬編碼是一個(gè)合理的解決方案。為此,我們將使用 JavaScript Date() 構(gòu)造函數(shù)。

          在下面的代碼片段中,firstDate 成為一個(gè)新的 Date() 對象。在括號之間,我們首先聲明年份 ( 2021 年 )、月份 ( 00,因?yàn)樵路菟饕秊榱闼饕⑷?( 01 ),并可選擇在它后面跟小時(shí)、分鐘和秒 ( 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]);

          如果保存項(xiàng)目,你將看到我們現(xiàn)在在 1 月 2021日的位置有一個(gè)標(biāo)簽。但是標(biāo)簽只給了我們 01 年,這并沒有錯(cuò),因?yàn)?Fri Jan 2021 00 00:00:2021 對應(yīng)于 <> 年的開始,但我們更愿意有一個(gè)月份標(biāo)簽。

          圖 4.7 默認(rèn)情況下,D3 調(diào)整軸標(biāo)簽上的時(shí)間表示。在我們的例子中,它表示 1 月 2021日作為 <> 年的開始。這沒有錯(cuò),但對于可讀性來說并不理想。


          我們可以使用方法 axis.tickFormat() 更改軸標(biāo)簽的格式,該方法在 d3 軸模塊 (https://github.com/d3/d3-axis) 中可用。刻度是您在軸上看到的短垂直線。它們通常(但不一定)附有勾號標(biāo)簽。

          假設(shè)我們希望刻度標(biāo)簽是縮寫的月份名稱。在 D3 中,我們可以使用方法 d3.timeFormat() 格式化與時(shí)間相關(guān)的值,來自模塊 d3-time-format (https://github.com/d3/d3-time-format)。此方法接受格式作為參數(shù),例如,%b 表示月份名稱的縮寫。您可以在模塊中查看可用格式的完整列表。

          在下面的代碼片段中,我們將 tickFormat() 方法鏈接到之前聲明的底部軸,并將時(shí)間格式作為參數(shù)傳遞。

          const bottomAxis = d3.axisBottom(xScale)
            .tickFormat(d3.timeFormat("%b"));

          圖 4.8 使用每個(gè)月縮寫名稱格式化的底部軸標(biāo)簽。


          我們的標(biāo)簽現(xiàn)在格式正確!它們標(biāo)記每個(gè)月的開始,這還不錯(cuò),但我們可以通過在各自的刻度之間居中月標(biāo)簽來提高可讀性,以建議每個(gè)月從一個(gè)刻度延伸到下一個(gè)刻度。

          要更改刻度標(biāo)簽的位置,我們首先需要選擇它們。打開瀏覽器的檢查器,仔細(xì)查看 D3 為軸生成的 SVG 元素。首先,我們有一個(gè)帶有類域的路徑元素,該元素在范圍(或域的表示)上繪制一條水平線。此路徑包括兩個(gè)外部刻度,即形狀兩端的短垂直線,如圖 4.9 所示。軸的刻度和標(biāo)簽由線條和文本元素組成,組織成具有刻度類的 SVG 組。這些 SVG 組沿軸平移以設(shè)置其行和文本元素的位置。軸生成器創(chuàng)建的元素的類型和類是 D3 公共 API 的一部分。您可以使用它們來自定義軸外觀。

          圖 4.9 組成軸的 SVG 元素


          考慮到這種結(jié)構(gòu),我們可以使用選擇器選擇x軸的所有標(biāo)簽 “.axis-x 文本” ,這意味著我們使用類軸-x抓取組中的每個(gè)文本元素。然后我們執(zhí)行一些調(diào)整。首先,我們使用文本元素的 y 屬性將文本元素向下移動(dòng) 10px。這種增加的垂直空白將提高可讀性。我們還將他們的字體系列設(shè)置為Roboto,這是我們已經(jīng)在項(xiàng)目中使用的字體。默認(rèn)情況下,D3 將軸的字體系列設(shè)置為無襯線,防止標(biāo)簽繼承項(xiàng)目的字體系列。最后,我們將它們的字體大小增加到 14px。

          出于關(guān)注點(diǎn)分離的目的,最后兩個(gè)樣式調(diào)整最好從CSS文件中處理。但在這里,我們使用 D3 來簡化指令。

          d3.selectAll(".axis-x text")
            .attr("y", "10px")
            .style("font-family", "Roboto, sans-serif")
            .style("font-size", "14px");

          為了使月份標(biāo)簽在其相應(yīng)的刻度之間居中,我們將使用 x 屬性。由于每個(gè)月都有不同的長度(在 28 到 31 天之間),我們需要為每個(gè)標(biāo)簽找到該月第一天和下個(gè)月第一天之間的中位數(shù)位置。請注意,D3 已在 g.axis-x 上將文本錨點(diǎn)屬性設(shè)置為“中間”。

          我們知道 D3 附加到每個(gè)標(biāo)簽的數(shù)據(jù)對應(yīng)于該月的第一天。在下面的代碼片段中,我們通過將 JavaScript 方法 getMonth() 應(yīng)用于當(dāng)前月份或附加到標(biāo)簽的值來查找下個(gè)月。此方法返回一個(gè)介于 0 和 11 之間的數(shù)字,0 表示 11 月,<> 表示 <> 月。然后,我們可以通過將年份、下個(gè)月和每月的第一天傳遞給 Date() 對象來創(chuàng)建新的 JavaScript 日期。

          最后,我們使用 xScale 計(jì)算月初和下個(gè)月開始之間的中位數(shù)距離。完成后,您的軸應(yīng)如圖 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 軸,月份標(biāo)簽在各自的刻度之間居中。


          那是很多操縱!但希望它能讓您了解我們可以自定義 D3 軸的不同方法。

          我們現(xiàn)在將添加 y 軸,其步驟將更加簡單。我們使用軸生成器 d3.axisLeft() ,因?yàn)槲覀兿雽?y 軸定位在圖表的左側(cè)。我們將 yScale 作為參數(shù)傳遞,并將軸保存在名為 leftAxis 的常量中。

          const leftAxis = d3.axisLeft(yScale);

          再一次,我們希望將軸附加到內(nèi)部圖表。我們將一個(gè)組附加到內(nèi)部圖表選擇中,給它一個(gè) axis-y 類并調(diào)用 leftAxis 。

          const leftAxis = d3.axisLeft(yScale);
          innerChart
            .append("g")
            .attr("class", "axis-y")
            .call(leftAxis);

          如果保存項(xiàng)目并在瀏覽器中查看,則會(huì)看到 y 軸已正確定位。我們所要做的就是更改標(biāo)簽的字體并增加它們的大小。在下面的代碼片段中,我們使用類軸-y 選擇組內(nèi)的所有文本元素。我們使用它們的 x 屬性將它們稍微向左移動(dòng)以獲得更好的可讀性,并設(shè)置它們的字體系列和字體大小屬性。

          d3.selectAll(".axis-y text")
            .attr("x", "-5px")
            .style("font-family", "Roboto, sans-serif")
            .style("font-size", "14px");

          您可能已經(jīng)注意到,我們必須重復(fù)代碼來設(shè)置軸標(biāo)簽的字體系列和字體大小屬性。在學(xué)習(xí)環(huán)境中,這沒什么大不了的,但我們通常會(huì)盡量避免在專業(yè)項(xiàng)目中出現(xiàn)這種重復(fù)。前面提到的更好的解決方案是從CSS文件控制這些樣式。另一種可能是使用組合選擇器應(yīng)用它們,如下所示。

          d3.selectAll(".axis-x text, .axis-y text")
            .style("font-family", "Roboto, sans-serif")
            .style("font-size", "14px");

          圖 4.11 完成的 x 軸和 y 軸。


          添加軸標(biāo)簽

          我們已經(jīng)完成了我們的軸,但我們?nèi)匀粦?yīng)該做一件事來幫助讀者理解我們的圖表。x 軸上的標(biāo)簽是不言自明的,但 y 軸上的標(biāo)簽不是。我們知道它們在 0 到 90 之間變化,但我們不知道它們代表什么。

          我們可以通過向軸添加標(biāo)簽來解決此問題。在 D3 項(xiàng)目中,標(biāo)簽只是文本元素,所以我們所要做的就是將文本元素附加到 SVG 容器中。我們將其內(nèi)容設(shè)置為“溫度(°F)”,并將其垂直位置設(shè)置為SVG容器原點(diǎn)下方20px。就是這樣!您的項(xiàng)目現(xiàn)在應(yīng)如圖 4.12 所示。在下一節(jié)中,我們將繪制折線圖。

          svg
            .append("text")
            .text("Temperature (°F)")
            .attr("y", 20);

          圖 4.12 完成的軸和標(biāo)簽。


          4.2 繪制折線圖

          現(xiàn)在,我們已準(zhǔn)備好構(gòu)建最常見的數(shù)據(jù)可視化之一:折線圖。折線圖由連接數(shù)據(jù)點(diǎn)的線或插入這些數(shù)據(jù)點(diǎn)的曲線組成。它們通常用于顯示現(xiàn)象隨時(shí)間推移的演變。在 D3 中,這些直線和曲線是使用 SVG 路徑元素構(gòu)建的,這些元素的形狀由其 d 屬性確定。在第 1 章中,我們討論了 d 屬性是如何由一系列命令組成的,這些命令指示如何繪制形狀。我們還說過,它很快就會(huì)變得復(fù)雜。值得慶幸的是,d3 形狀模塊 (https://github.com/d3/d3-shape) 提供了為我們計(jì)算 d 屬性的線和曲線生成器函數(shù),簡化了折線圖的創(chuàng)建。

          在本節(jié)中,我們將繪制一條線/曲線,顯示 2021 年紐約市平均溫度的演變,就像您在托管項(xiàng)目 (https://d3js-in-action-third-edition.github.io/new-york-city-weather-2021/) 或圖 4.1 中看到的那樣。但首先,讓我們在屏幕上顯示每個(gè)數(shù)據(jù)點(diǎn)。雖然這一步對于繪制折線圖不是必需的,但它將幫助我們了解 D3 的線生成器函數(shù)的工作原理。

          在函數(shù) drawLineChart() 中工作,我們使用數(shù)據(jù)綁定模式為數(shù)據(jù)集weekly_temperature.csv中的每一行創(chuàng)建一個(gè)圓圈。我們將這些圓圈附加到 innerChart 選擇中,并給它們一個(gè) 4px 的半徑。然后我們使用x和y尺度計(jì)算它們的位置屬性(cx和cy)。

          如果你還記得我們從第 3 章開始關(guān)于數(shù)據(jù)綁定的討論,你就知道我們可以使用訪問器函數(shù)訪問綁定到每個(gè)圓圈的數(shù)據(jù)。在下面的代碼片段中,d 公開了附加到每個(gè)圓的基準(zhǔn)面。這些數(shù)據(jù)是一個(gè) JavaScript 對象,我們可以使用點(diǎn)符號訪問日期或平均溫度。如果您需要查看此概念,請參閱第 3.3.1 節(jié)。

          請注意我們?nèi)绾温暶饕粋€(gè)名為“茄子”的單獨(dú)顏色常量,并使用它來設(shè)置圓圈的填充屬性。在這個(gè)項(xiàng)目中,我們將重復(fù)使用相同的顏色幾次,因此將其放在常量中會(huì)很方便。隨意使用您喜歡的任何顏色!

          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);

          保存您的項(xiàng)目并查看瀏覽器中的圓圈。它們應(yīng)位于 29 到 80°F 之間,并形成圓頂狀形狀,如圖 4.13 所示。

          圖 4.13 平均溫度隨時(shí)間演變的數(shù)據(jù)點(diǎn)。


          您現(xiàn)在可以繪制散點(diǎn)圖

          在這個(gè)階段要指出的一件很酷的事情是,即使沒有注意到它,你現(xiàn)在也知道如何繪制散點(diǎn)圖!散點(diǎn)圖只是一個(gè)圖表,顯示沿 x 軸和 y 軸定位的數(shù)據(jù)點(diǎn)集合,并可視化兩個(gè)或多個(gè)變量之間的關(guān)系。

          您知道如何繪制軸,并且知道如何根據(jù)其相關(guān)數(shù)據(jù)在屏幕上定位數(shù)據(jù)點(diǎn),因此您可以完全構(gòu)建散點(diǎn)圖!這就是D3的酷之處。您不必學(xué)習(xí)如何創(chuàng)建特定的圖表。相反,您可以通過生成和組裝構(gòu)建基塊來構(gòu)建可視化效果。對于散點(diǎn)圖,這些構(gòu)建基塊可以像兩個(gè)軸和一組圓一樣簡單。在第 7 章中,我們將構(gòu)建一個(gè)散點(diǎn)圖,其中圓的面積根據(jù)變量而變化。

          散點(diǎn)圖示例


          4.2.1 使用線路生成器

          現(xiàn)在我們清楚地看到了每個(gè)數(shù)據(jù)點(diǎn)的位置,引入 D3 的線生成器會(huì)更容易。行生成器 d3.line() 是一個(gè)函數(shù),它將每個(gè)數(shù)據(jù)點(diǎn)的水平和垂直位置作為輸入,并返回線或折線的 d 屬性,作為輸出傳遞通過這些數(shù)據(jù)點(diǎn)。我們通常用兩個(gè)訪問函數(shù) x() 和 y() 鏈接線生成器,分別將數(shù)據(jù)點(diǎn)的水平和垂直位置作為參數(shù),如圖 4.14 所示。

          圖 4.14 行生成器 d3.line() 與兩個(gè)訪問器函數(shù) x() 和 y() 結(jié)合使用,它們分別將每個(gè)數(shù)據(jù)點(diǎn)的水平和垂直位置作為參數(shù)。


          讓我們?yōu)檎劬€圖聲明一個(gè)線生成器函數(shù)。我們首先調(diào)用方法 d3.line() 并使用 x() 和 y() 訪問器函數(shù)進(jìn)行鏈接。x() 訪問器函數(shù)將每個(gè)數(shù)據(jù)點(diǎn)的水平位置作為參數(shù)。如果我們像到目前為止所做的那樣遍歷數(shù)據(jù),我們可以使用參數(shù) d 來訪問每個(gè)基準(zhǔn)面(數(shù)據(jù)集的每一行)。數(shù)據(jù)點(diǎn)的水平位置對應(yīng)于它們表示的日期,并使用 xScale() 計(jì)算。同樣,數(shù)據(jù)點(diǎn)的垂直位置與當(dāng)天的平均溫度成正比,并由 yScale() 返回。我們將行生成器函數(shù)存儲(chǔ)在名為 lineGenerator 的常量中,以便稍后可以調(diào)用它。

          const lineGenerator = d3.line()
            .x(d => xScale(d.date)) #A
            .y(d => yScale(d.avg_temp_F)); #B

          然后,我們將一個(gè)路徑元素附加到內(nèi)部圖表,并通過調(diào)用線生成器并將數(shù)據(jù)集作為參數(shù)傳遞來設(shè)置其 d 屬性。

          默認(rèn)情況下,SVG 路徑具有黑色填充。如果我們只想看到一條線,我們需要將填充屬性設(shè)置為 none 并將筆觸屬性設(shè)置為我們選擇的顏色;這里,顏色存儲(chǔ)在茄子常數(shù)中。此筆畫將成為我們的折線圖,如圖 4.15 所示。

          innerChart
            .append("path")
              .attr("d", lineGenerator(data)) #A
              .attr("fill", "none")
              .attr("stroke", aubergine);

          圖 4.15 使用線生成器創(chuàng)建并穿過每個(gè)數(shù)據(jù)點(diǎn)的 SVG 路徑,生成折線圖。


          4.2.2 將數(shù)據(jù)點(diǎn)插值到曲線中

          在像我們的折線圖這樣的情況下,離散數(shù)據(jù)點(diǎn)覆蓋了整個(gè)數(shù)據(jù)范圍,用簡單的線條表示數(shù)據(jù)點(diǎn)是一個(gè)很好的解決方案。但有時(shí),我們需要在點(diǎn)之間插值數(shù)據(jù),為此 D3 提供了各種生成曲線的插值函數(shù)。

          曲線生成器用作 d3.line() 的訪問函數(shù)。要將上一節(jié)中聲明的線生成器轉(zhuǎn)換為曲線生成器,我們只需鏈接 curve() 訪問器函數(shù)并傳遞 D3 的一個(gè)插值器。在下面的代碼片段中,我們使用插值器 d3.curveCatmullRom ,它產(chǎn)生一個(gè)三次樣條曲線(通過每個(gè)數(shù)據(jù)點(diǎn)并使用三階多項(xiàng)式函數(shù)計(jì)算的平滑靈活的形狀)。結(jié)果如圖4.16所示。

          const curveGenerator = d3.line()
            .x(d => xScale(d.year))
            .y(d => yScale(d.electoral_democracies))
            .curve(d3.curveCatmullRom);

          圖 4.16 使用Catmull-Roll樣條進(jìn)行曲線插值的折線圖。


          什么是最好的插值?

          插值會(huì)修改數(shù)據(jù)表示,不同的插值函數(shù)會(huì)創(chuàng)建不同的可視化效果。數(shù)據(jù)可以通過各種方式可視化,從編程的角度來看,所有這些都是正確的。但是,我們有責(zé)任確保我們可視化的信息反映了實(shí)際現(xiàn)象。

          由于數(shù)據(jù)可視化處理統(tǒng)計(jì)原理的可視化表示,因此它受到濫用統(tǒng)計(jì)數(shù)據(jù)的所有危險(xiǎn)的影響。線條的插值特別容易被誤用,因?yàn)樗鼘⒁粭l看起來笨拙的線條變成了一條平滑的“自然”線條。

          在圖 4.17 中,您可以看到使用不同曲線插值跟蹤的相同折線圖,并了解它們?nèi)绾斡绊懸曈X表示。選擇適當(dāng)?shù)牟逯岛瘮?shù)在很大程度上取決于您正在使用的數(shù)據(jù)。在我們的例子中,d3.curveBasis低估了溫度的突然變化,而d3.curveBundle旨在拉直曲線并減少其變化,這對于我們的數(shù)據(jù)來說是不夠的。如果我們沒有在圖表上繪制數(shù)據(jù)點(diǎn),我們就不知道曲線不能準(zhǔn)確地表示它們。這就是為什么仔細(xì)選擇和測試曲線插值函數(shù)很重要的原因。

          另一方面,函數(shù) d3.curveMonotoneX 和 d3.curveCatmullRom 創(chuàng)建緊隨數(shù)據(jù)點(diǎn)的曲線,類似于原始折線圖。d3.curveStep 還可以在上下文適當(dāng)時(shí)提供對數(shù)據(jù)的有趣解釋。圖 4.17 中所示的曲線插值列表并不詳盡,其中一些插值器還接受影響最終曲線形狀的參數(shù)。有關(guān)所有可用選項(xiàng),請參閱 d3 形狀模塊 (https://github.com/d3/d3-shape)。

          圖 4.17 不同的曲線插值及其如何修改數(shù)據(jù)的表示。


          您現(xiàn)在知道如何使用 D3 繪制折線圖了!回顧一下,我們首先需要初始化一個(gè)線生成器函數(shù)并設(shè)置其 x() 和 y() 訪問器函數(shù)。這些將負(fù)責(zé)計(jì)算每個(gè)數(shù)據(jù)點(diǎn)的水平和垂直位置。然后,我們可以通過鏈接 curve() 訪問器函數(shù)并選擇插值來選擇將直線轉(zhuǎn)換為曲線。最后,我們將一個(gè) SVG 路徑元素附加到我們的圖表中,并通過調(diào)用線條生成器并將數(shù)據(jù)作為參數(shù)傳遞來設(shè)置其 d 屬性。在第 7 章中,我們將通過工具提示使此折線圖具有交互性。如果您想立即學(xué)習(xí)該章節(jié),請隨時(shí)直接轉(zhuǎn)到該章節(jié)!

          圖 4.18 創(chuàng)建折線圖的步驟


          4.3 繪制區(qū)域

          在本節(jié)中,我們將在折線圖后面添加一個(gè)區(qū)域,以顯示每個(gè)日期的最低和最高溫度之間的范圍。在 D3 中繪制區(qū)域的過程與用于繪制線條的過程非常相似。像線條一樣,區(qū)域是使用 SVG 路徑元素創(chuàng)建的,D3 為我們提供了一個(gè)方便的區(qū)域生成器函數(shù), d3.area() ,用于計(jì)算該路徑的 d 屬性。

          在開始之前需要注意的一件事是,我們希望顯示折線圖后面的區(qū)域。由于元素在屏幕上的繪制順序與它們追加在 SVG 父項(xiàng)中的順序相同,因此應(yīng)在創(chuàng)建折線圖的代碼之前添加用于繪制區(qū)域的代碼。

          4.3.1 使用面積生成器

          讓我們首先聲明一個(gè)區(qū)域生成器函數(shù),并將其存儲(chǔ)在名為 areaGenerator 的常量中。正如您在以下代碼片段中觀察到的那樣,區(qū)域生成器至少需要三個(gè)訪問器函數(shù)。第一個(gè) x() 負(fù)責(zé)計(jì)算數(shù)據(jù)點(diǎn)的水平位置,與線生成器完全相同。但是現(xiàn)在,我們不僅有一組數(shù)據(jù)點(diǎn),而是兩組:一個(gè)沿著區(qū)域的下邊緣,另一個(gè)在其上邊緣,因此訪問器函數(shù) y0() 和 y1() 。請注意,在我們的例子中,區(qū)域下邊緣和上邊緣的數(shù)據(jù)點(diǎn)共享相同的水平位置。

          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 可能有助于可視化區(qū)域的下限和上限,以及面積生成器如何計(jì)算與該區(qū)域相關(guān)的數(shù)據(jù)。

          圖 4.19 面積生成器 d3.area() 與三個(gè)或更多訪問器函數(shù)結(jié)合使用。為了繪制最低和最高溫度之間的面積,我們使用 x()、y0() 和 y1()。第一個(gè)計(jì)算每個(gè)數(shù)據(jù)點(diǎn)的水平位置,第二個(gè)計(jì)算數(shù)據(jù)點(diǎn)在下邊界上的垂直位置,這里是最低溫度,第三個(gè)是數(shù)據(jù)點(diǎn)在上邊緣的垂直位置,這里是最高溫度。


          正如我們對折線圖所做的那樣,通過將 curve() 訪問器函數(shù)鏈接到面積生成器,將區(qū)域的邊界插值為曲線。這里我們也使用相同的曲線插值器函數(shù), 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);

          一旦面積生成器準(zhǔn)備就緒,我們需要做的就是將 SVG 路徑元素附加到內(nèi)部圖表中。為了設(shè)置其 d 屬性,我們調(diào)用區(qū)域生成器并將數(shù)據(jù)集作為參數(shù)傳遞。其余的純粹與美學(xué)有關(guān)。我們將填充屬性設(shè)置為之前聲明的茄子色常數(shù),并將填充不透明度設(shè)置為 20%,以確保區(qū)域和折線圖之間的對比度足夠。請注意,茄子常數(shù)的聲明需要在我們使用它來設(shè)置區(qū)域的填充之前進(jìn)行。

          innerChart
            .append("path")
              .attr("d", areaGenerator(data))
              .attr("fill", aubergine)
              .attr("fill-opacity", 0.2);

          圖4.20 平均溫度的折線圖,結(jié)合顯示最低溫度和最高溫度之間變化的區(qū)域。


          如您所見,繪制區(qū)域的過程與繪制線條的過程非常相似。主要區(qū)別在于,一條線只有一組數(shù)據(jù)點(diǎn),在這些數(shù)據(jù)點(diǎn)之間繪制了這條線,而區(qū)域是兩條邊之間的區(qū)域,每條邊都有一組數(shù)據(jù)點(diǎn)。這就是為什么線發(fā)生器只需要兩個(gè)訪問器函數(shù) x() 和 y() ,而面積生成器至少需要三個(gè),在我們的例子中是 x() 、y0() 和 y1()。

          圖 4.21 創(chuàng)建區(qū)域的步驟


          4.3.2 使用標(biāo)簽增強(qiáng)可讀性

          我們現(xiàn)在有一張 2021 年紐約市平均溫度的折線圖,以及一個(gè)顯示最低和最高溫度之間變化的區(qū)域。它看起來已經(jīng)相當(dāng)不錯(cuò)了,但我們需要確保看到這張圖表的人很容易理解線條和面積的含義。標(biāo)簽是一個(gè)很好的工具!

          在 D3 中,標(biāo)簽只是我們放置在可視化效果上的 SVG 文本元素。在這里,我們將創(chuàng)建三個(gè)標(biāo)簽,一個(gè)用于我們將放置在折線圖末尾的平均溫度,一個(gè)用于放置在該區(qū)域下方的最低溫度,另一個(gè)用于放置在該區(qū)域上方的最高溫度。

          讓我們從折線圖的標(biāo)簽開始。我們首先將 SVG 文本元素附加到內(nèi)部圖表,并使用 text() 方法將其內(nèi)容設(shè)置為“平均溫度”。然后我們計(jì)算它的位置,由屬性 x 和 y 控制。

          我們希望標(biāo)簽位于折線圖的末尾或緊靠其最后一個(gè)數(shù)據(jù)點(diǎn)之后。我們可以通過將之前聲明刻度時(shí)計(jì)算的 lastDate 常量傳遞給 xScale() 來獲取該值。我們還添加了 10px 的額外填充。

          對于垂直位置,我們還沒有一個(gè)常數(shù)來為我們提供最后一個(gè)溫度值。盡管如此,我們?nèi)匀豢梢允褂?data[data.length - 1] 找到數(shù)據(jù)集中的最后一行,并使用點(diǎn)符號來訪問平均溫度。我們將這個(gè)值傳遞給 yScale() 并獲取標(biāo)簽的垂直位置。

          最后,我們重用顏色常數(shù)茄子作為文本的顏色,由其填充屬性控制。

          innerChart
            .append("text")
              .text("Average temperature")
              .attr("x", xScale(lastDate) + 10)
              .attr("y", yScale(data[data.length - 1].avg_temp_F))
              .attr("fill", aubergine);

          如果保存項(xiàng)目并在瀏覽器中查看,則會(huì)發(fā)現(xiàn)標(biāo)簽的底部與折線圖上最后一個(gè)數(shù)據(jù)點(diǎn)的中心垂直對齊。默認(rèn)情況下,SVG 文本的基線位于文本底部,如圖 4.22 所示。我們可以使用主導(dǎo)基線屬性來更改此設(shè)置。在下面的代碼片段中,我們給此屬性一個(gè)值 中間 ,以將基線移動(dòng)到文本的垂直中心。

          圖 4.22 SVG 文本的 y 屬性設(shè)置其基線的垂直位置,默認(rèn)情況下位于文本底部。我們可以使用主導(dǎo)基線屬性來更改它。如果我們給此屬性值“middle”,則文本的基線將移動(dòng)到其垂直中間,而值“hanging”會(huì)將基線移動(dòng)到文本的頂部。


          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);

          然后,我們將為該區(qū)域的下邊界添加一個(gè)標(biāo)簽,該標(biāo)簽表示最低溫度的演變。策略非常相似。我們首先附加一個(gè) SVG 文本元素,并為其提供“最低溫度”的內(nèi)容。

          對于它的位置,我們選擇了最后一個(gè)向下的突起,它對應(yīng)于倒數(shù)第三個(gè)數(shù)據(jù)點(diǎn)。我們將這些數(shù)據(jù)點(diǎn)的值傳遞給我們的秤以找到它的位置,并將標(biāo)簽向下移動(dòng) 20px,向右移動(dòng) 13px。這些數(shù)字是通過移動(dòng)標(biāo)簽找到的,直到我們找到一個(gè)看起來合適的位置。瀏覽器的檢查器工具是測試此類微小調(diào)整的好地方。請注意,我們已將標(biāo)簽的主要基線設(shè)置為 掛起 。如圖 4.22 所示,這意味著 y 屬性控制文本頂部的位置。

          最后,在代碼段中,您將看到我們在標(biāo)簽中添加了一條線,在該區(qū)域的向下突起和標(biāo)簽之間跟蹤,以闡明標(biāo)簽代表的內(nèi)容。您可以在圖 4.23 中看到它的外觀。同樣,我們使用刻度來計(jì)算直線的 x1、y1、x2 和 y2 屬性,這些屬性控制其起點(diǎn)和終點(diǎn)的位置。

          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);

          我們使用非常相似的過程為該區(qū)域的上邊界附加一個(gè)標(biāo)簽,該標(biāo)簽表示最高溫度的演變。我們選擇將此標(biāo)簽放置在與倒數(shù)第四個(gè)數(shù)據(jù)點(diǎn)相對應(yīng)的向上突起附近。同樣,我們在標(biāo)簽和突起之間畫了一條線。完成后,折線圖就完成了!

          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年紐約市溫度演變的完整折線圖。


          4.4 繪制圓弧

          在最后一節(jié)中,我們將討論如何使用 D3 繪制弧線。弧形是數(shù)據(jù)可視化中的常見形狀。它們用于餅圖、旭日圖和南丁格爾圖,以可視化金額與總數(shù)的關(guān)系,我們經(jīng)常在自定義徑向可視化中使用它們。

          像直線和面積一樣,弧是用 SVG 路徑繪制的,而且,正如你現(xiàn)在可能已經(jīng)猜到的那樣,D3 提供了一個(gè)方便的弧發(fā)生器函數(shù),可以為我們計(jì)算弧路徑的 d 屬性。

          在詳細(xì)討論電弧發(fā)生器之前,讓我們準(zhǔn)備我們的項(xiàng)目。在這里,我們將繪制構(gòu)成徑向圖的弧線,您可以在圖 4.1 中的“有降水的日子”或托管項(xiàng)目 (https://d3js-in-action-third-edition.github.io/new-york-city-weather-2021/) 中看到。藍(lán)色弧線表示 2021 年紐約市有降水的天數(shù)百分比 (35%),而灰色弧線表示其余天數(shù)。

          首先,打開文件弧.js .這就是我們將在本章其余部分工作的地方。像往常一樣,我們需要加載一個(gè)數(shù)據(jù)集,在本例中為 daily_precipitations.csv ,它包含在數(shù)據(jù)文件夾中。如果您查看 CSV 文件,您會(huì)發(fā)現(xiàn)它只包含兩列:日期列列出了 2021 年的每一天,而total_precip_in列則提供了每天的總降水量(以英寸為單位)。

          在下面的代碼片段中,我們使用 d3.csv() 獲取數(shù)據(jù)集,使用 d3.autoType 正確格式化日期和數(shù)字,并使用 Promise 將其鏈接,我們將數(shù)據(jù)記錄到控制臺(tái)中。我們不會(huì)在這里討論如何使用 d3.csv() 的細(xì)節(jié)。有關(guān)更多說明,請參閱第 3 章,有關(guān) d4.autoType 的討論,請參閱本章的第 1.3 節(jié)。

          d3.csv("./data/daily_precipitations.csv", d3.autoType).then(data => {
            console.log("precipitations data", data);
          });

          如果您在控制臺(tái)中查看數(shù)據(jù),您會(huì)發(fā)現(xiàn)日期和數(shù)字的格式都正確。偉大!我們可以獲取格式化的數(shù)據(jù)集并將其傳遞給函數(shù) drawArc() ,它已經(jīng)存在于 arcs 中.js .

          d3.csv("./data/daily_precipitations.csv", d3.autoType).then(data => {
            console.log("precipitations data", data);
            drawArc(data);
          });

          在 drawArc() 中,我們現(xiàn)在可以附加一個(gè)新的 SVG 容器。正如您在以下代碼片段中看到的,我們?yōu)?SVG 容器提供了 300px 的寬度和高度,并將其附加到 div 中,其中包含索引中已經(jīng)存在的 arc id.html 。我們使用第 1 章中解釋的策略使 SVG 響應(yīng):將 viewBox 屬性的最后兩個(gè)值設(shè)置為其寬度和高度,并完全省略寬度和高度屬性。這樣,SVG 容器將適應(yīng)其父容器的大小,同時(shí)保留其縱橫比。請注意,我們將 SVG 容器選擇保存在名為 svg 的常量中。

          const pieChartWidth = 300;
          const pieChartHeight = 300;
          const svg = d3.select("#arc")
            .append("svg")
            .attr("viewBox", [0, 0, pieChartWidth, pieChartHeight]);

          4.4.1 極坐標(biāo)系

          如第 4.1 節(jié)所述,我們將圖表包裝在 SVG 組中,并將該組轉(zhuǎn)換為所需位置。不過,這次的策略有點(diǎn)不同。我們不需要為軸或標(biāo)簽保留空間,因此我們可以省略邊距約定。但是,與迄今為止構(gòu)建的所有可視化相反,弧位于極坐標(biāo)系中,而不是笛卡爾坐標(biāo)系中,后者的行為略有不同。

          如圖 4.24 所示,SVG 容器的坐標(biāo)系是笛卡爾坐標(biāo)系。它使用兩個(gè)垂直維度 x 和 y 來描述 2D 空間中的位置。我們在第 1 章中討論過,SVG 元素的坐標(biāo)系有點(diǎn)特殊,因?yàn)樗脑c(diǎn)位于 SVG 容器的左上角,使 y 維在從上到下的方向上為正。

          2D 極坐標(biāo)系還使用兩個(gè)維度:半徑和角度。半徑是原點(diǎn)與空間中點(diǎn)之間的距離,而角度是從 12 點(diǎn)鐘方向沿順時(shí)針方向計(jì)算的。這種描述空間位置的方法在處理圓弧時(shí)特別有用。

          圖 4.24 笛卡爾坐標(biāo)的尺寸彼此垂直,而極坐標(biāo)系統(tǒng)使用半徑和角度尺寸來描述空間中的位置。


          由于元素位于極坐標(biāo)系中的原點(diǎn)周圍,因此我們可以說我們將要構(gòu)建的弧可視化的原點(diǎn)位于 SVG 容器的中心,如圖 4.25 所示。

          圖 4.25 通過將弧包裝成 SVG 組并將該組轉(zhuǎn)換為 SVG 容器的中心,我們簡化了一組弧的創(chuàng)建。當(dāng)我們向組追加弧時(shí),它們的位置將自動(dòng)相對于圖表的中心,這對應(yīng)于其極坐標(biāo)系的原點(diǎn)。


          在下一個(gè)代碼片段中,我們選擇SVG容器并在其中附加一個(gè)組,我們將該組轉(zhuǎn)換為SVG容器的中心,并將其保存在常量innerChart中。

          const innerChart = svg
            .append("g")
              .attr("transform", `translate(${pieChartWidth/2}, 
                 ? ${pieChartHeight/2})`);

          在創(chuàng)建弧線之前,我們需要做最后一件事:計(jì)算圖表上有降水的日子所采用的角度。使用 D3 創(chuàng)建餅圖或圓環(huán)圖時(shí),我們通常使用餅圖布局生成器處理此類計(jì)算,我們將在下一章中介紹。但是由于我們在這里只畫兩個(gè)弧線,所以數(shù)學(xué)很容易。

          首先,我們可以使用數(shù)據(jù)集的 length 屬性知道 2021 年的總天數(shù),即 365。然后,我們通過過濾數(shù)據(jù)集來查找有降水的天數(shù),以僅保留降水量大于零的天數(shù),即 126 天。最后,我們將降水的天數(shù)除以總天數(shù)(得到 35%),將降水天數(shù)轉(zhuǎn)換為百分比。

          const numberOfDays = data.length;
          const numberOfDaysWithPrecipitations = data.filter(d => 
            ? d.total_precip_in > 0).length;
          const percentageDaysWithPrecipitations =   
            ? Math.round(numberOfDaysWithPrecipitations / numberOfDays * 100);

          然后,我們可以通過將這個(gè)數(shù)字乘以 360 度(一個(gè)完整圓的度數(shù))來計(jì)算對應(yīng)于降水天數(shù)的角度,得到 126 度。我們從度開始,因?yàn)樗庇^,但我們還需要將此值轉(zhuǎn)換為弧度。為此,我們將降水天數(shù)百分比(以度為單位)所覆蓋的角度乘以數(shù)字 pi (3.1416),然后將其除以 180,得到大約 2.2 弧度的角度,我們將其保存在常數(shù)angleDaysWithPrecipitations_rad中。

          我們執(zhí)行此轉(zhuǎn)換是因?yàn)槲覀儗⒃谝粫?huì)兒使用的電弧發(fā)生器期望角度以弧度而不是度為單位。作為處理角度的經(jīng)驗(yàn)法則,JavaScript 通常希望它們以弧度為單位,而 CSS 使用度數(shù)。

          const angleDaysWithPrecipitations_deg = percentageDaysWithPrecipitations * 
            ? 360 / 100;
          const angleDaysWithPrecipitations_rad = angleDaysWithPrecipitations_deg * 
            ? Math.PI / 180;

          4.4.2 使用電弧發(fā)生器

          我們終于到了有趣的部分,生成弧線!首先,我們需要聲明一個(gè)電弧發(fā)生器,就像我們對線和區(qū)域所做的那樣。弧發(fā)生器 d3.arc() 是模塊 d3-shape (https://github.com/d3/d3-shape) 的一部分,在我們的例子中,需要兩個(gè)主要的訪問器函數(shù):弧的內(nèi)半徑和外半徑,分別由 innerRadius() 和 outerRadius() 處理,并給定值為 80 和 120px。請注意,如果內(nèi)半徑為零,我們會(huì)得到一個(gè)類似于餅圖中的弧線,并從原點(diǎn)開始。

          const arcGenerator = d3.arc()
            .innerRadius(80)
            .outerRadius(120);

          我們可以通過使用訪問器函數(shù)在弧形之間添加填充來個(gè)性化我們的弧線 padAngle() ,它接受以弧度為單位的角度。這里我們使用 0.02 弧度,對應(yīng)于略多于 1 度。我們也可以用 角半徑() ,它接受一個(gè)以像素為單位的值。此訪問器函數(shù)與 CSS 邊框半徑屬性具有類似的效果。

          const arcGenerator = d3.arc()
            .innerRadius(80)
            .outerRadius(120)
            .padAngle(0.02)
            .cornerRadius(6);

          圖 4.26 電弧發(fā)生器使用多個(gè)訪問器函數(shù)來計(jì)算電弧的 d 屬性。在這里,我們在生成器聲明期間設(shè)置其內(nèi)半徑、外半徑、填充角度和角半徑。我們將在將路徑元素附加到圖表時(shí)傳遞每個(gè)弧的開始和結(jié)束角度。


          此時(shí),您可能想知道為什么我們不使用處理弧線覆蓋的角度的訪問器函數(shù)。在我們的例子中,由于我們已經(jīng)手動(dòng)計(jì)算了角度,因此當(dāng)我們附加路徑時(shí),將這些值傳遞給電弧發(fā)生器會(huì)更簡單。但我們將在下一章中看到,情況并非總是如此。

          因此,讓我們附加第一個(gè)弧線,即顯示降水天數(shù)的弧線。在下面的代碼片段中,我們首先將一個(gè) path 元素附加到內(nèi)部圖表選擇中。然后,我們通過調(diào)用最后一個(gè)代碼段中聲明的 arc 生成器來設(shè)置其 d 屬性。

          觀察我們?nèi)绾螌㈤_始和結(jié)束角度作為對象傳遞給生成器。起始角度的值為零,對應(yīng)于 12 點(diǎn)鐘位置,而結(jié)束角度的值是之前計(jì)算的降水天數(shù)所覆蓋的角度。最后,我們將弧線的填充設(shè)置為顏色 #6EB7C2,青藍(lán)色。

          innerChart
            .append("path")
              .attr("d", () => {
                return arcGenerator({
                  startAngle: 0,
                  endAngle: angleDaysWithPrecipitations_rad
                });
              })
              .attr("fill", "#6EB7C2");

          我們以類似的方式附加第二個(gè)弧線。這一次,弧從前一個(gè)弧線結(jié)束的地方開始,到圓圈完成時(shí)結(jié)束,對應(yīng)于弧度中的角度 2*Pi。我們給弧線一個(gè) #DCE2E2,一種更接近灰色的顏色,以表明這些日子沒有降水。

          innerChart
            .append("path")
              .attr("d", () => {
                return arcGenerator({
                  startAngle: angleDaysWithPrecipitations_rad,
                  endAngle: 2 * Math.PI
                });
              })
              .attr("fill", "#DCE2E2");

          保存項(xiàng)目后,弧應(yīng)如圖 4.27 所示。我們鼓勵(lì)您使用傳遞給生成器的訪問器函數(shù)的值(如半徑或角半徑),以了解它們?nèi)绾涡薷幕〉耐庥^。

          圖 4.27 弧線顯示有降水天數(shù)和無降水天數(shù)之間的比率。


          如您所見,繪制圓弧的過程類似于繪制線條和區(qū)域的過程。主要區(qū)別在于弧在空間中的位置是用極坐標(biāo)而不是笛卡爾來處理的,這反映在弧發(fā)生器的訪問器函數(shù)中。

          圖 4.28 繪制弧線的步驟。


          4.4.3 計(jì)算弧的質(zhì)心

          餅圖和圓環(huán)圖最近在數(shù)據(jù)可視化社區(qū)中得到了很多負(fù)面報(bào)道,主要是因?yàn)槲覀円庾R到人眼不太擅長估計(jì)弧線所代表的比率。但是,這些圖表并不總是一個(gè)糟糕的選擇,尤其是當(dāng)它們包含少量類別時(shí)。但我們絕對可以通過標(biāo)簽幫助他們提高可讀性,這就是我們在這里要做的!

          在表示有降水天數(shù)的弧線上,我們將添加標(biāo)簽“35%”,即之前計(jì)算的有降水的天數(shù)百分比。放置此標(biāo)簽的好地方是弧的質(zhì)心,也稱為其質(zhì)心。此值可由電弧發(fā)生器提供。

          在下面的代碼片段中,我們在前面初始化的弧發(fā)生器函數(shù)上調(diào)用方法。這一次,我們將它與 startAngle() 和 endAngle() 訪問器函數(shù)鏈接起來,分別將它們傳遞代表有降水的日子的弧的開始角和結(jié)束角的值。最后,我們鏈接方法centroid(),它將計(jì)算弧的中點(diǎn)。

          const centroid = arcGenerator
            .startAngle(0)
            .endAngle(angleDaysWithPrecipitations_rad)
            .centroid();

          將質(zhì)心記錄到控制臺(tái)中。您將看到它由兩個(gè)值的數(shù)組組成:質(zhì)心的水平和垂直位置,在我們的例子中是 [89, -45] ,從內(nèi)部圖表的原點(diǎn)計(jì)算得出。

          在下一個(gè)代碼段中,我們通過向內(nèi)部圖表追加文本元素來創(chuàng)建標(biāo)簽。為了使標(biāo)簽包含“%”符號,我們使用方法 d3.format(“.0%”) ,后跟括號中的值。此方法便于以特定方式(如貨幣、百分比和指數(shù))格式化數(shù)字,或?yàn)檫@些數(shù)字添加特定后綴,如“M”表示百萬或“μ”表示微型。您可以在模塊 d3 格式 (https://github.com/d3/d3-format) 中找到所有可用格式的詳細(xì)列表。

          然后,我們使用質(zhì)心數(shù)組中返回的第一個(gè)和第二個(gè)值設(shè)置 x 和 y 屬性。請注意我們?nèi)绾卧O(shè)置文本錨點(diǎn)和主要基線屬性,以確保標(biāo)簽在水平和垂直方向上以 x 和 y 屬性為中心。

          最后,我們給標(biāo)簽一個(gè)白色和500的字體粗細(xì),以提高其易讀性。保存后,帶有標(biāo)簽的弧應(yīng)如圖 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 帶有標(biāo)簽的已完成弧。


          您現(xiàn)在知道如何使用 D3 繪制線條、面積和弧線了!在下一章中,我們將使用布局生成器將這些形狀提升到另一個(gè)層次。

          4.5 小結(jié)

          • D3 邊距約定的作用是以系統(tǒng)和可重用的方式為軸、標(biāo)簽和圖例保留圖表周圍的空間。
          • 我們通過聲明一個(gè)包含上邊距、右邊距、下邊距和左邊距值的邊距對象來實(shí)現(xiàn)這一點(diǎn)。
          • 一個(gè)有用的策略是將構(gòu)成圖表本身的元素包裝到 SVG 組中,并根據(jù)邊距將此組放置在 SVG 容器中。這將為圖表元素創(chuàng)建一個(gè)新的原點(diǎn),并促進(jìn)其實(shí)現(xiàn)。
          • D3 有四個(gè)軸生成器:axisTop()、axisRight()、axisBottom() 和 axisLeft() ,它們分別創(chuàng)建頂部、右側(cè)、底部和左側(cè)軸的組件。
          • 這些軸生成器將刻度作為輸入,并返回組成軸的 SVG 元素作為輸出(沿軸的一條線以及多組刻度和標(biāo)簽)。
          • 通過將 call() 方法鏈接到選擇并將軸作為參數(shù)傳遞,我們將軸附加到圖表。
          • 折線圖是最常見的圖表之一,可用于顯示現(xiàn)象隨時(shí)間推移的演變。我們繪制帶有連接數(shù)據(jù)點(diǎn)的線條或曲線的折線圖。為了繪制折線圖,我們首先使用 d3.line() 方法初始化一個(gè)線生成器。線生成器有兩個(gè)訪問器函數(shù),x() 和 y(),它們計(jì)算每個(gè)數(shù)據(jù)點(diǎn)的水平和垂直位置。我們可以使用 curve() 訪問器函數(shù)將折線圖轉(zhuǎn)換為曲線。D3提供多種曲線插值函數(shù),這些函數(shù)會(huì)影響數(shù)據(jù)表示,必須仔細(xì)選擇。為了使折線圖顯示在屏幕上,我們將路徑元素附加到選擇中,并通過調(diào)用線生成器并將數(shù)據(jù)集作為屬性傳遞來設(shè)置其 d 屬性。
          • 面積是兩個(gè)邊界之間的區(qū)域,使用 D3 繪制區(qū)域類似于繪制一條線。為了繪制一個(gè)區(qū)域,我們首先用方法 d3.area() 聲明一個(gè)區(qū)域生成器。此方法至少需要三個(gè)訪問器函數(shù)來計(jì)算每個(gè)數(shù)據(jù)點(diǎn)沿區(qū)域邊緣的位置,例如 x() 、y0() 和 y1() 或 x0()、x1() 和 y()。與直線一樣,D3 提供了可與 curve() 訪問器函數(shù)一起應(yīng)用的插值函數(shù)。為了使區(qū)域出現(xiàn)在屏幕上,我們將路徑元素附加到選擇中,并通過調(diào)用區(qū)域生成器并將數(shù)據(jù)集作為屬性傳遞來設(shè)置其 d 屬性。
          • 標(biāo)簽對于幫助讀者理解我們的數(shù)據(jù)可視化特別有用。在 D3 中,標(biāo)簽只是我們需要在 SVG 容器中定位的文本元素。SVG 文本的位置由其 x 和 y 屬性控制。y 屬性設(shè)置文本基線的位置,默認(rèn)情況下,基線位于其底部。我們使用屬性移動(dòng) SVG 文本的基線 主導(dǎo)基線 .值中間將基線移動(dòng)到文本的垂直中間,而值掛起 將基線移動(dòng)到頂部。
          • 使用弧的可視化通常使用極坐標(biāo)系進(jìn)行描述。此坐標(biāo)系使用半徑、原點(diǎn)與點(diǎn)之間的距離以及角度來描述空間中的位置。
          • 弧是使用 SVG 路徑元素創(chuàng)建的,其中 d 屬性是使用弧發(fā)生器計(jì)算的。D3 的弧發(fā)生器 d3.arc() 具有訪問函數(shù),用于定義弧的起始和結(jié)束角度( startAngle() 和 endAngle() ),以及它的內(nèi)半徑和外半徑( innerRadius() 和 outerRadius() )。我們還可以使用訪問器函數(shù)來圓弧的角( cornerRadius() ) 或在弧之間添加填充 ( padAngle() )。電弧發(fā)生器期望角度以弧度表示。
          • 弧的質(zhì)心可以用 centroid() 方法計(jì)算。此訪問器函數(shù)鏈接到電弧發(fā)生器,返回一個(gè)包含質(zhì)心水平和垂直位置的數(shù)組。

          經(jīng)常看到圖片有一道高亮光弧閃過很炫的效果,其原理很簡單可以用css3動(dòng)畫就可以做出這樣的效果,用css3動(dòng)畫控制position:absolute的left值,從左向右閃過,這個(gè)具體的請看以下代碼哈。效果截圖如下:

          鼠標(biāo)houver在盒子box上時(shí)動(dòng)畫會(huì)停止,代碼如下:

          <!Doctype html>

          <html>

          <head>

          <meta http-equiv="Content-Type" content="text/html; charset=gbk2312"/>

          <title>CSS3動(dòng)畫實(shí)現(xiàn)高亮光弧效果</title>

          <style type="text/css">

          body{margin:0;padding:0;}

          .box{width:1000px;margin:200px auto;height:500px;position:relative;text-align:center;}

          .box:hover .rolled{

          -webkit-animation-play-state:paused;

          -moz-animation-play-state:paused;

          -o-animation-play-state:paused;

          -ms-animation-play-state:paused;

          }

          .rolled{

          position:absolute;

          top: 0;

          width:80px;

          height:500px;

          background: -moz-linear-gradient(left,rgba(255,255,255,0)0,rgba(255,255,255,.2)50%,rgba(255,255,255,0)100%);

          background: -webkit-gradient(linear,left top,right top,color-stop(0%,rgba(255,255,255,0)),color-stop(50%,rgba(255,255,255,.2)),color-stop(100%,rgba(255,255,255,0)));

          background: -webkit-linear-gradient(left,rgba(255,255,255,0)0,rgba(255,255,255,.2)50%,rgba(255,255,255,0)100%);

          background: -o-linear-gradient(left,rgba(255,255,255,0)0,rgba(255,255,255,.2)50%,rgba(255,255,255,0)100%);

          -webkit-transform: skewX(-25deg);

          -moz-transform: skewX(-25deg);

          -webkit-animation:rolled 2.5s .2s ease both infinite;

          -moz-animation:rolled 2.5s .2s ease both infinite;

          -o-animation:rolled 2.5s .2s ease both infinite;

          -ms-animation:rolled 2.5s .2s ease both infinite;

          overflow: hidden;

          }

          @-webkit-keyframes rolled{

          0%{left:-150px ;}

          100%{left:920px;}

          }

          @-moz-keyframes rolled{

          0%{left:-150px ;}

          100%{left:920px;}

          }

          @-o-keyframes rolled{

          0%{left:-150px ;}

          100%{left:920px;}

          }

          @-ms-keyframes rolled{

          0%{left:-150px ;}

          100%{left:920px;}

          }

          @keyframes rolled{

          0%{left:-150px ;}

          100%{left:920px;}

          }

          </style>

          </head>

          <body>

          <div class="box">

          <img src="http://www.jungjaehyung.com/uploadfile/2024/0808/20240808035600329.jpg">

          <div class="rolled"></div>

          </div>

          </body>

          </html>

          查看效果演示:http://tangjiusheng.com/css3/page01.html

          另外一種效果,鼠標(biāo)houver時(shí)效果才出現(xiàn)(觸發(fā)),代碼如下:

          <!Doctype html>

          <html>

          <head>

          <meta http-equiv="Content-Type" content="text/html; charset=gbk2312"/>

          <title>CSS3實(shí)現(xiàn)高亮光弧效果</title>

          <style type="text/css">

          body{

          margin: 0;padding: 0;

          }

          .box{width:1000px;margin:200px auto;height:500px;position:relative;text-align:center;}

          a.floor:before{

          display: block;

          height: 500px;

          }

          a.floor:hover:before{

          -webkit-transition: left 1.5s;

          -moz-transition: left 1.5s;

          transition: left 1.5s;

          left: 920px;

          }

          a.floor:before{

          content: "";

          position: absolute;

          width: 80px;

          height:500px;

          top: 0;

          left: -150px;

          overflow: hidden;

          background: -moz-linear-gradient(left,rgba(255,255,255,0)0,rgba(255,255,255,.2)50%,rgba(255,255,255,0)100%);

          background: -webkit-gradient(linear,left top,right top,color-stop(0%,rgba(255,255,255,0)),color-stop(50%,rgba(255,255,255,.2)),color-stop(100%,rgba(255,255,255,0)));

          background: -webkit-linear-gradient(left,rgba(255,255,255,0)0,rgba(255,255,255,.2)50%,rgba(255,255,255,0)100%);

          background: -o-linear-gradient(left,rgba(255,255,255,0)0,rgba(255,255,255,.2)50%,rgba(255,255,255,0)100%);

          -webkit-transform: skewX(-25deg);

          -moz-transform: skewX(-25deg);

          }

          </style>

          </head>

          <body>

          <div class="box">

          <a class="floor" href="#"><img src="http://www.jungjaehyung.com/uploadfile/2024/0808/20240808035600329.jpg"></a>

          </div>

          </body>

          </html>

          除注明外的文章,均為來源:湯久生博客,轉(zhuǎn)載請保留本文地址!

          原文鏈接:http://tangjiusheng.com/css3/125.html

          單形狀繪制《HTML5系列教程18》

          簡單形狀繪制《HTML5系列教程18》

          Canvas在HTML5中有著非常強(qiáng)大的功能,它不僅可以繪制一些簡單的圖案,還可以進(jìn)行一些簡單形狀繪制如:圓形、三角形、矩形、圓角矩形等二維形狀,并可以根據(jù)用戶的實(shí)際需要繪制自定義二維圖形。而要完成這項(xiàng)功能,就需要用到CanvasRenderingContext2D對象提供的另外一些API。

          1.圓形的繪制方法

          在上篇文中我們使用了arc函數(shù)繪制圓弧,不知道大家有沒有注意到我們在指定圓弧結(jié)束角時(shí)使用了“2*math.PI*0.75”這樣的參數(shù),Math.PI是計(jì)算中表示數(shù)學(xué)π的意思,我們用2π乘以0.75就是繪制了四分之三個(gè)圓,這樣就得到了一個(gè)圓弧。如果我們把0.75去掉,就能繪制一個(gè)圓形了,代碼如圖所示:

          HTML5中圓形的繪制方法代碼

          在谷歌瀏覽器中顯示預(yù)覽效果如圖所示:

          HTML5中圓形的繪制方法效果預(yù)覽

          2.三角形的繪制方法

          在上一篇文章我們也學(xué)了如何繪制一條直線,那么如果我們繪制三條直線,并設(shè)置三條直線的起點(diǎn)和終點(diǎn)互相連接,這樣我們就能得到一個(gè)三角形了,繪制三角形的代碼如圖所示:

          HTML中三角形的繪制方法代碼

          在谷歌瀏覽器中顯示預(yù)覽效果如圖所示:

          HTML中三角形的繪制方法效果預(yù)覽

          3.圓角矩形的繪制方法

          上面的圖形我們都是用Canvas的CanvasRenderingContext2D能夠直接提供的圖形對象,那如果需要繪制如圓角矩形這樣特殊一點(diǎn)的圖形,我們應(yīng)該怎樣繪制呢?那么這里就需要用到另外幾個(gè)API對象了,通過一些特殊的處理,繪制出符合我們要求的圖形。在下面的代碼中,我們使用了arcTo函數(shù)繪制圓角矩形,該函數(shù)的描述見下表:

          HTML5中arcTo函數(shù)詳情描述

          arcTo函數(shù)為我們提供了兩條切線之間圓弧的繪制方法,圓角矩形是由四個(gè)這樣的圓弧和四條直線組成的,所以只要我們能精確控制圓弧和直線的每個(gè)點(diǎn),就能準(zhǔn)確繪制出一個(gè)圓角矩形。繪制圓角矩形的代碼如圖所示:

          在HTML5中圓角矩形的繪制方法代碼

          在谷歌瀏覽器中顯示預(yù)覽效果如圖所示:

          在HTML5中圓角矩形的繪制方法效果預(yù)覽

          4.簡單的自定義圖形繪制“微笑表情繪制”

          雖然Canvas提供了很多基本圖形的繪制,但在實(shí)際應(yīng)用中,經(jīng)常會(huì)需要繪制一些復(fù)雜的圖形,而Canvas卻并沒有提供繪制這些圖案的API,所以這時(shí)候就需要使用多種繪圖方法來繪制這些圖形。以下是作為示例所繪制的一個(gè)簡單的自定義圖形“微笑表情”

          在HTML中微笑表情繪制方法代碼

          在谷歌瀏覽器中顯示預(yù)覽效果如圖所示:

          在HTML中微笑表情繪制方法效果預(yù)覽

          日常生活中可以見到很多不規(guī)則的圖形,你可以想到些什么圖形呢?大家可以嘗試一下利用已經(jīng)學(xué)了的知識把自己想到的一些簡單形狀繪制出來。祝大家:身體健康、生活愉快!


          主站蜘蛛池模板: 亚洲一区二区成人| 国产精品美女一区二区三区| 精品视频一区在线观看| 一区二区精品在线| 日韩一区二区精品观看| 麻豆国产一区二区在线观看| 波多野结衣的AV一区二区三区 | 国产剧情一区二区| 免费无码毛片一区二区APP| 亚洲国产精品一区二区九九| 午夜一区二区在线观看| 精品国产一区二区三区不卡| 亲子乱AV视频一区二区| 国产成人精品一区二三区在线观看 | 日韩一区二区三区在线精品| 精品一区二区三区免费毛片爱| 国产视频一区在线观看| 久久久久人妻一区精品| 国产综合无码一区二区三区| www一区二区三区| 国产激情一区二区三区成人91| 波多野结衣在线观看一区| 国产精品久久久久一区二区| 国产精品综合一区二区三区| 成人H动漫精品一区二区| 亚洲精品日韩一区二区小说| 亚洲AV无码片一区二区三区| 亚洲AV日韩AV一区二区三曲| 成人国产精品一区二区网站公司| 亚洲欧美日韩国产精品一区 | 久久国产午夜精品一区二区三区| 一区在线观看视频| 亚洲性日韩精品一区二区三区| 国产aⅴ一区二区三区| 精品国产一区二区三区2021| 日本免费电影一区二区| 国产精品亚洲午夜一区二区三区| 亚洲Av无码国产一区二区| 国产精品一区二区AV麻豆| 久久久久女教师免费一区| 日本在线一区二区|