Skip to content

Instantly share code, notes, and snippets.

@ckinmind
Last active December 11, 2017 09:25
Show Gist options
  • Save ckinmind/4beccd5b89da95b1d0e5961b20660ee5 to your computer and use it in GitHub Desktop.
Save ckinmind/4beccd5b89da95b1d0e5961b20660ee5 to your computer and use it in GitHub Desktop.
Line——动画效果

说明

  • 图表类型:折线图(面积图)
  • 原图地址:D3 version of animated chart dribbble
  • 原图是d3.v3版本实现,这版用v4版本重写了
  • 改写后的动画有点问题,问题出在ease上,暂时不知道这个带数值的写法

知识点

  • v3版本:d3.time.format("%d-%b-%Y").parse

  • v4版本: d3.timeParse("%d-%b-%Y")

  • v3版本:d3.svg.area()

  • v4版本:d3.area()

  • v3版本:d3.svg.line()

  • v4版本:d3.line()

  • v3版本:ease("elastic", 1, 0.9)

  • v4版本: ease(d3.easeElastic, 1, 0.9) (存疑)

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
#refl {
/*
This is the 'correct' way to make the reflection,
using the element() background technique:
http://lea.verou.me/2011/06/css-reflections-for-firefox-with-moz-element-and-svg-masks/
Currently only works in moz though.
*/
content: "";
display: block;
width: 800px;
height: 500px;
margin: 0 auto;
opacity: 0.1;
z-index: -1;
transform: translateY(-62px) scaleY(-1);
background: -moz-linear-gradient(bottom,
rgba(255,255,255,1) 0%,
rgba(255,255,255,1) 5%,
rgba(255,255,255,0) 6%,
rgba(255,255,255,0) 45%,
rgba(255,255,255,1) 75%,
rgba(255,255,255,1) 100%),
-moz-element(#chart);
background-position: bottom, bottom;
-moz-background-size: cover, cover;
}
body {
font-family: helvetica, sans-seirf;
}
#chart {
display: block;
margin: 0 auto;
/*
This is the nonstandard way of making the reflection.
Only works in webkit (the other way doesnt work in webkit so use this instead)
*/
-webkit-box-reflect: below -60px
-webkit-gradient(linear, left bottom, left top,
color-stop(0.00, rgba(0,0,0,0)),
color-stop(0.05, rgba(0,0,0,0)),
color-stop(0.06, rgba(0,0,0,0.1)),
color-stop(0.45, rgba(0,0,0,0.1)),
color-stop(0.75, rgba(0,0,0,0)),
color-stop(1.00, rgba(0,0,0,0)));
}
.x.axis g:first-of-type, .x.axis g:last-of-type {
display: none;
}
.axis line, .axis path {
fill: none;
stroke: rgba(236, 60, 12, 0.6);
shape-rendering: crispEdges;
}
.axis path.domain {
stroke: none;
}
.axis text {
fill: #8C807D;
font-size: 1em;
font-weight: 300;
}
.line {
fill: none;
stroke-width: 1px;
stroke: rgba(236, 60, 12, 0.6);
}
.line.done {
stroke: none;
}
.area {
stroke-width: 0px;
}
.area1 {
fill: rgba(245, 166, 189, 0.6);
}
.area2 {
fill: rgba(236, 60, 12, 0.6);
}
.basline {
stroke-width: 10px;
}
.area1Baseline {
stroke: rgba(245, 166, 189, 0.6);
}
.area2Baseline {
stroke: rgba(236, 60, 12, 0.6);
}
.axis line {
transition: opacity 0.5s;
}
.axis.done line {
opacity: 0;
}
#dribbble {
position: fixed;
bottom: 1em;
width: 100%;
font-family: 'Rambla', sans-serif;
font-size: 2em;
font-weight: bold;
text-align: center;
text-decoration: none;
color: rgba(236, 60, 12, 0.6);
}
</style>
</head>
<body>
<svg id="chart"></svg>
<div id="refl"></div>
<a id="dribbble" href="http://dribbble.com/shots/1215165-Infographic">D3 animation based on Jelio Dimitrov's Dribbble</a>
<script src='//d3js.org/d3.v4.min.js'></script>
<script>
const d3 = window.d3
const parseDate = d3.timeParse("%d-%b-%Y")
// d3.time.format("%d-%b-%Y").parse => d3.timeParse("%d-%b-%Y")
const lineData = [
[parseDate("1-jan-2013"), 25],
[parseDate("1-apr-2013"), 30],
[parseDate("1-may-2013"), 50],
[parseDate("1-jun-2013"), 60],
[parseDate("1-dec-2013"), 45]
]
const intermediateLineData = [
[parseDate("1-jan-2013"), 0],
[parseDate("1-apr-2013"), 5],
[parseDate("1-may-2013"), 30],
[parseDate("1-jun-2013"), 50],
[parseDate("1-dec-2013"), 5]
]
const area1Data = [
[parseDate("1-dec-2012"), 0],
[parseDate("1-jan-2013"), 25],
[parseDate("1-feb-2013"), 10],
[parseDate("1-mar-2013"), 17],
[parseDate("1-apr-2013"), 30],
[parseDate("1-may-2013"), 25],
[parseDate("1-jun-2013"), 50],
[parseDate("1-jul-2013"), 60],
[parseDate("1-aug-2013"), 50],
[parseDate("1-sep-2013"), 30],
[parseDate("1-oct-2013"), 25],
[parseDate("1-nov-2013"), 45],
[parseDate("1-dec-2013"), 20],
[parseDate("1-jan-2014"), 0]
]
const area2Data = [
[parseDate("1-dec-2012"), 0],
[parseDate("1-jan-2013"), 10],
[parseDate("1-feb-2013"), 7],
[parseDate("1-mar-2013"), 12],
[parseDate("1-apr-2013"), 25],
[parseDate("1-may-2013"), 35],
[parseDate("1-jun-2013"), 25],
[parseDate("1-jul-2013"), 15],
[parseDate("1-aug-2013"), 6],
[parseDate("1-sep-2013"), 9],
[parseDate("1-oct-2013"), 11],
[parseDate("1-nov-2013"), 40],
[parseDate("1-dec-2013"), 30],
[parseDate("1-jan-2014"), 0]
]
const nullLineData = [
[parseDate("1-jan-2013"), 0],
[parseDate("1-apr-2013"), 0],
[parseDate("1-may-2013"), 0],
[parseDate("1-jun-2013"), 0],
[parseDate("1-dec-2013"), 0],
]
const extremeNullData = [
[parseDate("1-dec-2012"), 0],
[parseDate("1-jan-2013"), 0],
[parseDate("1-feb-2013"), 0],
[parseDate("1-mar-2013"), 0],
[parseDate("1-apr-2013"), 0],
[parseDate("1-may-2013"), 0],
[parseDate("1-jun-2013"), 0],
[parseDate("1-jul-2013"), 0],
[parseDate("1-aug-2013"), 0],
[parseDate("1-sep-2013"), 0],
[parseDate("1-oct-2013"), 0],
[parseDate("1-nov-2013"), 0],
[parseDate("1-dec-2013"), 0],
[parseDate("1-jan-2014"), 0]
]
// Timing
const start = 0
const beginChartIn = start + 800 //After baseline comes in
const finishChartIn = beginChartIn + 1400
const beginChartOut = finishChartIn + 1500
const finishChartOut = beginChartOut + 1300 //begin taking baseline out
const finish = finishChartOut + 1500
const marginBottom = 30
const width = 800
const height = 500
const chartBottom = height - marginBottom
const chartHeight = chartBottom - 10; // 10 = baseline
const svg = d3.select('#chart')
.attr('width', width)
.attr('height', height)
// Scaling functions
var xScale = d3.scaleTime ()
.range([0, width])
.domain(d3.extent(area1Data, d => d[0]))
var yScale = d3.scaleLinear()
.range([chartHeight, 0])
.domain([0, d3.max(area1Data, d => d[1] * 1.2)])
/* CREATE SVG ELEMENTS */
const xAxis = d3.axisBottom(xScale)
.ticks(12)
.tickFormat(d3.timeFormat("%b"))
const axisPath = svg.append("g")
.attr("class", "x axis done")
.attr("transform", "translate(0," + chartBottom + ")")
.call(xAxis);
// Pink Area
const area1 = d3.area()
.x(d => xScale(d[0]))
.y0(chartHeight)
.y1(d => yScale(d[1]))
const area1Path = svg.append("path")
.attr("class", "area area1")
const area1LineR = svg.append("line")
.attr("class", "basline area1Baseline rightBaseline")
const area1LineL = svg.append("line")
.attr("class", "basline area1Baseline leftBaseline")
// Orange Area
const area2 = d3.area()
.x(d => xScale(d[0]))
.y0(chartHeight)
.y1(d => yScale(d[1]))
const area2Path = svg.append("path")
.attr("class", "area area2")
const area2LineR = svg.append("line")
.attr("class", "basline area2Baseline rightBaseline")
const area2LineL = svg.append("line")
.attr("class", "basline area2Baseline leftBaseline")
// Line Graph
const line = d3.line()
.x((d,i) => xScale(d[0]))
.y((d,i) => yScale(d[1]))
const linePath = svg.append("svg:path")
.attr("class", "line")
// Line that covers whole area to stop moz element reflection from thinking that the svg is shrinking when the graph shrinks.
svg.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", width)
.attr("y2", height)
.style("stroke", "blue")
.style("stroke-width", "5px")
.style("opacity", "0")
/*
THE FOLLOWING FUNCTION ACTUALLY PREFORMS THE ANIMATION
CALL ON A LOOP TO MAKE THE ANIMATION LOOP
*/
const beginAnimation = () => {
area1Path
.attr("d", area1(extremeNullData))
.transition()
.delay(beginChartIn + 200)
.duration(700)
.ease(d3.easeElastic, 1, 0.9)
.attr("d", area1(area1Data))
.transition()
.delay(beginChartOut + 300)
.duration(500)
.ease(d3.easeCubic)
.attr("d", area1(extremeNullData));
area1LineR
.attr("x1", (width/2))
.attr("x2", (width/2))
.attr("y1", (height-marginBottom-5))
.attr("y2", (height-marginBottom-5))
.transition()
.delay(start)
.duration(500)
.attr("x1", 0)
.transition()
.delay(finishChartOut+300)
.duration(500)
.attr("x1", (width/2))
area1LineL
.attr("x1", (width/2))
.attr("x2", (width/2))
.attr("y1", (height-marginBottom-5))
.attr("y2", (height-marginBottom-5))
.transition()
.delay(start)
.duration(500)
.attr("x2", width)
.transition()
.delay(finishChartOut+300)
.duration(500)
.attr("x2", (width/2))
area2Path
.attr("d", area2(extremeNullData))
.transition()
.delay(beginChartIn + 200)
.duration(700)
.ease(d3.easeElastic, 1, 0.9)
.attr("d", area2(area2Data))
.transition()
.delay(beginChartOut)
.duration(500)
.ease(d3.easeCubic)
.attr("d", area2(extremeNullData))
area2LineR
.attr("x1", (width/2))
.attr("x2", (width/2))
.attr("y1", (height-marginBottom-5))
.attr("y2", (height-marginBottom-5))
.transition()
.delay(start + 300)
.duration(500)
.attr("x1", 0)
.transition()
.delay(finishChartOut)
.duration(500)
.attr("x1", (width/2))
area2LineL
.attr("x1", (width/2))
.attr("x2", (width/2))
.attr("y1", (height-marginBottom-5))
.attr("y2", (height-marginBottom-5))
.transition()
.delay(start + 300)
.duration(500)
.attr("x2", width)
.transition()
.delay(finishChartOut)
.duration(500)
.attr("x2", (width/2));
linePath
.attr("d", line(nullLineData))
.transition()
.delay(beginChartIn+200)
.duration(600)
.ease(d3.easeLinear, 1, 0.4)
.attr("d", line(intermediateLineData))
.attr("class", "line")
.transition()
.delay(beginChartIn+800)
.duration(600)
.ease(d3.easeElastic, 1, 0.4)
.attr("d", line(lineData))
.transition()
.delay(beginChartOut+600)
.duration(500)
.ease(d3.easeCubic)
.attr("d", line(nullLineData))
.transition()
.delay(finishChartOut)
.attr("class", "line done")
axisPath
.transition()
.delay(start)
.duration(500)
.attr("class", "x axis")
.transition()
.delay(finishChartOut+800)
.duration(500)
.attr("class", "x axis done")
}
beginAnimation()
setInterval(beginAnimation, finish)
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment