简体   繁体   中英

D3: How to update a line chart when selecting from a drop-down menu?

Let's assume I have the following data:

Year      Teams     Matches    Goals
2014      32        64         171
2010      32        64         125
2006      32        64         147
2002      32        64         161
1998      32        64         171
1994      24        52         141

I'm trying to create a line plot with data from either Teams , Matches , or Goals (based on which of these the user selects from a drop-down menu).

Here is my code:

index.html file:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <link rel="stylesheet"
          href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css"
          integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N"
          crossorigin="anonymous">

    <title>Line Chart</title>

</head>
<body>

<div class="row">

    <div class="row">
        <div id="line-plot-user-selection">
            <div class="form-group">
                <select class="form-control" id="metric-type" onchange="updateMetric()">
                    <option value="Teams">Number of Teams</option>
                    <option value="Matches">Number of Matches</option>
                    <option value="Goals" selected>Number of Goals</option>
                </select>
            </div>
        </div>
    </div>

    <div id="line-plot-div">

    </div>
</div>



</body>
</html>

In main.js :

let promises = [
    d3.csv('data/world-cup-data.csv', d3.autoType),
];

Promise.all(promises)
    .then(function (data) {
        createVis(data)
    })
    .catch(function (err) {
        console.log(err)
    });


function createVis(data) {

    let lineChartData = data[0]

    // Instantiate the visualizations
    lineChart = new LineChart('line-plot-div', lineChartData, 'Test Title');

}




let metricType = document.getElementById('metric-type').value;

function updateMetric() {

    metricType = document.getElementById('metric-type').value;

    lineChart.wrangleData();

}

Then, in lineChart.js :

class LineChart {

    constructor(parentElement, data, chartTitle) {
        this.parentElement = parentElement;
        this.data = data;
        this.chartTitle = chartTitle;

        this.initVis()
    }

    initVis() {

        let vis = this;

        vis.metricType = 'Teams';

        vis.margin = {top: 80, right: 90, bottom: 90, left: 80};
        vis.width = 800 - vis.margin.left - vis.margin.right;
        vis.height = 700 - vis.margin.top - vis.margin.bottom;

        // initialize the drawing area
        vis.svg = d3.select('#' + vis.parentElement).append('svg')
            .attr('width', vis.width + vis.margin.left + vis.margin.right)
            .attr('height', vis.height + vis.margin.top + vis.margin.bottom)
            .append('g')
            .attr('transform', `translate (${vis.margin.left}, ${vis.margin.top})`);


        // scales and axes
        vis.xScale = d3.scaleLinear()
            .range( [ 0, vis.width ] );

        vis.yScale = d3.scaleLinear()
            .range( [ vis.height, 0 ] );

        vis.xAxis = d3.axisBottom()
            .scale(vis.xScale);

        vis.yAxis = d3.axisLeft()
            .scale(vis.yScale);

        // add chart title
        vis.svg.append('g')
            .attr('class', 'title line-chart-title')
            .append('text')
            .text(vis.chartTitle)
            .attr('transform', `translate(${vis.width / 2 + 45}, -30)`)
            .attr('text-anchor', 'middle');


        // create the axis groups
        vis.xAxisGroup = vis.svg.append('g')
            .attr('class', 'x-axis axis')
            .attr('transform', 'translate(0, ' + vis.height + ')');

        vis.yAxisGroup = vis.svg.append('g')
            .attr('class', 'y-axis axis');


        // create axis labels

        // vertical axis
        vis.svg.append('text')
            .attr('x', -65)
            .attr('y', -20)
            .text('Count')
            .style('font-size', 20)
            .style('font-weight', 600)
            .style('fill', 'blue');

        // horizontal axis
        vis.svg.append('text')
            .attr('x', vis.width - 35)
            .attr('y', vis.height + 50)
            .text('Year')
            .style('font-size', 20)
            .style('font-weight', 600)
            .style('fill', 'blue');

        this.wrangleData()

    }

    wrangleData() {

        let vis = this;

        let filteredData = []

        vis.metricType = metricType;

        filteredData = vis.data


        vis.updateVis()

    }

    updateVis() {

        let vis = this;

        // update the domains
        vis.xScale.domain([ d3.min(vis.data, function(d) {return d.Year; }), d3.max(vis.data, function(d) {return d.Year }) ]);
        vis.yScale.domain( [ 0, d3.max(vis.data, function(d) {return d[vis.metricType]; }) ] );

        // draw the line
        vis.line = vis.svg.selectAll('.line')
            .data(vis.data)

        vis.line.exit().remove();

        // draw the line
        vis.line
            .enter()
            .append('path')
            .attr('class', 'line')
            .merge(vis.line)
            .datum(vis.data)
            .transition()
            .duration(500)
            .attr('d', d3.line()
                .x(function (d) { return vis.xScale(d.Year); })
                .y(function (d) { return vis.yScale(d[vis.metricType]); })
                .curve(d3.curveMonotoneX)
            );


        // add the axes
        vis.xAxisGroup
            .transition()
            .duration(500)
            .style('font-size', '15px')
            .style('color', 'blue')
            .call(d3.axisBottom((vis.xScale)).tickFormat(d3.format('')))
            .selectAll('text')
            .attr('y', 20)
            .attr('x', 0)
            .attr('dy', '.35em')

        ;

        vis.yAxisGroup
            .transition()
            .duration(500)
            .style('font-size', '15px')
            .style('color', 'blue')
            .call(d3.axisLeft(vis.yScale));

    }

}

Currently, the line chart for Teams is displaying properly. But, when I select Matches or Goals from the drop-down menu, the line does not update. It remains static and only displays the data for Teams .

I believe the problem is in wrangleData() . It's not grabbing the user's section and applying the filter.

Can someone help me understand how to fix my code so that the line draws when Matches or Goals is selected by the user?

Thanks in advance!

I have just seen your question

i'm a bit confusing why are you making so complicated by using class and constructor for updating graph.. ok anywhay

so i have just made a simple functional graph with dropdown you can see on this live code but i did not used your core graph code, i made the simple functional graph with all futures like tooltip gridlines and transition etc If you dont mind you can chage the design according to your needs. that i'm pasting the codepan source code link too

here is the graph

 const convertRemToPixels = (rem) => { return rem * parseFloat(getComputedStyle(document.documentElement).fontSize); }; const dt = [ { year: "2014", value: 35 }, { year: "2010", value: 32 }, { year: "2006", value: 32 }, { year: "2002", value: 34 }, { year: "1998", value: 25 }, { year: "1994", value: 32 }, ]; const margin = { top: 10, right: 30, bottom: 30, left: 60 }, width = 600 - margin.left - margin.right, height = 400 - margin.top - margin.bottom; // append the svg object to the body of the page const update = (data) => { const svg = d3.select("#mainChart").attr("width", width + margin.left + margin.right).attr("height", height + margin.top + margin.bottom).select("#mainGroup").attr("transform", `translate(${margin.left},${margin.top})`); let tooltip = d3.select("#tooltipContainer").style("position", "absolute").style("top", 0).style("left", 0).style("display", "none"); // grid function function make_y_gridlines() { return d3.axisLeft(y).ticks(6); } // Add X axis --> it is a date format const x = d3.scalePoint().range([0, width]); const y = d3.scaleLinear().domain([0, d3.max(data.map((e) => parseInt(e.value)))]).range([height, 0]); var xAxis = d3.axisBottom(x); var yAxis = d3.axisLeft(y).ticks(6); x.domain( data.map(function (d) { console.log(d); return d.year; }) ).padding([0.2]); svg.select("#XAxis").transition().attr("transform", "translate(0," + height + ")").call(xAxis).style("color", "#a4a4a4"); svg.select("#XAxis").call(xAxis).selectAll("text").style("text-anchor", "end").attr("dx", ".6em").attr("dy", "1em").style("font-size", ".8rem").attr("transform", "rotate(0)").style("font-family", '"Roboto", sans-serif'); // Add Y axis svg.select("#YAxis").transition().call(yAxis).style("color", "#a4a4a4").style("font-size", ".7rem"); svg.select("#linePath").datum(data).attr("fill", "none").attr("stroke", "#14c884").transition().attr( "d", d3.line().x(function (d) { console.log(d); return x(d.year); }).y(function (d) { return y(d.value); }).curve(d3.curveCatmullRom.alpha(1.1)) ); const circle = d3.select("#LineDots"); circle.selectAll(".point").data(data).join("circle").on("mouseover", (e, i) => { d3.select(e.target).transition().attr("r", 4); tooltip.transition().duration(0).style("display", "block"); tooltip.html(`<div>${i.year}: <span>${i.value}</span></div>`).style("left", e.pageX + convertRemToPixels(-1.6) + "px").style("top", e.pageY - convertRemToPixels(2) + "px"); }).on("mouseout", (e) => { d3.select(e.target).transition().attr("r", 2); tooltip.transition().duration(0); tooltip.style("left", "0px").style("top", "0px").style("display", "none"); }).transition().attr("class", "point").attr("stroke", "#14c884").attr("fill", function (d, i) { return "#14c884"; }).attr("cx", function (d, i) { return x(d.year); }).attr("cy", function (d, i) { return y(d.value); }).attr("r", function (d, i) { return 2; }).style("opacity", 1); d3.select("#Grid").transition().call(make_y_gridlines().tickSize(-width).tickFormat("")).attr("id", "gridSystem"); }; update(dt); const selectGroupFromDropDown = document.getElementById("metric-type"); selectGroupFromDropDown.addEventListener("change", (e) => { if(e.target.value === "Matches"){ data = [ { year: "2014", value: 64 }, { year: "2010", value: 64 }, { year: "2006", value: 64 }, { year: "2002", value: 64 }, { year: "1998", value: 64 }, { year: "1994", value: 52 }, ]; update(data); }else if(e.target.value === "Goals"){ data = [ { year: "2014", value: 171 }, { year: "2010", value: 125 }, { year: "2006", value: 149 }, { year: "2002", value: 161 }, { year: "1998", value: 171 }, { year: "1994", value: 141 }, ]; update(data); }else if(e.target.value === "Teams"){ const dt = [ { year: "2014", value: 35 }, { year: "2010", value: 32 }, { year: "2006", value: 32 }, { year: "2002", value: 34 }, { year: "1998", value: 25 }, { year: "1994", value: 32 }, ]; update(dt) } });
 #gridSystem line{ stroke: lightgrey; stroke-opacity: 0.7; shape-rendering: crispEdges; stroke-dasharray: 2 2; stroke-width: .05rem; } #gridSystem path { stroke-width: 0; } /* tooltip */.barTitle{ text-align: center; font-weight: bolder; padding: .2em 0; font-size: .8rem; color: black; }.tooltipContainer div span{ color: #536876; font-weight: bold; }.bar{ border-top-left-radius: 1em;important: } #tooltipContainer { line-height. 1;1: font-weight; bold: padding. .6em 1em;6em 1em: background;white: color; #9cb3c3: border-radius. ;4em: font-weight; 600: box-shadow. 0em 0em,5em rgb(165, 163; 163): font-size. ;6rem: font-family, 'Roboto'; sans-serif; }
 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.6.1/d3.min.js"></script> <select class="form-control" id="metric-type"> <option value="Teams">Number ofTeams</option> <option value="Matches">Number of Matches</option> <option value="Goals" selected>Number of Goals</option> </select> <div id="tooltipContainer"></div> <svg id="mainChart"> <g id="mainGroup"> <g id="XAxis"></g> <g id="YAxis"></g> <g id="Grid"></g> <path id="linePath"></path> <g id="LineDots"></g> </g> </svg>

and here is the same source code from codepan

https://codepen.io/codingdarci/pen/LYrByKG

i hope this would solve your issue

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM