/*
* 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));