Example #1
0
      this._wrapLines = function() {

        lines++;

        if (lines > maxLines) return;

        const wrappable = lines === 1 ? this._lineData.slice()
          : this._lineData.filter(d => d.width + d.shapeWidth + this._padding * (d.width ? 2 : 1) > availableWidth && d.words.length >= lines)
              .sort((a, b) => b.sentence.length - a.sentence.length);

        if (wrappable.length && availableHeight > wrappable[0].height * lines) {

          let truncated = false;
          for (let x = 0; x < wrappable.length; x++) {
            const label = wrappable[x];
            const h = label.og.height * lines, w = label.og.width * (1.5 * (1 / lines));
            const res = textWrap().fontFamily(label.f).fontSize(label.s).lineHeight(label.lh).width(w).height(h)(label.sentence);
            if (!res.truncated) {
              label.width = Math.ceil(max(res.lines.map(t => textWidth(t, {"font-family": label.f, "font-size": label.s})))) + label.s;
              label.height = res.lines.length * (label.lh + 1);
            }
            else {
              truncated = true;
              break;
            }
          }
          if (!truncated) this._wrapRows();
        }
        else {
          newRows = [];
          return;
        }

      };
Example #2
0
    this._lineData = this._data.map((d, i) => {

      const label = this._label(d, i);

      let res = {
        data: d,
        i,
        id: this._id(d, i),
        shapeWidth: this._fetchConfig("width", d, i),
        shapeHeight: this._fetchConfig("height", d, i),
        y: 0
      };

      if (!label) {
        res.sentence = false;
        res.words = [];
        res.height = 0;
        res.width = 0;
        return res;
      }

      const f = this._fetchConfig("fontFamily", d, i),
            lh = this._fetchConfig("lineHeight", d, i),
            s = this._fetchConfig("fontSize", d, i);

      const h = availableHeight - (this._data.length + 1) * this._padding,
            w = this._width;

      res = Object.assign(res, textWrap()
        .fontFamily(f)
        .fontSize(s)
        .lineHeight(lh)
        .width(w)
        .height(h)
        (label));

      res.width = Math.ceil(max(res.lines.map(t => textWidth(t, {"font-family": f, "font-size": s})))) + s * 0.75;
      res.height = Math.ceil(res.lines.length * (lh + 1));
      res.og = {height: res.height, width: res.width};
      res.f = f;
      res.s = s;
      res.lh = lh;

      return res;

    });
Example #3
0
  /**
      @memberof Legend
      @desc Renders the current Legend to the page. If a *callback* is specified, it will be called once the legend is done drawing.
      @param {Function} [*callback* = undefined]
      @chainable
  */
  render(callback) {

    if (this._select === void 0) this.select(select("body").append("svg").attr("width", `${this._width}px`).attr("height", `${this._height}px`).node());

    // Shape <g> Group
    this._group = elem("g.d3plus-Legend", {parent: this._select});

    let availableHeight = this._height;
    this._titleHeight = 0;
    this._titleWidth = 0;
    if (this._title) {

      const f = this._titleConfig.fontFamily || this._titleClass.fontFamily()(),
            s = this._titleConfig.fontSize || this._titleClass.fontSize()();
      let lH = lH = this._titleConfig.lineHeight || this._titleClass.lineHeight();
      lH = lH ? lH() : s * 1.4;

      const res = textWrap()
        .fontFamily(f)
        .fontSize(s)
        .lineHeight(lH)
        .width(this._width)
        .height(this._height)
        (this._title);
      this._titleHeight = lH + res.lines.length + this._padding;
      this._titleWidth = max(res.widths);
      availableHeight -= this._titleHeight;
    }

    // Calculate Text Sizes
    this._lineData = this._data.map((d, i) => {

      const label = this._label(d, i);

      let res = {
        data: d,
        i,
        id: this._id(d, i),
        shapeWidth: this._fetchConfig("width", d, i),
        shapeHeight: this._fetchConfig("height", d, i),
        y: 0
      };

      if (!label) {
        res.sentence = false;
        res.words = [];
        res.height = 0;
        res.width = 0;
        return res;
      }

      const f = this._fetchConfig("fontFamily", d, i),
            lh = this._fetchConfig("lineHeight", d, i),
            s = this._fetchConfig("fontSize", d, i);

      const h = availableHeight - (this._data.length + 1) * this._padding,
            w = this._width;

      res = Object.assign(res, textWrap()
        .fontFamily(f)
        .fontSize(s)
        .lineHeight(lh)
        .width(w)
        .height(h)
        (label));

      res.width = Math.ceil(max(res.lines.map(t => textWidth(t, {"font-family": f, "font-size": s})))) + s * 0.75;
      res.height = Math.ceil(res.lines.length * (lh + 1));
      res.og = {height: res.height, width: res.width};
      res.f = f;
      res.s = s;
      res.lh = lh;

      return res;

    });

    let spaceNeeded;
    const availableWidth = this._width - this._padding * 2;
    spaceNeeded = this._rowWidth(this._lineData);

    if (this._direction === "column" || spaceNeeded > availableWidth) {
      let lines = 1, newRows = [];

      const maxLines = max(this._lineData.map(d => d.words.length));
      this._wrapLines = function() {

        lines++;

        if (lines > maxLines) return;

        const wrappable = lines === 1 ? this._lineData.slice()
          : this._lineData.filter(d => d.width + d.shapeWidth + this._padding * (d.width ? 2 : 1) > availableWidth && d.words.length >= lines)
              .sort((a, b) => b.sentence.length - a.sentence.length);

        if (wrappable.length && availableHeight > wrappable[0].height * lines) {

          let truncated = false;
          for (let x = 0; x < wrappable.length; x++) {
            const label = wrappable[x];
            const h = label.og.height * lines, w = label.og.width * (1.5 * (1 / lines));
            const res = textWrap().fontFamily(label.f).fontSize(label.s).lineHeight(label.lh).width(w).height(h)(label.sentence);
            if (!res.truncated) {
              label.width = Math.ceil(max(res.lines.map(t => textWidth(t, {"font-family": label.f, "font-size": label.s})))) + label.s;
              label.height = res.lines.length * (label.lh + 1);
            }
            else {
              truncated = true;
              break;
            }
          }
          if (!truncated) this._wrapRows();
        }
        else {
          newRows = [];
          return;
        }

      };

      this._wrapRows = function() {
        newRows = [];
        let row = 1, rowWidth = 0;
        for (let i = 0; i < this._lineData.length; i++) {
          const d = this._lineData[i],
                w = d.width + this._padding * (d.width ? 2 : 1) + d.shapeWidth;
          if (sum(newRows.map(row => max(row, d => max([d.height, d.shapeHeight])))) > availableHeight) {
            newRows = [];
            break;
          }
          if (w > availableWidth) {
            newRows = [];
            this._wrapLines();
            break;
          }
          else if (rowWidth + w < availableWidth) {
            rowWidth += w;
          }
          else if (this._direction !== "column") {
            rowWidth = w;
            row++;
          }
          if (!newRows[row - 1]) newRows[row - 1] = [];
          newRows[row - 1].push(d);
          if (this._direction === "column") {
            rowWidth = 0;
            row++;
          }
        }
      };

      this._wrapRows();

      if (!newRows.length || sum(newRows, this._rowHeight.bind(this)) + this._padding > availableHeight) {
        spaceNeeded = sum(this._lineData.map(d => d.shapeWidth + this._padding)) - this._padding;
        for (let i = 0; i < this._lineData.length; i++) {
          this._lineData[i].width = 0;
          this._lineData[i].height = 0;
        }
        this._wrapRows();
      }

      if (newRows.length && sum(newRows, this._rowHeight.bind(this)) + this._padding < availableHeight) {
        newRows.forEach((row, i) => {
          row.forEach(d => {
            if (i) {
              d.y = sum(newRows.slice(0, i), this._rowHeight.bind(this));
            }
          });
        });
        spaceNeeded = max(newRows, this._rowWidth.bind(this));
      }
    }

    const innerHeight = max(this._lineData, (d, i) => max([d.height, this._fetchConfig("height", d.data, i)]) + d.y) + this._titleHeight,
          innerWidth = max([spaceNeeded, this._titleWidth]);

    this._outerBounds.width = innerWidth;
    this._outerBounds.height = innerHeight;

    let xOffset = this._padding,
        yOffset = this._padding;
    if (this._align === "center") xOffset = (this._width - innerWidth) / 2;
    else if (this._align === "right") xOffset = this._width - this._padding - innerWidth;
    if (this._verticalAlign === "middle") yOffset = (this._height - innerHeight) / 2;
    else if (this._verticalAlign === "bottom") yOffset = this._height - this._padding - innerHeight;
    this._outerBounds.x = xOffset;
    this._outerBounds.y = yOffset;

    this._titleClass
      .data(this._title ? [{text: this._title}] : [])
      .duration(this._duration)
      .select(this._group.node())
      .textAnchor({left: "start", center: "middle", right: "end"}[this._align])
      .width(this._width - this._padding * 2)
      .x(this._padding)
      .y(this._outerBounds.y)
      .config(this._titleConfig)
      .render();

    this._shapes = [];
    const baseConfig = configPrep.bind(this)(this._shapeConfig, "legend"),
          config = {
            id: d => d.id,
            label: d => d.label,
            lineHeight: d => d.lH
          };

    const data = this._data.map((d, i) => {

      const obj = {
        __d3plus__: true,
        data: d, i,
        id: this._id(d, i),
        label: this._lineData[i].width ? this._label(d, i) : false,
        lH: this._fetchConfig("lineHeight", d, i),
        shape: this._shape(d, i)
      };

      return obj;

    });

    // Legend Shapes
    this._shapes = [];
    ["Circle", "Rect"].forEach(Shape => {

      this._shapes.push(new shapes[Shape]()
        .data(data.filter(d => d.shape === Shape))
        .duration(this._duration)
        .labelConfig({padding: 0})
        .select(this._group.node())
        .verticalAlign("top")
        .config(assign({}, baseConfig, config))
        .render());

    });

    if (callback) setTimeout(callback, this._duration + 100);

    return this;

  }