mapování pomocí d3.js

Dost jsem přemýšlel o tom, jak vývojáři postupují při výběru a učení knihoven grafů. Cítím, že d3.js je pravděpodobně nejvlivnějším nástrojem mezi vývojářskou komunitou pro vytváření interaktivních vizualizací dat. Můj zájem o d3 začal, když jsem začátkem tohoto roku (leden 2020) navštívil stav javascriptových webových stránek. Zamiloval jsem se do způsobu, jakým byly na webu vyvíjeny grafy, protože vysvětlovaly věci, které je opravdu těžké vysvětlit slovy nebo pouhým pohledem na nezpracovaná data.

Jsem velkým fanouškem kriketu a Sachin. Chtěl jsem si představit, jak si každý rok vedl od svého debutu až do důchodu. Data, která jsem použil k vytvoření grafu, najdete zde. Sachin data podle roku – github gist.

Konečný výstup vypadá níže

Podívejme se na kroky, jak se tam dostat.

Krok – 1

Stáhněte si nejnovější soubor knihovny d3 a umístěte jej do složky, kde je vytvořen index.html.

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>My Timeline</title>
    </head>
    <body>
        <div id="wrapper"></div>

        <script src="./d3.v5.js"></script>
        <script src="./chart.js"></script>
    </body>
</html>

Krok – 2

Ke spuštění dev serveru v aktuální složce použijte node live-server nebo python SimpleHTTPServer.

 live-server // starts the webserver on port 8080
 python -m SimpleHTTPServer // start the webserver on port 8000

Krok – 3

Využijme interní funkce d3 ke generování spojnicového grafu. Vysvětlím význam každého řádku s v souboru js.

async function drawLineChart() {

    // 1. Read the data from json
    const dataset = await d3.json("./sachin-by-year.json")

    // 2. Verify if your data is retrieved correctly.
    console.log("what is dataset ", dataset)

    /* 3. Define x and y axis accessor methods 
       (x-axis -> Year, y-axis -> runs). Accessor methods 
       helps to fetch the to be plotted info from the datapoint.
       Say the datapoint represent an object e.g dataset[0] 
       from dataset.
    */
    const yAccessor = d => parseInt(d.runs)
    const dateParser = d3.timeParse("%Y")
    const xAccessor = d => dateParser(parseInt(d.year))

    /*
      4. Define chart dimensions (external border, #wrapper) 
         and bounds (internal border, covers axes labels or info 
         on the chart). In general the dimensions depend on the
         amount of space we get on the page for the chart.
    */
    let dimensions = {
        width: window.innerWidth * 0.6,
        height: 400,
        margin: {
            top: 15,
            right: 15,
            bottom: 40,
            left: 60,
        },
    }
    dimensions.boundedWidth = dimensions.width -
        dimensions.margin.left -
        dimensions.margin.right
    dimensions.boundedHeight = dimensions.height -
        dimensions.margin.top -
        dimensions.margin.bottom


    /*
       5. Select an external wrapper (for the chart).
       d3's d3-selection module helps in querying 
       and manipulating the DOM. (If you are familiar with
       JQuery module methods, this module doco is easy
       to understand). 
    */
    const wrapper = d3.select("#wrapper")
        .append("svg")
        .attr("width", dimensions.width)
        .attr("height", dimensions.height)

    /* 
      Note: This explanation is copied from book. FYI

      The <g> SVG element is not visible on its own, but 
      is used to group other elements. Think of it as the 
      <div> of SVG — a wrapper for other elements. We can 
      draw our chart inside of a <g> element and shift it 
      all at once using the CSS transform property.
    */
    const bounds = wrapper.append("g")
        .style("transform", `translate(${
        dimensions.margin.left
      }px, ${
        dimensions.margin.top
      }px)`)

    // 6. Define scales (x and y scales)
    const yScale = d3.scaleLinear()
        .domain(d3.extent(dataset, yAccessor))
        .range([dimensions.boundedHeight, 0])

    /*
       I want to understand the years when sachin
       Scored just 500 runs in a year. (Area with light
       blue colour in the graph depicts that)
    */
    const runsLessThan500InAYear = yScale(500)
    const runsLessThan500 = bounds.append("rect")
        .attr("x", 0)
        .attr("width", dimensions.boundedWidth)
        .attr("y", runsLessThan500InAYear)
        .attr("height", dimensions.boundedHeight
            - runsLessThan500InAYear)
        .attr("fill", "#e0f3f3")

    // x axis defines years from 1989 to 2012
    /*
      Note: I thought of using x axis labels as scaleLinear()
      but the problem with that was labels were treated as numbers
      and the display was like 1,998, 1,999 etc which is wrong.
      Hence i used date parser to show the labels like years.May be
      there is a better way to do this.
    */
    const xScale = d3.scaleTime()
        .domain(d3.extent(dataset, xAccessor))
        .range([0, dimensions.boundedWidth])

    // 7. Map data points now
    const lineGenerator = d3.line()
        .x(d => xScale(xAccessor(d)))
        .y(d => yScale(yAccessor(d)))

    /* 
       Use 'attr' or 'style' methods to add css

       Note: As this is a simple example, CSS and JS are 
       mixed.
    */
    const line = bounds.append("path")
        .attr("d", lineGenerator(dataset))
        .attr("fill", "none")
        .attr("stroke", "#af9358")
        .attr("stroke-width", 2)

    // 8. Draw bounds (x and y both axes)
    const yAxisGenerator = d3.axisLeft()
        .scale(yScale)

    const yAxis = bounds.append("g")
        .call(yAxisGenerator)

    const xAxisGenerator = d3.axisBottom()
        .scale(xScale)

    /*
     d3 don't know where to place the axis 
     line and hence the transform property required to 
     place it where we want. In this case we displace it 
     along y axis by boundedHeight.
    */
    const xAxis = bounds.append("g")
        .call(xAxisGenerator)
        .style("transform", `translateY(${
        dimensions.boundedHeight
      }px)`)

}
drawLineChart()

Závěr

Nemám dostatek vývojářských zkušeností s d3, abych to mohl porovnat s knihovnami grafů a poskytnout výhody. Můj další krok je ušpinit si ruce s d3 knihovnou čtením knihy Amelie Wattenberger :P. Úryvky kódu budu sdílet, jakmile se naučím koncept v knize prostřednictvím článků dev.to :).

Budu rád za konstruktivní zpětnou vazbu k článku. Podělte se prosím o své zkušenosti s d3 a jakýmikoli dobrými knihami, abyste se naučili toto téma.

Děkuji :)