Home Reference Source Repository

src/es6/regiontree.es6

import {d3, uuid} from "nbpresent-deps";

import {Toolbar} from "./toolbar";
import {MiniSlide} from "./mini";

class RegionTree {
  constructor(slide, region){
    this.slide = slide;
    this.selectedRegion = region;

    this.mini = (new MiniSlide(this.selectedRegion))
      .regions((d) => {
        var obj = {};
        obj[d.region.id] = d.region;
        return obj;
      });

    this.initUI();
    this.update();

    this.update = this.update.bind(this);

    this.slide.on("update", this.update);
    this.selectedRegion.on("update", this.update);
  }

  destroy() {
    this.$ui.transition()
      .style({opacity: 0})
      .remove();
    this.killed = true;
  }

  width() {
    return 300;
  }

  layout(layout){
    this.slide.set("layout", layout);
  }

  initUI(){
    this.$ui = d3.select("body")
      .append("div")
      .classed({nbpresent_regiontree: 1});

    let toolbar = new Toolbar();

    this.$toolbar = this.$ui.append("div")
      .datum([
        [{
          icon: "plus-square-o",
          click: () => this.addRegion(),
          tip: "Add Region"
        }],
        // TODO: make this extensible
        [{
          icon: "arrows",
          click: () => this.layout("manual"),
          tip: "Manual Layout"
        },{
          icon: "tree",
          click: () => this.layout("treemap"),
          tip: "Treemap Layout"
        }],
        // TODO: fix this
        // [{
        //   icon: "header",
        //   click: () => this.toggleStyle("slab"),
        //   tip: "Toggle Slab Effect"
        // }],
      ])
      .call(toolbar.update);
  }

  toggleStyle(style){
    let {slide, region} = this.selectedRegion.get() || {},
      path = ["regions", region, "style", style];
    this.slide.set(path, !this.slide.get(path));
  }

  addRegion(){
    let id = uuid.v4();
    this.slide.set(["regions", id], {
      id,
      attrs: {
        x: 0.1,
        y: 0.1,
        width: 0.8,
        height: 0.8
      }
    });
  }

  update(){
    if(this.killed){
      return;
    }

    let that = this;

    let regions = d3.entries(this.slide.get("regions")),
      $region = this.$ui.selectAll(".region_info")
        .data(regions, (d) => d.key),
      slide = this.slide.get();

    $region
      .enter()
      .append("div")
      .classed({region_info: 1});

    let $mini = $region
      .selectAll(".slide")
      .data((d) => [{value: slide, key: slide.id, region: d.value}]);

    $mini.enter()
      .append("div")
      .classed({slide: 1});

    $mini.exit().transition()
      .style({opacity: 0})
      .remove();

    $mini.call(this.mini.update);

    let layout = this.slide.get(["layout"]);

    let $attr = $region.selectAll(".region_attr")
      .data((region) =>
        d3.entries(region.value.attrs)
          .map((attr) => { return {region, attr}; })
          .filter((d)=>{
            if(layout == "treemap"){
              return d.attr.key.indexOf(layout) === 0;
            }else{
              return d.attr.key.indexOf("treemap") === -1;
            }
          })
        )


    $attr.exit().remove();

    $attr.enter()
      .append("div")
      .classed({
        region_attr: 1,
        "input-group": 1,
        "input-group-sm": 1
      })
      .call(function($attr){
        $attr.append("span").classed({"input-group-btn": 1})
          .append("button").classed({
            btn: 1,
            "btn-default": 1,
            "btn-xs": 1,
            attr_name: 1
          });

        $attr.append("span").classed({"input-group-addon": 1, attr_ns: 1})
          .append("i")
          .classed({fa: 1, "fa-fw": 1});

        $attr.append("input").classed({"form-control": 1})
          .attr({type: "text"})
          .each(function(){
            Jupyter.keyboard_manager.register_events(this);
          })
          .on("change", function(d){
            let el = d3.select(this),
              val = parseFloat(el.property("value"));
            that.slide.set(["regions", d.region.key, d.attr.key], val);
          });
      })
      .on("mousedown", function(d){
        that.makeSlider(this, d);
      });


    $attr.select(".attr_name").text((d) => {
      return d.attr.key.indexOf(":") === -1 ?
        d.attr.key :
        d.attr.key.split(":")[1];
    });
    $attr.select(".attr_ns").each(function(d){
      let hasIcon = d.attr.key.indexOf(":") !== -1;
      let el = d3.select(this)
        .style({
          display: hasIcon ? null : "none"
        });

      if(!hasIcon){
        return;
      }

      // TODO: put this in layout
      let icon = {
        treemap: "tree"
      }[d.attr.key.split(":")[0]];

      el.select(".fa")
        .classed(`fa-${icon}`, 1);

    })
    $attr.select(".form-control").attr({
      value: (d) => {
        if(typeof d.attr.value === "boolean"){
          return d.attr.value;
        }else if(typeof d.attr.value === "number"){
          return d.attr.value.toFixed(3);
        }
      }
    });
  }

  makeSlider(element, d) {
    let that = this;
    let [x, y] = d3.mouse(element);
    let el = d3.select(element);
    el.on("mousemove", function(d){
      let [x1, y1] = d3.mouse(this);
      let dx = (x1 - x) / 20;
      x = x1;
      let path = ["regions", d.region.key, "attrs", d.attr.key];
      that.slide.set(path, that.slide.get(path) + dx)
    })
    .on("mouseup", function(){
      el.on("mousemove", null);
    })
    .on("mouseexit", function(){
      el.on("mousemove", null);
    });
  }

}

export {RegionTree};