• 0

  • 482

D3基础03 - 比例尺与坐标轴

3星期前

写在前面:

  1. 本文是单纯的个人学习总结和课堂笔记整理,大部分知识点来自“开课吧”线上直播课程,以及官方资料。
  2. 由于目前仍处于前端学习、入门阶段,因此对于知识的掌握和理解难免会有偏差,因此该文章仅仅是个人记录,请查阅者不要以此作为资料依据。

D3基础03 - 比例尺与坐标轴

我们可以利用D3提供的比例尺、坐标轴以及绘图功能,实现一个简单的柱状图。并且通过增加鼠标事件、transition动画和缓动动画来优化柱状图的用户体验,接下来我们逐步看一下如何实现柱状图。

比例尺

D3的比例尺功能可以让我们自由的建立起一个坐标系统,并设置每一个坐标轴的分量大小,同时可以让坐标轴的刻度与画布坐标轴的像素点对应起来。

坐标轴的刻度可以分成两种:

  • 类目型坐标轴:坐标轴是由不连续的元素组成,如[张三,李四,王五]
  • 数值型坐标轴:坐标轴是由连续的数值组成,如[0,1,2],这里指的是数组,不是数学上的区间,这一点需要区分开,后续以中括号出现的数值对都是数组。

因此比例尺规则也就与之对应的有两种:

  • scaleBand 类目轴型比例尺规则
  • scaleLinear 数值轴型比例尺规则

利用比例尺建立x坐标轴

  • x轴是类目型坐标轴
  • 首先需要获取x轴数据的长度并将每个数据的索引存储起来
  • 然后设置x坐标轴起始和结束的坐标轴像素位置
  • 然后利用比例尺将数据与坐标轴刻度对应起来
  • 基于x轴比例尺建立x轴坐标生成器
  • 调用生成器绘制x轴,并将数据填充进去。

废话不多说,我们还是用代码说话:

/*===========1-必备数据===========*/
/*categories 类目数据*/
const categories = ['html', 'css', 'js'];

/*===========2-建立容器对象===========*/
/*获取main 容器*/
const main = d3.select('#main')

/*声明绘图框尺寸
    width 宽度,600
    height 高度,600
*/
const width = 600
const height = 600

/*建立svg 对象
 *   svg 画布尺寸100%充满容器对象
 *   绘图框尺寸按照600设置
 * */
const svg = main.append('svg')
    .attr('version', 1.2)
    .attr('xmlns', 'http://www.w3.org/2000/svg')
    .attr('width', '100%')
    .attr('height', '100%')
    .attr('viewBox', `0 0 ${width} ${height}`)

/*===========3-建立基础数据===========*/
/*计算类目数量 len*/
const len = categories.length

/*用range()方法,基于类目数量,获取x轴的在图表坐标系中的数据 xChartData,如[0,1,2]*/
const xChartData = d3.range(len)
console.log('xChartData', xChartData);

/*x轴在像素坐标内的起始点和结束点 xPixelRange,左右各偏移50*/
const xPixelRange = [50, width - 50]

/*===========4-建立x 轴比例尺 xScale===========*/
/*
 * 用scaleBand()方法建立分段比例尺 xScale
 * 用domain()方法在比例尺中写入图表数据xChartData
 * 用rangeRound()方法在比例尺中写入像素数据,即像素的起始位和结束位xPixelRange
 * */
const xScale = d3.scaleBand()
    .domain(xChartData)
    .rangeRound(xPixelRange)
console.log('xScale', xScale)
/*===========5-建立x 轴对象===========*/
/*基于比例尺xScale,用axisBottom()方法创建刻度朝下的坐标轴生成器 xAxisGenerator*/
const xAxisGenerator = d3.axisBottom(xScale)

/*利用坐标轴生成器绘制坐标轴
 *   在svg中append 加入g 对象
 *   用transform 属性中的translate设置x轴的y位置
 *   用call()方法调用xAxisGenerator轴生成器,生成坐标轴
 *   用selectAll()方法选择所有的text文本
 *   用text()方法将图表数据设置为类目数据
 *   用attr()方法设置字体大小
 * */
svg.append('g')
    .attr('transform', `translate(0,${height-50})`)
    .call(xAxisGenerator)
    .selectAll('text')
    .text(d => categories[d])
    .style('font-size', '12px')
复制代码

利用比例尺建立y坐标轴

虽然y轴是数值型,但是整体思路与x轴的绘制一致,唯一需要注意的一点是:像素坐标轴的y轴方向和我们绘制的y轴方向相反,因此在建立比例尺时要注意对应关系。

/*===========1-必备数据===========*/
/*数据源source:两个系列的数据*/
const source = [
    //html css js
    [30, 20, 40], //学习人数
    [40, 30, 50] //就业人数
]

/*===========1-建立容器对象===========*/
/*获取main 容器*/
const main = d3.select('#main')

/*声明绘图框尺寸
    width 宽度,600
    height 高度,600
*/
const width = 600
const height = 600

/*建立svg 对象
 *   svg 画布尺寸100%充满容器对象
 *   绘图框尺寸按照600设置
 * */
const svg = main.append('svg')
    .attr('version', 1.2)
    .attr('xmlns', 'http://www.w3.org/2000/svg')
    .attr('width', '100%')
    .attr('height', '100%')
    .attr('viewBox', `0 0 ${width} ${height}`)

/*===========3-建立基础数据===========*/
/*计算数据源中所有数据的极值 maxY
 *   用js原生方法flat()展开数据源,再通过max()方法取极值
 * */
const maxY = Math.max(...source.flat())
console.log('maxY', maxY);

/*声明y轴在图表坐标系中的数据起点和结束点 yChartRange*/
const yChartRange = [0, maxY]

/*声明y轴在像素坐标系中的数据起点和结束点 yPixelRange*/
const yPixelRange = [height - 50, 50]

/*===========4-建立y 轴比例尺 yScale===========*/
/*
 * 用scaleLinear()方法建立线性比例尺 yScale
 * 用domain()方法在比例尺中写入图表数据yChartRange
 * range()方法在比例尺中写入像素数据,即像素的起始位和结束位yPixelRange
 * */
const yScale = d3.scaleLinear()
    .domain(yChartRange)
    .range(yPixelRange)

/*===========5-建立y 轴对象===========*/
/*基于比例尺yScale,用axisLeft()方法创建刻度朝左的坐标轴生成器 yAxisGenerator*/
const yAxisGenerator = d3.axisLeft(yScale)

/*利用坐标轴生成器生成坐标轴
 *   在svg中append 加入g 对象
 *   用transform 属性中的translate设置y轴的x位置
 *   用call()方法调用xAxisGenerator轴生成器,生成坐标轴
 *   用style()方法设置字体大小
 * */
svg.append('g')
    .attr('transform', 'translate(50 0)')
    .call(yAxisGenerator)
    .style('font-size', '12px')
复制代码

绘制图表

上面我们已经成功建立起了坐标系,接下来离成功就只有一步了:绘图。

  • 初始化绘图相关数据
    • 获取到x轴每个类目的像素宽度(HTML、CSS、JS)
    • 获取到系列的长度(就业人数、学习人数)
    • 两个相除,得到每个类目下每个系列所占的x轴像素宽度。
  • 构建绘图区域 g ——只是搭建了绘图区域框架,到这一步是还没有图形绘制出来
    • 创建好绘图区域集合,并将类目数据绑定到每个集合
    • 设置未来会填充到 g 内的每一个柱状体x轴坐标起始像素位置
    • 设置填充颜色
  • 构建矩形集合 rect ——只是创建了柱状体集合和对象,但是没有设置属性
    • 创建好柱状体集合,并将系列数据绑定到每个合集
    • 在每个 g 元素内批量穿件 rect 元素
  • 设置每个柱状体的属性
/*===========6-建立绘图区相关的基础数据===========*/
/*-----绘图区相关的基础数据-----*/
/*用x轴比例尺xScale的bandwidth()方法获取x轴上一个类目的像素宽xBandW*/
const xBandW = xScale.bandwidth()
console.log('xBandW', xBandW);
/*获取系列的数量n*/
const n = source.length

/*用类目宽除以系列数,得到一个类目中每个系列元素的宽,即列宽colW*/
const colW = xBandW / n
console.log('colW', colW);

/*计算调色盘颜色数量colorLen*/
const colorLen = color.length

/*===========7-架构绘图区===========*/
/*在svg中建立系列集合seriesObjs,在系列集合中建立系列对象
 *   在svg中append 加入g 对象
 *   selectAll() 选择所有g元素,此处重点不在选择,而是建立一个选择集对象
 *   用data() 方法将具备系列信息的数据源source绑定到系列集合中
 *   用join() 基于数据源批量创建g元素,一个g代表一个系列,之后每个g元素里都会放入三个不同类目的柱状体
 *   用transform 属性中的translate设置系列的x像素位——列宽乘以系列索引
 *   基于系列索引,从调色盘中取色,然后将其作为一个系列中所有图形的填充色
 * */
const seriesObjs = svg.append('g')
    .selectAll('g')
    .data(source)
    .join('g')
    .attr('transform', (seriesData, seriesInd) => {
        const seriesX = colW * seriesInd
        return `translate(${seriesX},0)`
    })
    .attr('fill', (seriesData, seriesInd) => color[seriesInd % colorLen])

/*在系列集合中建立柱状体集合rects
 *   用系列集合seriesObjs 的selectAll()方法选择所有的rect元素,用于建立选择集对象
 *   用data()方法将之前绑定在每个系列集合中的数据绑定到柱状体集合中
 *   用join()基于每个系列的数据数据批量创建rect元素
 *   用classed() 方法为其添加item属性
 * */
const rects = seriesObjs.selectAll('rect')
    .data(seriesData => seriesData)
    .join('rect')
    .classed('item', true)

console.log('rects', rects);
/*=8-用attr()方法设置每个柱状体的x、y位置和width、height 尺寸=*/
/*
 * 设置柱状体的x像素位
 *   从回调参数中获取柱状体在当前系列中的索引rectInd,系列索引 seriesInd
 *   基于柱状体在当前系列中的索引rectInd,用x轴比例尺xScale()获取柱状体在当前系列中的x像素位
 * 设置柱状体像素宽width为列宽colW
 * 设置柱状体的y像素位
 *   从回调参数中解构柱状体数据rectData
 *   基于柱状体数据rectData,用y轴比例尺yScale()获取柱状体的y像素位
 * 设置柱状体的像素像素高
 *   从回调参数中解构柱状体数据rectData
 *   让y轴上刻度为0的像素位,减去刻度为柱状图实际数据的像素位,即为柱状图的像素高
 * */
rects.attr('x', (rectData, rectInd) => xScale(rectInd))
    .attr('width', colW)
    .attr('y', rectData => yScale(rectData))
    .attr('height', rectData => yScale(0) - yScale(rectData))
复制代码

到这里,就成功建立起一个直观的柱状图,后续还可以用过添加transition动画、缓动动画、鼠标事件让这个柱状图用户体验更好、更炫酷(这个留到出差回来之后再写)

免责声明:文章版权归原作者所有,其内容与观点不代表Unitimes立场,亦不构成任何投资意见或建议。

程序员

482

相关文章推荐

未登录头像

暂无评论