d3js scales深入理解
转自:https://www.cnblogs.com/kidsitcn/p/7182274.html
比例尺函数是这样的javascript函数:
- 接收通常是数字,日期,类别等data输入并且:
- 返回一个代表可视化元素的值,比如坐标,颜色,长度或者半径等
比例尺通常用于变换(或者说映射)抽象的数据值到可视量化变量(比如位置,长度,颜色等)
比如,假设我们有以下数组数据:
[ 0, 2, 3, 5, 7.5, 9, 10 ]
我们可以这样创建一个比例尺函数:
var myScale = d3.scaleLinear()
.domain([0, 10])
.range([0, 600]);
d3将创建一个myScale函数用于接收[0,10]之间的数据输入(domain)映射为[0,600]像素的位置数据(range)
我们可以使用myScale函数来计算对应数据的positions数据:
myScale(0); // returns 0
myScale(2); // returns 120
myScale(3); // returns 180
...
myScale(10); // returns
如上面所说,比例尺主要用于将抽象数据映射为可视的量化元素,比如位置,长度,半径,颜色等。比如,他们可以这样应用
- 将抽象数据映射为0到500的长度值以便在bar chart中使用
- 将抽象数据映射为0到200之间的位置数据值以便作为line charts中点的坐标来使用
- 将百分比变化数据(+4%,+10%,-5%等)映射为颜色的对应变化(比如使用红色表示为正值,正的越多越红,负值为绿色,负的越多绿色饱和度越高)
- 将日期数据映射为x轴上的位置
Constructing scales
这部分我们集中使用线性比例尺linear scale作为例子探讨scale的相关知识,在后面再探讨其他类型的比例尺
var myScale = d3.scaleLinear();
注意:v4和v3的声明方式是不同的 d3.scaleLinear() in v4 and d3.scale.linear() in
myScale
.domain([0, 100])
.range([0, 800]);
通过上面的代码,myScale就成为有特定意义的比例尺函数了。现在myScale可以接收任何在0,100之间的domain,而映射到0到800的range里面。我们可以像下面一样来调用这个比例尺函数:
myScale(0); // returns 0
myScale(50); // returns 400
myScale(100); // returns 800
D3 scale types
D3大约有12种不同的比例尺类型(scaleLinear, scalePow, scaleQuantise, scaleOrdinal etc.) ,而总体来说我们可以分为3类
- scales with continuous input and continuous output(连续性输入连续性输出)
- scales with continuous input and discrete output (连续性输入离散性输出)
- scales with discrete input and discrete output (离散输入离散输出)
下面我们一个一个地来仔细学习一下
Scales with continuous input and continuous output(连续性输入连续性输出)
scaleLinear
线性比例尺可能是应用最为广泛的比例尺类型了,因为他们最适合将数据转化为位置和长度。因此往往也以线性比例尺为例去讲解和学习比例次的知识
他们使用一个线性函数(y=m*x+b)来表达(domain)和(range)之间的数学函数关系
var linearScale = d3.scaleLinear()
.domain([0, 10])
.range([0, 600]); linearScale(0); // returns 0
linearScale(5); // returns 300
linearScale(10); // returns 600
典型地,他们被用于将抽象数据转换为位置,长度等可视元素。因此当我们创bar chart,line chart,或者其他很多图标类型时,我们可以使用它。
除了位置长度作为range,也可以使用颜色值哦(实际上颜色也可以作为连续性数据的):
var linearScale = d3.scaleLinear()
.domain([0, 10])
.range(['yellow', 'red']); linearScale(0); // returns "rgb(255, 255, 0)"
linearScale(5); // returns "rgb(255, 128, 0)"
linearScale(10); // returns "rgb(255, 0, 0)"
这个特性常常被用于等值线图(choropleth),当然我们也可以使用scaleQuantize,scaleQuantile和scaleThrshold.
scalePow
scalePow这个比例次使用y = m * x^k + b这个数学函数来表达domain和range之间的数学函数关系。指数k使用.exponent()来设定:
var powerScale = d3.scalePow() .exponent(0.5) .domain([0, 100]) .range([0, 30]);
powerScale(0); // returns 0
powerScale(50); // returns 21.21...
powerScale(100); // returns 30
scaleSqrt
scaleSqrt
比例次是一种scalePow的特殊形式(k=0.5)通常用于通过面积来表征圆(而不用半径)(当使用圆的大小来表达数据值的大小时,通常最佳实践是使用和数据等比例的面积而非半径来表达)
var sqrtScale = d3.scaleSqrt()
.domain([0, 100])
.range([0, 30]); sqrtScale(0); // returns 0
sqrtScale(50); // returns 21.21...
sqrtScale(100); // returns 30
我们可以看到这个例子和上面的例子输出是一样的。
scaleLog
Log scales 使用数学对数函数(y = m * log(x) + b
)来映射domain和range.如果数据之间有指数关系的话,这个log比例尺最适合
var logScale = d3.scaleLog()
.domain([10, 100000])
.range([0, 600]); logScale(10); // returns 0
logScale(100); // returns 150
logScale(1000); // returns 300
logScale(100000); // returns 600
scaleTime
scaleTime和scaleLinear是类似的,唯一的区别是domain用于代表date的数组。(通常对于时间序列非常有用)
timeScale = d3.scaleTime() .domain([new Date(2016, 0, 1), new Date(2017, 0, 1)]) .range([0, 700]);
timeScale(new Date(2016, 0, 1)); // returns 0
timeScale(new Date(2016, 6, 1)); // returns 348.00...
timeScale(new Date(2017, 0, 1)); // returns 700
scaleSequential
scaleSequential用于将连续性的数据映射为由预定义或者定制的插值函数决定的range.(一个插值函数是一个接受0到1之间的数值而输出一个在两个数字,两个颜色值或者两个字符串之间的插值的函数)
D3提供了很多预定义的插值函数,其中包含着很多颜色相关的插值函数。例如,我们可以使用d3.interpolateRainbow来创建著名的彩虹色系图:
var sequentialScale = d3.scaleSequential()
.domain([0, 100])
.interpolator(d3.interpolateRainbow); sequentialScale(0); // returns 'rgb(110, 64, 170)'
sequentialScale(50); // returns 'rgb(175, 240, 91)'
sequentialScale(100); // returns 'rgb(110, 64, 170)'
需要注意的是插值函数决定了output range,因此你不需要对这个sequential scale来指定range
下面的列子展示由d3提供的其他颜色插值范围函数:
var linearScale = d3.scaleLinear()
.domain([0, 100])
.range([0, 600]); var sequentialScale = d3.scaleSequential()
.domain([0, 100]); var interpolators = [
'interpolateViridis',
'interpolateInferno',
'interpolateMagma',
'interpolatePlasma',
'interpolateWarm',
'interpolateCool',
'interpolateRainbow',
'interpolateCubehelixDefault'
]; var myData = d3.range(0, 100, 2); function dots(d) {
sequentialScale
.interpolator(d3[d]); d3.select(this)
.append('text')
.attr('y', -10)
.text(d); d3.select(this)
.selectAll('rect')
.data(myData)
.enter()
.append('rect')
.attr('x', function(d) {
return linearScale(d);
})
.attr('width', 11)
.attr('height', 30)
.style('fill', function(d) {
return sequentialScale(d);
});
} d3.select('#wrapper')
.selectAll('g.interpolator')
.data(interpolators)
.enter()
.append('g')
.classed('interpolator', true)
.attr('transform', function(d, i) {
return 'translate(0, ' + (i * 70) + ')';
})
.each(dots);
除了d3定义的这些颜色插值范围函数,也有一个 d3-scale-chromatic plugin, 提供著名的 ColorBrewer colour schemes.
Clamping
默认情况下 scaleLinear
, scalePow
, scaleSqrt
, scaleLog
, scaleTime
and scaleSequential
允许输入值在domain范围之外比如:
var linearScale = d3.scaleLinear() .domain([0, 10])
.range([0, 100]); linearScale(20); // returns 200
linearScale(-10); // returns -100
在这种情况下scale函数就使用外推算法来返回domain范围之外的输入值对应的返回值。
如果我们希望比例尺函数严格限制输入值必须在domain规定的范围内,我们则可以使用.clamp()调用
linearScale.clamp(true); linearScale(20); // returns 100
linearScale(-10); // returns 0
我们也可以随时通过clamp(false)来关闭这个功能
Nice
如果domain是由实际数据自动算出来的,比如使用d3.extent,d3.min/max来定义,那么起始和结束数据可能并不是整数。这本身并不是什么问题,但是如果使用这个比例尺函数来定义一个坐标轴,则显得很不整洁
var data = [0.243, 0.584, 0.987, 0.153, 0.433];
var extent = d3.extent(data); var linearScale = d3.scaleLinear()
.domain(extent)
.range([0, 100]);
我们通过使用.nice()函数,那么就将domain做了nice处理:
linearScale.nice();
需要注意的是.nice()函数必须在domain更新后每次都必须重新调用!
Multiple segments
The domain and range of scaleLinear
, scalePow
, scaleSqrt
, scaleLog
and scaleTime
usually consists of two values, but if we provide 3 or more values the scale function is subdivided into multiple segments:
通常scaleLinear,scalePow,scaleSqrt,scaleLog和scaleTime比例尺的domain和range都只包含两个数值:起始和结束值来定义,但是如果我们提供3个甚至更多的值,那么比例尺函数就将被划分为几个段segments:
var linearScale = d3.scaleLinear()
.domain([-10, 0, 10])
.range(['red', '#ddd', 'blue']); linearScale(-10); // returns "rgb(255, 0, 0)"
linearScale(0); // returns "rgb(221, 221, 221)"
linearScale(5); // returns "rgb(128, 128, 255)"
看看一个例子效果:
var xScale = d3.scaleLinear()
.domain([-10, 10])
.range([0, 600]); var linearScale = d3.scaleLinear()
.domain([-10, 0, 10])
.range(['red', '#ddd', 'blue']); var myData = [-10, -8, -6, -4, -2, 0, 2, 4, 6, 8, 10]; d3.select('#wrapper')
.selectAll('circle')
.data(myData)
.enter()
.append('circle')
.attr('r', 10)
.attr('cx', function(d) {
return xScale(d);
})
.style('fill', function(d) {
return linearScale(d);
});
典型地,多segment的比例尺通常用于区分正负值(正如上面例子所示)。只要domain和range的段数是相同的,我们可以使用任意多segments的比例尺.
Inversion
.invert()
方法接受一个range输出来反算对应的input domain
var linearScale = d3.scaleLinear()
.domain([0, 10])
.range([0, 100]); linearScale.invert(50); // returns 5
linearScale.invert(100); // returns 10
A common use case is when we want to convert a user’s click along an axis into a domain value:
这个方法的典型使用场景是我们将用户沿着某坐标轴点击坐标反转为domain值:
var width = 600; var linearScale = d3.scaleLinear()
.domain([-50, 50])
.range([0, width])
.nice(); var clickArea = d3.select('.click-area').node(); function doClick() {
var pos = d3.mouse(clickArea);
var xPos = pos[0];
var value = linearScale.invert(xPos);
d3.select('.info')
.text('You clicked ' + value.toFixed(2));
} // Construct axis
var axis = d3.axisBottom(linearScale);
d3.select('.axis')
.call(axis); // Update click area size
d3.select('.click-area')
.attr('width', width)
.attr('height', 40)
.on('click', doClick);
Scales with continuous input and discrete output
scaleQuantize
scaleQuantize
接受连续性的range输入而输出由range定义的离散输出
var quantizeScale = d3.scaleQuantize()
.domain([0, 100])
.range(['lightblue', 'orange', 'lightgreen', 'pink']); quantizeScale(10); // returns 'lightblue'
quantizeScale(30); // returns 'orange'
quantizeScale(90); // returns 'pink'
Each range value is mapped to an equal sized chunk in the domain so in the example above:
每一个range值都被映射为一个domain的等分量值区间
- 0 ≤ u < 25 is mapped to ‘lightblue’
- 25 ≤ u < 50 is mapped to ‘orange’
- 50 ≤ u < 75 is mapped to ‘lightgreen’
- 75 ≤ u < 100 is mapped to ‘pink’
u 是输入domain值
注意由于我们使用了.clamp指示,因此quantizeScale(-10)返回'lightblue',而quantizeScale(110)返回'pink'
scaleQuantile
scaleQuantile
将输入的连续性domain值映射为离散的值。domain是由数组来定义:
var myData = [0, 5, 7, 10, 20, 30, 35, 40, 60, 62, 65, 70, 80, 90, 100]; var quantileScale = d3.scaleQuantile()
.domain(myData)
.range(['lightblue', 'orange', 'lightgreen']); quantileScale(0); // returns 'lightblue'
quantileScale(20); // returns 'lightblue'
quantileScale(30); // returns 'orange'
quantileScale(65); // returns 'lightgreen'
var myData = [0, 5, 7, 10, 20, 30, 35, 40, 60, 62, 65, 70, 80, 90, 100]; var linearScale = d3.scaleLinear()
.domain([0, 100])
.range([0, 600]); var quantileScale = d3.scaleQuantile()
.domain(myData)
.range(['lightblue', 'orange', 'lightgreen']); d3.select('#wrapper')
.selectAll('circle')
.data(myData)
.enter()
.append('circle')
.attr('r', 3)
.attr('cx', function(d) {
return linearScale(d);
})
.style('fill', function(d) {
return quantileScale(d);
});
排好序的domain数组被均分为n个子范围,这里n是range数值的个数
这样在上面的例子中domain数组就被均分为3个groups:
- the first 5 values are mapped to ‘lightblue’
- the next 5 values to ‘orange’ and
- the last 5 values to ‘lightgreen’.
具体的domain均分点可以通过.quantiles()来访问
quantileScale.quantiles(); // returns [26.66..., 63]
如果range包含4个值,那么quantileScale将这样计算quantiles: 最低25%的数据被映射为range[0], 下一个25%则映射为range[1],以此类推。
scaleThreshold
scaleThreshold
映射连续的输入domain为由range来定义的离散值. 如果range值有n个,则将会有n-1个切分点
下面的例子我们在0
, 50和
100处切分
- u < 0 is mapped to ‘#ccc’
- 0 ≤ u < 50 to ‘lightblue’
- 50 ≤ u < 100 to ‘orange’
- u ≥ 100 to ‘#ccc’
这里u 是input value.
var thresholdScale = d3.scaleThreshold()
.domain([0, 50, 100])
.range(['#ccc', 'lightblue', 'orange', '#ccc']); thresholdScale(-10); // returns '#ccc'
thresholdScale(20); // returns 'lightblue'
thresholdScale(70); // returns 'orange'
thresholdScale(110); // returns '#ccc'
详细代码如下:
var linearScale = d3.scaleLinear()
.domain([-10, 110])
.range([0, 600]); var thresholdScale = d3.scaleThreshold()
.domain([0, 50, 100])
.range(['#ccc', 'lightblue', 'orange', '#ccc']); var myData = d3.range(-10, 110, 2); d3.select('#wrapper')
.selectAll('rect')
.data(myData)
.enter()
.append('rect')
.attr('x', function(d) {
return linearScale(d);
})
.attr('width', 9)
.attr('height', 30)
.style('fill', function(d) {
return thresholdScale(d);
});
Scales with discrete input and discrete output
scaleOrdinal
scaleOrdinal
将离散的domain values array映射为离散的range values array. domain input array指定可能的输入value,而range array则定义对应的可能的输出value.如果range array比domain array要短,则range array会重复循环
var myData = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] var ordinalScale = d3.scaleOrdinal()
.domain(myData)
.range(['black', '#ccc', '#ccc']); ordinalScale('Jan'); // returns 'black';
ordinalScale('Feb'); // returns '#ccc';
ordinalScale('Mar'); // returns '#ccc';
ordinalScale('Apr'); // returns 'black';
完整代码如下:
var myData = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] var linearScale = d3.scaleLinear()
.domain([0, 11])
.range([0, 600]); var ordinalScale = d3.scaleOrdinal()
.domain(myData)
.range(['black', '#ccc', '#ccc']); d3.select('#wrapper')
.selectAll('text')
.data(myData)
.enter()
.append('text')
.attr('x', function(d, i) {
return linearScale(i);
})
.text(function(d) {
return d;
})
.style('fill', function(d) {
return ordinalScale(d);
});
By default if a value that’s not in the domain is used as input, the scale will implicitly add the value to the domain:
默认情况下如果输入值不在domain范围内,scale会隐含地添加这个值到domain中去。
ordinalScale('Monday'); // returns 'black';
如果这不是我们想要的行为,我们可以使用.unknown()函数来设定一个unknown values
ordinalScale.unknown('Not a month');
ordinalScale('Tuesday'); // returns 'Not a month'
D3也提供一些预定义好的color scheme
var ordinalScale = d3.scaleOrdinal()
.domain(myData)
.range(d3.schemePaired);
var myData = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] var linearScale = d3.scaleLinear()
.domain([0, 11])
.range([0, 600]); var ordinalScale = d3.scaleOrdinal()
.domain(myData)
.range(d3.schemePaired); d3.select('#wrapper')
.selectAll('text')
.data(myData)
.enter()
.append('text')
.attr('x', function(d, i) {
return linearScale(i);
})
.text(function(d) {
return d;
})
.style('fill', function(d) {
return ordinalScale(d);
});
(Note that the Brewer colour schemes are defined within a separate file d3-scale-chromatic.js.)
scaleBand
当创建一个bar chart时,scaleBand可以帮助我们来决定bar的几何形状,并且已经考虑好了各个bar之间的padding值。
输入的domain通过一个数值数组来指定(每个值都对应一个band)并且range通过bands的最小和最大范围来定义(也就是bar chart的整个宽度)
scaleBand会将range划分为n个bands(n是domain数组的数值个数)并且在考虑padding的情况下计算出每个band的位置和宽度.
var bandScale = d3.scaleBand()
.domain(['Mon', 'Tue', 'Wed', 'Thu', 'Fri'])
.range([0, 200]); bandScale('Mon'); // returns 0
bandScale('Tue'); // returns 40
bandScale('Fri'); // returns 160
每个band的宽度可以使用.bandWidth()来访问。
bandScale.bandwidth(); // returns 40
有两种padding可以被配置:
paddingInner
which specifies (as a percentage of the band width) the amount of padding between each bandpaddingOuter
which specifies (as a percentage of the band width) the amount of padding before the first band and after the last band
我们在上面的例子中添加一点inner padding
bandScale.paddingInner(0.05); bandScale.bandWidth(); // returns 38.38...
bandScale('Mon'); // returns 0
bandScale('Tue'); // returns 40.40...
Putting this all together we can create this bar chart:
上面加起来我们可以得到下面的图表:
var myData = [
{day : 'Mon', value: 10},
{day : 'Tue', value: 40},
{day : 'Wed', value: 30},
{day : 'Thu', value: 60},
{day : 'Fri', value: 30}
]; var bandScale = d3.scaleBand()
.domain(['Mon', 'Tue', 'Wed', 'Thu', 'Fri'])
.range([0, 200])
.paddingInner(0.05); d3.select('#wrapper')
.selectAll('rect')
.data(myData)
.enter()
.append('rect')
.attr('y', function(d) {
return bandScale(d.day);
})
.attr('height', bandScale.bandwidth())
.attr('width', function(d) {
return d.value;
});
scalePoint
scalePoint
将离散的输入数值映射为在range内等距的点:
var pointScale = d3.scalePoint()
.domain(['Mon', 'Tue', 'Wed', 'Thu', 'Fri'])
.range([0, 500]); pointScale('Mon'); // returns 0
pointScale('Tue'); // returns 125
pointScale('Fri'); // returns 500
完整代碼:
var myData = [
{day : 'Mon', value: 10},
{day : 'Tue', value: 40},
{day : 'Wed', value: 30},
{day : 'Thu', value: 60},
{day : 'Fri', value: 30}
]; var pointScale = d3.scalePoint()
.domain(['Mon', 'Tue', 'Wed', 'Thu', 'Fri'])
.range([0, 600]); d3.select('#wrapper')
.selectAll('circle')
.data(myData)
.enter()
.append('circle')
.attr('cx', function(d) {
return pointScale(d.day);
})
.attr('r', 4);
点之间的距离可以通过.step()来访问:
pointScale.step(); // returns 125
outside padding可以通过和padding to point spacing的比例来指定。比如,如果希望设定outside padding为point spacing的1/4,那么可以这样设置:
pointScale.padding(0.25); pointScale('Mon'); // returns 27.77...
pointScale.step(); // returns 111.11...
参考阅读
d3js scales深入理解的更多相关文章
- d3js layout 深入理解
D3 layouts help you create more advanced visualisations such as treemaps: D3 layouts帮助您创造更加高级复杂的可视化图 ...
- d3js shape深入理解
本文将视图了解d3js提供的帮助我们创建矢量图形的helper函数,比如下面的: http://d3indepth.com/shapes/ lines curves pie chart segment ...
- d3js selections深入理解
D3 selections选择DOM元素以便可以对这些dom元素做相应的操作,比如:更改其style,修改其属性,执行data-join操作,或者插入.删除相应elements 比如,如果给定5个ci ...
- d3js data joins深入理解
Data joins 给定一个数据数组和一个 D3 selection 我们就可以attach或者说是'join'数组中的每个数据到selection中的每个元素上. 这将使得我们的数据和可视化元素 ...
- Angular指令渗透式理解
通过一段时间对angular指令的使用,理解了angular指令的意义,下面逐一介绍一下. ng-app:定义一个angualr模块,表示angular作用的范围,如下代码: ng-app在html标 ...
- d3可视化实战02:理解d3数据驱动的真正含义
前文中已经提到,SVG从诞生之初起就可以非常方便地使用javascript脚本语言来进行其DOM对象的控制.当然,控制的方法有很多,有直接控制SVG对象的方法,例如使用原生js:有帮你封装一下图形接口 ...
- D3js初探及数据可视化案例设计实战
摘要:本文以本人目前所做项目为基础,从设计的角度探讨数据可视化的设计的方法.过程和结果,起抛砖引玉之效.在技术方案上,我们采用通用web架构和d3js作为主要技术手段:考虑到项目需求,这里所做的可视化 ...
- d3js可视化策略
d3js是数据驱动图形的思路.基本可以这么理解,有什么样的图形,后面基本就有类似结构的数据.大概思路步骤如下: 一.适配数据格式 这一步主要是为第二部服务,第一步的结果作为第二部的入参. 比如,画层级 ...
- 你可以这么理解五种I/O模型
因为项目需要,接触和使用了Netty,Netty是高性能NIO通信框架,在业界拥有很好的口碑,但知其然不知其所以然. 所以本系列文章将从基础开始学起,深入细致的学习NIO.本文主要是介绍五种I/O模型 ...
随机推荐
- C#操作符??,?,?:功能解析
??操作符:叫做空合并操作符,它会对左右两个操作数进行判断,如果左边的数不为空,就返回左边的数,否则返回右边的数. ?操作符:语法糖,表示可空类型,可空类型也是值类型,它是包含null值的值类型,可通 ...
- MVC设计模式思想及简单实现
一.什么是MVC MVC即Model-View-Controller(模型-视图-控制器)是一种软件设计模式,最早出现在Smalltalk语言中,后被Sun公司推荐为Java EE平台的设计模式. M ...
- PHP开发模式之-单例模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Db{ private function __construct(){} private static ...
- 简单几步用纯CSS3实现3D翻转效果
作为前端开发人员的必修课,CSS3翻转能带我们完成许多基本动效,本期我们将用CSS3实现hover翻转效果~ 第一步非常简单,我们简单画1个演示方块,为其 添加transition和transform ...
- Android 程序结构
Android程序在创建的时,Android studio就为其构建了基本结构,设计者可在此结构上开发应用程序, manifests :用于存放AndroidManifest.xml文件(又称清单文件 ...
- windowns10安装httpd
下载页面:https://www.apachehaus.com/cgi-bin/download.plx 下载内容:httpd-2.4.38-o102r-x64-vc14-r2.zip 解压到本地磁盘 ...
- MUI开发大全
最近很久没有更新博客了,因为一直在学习前端h5 手机app的开发.曾经一度觉得自己css和js学得不错,进入到前端领域后才发现水很深~,写代码时HBuilder和VS混用,HBuilder的快捷键和代 ...
- iOS 11: CORE ML—浅析
本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/OWD5UEiVu5JpYArcd2H9ig 作者:l ...
- WPF 通过透明度遮罩和变换制作倒影效果
倒影效果 代码 <Canvas xmlns="http://schemas.microsoft.com/client/2007" xmlns:x="http:/ ...
- ping命令的七种用法,看完瞬间成大神
一.ping基本使用详解 在网络中ping是一个十分强大的TCP/IP工具.它的作用主要为: 1.用来检测网络的连通情况和分析网络速度 2.根据域名得到服务器IP 3.根据ping返回的TTL值来判断 ...