https://github.com/thu-vis/MutualDetector
Raw File
Tip revision: 2b019233d6851facadec8e9215cc805eef47932c authored by Changjian Chen on 20 May 2024, 01:52:04 UTC
update readme
Tip revision: 2b01923
render_image_card.js
import * as Global from "../plugins/global";
import * as d3 from "d3";

const ImageCards = function(parent) {
  let that = this;
  that.parent = parent;
  that.server_url = that.parent.server_url;

  that.set_group = that.parent.set_group;
  that.grid_group = that.parent.grid_group;
  that.drag_grid_group = that.parent.drag_grid_group;
  that.label_group = that.parent.label_group;
  that.nav_group = that.parent.nav_group;

  that.nav_gray = "#8f8f8f";
  that.nav_offset_x = 800;
  that.nav_offset_y = 18;
  that.nav_margin = 50;
  that.nav_group.attr(
    "transform",
    "translate(" + that.nav_offset_x + ", " + that.nav_offset_y + ")"
  );
  that.nav_group
    .append("rect")
    .attr("class", "nav-rect")
    .attr("width", 5)
    .attr("height", 0)
    .attr("x", -2.5)
    .attr("fill", Global.GrayColor);

  // animation
  that.create_ani = that.parent.create_ani;
  that.update_ani = that.parent.update_ani;
  that.remove_ani = that.parent.remove_ani;

  // let match_color = "#D3D3E5";
  // let mismatch_color = "#ED2939";
  let match_color = "#D3D3E5";
  let mismatch_color = "#E05246";

  that.image_margin_percent = 0.9;
  that.selected_image_idx = null;
  //
  that.boundingbox_width = 12;

  // let labels = Array(); // Label layout
  let img_width = 80;

  let margin_size = 20;
  // let margin_top_size = 100;
  let plot_width = 800;
  let plot_height = 800;
  var offset_x = 0; // position of the left grid when the mode is "juxtaposition"
  var offset_y = 100;

  let grid_margin = 0.5 * 2;

  this.focus_grid_for_edit = null;
  this.target_grid_image = null;

  let mouse_pressed = false;
  let mouse_pos = {
    x: -1,
    y: -1,
  };

  that.relative_sampling_area = {
    x: 0,
    y: 0,
    w: 1,
    h: 1,
  };
  let plot_x,
    plot_y = 1;
  that.click_ids = [];
  that.click_count = 0;
  that.labels = [];
  that.use_label_layout = false;

  this.get_set_layout_from_parent = function() {
    // set
    that.layout_height = that.parent.layout_height;
    that.set_height = that.parent.set_height;
    that.set_left = that.parent.set_left;
    that.set_width = that.parent.set_width;
    that.set_margin = that.parent.set_margin;
    that.image_height = that.parent.image_height;
    that.image_margin = that.parent.image_margin;
    that.text_height = that.parent.text_height;
    that.top_image_margin = that.parent.top_image_margin;
  };
  this.get_set_layout_from_parent();

  (this.set_focus_image = function(image) {
    that.parent.set_focus_image(image);
  }),
    (this.set_expand_set_id = function(id) {
      that.parent.set_expand_set_id(id);
    });

  this.set_grid_layout_data = function(data) {
    that.parent.set_grid_layout_data(data);
  };

  that.set_animation_time = function() {
    // animation
    that.create_ani = that.parent.create_ani;
    that.update_ani = that.parent.update_ani;
    that.remove_ani = that.parent.remove_ani;
  };
  this.get_expand_set_id = function() {
    return that.parent.expand_set_id;
  };

  this.get_grid_data = function() {
    return that.parent.grid_data;
  };

  this.get_grid_image_info = function() {
    return that.parent.grid_image_info;
  };

  this.get_nav_id = function() {
    return that.parent.nav_id;
  };

  this.visible = function(d) {
    if (that.get_expand_set_id() === -1) {
      if (d.collapse === 1) {
        return 0;
      } else {
        return 1;
      }
    } else {
      return 0;
    }
  };

  this.fetch_grid_layout = function(query) {
    return that.parent.fetch_grid_layout(query);
  };

  this.sub_component_update = function(
    sets,
    vis_image_per_cluster,
    grids,
    grids_pos
  ) {
    // update layout config
    that.get_set_layout_from_parent();

    // update state
    that.vis_image_per_cluster = vis_image_per_cluster;
    sets.forEach((d) => {
      that.vis_image_per_cluster[d.id].forEach((n) => {
        n.collapse = d.collapse;
      });
    });
    that.grids = grids;
    offset_x = grids_pos.offset_x;
    offset_y = grids_pos.offset_y;
    plot_width = grids_pos.side_length;
    plot_height = grids_pos.side_length;
    Object.values(that.vis_image_per_cluster).forEach((d) => {
      // let x = that.image_margin;
      d.forEach((n, i) => {
        n.vis_h = that.image_height;
        n.vis_w = that.image_height;
        n.x = that.parent.x_position(i) + that.image_margin;
        // n.inner_margin = that.image_height * that.image_margin_percent / 2;
        // x = x + n.vis_w + that.image_margin;
      });
    });
    that.labels = that.label_layout(
      Global.deepCopy(that.grids),
      plot_width,
      that.labels
    );
    // let grid_image_info = that.get_grid_image_info();
    // that.labels.forEach(d => {
    //   let info = grid_image_info.find(info => info.idx === d.img_id);
    //   d.d = info.d;
    // })

    that.use_label_layout = that.labels.length ? 1 : 0;
    if (that.get_expand_set_id() < 0) {
      that.click_ids = [];
    } else {
      that.click_ids.push({
        id: that.get_nav_id(),
        layout: Global.deepCopy(grids),
      });
      that.click_ids.forEach((d, i) => {
        // d.x = i * that.nav_margin;
        // d.y = 0;
        d.x = 0;
        d.y = i * that.nav_margin;
        d.last_one = i === that.click_ids.length - 1 ? true : false;
      });
    }
    console.log("image card sub component update", sets, grids);

    // update view
    that.e_sets = that.set_group.selectAll(".set").data(sets, (d) => d.id);
    that.e_grids = that.grid_group
      .selectAll(".grid")
      .data(grids, (d) => d.img_id);
    that.e_labels = that.label_group
      .selectAll(".label")
      .data(that.labels, (d) => d.img_id);
    that.e_navs = that.nav_group
      .selectAll(".nav-circle")
      .data(that.click_ids, (d) => d.id);

    that.remove();
    that.update();
    that.create();
    that.set_animation_time();
  };

  this.create = function() {
    that.set_create();
    that.grid_create();
    that.label_create();
    that.nav_create();
  };

  this.set_create = function() {
    // set
    let set_groups = that.e_sets
      .enter()
      .append("g")
      .attr("class", "set")
      .attr("id", (d) => "set-" + d.id)
      .attr("transform", (d) => "translate(" + d.x + ", " + d.y + ")");
    set_groups
      .style("opacity", 0)
      .on("mouseover", function(ev) {
        if (that.get_expand_set_id() === -1) that.box_highlight(ev);
      })
      .on("mouseout", function() {
        that.box_dehighlight();
      })
      .transition()
      .duration(that.create_ani)
      .delay(that.update_ani + that.remove_ani)
      .style("opacity", 1);

    // expand icon
    set_groups
      .append("rect")
      .attr("class", "expand-rect")
      .attr("x", -11)
      .attr("y", 0)
      .attr("width", 10)
      .attr("height", 10)
      .style("rx", 3)
      .style("ry", 3)
      .style("fill", "white")
      .style("stroke", "gray")
      .style("stroke-width", 1)
      .on("click", (_, d) => {
        if (d.id === that.get_expand_set_id()) {
          that.set_expand_set_id(-1);
        } else {
          if (that.parent.selected_node["node_ids"].length == 0) {
            alert(
              "To check the grid layout, you should select one label in the label hierarchy."
            );
            return;
          }
          that.set_expand_set_id(d.id);
        }
      });

    set_groups
      .append("path")
      .attr("class", "expand-path")
      .style("stroke", "none")
      .style("fill", "gray")
      .attr("d", Global.plus_path_d(-11, 0, 10, 10, 2));


    // set_groups
    //   .append("rect")
    //   .attr("class", "collapse-rect")
    //   .attr("x", -11)
    //   .attr("y", 20)
    //   .attr("width", 10)
    //   .attr("height", 10)
    //   .style("rx", 3)
    //   .style("ry", 3)
    //   .style("fill", "white")
    //   .style("stroke", "gray")
    //   .on("click", (_, d) => {
    //     console.log("collapse click");
    //     d.collapse = d.collapse === 1 ? 0 : 1;
    //     that.parent.update_data();
    //     that.parent.update_view(); // TODO: watch-function way
    //   });
      
    set_groups
      .append("rect")
      .attr("class", "background")
      .style("fill", "white")
      .style("stroke", "#d0d0d0")
      .style("stroke-width", 1.5)
      .attr("width", (d) => d.width)
      .attr("height", (d) => d.height);


    set_groups
      .append("path")
      .attr("class", "collapse-path")
      .style("stroke", "gray")
      .style("stroke-width", "2")
      .style("fill", "white")
      .style("cursor", "hand")
      .attr("d", d => d.collapse ? Global.collapse_icon(11, d.height / 2, 0) 
        : Global.collapse_icon(-11, d.height / 2 - 10, 1))
      .style("opacity", (d) => (that.visible(d) ? 1 : 0))
      .style("pointer-events", (d) => (that.visible(d) ? "auto" : "none"))
      .on("click", (_, d) => {
        console.log("collapse click");
        d.collapse = d.collapse === 1 ? 0 : 1;
        that.parent.update_data();
        that.parent.update_view(); // TODO: watch-function way
      });
  
  

    that.image_groups = set_groups
      .selectAll("g.detection-result")
      .data((d) => that.vis_image_per_cluster[d.id]);
    let g_image_groups = that.image_groups
      .enter()
      .append("g")
      .attr("class", "detection-result")
      .attr(
        "transform",
        (d) => "translate(" + d.x + ", " + that.top_image_margin + ")"
      );

    g_image_groups
      .append("rect")
      .attr("class", "image-shadow")
      .attr("id", (d) => "image-shadow-" + d.idx)
      .attr("x", 0)
      .attr("y", 0)
      .attr("width", (d) => d.vis_w)
      .attr("height", (d) => d.vis_h)
      .style("opacity", (d) => (that.visible(d) ? 1 : 0))
      .style("pointer-events", "none")
      .style("fill", "white")
      .style("stroke", "white")
      .style("stroke-width", 5);
    g_image_groups
      .append("image")
      .attr("id", (d) => "set-image-" + d.idx)
      .attr("x", 0)
      .attr("y", 0)
      .attr("width", (d) => d.vis_w)
      .attr("height", (d) => d.vis_h)
      .style("opacity", (d) => (that.visible(d) ? 1 : 0))
      .style("pointer-events", (d) => (that.visible(d) ? 1 : "none"))
      .attr(
        "href",
        (d) => that.server_url + `/image/image?filename=${d.idx}.jpg`
      )
      .on("mouseover", function(ev) {
        if (that.get_expand_set_id() === -1) that.box_highlight(ev);
        that.image_highlight(ev);
      })
      .on("mouseout", function() {
        that.box_dehighlight();
        that.image_dehighlight();
      })
      .on("click", (ev, d) => {
        console.log("click image", d);
        ev.stopPropagation();
        that.selected_image_idx = d.idx;
        let self = d3
          .select(ev.target.parentElement)
          .selectAll(".image-shadow");
        self.style("stroke", "rgb(237,129,55)");
        // that.set_focus_image(d);
        that.parent.fetch_single_image_detection_for_focus_text({
          image_id: d.idx,
        });
      });

    that.box_groups = that.set_group
      .selectAll(".set")
      .selectAll(".detection-result")
      .selectAll("rect.box")
      .data((d) => {
        // that.box_groups = g_image_groups.selectAll("rect.box").data((d) => {
        let dets = d.d;
        let res = [];
        for (let i = 0; i < dets.length; i++) {
          let x = d.vis_w * dets[i][0];
          let width = d.vis_w * (dets[i][2] - dets[i][0]);
          let y = d.vis_h * dets[i][1];
          let height = d.vis_h * (dets[i][3] - dets[i][1]);
          let collapse = d.collapse;
          res.push({ x, y, width, height, collapse });
        }
        return res;
      });
    that.box_groups
      .exit()
      .transition()
      .duration(that.remove_ani)
      .style("opacity", 0)
      .remove();
    that.box_groups
      .enter()
      .append("rect")
      .attr("class", "box")
      .attr("x", (d) => d.x)
      .attr("y", (d) => d.y)
      .attr("width", (d) => d.width)
      .attr("height", (d) => d.height)
      .style("fill", "none")
      .style("stroke", Global.BoxRed)
      .style("stroke-width", 1)
      .style("opacity", (d) => (that.visible(d) ? 1 : 0))
      .style("pointer-events", (d) => (that.visible(d) ? 1 : "none"));
    that.box_groups
      .transition()
      .duration(that.update_ani)
      .delay(that.remove_ani)
      .attr("x", (d) => d.x)
      .attr("y", (d) => d.y)
      .attr("width", (d) => d.width)
      .attr("height", (d) => d.height)
      .style("opacity", (d) => (that.visible(d)  ? 1 : 0))
      .style("pointer-events", (d) => (that.visible(d) ? "auto" : "none"));
  };

  this.grid_create = function() {
    let set_id = that.get_expand_set_id();
    let selected_set = that.parent.sets[set_id];
    let images = that.vis_image_per_cluster[set_id];
    let dragstarted = function() {
      let tag = d3.select(this);
      console.log("drag start", tag);
      that.timer = setTimeout(function() {
        _dragstarted(tag);
      }, 200);
    };
    let _dragstarted = function(tag) {
      that.focus_grid_for_edit = tag.data()[0];
      that.drag_grid_group
        .append("rect")
        .attr("id", "drag-background")
        .attr("x", that.parent.set_left)
        .attr("y", 0)
        .attr("width", that.parent.set_width)
        .attr("height", that.parent.layout_height)
        .style("fill", "gray")
        .style("opacity", 0.1);
      let drag_grid_group = that.drag_grid_group
        .selectAll("g")
        .data(images)
        .enter()
        .append("g")
        .attr("class", "drag-image")
        .attr(
          "transform",
          (d) =>
            "translate(" +
            (selected_set.x + d.x) +
            ", " +
            that.top_image_margin +
            ")"
        );
      drag_grid_group
        .append("rect")
        .attr("x", 0)
        .attr("y", 0)
        .attr("width", (d) => d.vis_w)
        .attr("height", (d) => d.vis_h)
        .style("pointer-events", "none")
        .style("fill", "white")
        .style("stroke", "white")
        .style("stroke-width", 5);
      drag_grid_group
        .append("image")
        .attr("x", 0)
        .attr("y", 0)
        .attr("width", (d) => d.vis_w)
        .attr("height", (d) => d.vis_h)
        .attr(
          "href",
          (d) => that.server_url + `/image/image?filename=${d.idx}.jpg`
        )
        .on("mouseover", function(ev, d) {
          console.log("drag image mouseover");
          that.target_grid_image = d;
          let element = ev.target;
          let self = d3.select(element.parentElement).selectAll(".rect");
          self.style("stroke", "rgb(237,129,55)");
        })
        .on("mouseout", function() {
          console.log("drag image mouseout");
          that.target_grid_image = null;
          that.drag_grid_group
            .selectAll(".drag-image")
            .selectAll("rect")
            .style("stroke", "white");
        });
      that.drag_grid_group
        .append("rect")
        .attr("id", "drag-grid")
        .attr("x", tag.data()[0].x)
        .attr("y", tag.data()[0].y)
        .attr("width", tag.data()[0].width - 2 * grid_margin)
        .attr("height", tag.data()[0].width - 2 * grid_margin)
        .style("pointer-events", "none")
        .style("stroke", "black")
        .style("fill", "green")
        .style("opacity", 0.5);

      that.grid_group.style("opacity", 0.1);
      that.label_group.style("opacity", 0.1);
    };

    let dragged = function(event) {
      that.drag_grid_group
        .select("#drag-grid")
        .attr("x", event.x)
        .attr("y", event.y);
    };

    let dragended = function() {
      clearTimeout(that.timer);
      console.log("drag end");
      that.drag_grid_group.select("#drag-grid").remove();
      if (that.target_grid_image !== null) {
        let set_id = that.get_expand_set_id();
        let images = that.vis_image_per_cluster[set_id];
        let images_ids = images.map((d) => d.idx);
        let idx = images_ids.indexOf(that.target_grid_image.idx);
        let removed_image = that.vis_image_per_cluster[set_id][idx];
        removed_image.d = that.focus_grid_for_edit.d;
        removed_image.idx = that.focus_grid_for_edit.img_id;
        console.log("image", images);
        that.drag_grid_group
          .selectAll("g")
          .data(images)
          .selectAll("image")
          .attr(
            "href",
            (d) => that.server_url + `/image/image?filename=${d.idx}.jpg`
          );
      }
      that.drag_grid_group.select("#drag-background").remove();
      setTimeout(function() {
        that.drag_grid_group
          .selectAll(".drag-image")
          .transition()
          .duration(that.remove_ani)
          .style("opacity", 0)
          .remove();
        that.grid_group
          .transition()
          .duration(that.remove_ani)
          .style("opacity", 0)
          .style("opacity", 1);
        that.label_group
          .transition()
          .duration(that.remove_ani)
          .style("opacity", 0)
          .style("opacity", 1);
      }, 200);
    };
    let drag = d3
      .drag()
      .on("start", dragstarted)
      .on("drag", dragged)
      .on("end", dragended);
    let grid_groups = that.e_grids
      .enter()
      .append("g")
      .attr("class", "grid")
      .attr("id", (d) => "grid-id-" + d.img_id + "-" + d.id)
      .attr("transform", (d) => "translate(" + d.x + ", " + d.y + ")")
      .call(drag)
      .on("mouseover", function() {
        d3.select(this)
          .select("rect")
          .style("stroke-width", 2.0);
      })
      .on("mouseout", function() {
        d3.select(this)
          .select("rect")
          .style("stroke-width", 0.0);
      })
      .on("click", (_, d) => {
        console.log("click image", d);
        // that.set_focus_image(d);
        that.parent.fetch_single_image_detection_for_focus_text({
          image_id: d.img_id,
        });
      });
    // TOOD: mouseover, mouseout
    grid_groups
      .style("opacity", 0)
      .transition()
      .duration(that.create_ani)
      .delay(that.update_ani + that.remove_ani)
      .style("opacity", 1);

    grid_groups
      .append("rect")
      .attr("class", "boundingbox")
      .attr("x", 0 + grid_margin)
      .attr("y", 0 + grid_margin)
      .attr("width", (d) => d.width - 2 * grid_margin)
      .attr("height", (d) => d.width - 2 * grid_margin)
      .style("fill", (d) => (d.mismatch > 0 ? mismatch_color : match_color))
      .style("stroke", "black")
      .style("stroke-width", 0)
      .style("stroke-opacity", 1);

    grid_groups
      .append("image")
      .attr("class", "display")
      .attr("x", 0.5 * that.boundingbox_width + grid_margin)
      .attr("y", 0.5 * that.boundingbox_width + grid_margin)
      .attr("width", (d) => d.width - that.boundingbox_width - 2 * grid_margin)
      .attr("height", (d) => d.width - that.boundingbox_width - 2 * grid_margin)
      .attr("xlink:href", (d) =>
        that.use_label_layout
          ? null
          : that.server_url + `/image/image?filename=${d.img_id}.jpg`
      )
      .style("pointer-events", "none");

    let box_groups = grid_groups.selectAll("rect.box").data((d) => {
      let dets = d.d;
      let res = [];
      for (let i = 0; i < dets.length; i++) {
        let tmp = dets[i].slice(0, 4);
        tmp.width = d.width;
        res.push(tmp);
      }
      return res;
    });
    box_groups
      .enter()
      .append("rect")
      .attr("class", "box")
      .attr(
        "x",
        (d) =>
          0.5 * that.boundingbox_width +
          (d.width - that.boundingbox_width) * d[0]
      )
      .attr(
        "y",
        (d) =>
          0.5 * that.boundingbox_width +
          (d.width - that.boundingbox_width) * d[1]
      )
      .attr("width", (d) => (d.width - that.boundingbox_width) * (d[2] - d[0]))
      .attr("height", (d) => (d.width - that.boundingbox_width) * (d[3] - d[1]))
      .style("fill", "none")
      .style("stroke", Global.BoxRed)
      .style("stroke-width", 1)
      .style(
        "opacity",
        !that.use_label_layout && that.get_expand_set_id() > -1 ? 1 : 0
      )
      .style(
        "pointer-events",
        !that.use_label_layout && that.get_expand_set_id() > -1 ? 1 : "none"
      );
  };

  this.label_create = function() {
    let label_group = that.e_labels
      .enter()
      .append("g")
      .attr("class", "label")
      .attr("id", (d) => "label-id-" + d.img_id);
    label_group
      .style("opacity", 0)
      .transition()
      .duration(that.create_ani)
      .delay(that.update_ani + that.remove_ani)
      .style("opacity", 1);
    // label_group
    //       .append("rect")
    //       .attr("x", d => d.grid.x + offset_x + 1)
    //       .attr("y", d => d.grid.y + offset_y + 1)
    //       .attr("width", d => d.grid.w - 2)
    //       .attr("height", d => d.grid.h - 2)
    //       .style("fill", "black")
    //       .style("stroke", "none")
    //       .style("opacity", 0.25);
    label_group
      .append("rect")
      .attr("x", (d) => d.grid.x + offset_x)
      .attr("y", (d) => d.grid.y + offset_y)
      .attr("width", (d) => d.grid.w)
      .attr("height", (d) => d.grid.h)
      .style("fill", "none")
      .style("stroke", "#fdfe6c")
      .style("stroke-width", 4);
    label_group
      .append("image")
      .attr("x", (d) => d.label.x + offset_x)
      .attr("y", (d) => d.label.y + offset_y)
      .attr("width", (d) => d.label.w)
      .attr("height", (d) => d.label.h)
      .attr(
        "xlink:href",
        (d) => that.server_url + `/image/image?filename=${d.img_id}.jpg`
      )
      .on("click", (_, d) => {
        console.log("click image", d);
        // that.set_focus_image(d);
        that.parent.fetch_single_image_detection_for_focus_text({
          image_id: d.img_id,
        });
      });

    let box_groups = label_group.selectAll("rect.box").data((d) => {
      let dets = d.d;
      let res = [];
      for (let i = 0; i < dets.length; i++) {
        let x = d.label.x + offset_x + d.label.w * dets[i][0];
        // let width = d.grid.w * (dets[i][2] - dets[i][0]);
        let width = d.label.w * (dets[i][2] - dets[i][0]);
        let y = d.label.y + offset_y + d.label.h * dets[i][1];
        let height = d.label.h * (dets[i][3] - dets[i][1]);
        res.push({ x, y, width, height });
      }
      return res;
    });

    box_groups
      .enter()
      .append("rect")
      .attr("class", "box")
      .attr("x", (d) => d.x)
      .attr("y", (d) => d.y)
      .attr("width", (d) => d.width)
      .attr("height", (d) => d.height)
      .style("fill", "none")
      .style("stroke", Global.BoxRed)
      .style("stroke-width", 1)
      .style("opacity", that.get_expand_set_id() > -1 ? 1 : 0)
      .style("pointer-events", that.get_expand_set_id() > -1 ? 1 : "none");
  };

  this.nav_create = function() {
    let nav_group = that.e_navs
      .enter()
      .append("circle")
      .attr("class", "nav-circle")
      .attr("cx", (d) => d.x)
      .attr("cy", (d) => d.y)
      .attr("r", 10)
      .style("fill", (d) => (d.last_one ? "white" : that.nav_gray))
      .style("stroke", (d) => (d.last_one ? that.nav_gray : "white"))
      .style("stroke-width", (d) => (d.last_one ? 3 : 0))
      .on("click", (_, d) => {
        if (d.id === that.click_ids.slice(-1)[0].id) return;
        let i = 0;
        for (; i < that.click_ids.length; i++) {
          if (d.id === that.click_ids[i].id) break;
        }
        that.click_ids = that.click_ids.slice(0, i);
        that.set_grid_layout_data(d);
      });
    nav_group
      .style("opacity", 0)
      .transition()
      .duration(that.create_ani)
      .delay(that.update_ani + that.remove_ani)
      .style("opacity", 1);
  };

  this.update = function() {
    that.set_update();
    that.grid_update();
    that.label_update();
    that.nav_update();
  };

  this.set_update = function() {
    that.e_sets
      .transition()
      .duration(that.update_ani)
      .delay(that.remove_ani)
      .attr("transform", (d) => "translate(" + d.x + ", " + d.y + ")");

    that.set_group
      .selectAll(".set")
      .select("rect.background")
      .transition()
      .duration(that.update_ani)
      .delay(that.remove_ani)
      .attr("height", (d) => d.height);

    that.e_sets
      .selectAll("g.detection-result")
      .selectAll(".image-shadow")
      .transition()
      .duration(that.update_ani)
      .delay(that.remove_ani)
      // .attr("height", d => that.get_expand_set_id() === -1 ? d.vis_h : 0);
      .attr("width", (d) => d.vis_w)
      .attr("height", (d) => d.vis_h)
      .style("opacity", (d) => (that.visible(d) ? 1 : 0))
      .style("pointer-events", "none");

    that.set_group
      .selectAll(".set")
      .selectAll("g.detection-result")
      .transition()
      .duration(that.update_ani)
      .delay(that.remove_ani)
      .attr(
        "transform",
        (d) => "translate(" + d.x + ", " + that.top_image_margin + ")"
      );

    that.set_group
      .selectAll(".set")
      .selectAll("g.detection-result")
      .selectAll("image")
      .attr(
        "href",
        (d) => that.server_url + `/image/image?filename=${d.idx}.jpg`
      )
      .transition()
      .duration(that.update_ani)
      .delay(that.remove_ani)
      .attr("width", (d) => d.vis_w)
      .attr("height", (d) => d.vis_h)
      // .attr("height", d => that.get_expand_set_id() === -1 ? d.vis_h : 0);
      .style("opacity", (d) => (that.visible(d) ? 1 : 0))
      .style("pointer-events", (d) => (that.visible(d) ? "auto" : "none"));
    // .style("pointer-events", "auto");

    // that.set_group
    //   .selectAll(".set")
    //   .selectAll("g.detection-result")
    //   .selectAll("rect.box")
    //   .transition()
    //   .duration(that.update_ani)
    //   .delay(that.remove_ani)
    //   .style("opacity", (d) => 
    //   (that.visible(d) ? 1 : 0))
    //   .style("pointer-events", (d) => (that.visible(d) ? "auto" : "none"));

      that.set_group
      .selectAll(".set")
      .selectAll(".collapse-path")
      .transition()
      .duration(that.update_ani)
      .delay(that.remove_ani)
      .attr("d", d => d.collapse ? Global.collapse_icon(11, d.height / 2, 0) 
      : Global.collapse_icon(-11, d.height / 2 - 10, 1))
      .style("opacity", that.get_expand_set_id() == -1 ? 1 : 0)
      .style("pointer-events", that.get_expand_set_id() == -1 ? "auto" : "none");

    that.e_sets
      .select(".expand-path")
      .transition()
      .duration(that.update_ani)
      .delay(that.remove_ani)
      .attr("d", (d) =>
        d.id === that.get_expand_set_id()
          ? Global.minus_path_d(-11, 0, 10, 10, 2)
          : Global.plus_path_d(-11, 0, 10, 10, 2)
      )
      .style("opacity", (d) =>
        that.visible(d) || that.get_expand_set_id() === d.id
          ? 1
          : 0
      );

    that.e_sets
      .select(".expand-rect")
      .transition()
      .duration(that.update_ani)
      .delay(that.remove_ani)
      .style("opacity", (d) =>
        that.visible(d) || that.get_expand_set_id() === d.id
          ? 1
          : 0
      );
  };

  this.grid_update = function() {
    that.e_grids
      .transition()
      .duration(that.update_ani)
      .delay(that.remove_ani)
      .attr("transform", (d) => "translate(" + d.x + ", " + d.y + ")");

    that.e_grids
      .select(".boundingbox")
      .transition()
      .duration(that.update_ani)
      .delay(that.remove_ani)
      .attr("x", 0 + grid_margin)
      .attr("y", 0 + grid_margin)
      .attr("width", (d) => d.width - 2 * grid_margin)
      .attr("height", (d) => d.width - 2 * grid_margin)
      .style("fill", (d) => (d.mismatch > 0 ? mismatch_color : match_color));

    that.e_grids
      .select(".display")
      .transition()
      .duration(that.update_ani)
      .delay(that.remove_ani)
      .attr("x", 0.5 * that.boundingbox_width + grid_margin)
      .attr("y", 0.5 * that.boundingbox_width + grid_margin)
      .attr("width", (d) => d.width - that.boundingbox_width - 2 * grid_margin)
      .attr("height", (d) => d.width - that.boundingbox_width - 2 * grid_margin)
      .attr("xlink:href", (d) =>
        that.use_label_layout
          ? null
          : that.server_url + `/image/image?filename=${d.img_id}.jpg`
      );

    that.e_grids
      .selectAll("rect.box")
      .data((d) => {
        let dets = d.d;
        let res = [];
        for (let i = 0; i < dets.length; i++) {
          let tmp = dets[i].slice(0, 4);
          tmp.width = d.width;
          res.push(tmp);
        }
        return res;
      })
      .transition()
      .duration(that.update_ani)
      .delay(that.remove_ani)
      .attr(
        "x",
        (d) =>
          0.5 * that.boundingbox_width +
          (d.width - that.boundingbox_width) * d[0]
      )
      .attr(
        "y",
        (d) =>
          0.5 * that.boundingbox_width +
          (d.width - that.boundingbox_width) * d[1]
      )
      .attr("width", (d) => (d.width - that.boundingbox_width) * (d[2] - d[0]))
      .attr("height", (d) => (d.width - that.boundingbox_width) * (d[3] - d[1]))
      .style(
        "opacity",
        !that.use_label_layout && that.get_expand_set_id() > -1 ? 1 : 0
      )
      .style(
        "pointer-events",
        !that.use_label_layout && that.get_expand_set_id() > -1 ? 1 : "none"
      );
  };

  this.label_update = function() {
    that.e_labels
      .select("rect")
      .transition()
      .duration(that.update_ani)
      .delay(that.remove_ani)
      .attr("x", (d) => d.grid.x + offset_x)
      .attr("y", (d) => d.grid.y + offset_y)
      .attr("width", (d) => d.grid.w)
      .attr("height", (d) => d.grid.h);
    that.e_labels
      .select("image")
      .transition()
      .duration(that.update_ani)
      .delay(that.remove_ani)
      .attr("x", (d) => d.label.x + offset_x)
      .attr("y", (d) => d.label.y + offset_y)
      .attr("width", (d) => d.label.w)
      .attr("height", (d) => d.label.h)
      .attr(
        "xlink:href",
        (d) => that.server_url + `/image/image?filename=${d.img_id}.jpg`
      );
    // that.e_labels
    //     .selectAll("rect.box")
    //     .transition()
    //     .duration(that.update_ani)
    //     .delay(that.remove_ani)
    //     .style("opacity", that.get_expand_set_id() > -1 ? 1 : 0)
    //     .style("pointer-events", that.get_expand_set_id() > -1 ? 1 : "none");
  };

  this.nav_update = function() {
    that.e_navs
      .transition()
      .duration(that.update_ani)
      .delay(that.remove_ani)
      .attr("cx", (d) => d.x)
      .attr("cy", (d) => d.y)
      .style("fill", (d) => (d.last_one ? "white" : that.nav_gray))
      .style("stroke", (d) => (d.last_one ? that.nav_gray : "white"))
      .style("stroke-width", (d) => (d.last_one ? 3 : 0));
    that.nav_group
      .select("rect")
      .transition()
      .duration(that.update_ani)
      .delay(that.remove_ani)
      .attr(
        "height",
        that.click_ids.length === 0
          ? 0
          : that.nav_margin * (that.click_ids.length - 1)
      );
  };

  this.remove = function() {
    that.set_remove();
    that.grid_remove();
    that.label_remove();
    that.nav_remove();
  };

  this.set_remove = function() {
    that.e_sets
      .exit()
      .transition()
      .duration(that.remove_ani)
      .style("opacity", 0)
      .remove();
  };

  that.grid_remove = function() {
    that.e_grids
      .exit()
      .transition()
      .duration(that.remove_ani)
      .style("opacity", 0)
      .remove();
  };

  this.label_remove = function() {
    that.e_labels
      .exit()
      .transition()
      .duration(that.remove_ani)
      .style("opacity", 0)
      .remove();
  };

  this.nav_remove = function() {
    that.e_navs
      .exit()
      .transition()
      .duration(that.remove_ani)
      .style("opacity", 0)
      .remove();
  };

  that.set_mode = function(mode) {
    console.log("set mode", mode);
    that.mode = mode;
    if (mode === "cropping") {
      d3.select("#cropping")
        .select("path")
        .attr("d", Global.d_rollback);
      d3.select("#selecting")
        .select("path")
        .attr("d", Global.d_select);
      that.enter_overview();
    } else if (mode === "selecting") {
      d3.select("#selecting")
        .select("path")
        .attr("d", Global.d_rollback);
      d3.select("#cropping")
        .select("path")
        .attr("d", Global.d_scan);
      that.enter_overview();
    } else if (mode === "exploring") {
      d3.select("#cropping")
        .select("path")
        .attr("d", Global.d_scan);
      d3.select("#selecting")
        .select("path")
        .attr("d", Global.d_select);
      that.quit_overview();
    }
  };

  that.get_mode = function() {
    return that.mode;
  };

  that.enter_overview = function() {
    that.overview_group
      .select("#overview-1")
      .attr("x", offset_x)
      .attr("y", offset_y)
      .attr("width", plot_width)
      .attr("height", plot_height);
    // that.overview_group.select("#overview-2")
    //     .attr("x", small_x_2)
    //     .attr("y", small_y_2)
    //     .attr("width", small_grid_width)
    //     .attr("height", small_grid_width);
    that.overview_group.style("visibility", "visible");
    that.overview_group.select("#viewbox").style("visibility", "hidden");
    that.confirm_button.style("visibility", "hidden");
  };

  that.quit_overview = function() {
    that.overview_group.style("visibility", "hidden");
    that.overview_group.select("#viewbox").style("visibility", "hidden");
    that.confirm_button
      .select("#confirm-resample")
      .style("visibility", "hidden");
  };

  that._init = function() {
    that.overview_group = that.parent.svg
      .append("g")
      .attr("id", "overview-group");

    that.overview_group
      .attr("transform", "translate(" + 0 + "," + that.text_height + ")")
      .style("visibility", "hidden");
    that.overview_group
      .append("rect")
      .attr("id", "overview-1")
      .attr("class", "overview-box");
    // that.overview_group.append("rect")
    //     .attr("id", "overview-2")
    //     .attr("class", "overview-box");
    that.overview_group
      .selectAll(".overview-box")
      .attr("x", 0)
      .attr("y", 0)
      .style("fill", "white")
      .style("stroke", "grey")
      .style("stroke-width", 5)
      .style("opacity", 0.3);
    that.overview_group
      .append("rect")
      .attr("id", "viewbox")
      .style("stroke-dasharray", "5, 5")
      .style("fill", "white")
      .style("stroke", "grey")
      .style("stroke-width", 5)
      .style("opacity", 0.5);

    d3.select("#cropping").on("click", function() {
      var mode =
        d3
          .select(this)
          .select("path")
          .attr("d") === Global.d_scan
          ? "cropping"
          : "exploring";
      that.set_mode(mode);
    });

    d3.select("#selecting").on("click", function() {
      var mode =
        d3
          .select(this)
          .select("path")
          .attr("d") === Global.d_select
          ? "selecting"
          : "exploring";
      that.set_mode(mode);
    });

    d3.select("#home").on("click", () => {
      console.log("home buttom click");
      // let d = that.click_ids[0];
      // that.click_ids = that.click_ids.slice(0, i);
      that.update_ani = 2000;
      that.create_ani = 2000;
      let d = that.click_ids[1];
      that.click_ids = that.click_ids.slice(0, 1);
      that.set_grid_layout_data(d);
    });

    function adjust_sampling_area(area) {
      that.relative_sampling_area = area;
      // console.log("relative_sampling are", that.relative_sampling_area);
      that.overview_group
        .select("#viewbox")
        .attr("x", that.relative_sampling_area.x * plot_width + offset_x)
        .attr("y", that.relative_sampling_area.y * plot_height + offset_y) //- that.text_height)
        .attr("width", that.relative_sampling_area.w * plot_width)
        .attr("height", that.relative_sampling_area.h * plot_height);
    }
    function compute_viewbox(x1, y1, x2, y2) {
      var min_x = Math.min(x1, x2),
        max_x = Math.max(x1, x2),
        min_y = Math.min(y1, y2),
        max_y = Math.max(y1, y2);
      var new_area = {
        x: (min_x - plot_x) / plot_width,
        y: (min_y - plot_y) / plot_height,
        w: (max_x - min_x) / plot_width,
        h: (max_y - min_y) / plot_height,
      };
      if (new_area.x + new_area.w > 1 && new_area.x < 1) {
        return that.relative_sampling_area;
      } else {
        return new_area;
      }
    }

    that.overview_group
      .on("mousedown", function(ev) {
        // var offset = $(d3.select(this).node()).offset();
        plot_x = offset_x;
        plot_y = offset_y + that.text_height;
        mouse_pos = {
          x: ev.offsetX,
          y: ev.offsetY,
        };
        console.log(plot_x, plot_y, mouse_pos);
        mouse_pressed = d3.select(this).attr("id");
        that.overview_group.select("#viewbox").style("visibility", "visible");
        that.confirm_button.style("visibility", "hidden");
        adjust_sampling_area(
          compute_viewbox(mouse_pos.x, mouse_pos.y, mouse_pos.x, mouse_pos.y)
        );
      })
      .on("mousemove", function(ev) {
        if (!mouse_pressed) {
          return;
        }

        adjust_sampling_area(
          compute_viewbox(mouse_pos.x, mouse_pos.y, ev.offsetX, ev.offsetY)
        );

        let left_x = that.relative_sampling_area.x;
        let top_y = that.relative_sampling_area.y;
        let right_x = left_x + that.relative_sampling_area.w;
        let bottom_y = top_y + that.relative_sampling_area.h;
        // console.log("relative sampling area", left_x, right_x, top_y, bottom_y);
        if (that.get_mode() !== "exploring") {
          let grid_data = that.get_grid_data();
          grid_data.forEach((d) => {
            let x = d.pos[0];
            let y = d.pos[1];
            let width = d.normed_w;
            if (
              x + width > left_x &&
              x < right_x &&
              y + width > top_y &&
              y < bottom_y
            ) {
              if (d.selected === false) {
                d.selected = true;
                d3.select("#grid-id-" + d.img_id)
                  .select(".boundingbox")
                  .style(
                    "fill",
                    d.mismatch > 0
                      ? d3.rgb(Global.Orange).darker(1.5)
                      : d3.rgb(Global.GrayColor).darker(1.5)
                  );
                d3.select("#grid-id-" + d.img_id)
                  .select(".display")
                  .style(
                    "fill",
                    d.mismatch > 0
                      ? d3.rgb(Global.Orange).darker(1.5)
                      : d3.rgb(Global.GrayColor).darker(1.5)
                  );
              }
            } else {
              if (d.selected === true) {
                d.selected = false;
                d3.select("#grid-id-" + d.img_id)
                  .select(".boundingbox")
                  .style(
                    "fill",
                    d.mismatch > 0 ? Global.Orange : Global.GrayColor
                  );
                d3.select("#grid-id-" + d.img_id)
                  .select(".display")
                  .style(
                    "fill",
                    d.mismatch > 0 ? Global.Orange : Global.GrayColor
                  );
              }
            }
          });
        }
      })
      .on("mouseup", function(ev) {
        if (!mouse_pressed) {
          return;
        }
        mouse_pressed = false;
        adjust_sampling_area(
          compute_viewbox(mouse_pos.x, mouse_pos.y, ev.offsetX, ev.offsetY)
        );
        let button_x =
          (that.relative_sampling_area.x + that.relative_sampling_area.w) *
            plot_width +
          margin_size +
          offset_x -
          10;
        let button_y =
          (that.relative_sampling_area.y + that.relative_sampling_area.h) *
            plot_height +
          // margin_top_size +
          that.text_height +
          offset_y;
        that.confirm_button
          .attr("transform", "translate(" + button_x + ", " + button_y + ")")
          .style("visibility", "visible");
      });

    that.confirm_button = that.parent.svg
      .append("g")
      .attr("id", "confirm-resample")
      .style("visibility", "hidden");
    that.confirm_button
      .append("circle")
      .attr("r", 20)
      .attr("fill", "grey");
    that.confirm_button
      .append("text")
      .attr("class", "glyphicon")
      .attr("text-anchor", "middle")
      .attr("dominant-baseline", "middle")
      .attr("dy", "0.25em")
      .style("fill", "white")
      .style("opacity", 1)
      .style("font-size", "20px")
      .style("cursor", "hand")
      .text("\ue015");

    that.confirm_button.on("click", function(ev) {
      console.log("confirm buttom click");
      if (that.get_mode() === "cropping") {
        that.click_count = that.click_ids.slice(-1)[0].id;
        // that.click_ids.push(that.click_count);
        let query = {
          // "image_cluster_id": that.expand_set_id,
          cat_ids: that.parent.selected_node.node_ids,
          "left-x": that.relative_sampling_area.x,
          "top-y": that.relative_sampling_area.y,
          width: that.relative_sampling_area.w,
          height: that.relative_sampling_area.h,
          "node-id": that.click_count,
          image_cluster_id: that.get_expand_set_id(),
        };
        that.fetch_grid_layout(query);
      } else if (that.get_mode() === "selecting") {
        // let selected_items_id = [];
        // for (let i = 0; i < train_data.length; i++) {
        //     if (train_data[i].selected === true) {
        //         selected_items_id.push(train_data[i].get_id());
        //     }
        // }
        // for (let i = 0; i < test_data.length; i++) {
        //     if (test_data[i].selected === true) {
        //         selected_items_id.push(test_data[i].get_id());
        //     }
        // }
      }
      that.set_mode("exploring");
      d3.select(this).style("visibility", "hidden");
      ev.stopPropagation();
    });
  }.call();

  that.label_layout = function(data, plot_size, labels) {
    let padding_label = 40;
    if (data.length <= 25 ** 2) {
      labels = Array();
      return [];
    }
    var grid_N = Math.ceil(Math.sqrt(data.length));
    var grid_size = plot_size / grid_N;
    var img_size = img_width;
    var new_labels = [];

    var intersect = function(rect1, rect2) {
      return !(
        rect1.x + rect1.w + padding_label < rect2.x ||
        rect2.x + rect2.w + padding_label < rect1.x ||
        rect1.y + rect1.h + padding_label < rect2.y ||
        rect2.y + rect2.h + padding_label < rect1.y
      );
    };
    var legal = function(rect) {
      return (
        rect.x > 0 &&
        rect.y > 0 &&
        rect.x + rect.w < plot_size &&
        rect.y + rect.h < plot_size
      );
    };
    const offset = [
      { x: 0, y: 0 },
      { x: 0, y: -img_size },
      { x: -img_size, y: -img_size },
      { x: -img_size, y: 0 },
    ];
    data.sort((x, y) => y.mismatch - x.mismatch);
    for (let d of data) {
      var center = {
        x: d.pos[0] * plot_size + 0.5 * grid_size,
        y: d.pos[1] * plot_size + 0.5 * grid_size,
      };
      var tmp_grid = {
        x: d.pos[0] * plot_size,
        y: d.pos[1] * plot_size,
        w: d.normed_w * plot_size,
        h: d.normed_w * plot_size,
      };
      var tmp_label;
      var can_placed;
      var prev_label = labels.filter((lbl) => lbl.id === d.id);
      if (prev_label.length > 0) {
        var direction = prev_label[0].label.dir;
        tmp_label = {
          dir: direction,
          x: center.x + offset[direction].x,
          y: center.y + offset[direction].y,
          w: img_size,
          h: img_size,
        };
        can_placed = true;
        for (let r of new_labels) {
          if (
            intersect(tmp_label, r.label) ||
            intersect(tmp_grid, r.label) ||
            intersect(tmp_label, r.grid) ||
            intersect(tmp_grid, r.grid) ||
            !legal(tmp_label)
          ) {
            can_placed = false;
            break;
          }
        }
        if (can_placed) {
          new_labels.push({
            label: tmp_label,
            grid: tmp_grid,
            ...d,
          });
        }
      } else {
        for (var i = 0; i < 4; ++i) {
          tmp_label = {
            dir: i,
            x: center.x + offset[i].x,
            y: center.y + offset[i].y,
            w: img_size,
            h: img_size,
          };
          can_placed = true;
          for (let r of new_labels) {
            if (
              intersect(tmp_label, r.label) ||
              intersect(tmp_grid, r.label) ||
              intersect(tmp_label, r.grid) ||
              intersect(tmp_grid, r.grid) ||
              !legal(tmp_label)
            ) {
              can_placed = false;
              break;
            }
          }
          if (can_placed) {
            new_labels.push({
              label: tmp_label,
              grid: tmp_grid,
              ...d,
            });
            break;
          }
        }
      }
    }
    return new_labels;
  };

  that.box_highlight = function(ev) {
    let element = ev.target;
    while (element.tagName !== "g" || element.className.baseVal !== "set") {
      element = element.parentElement;
    }
    let self = d3.select(element);
    let d = self.data()[0];
    let group = that.set_group.select("#set-" + d.id);
    group
      .selectAll(".background")
      // .style("stroke", "rgb(128, 128, 128)");
      .style("stroke", "rgb(237,129,55)")
      .style("stroke-width", 2);
    group.selectAll(".expand-rect").style("stroke", "rgb(38, 38, 38)");

    if (Global.linked_highlight) that.connection_highlight(d);
  };

  that.box_dehighlight = function() {
    let groups = that.set_group.selectAll(".set");
    groups
      .selectAll(".background")
      .style("stroke", "rgb(208, 208, 208)")
      .style("stroke-width", 1);
    groups.selectAll(".expand-rect").style("stroke", "gray");
    if (Global.linked_highlight) that.connection_dehighlight();
  };

  that.image_highlight = function(ev) {
    // console.log('image highlight');
    let element = ev.target;
    let self = d3.select(element.parentElement).selectAll(".image-shadow");
    self.style("stroke", "rgb(237,129,55)");
  };

  that.image_dehighlight = function() {
    that.set_group
      .selectAll(".set")
      .selectAll(".image-shadow")
      .style("stroke", "white");
    if (that.selected_image_idx !== null) {
      d3.select("#image-shadow-" + that.selected_image_idx).style(
        "stroke",
        "rgb(237,129,55)"
      );
    }
  };

  that.connection_highlight = function(node) {
    that.parent.connection_view.highlight_by_image_cluster(node);
  };

  that.connection_dehighlight = function() {
    that.parent.connection_view.dehighlight();
  };

  that.clean = function() {
    that.set_group.selectAll(".set").remove();
  };
};

export default ImageCards;
back to top