API Docs for: 0.0.1
Show:

File: lib/graphs/header.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/>.
 */

/*eslint complexity: [2, 14]*/
(function (niviz) {
  'use strict';

  // --- Module Dependencies ---
  var GrainShape   = niviz.Value.GrainShape;
  var t            = niviz.Translate.gettext;
  var round        = niviz.util.round;
  var format       = niviz.Visuals.format;
  var formatSimple = niviz.Visuals.formatSimple;

  /** @module niviz */
  /**
   * The static Header class provides a few static methods to draw compact or full
   * headers and the grain shape legend in SLFProfile, StructureProfile and SimpleProfile.
   * Furthermore methods such as Header.link may be used to make any Element
   * object into a clickable hyperlink.
   *
   * @class Header
   * @static
   */
  var Header = {};

  var printtext = function (paper, p) {
    var txt;

    return function (left, top, lbl, val, extra, group) {
      lbl = t(lbl);

      txt = paper.text(left, (top + .5) * p.fontsize, lbl + ': ' + val).attr(p.font)
        .attr('text-anchor', 'start');
      if (extra) txt.attr(extra);

      return txt;
    };
  };

  /**
   * This method accepts a Element and turns it into a hyperlink
   * pointing to the passed URL. When hovering over the element the color
   * changes to blue.
   * Note: Best used for elements of type 'text'.
   *
   * @static
   * @method link
   * @param {Element} element Element object
   * @param {String} url The URL to link to
   * @param {String} [color] The default font color
   */
  Header.link = function (element, url, color) {
    color = color || '#000';

    element.attr('cursor', 'pointer')
      .click(function () {
        this.attr({ fill: color });
        window.open(url);
      })
      .hover(function () {
        this.attr({ fill: '#00c' });
      }, function () {
        this.attr({ fill: color });
      });
  };

  var legend = function (paper, p, profile, left, width, returnheight, top) {
    var gts = [], codes = [], start = { 'text-anchor': 'start' },
      snowfont = {'font': (p.fontsize + 3) + 'px snowsymbolsiacs',
                  'fill': p.font_color, 'font-family': 'snowsymbolsiacs' };

    top = top || 0;

    if (!profile.grainshape || !profile.grainshape.layers) return;

    profile.grainshape.layers.forEach(function (l) {
      var code = '', primary = l.value.primary;
      if (l.value.primary === 'MFcr' && l.value.secondary === '') {
        primary = 'MF';
        code = 'h';
      } else if (l.value.primary === 'MFcr' && l.value.secondary !== '') {
        code = '';
      } else {
        code = GrainShape.type[l.value.primary].key;
      }

      if (code && codes.indexOf(code) === -1) {
        gts.push(primary);
        codes.push(code);
      }

      if (l.value.secondary && gts.indexOf(l.value.secondary) === -1) {
        gts.push(l.value.secondary);
        codes.push(GrainShape.type[l.value.secondary].key);
      }
    });

    if (gts.length === 0) return;

    var textbox1, textbox2, x = left + 5, y = top + 0.3 * p.fontsize, height = p.fontsize * 2,
      set = paper.g();

    gts.forEach(function (gt, index) {
      textbox1 = paper.text(x, y, codes[index]).attr(snowfont).attr(start);
      x = x + textbox1.getBBox().width + p.fontsize;

      textbox2 = paper.text(x, y, t(gt, 'grainshape')).attr(p.font).attr(start);
      x = x + textbox2.getBBox().width + p.fontsize;

      if (x > left + width) {
        x = left + 5;
        y = y + p.fontsize * 1.5;
        height += p.fontsize * 1.5;

        textbox1.attr({ x: x, y: y });
        x = x + textbox1.getBBox().width + p.fontsize;

        textbox2.attr({ x: x, y: y });
        x = x + textbox2.getBBox().width + p.fontsize;
      }

      set.add(textbox1);
      set.add(textbox2);
    });

    set.add(paper.rect(left, top - p.fontsize, width, height).attr({
      fill: 'none',
      stroke: p.font_color
    }).transform('t0.5,0.5'));

    if (returnheight) {
      set.remove();
      return round(13 + height / p.fontsize, 2);
    } else {
      return set;
    }
  };

  var preservews = function (node) {
    node.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
  };

  var compact = function (paper, p, left, width, loc, position, datetime) {
    var start = { 'text-anchor': 'start' }, end = { 'text-anchor': 'end' }, set = paper.g();

    set.add(paper.rect(left, 0, width, p.fontsize * 3).attr({
      fill: 'none',
      stroke: p.font_color
    }).transform('t0.5,0.5'));
    left = left + 5;

    var e = paper.text(left, 1.8 * p.fontsize, loc).attr(p.font).attr(start);
    Header.link(e, position.link, p.font_color);
    set.add(e);

    var tmp = (position.altitude ? position.altitude + ' ' + position.uom : '')
        + (position.direction ? ' \u2013 ' + t(position.direction, 'directions') : '')
        + (position.angle !== undefined ? ' \u2013 ' + position.angle + '°' : '')
        + '      ' + datetime;
    tmp = paper.text(left + width - 10, 1.8 * p.fontsize, tmp).attr(p.font).attr(end);
    preservews(tmp.node);
    set.add(tmp);

    return set;
  };

  var coordinates = function (position) {
    var pos;

    if (position.x && position.y) {
      pos = position.x + ' / ' + position.y;
    } else {
      pos  = Math.round(position.latitude * 100000) / 100000;
      pos += ' / ';
      pos += Math.round(position.longitude * 100000) / 100000;
    }

    return pos;
  };

  var location = function (station) {
    var position = station.position,
      state = position.state ? ' (' + position.state + ') ' : '',
      stationnr = position.station ? ', ' + t('Station') + ': ' + position.station : '';

    return station.name + state + stationnr;
  };

  var wind = function (profile) {
    if (!profile.info.wind) return '';

    var speed = profile.info.wind.speed;
    if (speed || speed === 0) {
      speed = Math.round(Number(speed) * 100 * 3.6) / 100 + ' km/h';
    } else {
      speed = null;
    }

    var dir = (profile.info.wind.dir !== '') ? profile.info.wind.dir : '';
    if (dir !== '') dir = t(dir, 'directions');

    if (dir && speed) return dir + ' / ' + speed;
    if (speed) return speed;
    return dir;
  };

  var airtemp = function (profile) {
    var ta = profile.info.ta !== '' ? Number(profile.info.ta) : undefined;

    if (!isNaN(ta)) return Math.round(ta * 100) / 100  + ' °C';
    return '';
  };

  var snowheight = function (profile) {
    var hs = profile.hs || 0, text = '', densityavg;

    if (hs === 0) return '--';
    else text = round(profile.hs, 2) + ' cm';

    if (profile.density && profile.density.elements.length)
      densityavg = profile.density.elements[0].average();

    if (densityavg && Header.checkdensity(profile))
      text += ' (SWE: ' + round( densityavg * profile.hs / 100, 2) + ' kg/m\u00b2)';

    return text;
  };

  var exposition = function (position) {
    var text = t('Exposition') + ': ' + t(position.direction, 'directions') || '--';

    if (position.angle || position.angle === 0) {
      var unit = (position.angle !== '') ? '°' : '';
      text += ' ' + t('/ Slope') + ': ' + position.angle + unit;
    }

    return text;
  };

  Header.checkdensity = function (profile) {
    if (!profile.hs || !profile.density || !profile.density.elements.length
        || !profile.density.elements[0].layers.length) return false;

    var layerheight = profile.density.elements[0].layerheight();

    if (layerheight >= profile.hs - 10 && layerheight <= profile.hs + 10)
      return true;

    return false;
  };

  var print_roughness = function (paper, p, left, profile, set) {
    if (!profile.info.roughness) return;

    var rlbl = t('Roughness'), snowfont = {
      'font': (p.fontsize + 3) + 'px snowsymbolsiacs',
      'fill': p.font_color, 'font-family': 'snowsymbolsiacs' };

    var rtxt = paper.text(left, 9 * p.fontsize, rlbl + ': ').attr(p.font)
        .attr('text-anchor', 'start');

    set.add(rtxt);

    var offset = rtxt.getBBox();
    var rarray = niviz.CAAML.roughness.filter(function (e) {
      return e.id === profile.info.roughness;
    });

    set.add(paper.text(offset.x2 + 10, 9 * p.fontsize + 1, rarray[0].symbol).attr(snowfont)
            .attr('text-anchor', 'start'));
  };

  /**
   * This method draws the compact or full header or the full header
   * with a grain shape legend below.
   *
   * @static
   * @method draw
   * @param {Object} ctx The context to be used (typically a profile graph)
   * @param {Number} left Leftmost pixel of the header
   * @param {Number} width The width of the header in pixels
   */
  Header.draw = function (ctx, left, width) {
    var p = ctx.properties, paper = ctx.paper, profile = ctx.profile,
      datetime = profile.date.format('YYYY-MM-DD HH:mm Z'),
      position = ctx.station.position,
      loc = location(ctx.station),
      bold = {'font-weight': 'bold'},
      g = printtext(paper, p), set = paper.g(), tmp;

    if (p.head === 'compact') {
      set.add(compact(paper, p, left, width, loc, position, datetime));
    } else if (p.head === 'full' || p.head === 'full+legend') {
      var ch = comments(paper, p, profile, left, width, true);

      set.add(paper.rect(left, 0, width, p.fontsize * 12.5 + ch).attr({
        fill: 'none',
        stroke: p.font_color
      }).transform('t0.5,0.5'));

      if (p.head === 'full+legend')
        set.add(legend(paper, p, profile, left, width, false, p.fontsize * 14 + ch));

      left = left + 5;

      tmp = g(left, 1, 'Location', loc, bold);
      Header.link(tmp, position.link, p.font_color);
      set.add(tmp);

      set.add(g(left, 2.5, 'Observer', profile.info.observer || ''));

      if (profile.info.slf)
        set.add(g(left, 4, 'Profilenr', profile.info.slf.profilenr || ''));

      set.add(g(left, 7, 'Snow height', snowheight(profile)));

      if (profile.info.slf) {
        set.add(g(left, 8.5, 'Hasty Pit', profile.info.slf.hasty ? t('Yes') : t('No')));

        if (profile.info.slf.txt && profile.info.slf.txt.weather)
          set.add(g(left, 10, 'Weather / Precipitation', profile.info.slf.txt.weather || ''));
        else if (profile.info.comment && profile.info.comment.weather)
          set.add(g(left, 10, 'Comment', profile.info.comment.weather || ''));      }

      if (profile.info.comment)
        set.add(g(left, 11.5, 'Remarks', profile.info.comment.metadata
                  ||  profile.info.comment.location || ''));


      // Add comments
      comments(paper, p, profile, left, width - 25, false, set);

      left = left + width / 3;

      set.add(g(left, 2.5, 'Altitude', (position.altitude ? position.altitude : '--')
                + ' ' + position.uom));

      tmp = paper.text(left, 4.5 * p.fontsize, exposition(position)).attr(p.font)
        .attr('text-anchor', 'start');
      set.add(tmp);

      tmp = g(left, 5.5, 'Coordinates', position.longitude || position.x ? coordinates(position) : '--');
      set.add(tmp);
      Header.link(tmp, position.link, p.font_color);

      var density = profile.density && Header.checkdensity(profile)
                    && profile.density.elements[0].average();
      tmp = g(left, 7, 'Avg. density', (density ? Math.round(density) + ' kg/m\u00b3' : '--'));
      set.add(tmp);

      left = left + width / 3;
      set.add(g(left, 1, 'Date / Time', datetime, bold));
      set.add(g(left, 2.5, 'Air temp.', airtemp(profile)));
      set.add(g(left, 4, 'Cloudiness', t(profile.info.sky || '', 'cloudiness')));
      set.add(g(left, 5.5, 'Wind', wind(profile)));

      var ramm = profile.ramm && profile.ramm.elements[0].average();
      set.add(g(left, 7, 'Avg. ram resistance', (ramm ? Math.round(ramm) + ' N' : '')));

      print_roughness(paper, p, left, profile, set);
    }

    return set;
  };


  var comments = function (paper, p, profile, left, width, returnheight, set) {
    var layers = profile.comments && profile.comments.layers.filter(function (l) { return l.value }) || [];
    var text = paper.g(), //references = paper.g(),
      y, fontsize = p.fontsize - 2, formatting = { fontSize: fontsize, textAnchor: 'start'};

    var show_layer_comments = false;
    p.other_parameters && p.other_parameters.forEach(function (other) {
      if (other.name === 'comments') show_layer_comments = true;
    });

    if (!layers.length || !show_layer_comments) return 0;

    var box = paper.text(0, 0, '').attr(p.font).attr(formatting), sum = 0;
    var ignoreLibs = !!window.ignoreLibs;

    layers.slice().reverse().forEach(function (layer, i) {
      var currenttxt = format(box, '', layer.value, width);
      if (ignoreLibs) currenttxt = formatSimple(layer.value);
      var mbox = paper.multitext(0, 0, currenttxt).attr(p.font).attr(formatting);

      // Increase the height of the SVG canvas depending on the height of mbox
      var lines = (currenttxt.match(/\n/g) || []).length + 1, h = lines * fontsize * 1.2;
      if (!ignoreLibs) h = mbox.getBBox().height;

      var cy = 8.5 * p.fontsize + 5 * p.fontsize + sum + i * 0.2 * p.fontsize;

      text.add(paper.text(left, cy, '(' + (i + 1) + ') ')
        .attr(p.font).attr(formatting).attr({ textAnchor: 'start' }));
      mbox.transform('t' + (left + 20) + ',' + cy);

      text.add(mbox);
      sum += h;
    });

    box.remove();
    text.attr(p.font).attr({ fontSize: p.fontsize - 3, textAnchor: 'end' });

    if (returnheight) {
      text.remove();
      return sum + p.fontsize;
    } else {
      set.add(text);
    }
  };

  /**
   * This function returns the presumptive pixel height of the header
   * for the passed properties.
   *
   * @static
   * @method height
   * @param {Object} p niViz graph properties
   * @param {Object} paper SnapSVG paper
   * @param {Profile} profile niViz profile
   * @param {Number} left Left offset of the header
   * @param {Number} width Width of the header
   * @return {Number} The height of the header in pixel
   */
  Header.height = function (p, paper, profile, left, width) {
    var commentheight = comments(paper, p, profile, left, width, true);

    if (p.head === 'compact') return p.fontsize * 4;
    if (p.head === 'full') return p.fontsize * 13 + commentheight;

    if (p.head === 'full+legend') {
      var legendheight = legend(paper, p, profile, left, width, true);
      if (legendheight === undefined) legendheight = 13;
      return p.fontsize * legendheight + commentheight;
    }

    return 0;
  };

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

}(niviz));