- Jump To … +
  butterfly_example.ts flamechart_sunburst_example.ts legend_example.ts pie_example.ts plot_area_example.ts plot_zoom_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();
      });