Python數據科學和Web開發領域,可視化是理解和表達數據的重要手段之一。其中,直方圖作為一種統計圖形,能夠清晰地展現數據分布的特點,幫助我們快速識別集中趨勢、分散程度以及其他潛在模式。本文將以Python中最常用的可視化庫Matplotlib為基礎,詳細介紹如何利用其內置的hist()函數繪制高質量的直方圖,并結合實際Web應用展示如何將直方圖嵌入到Web界面中。
直方圖是一種統計圖形,它將連續變量的值劃分為一系列區間(稱為“bins”),并通過柱狀的高度表示落在各個區間內的數據點數量。這種圖形特別適用于展示數據的頻數分布,即數據集中在哪些數值范圍。
首先,讓我們通過一個簡單的例子來看看如何使用Matplotlib繪制直方圖:
import matplotlib.pyplot as plt
import numpy as np
# 創建一組模擬數據
data = np.random.normal(size=1000)
# 使用hist()函數繪制直方圖
plt.hist(data, bins=30, edgecolor='black') # 參數bins決定區間個數
plt.xlabel('Value Range')
plt.ylabel('Frequency')
plt.title('Normal Distribution Histogram')
# 顯示圖形
plt.show()
1. 多組數據對比
在同一張圖上對比兩組或多組數據的分布情況:
data1 = np.random.normal(loc=0, scale=1, size=1000)
data2 = np.random.normal(loc=1, scale=1.5, size=1000)
fig, ax = plt.subplots()
ax.hist(data1, bins=30, alpha=0.5, label='Data Set 1')
ax.hist(data2, bins=30, alpha=0.5, label='Data Set 2')
ax.set_xlabel('Value')
ax.set_ylabel('Density')
ax.legend()
plt.show()
2. 自定義顏色、樣式及誤差條
colors = ['tab:blue', 'tab:orange']
edgecolors = ['black'] * 2
weights = [np.ones_like(data1), 0.5 * np.ones_like(data2)]
fig, ax = plt.subplots()
ax.hist([data1, data2], bins=30, color=colors, edgecolor=edgecolors, weights=weights)
plt.show()
在Web應用中,通常需要將直方圖轉化為可嵌入網頁的靜態圖像或交互式圖表。例如,借助Flask框架,我們可以生成直方圖圖片并在HTML頁面中展示:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def histogram_example():
# 同上創建數據并繪制直方圖
...
# 保存為圖片
fig.savefig('histogram.png', dpi=300)
return render_template('index.html', image_url='/static/histogram.png')
if __name__ == '__main__':
app.run(debug=True)
對于更高級的應用場景,可以考慮采用Bokeh、Plotly等支持Web交互式的圖表庫,它們可以直接生成可在瀏覽器中動態縮放和平移的直方圖。
總之,直方圖作為揭示數據分布特征的強大工具,在Python Web開發中扮演了重要角色。通過深入理解和熟練運用Matplotlib或其他相關庫,開發者可以設計出富有洞察力的數據可視化界面,提升用戶體驗和決策效率。
話不多說直接上代碼
data = [
{
letter: "白皮雞蛋",
child: {
category: "0",
value: "459.00"
}
},
{
letter: "紅皮雞蛋",
child: {
category: "0",
value: "389.00"
}
},....]
d3在vue中安裝也很簡單 直接 npm install d3
import * as d3 from "d3";
我是封裝在chart函數中的(兩個參數分別是源數據和圖像掛載dom在vue中直接用this.$refs.cil其他地方的用法和JQ類似)
function chart(data, myEle) {
{
var margin = {
top: 20,
right: 50,
bottom: 50,
left: 90
};
var svgWidth = window.screen.width -margin.right -margin.left; // 檢測屏幕
var svgHeight = 500;
//創建各個面的顏色數組
var mainColorList = [
"#f6e242",
"#ebec5b",
"#d2ef5f",
"#b1d894",
"#97d5ad",
"#82d1c0",
"#70cfd2",
"#63c8ce",
"#50bab8",
"#38a99d"
];
var topColorList = [
"#e9d748",
"#d1d252",
"#c0d75f",
"#a2d37d",
"#83d09e",
"#68ccb6",
"#5bc8cb",
"#59c0c6",
"#3aadab",
"#2da094"
];
var rightColorList = [
"#dfce51",
"#d9db59",
"#b9d54a",
"#9ece7c",
"#8ac69f",
"#70c3b1",
"#65c5c8",
"#57bac0",
"#42aba9",
"#2c9b8f"
];
// console.log(this.$refs.cil)
var svg = d3
.select(myEle)
.append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight)
.attr("id", "svg-column");
// 創建X軸序數比例尺
function addXAxis() {
var transform = d3.geoTransform({
point: function (x, y) {
this.stream.point(x, y);
}
}); //定義幾何路徑
var path = d3.geoPath().projection(transform);
var xLinearScale = d3
.scaleBand()
.domain(
data.map(function (d) {
return d.letter;
})
)
.range([0, svgWidth - margin.right - margin.left], 0.1);
var xAxis = d3.axisBottom(xLinearScale).ticks(data.length); //繪制X軸
var xAxisG = svg
.append("g")
.call(xAxis)
.attr(
"transform",
"translate(" + margin.left + "," + (svgHeight - margin.bottom) +")"
); //刪除原X軸
xAxisG.select("path").remove();
xAxisG.selectAll("line").remove(); //繪制新的立體X軸
xAxisG
.append("path")
.datum({
type: "Polygon",
coordinates: [
[
[20, 0],
[0, 15],
[svgWidth - margin.right - margin.left, 15],
[svgWidth + 20 - margin.right - margin.left, 0],
[20, 0]
]
]
})
.attr("d", path)
.attr("fill", "rgb(187,187,187)");
xAxisG
.selectAll("text")
.attr("fill", "#646464")
.attr("class", 'rotate_txt')
.attr("transform-origin", "50% top 0")
.attr("transform", "translate(0,20)");
dataProcessing(xLinearScale); //核心算法
}
var yLinearScale;
//創建y軸的比例尺渲染y軸
function addYScale() {
yLinearScale = d3
.scaleLinear()
.domain([
0,
d3.max(data, function (d, i) {
return d.child.value * 1;
}) * 1.2
])
.range([svgHeight - margin.top - margin.bottom, 0]);
//定義Y軸比例尺以及刻度
var yAxis = d3.axisLeft(yLinearScale).ticks(6);
//繪制Y軸
var yAxisG = svg
.append("g")
.call(yAxis)
.attr(
"transform",
"translate(" + (margin.left + 10) + "," + margin.top + ")"
);
yAxisG
.selectAll("text")
.attr("font-size", "18px")
.attr("fill", "#636363");
//刪除原Y軸路徑和tick
yAxisG.select("path").remove();
yAxisG.selectAll("line").remove();
}
//核心算法思路是
function dataProcessing(xLinearScale) {
var angle = Math.PI / 2.3;
for (var i = 0; i < data.length; i++) {
var d = data[i];
var depth = 10;
d.ow = xLinearScale.bandwidth() * 0.7;
d.ox = xLinearScale(d.letter);
d.oh = 1;
d.p1 = {
x: Math.cos(angle) * d.ow,
y: -Math.sin(angle) - depth
};
d.p2 = {
x: d.p1.x + d.ow,
y: d.p1.y
};
d.p3 = {
x: d.p2.x,
y: d.p2.y + d.oh
};
}
}
//tip的創建方法
var tipTimerConfig = {
longer: 0,
target: null,
exist: false,
winEvent: window.event,
boxHeight: 398,
boxWidth: 376,
maxWidth: 376,
maxHeight: 398,
tooltip: null,
showTime: 3500,
hoverTime: 300,
displayText: "",
show: function (val, e) {
"use strict";
var me = this;
if (e != null) {
me.winEvent = e;
}
me.displayText = val;
me.calculateBoxAndShow();
me.createTimer();
},
calculateBoxAndShow: function () {
"use strict";
var me = this;
var _x = 0;
var _y = 0;
var _w = document.documentElement.scrollWidth;
var _h = document.documentElement.scrollHeight;
var wScrollX = window.scrollX || document.body.scrollLeft;
var wScrollY = window.scrollY || document.body.scrollTop;
var xMouse = me.winEvent.x + wScrollX;
if (_w - xMouse < me.boxWidth) {
_x = xMouse - me.boxWidth - 10;
} else {
_x = xMouse;
}
var _yMouse = me.winEvent.y + wScrollY;
if (_h - _yMouse < me.boxHeight + 18) {
_y = _yMouse - me.boxHeight - 25;
} else {
_y = _yMouse + 18;
}
me.addTooltip(_x, _y);
},
addTooltip: function (page_x, page_y) {
"use strict";
var me = this;
me.tooltip = document.createElement("div");
me.tooltip.style.left = page_x + "px";
me.tooltip.style.top = page_y + "px";
me.tooltip.style.position = "absolute";
me.tooltip.style.width = me.boxWidth + "px";
me.tooltip.style.height = me.boxHeight + "px";
me.tooltip.className = "three-tooltip";
var divInnerHeader = me.createInner();
divInnerHeader.innerHTML = me.displayText;
me.tooltip.appendChild(divInnerHeader);
document.body.appendChild(me.tooltip);
},
createInner: function () {
"use strict";
var me = this;
var divInnerHeader = document.createElement("div");
divInnerHeader.style.width = me.boxWidth + "px";
divInnerHeader.style.height = me.boxHeight + "px";
return divInnerHeader;
},
ClearDiv: function () {
"use strict";
var delDiv = document.body.getElementsByClassName("three-tooltip");
for (var i = delDiv.length - 1; i >= 0; i--) {
document.body.removeChild(delDiv[i]);
}
},
createTimer: function (delTarget) {
"use strict";
var me = this;
var delTip = me.tooltip;
var delTarget = tipTimerConfig.target;
var removeTimer = window.setTimeout(function () {
try {
if (delTip != null) {
document.body.removeChild(delTip);
if (tipTimerConfig.target == delTarget) {
me.exist = false;
}
}
clearTimeout(removeTimer);
} catch (e) {
clearTimeout(removeTimer);
}
}, me.showTime);
},
hoverTimerFn: function (showTip, showTarget) {
"use strict";
var me = this;
var showTarget = tipTimerConfig.target;
var hoverTimer = window.setInterval(function () {
try {
if (tipTimerConfig.target != showTarget) {
clearInterval(hoverTimer);
} else if (
!tipTimerConfig.exist &&
new Date().getTime() - me.longer > me.hoverTime
) {
//show
tipTimerConfig.show(showTip);
tipTimerConfig.exist = true;
clearInterval(hoverTimer);
}
} catch (e) {
clearInterval(hoverTimer);
}
}, tipTimerConfig.hoverTime);
}
};
var createTooltipTableData = function (info) {
var ary = [];
ary.push("<div class='tip-hill-div'>");
ary.push("<p>" + info.letter +' '+ info.child.value + "</p>");
ary.push("</div>");
return ary.join("");
};
// 核心算法寫完,就到了最終的渲染了
function addColumn() {
function clumnMouseover(d) {
d3.select(this)
.selectAll(".transparentPath")
.attr("opacity", 0.8);
// 添加 div
tipTimerConfig.target = this;
tipTimerConfig.longer = new Date().getTime();
tipTimerConfig.exist = false;
//獲取坐標
tipTimerConfig.winEvent = {
x: event.clientX - 100,
y: event.clientY
};
tipTimerConfig.boxHeight = 50;
tipTimerConfig.boxWidth = 140;
//hide
tipTimerConfig.ClearDiv();
//show
tipTimerConfig.hoverTimerFn(createTooltipTableData(d));
}
function clumnMouseout(d) {
d3.select(this)
.selectAll(".transparentPath")
.attr("opacity", 1);
tipTimerConfig.target = null;
tipTimerConfig.ClearDiv();
}
var g = svg
.selectAll(".g")
.data(data)
.enter()
.append("g")
.on("mouseover", clumnMouseover)
.on("mouseout", clumnMouseout)
.attr("transform", function (d) {
return (
"translate(" +
(d.ox + margin.left + 20) +
"," +
(svgHeight - margin.bottom + 15) +
")"
);
});
g.transition()
.duration(2500)
.attr("transform", function (d) {
return (
"translate(" +
(d.ox + margin.left + 20) +
", " +
(yLinearScale(d.child.value) + margin.bottom - 15) +
")"
);
});
g.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("class", "transparentPath")
.attr("width", function (d, i) {
return d.ow;
})
.attr("height", function (d) {
return d.oh;
})
.style("fill", function (d, i) {
if (!mainColorList[i]) {
return "#38a99d"
}
return mainColorList[i];
})
.transition()
.duration(2500)
.attr("height", function (d, i) {
return (
svgHeight -
margin.bottom -
margin.top -
yLinearScale(d.child.value)
);
});
g.append("path")
.attr("class", "transparentPath")
.attr("d", function (d) {
return (
"M0,0 L" +
d.p1.x +
"," +
d.p1.y +
" L" +
d.p2.x +
"," +
d.p2.y +
" L" +
d.ow +
",0 L0,0"
);
})
.style("fill", function (d, i) {
if (!topColorList[i]) {
return "#2da094"
}
return topColorList[i];
});
g.append("path")
.attr("class", "transparentPath")
.attr("d", function (d) {
return (
"M" +
d.ow +
",0 L" +
d.p2.x +
"," +
d.p2.y +
" L" +
d.p3.x +
"," +
d.p3.y +
" L" +
d.ow +
"," +
d.oh +
" L" +
d.ow +
",0"
);
})
.style("fill", function (d, i) {
if (!rightColorList[i]) {
return "#2c9b8f"
}
return rightColorList[i];
})
.transition()
.duration(2500)
.attr("d", function (d, i) {
return (
"M" +
d.ow +
",0 L" +
d.p2.x +
"," +
d.p2.y +
" L" +
d.p3.x +
"," +
(d.p3.y +
svgHeight -
margin.top -
margin.bottom -
yLinearScale(d.child.value)) +
" L" +
d.ow +
"," +
(svgHeight -
margin.top -
margin.bottom -
yLinearScale(d.child.value)) +
" L" +
d.ow +
",0"
);
});
}
// 函數調用
addXAxis();
addYScale();
addColumn();
}
}
export function showCahrt(data, myEle) {
return chart(data, myEle)
}
這里有個bug我一直沒有找到很好的解決辦法就是在繪制x軸時文字的樣式調整
就是這里使用.attr("transform", "translate(0,20) rotate(-30deg)")也就是旋轉加下移組合是根本不生效只能寫其中的一個。不知道小伙伴們有沒有什么高見很是期待!
整篇的代碼有點長其實整體還是很清晰的 。在最后面
有收獲的小朋友記得點贊哦
一)測光
測光分入射測光和反射測光。顧名思義,入射測光是測量直接照射到被攝體上的光線強度,而反射測光是測量從被攝體反射回來(進入相機鏡頭)的光線強度。相機的內置測光表都是反射測光表。
反射測光表的工作原理是根據對被攝體反射的光的測量來估計入射到被攝體上的光強度。很明顯,估算的結果依賴于被攝體對光的反射率。因為現實中場景和被攝體的反射率變化很大,這導致反射測光表可能給出錯誤的結果。
反射測光表一般采用中度灰來校準。也就是說,它總是假設被攝體的黑白顏色是中度灰的,以中度灰的反射率來估算入射光的強度,再決定在給定的光強照度和膠片或影像傳感器的ISO值下的光圈和快門的組合。中度灰是介于純白和純黑之間的灰色,對應的反射率是18%,所以又叫做18%灰。
幾種顏色的18%反射光強
現在的測光技術都很發達,在大多數場合相機的內置測光表都能給出比較準確的測光。但在特殊或者極端的條件下,自動測光的結果會和真實結果差別較大,必須通過曝光補償來修正。最好在拍攝的時候就做曝光補償,不要留到后期,因為錯誤的曝光會讓照片丟失細節。(如果想得到比較準確的反射測光可以使用灰卡,這里不作介紹。)
因為反射測光表總是假設被攝體是中度灰的,如果使用有內置測光表的相機拍攝充滿畫面的一張白紙,出來的相片是一張灰顏色的紙;如果使用有內置測光表的相機拍攝充滿畫面的一張黑紙,出來的相片也是一張灰顏色的紙。如果想要白紙出來的相片也是白紙,就要增加一定量的曝光;如果想要黑紙出來的相片也是黑紙,就要減少一定量的曝光。
要養成這樣的習慣:每次拍照之前,都要問一下“這個場景是否是平均的?是否需要曝光補償?”數碼相機讓測光變得簡單:可以先用自動測光的設置拍攝一張,查看結果再決定是否需要曝光補償。
一些有用的規律:
(二)直方圖
相機的直方圖是個非常有用的工具,可以幫助拍攝者獲得正確的曝光。所以,學會讀懂直方圖對提高攝影技術至關重要。
直方圖能直觀地顯示影像的明暗色調分布,對比度,和曝光。直方圖的定義是:橫軸代表黑白亮度等級(從左到右代表由暗到明的變化),縱軸代表每個亮度等級下的圖像像素個數。亮度由左到右分成256個等級,從0到255,0代表純黑,255代表純白,128是中度灰。一個典型的直方圖如下圖所示。(高級相機可以顯示紅、綠、藍每個通道的直方圖。)
直方圖整體的絕對高度并不重要,因為相機對直方圖做了一定程度的歸一化以保證中間最大的像素值能表示在圖上。有意義的是每個亮度等級里邊像素數目的相對分布,即直方圖的形狀。
拍攝的影像的直方圖取決于以下幾個因素:(1)拍攝場景的動態范圍;(2)相機影像傳感器能撲捉到的動態范圍;(3)曝光的選擇。動態范圍由光強分布的最大值和最小值之比(即對比度)來決定,在攝影中由2的冪律指數即曝光檔數(或光圈檔數)表示(參見http://blog.sina.com.cn/s/blog_713dd87d0100nt1j.html)。如果真實場景的動態范圍(即對比度)小于相機的動態范圍(即相機能拍下的對比度),那么只要曝光正確,相機就能拍下場景的整個動態范圍,得到的直方圖是這樣的:
顯然這個直方圖的位置還有調整的余地:可以整體向左移動,也可以整體向右移動,只要不貼到左右兩側邊緣,都不會導致細節丟失。向左移動圖像整體變暗(欠曝),噪點會增加。向右移動圖像整體變亮,噪點減少。所謂“向右曝光”,說的是讓直方圖盡量靠右,但不要貼到右端,這樣的圖像可以擁有最多的細節,最少的噪點,利于后期處理。
相反,如果真實場景的動態范圍大于相機的動態范圍,那么無論曝光怎樣設置,都無法一次撲捉到場景的全部動態范圍,只能撲捉到真是動態范圍的一部分,如圖:
在給定的曝光設置下,相機的傳感器能撲捉到光強有個上限和下限。真實場景中亮度超過這個上限的部分,對應的傳感器像素白色已經達到極限(不能更白了),全部記錄為“最白”;真實場景中亮度低于這個下限的部分,對應的傳感器像素黑色達到極限(不能更黑了),全部記錄為“最黑”。也就是說,比影像傳感器的“最白”更白的部分全部記錄為“最白”,比影像傳感器的“最黑”更黑的部分全部記錄為“最黑”。體現到直方圖上,就是在直方圖的右邊(最白)和左邊(最黑)像素的數目堆積,形成僅靠兩邊的峰值(如上圖)。這種現象叫做“溢出”,會導致圖像亮部和暗部的細節丟失,一般應該避免。當然,也可以設置曝光使得亮端的動態全部被拍下,這樣暗端必然溢出;或者使得暗端全部拍下,這樣亮端必然溢出。只要是真實場景動態范圍超過了相機的動態范圍,溢出就不可避免。溢出直接導致亮部或暗部的細節丟失。
如果直方圖的左邊溢出,那么圖像圖像暗部信息丟失。如果右邊溢出,那么圖像亮部信息丟失。左右兩邊都溢出,那么圖像亮部和暗部都有信息丟失。拍攝的時候兩個方向的溢出都應該避免,以保持圖像擁有最多的細節。如果無法避免,就要看暗部細節更重要一些還是亮部細節更重要一些,選擇保留重要的細節。數碼相機最怕過曝,過曝了細節無法在后期恢復;一定程度的欠曝可以在后期恢復,代價是噪點的增加。這點跟膠片正相反。所以,使用數碼相機一定要盡可能避免右方溢出。
左圖:暗端溢出;右圖:亮端溢出
完美的標準直方圖是不存在的,因為直方圖的形狀取決于相機鏡頭前方的具體景象。好的直方圖是有的。好的直方圖應該是左右兩端都沒有溢出,整個直方圖完整地分布在亮度0到255之間(實際拍攝中有時無法做到,總會在一端有溢出,如上所述)。到底是偏左一點好還是偏右一點好,完全取決于拍攝者的意圖。比如要拍攝一幅主要由暗部構成的低調照片,直方圖就要偏左。要拍攝一幅主要由亮部構成的高調照片,直方圖就要偏右。
相機的自動測光總是努力把直方圖的像素分布平均值位置放在中間(中度灰位置)。如果這不是你要的結果,你就要通過曝光補償重新拍照來把直方圖往左或者往右來移動。直方圖峰值代表了圖像占主導的亮度等級的分布。如果直方圖整體較窄,它表示量度分布在一個狹窄的區域,圖像的對比度較低。如果整體分布較寬,代表亮度分布在一個寬泛的區域,圖像的對比度較高。如果直方圖出現了相距較遠的兩個峰值,那么這個圖象的對比度非常高。
左圖:對比度低,色調單一;右圖:對比度較高,色調飽滿
如果想在圖像中保留盡可能多的細節以便后期處理,可以選擇盡量向右曝光,即讓直方圖的最右邊貼在直方圖的右端但不要溢出。向右曝光還可以盡可能地減少噪點(提高整體信噪比)。這樣的照片看上去幾乎總是白亮亮的,需要后期處理得到想要的結果。
下面是一些具體直方圖例子:
光線色調非常復雜,左右溢出同時存在,缺少中色調
低調場景,直方圖偏左
高調場景,直方圖偏右
其它幾個例子
場景具有幾近完美的亮度分布
低調照片,完美地撲捉到暗部(背景)和亮部(月亮)
雖然直方圖整體偏右(過曝),但前當地反應了眼睛看到的景象
*請認真填寫需求信息,我們會在24小時內與您取得聯系。