• Jump To … +
    butterfly_example.ts dark_example.ts flamechart_example.ts icicle_sunburst_example.ts legend_example.ts pie_example.ts plot_area_example.ts plot_zoom_example.ts region_example.ts sankey_example.ts scatterplot_example.ts stack_example.ts swimlanes_example.ts sync_example.ts table_searchable_example.ts table_selectable_example.ts table_sortable_example.ts timeline_example.ts worldbank_example.ts
  • ¶

    C3 Scatter Plot

    This example covers a scatter plot using different C3 concepts as discussed in the API Overview.

  • ¶

    Student Data

  • ¶

    An interface is just for TypeScript type annotation and can be ignored for JavaScript.

    interface StudentData {
        name: string
        id: number
        gender: string
        age: number
        grade: number
    }
    var student_id = 0;
  • ¶

    Create student_data array.

    var student_data = [
        { name: "Joe", id: ++student_id, gender: "male", age: 16, grade: 3.2 },
        { name: "Lisa", id: ++student_id, gender: "female", age: 18, grade: 2.3 },
        { name: "Patrick", id: ++student_id, gender: "male", age: 28, grade: 1.4 },
        { name: "Mandy", id: ++student_id, gender: "female", age: 32, grade: 3.8 },
        { name: "Tommy", id: ++student_id, gender: "male", age: 9, grade: 1.7 },
    ];
  • ¶

    Scatter Plot

  • ¶

    Create the scatter plot and attach to the DOM.

    var my_chart = new c3.Plot<StudentData>({
        anchor: '#scatter_plot_example',
        height: 300,
        width: '50%',
  • ¶

    Scales

  • ¶

    Setup the scales for student ages 0-50 and grades 0-4.

        h: d3.scale.linear().domain([0, 50]),
        v: d3.scale.linear().domain([0, 4]),
  • ¶

    Axes

  • ¶

    Add margins and disable cropping so the dots can extend past the edge of the plot.

        margins: 10,
        crop_margins: false,
  • ¶

    Add axes for grid lines and labels

        axes: [
            new c3.Axis.X({
                grid: true,
                label: "Age",
            }),
  • ¶

    The y axis will display letter grades

            new c3.Axis.Y({
                grid: true,
                label: "Grade",
                tick_values: [0, 1, 2, 3, 4],
  • ¶

    Create a quantize scale to translate from numeric to letter grades.

                tick_label: d3.scale.quantize<string>()
                    .domain([0, 4])
                    .range(['F', 'D', 'C', 'B', 'A']),
            }),
        ],
  • ¶

    Layers

  • ¶

    Setup the plot layers

        layers: [
  • ¶

    Create a c3.Plot.Layer.Scatter scatter layer

            new c3.Plot.Layer.Scatter<StudentData>({
  • ¶

    Data set accessors

  • ¶

    Bind with student data

                data: student_data,
  • ¶

    key accessor to assign unique id. This aids in animations when students are added and removed.

                key: (student) => student.id,
  • ¶

    Accessors for x and y positions based on the age and grade of the students.

                x: (student) => student.age,
                y: (student) => student.grade,
  • ¶

    All dots have a radius of 10

                r: 10,
  • ¶

    Filter data for underage students

                filter: (student) => student.age >= 16,
  • ¶

    Selection Options

  • ¶

    Enable animation in the chart when updating data

                point_options: {
                    animate: true,
                    duration: 1000,
                },
  • ¶

    Enable labels

                label_options: {
                    text: (student) => student.grade.toFixed(1),
                    styles: {
                        'fill': (student) => student.gender === 'male' ? "white" : "black",
                        'font-size': "x-small",
                    },
                },
    
                circle_options: {
  • ¶

    Styles

  • ¶

    Set CSS classes based on the data. There is a corresponding CSS rule in the <style> section of the example’s HTML to give failing students a red stroke.

                    classes: {
                        'passing': (student) => student.grade >= 2,
                        'failing': (student) => student.grade < 2,
                    },
  • ¶

    Style the circles by coloring the dots based on the student’s gender

                    styles: {
                        'fill': (student) => student.gender === 'male' ? "blue" : "pink",
                    },
  • ¶

    Event Handlers

  • ¶

    Add event handlers to expand the size of the circle when the user hovers over them and displays detailed student information for that particular student in an info box elsewhere on the page.

                    events: {
                        'mouseenter': function(student) {
                            d3.select('#info_box').html("<b>" + student.name + "</b> " +
                                (student.gender === 'male' ? 'm' : 'f') + "<br/>" +
                                "age: " + Math.round(student.age) + "<br/>" +
                                "grade: " + Math.round(student.grade * 10) / 10);
                            d3.select(this).transition().duration(500).attr('r', 20);
                        },
                        'mouseleave': function() {
                            d3.select('#info_box').html("<i>Hover over student to view info</i>");
                            d3.select(this).transition().duration(500).attr('r', 10);
                        },
  • ¶

    Add click event to delete an existing student. redraw() must be called for the chart to reflect the updated dataset.

                        'click': function(student) {
                            var index = student_data.indexOf(student);
                            if (index >= 0) {
                                alert("Deleting " + student.name);
                                student_data.splice(student_data.indexOf(student), 1);
                                my_chart.redraw();
                            }
                            (<MouseEvent>d3.event).stopPropagation();
                        },
                    },
                },
            }),
  • ¶

    Create a horizontal line layer for the class grade average.

            new c3.Plot.Layer.Line.Horizontal<number>({
                data: [],
  • ¶

    Assign the layer with the CSS class average-grade. There is a corresponding CSS rule in the HTML <style> to give this layer a purple stroke.

                class: 'average-grade',
  • ¶

    Give the line a label.

                label_options: {
                    text: (grade)=> "Avg Grade: "+grade.toFixed(2),
                },
  • ¶

    Extend Custom Functionality

  • ¶

    Extend custom functionality with a handler to update the layer’s data based on the average grade of the class by adding a redraw_start callback to the layer. This is called everytime the data is updated or resized, but before any built-in functionality, so you can use it to update the dataset in advance.

                handlers: {
                    redraw_start: function () {
                        var students = student_data.filter((student) => student.age >= 16);
                        var average_grade = d3.sum(students, (student) => student.grade) / students.length;
                        this.data = isNaN(average_grade) ? [] : [average_grade];
                    },
                }
            }),
        ],
  • ¶

    Setup handlers to extend custom functionality

        handlers: {
  • ¶

    Add a render callback to add guidelines to track cursor movement using D3. render is used for custom initialization and is only called once when the plot is first rendered.

            render: function () {
                this.content.all.append('line').attr('class', 'guideline x');
                this.content.all.append('line').attr('class', 'guideline y');
                this.content.all.selectAll('line.guideline')
                    .style('display', 'none')
                    .style('stroke', 'orange')
                    .style('pointer-events', 'none')
                    .style('shape-rendering', 'crispEdges');
            },
  • ¶

    Add a resize callback to properly size the guidelines. resize is called whenever the chart is resized.

            resize: function () {
                this.content.all.selectAll('line.guideline.x').attr('y2', this.content.height);
                this.content.all.selectAll('line.guideline.y').attr('x2', this.content.width);
            },
        },
    
        content_options: {
  • ¶

    Add event callbacks for custom behavior.

            events: {
  • ¶

    Setup mousemove and mouseleave event callbacks to track the mouse movement with the guidelines.

    Note use of d3.mouse() to identify the mouse location.

                'mousemove': function () {
                    var [x, y] = d3.mouse(this);
                    my_chart.content.all.selectAll('line.guideline.x')
                        .style('display', '')
                        .attr('x1', x)
                        .attr('x2', x);
                    my_chart.content.all.selectAll('line.guideline.y')
                        .style('display', '')
                        .attr('y1', y)
                        .attr('y2', y);
                },
                'mouseleave': function () {
                    my_chart.content.all.selectAll('line.guideline')
                        .style('display', 'none');
                },
  • ¶

    Setup a click event handler to add a new student with an age and grade based on where the user clicked.

                'click': function () {
    
                    var [x, y] = d3.mouse(this);
                    var student = {
                        name: d3.scale.quantize<string>().range(["Chris", "Alex", "Pat", "Ryan"])(Math.random()),
                        id: ++student_id,
                        gender: Math.random() > 0.5 ? 'male' : 'female',
                        age: (<d3.scale.Linear<number, number>>my_chart.h).invert(x),
                        grade: (<d3.scale.Linear<number, number>>my_chart.v).invert(y),
                    };
                    if (student.age < 16) alert("Student is too young to track");
                    else {
                        student_data.push(student);
                        my_chart.redraw();
                    }
                },
            },
        },
    });
  • ¶

    Render

  • ¶

    Render your chart

    my_chart.render();
  • ¶

    Resize

  • ¶

    Resize the chart when the window is resized

    window.onresize = () => {
        my_chart.resize();
    }
  • ¶

    Data update and redraw

  • ¶

    Randomly adjust the student grades and call redraw() to update. Because we enabled animation in this chart, the student dots will smoothly transition to their new locations.

    d3.select('#event_regrade_button').on('click', function () {
        for (let student of student_data) {
            student.grade += 2 * Math.random() - 1;
            if (student.grade > 4) student.grade -= (student.grade - 4) * 2;
            if (student.grade < 0) student.grade += -student.grade * 2;
        }
        my_chart.redraw();
    });