d3
Draw diagrams
A histogram
Basic part
<! DOCTYPEhtml>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>plot</title>
<style>* {box-sizing: border-box;
}
#main{
margin: 20px;
width: 600px;
height: 600px;
border: 1px solid #ddd;
}
.item{
cursor: pointer;
}
.item:hover{
opacity: 0.9;
}
</style>
</head>
<body>
<div id="main"></div>
<script src="https://d3js.org/d3.v6.js"></script>
<script>
/*===========1- Required data ===========*/
/* Categories */
const categories=['html'.'css'.'js'];
/* Data source: two series of data */
const source=[
//html css js
[ 30.20.40].// The number of students
[ 40.30.50].// Employment number
]
/* Color palette */
const color=['#c23531'.'#2f4554'.'#61a0a8'.'#d48265'.'#91c7ae'.'#749f83'.'#ca8622'.'#bda29a'.'#6e7074'.'# 546570'.'#c4ccd3'];
/*===========2- Create the container object ===========*/
/* Get the main container */
const main=d3.select('#main')
/* Declare drawing box size width, 600 height, 600 */
const width=600
const height=600
/* Create SVG object * SVG canvas size 100% fill container object * Drawing frame size set to 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- Create axis-related basic data ===========*/
/*----- Basic data related to the x axis -----*/
/* Count the number of categories len*/
const len=categories.length
/* use range() to get the X-axis xChartData in the charting coordinate system based on the number of categories, such as [0,1,2]*/
const xChartData=d3.range(len)
/* The starting and ending points of the X-axis in the pixel coordinates are xPixelRange, offset by 50*/
const xPixelRange=[50,width-50]
/*----- basic data related to the y axis -----*/
/* Calculate the extreme value of all data in the data source maxY * expand the data source with JS native method flat(), and then use Max () method to get the extreme value * */
const maxY=Math.max(... source.flat())/* Declare the starting and ending points of the Y-axis in the charting coordinate system yChartRange*/
const yChartRange=[0,maxY]
/* Declare the starting and ending points of the Y-axis data in the pixel coordinate system yPixelRange*/
const yPixelRange=[height-50.50]
/*===========4- Establish scale ===========*/
/*-----x scale xScale-----*/
/* * scaleBand() : xScale * Domain () : xChartData * rangeRound() : rangeRound() : The padding() method is used to set the inner margin of the class, in percentage units, such as 0.1 * */
const xScale=d3.scaleBand()
.domain(xChartData)
.rangeRound(xPixelRange)
/*-----y scale xScale-----*/
YScale scaleLinear(); yChartRange (); yChartRange (); That is, the start and end bits of the pixel, yPixelRange * */
const yScale=d3.scaleLinear()
.domain(yChartRange)
.range(yPixelRange)
/*===========5- Create the axis object ===========*/
/*----- X-axis object -----*/
/* Create a scale-down axis generator xAxisGenerator*/ using the axisBottom() method based on the scale xScale
const xAxisGenerator=d3.axisBottom(xScale)
/* Draw axis with axis generator * add g object to APPend in SVG * set x axis y position with translateY in transform property * call xAxisGenerator axis generator with call() method, Generate axes * selectAll text text with selectAll() * set chart data to category data with text() * set font size with attr() * */
svg.append('g')
.attr('transform'.`translate(0, ${height-50}) `)
.call(xAxisGenerator)
.selectAll('text')
.text(n= >categories[n])
.attr('font-size'.'12px')
/*----- Y-axis object -----*/
/* Create a left-graduated axis generator yAxisGenerator*/ using the axisLeft() method based on the scale yScale
const yAxisGenerator=d3.axisLeft(yScale)
/* Generate axis with axis generator * add g object to SVG append * set y axis x position with transform property translate * call xAxisGenerator axis generator with call() method, Generate axes * set font size with attr() method * */
svg.append('g')
.attr('transform'.'translate(50 0)')
.call(yAxisGenerator)
.attr('font-size'.'12px')
/*===========6- Create basic data related to the drawing area ===========*/
/*----- Basic data related to the plot area -----*/
/* Get a class pixel width xBandW*/ from xScale's bandwidth()
const xBandW=xScale.bandwidth()
console.log('xBandW',xBandW);
/* Get the number of series n*/
const n=source.length
/* Divide the category width by the number of series to get the width of each series element in a category. ColW */
const colW=xBandW/n
console.log('colW',colW);
/* Count the number of colors in the palette colorLen*/
const colorLen=color.length
/*===========7- Architecture drawing area ===========*/
/* create seriesObjs in SVG, create seriesObjs in SVG * add g object to append in SVG * selectAll() selectAll g elements. Instead, create a selection set object * bind the data source with the series of information to the series with the data() method * use join() to batch create g elements based on the data sources, one G represents a series, Each G element is then filled with three columns of a different class. * Set the series x pixel position using the Transform attribute Translate -- the column width multiplied by the series index * Based on the series index, take the color from the palette and use it as the fill color for all shapes in the series * */
const seriesObjs=svg.append('g')
.selectAll('g')
.data(source)
.join('g')
.attr('transform'.(seriesData,seriesInd) = >{
const seriesX=colW*seriesInd
console.log('seriesData:' + seriesData, 'seriesInd:' + seriesInd);
console.log(seriesInd + 'color:' + color[seriesInd%colorLen]);
return `translate(${seriesX}`, 0)
})
.attr('fill'.(seriesData,seriesInd) = >color[seriesInd%colorLen])
/* selectAll rect elements with seriesObjs selectAll(), Use to create selection set objects * bind data previously bound in each collection to the cylinder collection using the data() method * batch create rect elements based on each collection of data using join() * add item attributes to them using classed() method * */
const rects=seriesObjs.selectAll('rect')
.data(seriesData= >seriesData)
.join('rect')
.classed('item'.true)
console.log('rects',rects);
/*=8- Use attr() to set the x, y positions and width, height dimensions of each cylinder =*/
/* * Set the column's x pixel level * From the callback parameter to get the column's index in the current series rectInd, seriesInd * Based on the column's index in the current series rectInd, * Set column width to column width colW * Set column y pixel * Deconstruct column data from callback parameters * rectData based on column data rectData, RectData * rectData is deconstructed from the callback parameter. RectData * rectData is the height of the column * */ by subtracting the number of pixels calibrated to the actual data on the column from the 0 scale on the y axis
rects.attr('x'.(rectData,rectInd) = >xScale(rectInd))
.attr('width',colW)
.attr('y'.rectData= >yScale(rectData))
.attr('height'.rectData= >yScale(0)-yScale(rectData))
</script>
</body>
</html>
Copy the code
Mouse events
<script>
/*===========9- Create prompt object ===========*/
/* Add div to main using append() as tip * Add id * */ to tip using attr()
const tip=main.append('div')
.attr('id'.'tip')
/*===========10- Added mouse event for column ===========*/
/*----- mouseover-----*/
/* * Resolves the target object and mouse position from the first callback parameter in the event * mouse position clientX,clientY * resolves the data for the current cylinder from the second callback parameter in the event * Cylinder data rectData * Cylinder name rectName * series name SeriesName * Based on mouse position and column information display prompt * style() set display to block * style() set left, top position * HTML () set the HTML content of the element * */
rects.on('mouseover'.({clientX,clientY},{rectData,rectName,seriesName}) = >{
tip.style('display'.'block')
.style('left'.`${clientX}px`)
.style('top'.`${clientY}px`)
.html(`
<div>${rectName}</div>
<div>${seriesName}:${rectData}</div>
`)})/*----- mouse movement event mousemove-----*/
/* Set the left and top positions */
rects.on('mousemove'.({clientX,clientY}) = >{
tip.style('left'.`${clientX}px`)
.style('top'.`${clientY}px`)})/*----- mouse pointer event mouseout-----*/
/* Hide the hint */
rects.on('mouseout'.() = >{
tip.style('display'.'none')})</script>
Copy the code
The transition animation
<script>
/*=8- Use attr() to set the x, y positions and width, height dimensions of each cylinder =*/
/* * Set the column's x pixel level * From the callback parameter to get the column's index in the current series rectInd, seriesInd * Based on the column's index in the current series rectInd, * Set column width to column width colW * Set column y pixel * Deconstruct column data from callback parameters * rectData based on column data rectData, RectData * rectData is deconstructed from the callback parameter. RectData * rectData is the height of the column * */ by subtracting the number of pixels calibrated to the actual data on the column from the 0 scale on the y axis
rects.attr('x'.({rectData},rectInd) = >xScale(rectInd))
.attr('width',colW)
/* The first keyframe - the initial state of the cylinder * y the pixel bit of the scale 0 on the y axis * height 0 * */
rects.attr('y'.() = >yScale(0))
.attr('height'.0)
/* Second keyframe - complete state of the cylinder * Transition () establish tween animation * duration() animation time * delay animation delay * ease interpolation algorithm for tween animation, e.g. D3.easebounce, See https://github.com/d3/d3-ease * */
rects.transition()
.duration(1000)
.delay(({rectInd,seriesInd}) = >{
console.log('rectInd:'+rectInd, 'seriesInd:'+seriesInd);
return (seriesInd+rectInd)*300
})
.ease(d3.easeBounce)
.attr('y'.({rectData}) = >yScale(rectData))
.attr('height'.({rectData}) = >yScale(0)-yScale(rectData))
</script>
Copy the code
Mouse slow follow
<script>
/*===========9- Create prompt object ===========*/
/* Add div to main using append() as tip * Add id * */ to tip using attr()
const tip=main.append('div')
.attr('id'.'tip')
/*===========10- Added mouse event for column ===========*/
/*----- mouseover-----*/
/* * Resolves the target object and mouse position from the first callback parameter in the event * mouse position clientX,clientY * resolves the data for the current cylinder from the second callback parameter in the event * Cylinder data rectData * Cylinder name rectName * series name SeriesName * Based on mouse position and column information display prompt * style() set display to block * style() set left, top position * HTML () set the HTML content of the element * */
/* slow follow * update end position endPos * start slow follow * */
rects.on('mouseover'.({clientX,clientY},{seriesName,rectName,rectData}) = >{
tip.style('display'.'block')
.html(`
<div>${seriesName}</div>
<div>${rectName}:${rectData}</div>
`)
easeTip.endPos={x:clientX,y:clientY}
easeTip.play=true
})
/*----- mouse movement event mousemove-----*/
/* Set the left and top positions */
/* Slow following * update end position endPos * */
rects.on('mousemove'.({clientX,clientY}) = >{
easeTip.endPos={x:clientX,y:clientY}
})
/*----- mouse pointer event mouseout-----*/
/* Hide the hint */
/* Slow to follow * remove animation frames * */
rects.on('mouseout'.() = >{
tip.style('display'.'none')
easeTip.play=false
})
/*===========12- The hint of easing follows ===========*/
/*EaseObj EaseObj * target EaseObj * FM current animation frame * POS drawing position * endPos target position * ratio movement ratio, e.g. 0.1 * _play whether to start to ease following * */
class EaseObj{
/* Constructor */
constructor(target){
this.target=target
this.fm=0
this.pos={x:0.y:0}
this.endPos={x:0.y:0}
this.ratio=0.1
this._play=false
}
/* The value of the play property */
get play() {return play
}
/* The present value of the play property is not equal to the past value * When the present value is true * slow follow * update target object * continuous render * When the present value is false * remove animation frames, cancel continuous render * */
set play(val) {if(val! = =this._play){
if(val){
this.render()
}else{
this.cancel()
}
this._play=val
}
}
/* endPos * Update the style of the target object * continuous render * */
render(){
const {pos,endPos,ratio,target}=this
// Why not go beyond the mouse position? Because pos.x gradually becomes larger in the process of motion, and when it is directly larger than endpos. x in the end, it cannot continue to increase, which is the solution to the puzzle of elastic animation of pie chart in Canvas video
pos.x+=(endPos.x-pos.x)*ratio
pos.y+=(endPos.y-pos.y)*ratio
target.style('left'.`${pos.x}px`)
.style('top'.`${pos.y}px`)
this.fm=requestAnimationFrame(() = >{
this.render()
})
}
/*cancel removes animation frames and cancels continuous rendering */
cancel(){
cancelAnimationFrame(this.fm)
}
}
EaseTip instantiates the easeTip object */
const easeTip=new EaseObj(tip)
</script>
Copy the code