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
JSON是一種輕量級的數(shù)據(jù)交互格式,本質(zhì)上是一個帶有特定格式的字符串。
JSON就是一種在各個編程語言中流通的數(shù)據(jù)格式,負責不同編程語言中的數(shù)據(jù)傳遞和交互。
python數(shù)據(jù)和json數(shù)據(jù)的相互轉(zhuǎn)化
# 導入json模塊
import json
# 準備符合json格式要求的python數(shù)據(jù)
data=[{"name":"aa","age":16},{"name":"bb","age":18}]
# 通過json.dumps(data)方法把python數(shù)據(jù)轉(zhuǎn)化為了json數(shù)據(jù)
data=json.dumps(data)
# 如果有中文可以帶上:ensure_ascii=False參數(shù)確保中文正常轉(zhuǎn)換
# 通過json.loads(data)方法把json數(shù)據(jù)轉(zhuǎn)化為了python數(shù)據(jù)
data=json.loads(data)
開發(fā)可視化圖表使用的技術(shù)棧:pyecharts包
官方網(wǎng)站:pyecharts.org
查看官方示例
官方畫廊:https://gallery.pyecharts.org/#/README
模塊安裝:pip install pyecharts
pyecharts入門
基礎(chǔ)折線圖
# 導包,導入Line功能構(gòu)建折線圖對象
from pyecharts.charts import Line
# 得到折線圖對象
line=Line()
# 添加x軸數(shù)據(jù)
line.add_xaxis(["中國","美國","英國"])
# 添加y軸數(shù)據(jù)
line.add_yaxis("GDP",[30, 20 ,10])
# 生成圖表
line.render()
運行后,
生成了一個html文件,用瀏覽器打開:
折線圖基本構(gòu)建完成~
全局配置選項 set_global_opts
TitleOpts:標題配置項
LegendOpts:圖例配置項
ToolboxOpts:工具箱配置項
VisualMapOpts:視覺映射配置項
TooltipOpts:提示框配置項
DataZoomOpts:區(qū)域縮放配置項
# 設(shè)置全局配置項set_global_opts來設(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è)置過后的折線圖:
數(shù)據(jù)處理
json原始數(shù)據(jù)格式化 網(wǎng)站ab173.com
"""
演示可視化需求1:折線圖開發(fā)
"""
import json
from pyecharts.charts import Line
from pyecharts.options import TitleOpts, LabelOpts
# 處理數(shù)據(jù)
f_us=open("D:/rfpython/美國.txt", "r", encoding="UTF-8")
us_data=f_us.read() # 美國的全部內(nèi)容
f_jp=open("D:/rfpython/日本.txt", "r", encoding="UTF-8")
jp_data=f_jp.read() # 日本的全部內(nèi)容
f_in=open("D:/rfpython/印度.txt", "r", encoding="UTF-8")
in_data=f_in.read() # 印度的全部內(nèi)容
# 去掉不合JSON規(guī)范的開頭
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下標結(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]
# 獲取確認數(shù)據(jù),用于y軸,取2020年(到314下標結(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)建折線圖對象
# 添加x軸數(shù)據(jù)
line.add_xaxis(us_x_data) # x軸是公用的,所以使用一個國家的數(shù)據(jù)即可
# 添加y軸數(shù)據(jù)
line.add_yaxis("美國確診人數(shù)", us_y_data, label_opts=LabelOpts(is_show=False)) # 添加美國的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è)置全局選項
line.set_global_opts(
# 標題設(shè)置
title_opts=TitleOpts(title="2020年美日印三國確診人數(shù)對比折線圖", pos_left="center", pos_bottom="1%")
)
# 調(diào)用render方法,生成圖表
line.render()
# 關(guān)閉文件對象
f_us.close()
f_jp.close()
f_in.close()
生成折線圖:
您已經(jīng)熟悉我們使用和組合用于制作數(shù)據(jù)可視化的常見 SVG 形狀:線條、矩形和圓形。您甚至已經(jīng)使用矩形從頭開始創(chuàng)建了條形圖。但是我們可以用原始形狀畫出的東西太多了。為了創(chuàng)建更復雜的可視化,我們通常轉(zhuǎn)向 SVG 路徑。正如我們在第 1 章中所討論的,SVG 路徑是所有 SVG 元素中最靈活的,幾乎可以采用任何形式。我們在 D3 項目中廣泛使用它們,最簡單的例子是在折線圖中繪制線條和曲線或在圓環(huán)圖中繪制弧線。
SVG 路徑的形狀由其 d 屬性確定。此屬性由指示路徑的起點和終點、用于更改方向的曲線類型以及路徑是打開還是閉合的命令組成。路徑的 d 屬性可能很快變得又長又復雜。大多數(shù)時候,我們不想自己創(chuàng)作它。這就是 D3 的形狀生成器功能的用武之地!
在本章中,我們將構(gòu)建如圖 4.1 所示的項目:溫度演變的折線圖和一組弧線,可視化 2021 年紐約市降水天數(shù)的百分比。您可以在 https://d3js-in-action-third-edition.github.io/new-york-city-weather-2021/ 在線找到此項目。基礎(chǔ)數(shù)據(jù)來自地下天氣(www.wunderground.com/)。
圖 4.1 我們將在本章中構(gòu)建的項目:2021 年紐約市溫度演變的折線圖和一組顯示降水天數(shù)百分比的弧線圖。
我們將使用 D3 的形狀生成器函數(shù)創(chuàng)建這兩個可視化效果。但在開始之前,我們將討論 D3 的邊距約定以及如何向圖表添加軸。
開發(fā)數(shù)據(jù)可視化通常需要提前規(guī)劃如何使用 SVG 容器中的可用空間。首先開始玩很酷的東西是非常誘人的,也就是可視化的核心,但相信我們。一點點的準備可以為您節(jié)省大量的執(zhí)行時間。所有編程任務(wù)都是如此,在一般生活中也是如此!在此規(guī)劃階段,我們不僅要考慮圖表本身,還要考慮使圖表可讀的互補元素,例如軸、標簽和圖例。
在本節(jié)中,我們將介紹邊距約定,這是一種便于為這些不同元素分配空間的方法。然后,我們將討論如何向可視化添加軸以及組成 D3 軸的多個 SVG 元素。我們將這些概念應(yīng)用于圖 4.1 所示的折線圖。
在我們開始之前,請轉(zhuǎn)到第 4 章的代碼文件。您可以從本書的 Github 存儲庫下載它們(如果您還沒有 (https://github.com/d3js-in-action-third-edition/code-files)。在名為 chapter_04 的文件夾中,代碼文件按節(jié)進行組織。要開始本章的練習,請在代碼編輯器中打開 4.1-Margin_convention_and_axes/start 文件夾并啟動本地 Web 服務(wù)器。如果您需要有關(guān)設(shè)置本地開發(fā)環(huán)境的幫助,請參閱附錄 A。
您可以在位于本章代碼文件根目錄下的自述文件中找到有關(guān)項目文件夾結(jié)構(gòu)的更多詳細信息。
使用本章的代碼文件時,在代碼編輯器中僅打開一個開始文件夾或一個結(jié)束文件夾。如果一次打開章節(jié)的所有文件并使用 Live Server 擴展為項目提供服務(wù),則數(shù)據(jù)文件的路徑將無法按預(yù)期工作。
我們將開始在文件折線圖中工作.js并使用方法 d3.csv() 加載每周溫度數(shù)據(jù)集。
d3.csv("../data/weekly_temperature.csv");
在第 3 章中,我們解釋了 D3 在加載表格數(shù)據(jù)集時執(zhí)行的類型轉(zhuǎn)換會影響值的類型。例如,原始數(shù)據(jù)集中的數(shù)字變成字符串,我們需要將它們變回數(shù)字以方便操作它們。我們已經(jīng)看到 d3.csv() 的回調(diào)函數(shù) ,我們可以逐行訪問數(shù)據(jù),是執(zhí)行此類轉(zhuǎn)換的好地方。在這里,我們將介紹一個小技巧。我們可以調(diào)用方法 d3.autoType ,而不是手動轉(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ù)據(jù)數(shù)組完全加載后仔細檢查數(shù)據(jù)數(shù)組非常重要。在下面的代碼片段中,我們使用 JavaScript Promise 訪問加載的數(shù)據(jù)集,并將其登錄到控制臺,以確認日期被格式化為 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ù)集,因為加載數(shù)據(jù)是一個異步過程(如果您需要復習有關(guān)使用 D3 加載和訪問數(shù)據(jù)的信息,請參閱第 3 章)。但是現(xiàn)在我們知道我們的數(shù)據(jù)集已完全加載并正確格式化,我們可以開始構(gòu)建圖表了。
文件折線圖.js已經(jīng)包含一個名為 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)在準備討論保證金慣例并將其應(yīng)用于我們的圖表!
D3 邊距約定旨在以系統(tǒng)和可重用的方式為軸、標簽和圖例保留圖表周圍的空間。該約定使用四個邊距:圖表的上方、右側(cè)、下方和左側(cè),如圖 4.3 所示。通過說明這些邊距,我們可以知道圖表核心剩余區(qū)域的位置和大小,我們稱之為內(nèi)部圖表。
圖 4.3 D3 邊距約定設(shè)置圖表頂部、右側(cè)、底部和左側(cè)的邊距值。
邊距值在邊距對象中聲明,該對象由上邊距、右邊距、下邊距和左邊距組成。讓我們?yōu)檎劬€圖創(chuàng)建邊距對象。在函數(shù) drawLineChart() 中,聲明一個名為 margin 的常量。如以下代碼片段所示,為上邊距、右邊距、下邊距和左邊距分別指定 40、170、25 和 40px 的值。
const drawLineChart=(partialData)=> {
const margin={top: 40, right: 170, bottom: 25, left: 40};
};
事先確切知道軸和標簽需要多少空間通常是不可能的。我們從一個有根據(jù)的猜測開始,如果需要,稍后會進行調(diào)整。例如,查看圖 4.1 中的折線圖或托管項目 (https://d3js-in-action-third-edition.github.io/new-york-city-weather-2021/)。您將看到可視化效果右側(cè)顯示的標簽相對較長,因此右邊距為 170px。另一方面,軸的標簽不占用太多空間;因此,剩余的邊距可以小得多。
聲明邊距對象后,我們就可以開始考慮 SVG 容器的大小了。知道了 SVG 容器的大小和邊距,我們最終可以計算出兩個新常量,分別名為 innerWidth 和 innerHeight ,它們代表內(nèi)部圖表的寬度和高度。這些尺寸如圖4.4所示。
圖 4.4 知道 SVG 容器的尺寸和邊距,我們可以計算內(nèi)部圖表的寬度和高度。
內(nèi)部圖表的寬度對應(yīng)于 SVG 容器的寬度減去左側(cè)和右側(cè)的邊距。如果 SVG 容器的寬度為 1000 像素,每邊的邊距分別為 170 和 40 像素,則內(nèi)部圖表仍保留 790 像素。同樣,如果 SVG 容器的高度為 500px,我們通過從總高度中減去頂部和底部邊距來計算內(nèi)部圖表的高度,因此為 435px。通過使常量 innerWidth 和 innerHeight 與邊距成正比,我們確保如果我們以后需要更改邊距,它們會自動調(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() 中工作,將一個 SVG 元素附加到 div 中,其 id 為 折線圖,該元素已經(jīng)存在于文件索引中.html并使用寬度和高度常量設(shè)置其 viewBox 屬性。您還可以臨時將邊框應(yīng)用于 SVG 元素,以幫助您查看正在工作的區(qū)域。如果您需要復習如何將元素附加到 DOM 或設(shè)置其屬性和樣式,請參閱第 2 章。
const svg=d3.select("#line-chart")
.append("svg")
.attr("viewBox", `0, 0, ${width}, ${height}`);
我們之前已經(jīng)聲明了邊距,這些邊距將決定為內(nèi)部圖表保留的區(qū)域。知道 SVG 容器的坐標系從其左上角開始,內(nèi)部圖表的每個元素都必須向保留區(qū)域移動。我們可以將內(nèi)部圖表包裝在 SVG 組中并僅對該組應(yīng)用平移,而不是將此置換應(yīng)用于每個元素。如圖 4.5 所示,此策略為內(nèi)部圖表創(chuàng)建了一個新的坐標系。
圖 4.5 應(yīng)用于將包含內(nèi)部圖表的 SVG 組的平移,為內(nèi)部圖表中包含的元素創(chuàng)建新的坐標系。
為了將此策略付諸實施,我們將一個組附加到 SVG 容器。然后,我們根據(jù)左邊距和上邊距對組應(yīng)用翻譯。最后,我們將 SVG 組保存到名為 innerChart 的常量中,稍后我們將使用該常量構(gòu)建折線圖。
const innerChart=svg
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);
保證金約定和此處介紹的策略的主要優(yōu)點是,一旦實施,我們就不再需要考慮它了。我們可以繼續(xù)創(chuàng)建軸和圖表,同時知道為標簽、圖例和其他補充信息保留了一個區(qū)域。
建立邊距約定后,我們準備向圖表添加軸。軸是數(shù)據(jù)可視化的重要組成部分。它們可作為查看者理解所代表的數(shù)字和類別的參考。
如果您查看圖 4.1 中的折線圖或托管項目 (https://d3js-in-action-third-edition.github.io/new-york-city-weather-2021/),您將看到兩個軸。水平軸,也稱為 x 軸,顯示每個月的位置。垂直軸或 y 軸用作以華氏度為單位的溫度的參考。
在 D3 中,我們使用 axis() 組件生成器創(chuàng)建軸。此生成器將比例作為輸入,并返回組成軸的 SVG 元素作為輸出。如果您還記得我們在第 3 章中關(guān)于比例的討論,您就會知道它們將數(shù)據(jù)值映射到屏幕上。例如,對于我們的折線圖,刻度將為我們計算數(shù)據(jù)集中每個日期的水平位置或其相關(guān)溫度的垂直位置。
創(chuàng)建軸的第一步實際上是聲明其比例。首先,我們需要一個水平定位日期的刻度。這正是 D3 的時間尺度 d3.scaleTime() 的作用(有關(guān)選擇 D3 尺度的幫助,請參閱附錄 B)。時間尺度是第3章討論的第一類尺度的一部分。它接受連續(xù)輸入并返回連續(xù)輸出。時間尺度的行為與第3章中使用的線性尺度非常相似,唯一的區(qū)別是它操縱與時間相關(guān)的數(shù)據(jù)并計算它們在空間中的位置。
讓我們聲明我們的時間刻度并將其命名為 xScale,因為它將負責沿 x 軸定位元素。我們規(guī)模的范圍從數(shù)據(jù)集中的第一個日期延伸到最后一個日期。在下面的代碼片段中,我們使用 d3.min() 和 d3.max() 來查找這些值。
刻度所涵蓋的范圍隨著內(nèi)部圖表中可用的水平空間而擴展(見圖4.5)。在內(nèi)部圖表的坐標系中,這意味著范圍從零擴展到之前計算的 innerWidth。如果您需要復習聲明 D3 刻度的域和范圍,請參閱第 3 章。
const firstDate=d3.min(data, d=> d.date);
const lastDate=d3.max(data, d=> d.date);
const xScale=d3.scaleTime()
.domain([firstDate, lastDate])
.range([0, innerWidth]);
沿y軸分布的溫度也需要第一個系列的刻度,具有連續(xù)的輸入和輸出。線性刻度在這里將是完美的,因為我們希望溫度和折線圖上的垂直位置是線性比例的。
在下面的代碼片段中,我們聲明了我們的溫標并將其命名為 yScale,因為它將負責沿 y 軸定位元素。在這里,我們希望我們的 y 軸從零開始,因此我們將零作為域的第一個值傳遞。盡管數(shù)據(jù)集中的最低溫度約為 26°F,但從零開始 y 軸通常是一個好主意,在我們的例子中,這將使我們能夠正確看到溫度的演變。但就像生活中的大多數(shù)事情一樣,這不是一個硬性規(guī)則,這個圖表沒有正確或錯誤的答案,特別是因為華氏度的零不是絕對的零。
我們將數(shù)據(jù)集中的最高溫度作為域的第二個值傳遞。我們通過使用函數(shù) d3.max() 查詢數(shù)據(jù)集中的列max_temp_F來找到此值。
我們的刻度范圍隨著內(nèi)部圖表的高度而擴展。由于垂直值是在 SVG 坐標系中從上到下計算的,因此范圍從 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 有四個軸生成器:axisTop()、axisRight()、axisBottom() 和 axisLeft() ,它們分別創(chuàng)建頂部、右側(cè)、底部和左側(cè)軸的組件。它們都是 d3 軸模塊 (https://github.com/d3/d3-axis) 的一部分。
我們提到軸生成器函數(shù)將刻度作為輸入。例如,要創(chuàng)建折線圖的底部軸,我們調(diào)用生成器 axisBottom() 并將 xScale 作為參數(shù)傳遞,因為此刻度負責沿底部軸分布數(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 默認情況下,D3 軸在所選內(nèi)容的原點生成,此處為內(nèi)部圖表的左上角。我們需要應(yīng)用翻譯將它們移動到所需的位置。
在瀏覽器中查看生成的軸。默認情況下,D3 軸顯示在所選內(nèi)容的原點,在本例中為內(nèi)部圖表區(qū)域的左上角,如圖 4.6 所示。我們可以通過對包含軸的 SVG 組應(yīng)用平移來將軸移動到圖表底部。請記住,應(yīng)用于組的轉(zhuǎn)換由其所有子級繼承。在下面的代碼片段中,我們將包含軸元素的組向下平移一個對應(yīng)于內(nèi)部圖表高度的值。
const bottomAxis=d3.axisBottom(xScale);
innerChart
.append("g")
.attr("class", "axis-x")
.attr("transform", `translate(0, ${innerHeight})`)
.call(bottomAxis);
我們要更改的另一件事是軸標簽的格式。默認情況下,D3 調(diào)整軸上的時間表示形式,根據(jù)域顯示小時、天、月或年標簽。但是這種默認格式并不總是提供我們正在尋找的標簽。幸運的是,D3 提供了多種方法來更改標簽的格式。
首先,我們注意到 x 軸有 3 月至 <> 月的標簽,這很好,但沒有 <> 月的標簽。根據(jù)您居住的時區(qū),第一個日期可能不完全是 <> 月 <> 日的午夜,這使 D<> 無法將其識別為我們第一個月的開始。由于我們的數(shù)據(jù)集不是動態(tài)的,因此對 firstDate 變量進行硬編碼是一個合理的解決方案。為此,我們將使用 JavaScript Date() 構(gòu)造函數(shù)。
在下面的代碼片段中,firstDate 成為一個新的 Date() 對象。在括號之間,我們首先聲明年份 ( 2021 年 )、月份 ( 00,因為月份索引為零索引)、日 ( 01 ),并可選擇在它后面跟小時、分鐘和秒 ( 0, 0, 0 )。
const firstDate=new Date(2021, 00, 01, 0, 0, 0);
const lastDate=d3.max(data, d=> d.date);
const xScale=d3.scaleTime()
.domain([firstDate, lastDate])
.range([0, innerWidth]);
如果保存項目,你將看到我們現(xiàn)在在 1 月 2021日的位置有一個標簽。但是標簽只給了我們 01 年,這并沒有錯,因為 Fri Jan 2021 00 00:00:2021 對應(yīng)于 <> 年的開始,但我們更愿意有一個月份標簽。
圖 4.7 默認情況下,D3 調(diào)整軸標簽上的時間表示。在我們的例子中,它表示 1 月 2021日作為 <> 年的開始。這沒有錯,但對于可讀性來說并不理想。
我們可以使用方法 axis.tickFormat() 更改軸標簽的格式,該方法在 d3 軸模塊 (https://github.com/d3/d3-axis) 中可用。刻度是您在軸上看到的短垂直線。它們通常(但不一定)附有勾號標簽。
假設(shè)我們希望刻度標簽是縮寫的月份名稱。在 D3 中,我們可以使用方法 d3.timeFormat() 格式化與時間相關(guān)的值,來自模塊 d3-time-format (https://github.com/d3/d3-time-format)。此方法接受格式作為參數(shù),例如,%b 表示月份名稱的縮寫。您可以在模塊中查看可用格式的完整列表。
在下面的代碼片段中,我們將 tickFormat() 方法鏈接到之前聲明的底部軸,并將時間格式作為參數(shù)傳遞。
const bottomAxis=d3.axisBottom(xScale)
.tickFormat(d3.timeFormat("%b"));
圖 4.8 使用每個月縮寫名稱格式化的底部軸標簽。
我們的標簽現(xiàn)在格式正確!它們標記每個月的開始,這還不錯,但我們可以通過在各自的刻度之間居中月標簽來提高可讀性,以建議每個月從一個刻度延伸到下一個刻度。
要更改刻度標簽的位置,我們首先需要選擇它們。打開瀏覽器的檢查器,仔細查看 D3 為軸生成的 SVG 元素。首先,我們有一個帶有類域的路徑元素,該元素在范圍(或域的表示)上繪制一條水平線。此路徑包括兩個外部刻度,即形狀兩端的短垂直線,如圖 4.9 所示。軸的刻度和標簽由線條和文本元素組成,組織成具有刻度類的 SVG 組。這些 SVG 組沿軸平移以設(shè)置其行和文本元素的位置。軸生成器創(chuàng)建的元素的類型和類是 D3 公共 API 的一部分。您可以使用它們來自定義軸外觀。
圖 4.9 組成軸的 SVG 元素
考慮到這種結(jié)構(gòu),我們可以使用選擇器選擇x軸的所有標簽 “.axis-x 文本” ,這意味著我們使用類軸-x抓取組中的每個文本元素。然后我們執(zhí)行一些調(diào)整。首先,我們使用文本元素的 y 屬性將文本元素向下移動 10px。這種增加的垂直空白將提高可讀性。我們還將他們的字體系列設(shè)置為Roboto,這是我們已經(jīng)在項目中使用的字體。默認情況下,D3 將軸的字體系列設(shè)置為無襯線,防止標簽繼承項目的字體系列。最后,我們將它們的字體大小增加到 14px。
出于關(guān)注點分離的目的,最后兩個樣式調(diào)整最好從CSS文件中處理。但在這里,我們使用 D3 來簡化指令。
d3.selectAll(".axis-x text")
.attr("y", "10px")
.style("font-family", "Roboto, sans-serif")
.style("font-size", "14px");
為了使月份標簽在其相應(yīng)的刻度之間居中,我們將使用 x 屬性。由于每個月都有不同的長度(在 28 到 31 天之間),我們需要為每個標簽找到該月第一天和下個月第一天之間的中位數(shù)位置。請注意,D3 已在 g.axis-x 上將文本錨點屬性設(shè)置為“中間”。
我們知道 D3 附加到每個標簽的數(shù)據(jù)對應(yīng)于該月的第一天。在下面的代碼片段中,我們通過將 JavaScript 方法 getMonth() 應(yīng)用于當前月份或附加到標簽的值來查找下個月。此方法返回一個介于 0 和 11 之間的數(shù)字,0 表示 11 月,<> 表示 <> 月。然后,我們可以通過將年份、下個月和每月的第一天傳遞給 Date() 對象來創(chuàng)建新的 JavaScript 日期。
最后,我們使用 xScale 計算月初和下個月開始之間的中位數(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 軸,月份標簽在各自的刻度之間居中。
那是很多操縱!但希望它能讓您了解我們可以自定義 D3 軸的不同方法。
我們現(xiàn)在將添加 y 軸,其步驟將更加簡單。我們使用軸生成器 d3.axisLeft() ,因為我們想將 y 軸定位在圖表的左側(cè)。我們將 yScale 作為參數(shù)傳遞,并將軸保存在名為 leftAxis 的常量中。
const leftAxis=d3.axisLeft(yScale);
再一次,我們希望將軸附加到內(nèi)部圖表。我們將一個組附加到內(nèi)部圖表選擇中,給它一個 axis-y 類并調(diào)用 leftAxis 。
const leftAxis=d3.axisLeft(yScale);
innerChart
.append("g")
.attr("class", "axis-y")
.call(leftAxis);
如果保存項目并在瀏覽器中查看,則會看到 y 軸已正確定位。我們所要做的就是更改標簽的字體并增加它們的大小。在下面的代碼片段中,我們使用類軸-y 選擇組內(nèi)的所有文本元素。我們使用它們的 x 屬性將它們稍微向左移動以獲得更好的可讀性,并設(shè)置它們的字體系列和字體大小屬性。
d3.selectAll(".axis-y text")
.attr("x", "-5px")
.style("font-family", "Roboto, sans-serif")
.style("font-size", "14px");
您可能已經(jīng)注意到,我們必須重復代碼來設(shè)置軸標簽的字體系列和字體大小屬性。在學習環(huán)境中,這沒什么大不了的,但我們通常會盡量避免在專業(yè)項目中出現(xiàn)這種重復。前面提到的更好的解決方案是從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 軸。
我們已經(jīng)完成了我們的軸,但我們?nèi)匀粦?yīng)該做一件事來幫助讀者理解我們的圖表。x 軸上的標簽是不言自明的,但 y 軸上的標簽不是。我們知道它們在 0 到 90 之間變化,但我們不知道它們代表什么。
我們可以通過向軸添加標簽來解決此問題。在 D3 項目中,標簽只是文本元素,所以我們所要做的就是將文本元素附加到 SVG 容器中。我們將其內(nèi)容設(shè)置為“溫度(°F)”,并將其垂直位置設(shè)置為SVG容器原點下方20px。就是這樣!您的項目現(xiàn)在應(yīng)如圖 4.12 所示。在下一節(jié)中,我們將繪制折線圖。
svg
.append("text")
.text("Temperature (°F)")
.attr("y", 20);
圖 4.12 完成的軸和標簽。
現(xiàn)在,我們已準備好構(gòu)建最常見的數(shù)據(jù)可視化之一:折線圖。折線圖由連接數(shù)據(jù)點的線或插入這些數(shù)據(jù)點的曲線組成。它們通常用于顯示現(xiàn)象隨時間推移的演變。在 D3 中,這些直線和曲線是使用 SVG 路徑元素構(gòu)建的,這些元素的形狀由其 d 屬性確定。在第 1 章中,我們討論了 d 屬性是如何由一系列命令組成的,這些命令指示如何繪制形狀。我們還說過,它很快就會變得復雜。值得慶幸的是,d3 形狀模塊 (https://github.com/d3/d3-shape) 提供了為我們計算 d 屬性的線和曲線生成器函數(shù),簡化了折線圖的創(chuàng)建。
在本節(jié)中,我們將繪制一條線/曲線,顯示 2021 年紐約市平均溫度的演變,就像您在托管項目 (https://d3js-in-action-third-edition.github.io/new-york-city-weather-2021/) 或圖 4.1 中看到的那樣。但首先,讓我們在屏幕上顯示每個數(shù)據(jù)點。雖然這一步對于繪制折線圖不是必需的,但它將幫助我們了解 D3 的線生成器函數(shù)的工作原理。
在函數(shù) drawLineChart() 中工作,我們使用數(shù)據(jù)綁定模式為數(shù)據(jù)集weekly_temperature.csv中的每一行創(chuàng)建一個圓圈。我們將這些圓圈附加到 innerChart 選擇中,并給它們一個 4px 的半徑。然后我們使用x和y尺度計算它們的位置屬性(cx和cy)。
如果你還記得我們從第 3 章開始關(guān)于數(shù)據(jù)綁定的討論,你就知道我們可以使用訪問器函數(shù)訪問綁定到每個圓圈的數(shù)據(jù)。在下面的代碼片段中,d 公開了附加到每個圓的基準面。這些數(shù)據(jù)是一個 JavaScript 對象,我們可以使用點符號訪問日期或平均溫度。如果您需要查看此概念,請參閱第 3.3.1 節(jié)。
請注意我們?nèi)绾温暶饕粋€名為“茄子”的單獨顏色常量,并使用它來設(shè)置圓圈的填充屬性。在這個項目中,我們將重復使用相同的顏色幾次,因此將其放在常量中會很方便。隨意使用您喜歡的任何顏色!
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);
保存您的項目并查看瀏覽器中的圓圈。它們應(yīng)位于 29 到 80°F 之間,并形成圓頂狀形狀,如圖 4.13 所示。
圖 4.13 平均溫度隨時間演變的數(shù)據(jù)點。
在這個階段要指出的一件很酷的事情是,即使沒有注意到它,你現(xiàn)在也知道如何繪制散點圖!散點圖只是一個圖表,顯示沿 x 軸和 y 軸定位的數(shù)據(jù)點集合,并可視化兩個或多個變量之間的關(guān)系。
您知道如何繪制軸,并且知道如何根據(jù)其相關(guān)數(shù)據(jù)在屏幕上定位數(shù)據(jù)點,因此您可以完全構(gòu)建散點圖!這就是D3的酷之處。您不必學習如何創(chuàng)建特定的圖表。相反,您可以通過生成和組裝構(gòu)建基塊來構(gòu)建可視化效果。對于散點圖,這些構(gòu)建基塊可以像兩個軸和一組圓一樣簡單。在第 7 章中,我們將構(gòu)建一個散點圖,其中圓的面積根據(jù)變量而變化。
散點圖示例
現(xiàn)在我們清楚地看到了每個數(shù)據(jù)點的位置,引入 D3 的線生成器會更容易。行生成器 d3.line() 是一個函數(shù),它將每個數(shù)據(jù)點的水平和垂直位置作為輸入,并返回線或折線的 d 屬性,作為輸出傳遞通過這些數(shù)據(jù)點。我們通常用兩個訪問函數(shù) x() 和 y() 鏈接線生成器,分別將數(shù)據(jù)點的水平和垂直位置作為參數(shù),如圖 4.14 所示。
圖 4.14 行生成器 d3.line() 與兩個訪問器函數(shù) x() 和 y() 結(jié)合使用,它們分別將每個數(shù)據(jù)點的水平和垂直位置作為參數(shù)。
讓我們?yōu)檎劬€圖聲明一個線生成器函數(shù)。我們首先調(diào)用方法 d3.line() 并使用 x() 和 y() 訪問器函數(shù)進行鏈接。x() 訪問器函數(shù)將每個數(shù)據(jù)點的水平位置作為參數(shù)。如果我們像到目前為止所做的那樣遍歷數(shù)據(jù),我們可以使用參數(shù) d 來訪問每個基準面(數(shù)據(jù)集的每一行)。數(shù)據(jù)點的水平位置對應(yīng)于它們表示的日期,并使用 xScale() 計算。同樣,數(shù)據(jù)點的垂直位置與當天的平均溫度成正比,并由 yScale() 返回。我們將行生成器函數(shù)存儲在名為 lineGenerator 的常量中,以便稍后可以調(diào)用它。
const lineGenerator=d3.line()
.x(d=> xScale(d.date)) #A
.y(d=> yScale(d.avg_temp_F)); #B
然后,我們將一個路徑元素附加到內(nèi)部圖表,并通過調(diào)用線生成器并將數(shù)據(jù)集作為參數(shù)傳遞來設(shè)置其 d 屬性。
默認情況下,SVG 路徑具有黑色填充。如果我們只想看到一條線,我們需要將填充屬性設(shè)置為 none 并將筆觸屬性設(shè)置為我們選擇的顏色;這里,顏色存儲在茄子常數(shù)中。此筆畫將成為我們的折線圖,如圖 4.15 所示。
innerChart
.append("path")
.attr("d", lineGenerator(data)) #A
.attr("fill", "none")
.attr("stroke", aubergine);
圖 4.15 使用線生成器創(chuàng)建并穿過每個數(shù)據(jù)點的 SVG 路徑,生成折線圖。
在像我們的折線圖這樣的情況下,離散數(shù)據(jù)點覆蓋了整個數(shù)據(jù)范圍,用簡單的線條表示數(shù)據(jù)點是一個很好的解決方案。但有時,我們需要在點之間插值數(shù)據(jù),為此 D3 提供了各種生成曲線的插值函數(shù)。
曲線生成器用作 d3.line() 的訪問函數(shù)。要將上一節(jié)中聲明的線生成器轉(zhuǎn)換為曲線生成器,我們只需鏈接 curve() 訪問器函數(shù)并傳遞 D3 的一個插值器。在下面的代碼片段中,我們使用插值器 d3.curveCatmullRom ,它產(chǎn)生一個三次樣條曲線(通過每個數(shù)據(jù)點并使用三階多項式函數(shù)計算的平滑靈活的形狀)。結(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樣條進行曲線插值的折線圖。
插值會修改數(shù)據(jù)表示,不同的插值函數(shù)會創(chuàng)建不同的可視化效果。數(shù)據(jù)可以通過各種方式可視化,從編程的角度來看,所有這些都是正確的。但是,我們有責任確保我們可視化的信息反映了實際現(xiàn)象。
由于數(shù)據(jù)可視化處理統(tǒng)計原理的可視化表示,因此它受到濫用統(tǒng)計數(shù)據(jù)的所有危險的影響。線條的插值特別容易被誤用,因為它將一條看起來笨拙的線條變成了一條平滑的“自然”線條。
在圖 4.17 中,您可以看到使用不同曲線插值跟蹤的相同折線圖,并了解它們?nèi)绾斡绊懸曈X表示。選擇適當?shù)牟逯岛瘮?shù)在很大程度上取決于您正在使用的數(shù)據(jù)。在我們的例子中,d3.curveBasis低估了溫度的突然變化,而d3.curveBundle旨在拉直曲線并減少其變化,這對于我們的數(shù)據(jù)來說是不夠的。如果我們沒有在圖表上繪制數(shù)據(jù)點,我們就不知道曲線不能準確地表示它們。這就是為什么仔細選擇和測試曲線插值函數(shù)很重要的原因。
另一方面,函數(shù) d3.curveMonotoneX 和 d3.curveCatmullRom 創(chuàng)建緊隨數(shù)據(jù)點的曲線,類似于原始折線圖。d3.curveStep 還可以在上下文適當時提供對數(shù)據(jù)的有趣解釋。圖 4.17 中所示的曲線插值列表并不詳盡,其中一些插值器還接受影響最終曲線形狀的參數(shù)。有關(guān)所有可用選項,請參閱 d3 形狀模塊 (https://github.com/d3/d3-shape)。
圖 4.17 不同的曲線插值及其如何修改數(shù)據(jù)的表示。
您現(xiàn)在知道如何使用 D3 繪制折線圖了!回顧一下,我們首先需要初始化一個線生成器函數(shù)并設(shè)置其 x() 和 y() 訪問器函數(shù)。這些將負責計算每個數(shù)據(jù)點的水平和垂直位置。然后,我們可以通過鏈接 curve() 訪問器函數(shù)并選擇插值來選擇將直線轉(zhuǎn)換為曲線。最后,我們將一個 SVG 路徑元素附加到我們的圖表中,并通過調(diào)用線條生成器并將數(shù)據(jù)作為參數(shù)傳遞來設(shè)置其 d 屬性。在第 7 章中,我們將通過工具提示使此折線圖具有交互性。如果您想立即學習該章節(jié),請隨時直接轉(zhuǎn)到該章節(jié)!
圖 4.18 創(chuàng)建折線圖的步驟
在本節(jié)中,我們將在折線圖后面添加一個區(qū)域,以顯示每個日期的最低和最高溫度之間的范圍。在 D3 中繪制區(qū)域的過程與用于繪制線條的過程非常相似。像線條一樣,區(qū)域是使用 SVG 路徑元素創(chuàng)建的,D3 為我們提供了一個方便的區(qū)域生成器函數(shù), d3.area() ,用于計算該路徑的 d 屬性。
在開始之前需要注意的一件事是,我們希望顯示折線圖后面的區(qū)域。由于元素在屏幕上的繪制順序與它們追加在 SVG 父項中的順序相同,因此應(yīng)在創(chuàng)建折線圖的代碼之前添加用于繪制區(qū)域的代碼。
讓我們首先聲明一個區(qū)域生成器函數(shù),并將其存儲在名為 areaGenerator 的常量中。正如您在以下代碼片段中觀察到的那樣,區(qū)域生成器至少需要三個訪問器函數(shù)。第一個 x() 負責計算數(shù)據(jù)點的水平位置,與線生成器完全相同。但是現(xiàn)在,我們不僅有一組數(shù)據(jù)點,而是兩組:一個沿著區(qū)域的下邊緣,另一個在其上邊緣,因此訪問器函數(shù) y0() 和 y1() 。請注意,在我們的例子中,區(qū)域下邊緣和上邊緣的數(shù)據(jù)點共享相同的水平位置。
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ū)域的下限和上限,以及面積生成器如何計算與該區(qū)域相關(guān)的數(shù)據(jù)。
圖 4.19 面積生成器 d3.area() 與三個或更多訪問器函數(shù)結(jié)合使用。為了繪制最低和最高溫度之間的面積,我們使用 x()、y0() 和 y1()。第一個計算每個數(shù)據(jù)點的水平位置,第二個計算數(shù)據(jù)點在下邊界上的垂直位置,這里是最低溫度,第三個是數(shù)據(jù)點在上邊緣的垂直位置,這里是最高溫度。
正如我們對折線圖所做的那樣,通過將 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);
一旦面積生成器準備就緒,我們需要做的就是將 SVG 路徑元素附加到內(nèi)部圖表中。為了設(shè)置其 d 屬性,我們調(diào)用區(qū)域生成器并將數(shù)據(jù)集作為參數(shù)傳遞。其余的純粹與美學有關(guān)。我們將填充屬性設(shè)置為之前聲明的茄子色常數(shù),并將填充不透明度設(shè)置為 20%,以確保區(qū)域和折線圖之間的對比度足夠。請注意,茄子常數(shù)的聲明需要在我們使用它來設(shè)置區(qū)域的填充之前進行。
innerChart
.append("path")
.attr("d", areaGenerator(data))
.attr("fill", aubergine)
.attr("fill-opacity", 0.2);
圖4.20 平均溫度的折線圖,結(jié)合顯示最低溫度和最高溫度之間變化的區(qū)域。
如您所見,繪制區(qū)域的過程與繪制線條的過程非常相似。主要區(qū)別在于,一條線只有一組數(shù)據(jù)點,在這些數(shù)據(jù)點之間繪制了這條線,而區(qū)域是兩條邊之間的區(qū)域,每條邊都有一組數(shù)據(jù)點。這就是為什么線發(fā)生器只需要兩個訪問器函數(shù) x() 和 y() ,而面積生成器至少需要三個,在我們的例子中是 x() 、y0() 和 y1()。
圖 4.21 創(chuàng)建區(qū)域的步驟
我們現(xiàn)在有一張 2021 年紐約市平均溫度的折線圖,以及一個顯示最低和最高溫度之間變化的區(qū)域。它看起來已經(jīng)相當不錯了,但我們需要確保看到這張圖表的人很容易理解線條和面積的含義。標簽是一個很好的工具!
在 D3 中,標簽只是我們放置在可視化效果上的 SVG 文本元素。在這里,我們將創(chuàng)建三個標簽,一個用于我們將放置在折線圖末尾的平均溫度,一個用于放置在該區(qū)域下方的最低溫度,另一個用于放置在該區(qū)域上方的最高溫度。
讓我們從折線圖的標簽開始。我們首先將 SVG 文本元素附加到內(nèi)部圖表,并使用 text() 方法將其內(nèi)容設(shè)置為“平均溫度”。然后我們計算它的位置,由屬性 x 和 y 控制。
我們希望標簽位于折線圖的末尾或緊靠其最后一個數(shù)據(jù)點之后。我們可以通過將之前聲明刻度時計算的 lastDate 常量傳遞給 xScale() 來獲取該值。我們還添加了 10px 的額外填充。
對于垂直位置,我們還沒有一個常數(shù)來為我們提供最后一個溫度值。盡管如此,我們?nèi)匀豢梢允褂?data[data.length - 1] 找到數(shù)據(jù)集中的最后一行,并使用點符號來訪問平均溫度。我們將這個值傳遞給 yScale() 并獲取標簽的垂直位置。
最后,我們重用顏色常數(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);
如果保存項目并在瀏覽器中查看,則會發(fā)現(xiàn)標簽的底部與折線圖上最后一個數(shù)據(jù)點的中心垂直對齊。默認情況下,SVG 文本的基線位于文本底部,如圖 4.22 所示。我們可以使用主導基線屬性來更改此設(shè)置。在下面的代碼片段中,我們給此屬性一個值 中間 ,以將基線移動到文本的垂直中心。
圖 4.22 SVG 文本的 y 屬性設(shè)置其基線的垂直位置,默認情況下位于文本底部。我們可以使用主導基線屬性來更改它。如果我們給此屬性值“middle”,則文本的基線將移動到其垂直中間,而值“hanging”會將基線移動到文本的頂部。
innerChart
.append("text")
.text("Average temperature")
.attr("x", xScale(lastDate) + 10)
.attr("y", yScale(data[data.length - 1].avg_temp_F))
.attr("dominant-baseline", "middle")
.attr("fill", aubergine);
然后,我們將為該區(qū)域的下邊界添加一個標簽,該標簽表示最低溫度的演變。策略非常相似。我們首先附加一個 SVG 文本元素,并為其提供“最低溫度”的內(nèi)容。
對于它的位置,我們選擇了最后一個向下的突起,它對應(yīng)于倒數(shù)第三個數(shù)據(jù)點。我們將這些數(shù)據(jù)點的值傳遞給我們的秤以找到它的位置,并將標簽向下移動 20px,向右移動 13px。這些數(shù)字是通過移動標簽找到的,直到我們找到一個看起來合適的位置。瀏覽器的檢查器工具是測試此類微小調(diào)整的好地方。請注意,我們已將標簽的主要基線設(shè)置為 掛起 。如圖 4.22 所示,這意味著 y 屬性控制文本頂部的位置。
最后,在代碼段中,您將看到我們在標簽中添加了一條線,在該區(qū)域的向下突起和標簽之間跟蹤,以闡明標簽代表的內(nèi)容。您可以在圖 4.23 中看到它的外觀。同樣,我們使用刻度來計算直線的 x1、y1、x2 和 y2 屬性,這些屬性控制其起點和終點的位置。
innerChart
.append("text")
.text("Minimum temperature")
.attr("x", xScale(data[data.length - 3].date) + 13)
.attr("y", yScale(data[data.length - 3].min_temp_F) + 20)
.attr("alignment-baseline", "hanging")
.attr("fill", aubergine);
innerChart
.append("line")
.attr("x1", xScale(data[data.length - 3].date))
.attr("y1", yScale(data[data.length - 3].min_temp_F) + 3)
.attr("x2", xScale(data[data.length - 3].date) + 10)
.attr("y2", yScale(data[data.length - 3].min_temp_F) + 20)
.attr("stroke", aubergine)
.attr("stroke-width", 2);
我們使用非常相似的過程為該區(qū)域的上邊界附加一個標簽,該標簽表示最高溫度的演變。我們選擇將此標簽放置在與倒數(shù)第四個數(shù)據(jù)點相對應(yīng)的向上突起附近。同樣,我們在標簽和突起之間畫了一條線。完成后,折線圖就完成了!
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年紐約市溫度演變的完整折線圖。
在最后一節(jié)中,我們將討論如何使用 D3 繪制弧線。弧形是數(shù)據(jù)可視化中的常見形狀。它們用于餅圖、旭日圖和南丁格爾圖,以可視化金額與總數(shù)的關(guān)系,我們經(jīng)常在自定義徑向可視化中使用它們。
像直線和面積一樣,弧是用 SVG 路徑繪制的,而且,正如你現(xiàn)在可能已經(jīng)猜到的那樣,D3 提供了一個方便的弧發(fā)生器函數(shù),可以為我們計算弧路徑的 d 屬性。
在詳細討論電弧發(fā)生器之前,讓我們準備我們的項目。在這里,我們將繪制構(gòu)成徑向圖的弧線,您可以在圖 4.1 中的“有降水的日子”或托管項目 (https://d3js-in-action-third-edition.github.io/new-york-city-weather-2021/) 中看到。藍色弧線表示 2021 年紐約市有降水的天數(shù)百分比 (35%),而灰色弧線表示其余天數(shù)。
首先,打開文件弧.js .這就是我們將在本章其余部分工作的地方。像往常一樣,我們需要加載一個數(shù)據(jù)集,在本例中為 daily_precipitations.csv ,它包含在數(shù)據(jù)文件夾中。如果您查看 CSV 文件,您會發(fā)現(xiàn)它只包含兩列:日期列列出了 2021 年的每一天,而total_precip_in列則提供了每天的總降水量(以英寸為單位)。
在下面的代碼片段中,我們使用 d3.csv() 獲取數(shù)據(jù)集,使用 d3.autoType 正確格式化日期和數(shù)字,并使用 Promise 將其鏈接,我們將數(shù)據(jù)記錄到控制臺中。我們不會在這里討論如何使用 d3.csv() 的細節(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);
});
如果您在控制臺中查看數(shù)據(jù),您會發(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)在可以附加一個新的 SVG 容器。正如您在以下代碼片段中看到的,我們?yōu)?SVG 容器提供了 300px 的寬度和高度,并將其附加到 div 中,其中包含索引中已經(jīng)存在的 arc id.html 。我們使用第 1 章中解釋的策略使 SVG 響應(yīng):將 viewBox 屬性的最后兩個值設(shè)置為其寬度和高度,并完全省略寬度和高度屬性。這樣,SVG 容器將適應(yīng)其父容器的大小,同時保留其縱橫比。請注意,我們將 SVG 容器選擇保存在名為 svg 的常量中。
const pieChartWidth=300;
const pieChartHeight=300;
const svg=d3.select("#arc")
.append("svg")
.attr("viewBox", [0, 0, pieChartWidth, pieChartHeight]);
如第 4.1 節(jié)所述,我們將圖表包裝在 SVG 組中,并將該組轉(zhuǎn)換為所需位置。不過,這次的策略有點不同。我們不需要為軸或標簽保留空間,因此我們可以省略邊距約定。但是,與迄今為止構(gòu)建的所有可視化相反,弧位于極坐標系中,而不是笛卡爾坐標系中,后者的行為略有不同。
如圖 4.24 所示,SVG 容器的坐標系是笛卡爾坐標系。它使用兩個垂直維度 x 和 y 來描述 2D 空間中的位置。我們在第 1 章中討論過,SVG 元素的坐標系有點特殊,因為它的原點位于 SVG 容器的左上角,使 y 維在從上到下的方向上為正。
2D 極坐標系還使用兩個維度:半徑和角度。半徑是原點與空間中點之間的距離,而角度是從 12 點鐘方向沿順時針方向計算的。這種描述空間位置的方法在處理圓弧時特別有用。
圖 4.24 笛卡爾坐標的尺寸彼此垂直,而極坐標系統(tǒng)使用半徑和角度尺寸來描述空間中的位置。
由于元素位于極坐標系中的原點周圍,因此我們可以說我們將要構(gòu)建的弧可視化的原點位于 SVG 容器的中心,如圖 4.25 所示。
圖 4.25 通過將弧包裝成 SVG 組并將該組轉(zhuǎn)換為 SVG 容器的中心,我們簡化了一組弧的創(chuàng)建。當我們向組追加弧時,它們的位置將自動相對于圖表的中心,這對應(yīng)于其極坐標系的原點。
在下一個代碼片段中,我們選擇SVG容器并在其中附加一個組,我們將該組轉(zhuǎn)換為SVG容器的中心,并將其保存在常量innerChart中。
const innerChart=svg
.append("g")
.attr("transform", `translate(${pieChartWidth/2},
? ${pieChartHeight/2})`);
在創(chuàng)建弧線之前,我們需要做最后一件事:計算圖表上有降水的日子所采用的角度。使用 D3 創(chuàng)建餅圖或圓環(huán)圖時,我們通常使用餅圖布局生成器處理此類計算,我們將在下一章中介紹。但是由于我們在這里只畫兩個弧線,所以數(shù)學很容易。
首先,我們可以使用數(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);
然后,我們可以通過將這個數(shù)字乘以 360 度(一個完整圓的度數(shù))來計算對應(yīng)于降水天數(shù)的角度,得到 126 度。我們從度開始,因為它往往更直觀,但我們還需要將此值轉(zhuǎn)換為弧度。為此,我們將降水天數(shù)百分比(以度為單位)所覆蓋的角度乘以數(shù)字 pi (3.1416),然后將其除以 180,得到大約 2.2 弧度的角度,我們將其保存在常數(shù)angleDaysWithPrecipitations_rad中。
我們執(zhí)行此轉(zhuǎn)換是因為我們將在一會兒使用的電弧發(fā)生器期望角度以弧度而不是度為單位。作為處理角度的經(jīng)驗法則,JavaScript 通常希望它們以弧度為單位,而 CSS 使用度數(shù)。
const angleDaysWithPrecipitations_deg=percentageDaysWithPrecipitations *
? 360 / 100;
const angleDaysWithPrecipitations_rad=angleDaysWithPrecipitations_deg *
? Math.PI / 180;
我們終于到了有趣的部分,生成弧線!首先,我們需要聲明一個電弧發(fā)生器,就像我們對線和區(qū)域所做的那樣。弧發(fā)生器 d3.arc() 是模塊 d3-shape (https://github.com/d3/d3-shape) 的一部分,在我們的例子中,需要兩個主要的訪問器函數(shù):弧的內(nèi)半徑和外半徑,分別由 innerRadius() 和 outerRadius() 處理,并給定值為 80 和 120px。請注意,如果內(nèi)半徑為零,我們會得到一個類似于餅圖中的弧線,并從原點開始。
const arcGenerator=d3.arc()
.innerRadius(80)
.outerRadius(120);
我們可以通過使用訪問器函數(shù)在弧形之間添加填充來個性化我們的弧線 padAngle() ,它接受以弧度為單位的角度。這里我們使用 0.02 弧度,對應(yīng)于略多于 1 度。我們也可以用 角半徑() ,它接受一個以像素為單位的值。此訪問器函數(shù)與 CSS 邊框半徑屬性具有類似的效果。
const arcGenerator=d3.arc()
.innerRadius(80)
.outerRadius(120)
.padAngle(0.02)
.cornerRadius(6);
圖 4.26 電弧發(fā)生器使用多個訪問器函數(shù)來計算電弧的 d 屬性。在這里,我們在生成器聲明期間設(shè)置其內(nèi)半徑、外半徑、填充角度和角半徑。我們將在將路徑元素附加到圖表時傳遞每個弧的開始和結(jié)束角度。
此時,您可能想知道為什么我們不使用處理弧線覆蓋的角度的訪問器函數(shù)。在我們的例子中,由于我們已經(jīng)手動計算了角度,因此當我們附加路徑時,將這些值傳遞給電弧發(fā)生器會更簡單。但我們將在下一章中看到,情況并非總是如此。
因此,讓我們附加第一個弧線,即顯示降水天數(shù)的弧線。在下面的代碼片段中,我們首先將一個 path 元素附加到內(nèi)部圖表選擇中。然后,我們通過調(diào)用最后一個代碼段中聲明的 arc 生成器來設(shè)置其 d 屬性。
觀察我們?nèi)绾螌㈤_始和結(jié)束角度作為對象傳遞給生成器。起始角度的值為零,對應(yīng)于 12 點鐘位置,而結(jié)束角度的值是之前計算的降水天數(shù)所覆蓋的角度。最后,我們將弧線的填充設(shè)置為顏色 #6EB7C2,青藍色。
innerChart
.append("path")
.attr("d", ()=> {
return arcGenerator({
startAngle: 0,
endAngle: angleDaysWithPrecipitations_rad
});
})
.attr("fill", "#6EB7C2");
我們以類似的方式附加第二個弧線。這一次,弧從前一個弧線結(jié)束的地方開始,到圓圈完成時結(jié)束,對應(yīng)于弧度中的角度 2*Pi。我們給弧線一個 #DCE2E2,一種更接近灰色的顏色,以表明這些日子沒有降水。
innerChart
.append("path")
.attr("d", ()=> {
return arcGenerator({
startAngle: angleDaysWithPrecipitations_rad,
endAngle: 2 * Math.PI
});
})
.attr("fill", "#DCE2E2");
保存項目后,弧應(yīng)如圖 4.27 所示。我們鼓勵您使用傳遞給生成器的訪問器函數(shù)的值(如半徑或角半徑),以了解它們?nèi)绾涡薷幕〉耐庥^。
圖 4.27 弧線顯示有降水天數(shù)和無降水天數(shù)之間的比率。
如您所見,繪制圓弧的過程類似于繪制線條和區(qū)域的過程。主要區(qū)別在于弧在空間中的位置是用極坐標而不是笛卡爾來處理的,這反映在弧發(fā)生器的訪問器函數(shù)中。
圖 4.28 繪制弧線的步驟。
餅圖和圓環(huán)圖最近在數(shù)據(jù)可視化社區(qū)中得到了很多負面報道,主要是因為我們意識到人眼不太擅長估計弧線所代表的比率。但是,這些圖表并不總是一個糟糕的選擇,尤其是當它們包含少量類別時。但我們絕對可以通過標簽幫助他們提高可讀性,這就是我們在這里要做的!
在表示有降水天數(shù)的弧線上,我們將添加標簽“35%”,即之前計算的有降水的天數(shù)百分比。放置此標簽的好地方是弧的質(zhì)心,也稱為其質(zhì)心。此值可由電弧發(fā)生器提供。
在下面的代碼片段中,我們在前面初始化的弧發(fā)生器函數(shù)上調(diào)用方法。這一次,我們將它與 startAngle() 和 endAngle() 訪問器函數(shù)鏈接起來,分別將它們傳遞代表有降水的日子的弧的開始角和結(jié)束角的值。最后,我們鏈接方法centroid(),它將計算弧的中點。
const centroid=arcGenerator
.startAngle(0)
.endAngle(angleDaysWithPrecipitations_rad)
.centroid();
將質(zhì)心記錄到控制臺中。您將看到它由兩個值的數(shù)組組成:質(zhì)心的水平和垂直位置,在我們的例子中是 [89, -45] ,從內(nèi)部圖表的原點計算得出。
在下一個代碼段中,我們通過向內(nèi)部圖表追加文本元素來創(chuàng)建標簽。為了使標簽包含“%”符號,我們使用方法 d3.format(“.0%”) ,后跟括號中的值。此方法便于以特定方式(如貨幣、百分比和指數(shù))格式化數(shù)字,或為這些數(shù)字添加特定后綴,如“M”表示百萬或“μ”表示微型。您可以在模塊 d3 格式 (https://github.com/d3/d3-format) 中找到所有可用格式的詳細列表。
然后,我們使用質(zhì)心數(shù)組中返回的第一個和第二個值設(shè)置 x 和 y 屬性。請注意我們?nèi)绾卧O(shè)置文本錨點和主要基線屬性,以確保標簽在水平和垂直方向上以 x 和 y 屬性為中心。
最后,我們給標簽一個白色和500的字體粗細,以提高其易讀性。保存后,帶有標簽的弧應(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 帶有標簽的已完成弧。
您現(xiàn)在知道如何使用 D3 繪制線條、面積和弧線了!在下一章中,我們將使用布局生成器將這些形狀提升到另一個層次。
折線圖目前應(yīng)用最廣的也是用來繪制各種軌跡,折線圖其實就是后面動態(tài)軌跡圖、飛機航線圖的前身,公用的一個方法addPolyline,折線圖可以設(shè)置顏色、粗細、透明度等屬性,如果開啟了懸浮繪圖工具欄,也可以直接單擊工具欄中的折線圖繪制工具,直接動態(tài)繪制。
1. 同時支持在線地圖和離線地圖兩種模式。
2. 同時支持webkit內(nèi)核、webengine內(nèi)核、miniblink內(nèi)核、IE內(nèi)核。
3. 支持設(shè)置多個標注點,信息包括名稱、地址、經(jīng)緯度。
4. 可設(shè)置地圖是否可單擊、拖動、鼠標滾輪縮放。
5. 可設(shè)置協(xié)議版本、秘鑰、主題樣式、中心坐標、中心城市、地理編碼位置等。
6. 可設(shè)置地圖縮放比例和級別,縮略圖、比例尺、路況信息等控件的可見。
7. 支持地圖交互,比如鼠標按下獲取對應(yīng)位置的經(jīng)緯度。
8. 支持查詢路線,可設(shè)置起點位置、終點位置、路線模式、路線方式、路線方案(最少時間、最少換乘、最少步行、不乘地鐵、最短距離、避開高速)。
9. 可顯示點線面工具,可直接在地圖上劃線、點、矩形、圓形等。
10. 可設(shè)置行政區(qū)劃,指定某個城市區(qū)域繪制圖層,在線地圖自動輸出行政區(qū)劃邊界點集合到j(luò)s文件給離線地圖使用。
11. 可靜態(tài)或者動態(tài)添加多個覆蓋物。支持點、折線、多邊形、矩形、圓形、弧線、點聚合等。
12. 提供函數(shù)接口處理經(jīng)緯度解析成地址和地址解析成經(jīng)緯度坐標。
13. 提供的demo直接可以單獨選點執(zhí)行對應(yīng)的處理比如路線查詢。
14. 可以拿到路線查詢到的點坐標信息集合,比如用于機器人坐標導航等。
15. 封裝了豐富的函數(shù)比如刪除指定點和所有點,刪除指定覆蓋物和所有覆蓋物等。
16. 標注點彈框信息可以自定義內(nèi)容,標準html格式。
17. 標注點單擊事件可選 0-不處理 1-自己彈框 2-發(fā)送信號。
18. 標注點可設(shè)置動畫效果 0-不處理 1-跳動 2-墜落
19. 標注點可設(shè)置本地圖片文件等。
20. 函數(shù)接口友好和統(tǒng)一,使用簡單方便,就一個類。
21. 支持js動態(tài)交互添加點、刪除點、清空點、重置點,不需要刷新頁面。
22. 支持任意Qt版本、任意系統(tǒng)、任意編譯器。
1. 體驗地址:[https://pan.baidu.com/s/1ZxG-oyUKe286LPMPxOrO2A](https://pan.baidu.com/s/1ZxG-oyUKe286LPMPxOrO2A) 提取碼:o05q 文件名:bin_map.zip
2. 國內(nèi)站點:[https://gitee.com/feiyangqingyun](https://gitee.com/feiyangqingyun)
3. 國際站點:[https://github.com/feiyangqingyun](https://github.com/feiyangqingyun)
4. 個人主頁:[https://blog.csdn.net/feiyangqingyun](https://blog.csdn.net/feiyangqingyun)
5. 知乎主頁:[https://www.zhihu.com/people/feiyangqingyun/](https://www.zhihu.com/people/feiyangqingyun/)
*請認真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。