API Docs for: 0.0.1
Show:

File: lib/graphs/visuals.js

/*
 * niViz -- snow profiles visualization
 * Copyright (C) 2015 WSL/SLF - Fluelastrasse 11 - 7260 Davos Dorf - Switzerland.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

(function (niviz) {
  'use strict';

  // --- Module Dependencies ---
  var Common  = niviz.Common;
  var t       = niviz.Translate.gettext;

  /** @module niviz */

  /**
   * The static Visuals class encompasses a few static methods to draw common
   * visual elements such as buttons or arrows. No instantiation or
   * initialization is necessary.
   *
   * @class Visuals
   * @static
   */
  var Visuals = {};

  /**
   * Create a grey button with a text on top, height 25px, width 80px
   *
   * @static
   * @method Button
   * @param {Object} paper SnapSVG paper object
   * @param {Number} x Top left corner x-coordinate
   * @param {Number} y Top left corner y-coordinate
   * @param {String} text Text to be show on button
   * @param {Object} self Context for callback function
   * @param {Function} callback Callback function executed on click
   * @return {Object} svg group container which holds all the visual elements
   */
  Visuals.Button = function (paper, x, y, text, self, callback) {
    var set = paper.g();

    var font = {
      fontSize: '11px',
      fontFamily: 'Helvetica, Arial',
      fill: '#707070',
      textAnchor: 'middle'
    }, grad1 = 'l(1,1,1,0)#ccc-#eee', grad2 = 'l(1,1,1,0)#ddd-#eee';

    var btn = paper.rect(x, y, 80, 25).attr({
      fill: grad1, opacity: 0.9, strokeWidth: 0.5, stroke: '#aaa'
    });

    var bounds = btn.getBBox();
    var label = paper.text(bounds.x + (bounds.x2 - bounds.x) / 2,
                           bounds.y2 - 9, text).attr(font);

    set.add(btn);
    set.add(label);

    set
      .hover(
        function () {
          btn.attr({'fill': grad1, 'opacity': 0.9});
          label.attr({'cursor': 'default'});
        },
        function () {
          btn.attr({'fill': grad2, 'opacity': 0.9});
        }
      )
      .mousedown(function () {
        btn.attr({'fill': grad2, 'opacity': 0.9});
      })
      .mouseup(function () {
        btn.attr({'fill': grad1, 'opacity': 0.9});
      })
      .click(function() {
        btn.attr({'fill': grad1, 'opacity': 0.9});
        callback.call(self);
      });

    return set;
  };

  /*
   * Arrows used for indicating instabilites
   */
  var arrow = function (paper, p, left, right) {
    var center = right - (right - left) / 2, path,
      font = p.font || {}, fontsize = p.fontsize || 11;

    return function (y, top, bottom, popup, fs) {
      var set = paper.g();
      y = Math.round(y);
      path = paper.line(right, y, left, y);
      set.add(paper.polygon(left, y, left + 10, y - 5, left + 10, y + 5, left, y));
      set.add(path.attr({ stroke: '#000', strokeWidth: 2.5 }));

      if (top) set.add(paper.text(center, y - 3, top).attr(font));
      if (bottom) {
        if (!fs) fs = fontsize;
        var b = paper.multitext(center, y + fs, bottom).attr(font).attr({ 'font-size': fs});
        b.transform('t0,' + (y - b.getBBox().y + 2));
        set.add(b);
      }

      set.attr('cursor', 'default');
      if (popup) {
        set.attr('cursor', 'help');
        var lbl, pop;
        set.hover( function () {
          lbl = paper.text(0, 0, popup).attr(font);
          pop = paper.popup(center, bottom ? y + fontsize + 2 : y + 3, lbl, 'bottom', 10)
            .attr({
              fill: '#fff', stroke: '#333', 'stroke-width': 1, 'fill-opacity': .9
            });
        }, function () {
          lbl.remove();
          pop.remove();
        });
      }

      return set;
    };
  };

  /**
   * Pass sk38, rb, ect or ct profile feature and draw black pointed arrows
   * at the respective heights.
   * Note: Only the first profile of each test is considered!
   *
   * @static
   * @method Arrows
   * @param {Object} ctx 'this' context to be utilized
   * @param {Feature} data A feature such as rb, ect, ct or sk38 of a profile
   * @param {Number} left left x-coordinate (where the arrow points)
   * @param {Number} right right x-coordinate
   * @param {Number} min Minimal height for arrow to be drawn
   * @param {Number} max Maximal height for arrow to be drawn
   * @param {Function} coord Function to be map height to actual pixel value
   * @return {Object} svg group container which holds all the visual elements
   */
  Visuals.Arrows = function (ctx, data, left, right, min, max, coord) {
    var current, paper = ctx.paper, set = paper.g(), tops = [],
      p = ctx.properties, draw = arrow(paper, p, left, right), fs;

    if (!data || !data.layers) return undefined;

    data.layers.forEach(function (layer) {
      top = layer.top;

      if (layer.nofailure) top = ctx.inverted ? 0 : ctx.profile.top;

      if (top > max || top < min || tops.indexOf(top) !== -1) return;
      tops.push(top);
      current = coord(top);

      var top, bottom, popup;
      if (data.type === 'sk38') {
        top = layer.text !== '-1' ? 'cl: ' + layer.text : undefined;
        bottom = 'SK38=' + layer.sk38;
      } else if (data.type === 'ct' || data.type === 'saw' || data.type === 'sf') {
        top = layer.text;
        if (layer.character) top = top + ' ' + layer.character;
        if (layer.comment) popup = layer.comment;
      } else if (data.type === 'ect') {
        top = layer.text;
        if (Common.defaults.ECT_format === 'Swiss') top = layer.swisscode || layer.text;
        if (layer.comment) popup = layer.comment;
      } else if (data.type === 'rb') {
        bottom = '';
        if (layer.releasetype) bottom += t(layer.releasetype) + '\n';
        if (layer.character) bottom += t(layer.character);
        if (layer.text) top = layer.text;
        if (layer.comment) popup = layer.comment;
        fs = 10;
      }

      set.add(draw(current, top, bottom, popup, fs));
    }, ctx);

    return set;
  };

  /**
   * Try to print the text handed over in a textbox with a maximum width
   * of maxwidth
   *
   * @method format
   * @private
   * @param {Object} textbox SnapSVG text element
   * @param {String} current The current string
   * @param {String} addendum Text string to be added
   * @param {Number} maxwidth Max width of the text box, default: 250px
   * @return {String} The altered comment string
   */
  Visuals.format = function (textbox, current, addendum, maxwidth) {
    maxwidth = maxwidth || 250;
    addendum += '';

    var words = addendum.split(/[ ]/) || [], ii, last = current.lastIndexOf('\n'), index;
    if (last) last = current.substring(last);
    else last = '';

    for (ii = 0; ii < words.length; ++ii) {
      last = last + (ii === 0 ? '' : ' ') + words[ii];
      textbox.attr('text', last);

      if (textbox.getBBox().width > maxwidth) {
        current += '\n' + words[ii];
        last = '';
      } else {
        if (words[ii] === '\n') {
          current += '\u200C\n';
          last = '';
        } else if (words[ii] === '') {
          current = current;
        } else {
          current += (ii === 0 ? '' : ' ') + words[ii];

          index = words[ii].lastIndexOf('\n');
          if (index > -1) last = words[ii].substring(index);
        }
      }
    }

    return current;
  };

  /**
   * Return text with linebreaks after maxwidth chars
   *
   * @method format
   * @private
   * @param {String} text Text string to be added
   * @param {Number} maxwidth Max width of the text box, default: 60 chars
   * @return {String} The altered string
   */
  Visuals.formatSimple = function (text, maxlength) {
    maxlength = maxlength || 60;

    var words = text.split(/[ ]/) || [], result = '', sum = 0;

    for (var ii = 0; ii < words.length; ++ii) {
      if (sum + words[ii].length > maxlength) {
        result = result + '\n' + words[ii];
        sum = words[ii].length;
      } else {
        result += ' ' + words[ii];
        sum += words[ii].length + 1;
      }
    }

    return result;
  };

  // --- Module Exports ---
  niviz.Visuals = Visuals;

}(niviz));