昨天已经介绍完散布图了,大致上与散布图的作法大同小异,差别在於气泡本身也就是circle
,也能呈现一个变数值,在svg里面我们将设定其属性r来定义气泡的半径大小。
本日预计使用实价登录网站的台南交易资料来实作一个气泡图、其中x轴表示售出时的建物面积、y轴表示售出时的土地面积,其气泡的大小表示成交价格。
为了肉眼辨别视觉差异,让使用者观看面积有总价上的感觉,因此R必须再开平方根
避免渲染出来的图形造成次方倍的扭曲解读。
这边使用数学式子来稍微讲解一下关系
关系表格如下假设半径是r
半径与面积关系如下
r:πr²
半径( r ) | 1 | 2 | 3 | 4 |
---|---|---|---|---|
半径平方r² | 1 | 4 | 9 | 16 |
圆面积πr² | π | 4π | 9π | 16π |
套用到上述的例子当中,
半径1的半径平方是1圆面积是π
半径1的半径平方是4圆面积是4π
写数学式子如下
1:π = 4:4π = 9:9π
故圆面积是与半径平方成正比
input
栏位来输入x轴、y轴的范围并加上一个轴线更新的按钮Math.sqrt()
将其半径开平方根,以便绘图完的圆形面积比与房屋价格比相同,与上一篇文章大同小异会是设置scaleX和scaleY来进行资料转换至svg的座标点位置。<div class="wrap">
<select id="district">
</select>
<div class="area-str">建物面积最小值</div>
<input type="number" id="min-bulid" value=0>
<span>平方公尺</span>
<div class="area-str">建物面积最大值</div>
<input type="number" id="max-bulid" value=300>
<span>平方公尺</span>
<div class="area-str">土地面积最大值</div>
<input type="number" id="max-land" value=500>
<span>平方公尺</span>
<div class="area-str">土地面积最小值</div>
<input type="number" id="min-land" value=0>
<span>平方公尺</span>
<button id="btn">轴线更新</button>
</div>
这边在input
使用id属性
以便日後要使用js选取时更为方便。
const groupData = d3.group(data,d=>d["乡镇市区"]);
groupData.delete("The villages and towns urban district");
console.log(groupData);
const districtAry = [...groupData.keys()];
let defaultDistrict = districtAry[0];
for (let i=0;i<districtAry.length;i++) {
d3.select("#district").append("option").text(districtAry[i]);
}
如果不熟悉map或展开运算不熟的话可以参考MDN
Map物件操作MDN介绍
展开运算子
可以console.log(groupData.get(defaultDistrict))
查看资料内容
为了确保他是否为一个阵列再次使用Array.isArray()
方法检查
确认无误後使用阵列的操作方法进行资料筛选,本次希望以交易标的是房子为主,因此过滤掉交易标的为车位和土地的资料
使用forEach()
转换将土地、建物、总价的资料从字串型态转换成数字型态
程序码如下
console.log(groupData.get(defaultDistrict));
console.log(Array.isArray(groupData.get(defaultDistrict)));
const house = groupData.get(defaultDistrict).filter(function (d) {
if (d["交易标的"] !== "土地" && d["交易标的"] !== "车位") {
return d;
}
});
house.forEach(el => {
el["建物移转总面积平方公尺"] = +(el["建物移转总面积平方公尺"]);
el["土地移转总面积平方公尺"] = +(el["土地移转总面积平方公尺"]);
el["总价元"] = +el["总价元"];
});
console出来的资料如下图
取得HTML元素虽然可以使用原生Js的getElementById
,不过这边介绍一个在d3的select底下的一个方法,selection.node()
尝试着撰写以下程序码
console.log(d3.select("#min-bulid").node());
console.log(document.getElementById("min-bulid"));
console.log(d3.select("#min-bulid").node()===document.getElementById("min-bulid"));
这边可以发现透过d3的selecttion.node()
的API和document.getElementById()
的方法所得到的内容是相等的情形如下图
因此接下来我们可以撰写以下的程序码来得到使用者输入的值并且将其设置为座标比例尺的上限和下限
具体程序码如下
let minBuildArea =d3.select("#min-bulid").node().value;
let maxBuildArea =d3.select("#max-bulid").node().value;
let minLandArea = d3.select("#min-land").node().value;
let maxLandArea =d3.select("#max-land").node().value;
const scaleX = d3.scaleLinear().domain([minBuildArea,maxBuildArea]).range([0,800]).clamp(true);
const scaleY = d3.scaleLinear().domain([maxLandArea,minLandArea]).range([0,800]).clamp(true);
与昨天不同的是这次我们要再加入一个ScaleR来做为圆点的半径,我们将其范围映射到5到900,换句话说希望圆点的半径最小有5,由於之後会开平方根,因此最大值也顶多占座标轴的√900=30而已
然後房价输入价位最大值设定1亿(这样范围应该可以容纳大多数的房价了吧???),
因此添加以下程序码
let minPrice = 0;
let maxPrice = 10000000;
const scaleR = d3.scaleLinear().domain([minPrice,maxPrice]).range([5,900]).clamp(false);
最後我们将刚刚所做出来的比例尺配合axisAPI制作座标轴程序码如下
const axisX = d3.axisBottom(scaleX)
.ticks(15)
.tickFormat(d=>(d+"m²"))
.tickSize(-800);
const axisY = d3.axisLeft(scaleY)
.ticks(15)
.tickFormat(d=>(d+"m²"))
.tickSize(-800);
const gX = svg.append("g")
.attr("transform",`translate(50,850)`)
.classed("xAxis",true);
const gY = svg.append("g")
.attr("transform",`translate(50,50)`)
.classed("yAxis",true);
这边简单介绍一下selection.call
,先前我们再进行座标渲染的时候都是使用axis(selection)
的方式,例如axisBottom( svg.append("g"))
,然而d3Js是大量使用方法链所形成的一个套件,因此如果使用axisY(gY)的方式来渲染座标轴的话,不便将方法链形成(由於回传的内容不能传递给下一个函式使用。)
我们可以撰写以下程序码来观看差异
console.log(axisY(gY));
console.log(gY.call(axisY));
这边观看回传的东西可以发现axisY(gY)
回传的是undefined,但是Y.call(axisY)
可以回传一个物件,所以当我们执行axisY(gY)
或gY.call(axisY)
的时候虽然都可以将座标轴绘制到网页画面上,但是使用call的方式会回传物件以便後续给函式继续接续方法链。
这边也可以观看官方文件的说明,他表示下列两种执行方式都是一样的结果,如下图
因此我们可以使用call将座标轴添加至画面上并且加入动画如以下程序码
gX.transition().duration(1000).call(axisX);
gY.transition().duration(1000).call(axisY);
呈现结果如下图
接下来我们进行绘制圆型
程序码如下
const gCircle = svg.append("g");
gCircle.selectAll("circle")
.data(house)
.join("circle")
.attr("transform", "translate(50,50)")
.attr("fill","rgba(255,0,0,.1)")
.attr("cx",d=>(scaleX(d["土地移转总面积平方公尺"])))
.attr("cy",d=>(scaleY(d["建物移转总面积平方公尺"])))
.attr("r",d=>{return Math.sqrt(scaleR(d["总价元"]))})
基本上资料绑定的data和透过scale函式将资料转换到圆点的x和y的位置与昨天的制作方式大同小异。
这边比较不一样的地方是R需要带入ScaleR()
将房屋价格带入比例尺函式映射出圆形的半径,记得再使用Math.sqrt()进行开平方根。
到此为止应当可以映射出圆点如下图
本日浅谈了程序构想,使用一些方法处理资料并且介绍了先前未提及的selection.call()和selection.node(),另外依序将三个数值带Scale()
後添加动画,明天将处理掉圆形超出范围的部分和轴线更新以及行政区切换,先行预告一下可能需要有一点座标平面的概念,以上。
>>: 23 - 建立结构化的 Log (1/4) - Elastic Common Schema 结构化 Log 的规范
第 17 天! 昨天我们建立了, To Do List 专案 这是我们预期的画面, 昨天做到 今天我...
fetch() 是 ES6 的新语法,主要是搭配 Promise来执行请求网站和请求後获取 Resp...
那我就延续上一篇的实作吧! 已经将会用到的套件装上,并且在网站的控制室找到所需的资讯位置,接下来就是...
我慢慢的告诉小路,我记得的事,包括那个R... 小路一直强调,只有我一个人参加,然後,不会有什麽NP...
用键盘输入讯息,对年轻人或许稀松平常,但对长者而言,使用语音的方式或许更轻松。所以除了画面字体放大外...