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小时内与您取得联系。