線圖 面積圖
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.line {
fill: none;
stroke: steelblue;
stroke-width: 2;
}
.grid-line{
stroke: black;
shape-rendering: crispEdges;
stroke-opacity: .2;
}
.area {
stroke: none;
fill: steelblue;
fill-opacity: .2;
}
.dot {
fill: #fff;
stroke: steelblue;
}
</style>
</head>
<body>
<script src="../d3.js"></script>
<script>
function areaChart() {
const _chart = {}
let _width = 600,
_height = 300,
_margins = {
top: 30,
right: 30,
bottom: 30,
left: 30
},
_colors = d3.scaleOrdinal(d3.schemeCategory10),
_data = []
let _x,
_y,
_svg,
_body
_chart.width = function (width) {
if (!arguments.length) return _width
_width = width
return _chart
}
_chart.height = function (height) {
if (!arguments.length) return _height
_height = height
return _chart
}
_chart.margins = function (margins) {
if (!arguments.length) return _margins
_margins = margins
return _chart
}
_chart.colors = function (colorScale) {
if (!arguments.length) return _colors
_colors = colorScale
return _chart
}
_chart.x = function (xScale) {
if (!arguments.length) return _x
_x = xScale
return _chart
}
_chart.y = function (yScale) {
if (!arguments.length) return _y
_y = yScale
return _chart
}
_chart.addSeries = function (series) {
_data.push(series)
return _chart
}
_chart.render = function () {
if (!_svg) {
_svg = d3.select('body')
.append('svg')
.attr('width', _width)
.attr('height', _height)
renderAxes()
}
renderBody()
}
function renderAxes() {
const axes = _svg.append('g')
.classed('axes', true)
axes.append('g')
.classed('x-axis', true)
.attr('transform', `translate(${_margins.left}, ${_height - _margins.bottom})`)
.call(d3.axisBottom().scale(_x.range([0, _width - _margins.left - _margins.right])))
axes.selectAll('g.x-axis g.tick')
.append('line')
.classed('grid-line', true)
.attr('x1', 0)
.attr('y1', 0)
.attr('x2', 0)
.attr('y2', -(_height - _margins.top - _margins.bottom))
axes.append('g')
.classed('y-axis', true)
.attr('transform', `translate(${_margins.left}, ${_margins.top})`)
.call(d3.axisLeft().scale(_y.range([_height - _margins.top - _margins.bottom, 0])))
axes.selectAll('g.y-axis g.tick')
.append('line')
.classed('grid-line', true)
.attr('x1', 0)
.attr('y1', 0)
.attr('x2', _width - _margins.left - _margins.right)
.attr('y2', 0)
}
function renderBody() {
if (!_body) {
_body = _svg.append('g')
.classed('body', true)
.attr('transform', `translate(${_margins.left}, ${_margins.top})`)
}
renderLines()
renderAreas()
renderDots()
}
function renderLines() {
const line = d3.line()
.x((d) => _x(d.x))
.y((d) => _y(d.y))
const pathLines = _body.selectAll('path.line')
.data(data)
pathLines.enter()
.append('path')
.merge(pathLines)
.classed('line', true)
.style('stroke', (d, i) => _colors(i))
.transition()
.attr('d', d => line(d))
}
function renderAreas() {
const area = d3.area()
.x(d => _x(d.x))
.y0(_y(0))
.y1(d => _y(d.y))
const pathAreas = _body.selectAll('path.area')
.data(data)
pathAreas.enter()
.append('path')
.merge(pathAreas)
.classed('area', true)
.style('fill', (d, i) => _colors(i))
.transition()
.attr('d', d => area(d))
}
function renderDots() {
_data.forEach((data, index) => {
const circles =_body.selectAll(`circle._${index}`)
.data(data)
circles.enter()
.append('circle')
.merge(circles)
.attr('class', `dot _${index}`)
.style('stroke', () => _colors(index))
.transition()
.attr('cx', d => _x(d.x))
.attr('cy', d => _y(d.y))
.attr('r', 4.5)
})
}
return _chart
}
const numberOfSeries = 2,
numberOfDataPoint = 11
data = []
for (let i = 0; i < numberOfSeries; i++) {
data.push(d3.range(numberOfDataPoint).map((i) => ({x: i, y: Math.random() * 10})))
}
const chart = areaChart()
.x(d3.scaleLinear().domain([0, 10]))
.y(d3.scaleLinear().domain([0, 10]))
data.forEach(series => {
chart.addSeries(series)
})
chart.render()
function update() {
for (let i = 0; i < data.length; i++) {
let series = data[i]
series.length = 0
for (let i = 0; i < numberOfDataPoint; i++) {
series.push({x: i, y: Math.random() * 10})
}
}
chart.render()
}
</script>
<button onclick="update()">update</button>
</body>
</html>
複製程式碼
效果如下:
我們用工廠模式,areaChart 返回一個 chart物件,我們可以向其設定渲染資料和一些可以自定義的屬性項,渲染的邏輯放在 render() 函式中。
if (!_svg) {
_svg = d3.select('body')
.append('svg')
.attr('width', _width)
.attr('height', _height)
renderAxes()
}
renderBody()
複製程式碼
座標軸只會在svg初始化的時候渲染一次。注意我們這裡是將座標軸和圖表內容主題分開的。
renderAxes() 裡面就是熟悉的渲染座標軸和柵格線的邏輯。
然後在初始化 body 時,位移到邊距的位置,這樣可以消除掉邊距的影響,因為 body 內的座標都是以 body 左上角為原點的。
接著,我們分別在renderLine() renderArea() renderDots() 內完成線條、面積、資料點的渲染。都是之前我們熟悉的程式碼。
散點圖
散點圖的工廠函式和上面類似,只實現了必須的功能,其餘的設定函式可以自行新增。
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.grid-line{
stroke: black;
shape-rendering: crispEdges;
stroke-opacity: .2;
}
</style>
</head>
<body>
<script src="../d3.js"></script>
<script>
function scatterPlotChart() {
const _chart = {}
let _width = 500,
_height = 500,
_margins = {top: 30, right: 30, bottom: 30, left: 30},
_colors = d3.scaleOrdinal(d3.schemeCategory10),
_x,
_y,
_data = [],
_svg,
_body,
_symbolTypes = d3.scaleOrdinal()
.range(d3.symbols)
_chart.render = function () {
if (!_svg) {
_svg = d3.select('body')
.append('svg')
.attr('width', _width)
.attr('height', _height)
renderAxes()
}
renderBody()
}
function renderAxes() {
const axes = _svg.append('g')
.classed('axes', true)
axes.append('g')
.classed('x-axis', true)
.attr('transform', `translate(${_margins.left}, ${_height - _margins.bottom})`)
.call(d3.axisBottom().scale(_x.range([0, _width - _margins.left - _margins.right])))
axes.selectAll('g.x-axis g.tick')
.append('line')
.classed('grid-line', true)
.attr('x1', 0)
.attr('y1', 0)
.attr('x2', 0)
.attr('y2', -(_height - _margins.top - _margins.bottom))
axes.append('g')
.classed('y-axis', true)
.attr('transform', `translate(${_margins.left}, ${_margins.top})`)
.call(d3.axisLeft().scale(_y.range([_height - _margins.top - _margins.bottom, 0])))
axes.selectAll('g.y-axis g.tick')
.append('line')
.classed('grid-line', true)
.attr('x1', 0)
.attr('y1', 0)
.attr('x2', _width - _margins.left - _margins.right)
.attr('y2', 0)
}
function renderBody() {
if (!_body) {
_body = _svg.append('g')
.classed('body', true)
.attr('transform', `translate(${_margins.left}, ${_margins.right})`)
}
renderSymbols()
}
function renderSymbols() {
_data.forEach((data, i) => {
const symbols = _body.selectAll('path._' + i)
.data(data)
symbols.enter()
.append('path')
.merge(symbols)
.attr('class', 'symbol _' + i)
.style('fill', d => _colors(i))
.transition()
.attr('transform', d => `translate(${_x(d.x)}, ${_y(d.y)})`)
.attr('d', d3.symbol().type(_symbolTypes(i)))
})
}
_chart.addSeries = function (series) {
_data.push(series)
return _chart
}
_chart.x = function (x) {
if (!arguments.length) return _x
_x = x
return _chart
}
_chart.y = function (y) {
if (!arguments.length) return _y
_y = y
return _chart
}
return _chart
}
const numberOfSeries = 5,
numberOfDataPoint = 11,
data = []
for (let i = 0; i < numberOfSeries; i++) {
data.push(d3.range(numberOfDataPoint).map(i => ({x: Math.random() * 10, y: Math.random() * 10})))
}
const chart = scatterPlotChart()
.x(d3.scaleLinear().domain([0, 10]))
.y(d3.scaleLinear().domain([0, 10]))
data.forEach(series => {
chart.addSeries(series)
})
chart.render()
</script>
</body>
</html>
複製程式碼
效果如下:
主要程式碼如下:
const _symbolTypes = d3.scaleOrdinal()
.range(d3.symbols)
function renderSymbols() {
_data.forEach((data, i) => {
const symbols = _body.selectAll('path._' + i)
.data(data)
symbols.enter()
.append('path')
.merge(symbols)
.attr('class', 'symbol _' + i)
.style('fill', d => _colors(i))
.transition()
.attr('transform', d => `translate(${_x(d.x)}, ${_y(d.y)})`)
.attr('d', d3.symbol().type(_symbolTypes(i)))
})
}
複製程式碼
我們先建立了個有序尺度,它對應著不同的符號形狀,d3.symbols 是 d3 內建符號形狀的陣列。
在 renderSymbol() 函式中 我們為每個資料項都建立 path 並呼叫 symbol 生成器為屬性 d 賦值。symbol.type(_symbolTypes(i))來設定 symbol 的型別為索引所對應著的尺度中的型別。
氣泡圖
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.grid-line{
stroke: black;
shape-rendering: crispEdges;
stroke-opacity: .2;
}
</style>
</head>
<body>
<script src="../d3.js"></script>
<script>
function scatterPlotChart() {
const _chart = {}
let _width = 500,
_height = 500,
_margins = {top: 30, right: 30, bottom: 30, left: 30},
_colors = d3.scaleOrdinal(d3.schemeCategory10),
_x,
_y,
_r,
_data = [],
_svg,
_body
_chart.render = function () {
if (!_svg) {
_svg = d3.select('body')
.append('svg')
.attr('width', _width)
.attr('height', _height)
renderAxes()
}
renderBody()
}
function renderAxes() {
const axes = _svg.append('g')
.classed('axes', true)
axes.append('g')
.classed('x-axis', true)
.attr('transform', `translate(${_margins.left}, ${_height - _margins.bottom})`)
.call(d3.axisBottom().scale(_x.range([0, _width - _margins.left - _margins.right])))
axes.selectAll('g.x-axis g.tick')
.append('line')
.classed('grid-line', true)
.attr('x1', 0)
.attr('y1', 0)
.attr('x2', 0)
.attr('y2', -(_height - _margins.top - _margins.bottom))
axes.append('g')
.classed('y-axis', true)
.attr('transform', `translate(${_margins.left}, ${_margins.top})`)
.call(d3.axisLeft().scale(_y.range([_height - _margins.top - _margins.bottom, 0])))
axes.selectAll('g.y-axis g.tick')
.append('line')
.classed('grid-line', true)
.attr('x1', 0)
.attr('y1', 0)
.attr('x2', _width - _margins.left - _margins.right)
.attr('y2', 0)
}
function renderBody() {
if (!_body) {
renderBodyClip()
_body = _svg.append('g')
.classed('body', true)
.attr('transform', `translate(${_margins.left}, ${_margins.right})`)
.attr('clip-path', 'url(#body-clip)')
}
renderBubbles()
}
function renderBodyClip() {
_svg.append('defs')
.append('clipPath')
.attr('id', 'body-clip')
.append('rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', _width - _margins.left - _margins.right)
.attr('height', _height - _margins.top - _margins.bottom)
}
function renderBubbles() {
_r.range([0, 20])
_data.forEach((data, i) => {
const bubbles = _body.selectAll('circle._' + i)
.data(data)
bubbles.enter()
.append('circle')
.merge(bubbles)
.attr('class', 'circle _' + i)
.style('stroke', _colors(i))
.style('fill', _colors(i))
.transition()
.attr('cx', d => _x(d.x))
.attr('cy', d => _x(d.y))
.attr('r', d => _r(d.r))
})
}
_chart.addSeries = function (series) {
_data.push(series)
return _chart
}
_chart.x = function (x) {
if (!arguments.length) return _x
_x = x
return _chart
}
_chart.y = function (y) {
if (!arguments.length) return _y
_y = y
return _chart
}
_chart.r = function (r) {
if (!arguments.length) return _r
_r = r
return _chart
}
return _chart
}
const numberOfSeries = 5,
numberOfDataPoint = 11,
data = []
for (let i = 0; i < numberOfSeries; i++) {
data.push(d3.range(numberOfDataPoint).map(i => ({x: Math.random() * 10, y: Math.random() * 10, r: Math.random() * 10})))
}
const chart = scatterPlotChart()
.x(d3.scaleLinear().domain([0, 10]))
.y(d3.scaleLinear().domain([0, 10]))
.r(d3.scalePow().exponent(2).domain([0, 10]))
data.forEach(series => {
chart.addSeries(series)
})
chart.render()
</script>
</body>
</html>
複製程式碼
效果如下:
氣泡圖的渲染和之前相比也只是多了個 r 屬性的賦值,另外,我們還給 body 新增了個裁剪區域,用來限制圖形越出 body,如下:
_svg.append('defs')
.append('clipPath')
.attr('id', 'body-clip')
.append('rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', _width - _margins.left - _margins.right)
.attr('height', _height - _margins.top - _margins.bottom)
_body = _svg.append('g')
.classed('body', true)
.attr('transform', `translate(${_margins.left}, ${_margins.right})`)
.attr('clip-path', 'url(#body-clip)')
複製程式碼
條形圖
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.grid-line{
stroke: black;
shape-rendering: crispEdges;
stroke-opacity: .2;
}
.bar {
fill: #4682b4;
}
</style>
</head>
<body>
<script src="../d3.js"></script>
<script>
function scatterPlotChart() {
const _chart = {}
let _width = 500,
_height = 500,
_margins = {top: 30, right: 30, bottom: 30, left: 30},
_x,
_y,
_data = [],
_svg,
_body
_chart.render = function () {
if (!_svg) {
_svg = d3.select('body')
.append('svg')
.attr('width', _width)
.attr('height', _height)
renderAxes()
}
renderBody()
}
function renderAxes() {
const axes = _svg.append('g')
.classed('axes', true)
axes.append('g')
.classed('x-axis', true)
.attr('transform', `translate(${_margins.left}, ${_height - _margins.bottom})`)
.call(d3.axisBottom().scale(_x.range([0, _width - _margins.left - _margins.right])))
axes.selectAll('g.x-axis g.tick')
.append('line')
.classed('grid-line', true)
.attr('x1', 0)
.attr('y1', 0)
.attr('x2', 0)
.attr('y2', -(_height - _margins.top - _margins.bottom))
axes.append('g')
.classed('y-axis', true)
.attr('transform', `translate(${_margins.left}, ${_margins.top})`)
.call(d3.axisLeft().scale(_y.range([_height - _margins.top - _margins.bottom, 0])))
axes.selectAll('g.y-axis g.tick')
.append('line')
.classed('grid-line', true)
.attr('x1', 0)
.attr('y1', 0)
.attr('x2', _width - _margins.left - _margins.right)
.attr('y2', 0)
}
function renderBody() {
if (!_body) {
_body = _svg.append('g')
.classed('body', true)
.attr('transform', `translate(${_margins.left}, ${_margins.right})`)
.attr('clip-path', 'url(#body-clip)')
renderBodyClip()
}
renderBubbles()
}
function renderBodyClip() {
_svg.append('defs')
.append('clipPath')
.attr('id', 'body-clip')
.append('rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', _width - _margins.left - _margins.right)
.attr('height', _height - _margins.top - _margins.bottom)
}
function renderBubbles() {
const padding = 2
const bars = _body.selectAll('rect.bar').data(_data)
bars.enter()
.append('rect')
.merge(bars)
.classed('bar', true)
.transition()
.attr('x', d => _x(d.x))
.attr('y', d => _y(d.y))
.attr('height', d => _height - _margins.top - _margins.bottom - _y(d.y))
.attr('width', d => Math.floor((_width - _margins.left - _margins.right) / _data.length) - padding)
}
_chart.addSeries = function (series) {
_data = series
return _chart
}
_chart.x = function (x) {
if (!arguments.length) return _x
_x = x
return _chart
}
_chart.y = function (y) {
if (!arguments.length) return _y
_y = y
return _chart
}
return _chart
}
const numberOfSeries = 5,
numberOfDataPoint = 10,
data = []
for (let i = 0; i < numberOfSeries; i++) {
data.push(d3.range(numberOfDataPoint).map(i => ({x: i, y: Math.random() * 10})))
}
const chart = scatterPlotChart()
.x(d3.scaleLinear().domain([0, 10]))
.y(d3.scaleLinear().domain([0, 10]))
data.forEach(series => {
chart.addSeries(series)
})
chart.render()
</script>
</body>
</html>
複製程式碼
效果如下: