UNPKG

plotter

Version:

Turns an array of data into a graph in a pdf document.

226 lines (199 loc) 6.53 kB
/* Richard Meadows 2012, 2013, 2014 */ /* -------- Includes -------- */ var exec = require('child_process').exec; var _ = require('underscore'); /* -------- Helper Functions -------- */ /** * Performs a n-point moving average on array. */ function moving_average(array, n) { var nums = []; for (i in array) { /* If this item in the array is a number */ if (_.isNumber(array[i])) { nums.push(array[i]); if (nums.length > n) { nums.splice(0,1); /* Remove the first element of the array */ } /* Take the average of the n items in this array */ var sum = _.reduce(nums, function(memo, num){ return memo + num; }, 0); array[i] = sum/nums.length; } } return array; } /** * Performs a n-point maximum on array. */ function moving_maximum(array, n) { var nums = []; for (i in array) { if (_.isNumber(array[i])) { nums.push(array[i]); if (nums.length > n) { nums.splice(0,1); /* Remove the first element of the array */ } /* Take the average of the n items in this array */ var maximum = _.max(nums); array[i] = maximum; } } return array; } /** * Applys an n-point moving filter to a set of series. */ function apply_moving_filter(set, filter, n) { if (!_.isNumber(n)) { n = 3; } for (series in set) { /* For each series */ /* Apply the filter */ set[series] = filter(set[series], n); } return set; } /** * Returns the string to give to gnuplot based on the value of options.time. */ function time_format(options) { if (_.isString(options.time)) { /* Translate the string we've been given into a format */ switch(options.time) { case 'days': case 'Days': return "%d/%m"; case 'hours': case 'Hours': return "%H:%M"; default: /* Presume we've been given a gnuplot-readable time format string */ return options.time; } } else { /* Just default to hours */ return "%H:%M"; } } /** * Sets up gnuplot based on the properties we're given in the options object. */ function setup_gnuplot(gnuplot, options) { if (options.format === 'svg') { /* Setup gnuplot for SVG */ gnuplot.stdin.write('set term svg fname \"Helvetica\" fsize 14\n'); } else if (options.format == 'pdf') { /* PDF: setup Gnuplot output to postscript so ps2pdf can interpret it */ gnuplot.stdin.write('set term postscript landscape enhanced color dashed' + '\"Helvetica\" 14\n'); } else { /* Setup gnuplot for png */ gnuplot.stdin.write('set term png\n'); } /* Formatting Options */ if (options.time) { gnuplot.stdin.write('set xdata time\n'); gnuplot.stdin.write('set timefmt "%s"\n'); gnuplot.stdin.write('set format x "' + time_format(options.time) + '"\n'); gnuplot.stdin.write('set xlabel "time"\n'); } if (options.title) { gnuplot.stdin.write('set title "'+options.title+'"\n'); } if (options.logscale) { gnuplot.stdin.write('set logscale y\n'); } if (options.xlabel) { gnuplot.stdin.write('set xlabel "'+options.xlabel+'"\n'); } if (options.ylabel) { gnuplot.stdin.write('set ylabel "'+options.ylabel+'"\n'); } /* Setup ticks */ gnuplot.stdin.write('set grid xtics ytics mxtics\n'); gnuplot.stdin.write('set mxtics\n'); if (options.nokey) { gnuplot.stdin.write('set nokey\n'); } } /** * Called after Gnuplot has finished. */ function post_gnuplot_processing(error, stdout, stderr) { /* Print stuff */ console.log('stdout: ' + stdout); console.log('stderr: ' + stderr); if (error !== null) { console.log('exec error: ' + error); } } /* -------- Public Functions -------- */ /** * Plots data to a PDF file. If it does not exist, the PDF file will * be created, otherwise this plot will be appended as a new page. */ function plot(options) { /* Required Options */ if (!options.data || !options.filename) { throw("The options object must have 'data' and 'filename' properties!"); return; } /* Translate data into an object if needs be */ if (_.isArray(options.data)) { /* If it's a one-dimentional array */ if (_.isEqual(_.flatten(options.data), options.data)) { options.data = { 'Series 1': options.data }; } } /* Defaults */ if (!options.style) { options.style = 'lines'; /* Default to lines */ } /* Apply moving averages and maximums */ if (options.moving_avg) { options.data = apply_moving_filter(options.data, moving_average, options.moving_avg); } if (options.moving_max) { options.data = apply_moving_filter(options.data, moving_maximum, options.moving_max); } /* Execute Gnuplot specifing a function to be called when it terminates */ if (options.format === 'pdf') { /* Special setup for pdf */ gnuplot = exec('gnuplot | ps2pdf - ' + options.filename, (options.exec ? options.exec : {}), options.finish || post_gnuplot_processing); } else { /* Default for everything else */ gnuplot = exec('gnuplot > ' + options.filename, (options.exec ? options.exec : {}), options.finish || post_gnuplot_processing); } /* Sets up gnuplot based on the properties we've been given in the * options object */ setup_gnuplot(gnuplot, options); /* Get an array containing all the series */ var series = _.keys(options.data); /* Reject series that are functions or come from higher up the protoype chain */ var i; for (i = 0; i < series.length; i += 1) { if (!options.data.hasOwnProperty(series[i]) || typeof options.data[series[i]] === 'function') { delete series[i]; /* undefine this element */ } } /* Filter out any undefined elements */ series = _.filter(series, function() { return true; }); /* Print the command to actually do the plot */ gnuplot.stdin.write('plot'); for (i = 1; i <= series.length; i += 1) { /* For each series */ /* Instruct gnuplot to plot this series */ gnuplot.stdin.write('\'-\' using 1:2 title\'' + series[i - 1] + '\' with ' + options.style + ' lt 1 lc ' + i); /* If another series is to follow, add a comma */ if (i < series.length) { gnuplot.stdin.write(','); } } gnuplot.stdin.write('\n'); /* Print out the data */ for (i = 0; i < series.length; i += 1) { /* For each series */ for (key in options.data[series[i]]) { gnuplot.stdin.write(key + ' ' + options.data[series[i]][key] + '\n'); } /* Terminate the data */ gnuplot.stdin.write('e\n'); } gnuplot.stdin.end(); } /* -------- Exports -------- */ exports.plot = plot;