function processTimeline()

in src/core/main.ts [1405:1902]


  function processTimeline(data, shouldDrawTimeline) {
    // check for earliest and latest numerical dates before parsing
    globals.earliest_date = d3.min(data, function (d) {
      if (d.start_date instanceof Date) {
        return d.start_date;
      }
      return +d.start_date;
    });

    globals.latest_start_date = d3.max(data, function (d) {
      if (d.start_date instanceof Date) {
        return d.start_date;
      }
      return +d.start_date;
    });

    globals.latest_end_date = d3.max(data, function (d) {
      if (d.end_date instanceof Date) {
        return d.end_date;
      }
      return +d.end_date;
    });

    // set flag for really epic time scales
    if (globals.isNumber(globals.earliest_date)) {
      if (globals.earliest_date < -9999 || d3.max([globals.latest_start_date, globals.latest_end_date]) > 10000) {
        globals.date_granularity = "epochs";
      }
    }

    //log("date_granularity after: " + globals.date_granularity);

    parseDates(data); // parse all the date values, replace blank end_date values

    // set annotation counter for each item
    data.forEach(function (item) {
      item.annotation_count = 0;
    });

    /**
    ---------------------------------------------------------------------------------------
    PROCESS CATEGORIES OF EVENTS
    ---------------------------------------------------------------------------------------
    **/

    // determine event categories from data
    globals.categories.domain(data.map(function (d) {
      return d.category;
    }));

    globals.num_categories = globals.categories.domain().length;

    globals.max_legend_item_width = 0;

    globals.categories.domain().sort().forEach(function (item) {
      var legend_dummy = document.createElement("span");
      legend_dummy.id = "legend_dummy";
      legend_dummy.style.fontSize = "12px";
      legend_dummy.style.fill = "#fff";
      legend_dummy.style.fontFamily = "Century Gothic";
      legend_dummy.innerHTML = item;
      document.querySelector(".timeline_storyteller").appendChild(legend_dummy);
      var legend_dummy_width = legend_dummy.offsetWidth;
      document.querySelector(".timeline_storyteller").removeChild(legend_dummy);

      if (legend_dummy_width > globals.max_legend_item_width) {
        globals.max_legend_item_width = legend_dummy_width;
      }
    });

    //logEvent("# categories: " + globals.num_categories, "preprocessing");

    var temp_palette;
    // assign colour labels to categories if # categories < 12
    if (globals.num_categories <= 20 && globals.num_categories >= 11) {
      temp_palette = colorSchemes.schema5();
      globals.categories.range(temp_palette);
      temp_palette = undefined;
    } else if (globals.num_categories <= 10 && globals.num_categories >= 3) {
      temp_palette = colorSchemes.schema2();
      globals.categories.range(temp_palette);
      temp_palette = undefined;
    } else if (globals.num_categories === 2) {
      temp_palette = ["#E45641", "#44B3C2"];
      globals.categories.range(temp_palette);
      temp_palette = undefined;
    } else {
      temp_palette = ["#E45641"];
      globals.categories.range(temp_palette);
      temp_palette = undefined;
    }
    if (globals.use_custom_palette) {
      globals.categories.range(globals.color_palette);
      //logEvent("custom palette: " + globals.categories.range(), "color palette");
    }

    filter_div.append("input")
      .attr({
        type: "image",
        name: "Hide filter panel",
        id: "export_close_btn",
        class: "img_btn_enabled",
        src: imageUrls("close.png"),
        height: 15,
        width: 15,
        title: "Hide filter panel"
      })
      .style("position", "absolute")
      .style("top", "0px")
      .style("left", "5px")
      .style("margin-top", "5px")
      .on("click", function () {
        selectWithParent("#filter_div").style("display", "none");

        //logEvent("hide filter panel", "export");
      });

    filter_div.append("text")
      .attr("class", "menu_label filter_label")
      .style("margin-right", "auto")
      .text("Filter Options")
      .style("cursor", "move")
      .call(filterDrag);

    filter_div.append("hr")
      .attr("class", "menu_hr");

    // filter type options
    var filter_type_picker = filter_div.append("div")
      .attr("id", "filter_type_picker")
      .attr("class", "filter_div_section");

    filter_type_picker.append("div")
      .attr("class", "filter_div_header")
      .append("text")
      .attr("class", "menu_label filter_label")
      .text("Filter Mode:");

    var filter_type_rb = filter_type_picker.selectAll("g")
      .data(["Emphasize", "Hide"])
      .enter();

    var filter_type_rb_label = filter_type_rb.append("label")
      .attr("class", "menu_rb");

    filter_type_rb_label.append("input")
      .attr({
        type: "radio",
        name: "filter_type_rb",
        value: function (d) {
          return d;
        }
      })
      .property("disabled", false)
      .property("checked", function (d) {
        return d === "Emphasize";
      });

    filter_type_rb_label.append("img")
      .attr({
        class: "img_btn_enabled",
        height: 30,
        width: 30,
        title: function (d) {
          return d;
        },
        src: function (d) {
          return imageUrls(d === "Emphasize" ? "highlight.png" : "hide.png");
        }
      })
      .style("margin-bottom", "0px");

    filter_type_rb_label.append("span")
      .attr("class", "option_rb_label")
      .html(function (d) {
        return d;
      });

    selectAllWithParent("#filter_type_picker input[name=filter_type_rb]").on("change", function () {
      const newCategories = selectWithParent("#category_picker").select("option");
      const newFacets = selectWithParent("#facet_picker").select("option");
      const newSegments = selectWithParent("#segment_picker").select("option");

      globals.filter_type = this.value;

      selectWithParent("#filter_div").style("display", "inline");

      //logEvent("filter type changed: " + this.value, "filter");

      const isHide = globals.filter_type === "Hide";
      if (!isHide) {
        globals.active_data = globals.all_data;
      }

      const trigger_remove_filter =
        globals.selected_categories[0].length !== 1 || globals.selected_categories[0][0].value !== "( All )" ||
        globals.selected_facets[0].length !== 1 || globals.selected_facets[0][0].value !== "( All )" ||
        globals.selected_segments[0].length !== 1 || globals.selected_segments[0][0].value !== "( All )";

      if (trigger_remove_filter) {
        const remove = globals.dispatch.remove;
        const emphasize = globals.dispatch.Emphasize;
        (isHide ? emphasize : remove).call(globals.dispatch, newCategories, newFacets, newSegments);
        (isHide ? remove : emphasize).call(globals.dispatch, globals.selected_categories, globals.selected_facets, globals.selected_segments);
      }
    });

    var category_filter = filter_div.append("div")
      .attr("class", "filter_div_section");

    var category_filter_header = category_filter.append("div")
      .attr("class", "filter_div_header");

    category_filter_header.append("text")
      .attr("class", "menu_label filter_label")
      .text("Category");

    category_filter_header.append("label")
      .attr("for", "category_picker")
      .style("display", "block")
      .style("margin-right", "100%")
      .attr("id", "category_picker_label")
      .append("img")
      .attr({
        name: "Filter by event category",
        class: "filter_header_icon",
        height: 30,
        width: 30,
        title: "Filter by event category",
        src: imageUrls("categories.png")
      });

    var all_categories = ["( All )"];

    category_filter.append("select")
      .attr("class", "filter_select")
      .attr("size", 8)
      .attr("id", "category_picker")
      .attr({
        multiple: true
      })
      .on("change", function () {
        instance._updateSelectedFilters(d3.select(this), "selected_categories");
      })
      .selectAll("option")
      .data(all_categories.concat(globals.categories.domain().sort()))
      .enter()
      .append("option")
      .text(function (d) { return d; })
      .property("selected", function (d) {
        return d === "( All )";
      });

    globals.selected_categories = selectWithParent("#category_picker")
      .selectAll("option")
      .filter(function () {
        return this.selected;
      });

    /**
    ---------------------------------------------------------------------------------------
    PROCESS FACETS
    ---------------------------------------------------------------------------------------
    **/

    // determine facets (separate timelines) from data
    globals.facets.domain(data.map(function (d) {
      return d.facet;
    }));

    globals.facets.domain().sort();

    globals.num_facets = globals.facets.domain().length;
    globals.total_num_facets = globals.num_facets;
    globals.num_facet_cols = Math.ceil(Math.sqrt(globals.num_facets));
    globals.num_facet_rows = Math.ceil(globals.num_facets / globals.num_facet_cols);

    //logEvent("# facets: " + globals.num_facets, "preprocessing");

    var facet_filter = filter_div.append("div")
      .attr("class", "filter_div_section");

    var facet_filter_header = facet_filter.append("div")
      .attr("class", "filter_div_header");

    facet_filter_header.append("text")
      .attr("class", "menu_label filter_label")
      .text("Facet");

    facet_filter_header.append("label")
      .attr("for", "facet_picker")
      .style("display", "block")
      .style("margin-right", "100%")
      .attr("id", "facet_picker_label")
      .append("img")
      .attr({
        name: "Filter by event facet",
        class: "filter_header_icon",
        height: 30,
        width: 30,
        title: "Filter by event facet",
        src: imageUrls("facets.png")
      });

    var all_facets = ["( All )"];

    facet_filter.append("select")
      .attr("class", "filter_select")
      .attr("size", 8)
      .attr("id", "facet_picker")
      .attr({
        multiple: true
      })
      .on("change", function () {
        instance._updateSelectedFilters(d3.select(this), "selected_facets");
      })
      .selectAll("option")
      .data(all_facets.concat(globals.facets.domain().sort()))
      .enter()
      .append("option")
      .text(function (d) { return d; })
      .property("selected", function (d) {
        return d === "( All )";
      });

    globals.selected_facets = selectWithParent("#facet_picker")
      .selectAll("option")
      .filter(function () {
        return this.selected;
      });

    /**
    ---------------------------------------------------------------------------------------
    PROCESS SEGMENTS
    ---------------------------------------------------------------------------------------
    **/

    // event sorting function
    data.sort(compareAscending);

    if (globals.date_granularity === "epochs") {
      data.min_start_date = globals.earliest_date;
      data.max_start_date = d3.max([globals.latest_start_date, globals.latest_end_date]);
      data.max_end_date = d3.max([globals.latest_start_date, globals.latest_end_date]);
    } else {
      // determine the time domain of the data along a linear quantitative scale
      data.min_start_date = d3.min(data, function (d) {
        return d.start_date;
      });
      data.max_start_date = d3.max(data, function (d) {
        return d.start_date;
      });
      data.max_end_date = d3.max(data, function (d) {
        return time.minute.floor(d.end_date);
      });
    }

    // determine the granularity of segments
    globals.segment_granularity = getSegmentGranularity(data.min_start_date, data.max_end_date);

    data.forEach(function (item) {
      item.segment = getSegment(item.start_date);
    });

    var segment_list = getSegmentList(data.min_start_date, data.max_end_date);

    globals.present_segments.domain(segment_list.map(function (d) {
      return d;
    }));

    var segment_filter = filter_div.append("div")
      .attr("class", "filter_div_section");

    var segment_filter_header = segment_filter.append("div")
      .attr("class", "filter_div_header");

    segment_filter_header.append("text")
      .attr("class", "menu_label filter_label")
      .text("Segment");

    segment_filter_header.append("label")
      .attr("for", "segment_picker")
      .style("display", "block")
      .style("margin-right", "100%")
      .attr("id", "segment_picker_label")
      .append("img")
      .attr({
        name: "Filter by chronological segment",
        class: "filter_header_icon",
        height: 30,
        width: 30,
        title: "Filter by chronological segment",
        src: imageUrls("segments.png")
      });

    var all_segments = ["( All )"];

    segment_filter.append("select")
      .attr("id", "segment_picker")
      .attr("class", "filter_select")
      .attr("size", 8)
      .attr({
        multiple: true
      })
      .on("change", function () {
        instance._updateSelectedFilters(d3.select(this), "selected_segments");
      })
      .selectAll("option")
      .data(all_segments.concat(globals.present_segments.domain().sort()))
      .enter()
      .append("option")
      .text(function (d) { return d; })
      .property("selected", function (d) {
        return d === "( All )";
      });

    globals.selected_segments = selectWithParent("#segment_picker")
      .selectAll("option")
      .filter(function () {
        return this.selected;
      });

    globals.all_data = data;
    globals.active_data = globals.all_data;

    measureTimeline(globals.active_data);
    selectWithParent("#timeline_metadata").style("display", "inline");
    selectWithParent("#timeline_metadata_contents")
      .append("span")
      .attr("class", "metadata_title")
      .style("text-decoration", "underline")
      .text("About this data:");

    selectWithParent("#timeline_metadata_contents")
      .append("div")
      .attr("class", "timeline_metadata_contents_div")
      .html("<p class='metadata_content'><img src='" + imageUrls("timeline.png") + "' width='36px' style='float: left; padding-right: 5px;'/><strong>Cardinality & extent</strong>: " +
        globals.active_data.length + " unique events spanning " + globals.range_text + " <br><strong>Granularity</strong>: " + globals.segment_granularity + "</p>");

    var category_metadata = selectWithParent("#timeline_metadata_contents")
      .append("div")
      .attr("class", "timeline_metadata_contents_div")
      .style("border-top", "1px dashed #999");

    var category_metadata_p = category_metadata
      .append("p")
      .attr("class", "metadata_content")
      .html("<img src='" + imageUrls("categories.png") + "' width='36px' style='float: left; padding-right: 5px;'/><strong>Event categories</strong>: ( " + globals.num_categories + " ) <em><strong>Note</strong>: click on the swatches to assign custom colors to categories.</em><br>");

    var category_metadata_element = category_metadata_p.selectAll(".category_element")
      .data(globals.categories.domain().sort())
      .enter()
      .append("g")
      .attr("class", "category_element");

    category_metadata_element.append("div")
      .attr("class", "colorpicker_wrapper")
      .attr("filter", "url(#drop-shadow)")
      .style("background-color", globals.categories)
      .on("click", function (d, i) {
        var colorEle = this;
        instance._colorPicker.show(this, globals.categories(d), function (value) {
          // Update the display
          d3.select(colorEle).style("background-color", value);

          instance.setCategoryColor(d, i, value);
        });
      });
    //   .append("input")
    //   .attr("type", "color")
    //   .attr("class", "colorpicker")
    //   .attr("value", globals.categories)
    //   .on("change", function (d, i) {

    //   });

    category_metadata_element.append("span")
      .attr("class", "metadata_content")
      .style("float", "left")
      .text(function (d) {
        return " " + d + " ..";
      });

    category_metadata.append("p")
      .html("<br>");

    selectWithParent("#timeline_metadata_contents")
      .append("div")
      .attr("class", "timeline_metadata_contents_div")
      .style("border-top", "1px dashed #999")
      .html(
        "<p class='metadata_content'><img src='" + imageUrls("facets.png") + "' width='36px' style='float: left; padding-right: 5px;'/><strong>Timeline facets</strong>: " +
        ((globals.facets.domain().length > 1) ? ("( " + globals.num_facets + " ) " + globals.facets.domain().slice(0, 30).join(" .. ")) : "(none)") + "</p>");

    if (shouldDrawTimeline) {
      drawTimeline(globals.active_data);
    }
  }