如何用d3生成一个饼图

initPieChart: function (el, data) { // var data = [2, 4, 8, 10]; // 得到容器的尺寸 var width = el.width(), height = el.height(); var radius = Math.min(width, height) / 3; // 清空容器 d3.select(el[0]) .select("svg") .remove(); // 容器插入svg var svg = d3.select(el[0]) .append("svg") .attr("width", width) .attr("height", height) .append("g") // 创建内部容器放置图表,并且移动到中间 g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); // 根据数据key从颜色序列取得固定颜色 var color = d3 .scaleOrdinal() .domain( data.map(function (d) { return d.lx; }) ) .range([ '#4aff14', '#f52c56', '#41a3f4', '#f2c92f' ]); // d3 v4 api指定使用数据的哪个字段生成pie var pie = d3.pie() .value(function (d) { return d.overps }); // pie部分的arc,得到一个函数,用来生成pie每个部分的path路径 var arc = d3.arc() .innerRadius(0) .outerRadius(radius); var innerArc = d3.arc() .innerRadius(0) .outerRadius(radius * 2); var outerArc = d3.arc() .innerRadius(0) .outerRadius(radius * 2.5); //pie容器 var arcs = g.selectAll("arc") .data(pie(data)) .enter() .append("g") .attr("class", "arc") //生成pie每个部分的path,并且赋值path的路径 arcs.append("path") .attr("fill", function (d, i) { return color(i); }) .attr("d", arc); var key = function (d) { return d.data.lx; }; var polyline = g.selectAll("polyline") .data(pie(data), key) // 生成指示折现 polyline.enter() .append("polyline") .style("stroke", '#fff') .style('fill', 'none') .style("stroke-width", '2px') .attr("points", function (d) { // var pos = outerArc.centroid(d); // pos[0] = radius * 0.5 * (midAngle(d) < Math.PI ? 1 : -1); var r1 = arc.centroid(d) var result = [innerArc.centroid(d), outerArc.centroid(d)] console.log(result) return result }); function midAngle (d) { return d.startAngle + (d.endAngle - d.startAngle) / 2; } var text = g.selectAll("text") .data(pie(data), key); text.enter() .append("text") .attr("dy", ".35em") .attr('fill', '#fff') .attr('text-anchor', function (d) { return midAngle(d) < Math.PI ? "start" : "end"; }) .text(function (d) { return d.data.lx; }) .attr('transform', function (d) { var pos = outerArc.centroid(d); return "translate(" + pos + ")"; }) }

July 10, 2019 · 2 min · jiezi

d3.js交互图表

d3js v5.9.2 如果对原生JS还有Jq比较熟悉的话,d3的交互很快就能掌握 还是拿完整的柱形图例子修改:完整的柱图 selection.on()d3通过selection.on()对元素进行事件绑定或移除,用法很简单,例如:selection.on(‘click’, function(){ console.log(this)})注意这里使用function(){}才有this指向对应元素 用法和原生JS及jq几乎一样给柱状图进行事件绑定这里我们使得鼠标悬浮的柱状图颜色变为lightblue svg中,元素属性的优先级为0,为方便之前的demo,这里添加class名来改变矩形的样式.selected-rect{ fill: lightblue}js代码如下barContainer.append(‘rect’) .attr(‘height’, d => barScale(d)) .attr(‘width’, barWidth - 1) .on(‘mouseover’, function () { d3.select(this) //指向元素 .attr(‘class’, ‘selected-rect’); //通过class改变样式 }) .on(‘mouseout’, function () { d3.select(this) .attr(‘class’, null); });总结很简单吧,没啥好说的,主要是一开始不了解svg样式的优先级查阅了资料,没想到优先级权重是0(翻译错了请指出 源代码参考资料交互式操作 Handling Events Presentation attributes

April 5, 2019 · 1 min · jiezi

d3.js 动态图表

d3js v5.9.2 使用d3创建动态图表主要使用到了d3.trasition部分的API,通过他们可以完成动画 我的学习记录是通过一个例子了解这些API会动的柱状图还是拿之前的例子,代码在此:完整的柱图 修改的代码部分在于创建rect处://原来的代码barContainer.append(‘rect’) .attr(‘height’, d => barScale(d)) .attr(‘width’, barWidth - 1);加上动画效果后代码如下barContainer.append(‘rect’) .attr(‘height’, 0) //动画开始前状态 .attr(‘width’, barWidth - 1) .transition() //selection.transition() 返回transition .duration(1000)//持续时间 .ease(d3.easeBackInOut)//transition.ease(easing function),easing function是d3的动画函数,还有很多其他api .attr(‘height’, d => barScale(d));//transition.attr()??是从上向下绘制的这里我们可以用rect的y属性控制动画,y表示矩形左上角端点的纵坐标barContainer.append(‘rect’) .attr(‘height’, 0) .attr(‘width’, barWidth - 1) .attr(‘y’, d => barScale(d)) //y是相对rect父级的g容器的,顶端从底部开始,故值设为矩形的高 .transition() .duration(2000) .ease(d3.easeBackInOut) .attr(‘height’, d => barScale(d)) .attr(‘y’, 0);//到g容器顶部所以过程就是矩形长度向下拉伸,同时位置上移这样子就完成了动画更简洁的写法上面的写法是最简单的写法,但是代码太长或者同一个动画效果多次使用就会很冗杂,我们可通过d3.transition()获得自定义的transition 使用也很简单//定义transitionlet t = d3.transition() .duration(2000) .ease(d3.easeBackInOut);barContainer.append(‘rect’) .attr(‘height’, 0) .attr(‘width’, barWidth - 1) .attr(‘y’, d => barScale(d)) .transition(t) //使用新定义的transition .ease(d3.easeBackInOut) .attr(‘height’, d => barScale(d)) .attr(‘y’, 0);效果相同总结d3还提供了其他常用的API,除了用到的ease(),duration()还有delay()等等 代码在这(d3库在build里)参考资料第九章 让图表动起来 SVG 图像入门教程——阮一峰 selection.transition() ...

April 4, 2019 · 1 min · jiezi

d3.js 创建完整柱形图

d3js.org v5.9.2 之前只是各个部分的demo,现在将各部分整合起来,发现还是学到了不少东西 主要是加深了对scale(比例尺)的理解代码样式及数据先是样式<style> rect { fill: pink } text { font-size: 10px; }</style>接着是数据及柱状图宽高等const data = [3, 6, 10, 25], barWidth = 100, barHeight = 300, padding = { //svg留白用 top: 100, right: 100, bottom: 100, left: 100 };创建比例尺实践之后对比例尺与坐标轴的理解加深了一点let barScale = d3.scaleLinear().domain([0, d3.max(data)]).range([0, barHeight]), yAxisScale = d3.scaleLinear().domain([d3.max(data), 0]).range([0, barHeight]),//y轴使用线性比例尺,注意domain输入域 xAxisScale = d3.scaleBand().domain([1, 2, 3, 4]).range([0, (barWidth - 1) * data.length]); //x轴使用scaleBand比例尺barScale用于柱形图创建 yAxisScale用于y轴创建,注意domain()的输入域,否则刻度数值大小排列会相反 xAxisScale用于x轴创建,使用scaleBand序数比例尺 之前对比例尺的理解太过肤浅,这里也做了几个小实验console.log(barScale(0): + barScale(0));console.log(yAxisScale(0): + yAxisScale(0));console.log(xAxisScale(2): + xAxisScale(2));显示如下 对于barScale与yAxisScale,输入域相反,所以映射相反,把一篇他人很棒的总结放于文末创建柱状图let barContainer = d3.select(’.chart’) .attr(‘width’, data.length * barWidth + padding.left + padding.right) .attr(‘height’, barHeight + padding.top + padding.bottom) .selectAll(‘g’) .data(data).enter().append(‘g’) .attr(’transform’, (d, i) => { return ’translate(’ + (padding.left + i * barWidth) + ‘,’ + (padding.top + barHeight - barScale(d)) + ‘)’ });barContainer.append(‘rect’) .attr(‘height’, d => barScale(d)) .attr(‘width’, barWidth - 1);barContainer .append(’text’) .text(d => d) .attr(‘y’, 10) .attr(‘x’, barWidth / 2 - 5);这里使用了之前定义的padding对象的值进行部分留白创建坐标轴/** * 创造y轴 /let yAxis = d3.axisLeft(yAxisScale);d3.select(’.chart’) .append(‘g’) .attr(’transform’, ’translate(’ + (padding.left - 10) + ‘,’ + padding.top + ‘) ‘) .call(yAxis);/* * 创建X轴 */let xAxis = d3.axisBottom(xAxisScale);d3.select(’.chart’) .append(‘g’) .attr(’transform’, ’translate(’ + (padding.left) + ‘,’ + (padding.top + barHeight) + ‘)’) .call(xAxis);最后创建坐标轴总结对之前学的东西做了一次结合,还有很多地方待提升 不足请大佬指出参考资料D3中常用的比例尺 完整的柱形图 ...

April 4, 2019 · 1 min · jiezi

d3.js 使用坐标轴

本片blog记录d3坐标轴入门,使用版本v5.9.2SVG如何实现坐标轴d3生成的坐标图是通过svg的path(路径)元素 + g + line + text元素组成的,如下图 path表示的是底部坐标轴(不包括内部刻度),如下 这部分通过d描绘,在d3中称为outer tick g容器则包括了text和line作为inner tick line即线,text即文字d3实现坐标轴主要步骤步骤主要如下:创建scale(比例尺)创建axis(坐标轴,是个函数)selection.call(axis)创建坐标轴小实验数据与宽高const data = [1, 10, 20];let svgWidth = 400, svgHeight = 400;小实验都用这些数据啦最简单的坐标轴/** * 创建scale /let scale = d3.scaleLinear() .domain([0, d3.max(data)]) //值域 .range([0, 200]); //可视范围/* * 创建axis /let axis = d3.axisTop(scale); //创建坐标轴,除此之外还有axisLeft,axisBottom,axisRight,区别在于tick(刻度)的位置/* * 插入 */d3.select(‘body’) .append(‘svg’) .attr(‘width’, svgWidth) .attr(‘height’, svgHeight) .append(‘g’)//需要放在g容器里 .attr(’transform’, ’translate(10,20)’) .call(axis);//创建坐标轴坐标轴的长度来源于比例尺的range 最大值来源于d3.max(data) 200(range) / 20(最大值) = 10段 20(最大值) / 10段 = 2 所以坐标轴如是显示仅显示所需的刻度的坐标轴上一个坐标轴显示了平均的刻度,但是有时这不是我们想要的,我们可以使用axis.tickValues()显示需要的刻度 仅需在axis后增加api即可let axis = d3.axisTop(scale).tickValues(data);设置刻度长短也是使用axis.tickSize()即可,为方便观察,这里使用axisBottom坐标轴let axis = d3.axisBottom(scale).tickValues(data).tickSize(200);设置样式其实设置样式就是对其中的svg设置,原理类似dom操作,选择svg元素,改变样式 举个简单的例子d3.select(‘body’) .append(‘svg’) .attr(‘width’, svgWidth) .attr(‘height’, svgHeight) .append(‘g’) .attr(’transform’, ’translate(10, 20)’) .call(axis) .selectAll(‘path’).attr(‘stroke’, ‘pink’).attr(‘stroke-width’, ‘5’);最后对g中的path进行赋样式 总结组合好不同的API会有更多效果! 参考资料d3.js直方图与坐标轴基础 d3.js API selection.call() d3中的axis.ticks详解 ...

April 3, 2019 · 1 min · jiezi

手动实现一个速度仪表盘

前言最近正在学习数据可视化, 这里记录一下一些心得与成果, 采用的技术是 (svg + react + d3)。 这种实现可视化方式本人个人感觉超级不错,如果你是有一定的基础的同学,强烈推荐一下。效果整体效果如下:这个是普通的速度仪表盘,有没有开发太多的动态交互,唯一的交互是点击图片最上面的加速与减速就能够调整速度了。开发思路搭建开发环境使用create-react-app创建一个新的项目,添加依赖d3yarn add d3初始化数据这里速度范围是[0, 200], 对应角度范围个人设置是[150, 390], 很明显这是一个线性比例尺。速度间隔是2。代码如下const scale = d3.scaleLinear().domain([0, 200]).range([150, 360 + 30])const ticks = scale.ticks(100) // 200 / 2 => 100 构建外部圈 function Chart(props) { const { width, height, margin } = props return ( <svg width={width} height={height}> <g transform={translate(${margin.left}, ${margin.top})}> { props.children } </g> </svg> )}……export default class Meter extends Component { … render () { // config => {width: xxx, height: xxx, margin: xxx} return ( <div className={‘container’}> <Chart {…config}> <g> <circle cx={0} cy={0} r={204} fill={‘rgba(158, 158, 158, .4)’}></circle> <circle cx={0} cy={0} r={196} fill={’#FFF’}></circle> <circle cx={0} cy={0} r={200} fill={’transparent’} stroke={’#000’}></circle> </g> </Chart> </div> ) }}上面其实是绘画了三个圆, 利用SVG后面的绘制的图画,会覆盖前面的图画的特性。先画最外面,然后最里面,最后中间的圆。 就把最外层的圈给描绘出来了,效果如下:描绘刻度尺接着上面的代码结构,我们开始刻画刻度尺 ……export default class Meter extends Component { … render () { // config => {width: xxx, height: xxx, margin: xxx} return ( <div className={‘container’}> <Chart {…config}> <g> <circle cx={0} cy={0} r={204} fill={‘rgba(158, 158, 158, .4)’}></circle> <circle cx={0} cy={0} r={196} fill={’#FFF’}></circle> <circle cx={0} cy={0} r={200} fill={’transparent’} stroke={’#000’}></circle> </g> <g fill={’transport’} stroke={’#000000’}> { ticks.map((tick) => { let IS_20_TIME = tick % 20 === 0 let title = IS_20_TIME ? <text x={160} dominantBaseline={‘middle’} textAnchor={’end’}>{tick}</text> : ’’ return ( <g transform={rotate(${scale(tick)})} key={tick}> <path d={M165, 0L185,0} strokeWidth={IS_20_TIME ? 3 : 1}></path> {title} </g> ) }) } </g> </Chart> </div> ) }}这里刻画刻度尺,我的思路很简单,刻度尺是对速度大小的描述,而速度跟角度又是线性相关,反过来,速度对应角度。所以,我只是需要根据速度所对应的角度,而对水平刻度进行旋转即可。效果大家可以看到:指向针指向针其实就是一个圆 + 三角形的组合,代码如下: <circle cx={0} cy={0} r={10} fill={’#’}></circle> <path d={M-20, 5L-20, -5L130, 0Z} transform={rotate(150)}> <animateTransform ></animateTransform> </path>上面本人实现的比较粗糙,大家可以把这个封装成一个shape, 以后可以直接复用的,后面如果需要旋转,可以通过<g>元素来实现。 到这一步,一个简单的仪表盘就初具原型了控制指针转动指针的转动是根据速度来的,所以我们需要先定义一个speed的状态。constructor(props) { super(props) this.state = { speed: 0 } }接下来,我们需要把speed映射到指针上面。怎么处理呢还记得前面定义的scale,这个是一个线性比例尺,通过它我们能够获取不同速度对应的角度我们把上面的指向针代码进行改造const {speed} = this.state……<circle cx={0} cy={0} r={10} fill={’#’}></circle><path d={M-20, 5L-20, -5L130, 0Z} transform={rotate(${scale(speed)})}> <animateTransform ></animateTransform></path>这样我们设置不同的speed就能在页面控制指针指向不同的刻度尺。速度标识区间所谓的速度标识区间,其实就是几段圆弧,通过不同的颜色来告知进入不同的速度区间。这里我对圆弧进行了封装,底层通过d3的arc方法来创建圆弧。function LArc(props) { const { start, end, color } = props let _arc = d3.arc()({ innerRadius: 165, outerRadius: 185, startAngle: Math.PI * 2 * (scale(start) + 90) / 360, endAngle: Math.PI * 2 * (scale(end) + 90) / 360 }) return ( <path d={_arc} fill={color}></path> )}这里其实还有一个问题,就是需要先加载速度标识区间,然后再去添加刻度尺,不然标识区间会覆盖刻度尺(切记)。这样一个基本速度仪表盘就出来了如果你能明白上面的实现思路,我觉得你可以自己实现一个时钟了如果你想了解更多:比如指示器如何实现的请参考https://github.com/cookhot/i-… (本人会在里面不定期增加新图表哦) ...

February 18, 2019 · 2 min · jiezi

数据可视化之 Sankey 桑基图的实现

原文地址:https://geekplux.com/2018/08/28/how-to-implement-sankey-diagram.html什么是桑基图Google 搜索桑基图,可以搜到一大堆定义。简而言之,桑基图是一种数据流图,展示了数据是如何从左到右流向最后的节点,每条边代表一条数据流,宽度代表数据流的大小。桑基图常用于流量分析,可以很清楚的看出数据是如何渐渐分流的。本文着重讲解如何实现,理论方面的东西各位可以自行了解。实现桑基图的关键点关键点有两个:1. 坐标计算桑基图要展现的数据流,算是图(拓扑类、网络型或关系型)数据的一种。实现一个数据可视化图,最重要的就是拆解元素。而实现一个图数据可视化,则最重要的是分清“节点”和“边”。拆解元素之后,最重要的便是坐标的计算,这里包括点和边的坐标。而图形中,任何的元素都可以看作是点连线而成,所以元素坐标的计算实际上就成了点坐标的计算。比如桑基图中,节点是一个矩形,那么只需计算两个点(左上和右下)的坐标(x0, y0),(x1, y1)便可确定;边是一个带形,需要计算四个端点才能确定,带形的弧度则可由简单的三次贝塞尔曲线计算得来。由此观之,实现桑基图的核心在于计算出以上的这些点坐标。其实实现任意一种可视化都是计算点的坐标。2. 减少边交叉当数据量到一定程度的时候, 桑基图中的边会出现重叠现象,造成一定的视觉混乱。如何减少可以阅读本文第二节。一、坐标计算的实现准备工作设计数据结构经典的图数据结构一般是邻接矩阵和邻接表,我们也可以自己设计。我在做拓扑数据可视化的时候,会先和后端或数据同学商定好我需要拿到的数据结构,通常是这个样子:{ nodes: [ { foo: bar }, { foo: baz }, … ], links: [ { source: 0, target: 1, value: 100 }, { source: 1, target: 2, value: 10 }, … ]}而我拿到之后要做的第一步就是先把 nodes 和 links 串联起来,这里每个 link 的 source 和 target 分别是 nodes 的下标,当然你也可以设置其他的引用(指针),总之通过引用讲两者串联起来,变成:{ nodes: [ { foo: bar, column: 0, // 节点所在第几列 row: 0, // 节点所在第几行 value: 100, // 节点数据流大小 sourceLinks: [ { source: 0, target: 1, … } ], targetLinks: [ … ] }, … ], links: [ { source: 0, target: 1, value: 100, sourceNode: { foo: bar, column: 0, row: 0, … }, targetNode: { … } }, … ]}这样,对于某个节点来说,可以直接用 O(1) 的时间复杂度访问到它的任意相邻节点。计算节点数据流大小这里的计算方法可自己定,通常是取该节点入边和出边的数据流大小之和的最小值。计算节点所在行列在桑基图的计算中,我们还需要进行一个关键的计算——计算节点在桑基图中的第几行第几列。第几列的计算,即为节点在图中的深度计算:入度为 0 的节点深度为 0,在第一列出度为 0 的节点深度最大,在最后一列其余节点的深度为他相连源节点的最大深度加 1第几行的计算,涉及到排序的问题,通常某一列中的节点都是按节点数据流大小,从大到小排序。节点坐标计算刚才我们说过,坐标计算可以分为两部分:节点和边。其中,边的坐标位置依赖于节点的坐标,所以应该先计算节点坐标。但在计算坐标之前,首先要明确一个问题:是否限定视图的宽高。这个问题引申出两种节点坐标的计算方式。不限定视图宽高如果不限定宽高,那么节点坐标的计算步骤很简单:设置一个节点的宽度设置节点的水平间距从左至右,根据刚才计算出的节点所在第几列,计算出节点的横坐标(x0, x1),初始的 x0 为 0设定一个比例尺函数(多大的数据流对应屏幕上的多少像素,通常是首先设定一个节点最小高度和一个节点最大高度,然后找出所有节点数据流的最小和最大值,映射成一个定义域为节点数据流大小,值域为节点高度的函数)通过比例尺计算出节点高度设置一个节点垂直间距从上至下,根据刚才计算出的节点所在第几行,计算出节点的纵坐标 (y0, y1),初始的 y0 为 0大致是这个思路,横坐标的计算取决于两个值,节点宽度和 节点水平间距;纵坐标的计算取决于 节点的数据流大小 和 节点垂直间距。具体的计算代码,可根据你自己的数据结构来调整。限定视图宽高如果限定宽高,那么计算步骤需要换个思路:节点的宽度和节点的水平间距需要根据节点的列数和视图宽度来计算,你可以自己手动调整也可以设计个算法来算从左只有,根据节点宽度和节点水平间距,计算出节点横坐标设定一个比例尺函数,计算出节点的高度设置一个节点垂直间距通过高斯-赛德尔迭代(Gauss–Seidel method)计算出纵坐标(大致的思路是,先根据前两步的数值算出一个初始节点坐标,如果总体布局超出视图的下界,则节点高度和节点垂直间距都按比例缩小(如 0.95),并同时上移 n 个像素,如果总体布局超出视图上界,则节点高度和垂直间距都按比例缩小,并同时下移 n 个像素,直到总体的桑基图布局适应一开始限定的视图宽高)这个思路是 d3-sankey 的实现思路。如果你有限定视图宽高的需求,那么可以直接使用 d3-sankey。边的坐标计算只要确定了节点坐标,边的坐标可以根据它源节点和目标节点的坐标来算出:对于一个节点,将它的出边和入边进行排序(排序方法通常是根据相连节点在第几行从上到下排,也可以通过一些其他排序方法减少边的交叉,具体在第二节介绍)计算每个节点中单位数据流占节点高度的比例根据出边入边的数据流大小,乘上一步计算出的比例,则可得到每条边左右两边的高度从上到下,计算每条边的纵坐标每条边四个端点的横坐标分别对应源节点和目标节点的横坐标以上操作可以通过遍历每个 node 的 sourceLinks 和 targetLinks 来计算。得到边的四个端点以后,就可以算出三次贝塞尔曲线的控制点了:二、如何减少交叉通常要减少边的交叉,可以采用下面两种方法:均值排序sugiyama 算法均值排序这个名字是我自己起的。。不过这个方法很实用有效。对于每个源节点来说,都有相连的目标节点。这里的“均值”指的是所有相连目标节点所在行数的平均值(所有目标节点的行数相加,除以目标节点个数),这个平均值可以大致描述该节点每个出边的位置。每条出边都有这样一个值,这个值越小,则说明该出边要连接的目标节点的位置越靠上,反之越靠下。所以可根据这个值,从小到大排出出边在该节点上从上到下的位置。三、具体项目中的交互我参与的 UBA (User Behavior Analytics 内部项目) 项目中,正好用到了桑基图。除了上述的图形绘制之外,主要复杂的是交互。如图所示,除了基本的 hover 交互之外,项目中主要还有minimap 拖拽和刷选主视图的拖拽和缩放左下角的过滤器点击交互,高亮只经过选中节点的路径,并且边上高亮的部分由最后一个选中节点懈怠的数据流值确定,其余部分半透明整个桑基图实现下来发现绘制只是一些计算,交互才是更难抽象和处理的部分。综上,桑基图是一个 展现数据流非常好用的视图,感兴趣的同学可以自己实现一个试试。除了我文章中这些基本的桑基图布局,你还可以试试其他变种,另外交互方面也可以突破刚才我提到的那些,比如我之前实现过点击节点进行折叠/展开的交互。总体来说可视化还是一个比较有意思的方向。本作品采用知识共享 署名-非商业性使用-禁止演绎 4.0 国际 许可协议进行许可。 ...

December 24, 2018 · 1 min · jiezi

前端每日实战:139# 视频演示如何用 CSS 和 D3 创作光斑粒子交相辉映的动画

效果预览按下右侧的“点击预览”按钮可以在当前页面预览,点击链接可以全屏预览。https://codepen.io/comehope/pen/zJybdq可交互视频此视频是可以交互的,你可以随时暂停视频,编辑视频中的代码。请用 chrome, safari, edge 打开观看。https://scrimba.com/p/pEgDAM/cGV7phy源代码下载每日前端实战系列的全部源代码请从 github 下载:https://github.com/comehope/front-end-daily-challenges代码解读定义 dom,容器中包含 3 个子元素:<div class=‘container’> <span></span> <span></span> <span></span></div>设置页面背景:body { margin: 0; width: 100vw; height: 100vh; background: radial-gradient(circle at center, #222, black 20%);}定义容器尺寸:.container { width: 100%; height: 100%;}设置光斑的样式,其中定义了偏亮和偏暗的 2 个颜色变量:.container { position: relative;}.container span { –bright-color: #d4ff00; –dark-color: #e1ff4d; position: absolute; width: 30px; height: 30px; margin-left: -15px; margin-top: -15px; background: radial-gradient(var(–bright-color), var(–dark-color)); border-radius: 50%; box-shadow: 0 0 25px 3px var(–dark-color);}把光斑定位到页面中心:.container span { transform: translateX(50vw) translateY(50vh);}增加光斑从中心向四周扩散和收缩的动画效果:.container span { animation: animate 1.5s infinite alternate; animation-delay: calc(var(–n) * 0.015s);}@keyframes animate { 80% { filter: opacity(1); } 100% { transform: translateX(calc(var(–x) * 1vw)) translateY(calc(var(–y) * 1vh)); filter: opacity(0); }}定义动画中用到的变量 –x、–y 和 –n:.container span:nth-child(1) { –x: 20; –y: 30; –n: 1; }.container span:nth-child(2) { –x: 60; –y: 80; –n: 2;}.container span:nth-child(3) { –x: 10; –y: 90; –n: 3;}设置容器的景深,使光斑的运动有从远到近的感觉:.container { perspective: 500px;}.container span { transform: translateX(50vw) translateY(50vh) translateZ(-1000px);}至此,少量元素的动画效果完成,接下来用 d3 批量创建 dom 元素和 css 变量。引入 d3 库,同时删除 html 文件中的子元素和 css 文件中的子元素变量:<script src=“https://d3js.org/d3.v5.min.js"></script>定义光斑粒子数量:const COUNT = 3;批量创建 dom 元素:d3.select(’.container’) .selectAll(‘span’) .data(d3.range(COUNT)) .enter() .append(‘span’);为 dom 元素设置 –x、–y 和 –n 的值,其中 –x 和 –y 是 1 到 99 的随机数:d3.select(’.container’) /* 略 / .style(’–x’, () => d3.randomUniform(1, 99)()) .style(’–y’, () => d3.randomUniform(1, 99)()) .style(’–n’, d => d);再为 dom 元素设置 –bright-color 和 –dark-color 的值:d3.select(’.container’) / 略 */ .style(’–dark-color’, (d) => d3.color(hsl(${70 + d * 0.1}, 100%, 50%))) .style(’–bright-color’, (d) => d3.color(hsl(${70 + d * 0.1}, 100%, 50%)).brighter(0.15));最后,把光斑粒子数量设置为 200 个:const COUNT = 200;大功告成! ...

September 20, 2018 · 2 min · jiezi