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