diff options
author | href <href@random.sh> | 2021-09-01 10:30:18 +0200 |
---|---|---|
committer | href <href@random.sh> | 2021-09-01 10:30:18 +0200 |
commit | 75687711f35355bc30e4829439384aab28fcac6d (patch) | |
tree | 8f3256f472893c39720a684d390e890a152f7303 /priv/static/js/metricsgraphics/misc/process.js | |
parent | link: post_* callbacks; html & pdftitle. (diff) |
Commit all the changes that hasn't been committed + updates.
Diffstat (limited to 'priv/static/js/metricsgraphics/misc/process.js')
-rw-r--r-- | priv/static/js/metricsgraphics/misc/process.js | 368 |
1 files changed, 368 insertions, 0 deletions
diff --git a/priv/static/js/metricsgraphics/misc/process.js b/priv/static/js/metricsgraphics/misc/process.js new file mode 100644 index 0000000..d0d312d --- /dev/null +++ b/priv/static/js/metricsgraphics/misc/process.js @@ -0,0 +1,368 @@ +function mg_process_scale_ticks(args, axis) { + var accessor; + var scale_ticks; + var max; + + if (axis === 'x') { + accessor = args.x_accessor; + scale_ticks = args.scales.X.ticks(args.xax_count); + max = args.processed.max_x; + } else if (axis === 'y') { + accessor = args.y_accessor; + scale_ticks = args.scales.Y.ticks(args.yax_count); + max = args.processed.max_y; + } + + function log10(val) { + if (val === 1000) { + return 3; + } + if (val === 1000000) { + return 7; + } + return Math.log(val) / Math.LN10; + } + + if ((axis === 'x' && args.x_scale_type === 'log') || (axis === 'y' && args.y_scale_type === 'log')) { + // get out only whole logs + scale_ticks = scale_ticks.filter(function(d) { + return Math.abs(log10(d)) % 1 < 1e-6 || Math.abs(log10(d)) % 1 > 1 - 1e-6; + }); + } + + // filter out fraction ticks if our data is ints and if xmax > number of generated ticks + var number_of_ticks = scale_ticks.length; + + // is our data object all ints? + var data_is_int = true; + args.data.forEach(function(d, i) { + d.forEach(function(d, i) { + if (d[accessor] % 1 !== 0) { + data_is_int = false; + return false; + } + }); + }); + + if (data_is_int && number_of_ticks > max && args.format === 'count') { + // remove non-integer ticks + scale_ticks = scale_ticks.filter(function(d) { + return d % 1 === 0; + }); + } + + if (axis === 'x') { + args.processed.x_ticks = scale_ticks; + } else if (axis === 'y') { + args.processed.y_ticks = scale_ticks; + } +} + +function raw_data_transformation(args) { + 'use strict'; + + // dupe our data so we can modify it without adverse effect + args.data = MG.clone(args.data); + + // we need to account for a few data format cases: + // #0 {bar1:___, bar2:___} // single object (for, say, bar charts) + // #1 [{key:__, value:__}, ...] // unnested obj-arrays + // #2 [[{key:__, value:__}, ...], [{key:__, value:__}, ...]] // nested obj-arrays + // #3 [[4323, 2343],..] // unnested 2d array + // #4 [[[4323, 2343],..] , [[4323, 2343],..]] // nested 2d array + args.single_object = false; // for bar charts. + args.array_of_objects = false; + args.array_of_arrays = false; + args.nested_array_of_arrays = false; + args.nested_array_of_objects = false; + + // is the data object a nested array? + + if (is_array_of_arrays(args.data)) { + args.nested_array_of_objects = args.data.map(function(d) { + return is_array_of_objects_or_empty(d); + }); // Case #2 + args.nested_array_of_arrays = args.data.map(function(d) { + return is_array_of_arrays(d); + }); // Case #4 + } else { + args.array_of_objects = is_array_of_objects(args.data); // Case #1 + args.array_of_arrays = is_array_of_arrays(args.data); // Case #3 + } + + if (args.chart_type === 'line') { + if (args.array_of_objects || args.array_of_arrays) { + args.data = [args.data]; + } + } else { + if (!(mg_is_array(args.data[0]))) { + args.data = [args.data]; + } + } + // if the y_accessor is an array, break it up and store the result in args.data + mg_process_multiple_x_accessors(args); + mg_process_multiple_y_accessors(args); + + // if user supplies keyword in args.color, change to arg.colors. + // this is so that the API remains fairly sensible and legible. + if (args.color !== undefined) { + args.colors = args.color; + } + + // if user has supplied args.colors, and that value is a string, turn it into an array. + if (args.colors !== null && typeof args.colors === 'string') { + args.colors = [args.colors]; + } + + // sort x-axis data + if (args.chart_type === 'line' && args.x_sort === true) { + for (var i = 0; i < args.data.length; i++) { + args.data[i].sort(function(a, b) { + return a[args.x_accessor] - b[args.x_accessor]; + }); + } + } + + return this; +} + +function mg_process_multiple_accessors(args, which_accessor) { + // turns an array of accessors into ... + if (mg_is_array(args[which_accessor])) { + args.data = args.data.map(function(_d) { + return args[which_accessor].map(function(ya) { + return _d.map(function(di) { + di = MG.clone(di); + + if (di[ya] === undefined) { + return undefined; + } + + di['multiline_' + which_accessor] = di[ya]; + return di; + }).filter(function(di) { + return di !== undefined; + }); + }); + })[0]; + args[which_accessor] = 'multiline_' + which_accessor; + } +} + +function mg_process_multiple_x_accessors(args) { + mg_process_multiple_accessors(args, 'x_accessor'); +} + +function mg_process_multiple_y_accessors(args) { + mg_process_multiple_accessors(args, 'y_accessor'); +} + +MG.raw_data_transformation = raw_data_transformation; + +function process_line(args) { + 'use strict'; + + var time_frame; + + // do we have a time-series? + var is_time_series = d3.sum(args.data.map(function(series) { + return series.length > 0 && mg_is_date(series[0][args.x_accessor]); + })) > 0; + + // are we replacing missing y values with zeros? + if ((args.missing_is_zero || args.missing_is_hidden) && args.chart_type === 'line' && is_time_series) { + for (var i = 0; i < args.data.length; i++) { + // we need to have a dataset of length > 2, so if it's less than that, skip + if (args.data[i].length <= 1) { + continue; + } + + var first = args.data[i][0]; + var last = args.data[i][args.data[i].length - 1]; + + // initialize our new array for storing the processed data + var processed_data = []; + + // we'll be starting from the day after our first date + var start_date = MG.clone(first[args.x_accessor]).setDate(first[args.x_accessor].getDate() + 1); + + // if we've set a max_x, add data points up to there + var from = (args.min_x) ? args.min_x : start_date; + var upto = (args.max_x) ? args.max_x : last[args.x_accessor]; + + time_frame = mg_get_time_frame((upto - from) / 1000); + + if (['four-days', 'many-days', 'many-months', 'years', 'default'].indexOf(time_frame) !== -1 && args.missing_is_hidden_accessor === null) { + for (var d = new Date(from); d <= upto; d.setDate(d.getDate() + 1)) { + var o = {}; + d.setHours(0, 0, 0, 0); + + // add the first date item, we'll be starting from the day after our first date + if (Date.parse(d) === Date.parse(new Date(start_date))) { + processed_data.push(MG.clone(args.data[i][0])); + } + + // check to see if we already have this date in our data object + var existing_o = null; + args.data[i].forEach(function(val, i) { + if (Date.parse(val[args.x_accessor]) === Date.parse(new Date(d))) { + existing_o = val; + + return false; + } + }); + + // if we don't have this date in our data object, add it and set it to zero + if (!existing_o) { + o[args.x_accessor] = new Date(d); + o[args.y_accessor] = 0; + o['_missing'] = true; //we want to distinguish between zero-value and missing observations + processed_data.push(o); + } + + // if the data point has, say, a 'missing' attribute set or if its + // y-value is null identify it internally as missing + else if (existing_o[args.missing_is_hidden_accessor] || existing_o[args.y_accessor] === null) { + existing_o['_missing'] = true; + processed_data.push(existing_o); + } + + //otherwise, use the existing object for that date + else { + processed_data.push(existing_o); + } + } + } else { + for (var j = 0; j < args.data[i].length; j += 1) { + var obj = MG.clone(args.data[i][j]); + obj['_missing'] = args.data[i][j][args.missing_is_hidden_accessor]; + processed_data.push(obj); + } + } + + // update our date object + args.data[i] = processed_data; + } + } + + return this; +} + +MG.process_line = process_line; + +function process_histogram(args) { + 'use strict'; + + // if args.binned == false, then we need to bin the data appropriately. + // if args.binned == true, then we need to make sure to compute the relevant computed data. + // the outcome of either of these should be something in args.computed_data. + // the histogram plotting function will be looking there for the data to plot. + + // we need to compute an array of objects. + // each object has an x, y, and dx. + + // histogram data is always single dimension + var our_data = args.data[0]; + + var extracted_data; + if (args.binned === false) { + // use d3's built-in layout.histogram functionality to compute what you need. + + if (typeof(our_data[0]) === 'object') { + // we are dealing with an array of objects. Extract the data value of interest. + extracted_data = our_data + .map(function(d) { + return d[args.x_accessor]; + }); + } else if (typeof(our_data[0]) === 'number') { + // we are dealing with a simple array of numbers. No extraction needed. + extracted_data = our_data; + } else { + console.log('TypeError: expected an array of numbers, found ' + typeof(our_data[0])); + return; + } + + var hist = d3.histogram(); + if (args.bins) { + hist.thresholds(args.bins); + } + + var bins = hist(extracted_data); + args.processed_data = bins.map(function(d) { + return { 'x': d.x0, 'y': d.length }; + }); + } else { + // here, we just need to reconstruct the array of objects + // take the x accessor and y accessor. + // pull the data as x and y. y is count. + + args.processed_data = our_data.map(function(d) { + return { 'x': d[args.x_accessor], 'y': d[args.y_accessor] }; + }); + + var this_pt; + var next_pt; + + // we still need to compute the dx component for each data point + for (var i = 0; i < args.processed_data.length; i++) { + this_pt = args.processed_data[i]; + if (i === args.processed_data.length - 1) { + this_pt.dx = args.processed_data[i - 1].dx; + } else { + next_pt = args.processed_data[i + 1]; + this_pt.dx = next_pt.x - this_pt.x; + } + } + } + + // capture the original data and accessors before replacing args.data + if (!args.processed) { + args.processed = {}; + } + args.processed.original_data = args.data; + args.processed.original_x_accessor = args.x_accessor; + args.processed.original_y_accessor = args.y_accessor; + + args.data = [args.processed_data]; + args.x_accessor = args.processed_x_accessor; + args.y_accessor = args.processed_y_accessor; + + return this; +} + +MG.process_histogram = process_histogram; + +// for use with bar charts, etc. +function process_categorical_variables(args) { + 'use strict'; + + var extracted_data, processed_data = {}, + pd = []; + //var our_data = args.data[0]; + var label_accessor = args.bar_orientation === 'vertical' ? args.x_accessor : args.y_accessor; + var data_accessor = args.bar_orientation === 'vertical' ? args.y_accessor : args.x_accessor; + + return this; +} + +MG.process_categorical_variables = process_categorical_variables; + +function process_point(args) { + 'use strict'; + + var data = args.data[0]; + var x = data.map(function(d) { + return d[args.x_accessor]; + }); + var y = data.map(function(d) { + return d[args.y_accessor]; + }); + + if (args.least_squares) { + args.ls_line = least_squares(x, y); + } + + return this; +} + +MG.process_point = process_point; |