Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 欧美毛片在线观看,久草精品在线观看,99久久成人国产精品免费

          整合營(yíng)銷(xiāo)服務(wù)商

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

          免費(fèi)咨詢(xún)熱線(xiàn):

          python基礎(chǔ)綜合案例 數(shù)據(jù)可視化-折線(xiàn)圖可視化

          python基礎(chǔ)綜合案例 數(shù)據(jù)可視化-折線(xiàn)圖可視化

          線(xiàn)圖

          JSON是一種輕量級(jí)的數(shù)據(jù)交互格式,本質(zhì)上是一個(gè)帶有特定格式的字符串。

          JSON就是一種在各個(gè)編程語(yǔ)言中流通的數(shù)據(jù)格式,負(fù)責(zé)不同編程語(yǔ)言中的數(shù)據(jù)傳遞和交互。

          python數(shù)據(jù)和json數(shù)據(jù)的相互轉(zhuǎn)化

          # 導(dǎo)入json模塊
          import json
          
          # 準(zhǔn)備符合json格式要求的python數(shù)據(jù)
          data=[{"name":"aa","age":16},{"name":"bb","age":18}]
          
          # 通過(guò)json.dumps(data)方法把python數(shù)據(jù)轉(zhuǎn)化為了json數(shù)據(jù)
          data=json.dumps(data)
          # 如果有中文可以帶上:ensure_ascii=False參數(shù)確保中文正常轉(zhuǎn)換
          
          # 通過(guò)json.loads(data)方法把json數(shù)據(jù)轉(zhuǎn)化為了python數(shù)據(jù)
          data=json.loads(data)

          開(kāi)發(fā)可視化圖表使用的技術(shù)棧:pyecharts包

          官方網(wǎng)站:pyecharts.org

          查看官方示例

          官方畫(huà)廊:https://gallery.pyecharts.org/#/README

          模塊安裝:pip install pyecharts


          pyecharts入門(mén)

          基礎(chǔ)折線(xiàn)圖

          # 導(dǎo)包,導(dǎo)入Line功能構(gòu)建折線(xiàn)圖對(duì)象
          from pyecharts.charts import Line
          
          # 得到折線(xiàn)圖對(duì)象
          line=Line()
          # 添加x軸數(shù)據(jù)
          line.add_xaxis(["中國(guó)","美國(guó)","英國(guó)"])
          # 添加y軸數(shù)據(jù)
          line.add_yaxis("GDP",[30, 20 ,10])
          # 生成圖表
          line.render()

          運(yùn)行后,

          生成了一個(gè)html文件,用瀏覽器打開(kāi):


          折線(xiàn)圖基本構(gòu)建完成~

          全局配置選項(xiàng) set_global_opts

          TitleOpts:標(biāo)題配置項(xiàng)

          LegendOpts:圖例配置項(xiàng)

          ToolboxOpts:工具箱配置項(xiàng)

          VisualMapOpts:視覺(jué)映射配置項(xiàng)

          TooltipOpts:提示框配置項(xiàng)

          DataZoomOpts:區(qū)域縮放配置項(xiàng)

          # 設(shè)置全局配置項(xiàng)set_global_opts來(lái)設(shè)置
          line.set_global_opts(
              title_opts=TitleOpts(title='GDP展示', pos_left='center', pos_bottom='1%'),
              legend_opts=LegendOpts(is_show=True),
              toolbox_opts=ToolboxOpts(is_show=True),
              visualmap_opts=VisualMapOpts(is_show=True),
              tooltip_opts=TooltipOpts(is_show=True),
          )

          設(shè)置過(guò)后的折線(xiàn)圖:


          數(shù)據(jù)處理

          json原始數(shù)據(jù)格式化 網(wǎng)站ab173.com



          """
          演示可視化需求1:折線(xiàn)圖開(kāi)發(fā)
          """
          import json
          from pyecharts.charts import Line
          from pyecharts.options import TitleOpts, LabelOpts
          
          # 處理數(shù)據(jù)
          f_us=open("D:/rfpython/美國(guó).txt", "r", encoding="UTF-8")
          us_data=f_us.read()   # 美國(guó)的全部?jī)?nèi)容
          
          f_jp=open("D:/rfpython/日本.txt", "r", encoding="UTF-8")
          jp_data=f_jp.read()   # 日本的全部?jī)?nèi)容
          
          f_in=open("D:/rfpython/印度.txt", "r", encoding="UTF-8")
          in_data=f_in.read()   # 印度的全部?jī)?nèi)容
          
          # 去掉不合JSON規(guī)范的開(kāi)頭
          us_data=us_data.replace("jsonp_1629344292311_69436(", "")
          jp_data=jp_data.replace("jsonp_1629350871167_29498(", "")
          in_data=in_data.replace("jsonp_1629350745930_63180(", "")
          
          # 去掉不合JSON規(guī)范的結(jié)尾
          us_data=us_data[:-2]
          jp_data=jp_data[:-2]
          in_data=in_data[:-2]
          
          # JSON轉(zhuǎn)Python字典
          us_dict=json.loads(us_data)
          jp_dict=json.loads(jp_data)
          in_dict=json.loads(in_data)
          
          # 獲取trend key
          us_trend_data=us_dict['data'][0]['trend']
          jp_trend_data=jp_dict['data'][0]['trend']
          in_trend_data=in_dict['data'][0]['trend']
          
          # 獲取日期數(shù)據(jù),用于x軸,取2020年(到314下標(biāo)結(jié)束)
          us_x_data=us_trend_data['updateDate'][:314]
          jp_x_data=jp_trend_data['updateDate'][:314]
          in_x_data=in_trend_data['updateDate'][:314]
          
          # 獲取確認(rèn)數(shù)據(jù),用于y軸,取2020年(到314下標(biāo)結(jié)束)
          us_y_data=us_trend_data['list'][0]['data'][:314]
          jp_y_data=jp_trend_data['list'][0]['data'][:314]
          in_y_data=in_trend_data['list'][0]['data'][:314]
          
          # 生成圖表
          line=Line()       # 構(gòu)建折線(xiàn)圖對(duì)象
          # 添加x軸數(shù)據(jù)
          line.add_xaxis(us_x_data)   # x軸是公用的,所以使用一個(gè)國(guó)家的數(shù)據(jù)即可
          # 添加y軸數(shù)據(jù)
          line.add_yaxis("美國(guó)確診人數(shù)", us_y_data, label_opts=LabelOpts(is_show=False))     # 添加美國(guó)的y軸數(shù)據(jù)
          line.add_yaxis("日本確診人數(shù)", jp_y_data, label_opts=LabelOpts(is_show=False))     # 添加日本的y軸數(shù)據(jù)
          line.add_yaxis("印度確診人數(shù)", in_y_data, label_opts=LabelOpts(is_show=False))     # 添加印度的y軸數(shù)據(jù)
          
          # 設(shè)置全局選項(xiàng)
          line.set_global_opts(
              # 標(biāo)題設(shè)置
              title_opts=TitleOpts(title="2020年美日印三國(guó)確診人數(shù)對(duì)比折線(xiàn)圖", pos_left="center", pos_bottom="1%")
          )
          
          # 調(diào)用render方法,生成圖表
          line.render()
          # 關(guān)閉文件對(duì)象
          f_us.close()
          f_jp.close()
          f_in.close()

          生成折線(xiàn)圖:


          章涵蓋

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

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

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

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

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


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

          4.1 創(chuàng)建軸

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

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

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

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

          警告

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

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

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

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

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

          請(qǐng)注意,數(shù)據(jù)類(lèi)型可能不明確,并且 d3.autoType 有時(shí)會(huì)選擇錯(cuò)誤的類(lèi)型。因此,在數(shù)據(jù)數(shù)組完全加載后仔細(xì)檢查數(shù)據(jù)數(shù)組非常重要。在下面的代碼片段中,我們使用 JavaScript Promise 訪(fǎng)問(wèn)加載的數(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 來(lái)訪(fǎng)問(wèn)數(shù)據(jù)集,因?yàn)榧虞d數(shù)據(jù)是一個(gè)異步過(guò)程(如果您需要復(fù)習(xí)有關(guān)使用 D3 加載和訪(fǎng)問(wèn)數(shù)據(jù)的信息,請(qǐng)參閱第 3 章)。但是現(xiàn)在我們知道我們的數(shù)據(jù)集已完全加載并正確格式化,我們可以開(kāi)始構(gòu)建圖表了。

          文件折線(xiàn)圖.js已經(jīng)包含一個(gè)名為 drawLineChart() ,我們將在其中創(chuàng)建折線(xiàn)圖。在 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)簽和圖例保留圖表周?chē)目臻g。該約定使用四個(gè)邊距:圖表的上方、右側(cè)、下方和左側(cè),如圖 4.3 所示。通過(guò)說(shuō)明這些邊距,我們可以知道圖表核心剩余區(qū)域的位置和大小,我們稱(chēng)之為內(nèi)部圖表。

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


          邊距值在邊距對(duì)象中聲明,該對(duì)象由上邊距、右邊距、下邊距和左邊距組成。讓我們?yōu)檎劬€(xiàn)圖創(chuàng)建邊距對(duì)象。在函數(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ù)的猜測(cè)開(kāi)始,如果需要,稍后會(huì)進(jìn)行調(diào)整。例如,查看圖 4.1 中的折線(xiàn)圖或托管項(xiàng)目 (https://d3js-in-action-third-edition.github.io/new-york-city-weather-2021/)。您將看到可視化效果右側(cè)顯示的標(biāo)簽相對(duì)較長(zhǎng),因此右邊距為 170px。另一方面,軸的標(biāo)簽不占用太多空間;因此,剩余的邊距可以小得多。

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

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


          內(nèi)部圖表的寬度對(duì)應(yīng)于 SVG 容器的寬度減去左側(cè)和右側(cè)的邊距。如果 SVG 容器的寬度為 1000 像素,每邊的邊距分別為 170 和 40 像素,則內(nèi)部圖表仍保留 790 像素。同樣,如果 SVG 容器的高度為 500px,我們通過(guò)從總高度中減去頂部和底部邊距來(lái)計(jì)算內(nèi)部圖表的高度,因此為 435px。通過(guò)使常量 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)在讓我們附加折線(xiàn)圖的 SVG 容器。仍然在函數(shù) drawLineChart() 中工作,將一個(gè) SVG 元素附加到 div 中,其 id 為 折線(xiàn)圖,該元素已經(jīng)存在于文件索引中.html并使用寬度和高度常量設(shè)置其 viewBox 屬性。您還可以臨時(shí)將邊框應(yīng)用于 SVG 元素,以幫助您查看正在工作的區(qū)域。如果您需要復(fù)習(xí)如何將元素附加到 DOM 或設(shè)置其屬性和樣式,請(qǐng)參閱第 2 章。

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

          我們之前已經(jīng)聲明了邊距,這些邊距將決定為內(nèi)部圖表保留的區(qū)域。知道 SVG 容器的坐標(biāo)系從其左上角開(kāi)始,內(nèi)部圖表的每個(gè)元素都必須向保留區(qū)域移動(dòng)。我們可以將內(nèi)部圖表包裝在 SVG 組中并僅對(duì)該組應(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ù)左邊距和上邊距對(duì)組應(yīng)用翻譯。最后,我們將 SVG 組保存到名為 innerChart 的常量中,稍后我們將使用該常量構(gòu)建折線(xiàn)圖。

          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ù)字和類(lèi)別的參考。

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

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

          聲明秤

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

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

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

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

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

          我們的刻度范圍隨著內(nèi)部圖表的高度而擴(kuò)展。由于垂直值是在 SVG 坐標(biāo)系中從上到下計(jì)算的,因此范圍從 innerHeight(內(nèi)圖左下角的位置)開(kāi)始,到零(對(duì)應(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)建折線(xiàn)圖的底部軸,我們調(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)用軸生成器。在下面的代碼片段中,請(qǐng)注意我們?nèi)绾卧谡{(diào)用軸生成器之前使用 innerChart 選擇并將組元素附加到其中。該組的類(lèi)名為 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 所示。我們可以通過(guò)對(duì)包含軸的 SVG 組應(yīng)用平移來(lái)將軸移動(dòng)到圖表底部。請(qǐng)記住,應(yīng)用于組的轉(zhuǎn)換由其所有子級(jí)繼承。在下面的代碼片段中,我們將包含軸元素的組向下平移一個(gè)對(duì)應(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 提供了多種方法來(lái)更改標(biāo)簽的格式。

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

          在下面的代碼片段中,firstDate 成為一個(gè)新的 Date() 對(duì)象。在括號(hào)之間,我們首先聲明年份 ( 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 年,這并沒(méi)有錯(cuò),因?yàn)?Fri Jan 2021 00 00:00:2021 對(duì)應(yīng)于 <> 年的開(kāi)始,但我們更愿意有一個(gè)月份標(biāo)簽。

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


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

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

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

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

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


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

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

          圖 4.9 組成軸的 SVG 元素


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

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

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

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

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

          最后,我們使用 xScale 計(jì)算月初和下個(gè)月開(kāi)始之間的中位數(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 軸,其步驟將更加簡(jiǎn)單。我們使用軸生成器 d3.axisLeft() ,因?yàn)槲覀兿雽?y 軸定位在圖表的左側(cè)。我們將 yScale 作為參數(shù)傳遞,并將軸保存在名為 leftAxis 的常量中。

          const leftAxis=d3.axisLeft(yScale);

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

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

          如果保存項(xiàng)目并在瀏覽器中查看,則會(huì)看到 y 軸已正確定位。我們所要做的就是更改標(biāo)簽的字體并增加它們的大小。在下面的代碼片段中,我們使用類(lèi)軸-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ù)代碼來(lái)設(shè)置軸標(biāo)簽的字體系列和字體大小屬性。在學(xué)習(xí)環(huán)境中,這沒(méi)什么大不了的,但我們通常會(huì)盡量避免在專(zhuān)業(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)該做一件事來(lái)幫助讀者理解我們的圖表。x 軸上的標(biāo)簽是不言自明的,但 y 軸上的標(biāo)簽不是。我們知道它們?cè)?0 到 90 之間變化,但我們不知道它們代表什么。

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

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

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


          4.2 繪制折線(xiàn)圖

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

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

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

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

          請(qǐng)注意我們?nèi)绾温暶饕粋€(gè)名為“茄子”的單獨(dú)顏色常量,并使用它來(lái)設(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è)階段要指出的一件很酷的事情是,即使沒(méi)有注意到它,你現(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)建特定的圖表。相反,您可以通過(guò)生成和組裝構(gòu)建基塊來(lái)構(gòu)建可視化效果。對(duì)于散點(diǎn)圖,這些構(gòu)建基塊可以像兩個(gè)軸和一組圓一樣簡(jiǎn)單。在第 7 章中,我們將構(gòu)建一個(gè)散點(diǎn)圖,其中圓的面積根據(jù)變量而變化。

          散點(diǎn)圖示例


          4.2.1 使用線(xiàn)路生成器

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

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


          讓我們?yōu)檎劬€(xiàn)圖聲明一個(gè)線(xiàn)生成器函數(shù)。我們首先調(diào)用方法 d3.line() 并使用 x() 和 y() 訪(fǎng)問(wèn)器函數(shù)進(jìn)行鏈接。x() 訪(fǎng)問(wèn)器函數(shù)將每個(gè)數(shù)據(jù)點(diǎn)的水平位置作為參數(shù)。如果我們像到目前為止所做的那樣遍歷數(shù)據(jù),我們可以使用參數(shù) d 來(lái)訪(fǎng)問(wèn)每個(gè)基準(zhǔn)面(數(shù)據(jù)集的每一行)。數(shù)據(jù)點(diǎn)的水平位置對(duì)應(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)部圖表,并通過(guò)調(diào)用線(xiàn)生成器并將數(shù)據(jù)集作為參數(shù)傳遞來(lái)設(shè)置其 d 屬性。

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

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

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


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

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

          曲線(xiàn)生成器用作 d3.line() 的訪(fǎng)問(wèn)函數(shù)。要將上一節(jié)中聲明的線(xiàn)生成器轉(zhuǎn)換為曲線(xiàn)生成器,我們只需鏈接 curve() 訪(fǎng)問(wèn)器函數(shù)并傳遞 D3 的一個(gè)插值器。在下面的代碼片段中,我們使用插值器 d3.curveCatmullRom ,它產(chǎn)生一個(gè)三次樣條曲線(xiàn)(通過(guò)每個(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)行曲線(xiàn)插值的折線(xiàn)圖。


          什么是最好的插值?

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

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

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

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

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


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

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


          4.3 繪制區(qū)域

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

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

          4.3.1 使用面積生成器

          讓我們首先聲明一個(gè)區(qū)域生成器函數(shù),并將其存儲(chǔ)在名為 areaGenerator 的常量中。正如您在以下代碼片段中觀察到的那樣,區(qū)域生成器至少需要三個(gè)訪(fǎng)問(wèn)器函數(shù)。第一個(gè) x() 負(fù)責(zé)計(jì)算數(shù)據(jù)點(diǎn)的水平位置,與線(xiàn)生成器完全相同。但是現(xiàn)在,我們不僅有一組數(shù)據(jù)點(diǎn),而是兩組:一個(gè)沿著區(qū)域的下邊緣,另一個(gè)在其上邊緣,因此訪(fǎng)問(wèn)器函數(shù) y0() 和 y1() 。請(qǐng)注意,在我們的例子中,區(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è)或更多訪(fǎng)問(wèn)器函數(shù)結(jié)合使用。為了繪制最低和最高溫度之間的面積,我們使用 x()、y0() 和 y1()。第一個(gè)計(jì)算每個(gè)數(shù)據(jù)點(diǎn)的水平位置,第二個(gè)計(jì)算數(shù)據(jù)點(diǎn)在下邊界上的垂直位置,這里是最低溫度,第三個(gè)是數(shù)據(jù)點(diǎn)在上邊緣的垂直位置,這里是最高溫度。


          正如我們對(duì)折線(xiàn)圖所做的那樣,通過(guò)將 curve() 訪(fǎng)問(wèn)器函數(shù)鏈接到面積生成器,將區(qū)域的邊界插值為曲線(xiàn)。這里我們也使用相同的曲線(xiàn)插值器函數(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ū)域和折線(xiàn)圖之間的對(duì)比度足夠。請(qǐng)注意,茄子常數(shù)的聲明需要在我們使用它來(lái)設(shè)置區(qū)域的填充之前進(jìn)行。

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

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


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

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


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

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

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

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

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

          對(duì)于垂直位置,我們還沒(méi)有一個(gè)常數(shù)來(lái)為我們提供最后一個(gè)溫度值。盡管如此,我們?nèi)匀豢梢允褂?data[data.length - 1] 找到數(shù)據(jù)集中的最后一行,并使用點(diǎn)符號(hào)來(lái)訪(fǎng)問(wè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)簽的底部與折線(xiàn)圖上最后一個(gè)數(shù)據(jù)點(diǎn)的中心垂直對(duì)齊。默認(rèn)情況下,SVG 文本的基線(xiàn)位于文本底部,如圖 4.22 所示。我們可以使用主導(dǎo)基線(xiàn)屬性來(lái)更改此設(shè)置。在下面的代碼片段中,我們給此屬性一個(gè)值 中間 ,以將基線(xiàn)移動(dòng)到文本的垂直中心。

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

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

          最后,在代碼段中,您將看到我們?cè)跇?biāo)簽中添加了一條線(xiàn),在該區(qū)域的向下突起和標(biāo)簽之間跟蹤,以闡明標(biāo)簽代表的內(nèi)容。您可以在圖 4.23 中看到它的外觀。同樣,我們使用刻度來(lái)計(jì)算直線(xiàn)的 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);

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

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


          4.4 繪制圓弧

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

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

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

          首先,打開(kāi)文件弧.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)更多說(shuō)明,請(qǐng)參閱第 3 章,有關(guān) d4.autoType 的討論,請(qǐng)參閱本章的第 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í)保留其縱橫比。請(qǐng)注意,我們將 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)換為所需位置。不過(guò),這次的策略有點(diǎn)不同。我們不需要為軸或標(biāo)簽保留空間,因此我們可以省略邊距約定。但是,與迄今為止構(gòu)建的所有可視化相反,弧位于極坐標(biāo)系中,而不是笛卡爾坐標(biāo)系中,后者的行為略有不同。

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

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

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


          由于元素位于極坐標(biāo)系中的原點(diǎn)周?chē)虼宋覀兛梢哉f(shuō)我們將要構(gòu)建的弧可視化的原點(diǎn)位于 SVG 容器的中心,如圖 4.25 所示。

          圖 4.25 通過(guò)將弧包裝成 SVG 組并將該組轉(zhuǎn)換為 SVG 容器的中心,我們簡(jiǎn)化了一組弧的創(chuàng)建。當(dāng)我們向組追加弧時(shí),它們的位置將自動(dòng)相對(duì)于圖表的中心,這對(duì)應(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)建弧線(xiàn)之前,我們需要做最后一件事:計(jì)算圖表上有降水的日子所采用的角度。使用 D3 創(chuàng)建餅圖或圓環(huán)圖時(shí),我們通常使用餅圖布局生成器處理此類(lèi)計(jì)算,我們將在下一章中介紹。但是由于我們?cè)谶@里只畫(huà)兩個(gè)弧線(xiàn),所以數(shù)學(xué)很容易。

          首先,我們可以使用數(shù)據(jù)集的 length 屬性知道 2021 年的總天數(shù),即 365。然后,我們通過(guò)過(guò)濾數(shù)據(jù)集來(lái)查找有降水的天數(shù),以?xún)H保留降水量大于零的天數(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);

          然后,我們可以通過(guò)將這個(gè)數(shù)字乘以 360 度(一個(gè)完整圓的度數(shù))來(lái)計(jì)算對(duì)應(yīng)于降水天數(shù)的角度,得到 126 度。我們從度開(kāi)始,因?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ā)生器

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

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

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

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

          圖 4.26 電弧發(fā)生器使用多個(gè)訪(fǎng)問(wèn)器函數(shù)來(lái)計(jì)算電弧的 d 屬性。在這里,我們?cè)谏善髀暶髌陂g設(shè)置其內(nèi)半徑、外半徑、填充角度和角半徑。我們將在將路徑元素附加到圖表時(shí)傳遞每個(gè)弧的開(kāi)始和結(jié)束角度。


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

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

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

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

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

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

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

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


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

          圖 4.28 繪制弧線(xiàn)的步驟。


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

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

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

          在下面的代碼片段中,我們?cè)谇懊娉跏蓟幕“l(fā)生器函數(shù)上調(diào)用方法。這一次,我們將它與 startAngle() 和 endAngle() 訪(fǎng)問(wèn)器函數(shù)鏈接起來(lái),分別將它們傳遞代表有降水的日子的弧的開(kāi)始角和結(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è)代碼段中,我們通過(guò)向內(nèi)部圖表追加文本元素來(lái)創(chuàng)建標(biāo)簽。為了使標(biāo)簽包含“%”符號(hào),我們使用方法 d3.format(“.0%”) ,后跟括號(hào)中的值。此方法便于以特定方式(如貨幣、百分比和指數(shù))格式化數(shù)字,或?yàn)檫@些數(shù)字添加特定后綴,如“M”表示百萬(wàn)或“μ”表示微型。您可以在模塊 d3 格式 (https://github.com/d3/d3-format) 中找到所有可用格式的詳細(xì)列表。

          然后,我們使用質(zhì)心數(shù)組中返回的第一個(gè)和第二個(gè)值設(shè)置 x 和 y 屬性。請(qǐng)注意我們?nèi)绾卧O(shè)置文本錨點(diǎn)和主要基線(xià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 繪制線(xiàn)條、面積和弧線(xiàn)了!在下一章中,我們將使用布局生成器將這些形狀提升到另一個(gè)層次。

          4.5 小結(jié)

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

          # 一、前言

          折線(xiàn)圖目前應(yīng)用最廣的也是用來(lái)繪制各種軌跡,折線(xiàn)圖其實(shí)就是后面動(dòng)態(tài)軌跡圖、飛機(jī)航線(xiàn)圖的前身,公用的一個(gè)方法addPolyline,折線(xiàn)圖可以設(shè)置顏色、粗細(xì)、透明度等屬性,如果開(kāi)啟了懸浮繪圖工具欄,也可以直接單擊工具欄中的折線(xiàn)圖繪制工具,直接動(dòng)態(tài)繪制。

          ## 二、功能特點(diǎn)

          1. 同時(shí)支持在線(xiàn)地圖和離線(xiàn)地圖兩種模式。

          2. 同時(shí)支持webkit內(nèi)核、webengine內(nèi)核、miniblink內(nèi)核、IE內(nèi)核。

          3. 支持設(shè)置多個(gè)標(biāo)注點(diǎn),信息包括名稱(chēng)、地址、經(jīng)緯度。

          4. 可設(shè)置地圖是否可單擊、拖動(dòng)、鼠標(biāo)滾輪縮放。

          5. 可設(shè)置協(xié)議版本、秘鑰、主題樣式、中心坐標(biāo)、中心城市、地理編碼位置等。

          6. 可設(shè)置地圖縮放比例和級(jí)別,縮略圖、比例尺、路況信息等控件的可見(jiàn)。

          7. 支持地圖交互,比如鼠標(biāo)按下獲取對(duì)應(yīng)位置的經(jīng)緯度。

          8. 支持查詢(xún)路線(xiàn),可設(shè)置起點(diǎn)位置、終點(diǎn)位置、路線(xiàn)模式、路線(xiàn)方式、路線(xiàn)方案(最少時(shí)間、最少換乘、最少步行、不乘地鐵、最短距離、避開(kāi)高速)。

          9. 可顯示點(diǎn)線(xiàn)面工具,可直接在地圖上劃線(xiàn)、點(diǎn)、矩形、圓形等。

          10. 可設(shè)置行政區(qū)劃,指定某個(gè)城市區(qū)域繪制圖層,在線(xiàn)地圖自動(dòng)輸出行政區(qū)劃邊界點(diǎn)集合到j(luò)s文件給離線(xiàn)地圖使用。

          11. 可靜態(tài)或者動(dòng)態(tài)添加多個(gè)覆蓋物。支持點(diǎn)、折線(xiàn)、多邊形、矩形、圓形、弧線(xiàn)、點(diǎn)聚合等。

          12. 提供函數(shù)接口處理經(jīng)緯度解析成地址和地址解析成經(jīng)緯度坐標(biāo)。

          13. 提供的demo直接可以單獨(dú)選點(diǎn)執(zhí)行對(duì)應(yīng)的處理比如路線(xiàn)查詢(xún)。

          14. 可以拿到路線(xiàn)查詢(xún)到的點(diǎn)坐標(biāo)信息集合,比如用于機(jī)器人坐標(biāo)導(dǎo)航等。

          15. 封裝了豐富的函數(shù)比如刪除指定點(diǎn)和所有點(diǎn),刪除指定覆蓋物和所有覆蓋物等。

          16. 標(biāo)注點(diǎn)彈框信息可以自定義內(nèi)容,標(biāo)準(zhǔn)html格式。

          17. 標(biāo)注點(diǎn)單擊事件可選 0-不處理 1-自己彈框 2-發(fā)送信號(hào)。

          18. 標(biāo)注點(diǎn)可設(shè)置動(dòng)畫(huà)效果 0-不處理 1-跳動(dòng) 2-墜落

          19. 標(biāo)注點(diǎn)可設(shè)置本地圖片文件等。

          20. 函數(shù)接口友好和統(tǒng)一,使用簡(jiǎn)單方便,就一個(gè)類(lèi)。

          21. 支持js動(dòng)態(tài)交互添加點(diǎn)、刪除點(diǎn)、清空點(diǎn)、重置點(diǎn),不需要刷新頁(yè)面。

          22. 支持任意Qt版本、任意系統(tǒng)、任意編譯器。

          ## 三、體驗(yàn)地址

          1. 體驗(yàn)地址:[https://pan.baidu.com/s/1ZxG-oyUKe286LPMPxOrO2A](https://pan.baidu.com/s/1ZxG-oyUKe286LPMPxOrO2A) 提取碼:o05q 文件名:bin_map.zip

          2. 國(guó)內(nèi)站點(diǎn):[https://gitee.com/feiyangqingyun](https://gitee.com/feiyangqingyun)

          3. 國(guó)際站點(diǎn):[https://github.com/feiyangqingyun](https://github.com/feiyangqingyun)

          4. 個(gè)人主頁(yè):[https://blog.csdn.net/feiyangqingyun](https://blog.csdn.net/feiyangqingyun)

          5. 知乎主頁(yè):[https://www.zhihu.com/people/feiyangqingyun/](https://www.zhihu.com/people/feiyangqingyun/)

          ## 四、效果圖

          ## 五、相關(guān)代碼


          主站蜘蛛池模板: 国产在线无码视频一区| 国产嫖妓一区二区三区无码| 在线精品自拍亚洲第一区| 香蕉视频一区二区三区| 无码精品视频一区二区三区| 国产精品一区不卡| 日本伊人精品一区二区三区| 国产成人高清亚洲一区91 | 麻豆一区二区在我观看| 国产视频一区在线播放| 国产日韩精品一区二区三区在线 | 风间由美在线亚洲一区| 国产一区二区三区免费在线观看| 日本精品视频一区二区| 国产剧情国产精品一区| 日韩一区二区在线播放| 成人精品一区久久久久| 亚洲乱码国产一区三区| 国产一区二区三区免费在线观看| 精品国产乱子伦一区二区三区| 无码精品人妻一区| 色窝窝免费一区二区三区| 亚洲av日韩综合一区在线观看| 欧美激情国产精品视频一区二区| 国模精品视频一区二区三区| 一区二区三区在线观看| 3d动漫精品啪啪一区二区免费 | 国产午夜精品一区理论片飘花| 色天使亚洲综合一区二区| 内射白浆一区二区在线观看| 久久久老熟女一区二区三区| 精品中文字幕一区二区三区四区 | 中文字幕久久亚洲一区| 精品一区精品二区| 亚洲日韩一区二区三区| 在线观看一区二区精品视频| 无码精品黑人一区二区三区 | 亚洲午夜精品一区二区公牛电影院 | 春暖花开亚洲性无区一区二区| 中文字幕一区二区三区精华液 | 国产精品99精品一区二区三区|