<commit_before>import { ReconstructionIssues } from './../enums.js';

/**
 * Checks if a series is reconstructable to a 3D volume.
 *
 * @param {Object[]} instances An array of `OHIFInstanceMetadata` objects.
 *
 * @returns {Object} reconstructable value, missingFrames and warningIssues.
 */
export default function isDisplaySetReconstructable(instances) {
  if (!instances.length) {
    return { value: false };
  }

  const firstInstance = instances[0].getData().metadata;

  const Modality = firstInstance.Modality;
  const isMultiframe = firstInstance.NumberOfFrames > 1;

  if (!constructableModalities.includes(Modality)) {
    return { value: false };
  }

  // Can't reconstruct if we only have one image.
  if (!isMultiframe && instances.length === 1) {
    return { value: false };
  }

  if (isMultiframe) {
    return processMultiframe(instances[0]);
  } else {
    return processSingleframe(instances);
  }
}

/**
 * Process reconstructable multiframes checks
 * TODO: deal with multriframe checks! return false for now as can't reconstruct.
 *
 * @param {Object} instanceof `OHIFInstanceMetadata` objects. Currently not used.
 *
 * @returns {Object} reconstructable value and warningIssues.
 */
function processMultiframe(instance) {
  const warningIssues = [ReconstructionIssues.MULTIFRAMES];
  return { value: false, warningIssues };
}

/**
 * Process reconstructable single frame checks
 *
 * @param {Object[]} instances An array of `OHIFInstanceMetadata` objects.
 *
 * @returns {Object} reconstructable value, missingFrames and warningIssues.
 */
function processSingleframe(instances) {
  const n = instances.length;
  const firstImage = instances[0].getData().metadata;
  const firstImageRows = firstImage.Rows;
  const firstImageColumns = firstImage.Columns;
  const firstImageSamplesPerPixel = firstImage.SamplesPerPixel;
  const firstImageOrientationPatient = firstImage.ImageOrientationPatient;
  const firstImagePositionPatient = firstImage.ImagePositionPatient;

  const warningIssues = [];
  // Can't reconstruct if we:
  // -- Have a different dimensions within a displaySet.
  // -- Have a different number of components within a displaySet.
  // -- Have different orientations within a displaySet.
  for (let ii = 1; ii < n; ++ii) {
    const instance = instances[ii].getData().metadata;
    const {
      Rows,
      Columns,
      SamplesPerPixel,
      ImageOrientationPatient,
    } = instance;

    if (Rows !== firstImageRows || Columns !== firstImageColumns) {
      warningIssues.push(ReconstructionIssues.VARYING_IMAGESDIMENSIONS);
    } else if (SamplesPerPixel !== firstImageSamplesPerPixel) {
      warningIssues.push(ReconstructionIssues.VARYING_IMAGESCOMPONENTS);
    } else if (!_isSameArray(ImageOrientationPatient, firstImageOrientationPatient)) {
      warningIssues.push(ReconstructionIssues.VARYING_IMAGESORIENTATION);
    }

    if (warningIssues.length !== 0) {
      break;
    }
  }

  let missingFrames = 0;

  // Check if frame spacing is approximately equal within a spacingTolerance.
  // If spacing is on a uniform grid but we are missing frames,
  // Allow reconstruction, but pass back the number of missing frames.
  if (n > 2) {
    const lastIpp = instances[n - 1].getData().metadata
      .ImagePositionPatient;

    // We can't reconstruct if we are missing ImagePositionPatient values
    if (firstImagePositionPatient && lastIpp) {
      const averageSpacingBetweenFrames =
        _getPerpendicularDistance(firstImagePositionPatient, lastIpp) /
        (n - 1);

      let previousImagePositionPatient = firstImagePositionPatient;

      for (let ii = 1; ii < n; ++ii) {
        const instance = instances[ii].getData().metadata;
        const { ImagePositionPatient } = instance;

        const spacingBetweenFrames = _getPerpendicularDistance(
          ImagePositionPatient,
          previousImagePositionPatient
        );
        const spacingIssue = _getSpacingIssue(
          spacingBetweenFrames,
          averageSpacingBetweenFrames
        );

        if (spacingIssue) {
          const issue = spacingIssue.issue;

          if (issue === ReconstructionIssues.MISSING_FRAMES) {
            missingFrames += spacingIssue.missingFrames;
          } else if (issue === ReconstructionIssues.IRREGULAR_SPACING) {
            warningIssues.push(issue);
            break;
          }
        }

        previousImagePositionPatient = ImagePositionPatient;
      }
    }
  }

  // check if dataset is 4D
  if (_isDataset4D(instances)) {
    warningIssues.push(ReconstructionIssues.DATASET_4D);
  }

  return { value: warningIssues.length === 0 ? true : false, missingFrames, warningIssues };
}

/**
 *  Check if 4D dataset.
 *
 *  Assuming that slices at different time have the same position, here we just check if
 *  there are multiple slices for the same ImagePositionPatient and disable MPR.
 *
 *  A better heuristic would be checking 4D tags, e.g. the presence of multiple TemporalPositionIdentifier values.
 *  However, some studies (e.g. https://github.com/OHIF/Viewers/issues/2113) do not have such tags.
 *
 * @param {Object[]} instances An array of `OHIFInstanceMetadata` objects.
 *
 * @returns {boolean} reconstructable value.
 */
 function _isDataset4D(instances) {
  const n = instances.length;
  for (let ii = 0; ii < n; ++ii) {
    const instanceMetadataControl = instances[ii].getData().metadata;
    if (
      !instanceMetadataControl ||
      instanceMetadataControl === undefined ||
      !instanceMetadataControl.ImagePositionPatient ||
      instanceMetadataControl.ImagePositionPatient === undefined
    ) {
      continue;
    }
    for (let jj = ii + 1; jj < n; ++jj) {
      const instanceMetadata = instances[jj].getData().metadata;
      if (
        !instanceMetadata ||
        instanceMetadata === undefined ||
        !instanceMetadata.ImagePositionPatient ||
        instanceMetadata.ImagePositionPatient === undefined
      ) {
        continue;
      }

      if (_isSameArray(instanceMetadataControl.ImagePositionPatient, instanceMetadata.ImagePositionPatient)) {
        return true;
      }
    }
  }

  return false;
}

function _isSameArray(iop1, iop2) {
  if (iop1 === undefined || !iop2 === undefined) {
    return;
  }

  return (
    Math.abs(iop1[0] - iop2[0]) < iopTolerance &&
    Math.abs(iop1[1] - iop2[1]) < iopTolerance &&
    Math.abs(iop1[2] - iop2[2]) < iopTolerance
  );
}

// TODO: Is 10% a reasonable spacingTolerance for spacing?
const spacingTolerance = 0.1;
const iopTolerance = 0.01;

/**
 * Checks for spacing issues.
 *
 * @param {number} spacing The spacing between two frames.
 * @param {number} averageSpacing The average spacing between all frames.
 *
 * @returns {Object} An object containing the issue and extra information if necessary.
 */
function _getSpacingIssue(spacing, averageSpacing) {
  const equalWithinTolerance =
    Math.abs(spacing - averageSpacing) < averageSpacing * spacingTolerance;

  if (equalWithinTolerance) {
    return;
  }

  const multipleOfAverageSpacing = spacing / averageSpacing;

  const numberOfSpacings = Math.round(multipleOfAverageSpacing);

  const errorForEachSpacing =
    Math.abs(spacing - numberOfSpacings * averageSpacing) / numberOfSpacings;

  if (errorForEachSpacing < spacingTolerance * averageSpacing) {
    return {
      issue: ReconstructionIssues.MISSING_FRAMES,
      missingFrames: numberOfSpacings - 1,
    };
  }

  return { issue: ReconstructionIssues.IRREGULAR_SPACING };
}

function _getPerpendicularDistance(a, b) {
  return Math.sqrt(
    Math.pow(a[0] - b[0], 2) +
    Math.pow(a[1] - b[1], 2) +
    Math.pow(a[2] - b[2], 2)
  );
}

const constructableModalities = ['MR', 'CT', 'PT', 'NM'];
<commit_msg>IDC2259: do not check uniform spacing in the 4th dimension for 4D datasets (#2356)<commit_after>import { ReconstructionIssues } from './../enums.js';

/**
 * Checks if a series is reconstructable to a 3D volume.
 *
 * @param {Object[]} instances An array of `OHIFInstanceMetadata` objects.
 *
 * @returns {Object} reconstructable value, missingFrames and warningIssues.
 */
export default function isDisplaySetReconstructable(instances) {
  if (!instances.length) {
    return { value: false };
  }

  const firstInstance = instances[0].getData().metadata;

  const Modality = firstInstance.Modality;
  const isMultiframe = firstInstance.NumberOfFrames > 1;

  if (!constructableModalities.includes(Modality)) {
    return { value: false };
  }

  // Can't reconstruct if we only have one image.
  if (!isMultiframe && instances.length === 1) {
    return { value: false };
  }

  if (isMultiframe) {
    return processMultiframe(instances[0]);
  } else {
    return processSingleframe(instances);
  }
}

/**
 * Process reconstructable multiframes checks
 * TODO: deal with multriframe checks! return false for now as can't reconstruct.
 *
 * @param {Object} instanceof `OHIFInstanceMetadata` objects. Currently not used.
 *
 * @returns {Object} reconstructable value and warningIssues.
 */
function processMultiframe(instance) {
  const warningIssues = [ReconstructionIssues.MULTIFRAMES];
  return { value: false, warningIssues };
}

/**
 * Process reconstructable single frame checks
 *
 * @param {Object[]} instances An array of `OHIFInstanceMetadata` objects.
 *
 * @returns {Object} reconstructable value, missingFrames and warningIssues.
 */
function processSingleframe(instances) {
  const n = instances.length;
  const firstImage = instances[0].getData().metadata;
  const firstImageRows = firstImage.Rows;
  const firstImageColumns = firstImage.Columns;
  const firstImageSamplesPerPixel = firstImage.SamplesPerPixel;
  const firstImageOrientationPatient = firstImage.ImageOrientationPatient;
  const firstImagePositionPatient = firstImage.ImagePositionPatient;

  const warningIssues = [];
  // Can't reconstruct if we:
  // -- Have a different dimensions within a displaySet.
  // -- Have a different number of components within a displaySet.
  // -- Have different orientations within a displaySet.
  for (let ii = 1; ii < n; ++ii) {
    const instance = instances[ii].getData().metadata;
    const {
      Rows,
      Columns,
      SamplesPerPixel,
      ImageOrientationPatient,
    } = instance;

    if (Rows !== firstImageRows || Columns !== firstImageColumns) {
      warningIssues.push(ReconstructionIssues.VARYING_IMAGESDIMENSIONS);
    } else if (SamplesPerPixel !== firstImageSamplesPerPixel) {
      warningIssues.push(ReconstructionIssues.VARYING_IMAGESCOMPONENTS);
    } else if (!_isSameArray(ImageOrientationPatient, firstImageOrientationPatient)) {
      warningIssues.push(ReconstructionIssues.VARYING_IMAGESORIENTATION);
    }

    if (warningIssues.length !== 0) {
      break;
    }
  }

  // check if dataset is 4D
  const datasetIs4D = _isDataset4D(instances);
  if (datasetIs4D) {
    warningIssues.push(ReconstructionIssues.DATASET_4D);
  }

  let missingFrames = 0;

  // Check if frame spacing is approximately equal within a spacingTolerance.
  // If spacing is on a uniform grid but we are missing frames,
  // Allow reconstruction, but pass back the number of missing frames.
  if (n > 2) {
    const lastIpp = instances[n - 1].getData().metadata
      .ImagePositionPatient;

    // We can't reconstruct if we are missing ImagePositionPatient values
    if (firstImagePositionPatient && lastIpp) {
      const averageSpacingBetweenFrames =
        _getPerpendicularDistance(firstImagePositionPatient, lastIpp) /
        (n - 1);

      let previousImagePositionPatient = firstImagePositionPatient;

      for (let ii = 1; ii < n; ++ii) {
        const instance = instances[ii].getData().metadata;
        const { ImagePositionPatient } = instance;

        const spacingBetweenFrames = _getPerpendicularDistance(
          ImagePositionPatient,
          previousImagePositionPatient
        );

        if (datasetIs4D && spacingBetweenFrames < 1.e-3) {
          // the dataset is 4D, if the distance is zero, means that we are
          // checking the 4th dimension. Do not return, since we want still to
          // check the 3rd dimension spacing.
          continue;
        }

        const spacingIssue = _getSpacingIssue(
          spacingBetweenFrames,
          averageSpacingBetweenFrames
        );

        if (spacingIssue) {
          const issue = spacingIssue.issue;

          if (issue === ReconstructionIssues.MISSING_FRAMES) {
            missingFrames += spacingIssue.missingFrames;
          } else if (issue === ReconstructionIssues.IRREGULAR_SPACING) {
            warningIssues.push(issue);
            break;
          }
        }

        previousImagePositionPatient = ImagePositionPatient;
      }
    }
  }

  return { value: warningIssues.length === 0 ? true : false, missingFrames, warningIssues };
}

/**
 *  Check if 4D dataset.
 *
 *  Assuming that slices at different time have the same position, here we just check if
 *  there are multiple slices for the same ImagePositionPatient and disable MPR.
 *
 *  A better heuristic would be checking 4D tags, e.g. the presence of multiple TemporalPositionIdentifier values.
 *  However, some studies (e.g. https://github.com/OHIF/Viewers/issues/2113) do not have such tags.
 *
 * @param {Object[]} instances An array of `OHIFInstanceMetadata` objects.
 *
 * @returns {boolean} reconstructable value.
 */
 function _isDataset4D(instances) {
  const n = instances.length;
  for (let ii = 0; ii < n; ++ii) {
    const instanceMetadataControl = instances[ii].getData().metadata;
    if (
      !instanceMetadataControl ||
      instanceMetadataControl === undefined ||
      !instanceMetadataControl.ImagePositionPatient ||
      instanceMetadataControl.ImagePositionPatient === undefined
    ) {
      continue;
    }
    for (let jj = ii + 1; jj < n; ++jj) {
      const instanceMetadata = instances[jj].getData().metadata;
      if (
        !instanceMetadata ||
        instanceMetadata === undefined ||
        !instanceMetadata.ImagePositionPatient ||
        instanceMetadata.ImagePositionPatient === undefined
      ) {
        continue;
      }

      if (_isSameArray(instanceMetadataControl.ImagePositionPatient, instanceMetadata.ImagePositionPatient)) {
        return true;
      }
    }
  }

  return false;
}

function _isSameArray(iop1, iop2) {
  if (iop1 === undefined || !iop2 === undefined) {
    return;
  }

  return (
    Math.abs(iop1[0] - iop2[0]) < iopTolerance &&
    Math.abs(iop1[1] - iop2[1]) < iopTolerance &&
    Math.abs(iop1[2] - iop2[2]) < iopTolerance
  );
}

// TODO: Is 10% a reasonable spacingTolerance for spacing?
const spacingTolerance = 0.1;
const iopTolerance = 0.01;

/**
 * Checks for spacing issues.
 *
 * @param {number} spacing The spacing between two frames.
 * @param {number} averageSpacing The average spacing between all frames.
 *
 * @returns {Object} An object containing the issue and extra information if necessary.
 */
function _getSpacingIssue(spacing, averageSpacing) {
  const equalWithinTolerance =
    Math.abs(spacing - averageSpacing) < averageSpacing * spacingTolerance;

  if (equalWithinTolerance) {
    return;
  }

  const multipleOfAverageSpacing = spacing / averageSpacing;

  const numberOfSpacings = Math.round(multipleOfAverageSpacing);

  const errorForEachSpacing =
    Math.abs(spacing - numberOfSpacings * averageSpacing) / numberOfSpacings;

  if (errorForEachSpacing < spacingTolerance * averageSpacing) {
    return {
      issue: ReconstructionIssues.MISSING_FRAMES,
      missingFrames: numberOfSpacings - 1,
    };
  }

  return { issue: ReconstructionIssues.IRREGULAR_SPACING };
}

function _getPerpendicularDistance(a, b) {
  return Math.sqrt(
    Math.pow(a[0] - b[0], 2) +
    Math.pow(a[1] - b[1], 2) +
    Math.pow(a[2] - b[2], 2)
  );
}

const constructableModalities = ['MR', 'CT', 'PT', 'NM'];
<|endoftext|>


<commit_before>#!/usr/bin/env node
"use strict";

const cp = require("child_process");
const yargs = require("yargs");
const argv = yargs.options({
        "b": {
            alias: "build",
            describe: "Build the project using prod build settings",
            boolean: true
        },
        "d": {
            alias: "deploy",
            describe: "Deploy the project to gh-pages",
            boolean: true
        },
        "q": {
            alias: "quiet",
            describe: "Minimal output from commands",
            boolean: true
        }
    })
    .help("h").alias("h", "help")
    .usage("Usage: $0 [-b] [-d]")
    .argv;

// Local variables:
let runBuild = argv.b;
let runDeploy = argv.d;
let quiet = argv.q;

if (!(runBuild || runDeploy)) {
    yargs.showHelp();
    process.exit();
}

if (runBuild) {
    console.log("Building for production...");
    runCommand("npm", ["run", "build:prod"]);
}

if (runDeploy) {
    // Fail if there are uncommitted changes
    let exitCode = runCommand("git", ["diff-index", "--quiet", "--cached", "HEAD"]);
    if (exitCode !== 0) {
        // TODO: automatically stash changes
        console.error("There are uncommitted changes in your index. Please commit or stash them first.");
        process.exit();
    }

    console.log("Adding dist to repository...");
    runCommand("git", ["add", "dist"]);
    runCommand("git", ["commit", "-m", "'Update prod build'"]);
    console.log("Pushing to gh-pages...");
    runCommand("git", ["subtree", "push", "--prefix", "dist", "origin", "gh-pages"]);
}

console.log("Done!");

// Helper functions:
function runCommand(command, args) {
    let options = {};
    if (!quiet) {
        options["stdio"] = "inherit";
    }
    let result = cp.spawnSync(command, args, options);
    return result.status;
}
<commit_msg>Update deploy script<commit_after>#!/usr/bin/env node
"use strict";

const cp = require("child_process");
const yargs = require("yargs");
const argv = yargs.options({
        "b": {
            alias: "build",
            describe: "Build the project using prod build settings",
            boolean: true
        },
        "d": {
            alias: "deploy",
            describe: "Deploy the project to gh-pages",
            boolean: true
        },
        "q": {
            alias: "quiet",
            describe: "Minimal output from commands",
            boolean: true
        }
    })
    .help("h").alias("h", "help")
    .usage("Usage: $0 [-b] [-d]")
    .argv;

// Local variables:
let runBuild = argv.b;
let runDeploy = argv.d;
let quiet = argv.q;

if (!(runBuild || runDeploy)) {
    yargs.showHelp();
    process.exit();
}

if (runBuild) {
    console.log("Building for production...");
    runCommand("npm", ["run", "build:prod"]);
}

if (runDeploy) {
    // Fail if there are uncommitted changes
    let exitCode = runCommand("git", ["diff-index", "--quiet", "--cached", "HEAD"]);
    if (exitCode !== 0) {
        // TODO: automatically stash changes
        console.error("There are uncommitted changes in your index. Please commit or stash them first.");
        process.exit();
    }

    console.log("Adding dist to repository...");
    runCommand("git", ["add", "dist"]);
    runCommand("git", ["commit", "-m", "'Update prod build'"]);
    console.log("Pushing to gh-pages...");
    runCommand("git", ["subtree", "push", "--prefix", "dist", "origin", "gh-pages"]);
}

console.log("Done!");

// Helper functions:
function runCommand(command, args) {
    let options = {};
    if (!quiet) {
        options["stdio"] = "inherit";
    }
    let result = cp.spawnSync(command, args, options);
    return result.status;
}
<|endoftext|>


<commit_before>require('shelljs/global');

config.silent = true

console.time("Время выполнения ");

rm('-r', 'static');
rm('-r', 'staticDecor/dist');

exec('buster generate', function() {
    console.log("Сгенерирована копия Ghost сайта");
    console.log("Запуск оптимизации");
    console.log("...");

    exec('cd staticDecor && grunt', function() {
        console.log("Оптимизированно");
        console.log("Подготовка перед отправкой");

        cp('-R', 'staticDecor/dist/', 'staticGit');

        console.log("Скопированно...");

        // Работа с git
        var commitMesg = "Обновление блога:" + new Date();

        exec('git add . && git commit -m"' + commitMesg + '" && git push', function() {
            console.log("Отправка основного репозитория закончена");
            console.log("Отправка стаческого блога");

            cd("staticGit")

            exec('git add . && git commit -m"' + commitMesg + '" && git push', function() {
                console.timeEnd("Время выполнения ");
            })
        })

    });
});
<commit_msg>Обновление блога:Tue Aug 18 2015 18:35:01 GMT+0300 (MSK)<commit_after>require('shelljs/global');

config.silent = true

console.time("Время выполнения ");

rm('-r', 'static');
rm('-r', 'staticDecor/dist');

exec('buster generate', function() {
    console.log("Сгенерирована копия Ghost сайта");
    console.log("Запуск оптимизации");
    console.log("...");

    exec('cd staticDecor && grunt', function() {
        console.log("Оптимизированно");
        console.log("Подготовка перед отправкой");

        cp('-R', 'staticDecor/dist/', 'staticGit');

        console.log("Скопированно...");

        // Работа с git
        var commitMesg = "Обновление блога:" + new Date();

        exec('git add . && git commit -m"' + commitMesg + '" && git push', function() {
            console.log("Отправка основного репозитория закончена");
            console.log("Отправка стаческого блога");

            cd("staticGit")

            exec('ls', function(code, output) {
                console.log('Exit code:', code);
                console.log('Program output:', output);
            });
        })

    });
});
<|endoftext|>


<commit_before>import strip from "rollup-plugin-strip";

export default {
	input: "source/Escher.js",
	plugins: [
		strip(
		{
			functions: ["assert.*", "debug", "alert"],
			debugger: false,
			sourceMap: false
		})
	],
	output: [
		{
			format: "umd",
			name: "Nunu",
			file: "build/nunu.js",
			indent: "\t"
		},
		{
			format: "es",
			file: "build/nunu.module.js",
			indent: "\t"
		}
	]
};
<commit_msg>Derp<commit_after><|endoftext|>


<commit_before>margin = 10;
barheight = 10;
width = 450;
textbar = 20;
precision_step = 8;

function sort_by(type) {
    return function(a, b) {
        return b[type] - a[type];
    }
}

function r10(d) {
    return "" + (Math.round(d * 10) / 10);
}

function make_graph(node, data, start, end) {
    var len = data.length;
    var precision = 64; // TODO

    var a = d3.selectAll("script");
    var script = a[0][a[0].length - 1];

    var svg = node
        .attr("width", width + 2 * margin)
        .attr("height", len * barheight + 2 * margin + textbar)
        .append("g").attr("transform", "translate(" + margin + "," + margin + ")");

    for (var i = 0; i <= precision; i += precision_step) {
        svg.append("line")
            .attr("class", "gridline")
            .attr("x1", i / precision * width)
            .attr("x2", i / precision * width)
            .attr("y1", 0)
            .attr("y2", len * barheight);

        svg.append("text").text(i)
            .attr("x", i / precision * width)
            .attr("width", 80)
            .attr("y", len * barheight + textbar);
    }

    var bar = svg.selectAll("g").data(data).enter();

    function line_y(d, i) { return (i + .5) * barheight; }
    function title(d, i) { return d.name + " (" + r10(d[start]) + " to " + r10(d[end]) + ")"; }

    bar.append("line")
        .attr("class", "guide")
        .attr("x1", 0)
        .attr("x2", function(d) { return (precision - Math.max(d[start], d[end])) / precision * width })
        .attr("y1", line_y)
        .attr("y2", line_y);

    var g = bar.append("g").attr("title", title).attr("data-id", function(d){ return d.id })
        .attr("class", function(d) { return d[start] > d[end] ? "arrow good" : "arrow bad" });

    g.append("line")
        .attr("x1", function(d) {return (precision - Math.max(d[start], d[end])) / precision * width})
        .attr("x2", function(d) { return (precision - Math.min(d[start], d[end])) / precision * width })
        .attr("y1", line_y)
        .attr("y2", line_y);

    g.append("g").attr("transform", function(d, i) {
        return "translate(" + ((precision - Math.min(d[start], d[end])) / precision * width) + ", " + line_y(d, i) + ")";
    })
        .append("polygon").attr("points", "0,-3,0,3,5,0");
}

function draw_results(node) {
    window.width = node.attr("width") - 2 * margin;
    d3.json("results.json", function(err, data) {
        if (err) return console.error(err);
        data = data.tests;
        for (var i = 0; i < data.length; i++) {
            data[i].id = i
        }

        data.sort(sort_by("start"));
        make_graph(node, data, "start", "end");

        var length = data.length;
        var BADGES = new Array(length);
        var ARROWS = new Array(length);

        BADGES.container = document.querySelector("#test-badges");
        ARROWS.container = document.querySelector("#graph");

        var arrows = document.querySelectorAll(".arrow");
        for (var i = 0; i < arrows.length; i++) {
            var idx = +arrows[i].attributes["data-id"].value;
            ARROWS[idx] = arrows[i]
        }

        var badges = document.querySelectorAll(".badge");
        for (var i = 0; i < badges.length; i++) {
            var idx = +badges[i].attributes["data-id"].value;
            BADGES[idx] = badges[i]
        }

        function clear() {
            var ho = document.querySelector(".highlight-one");
            if (ho) ho.classList.remove("highlight-one");
            var h = document.querySelector(".highlight");
            if (h) h.classList.remove("highlight");
        }

        for (var i = 0; i < length; i++) {
            (function (i) {
                BADGES[i].addEventListener("mouseover", function() {
                    clear();
                    ARROWS[i].classList.add("highlight");
                    ARROWS.container.classList.add("highlight-one");
                });
                BADGES[i].addEventListener("mouseout", clear);

                ARROWS[i].addEventListener("mouseover", function() {
                    clear();
                    BADGES[i].classList.add("highlight");
                    BADGES.container.classList.add("highlight-one");
                });
                ARROWS[i].addEventListener("mouseout", clear);
            })(i);
        }
    });
}
<commit_msg>Use bit-width info in drawing the arrow plot<commit_after>margin = 10;
barheight = 10;
width = 450;
textbar = 20;
precision = 64;
precision_step = 8;

function sort_by(type) {
    return function(a, b) {
        return b[type] - a[type];
    }
}

function r10(d) {
    return "" + (Math.round(d * 10) / 10);
}

function make_graph(node, data, start, end) {
    var len = data.length;

    var a = d3.selectAll("script");
    var script = a[0][a[0].length - 1];

    var svg = node
        .attr("width", width + 2 * margin)
        .attr("height", len * barheight + 2 * margin + textbar)
        .append("g").attr("transform", "translate(" + margin + "," + margin + ")");

    for (var i = 0; i <= precision; i += precision_step) {
        svg.append("line")
            .attr("class", "gridline")
            .attr("x1", i / precision * width)
            .attr("x2", i / precision * width)
            .attr("y1", 0)
            .attr("y2", len * barheight);

        svg.append("text").text(i)
            .attr("x", i / precision * width)
            .attr("width", 80)
            .attr("y", len * barheight + textbar);
    }

    var bar = svg.selectAll("g").data(data).enter();

    function line_y(d, i) { return (i + .5) * barheight; }
    function title(d, i) { return d.name + " (" + r10(d[start]) + " to " + r10(d[end]) + ")"; }

    bar.append("line")
        .attr("class", "guide")
        .attr("x1", 0)
        .attr("x2", function(d) { return (precision - Math.max(d[start], d[end])) / precision * width })
        .attr("y1", line_y)
        .attr("y2", line_y);

    var g = bar.append("g").attr("title", title).attr("data-id", function(d){ return d.id })
        .attr("class", function(d) { return d[start] > d[end] ? "arrow good" : "arrow bad" });

    g.append("line")
        .attr("x1", function(d) {return (precision - Math.max(d[start], d[end])) / precision * width})
        .attr("x2", function(d) { return (precision - Math.min(d[start], d[end])) / precision * width })
        .attr("y1", line_y)
        .attr("y2", line_y);

    g.append("g").attr("transform", function(d, i) {
        return "translate(" + ((precision - Math.min(d[start], d[end])) / precision * width) + ", " + line_y(d, i) + ")";
    })
        .append("polygon").attr("points", "0,-3,0,3,5,0");
}

function draw_results(node) {
    window.width = node.attr("width") - 2 * margin;
    d3.json("results.json", function(err, data) {
        if (err) return console.error(err);
        precision = data.bit_width;
        precision_step = Math.round(precision / 8);

        data = data.tests;
        for (var i = 0; i < data.length; i++) {
            data[i].id = i
        }

        data.sort(sort_by("start"));
        make_graph(node, data, "start", "end");

        var length = data.length;
        var BADGES = new Array(length);
        var ARROWS = new Array(length);

        BADGES.container = document.querySelector("#test-badges");
        ARROWS.container = document.querySelector("#graph");

        var arrows = document.querySelectorAll(".arrow");
        for (var i = 0; i < arrows.length; i++) {
            var idx = +arrows[i].attributes["data-id"].value;
            ARROWS[idx] = arrows[i]
        }

        var badges = document.querySelectorAll(".badge");
        for (var i = 0; i < badges.length; i++) {
            var idx = +badges[i].attributes["data-id"].value;
            BADGES[idx] = badges[i]
        }

        function clear() {
            var ho = document.querySelector(".highlight-one");
            if (ho) ho.classList.remove("highlight-one");
            var h = document.querySelector(".highlight");
            if (h) h.classList.remove("highlight");
        }

        for (var i = 0; i < length; i++) {
            (function (i) {
                BADGES[i].addEventListener("mouseover", function() {
                    clear();
                    ARROWS[i].classList.add("highlight");
                    ARROWS.container.classList.add("highlight-one");
                });
                BADGES[i].addEventListener("mouseout", clear);

                ARROWS[i].addEventListener("mouseover", function() {
                    clear();
                    BADGES[i].classList.add("highlight");
                    BADGES.container.classList.add("highlight-one");
                });
                ARROWS[i].addEventListener("mouseout", clear);
            })(i);
        }
    });
}
<|endoftext|>


<commit_before>import React from "react";
import { Observable } from "rxjs/Observable";
import { BehaviorSubject } from "rxjs/BehaviorSubject";
import "rxjs/add/operator/switchMap";

// graphqlObservable is our in house implementation of the graphql (not spec compliant yet)
// componentFromStream transforms a stream of data into a React Component
import { componentFromStream, graphqlObservable } from "data-service";

// tools to make easier to create schemas and queries
import { makeExecutableSchema } from "graphql-tools";
import gql from "graphql-tag";

// Streams we get our data from
import {
  liveFetchRepositories
} from "#PLUGINS/catalog/src/js/repositories/repositoriesStream";
import RepositoryList from "#SRC/js/structs/RepositoryList";

// The graphql schema and resolvers for those streams;
import { typeDefs, resolvers } from "./repositoriesModel";

// UI components
import RepositoriesTabUI from "./RepositoriesTabUI";

// Using the data layer

// 1. We first make an schema out of the resolvers and typeDefinitions
const schema = makeExecutableSchema({
  typeDefs,
  resolvers
});

// 2. We declare which data our component needs in graphql (data down)
const packageRepositoryQuery = filter => gql`
  query {
    packageRepository(filter: "${filter}") {
      name
      uri
    }
  }
`;

// 3. We create an event handler as a stream to deal with search (events up)
const searchTerm$ = new BehaviorSubject("");

// 4. We compose the result with the query, and massage it down the pipe until
// we get the props for our component.
// The idea is from an Observable of data and events, compose a stream o React.Components

const packageRepository$ = liveFetchRepositories();

const components$ = searchTerm$
  .switchMap(searchTerm => {
    const query = packageRepositoryQuery(searchTerm);
    const liveQuery = graphqlObservable(query, schema, {
      query: packageRepository$
    }).map(result => {
      // Backwards compatible with the previous struct/RepositoryList for packages
      return new RepositoryList({
        items: result.packageRepository
      });
    });

    return liveQuery.map(packageRepository => {
      return { packageRepository, searchTerm };
    });
  })
  // We map over the data and return a component to render
  .map(data => {
    return (
      <RepositoriesTabUI
        repositories={data.packageRepository}
        searchTerm={data.searchTerm}
        // onSearch has arity 2 which prevents to pass `next` as reference
        onSearch={value => {
          searchTerm$.next(value);
        }}
      />
    );
  })
  // The first component is the loading
  .startWith(<RepositoriesTabUI.Loading />)
  // If anything goes wrong, we render an error component
  .catch(err => {
    console.error(err);

    return Observable.of(<RepositoriesTabUI.Error err={err} />);
  });

// componentFromStream create a single component who render the latest emitted
// component from the stream
const RepositoriesTab = componentFromStream(() => components$);

// the reason we don`t `export componentFromStream()` is so you can add those
// router specific things
RepositoriesTab.routeConfig = {
  label: "Package Repositories",
  matches: /^\/settings\/repositories/
};

module.exports = RepositoriesTab;
<commit_msg>fix: add debounce search<commit_after>import React from "react";
import { Observable } from "rxjs/Observable";
import { BehaviorSubject } from "rxjs/BehaviorSubject";
import "rxjs/add/operator/switchMap";

// graphqlObservable is our in house implementation of the graphql (not spec compliant yet)
// componentFromStream transforms a stream of data into a React Component
import { componentFromStream, graphqlObservable } from "data-service";

// tools to make easier to create schemas and queries
import { makeExecutableSchema } from "graphql-tools";
import gql from "graphql-tag";

// Streams we get our data from
import {
  liveFetchRepositories
} from "#PLUGINS/catalog/src/js/repositories/repositoriesStream";
import RepositoryList from "#SRC/js/structs/RepositoryList";

// The graphql schema and resolvers for those streams;
import { typeDefs, resolvers } from "./repositoriesModel";

// UI components
import RepositoriesTabUI from "./RepositoriesTabUI";

// Using the data layer

// 1. We first make a schema out of the resolvers and typeDefinitions
const schema = makeExecutableSchema({
  typeDefs,
  resolvers
});

// 2. We declare which data our component needs in graphql (data down)
const packageRepositoryQuery = filter => gql`
  query {
    packageRepository(filter: "${filter}") {
      name
      uri
    }
  }
`;

// 3. We create an event handler as a stream to deal with search (events up)
const searchTerm$ = new BehaviorSubject("");

// 4. We compose the result with the query, and massage it down the pipe until
// we get the props for our component.
// The idea is from an Observable of data and events, compose a stream o React.Components

const packageRepository$ = liveFetchRepositories();

const keypressDebounceTime = 250;
const searchResults$ = searchTerm$
  .debounceTime(keypressDebounceTime)
  .switchMap(searchTerm => {
    const query = packageRepositoryQuery(searchTerm);

    return graphqlObservable(query, schema, {
      query: packageRepository$
    }).map(result => {
      // Backwards compatible with the previous struct/RepositoryList for packages
      return new RepositoryList({
        items: result.packageRepository
      });
    });
  });

const components$ = searchTerm$
  .combineLatest(searchResults$, (searchTerm, packageRepository) => {
    return { packageRepository, searchTerm };
  })
  // We map over the data and return a component to render
  .map(data => {
    return (
      <RepositoriesTabUI
        repositories={data.packageRepository}
        searchTerm={data.searchTerm}
        onSearch={value => searchTerm$.next(value)}
      />
    );
  })
  // The first component is the loading
  .startWith(<RepositoriesTabUI.Loading />)
  // If anything goes wrong, we render an error component
  .catch(err => Observable.of(<RepositoriesTabUI.Error err={err} />));

// componentFromStream create a single component who render the latest emitted
// component from the stream
const RepositoriesTab = componentFromStream(() => components$);

// the reason we don`t `export componentFromStream()` is so you can add those
// router specific things
RepositoriesTab.routeConfig = {
  label: "Package Repositories",
  matches: /^\/settings\/repositories/
};

module.exports = RepositoriesTab;
<|endoftext|>


<commit_before>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2015 Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * 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 License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

var _ = require('lodash');
var crossfilter = require('crossfilter');

var sundial = require('sundial');

var classifiersMkr = require('./classifiers');
var constants = require('./constants');

module.exports = function(bgClasses) {

  var classifiers = classifiersMkr(bgClasses);

  return {
    bgDistribution: function(basicsData) {
      var categorizeBg = classifiers.categorizeBg;
      function reshapeAsPercentages(grouped, total) {
        var distributionDefaults = {
          verylow: 0,
          low: 0,
          target: 0,
          high: 0,
          veryhigh: 0
        };
        var reshaped = {};
        for (var key in grouped) {
          var group = grouped[key];
          reshaped[key] = group.length/total;
        }
        return _.defaults(reshaped, distributionDefaults);
      }
      var cgm = basicsData.data.cbg;
      var bgm = basicsData.data.smbg;
      var bgDistribution = {};
      if (cgm) {
        var count = cgm.data.length;
        var spanInDays = (Date.parse(basicsData.dateRange[1]) -
          Date.parse(basicsData.dateRange[0]))/constants.MS_IN_DAY;
        if (count < (constants.CGM_IN_DAY/2 * spanInDays)) {
          bgDistribution.cgmStatus = constants.NOT_ENOUGH_CGM;
        }
        else {
          var categorizedCGM = _.groupBy(cgm.data, categorizeBg);
          bgDistribution.cbg = reshapeAsPercentages(categorizedCGM, count);
          bgDistribution.cgmStatus = constants.CGM_CALCULATED;
        }
      }
      else {
        bgDistribution.cgmStatus = constants.NO_CGM;
      }
      var categorizedBGM = _.groupBy(bgm.data, categorizeBg);
      bgDistribution.smbg = reshapeAsPercentages(categorizedBGM, bgm.data.length);

      return bgDistribution;
    },
    calculateBasalBolusStats: function(basicsData) {
      var basals = basicsData.data.basal.data;
      var boluses = basicsData.data.bolus.data;
      var start = basals[0].normalTime;
      if (start < basicsData.dateRange[0]) {
        start = basicsData.dateRange[0];
      }
      var end = basals[basals.length - 1].normalEnd;
      if (end > basicsData.dateRange[1]) {
        end = basicsData.dateRange[1];
      }

      // find the duration of a basal segment that falls within the basicsData.dateRange
      function getDurationInRange(d) {
        if (d.normalTime >= start && d.normalEnd <= end) {
          return d.duration;
        }
        else if (d.normalTime < start) {
          if (d.normalEnd > start) {
            if (d.normalEnd <= end) {
              return Date.parse(d.normalEnd) - Date.parse(start);
            }
            else {
              return Date.parse(end) - Date.parse(start);
            }
          }
          return 0;
        }
        else if (d.normalEnd > end) {
          if (d.normalTime < end) {
            return Date.parse(end) - Date.parse(d.normalTime);
          }
          return 0;
        }
        else {
          return 0;
        }
      }
      var sumDurations = _.reduce(basals, function(total, d) {
        return total + getDurationInRange(d);
      }, 0);
      var sumBasalInsulin = _.reduce(_.map(basals, function(d) {
        return d.rate * (getDurationInRange(d)/constants.MS_IN_HOUR);
      }), function(total, insulin) {
        return total + insulin;
      });

      var sumBolusInsulin = _.reduce(_.map(boluses, function(d) {
        if (d.normalTime >= start && d.normalTime <= end) {
          return (d.extended || 0) + (d.normal || 0);
        }
        else {
          return 0;
        }
      }), function(total, insulin) {
        return total + insulin;
      });
      var totalInsulin = sumBasalInsulin + sumBolusInsulin;

      return {
        basalBolusRatio: {
          basal: sumBasalInsulin/totalInsulin,
          bolus: sumBolusInsulin/totalInsulin
        },
        totalDailyDose: totalInsulin/((Date.parse(end) - Date.parse(start))/constants.MS_IN_DAY)
      };
    },
    infusionSiteHistory: function(basicsData) {
      var infusionSitesPerDay = basicsData.data.reservoirChange.dataByDate;
      var allDays = basicsData.days;
      var infusionSiteHistory = {};
      // daysSince does *not* start at zero because we have to look back to the
      // most recent infusion site change prior to the basics-restricted time domain
      var priorSiteChange = _.findLast(_.keys(infusionSitesPerDay), function(date) {
        return date < allDays[0].date;
      });
      var daysSince = (Date.parse(allDays[0].date) - Date.parse(priorSiteChange))/constants.MS_IN_DAY - 1;
      _.each(allDays, function(day) {
        if (day.type === 'future') {
          infusionSiteHistory[day.date] = {type: 'future'};
        }
        else {
          daysSince += 1;
          if (infusionSitesPerDay[day.date] && infusionSitesPerDay[day.date].count >= 1) {
            infusionSiteHistory[day.date] = {
              type: constants.SITE_CHANGE,
              count: infusionSitesPerDay[day.date].count,
              data: infusionSitesPerDay[day.date].data,
              daysSince: daysSince
            };
            daysSince = 0;
          }
          else {
            infusionSiteHistory[day.date] = {type: constants.NO_SITE_CHANGE};
          }
        }
      });
      return infusionSiteHistory;
    },
    reduceByDay: function(basicsData) {

      function getLocalDate(d) {
        return sundial.applyOffset(d.time, d.displayOffset).toISOString().slice(0,10);
      }

      function reduceAddMaker(classifier) {
        if (classifier) {
          return function reduceAdd(p, v) {
            var tags = classifier(v);
            if (!_.isEmpty(tags)) {
              ++p.total;
              _.each(tags, function(tag) {
                if (p.subtotals[tag]) {
                  p.subtotals[tag] += 1;
                }
                else {
                  p.subtotals[tag] = 1;
                }
              });
            }
            p.data.push(v);
            return p;
          };
        }
        else {
          return function reduceAdd(p, v) {
            ++p.count;
            p.data.push(v);
            return p;
          };
        }
      }

      function reduceRemoveMaker(classifier) {
        if (classifier) {
          return function reduceRemove(p, v) {
            var tags = classifier(v);
            if (!_.isEmpty(tags)) {
              --p.total;
              _.each(tags, function(tag) {
                p.subtotals[tag] -= 1;
              });
            }
            _.remove(p.data, function(d) {
              return d.id === v.id;
            });
            return p;
          };
        }
        else {
          return function reduceRemove(p, v) {
            --p.count;
            _.remove(p.data, function(d) {
              return d.id === v.id;
            });
            return p;
          };
        }
      }

      function reduceInitialMaker(classifier) {
        if (classifier) {
          return function reduceInitial() {
            return {
              total: 0,
              subtotals: {},
              data: []
            };
          };
        }
        else {
          return function reduceInitial() {
            return {
              count: 0,
              data: []
            };
          };
        }
      }

      function findSectionContainingType(type) {
        return function(section) {
          if (section.column === 'left') {
            return false;
          }
          return section.type === type;
        };
      }

      function reduceTotalByDate(typeObj) {
        return function(p, date) {
          return p + typeObj.dataByDate[date].total;
        };
      }

      function summarizeTag(typeObj, summary, total) {
        return function(tag) {
          summary[tag] = {count: Object.keys(typeObj.dataByDate)
            .reduce(function(p, date) {
              return p + (typeObj.dataByDate[date].subtotals[tag] || 0);
            }, 0)};
          summary[tag].percentage = summary[tag].count/total;
        };
      }

      function findScheduleChangesForDay(dataForDate) {
        var changes = _.compact(_.uniq(_.pluck(dataForDate.data, 'scheduleName'))).length - 1;
        dataForDate.subtotals.scheduleChange = changes < 0 ? 0 : changes;
      }

      var mostRecentDay = _.find(basicsData.days, {type: 'mostRecent'}).date;

      for (var type in basicsData.data) {
        var typeObj = basicsData.data[type];
        if (_.includes(['basal', 'bolus', 'reservoirChange'], type)) {
          typeObj.cf = crossfilter(typeObj.data);
          typeObj.byLocalDate = typeObj.cf.dimension(getLocalDate);
          var classifier = classifiers[type];
          var dataByLocalDate = typeObj.byLocalDate.group().reduce(
            reduceAddMaker(classifier),
            reduceRemoveMaker(classifier),
            reduceInitialMaker(classifier)
          ).all();
          var dataByDateHash = {};
          for (var j = 0; j < dataByLocalDate.length; ++j) {
            var day = dataByLocalDate[j];
            dataByDateHash[day.key] = day.value;
          }
          typeObj.dataByDate = dataByDateHash;
        }
        // because we're disabling this feature for now
        // see comment in state.js
        // if (type === 'basal') {
        //   _.each(typeObj.dataByDate, findScheduleChangesForDay);
        // }
        if (_.includes(['calibration', 'smbg'], type)) {
          if (!basicsData.data.fingerstick) {
            basicsData.data.fingerstick = {};
          }
          basicsData.data.fingerstick[type] = {
            cf: crossfilter(typeObj.data)
          };
          var fsTypeObj = basicsData.data.fingerstick[type];
          fsTypeObj.byLocalDate = fsTypeObj.cf.dimension(getLocalDate);
          var fsDataByLocalDate;
          fsDataByLocalDate = fsTypeObj.byLocalDate.group().reduce(
            reduceAddMaker(classifiers[type]),
            reduceRemoveMaker(classifiers[type]),
            reduceInitialMaker(classifiers[type])
          ).all();
          var fsDataByDateHash = {};
          for (var k = 0; k < fsDataByLocalDate.length; ++k) {
            var fsDay = fsDataByLocalDate[k];
            fsDataByDateHash[fsDay.key] = fsDay.value;
          }
          fsTypeObj.dataByDate = fsDataByDateHash;
        }

        /*
         * This is inelegant but necessary since reduceAdd will only
         * add to the total basal events if there are tags matched for the day.
         * (Schedule changes aren't counted as "tags".)
         */
        // if (type === 'basal') {
        //   _.each(typeObj.dataByDate, function(dateData) {
        //     if (dateData.subtotals.scheduleChange !== 0) {
        //       dateData.total += dateData.subtotals.scheduleChange;
        //     }
        //   });
        // }

        if (_.includes(['basal', 'bolus'], type)) {
          // NB: for basals, the totals and avgPerDay are basal *events*
          // that is, temps, suspends, & maybe schedule changes
          var section = _.find(basicsData.sections, findSectionContainingType(type));
          var tags = _.rest(_.pluck(section.selectorOptions, 'key'));
          var summary = {total: Object.keys(typeObj.dataByDate)
            .reduce(reduceTotalByDate(typeObj), 0)};
          _.each(tags, summarizeTag(typeObj, summary, summary.total));
          var mostRecentTotal = typeObj.dataByDate[mostRecentDay] ?
            typeObj.dataByDate[mostRecentDay].total : 0;
          var numDaysExcludingMostRecent = typeObj.dataByDate[mostRecentDay] ?
            Object.keys(typeObj.dataByDate).length - 1 : Object.keys(typeObj.dataByDate).length;
          // TODO: if we end up using this, do we care that this averages only over # of days that *have* data?
          // e.g., if you have a random day in the middle w/no boluses, that day (that 0) will be excluded from average
          summary.avgPerDay = (summary.total - mostRecentTotal)/numDaysExcludingMostRecent;
          typeObj.summary = summary;
        }
      }

      var fsSection = _.find(basicsData.sections, findSectionContainingType('fingerstick'));
      var fingerstickData = basicsData.data.fingerstick;
      var fsSummary = {total: 0};
      _.each(['calibration', 'smbg'], function(fsCategory) {
        fsSummary[fsCategory] = {total: Object.keys(fingerstickData[fsCategory].dataByDate)
          .reduce(function(p, date) {
            var dateData = fingerstickData[fsCategory].dataByDate[date];
            return p + (dateData.total || dateData.count);
          }, 0)};
        fsSummary.total += fsSummary[fsCategory].total;
      });
      fingerstickData.summary = fsSummary;
      var fsTags = _.pluck(_.filter(fsSection.selectorOptions, function(opt) {
        return opt.path === 'smbg' && !opt.primary;
      }), 'key');

      function summarizeFsTag(typeObj, summary, total) {
        return function(tag) {
          summary.smbg[tag] = {count: Object.keys(typeObj.dataByDate)
            .reduce(function(p, date) {
              return p + (typeObj.dataByDate[date].subtotals[tag] || 0);
            }, 0)};
          summary.smbg[tag].percentage = summary.smbg[tag].count/total;
        };
      }
      _.each(fsTags, summarizeFsTag(fingerstickData.smbg, fsSummary, fsSummary.smbg.total));
      var smbgSummary = fingerstickData.summary.smbg;
      var smbgData = fingerstickData.smbg;
      var mostRecentSmbgTotal = smbgData.dataByDate[mostRecentDay] ?
        smbgData.dataByDate[mostRecentDay].total : 0;
      var numDaysExcludingMostRecentSmbg = smbgData.dataByDate[mostRecentDay] ?
        Object.keys(smbgData.dataByDate).length - 1 : Object.keys(smbgData.dataByDate).length;
      smbgSummary.avgPerDay = (smbgSummary.total - mostRecentSmbgTotal)/numDaysExcludingMostRecentSmbg;
    }
  };
};
<commit_msg>add some if-wrappers for testing convenience<commit_after>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2015 Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * 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 License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

var _ = require('lodash');
var crossfilter = require('crossfilter');

var sundial = require('sundial');

var classifiersMkr = require('./classifiers');
var constants = require('./constants');

module.exports = function(bgClasses) {

  var classifiers = classifiersMkr(bgClasses);

  return {
    bgDistribution: function(basicsData) {
      var categorizeBg = classifiers.categorizeBg;
      function reshapeAsPercentages(grouped, total) {
        var distributionDefaults = {
          verylow: 0,
          low: 0,
          target: 0,
          high: 0,
          veryhigh: 0
        };
        var reshaped = {};
        for (var key in grouped) {
          var group = grouped[key];
          reshaped[key] = group.length/total;
        }
        return _.defaults(reshaped, distributionDefaults);
      }
      var cgm = basicsData.data.cbg;
      var bgm = basicsData.data.smbg;
      var bgDistribution = {};
      if (cgm) {
        var count = cgm.data.length;
        var spanInDays = (Date.parse(basicsData.dateRange[1]) -
          Date.parse(basicsData.dateRange[0]))/constants.MS_IN_DAY;
        if (count < (constants.CGM_IN_DAY/2 * spanInDays)) {
          bgDistribution.cgmStatus = constants.NOT_ENOUGH_CGM;
        }
        else {
          var categorizedCGM = _.groupBy(cgm.data, categorizeBg);
          bgDistribution.cbg = reshapeAsPercentages(categorizedCGM, count);
          bgDistribution.cgmStatus = constants.CGM_CALCULATED;
        }
      }
      else {
        bgDistribution.cgmStatus = constants.NO_CGM;
      }
      var categorizedBGM = _.groupBy(bgm.data, categorizeBg);
      bgDistribution.smbg = reshapeAsPercentages(categorizedBGM, bgm.data.length);

      return bgDistribution;
    },
    calculateBasalBolusStats: function(basicsData) {
      var basals = basicsData.data.basal.data;
      var boluses = basicsData.data.bolus.data;
      var start = basals[0].normalTime;
      if (start < basicsData.dateRange[0]) {
        start = basicsData.dateRange[0];
      }
      var end = basals[basals.length - 1].normalEnd;
      if (end > basicsData.dateRange[1]) {
        end = basicsData.dateRange[1];
      }

      // find the duration of a basal segment that falls within the basicsData.dateRange
      function getDurationInRange(d) {
        if (d.normalTime >= start && d.normalEnd <= end) {
          return d.duration;
        }
        else if (d.normalTime < start) {
          if (d.normalEnd > start) {
            if (d.normalEnd <= end) {
              return Date.parse(d.normalEnd) - Date.parse(start);
            }
            else {
              return Date.parse(end) - Date.parse(start);
            }
          }
          return 0;
        }
        else if (d.normalEnd > end) {
          if (d.normalTime < end) {
            return Date.parse(end) - Date.parse(d.normalTime);
          }
          return 0;
        }
        else {
          return 0;
        }
      }
      var sumDurations = _.reduce(basals, function(total, d) {
        return total + getDurationInRange(d);
      }, 0);
      var sumBasalInsulin = _.reduce(_.map(basals, function(d) {
        return d.rate * (getDurationInRange(d)/constants.MS_IN_HOUR);
      }), function(total, insulin) {
        return total + insulin;
      });

      var sumBolusInsulin = _.reduce(_.map(boluses, function(d) {
        if (d.normalTime >= start && d.normalTime <= end) {
          return (d.extended || 0) + (d.normal || 0);
        }
        else {
          return 0;
        }
      }), function(total, insulin) {
        return total + insulin;
      });
      var totalInsulin = sumBasalInsulin + sumBolusInsulin;

      return {
        basalBolusRatio: {
          basal: sumBasalInsulin/totalInsulin,
          bolus: sumBolusInsulin/totalInsulin
        },
        totalDailyDose: totalInsulin/((Date.parse(end) - Date.parse(start))/constants.MS_IN_DAY)
      };
    },
    infusionSiteHistory: function(basicsData) {
      var infusionSitesPerDay = basicsData.data.reservoirChange.dataByDate;
      var allDays = basicsData.days;
      var infusionSiteHistory = {};
      // daysSince does *not* start at zero because we have to look back to the
      // most recent infusion site change prior to the basics-restricted time domain
      var priorSiteChange = _.findLast(_.keys(infusionSitesPerDay), function(date) {
        return date < allDays[0].date;
      });
      var daysSince = (Date.parse(allDays[0].date) - Date.parse(priorSiteChange))/constants.MS_IN_DAY - 1;
      _.each(allDays, function(day) {
        if (day.type === 'future') {
          infusionSiteHistory[day.date] = {type: 'future'};
        }
        else {
          daysSince += 1;
          if (infusionSitesPerDay[day.date] && infusionSitesPerDay[day.date].count >= 1) {
            infusionSiteHistory[day.date] = {
              type: constants.SITE_CHANGE,
              count: infusionSitesPerDay[day.date].count,
              data: infusionSitesPerDay[day.date].data,
              daysSince: daysSince
            };
            daysSince = 0;
          }
          else {
            infusionSiteHistory[day.date] = {type: constants.NO_SITE_CHANGE};
          }
        }
      });
      return infusionSiteHistory;
    },
    reduceByDay: function(basicsData) {

      function getLocalDate(d) {
        return sundial.applyOffset(d.time, d.displayOffset).toISOString().slice(0,10);
      }

      function reduceAddMaker(classifier) {
        if (classifier) {
          return function reduceAdd(p, v) {
            var tags = classifier(v);
            if (!_.isEmpty(tags)) {
              ++p.total;
              _.each(tags, function(tag) {
                if (p.subtotals[tag]) {
                  p.subtotals[tag] += 1;
                }
                else {
                  p.subtotals[tag] = 1;
                }
              });
            }
            p.data.push(v);
            return p;
          };
        }
        else {
          return function reduceAdd(p, v) {
            ++p.count;
            p.data.push(v);
            return p;
          };
        }
      }

      function reduceRemoveMaker(classifier) {
        if (classifier) {
          return function reduceRemove(p, v) {
            var tags = classifier(v);
            if (!_.isEmpty(tags)) {
              --p.total;
              _.each(tags, function(tag) {
                p.subtotals[tag] -= 1;
              });
            }
            _.remove(p.data, function(d) {
              return d.id === v.id;
            });
            return p;
          };
        }
        else {
          return function reduceRemove(p, v) {
            --p.count;
            _.remove(p.data, function(d) {
              return d.id === v.id;
            });
            return p;
          };
        }
      }

      function reduceInitialMaker(classifier) {
        if (classifier) {
          return function reduceInitial() {
            return {
              total: 0,
              subtotals: {},
              data: []
            };
          };
        }
        else {
          return function reduceInitial() {
            return {
              count: 0,
              data: []
            };
          };
        }
      }

      function findSectionContainingType(type) {
        return function(section) {
          if (section.column === 'left') {
            return false;
          }
          return section.type === type;
        };
      }

      function reduceTotalByDate(typeObj) {
        return function(p, date) {
          return p + typeObj.dataByDate[date].total;
        };
      }

      function summarizeTag(typeObj, summary, total) {
        return function(tag) {
          summary[tag] = {count: Object.keys(typeObj.dataByDate)
            .reduce(function(p, date) {
              return p + (typeObj.dataByDate[date].subtotals[tag] || 0);
            }, 0)};
          summary[tag].percentage = summary[tag].count/total;
        };
      }

      function findScheduleChangesForDay(dataForDate) {
        var changes = _.compact(_.uniq(_.pluck(dataForDate.data, 'scheduleName'))).length - 1;
        dataForDate.subtotals.scheduleChange = changes < 0 ? 0 : changes;
      }

      var mostRecentDay = _.find(basicsData.days, {type: 'mostRecent'}).date;

      for (var type in basicsData.data) {
        var typeObj = basicsData.data[type];
        if (_.includes(['basal', 'bolus', 'reservoirChange'], type)) {
          typeObj.cf = crossfilter(typeObj.data);
          typeObj.byLocalDate = typeObj.cf.dimension(getLocalDate);
          var classifier = classifiers[type];
          var dataByLocalDate = typeObj.byLocalDate.group().reduce(
            reduceAddMaker(classifier),
            reduceRemoveMaker(classifier),
            reduceInitialMaker(classifier)
          ).all();
          var dataByDateHash = {};
          for (var j = 0; j < dataByLocalDate.length; ++j) {
            var day = dataByLocalDate[j];
            dataByDateHash[day.key] = day.value;
          }
          typeObj.dataByDate = dataByDateHash;
        }
        // because we're disabling this feature for now
        // see comment in state.js
        // if (type === 'basal') {
        //   _.each(typeObj.dataByDate, findScheduleChangesForDay);
        // }
        if (_.includes(['calibration', 'smbg'], type)) {
          if (!basicsData.data.fingerstick) {
            basicsData.data.fingerstick = {};
          }
          basicsData.data.fingerstick[type] = {
            cf: crossfilter(typeObj.data)
          };
          var fsTypeObj = basicsData.data.fingerstick[type];
          fsTypeObj.byLocalDate = fsTypeObj.cf.dimension(getLocalDate);
          var fsDataByLocalDate;
          fsDataByLocalDate = fsTypeObj.byLocalDate.group().reduce(
            reduceAddMaker(classifiers[type]),
            reduceRemoveMaker(classifiers[type]),
            reduceInitialMaker(classifiers[type])
          ).all();
          var fsDataByDateHash = {};
          for (var k = 0; k < fsDataByLocalDate.length; ++k) {
            var fsDay = fsDataByLocalDate[k];
            fsDataByDateHash[fsDay.key] = fsDay.value;
          }
          fsTypeObj.dataByDate = fsDataByDateHash;
        }

        /*
         * This is inelegant but necessary since reduceAdd will only
         * add to the total basal events if there are tags matched for the day.
         * (Schedule changes aren't counted as "tags".)
         */
        // if (type === 'basal') {
        //   _.each(typeObj.dataByDate, function(dateData) {
        //     if (dateData.subtotals.scheduleChange !== 0) {
        //       dateData.total += dateData.subtotals.scheduleChange;
        //     }
        //   });
        // }

        if (_.includes(['basal', 'bolus'], type)) {
          // NB: for basals, the totals and avgPerDay are basal *events*
          // that is, temps, suspends, & maybe schedule changes
          var section = _.find(basicsData.sections, findSectionContainingType(type));
          // wrap this in an if mostly for testing convenience
          if (section) {
            var tags = _.rest(_.pluck(section.selectorOptions, 'key'));
            var summary = {total: Object.keys(typeObj.dataByDate)
              .reduce(reduceTotalByDate(typeObj), 0)};
            _.each(tags, summarizeTag(typeObj, summary, summary.total));
            var mostRecentTotal = typeObj.dataByDate[mostRecentDay] ?
              typeObj.dataByDate[mostRecentDay].total : 0;
            var numDaysExcludingMostRecent = typeObj.dataByDate[mostRecentDay] ?
              Object.keys(typeObj.dataByDate).length - 1 : Object.keys(typeObj.dataByDate).length;
            // TODO: if we end up using this, do we care that this averages only over # of days that *have* data?
            // e.g., if you have a random day in the middle w/no boluses, that day (that 0) will be excluded from average
            summary.avgPerDay = (summary.total - mostRecentTotal)/numDaysExcludingMostRecent;
            typeObj.summary = summary;
          }
        }
      }

      function summarizeFsTag(typeObj, summary, total) {
        return function(tag) {
          summary.smbg[tag] = {count: Object.keys(typeObj.dataByDate)
            .reduce(function(p, date) {
              return p + (typeObj.dataByDate[date].subtotals[tag] || 0);
            }, 0)};
          summary.smbg[tag].percentage = summary.smbg[tag].count/total;
        };
      }

      var fsSection = _.find(basicsData.sections, findSectionContainingType('fingerstick'));
      // wrap this in an if mostly for testing convenience
      if (fsSection) {
        var fingerstickData = basicsData.data.fingerstick;
        var fsSummary = {total: 0};
        _.each(['calibration', 'smbg'], function(fsCategory) {
          fsSummary[fsCategory] = {total: Object.keys(fingerstickData[fsCategory].dataByDate)
            .reduce(function(p, date) {
              var dateData = fingerstickData[fsCategory].dataByDate[date];
              return p + (dateData.total || dateData.count);
            }, 0)};
          fsSummary.total += fsSummary[fsCategory].total;
        });
        fingerstickData.summary = fsSummary;
        var fsTags = _.pluck(_.filter(fsSection.selectorOptions, function(opt) {
          return opt.path === 'smbg' && !opt.primary;
        }), 'key');
        _.each(fsTags, summarizeFsTag(fingerstickData.smbg, fsSummary, fsSummary.smbg.total));
        var smbgSummary = fingerstickData.summary.smbg;
        var smbgData = fingerstickData.smbg;
        var mostRecentSmbgTotal = smbgData.dataByDate[mostRecentDay] ?
          smbgData.dataByDate[mostRecentDay].total : 0;
        var numDaysExcludingMostRecentSmbg = smbgData.dataByDate[mostRecentDay] ?
          Object.keys(smbgData.dataByDate).length - 1 : Object.keys(smbgData.dataByDate).length;
        smbgSummary.avgPerDay = (smbgSummary.total - mostRecentSmbgTotal)/numDaysExcludingMostRecentSmbg;
      }
    }
  };
};
<|endoftext|>


<commit_before>const BaseView = require('../BaseView');
const MapMarker = require('../../worldMap/MapMarker');
const MapLine = require('../../worldMap/MapLine');
const MapPolygon = require('../../worldMap/MapPolygon');
const MapObject = require('../../worldMap/MapObject');
const PositionDialog = require('../dialogs/PositionDialog');
const MapCircle = require('../../worldMap/MapCircle');

const positionComposer = require('../../../data/composers/PositionComposer');
const storageManager = require('../../../StorageManager');
const eventHandler = require('../../../EventCentral');
const socketManager = require('../../../SocketManager');
const mouseHandler = require('../../../MouseHandler');
const elementCreator = require('../../../ElementCreator');
const labelHandler = require('../../../labels/LabelHandler');
const viewSwitcher = require('../../../ViewSwitcher');

class WorldMapPage extends BaseView {
  constructor({
    mapStyles,
    polygonStyle,
    lineStyle,
    markerStyle,
    circleStyle,
    labelStyle,
    listId,
    clusterStyle,
    hoverExcludeRule = {},
    triggeredStyles = {},
    choosableStyles = {},
    alwaysShowLabels = {},
    classes = [],
    elementId = `mapView-${Date.now()}`,
    positionTypes = [],
    backgroundColor = '#000000',
    minZoom = 4,
    maxZoom = 15,
    centerCoordinates,
    cornerCoordinates,
  }) {
    super({
      elementId,
      classes: classes.concat(['worldMapPage']),
    });

    this.markers = {};
    this.worldMap = undefined;
    this.clusterer = undefined;
    this.overlay = undefined;
    this.backgroundColor = backgroundColor;
    this.polygonStyle = polygonStyle;
    this.lineStyle = lineStyle;
    this.markerStyle = markerStyle;
    this.circleStyle = circleStyle;
    this.clusterStyle = clusterStyle;
    this.minZoom = minZoom;
    this.maxZoom = maxZoom;
    this.mapStyles = mapStyles;
    this.centerCoordinates = centerCoordinates;
    this.cornerCoordinates = cornerCoordinates;
    this.positionTypes = Object.values(positionComposer.PositionTypes).concat(positionTypes);
    this.listId = listId;
    this.labelStyle = labelStyle;
    this.alwaysShowLabels = alwaysShowLabels;
    this.choosableStyles = choosableStyles;
    this.triggeredStyles = triggeredStyles;
    this.hoverExcludeRule = hoverExcludeRule;

    this.element.appendChild(MapObject.leftClickBox);
    this.element.appendChild(MapObject.rightClickBox);

    eventHandler.addWatcher({
      event: eventHandler.Events.WORLDMAP,
      func: () => {
        this.startMap();
      },
    });

    eventHandler.addWatcher({
      event: eventHandler.Events.SHOW_MAP_CLICK_BOX,
      func: () => {
        if (!document.body.contains(MapObject.leftClickBox)) {
          console.log('clicked box');

          this.element.appendChild(MapObject.leftClickBox);
          this.element.appendChild(MapObject.rightClickBox);
        }
      },
    });
  }

  insertPosition({
    position,
    changeType,
  }) {
    if (!this.worldMap || position.coordinatesHistory.length === 0) {
      return;
    }

    switch (changeType) {
      case socketManager.ChangeTypes.CREATE:
      case socketManager.ChangeTypes.UPDATE: {
        const oldMarker = this.markers[position.objectId];

        if (oldMarker) {
          this.clusterer.removeMarker(oldMarker.mapObject);
          oldMarker.setMap(null);
        }

        const newMarker = this.createMarker({ position });
        this.markers[position.objectId] = newMarker;

        newMarker.setMap(this.worldMap);

        if (newMarker.shouldCluster) {
          this.clusterer.addMarker(newMarker.mapObject);
        }

        break;
      }
      case socketManager.ChangeTypes.REMOVE: {
        const marker = this.markers[position.objectId];

        this.clusterer.removeMarker(marker.mapObject);
        marker.setMap(null);

        this.markers[position.objectId] = undefined;

        break;
      }
      default: {
        break;
      }
    }
  }

  realignMap({
    markers,
    zoomLevel,
    useDefaultCoordinates = false,
  }) {
    const bounds = new google.maps.LatLngBounds();

    if (!useDefaultCoordinates && markers) {
      markers.forEach((marker) => {
        const coordinates = marker.getCenter();

        bounds.extend(new google.maps.LatLng(coordinates.latitude, coordinates.longitude));
      });
    } else {
      const cornerCoordinates = this.cornerCoordinates || {
        upperLeft: storageManager.getCornerOneCoordinates(),
        bottomRight: storageManager.getCornerTwoCoordinates(),
      };

      this.worldMap.setZoom(zoomLevel || this.maxZoom);
      bounds.extend(new google.maps.LatLng(cornerCoordinates.upperLeft.latitude, cornerCoordinates.upperLeft.longitude));
      bounds.extend(new google.maps.LatLng(cornerCoordinates.bottomRight.latitude, cornerCoordinates.bottomRight.longitude));
    }

    this.worldMap.fitBounds(bounds);
    this.worldMap.setCenter(bounds.getCenter());

    if (zoomLevel) {
      this.worldMap.setZoom(zoomLevel);
    }
  }

  /**
   * Create a marker based on its type.
   * @param {Object} params Parameters.
   * @param {Object} params.position Position to create a marker for.
   * @return {Object} The created marker.
   */
  createMarker({ position }) {
    let newMarker;

    switch (position.positionStructure) {
      case positionComposer.PositionStructures.CIRCLE: {
        newMarker = new MapCircle({
          position,
          overlay: this.overlay,
          hoverExcludeRule: this.hoverExcludeRule.circles,
          triggeredStyles: this.triggeredStyles.circles,
          choosableStyles: this.choosableStyles.circles,
          alwaysShowLabel: this.alwaysShowLabels.circle,
          labelStyle: this.labelStyle,
          styles: this.circleStyle,
        });
        break;
      }
      case positionComposer.PositionStructures.LINE: {
        newMarker = new MapLine({
          position,
          overlay: this.overlay,
          hoverExcludeRule: this.hoverExcludeRule.lines,
          triggeredStyles: this.triggeredStyles.lines,
          choosableStyles: this.choosableStyles.lines,
          alwaysShowLabel: this.alwaysShowLabels.line,
          labelStyle: this.labelStyle,
          styles: this.lineStyle,
        });

        break;
      }
      case positionComposer.PositionStructures.POLYGON: {
        newMarker = new MapPolygon({
          position,
          overlay: this.overlay,
          hoverExcludeRule: this.hoverExcludeRule.polygons,
          triggeredStyles: this.triggeredStyles.polygons,
          choosableStyles: this.choosableStyles.polygons,
          alwaysShowLabel: this.alwaysShowLabels.polygon,
          labelStyle: this.labelStyle,
          styles: this.polygonStyle,
        });

        break;
      }
      default: {
        newMarker = new MapMarker({
          position,
          overlay: this.overlay,
          hoverExcludeRule: this.hoverExcludeRule.markers,
          triggeredStyles: this.triggeredStyles.markers,
          choosableStyles: this.choosableStyles.markers,
          alwaysShowLabel: this.alwaysShowLabels.marker,
          labelStyle: this.labelStyle,
          styles: this.markerStyle,
        });

        break;
      }
    }

    return newMarker;
  }

  /**
   * Markers will be created for all available positions and added to the marker collection.
   */
  createMarkers() {
    const positions = positionComposer.getPositions({ positionTypes: this.positionTypes });

    positions.forEach((position) => {
      this.insertPosition({
        position,
        changeType: socketManager.ChangeTypes.CREATE,
      });
    });
  }

  /**
   * Clear and then reinsert all markers to the clusterer.
   */
  resetClusterer() {
    if (!this.clusterer) {
      return;
    }

    this.clusterer.clearMarkers();
    this.clusterer.addMarkers(Object.keys(this.markers)
      .filter(markerId => this.markers[markerId].shouldCluster)
      .map(markerId => this.markers[markerId].mapObject));
  }

  /**
   * Show the right click menu DOM element on the clicked position on the map.
   * @param {Object} params Parameters.
   * @param {Object} params.event Click event.
   */
  showMapRightClickBox({ event }) { // eslint-disable-line
    const {
      CreatePosition = { accessLevel: 1 },
    } = storageManager.getPermissions();
    const { x, y } = event.pixel;
    const items = [];

    /**
     * Add create position item, if the user has access to the command.
     */
    if (storageManager.getAccessLevel() >= CreatePosition.accessLevel) {
      items.push({
        elements: [elementCreator.createSpan({
          text: labelHandler.getLabel({ baseObject: 'MapObject', label: 'createPosition' }),
        })],
        clickFuncs: {
          leftFunc: () => {
            MapObject.hideRightClickBox();

            const dialog = new PositionDialog({
              latitude: event.latLng.lat(),
              longitude: event.latLng.lng(),
            });

            dialog.addToView({ element: viewSwitcher.getParentElement() });
          },
        },
      });
    }

    /**
     * Show menu, if there are any items to show.
     */
    if (items.length > 0) {
      MapObject.showRightClickBox({
        x,
        y,
        container: elementCreator.createContainer({ elements: [elementCreator.createList({ items })] }),
      });
    }
  }

  /**
   * Create the map, clusterer, markers and attach listeners.
   */
  startMap() {
    if (this.worldMap) {
      return;
    }

    if (typeof google === 'undefined' || typeof MarkerClusterer === 'undefined' || typeof MapLabel === 'undefined') {
      setTimeout(this.startMap, 1000);

      return;
    }

    const centerCoordinates = this.centerCoordinates || storageManager.getCenterCoordinates();

    this.worldMap = new google.maps.Map(this.element, {
      disableDefaultUI: true,
      fullscreenControl: false,
      keyboardShortcuts: false,
      mapTypeControl: false,
      noClear: true,
      zoomControl: false,
      panControl: false,
      overviewMapControl: false,
      rotateControl: false,
      scaleControl: false,
      streetViewControl: false,
      center: {
        lat: centerCoordinates.latitude,
        lng: centerCoordinates.longitude,
      },
      zoom: this.defaultZoomLevel,
      backgroundColor: this.backgroundColor,
      minZoom: this.minZoom,
      maxZoom: this.maxZoom,
      styles: this.mapStyles,
    });
    this.clusterer = new MarkerClusterer(this.worldMap, [], this.clusterStyle);
    this.overlay = new google.maps.OverlayView();
    this.overlay.setMap(this.worldMap);

    this.createMarkers({
      worldMap: this.worldMap,
    });

    this.resetClusterer();

    this.realignMap({
      useDefaultCoordinates: true,
      markers: Object.keys(this.markers).map(markerId => this.markers[markerId]),
    });

    mouseHandler.addGMapsClickListener({
      element: this.worldMap,
      leftFunc: () => {
        MapObject.hideLeftClickBox();
        MapObject.hideRightClickBox();
      },
      right: (event) => {
        MapObject.hideLeftClickBox();
        this.showMapRightClickBox({ event });
      },
    });

    google.maps.event.addListener(this.worldMap, 'drag', () => {
      MapObject.hideLeftClickBox();
      MapObject.hideRightClickBox();
    });

    google.maps.event.addListener(this.worldMap, 'zoom_changed', () => {
      eventHandler.emitEvent({
        event: eventHandler.Events.ZOOM_WORLDMAP,
        params: {
          map: this.worldMap,
          zoomLevel: this.worldMap.getZoom() || this.minZoom,
        },
      });
    });

    eventHandler.addWatcher({
      event: eventHandler.Events.FOCUS_MAPPOSITION,
      func: ({
        showDescription,
        origin,
        position,
        zoomLevel,
      }) => {
        const marker = this.markers[position.objectId];

        if (marker && (!this.listId || this.listId === origin)) {
          this.realignMap({
            zoomLevel,
            markers: [marker],
          });
          marker.markPosition({});
        }

        if (showDescription) {
          MapObject.buildLeftClickBox({ position: positionComposer.getPosition({ positionId: position.objectId }) });
        }
      },
    });

    eventHandler.addWatcher({
      event: eventHandler.Events.POSITIONS,
      func: (data) => {
        const {
          changeType,
          positions = [],
        } = data;

        positions.forEach((position) => {
          this.insertPosition({
            position,
            changeType,
          });
        });
      },
    });

    eventHandler.addWatcher({
      event: eventHandler.Events.POSITION,
      func: (data) => {
        const { position, changeType } = data;

        this.insertPosition({
          position,
          changeType,
        });
      },
    });
  }
}

module.exports = WorldMapPage;
<commit_msg>Check if defined<commit_after>const BaseView = require('../BaseView');
const MapMarker = require('../../worldMap/MapMarker');
const MapLine = require('../../worldMap/MapLine');
const MapPolygon = require('../../worldMap/MapPolygon');
const MapObject = require('../../worldMap/MapObject');
const PositionDialog = require('../dialogs/PositionDialog');
const MapCircle = require('../../worldMap/MapCircle');

const positionComposer = require('../../../data/composers/PositionComposer');
const storageManager = require('../../../StorageManager');
const eventHandler = require('../../../EventCentral');
const socketManager = require('../../../SocketManager');
const mouseHandler = require('../../../MouseHandler');
const elementCreator = require('../../../ElementCreator');
const labelHandler = require('../../../labels/LabelHandler');
const viewSwitcher = require('../../../ViewSwitcher');

class WorldMapPage extends BaseView {
  constructor({
    mapStyles,
    polygonStyle,
    lineStyle,
    markerStyle,
    circleStyle,
    labelStyle,
    listId,
    clusterStyle,
    hoverExcludeRule = {},
    triggeredStyles = {},
    choosableStyles = {},
    alwaysShowLabels = {},
    classes = [],
    elementId = `mapView-${Date.now()}`,
    positionTypes = [],
    backgroundColor = '#000000',
    minZoom = 4,
    maxZoom = 15,
    centerCoordinates,
    cornerCoordinates,
  }) {
    super({
      elementId,
      classes: classes.concat(['worldMapPage']),
    });

    this.markers = {};
    this.worldMap = undefined;
    this.clusterer = undefined;
    this.overlay = undefined;
    this.backgroundColor = backgroundColor;
    this.polygonStyle = polygonStyle;
    this.lineStyle = lineStyle;
    this.markerStyle = markerStyle;
    this.circleStyle = circleStyle;
    this.clusterStyle = clusterStyle;
    this.minZoom = minZoom;
    this.maxZoom = maxZoom;
    this.mapStyles = mapStyles;
    this.centerCoordinates = centerCoordinates;
    this.cornerCoordinates = cornerCoordinates;
    this.positionTypes = Object.values(positionComposer.PositionTypes).concat(positionTypes);
    this.listId = listId;
    this.labelStyle = labelStyle;
    this.alwaysShowLabels = alwaysShowLabels;
    this.choosableStyles = choosableStyles;
    this.triggeredStyles = triggeredStyles;
    this.hoverExcludeRule = hoverExcludeRule;

    this.element.appendChild(MapObject.leftClickBox);
    this.element.appendChild(MapObject.rightClickBox);

    eventHandler.addWatcher({
      event: eventHandler.Events.WORLDMAP,
      func: () => {
        this.startMap();
      },
    });

    eventHandler.addWatcher({
      event: eventHandler.Events.SHOW_MAP_CLICK_BOX,
      func: () => {
        if (!document.body.contains(MapObject.leftClickBox)) {
          console.log('clicked box');

          this.element.appendChild(MapObject.leftClickBox);
          this.element.appendChild(MapObject.rightClickBox);
        }
      },
    });
  }

  insertPosition({
    position,
    changeType,
  }) {
    if (!this.worldMap || (position.coordinatesHistory && position.coordinatesHistory.length === 0)) {
      return;
    }

    switch (changeType) {
      case socketManager.ChangeTypes.CREATE:
      case socketManager.ChangeTypes.UPDATE: {
        const oldMarker = this.markers[position.objectId];

        if (oldMarker) {
          this.clusterer.removeMarker(oldMarker.mapObject);
          oldMarker.setMap(null);
        }

        const newMarker = this.createMarker({ position });
        this.markers[position.objectId] = newMarker;

        newMarker.setMap(this.worldMap);

        if (newMarker.shouldCluster) {
          this.clusterer.addMarker(newMarker.mapObject);
        }

        break;
      }
      case socketManager.ChangeTypes.REMOVE: {
        const marker = this.markers[position.objectId];

        this.clusterer.removeMarker(marker.mapObject);
        marker.setMap(null);

        this.markers[position.objectId] = undefined;

        break;
      }
      default: {
        break;
      }
    }
  }

  realignMap({
    markers,
    zoomLevel,
    useDefaultCoordinates = false,
  }) {
    const bounds = new google.maps.LatLngBounds();

    if (!useDefaultCoordinates && markers) {
      markers.forEach((marker) => {
        const coordinates = marker.getCenter();

        bounds.extend(new google.maps.LatLng(coordinates.latitude, coordinates.longitude));
      });
    } else {
      const cornerCoordinates = this.cornerCoordinates || {
        upperLeft: storageManager.getCornerOneCoordinates(),
        bottomRight: storageManager.getCornerTwoCoordinates(),
      };

      this.worldMap.setZoom(zoomLevel || this.maxZoom);
      bounds.extend(new google.maps.LatLng(cornerCoordinates.upperLeft.latitude, cornerCoordinates.upperLeft.longitude));
      bounds.extend(new google.maps.LatLng(cornerCoordinates.bottomRight.latitude, cornerCoordinates.bottomRight.longitude));
    }

    this.worldMap.fitBounds(bounds);
    this.worldMap.setCenter(bounds.getCenter());

    if (zoomLevel) {
      this.worldMap.setZoom(zoomLevel);
    }
  }

  /**
   * Create a marker based on its type.
   * @param {Object} params Parameters.
   * @param {Object} params.position Position to create a marker for.
   * @return {Object} The created marker.
   */
  createMarker({ position }) {
    let newMarker;

    switch (position.positionStructure) {
      case positionComposer.PositionStructures.CIRCLE: {
        newMarker = new MapCircle({
          position,
          overlay: this.overlay,
          hoverExcludeRule: this.hoverExcludeRule.circles,
          triggeredStyles: this.triggeredStyles.circles,
          choosableStyles: this.choosableStyles.circles,
          alwaysShowLabel: this.alwaysShowLabels.circle,
          labelStyle: this.labelStyle,
          styles: this.circleStyle,
        });
        break;
      }
      case positionComposer.PositionStructures.LINE: {
        newMarker = new MapLine({
          position,
          overlay: this.overlay,
          hoverExcludeRule: this.hoverExcludeRule.lines,
          triggeredStyles: this.triggeredStyles.lines,
          choosableStyles: this.choosableStyles.lines,
          alwaysShowLabel: this.alwaysShowLabels.line,
          labelStyle: this.labelStyle,
          styles: this.lineStyle,
        });

        break;
      }
      case positionComposer.PositionStructures.POLYGON: {
        newMarker = new MapPolygon({
          position,
          overlay: this.overlay,
          hoverExcludeRule: this.hoverExcludeRule.polygons,
          triggeredStyles: this.triggeredStyles.polygons,
          choosableStyles: this.choosableStyles.polygons,
          alwaysShowLabel: this.alwaysShowLabels.polygon,
          labelStyle: this.labelStyle,
          styles: this.polygonStyle,
        });

        break;
      }
      default: {
        newMarker = new MapMarker({
          position,
          overlay: this.overlay,
          hoverExcludeRule: this.hoverExcludeRule.markers,
          triggeredStyles: this.triggeredStyles.markers,
          choosableStyles: this.choosableStyles.markers,
          alwaysShowLabel: this.alwaysShowLabels.marker,
          labelStyle: this.labelStyle,
          styles: this.markerStyle,
        });

        break;
      }
    }

    return newMarker;
  }

  /**
   * Markers will be created for all available positions and added to the marker collection.
   */
  createMarkers() {
    const positions = positionComposer.getPositions({ positionTypes: this.positionTypes });

    positions.forEach((position) => {
      this.insertPosition({
        position,
        changeType: socketManager.ChangeTypes.CREATE,
      });
    });
  }

  /**
   * Clear and then reinsert all markers to the clusterer.
   */
  resetClusterer() {
    if (!this.clusterer) {
      return;
    }

    this.clusterer.clearMarkers();
    this.clusterer.addMarkers(Object.keys(this.markers)
      .filter(markerId => this.markers[markerId].shouldCluster)
      .map(markerId => this.markers[markerId].mapObject));
  }

  /**
   * Show the right click menu DOM element on the clicked position on the map.
   * @param {Object} params Parameters.
   * @param {Object} params.event Click event.
   */
  showMapRightClickBox({ event }) { // eslint-disable-line
    const {
      CreatePosition = { accessLevel: 1 },
    } = storageManager.getPermissions();
    const { x, y } = event.pixel;
    const items = [];

    /**
     * Add create position item, if the user has access to the command.
     */
    if (storageManager.getAccessLevel() >= CreatePosition.accessLevel) {
      items.push({
        elements: [elementCreator.createSpan({
          text: labelHandler.getLabel({ baseObject: 'MapObject', label: 'createPosition' }),
        })],
        clickFuncs: {
          leftFunc: () => {
            MapObject.hideRightClickBox();

            const dialog = new PositionDialog({
              latitude: event.latLng.lat(),
              longitude: event.latLng.lng(),
            });

            dialog.addToView({ element: viewSwitcher.getParentElement() });
          },
        },
      });
    }

    /**
     * Show menu, if there are any items to show.
     */
    if (items.length > 0) {
      MapObject.showRightClickBox({
        x,
        y,
        container: elementCreator.createContainer({ elements: [elementCreator.createList({ items })] }),
      });
    }
  }

  /**
   * Create the map, clusterer, markers and attach listeners.
   */
  startMap() {
    if (this.worldMap) {
      return;
    }

    if (typeof google === 'undefined' || typeof MarkerClusterer === 'undefined' || typeof MapLabel === 'undefined') {
      setTimeout(this.startMap, 1000);

      return;
    }

    const centerCoordinates = this.centerCoordinates || storageManager.getCenterCoordinates();

    this.worldMap = new google.maps.Map(this.element, {
      disableDefaultUI: true,
      fullscreenControl: false,
      keyboardShortcuts: false,
      mapTypeControl: false,
      noClear: true,
      zoomControl: false,
      panControl: false,
      overviewMapControl: false,
      rotateControl: false,
      scaleControl: false,
      streetViewControl: false,
      center: {
        lat: centerCoordinates.latitude,
        lng: centerCoordinates.longitude,
      },
      zoom: this.defaultZoomLevel,
      backgroundColor: this.backgroundColor,
      minZoom: this.minZoom,
      maxZoom: this.maxZoom,
      styles: this.mapStyles,
    });
    this.clusterer = new MarkerClusterer(this.worldMap, [], this.clusterStyle);
    this.overlay = new google.maps.OverlayView();
    this.overlay.setMap(this.worldMap);

    this.createMarkers({
      worldMap: this.worldMap,
    });

    this.resetClusterer();

    this.realignMap({
      useDefaultCoordinates: true,
      markers: Object.keys(this.markers).map(markerId => this.markers[markerId]),
    });

    mouseHandler.addGMapsClickListener({
      element: this.worldMap,
      leftFunc: () => {
        MapObject.hideLeftClickBox();
        MapObject.hideRightClickBox();
      },
      right: (event) => {
        MapObject.hideLeftClickBox();
        this.showMapRightClickBox({ event });
      },
    });

    google.maps.event.addListener(this.worldMap, 'drag', () => {
      MapObject.hideLeftClickBox();
      MapObject.hideRightClickBox();
    });

    google.maps.event.addListener(this.worldMap, 'zoom_changed', () => {
      eventHandler.emitEvent({
        event: eventHandler.Events.ZOOM_WORLDMAP,
        params: {
          map: this.worldMap,
          zoomLevel: this.worldMap.getZoom() || this.minZoom,
        },
      });
    });

    eventHandler.addWatcher({
      event: eventHandler.Events.FOCUS_MAPPOSITION,
      func: ({
        showDescription,
        origin,
        position,
        zoomLevel,
      }) => {
        const marker = this.markers[position.objectId];

        if (marker && (!this.listId || this.listId === origin)) {
          this.realignMap({
            zoomLevel,
            markers: [marker],
          });
          marker.markPosition({});
        }

        if (showDescription) {
          MapObject.buildLeftClickBox({ position: positionComposer.getPosition({ positionId: position.objectId }) });
        }
      },
    });

    eventHandler.addWatcher({
      event: eventHandler.Events.POSITIONS,
      func: (data) => {
        const {
          changeType,
          positions = [],
        } = data;

        positions.forEach((position) => {
          this.insertPosition({
            position,
            changeType,
          });
        });
      },
    });

    eventHandler.addWatcher({
      event: eventHandler.Events.POSITION,
      func: (data) => {
        const { position, changeType } = data;

        this.insertPosition({
          position,
          changeType,
        });
      },
    });
  }
}

module.exports = WorldMapPage;
<|endoftext|>


<commit_before>const classNames = require('classnames')
const Breadcrumbs = require('./Breadcrumbs')
const Filter = require('./Filter')
const Table = require('./ItemList')
const { h } = require('preact')

module.exports = (props) => {
  let filteredFolders = props.folders
  let filteredFiles = props.files

  if (props.filterInput !== '') {
    filteredFolders = props.filterItems(props.folders)
    filteredFiles = props.filterItems(props.files)
  }

  return (
    <div class={classNames('uppy-ProviderBrowser', `uppy-ProviderBrowser-viewType--${props.viewType}`)}>

{props.pluginIcon && props.pluginIcon()}

          {props.showBreadcrumbs && Breadcrumbs({
            getFolder: props.getFolder,
            directories: props.directories,
            title: props.title
          })}
          {props.username}
          <button type="button" onclick={props.logout} class="uppy-ProviderBrowser-userLogout">Log out</button>

      { props.showFilter && <Filter {...props} /> }
      {Table({
        columns: [{
          name: 'Name',
          key: 'title'
        }],
        folders: filteredFolders,
        files: filteredFiles,
        activeRow: props.isActiveRow,
        sortByTitle: props.sortByTitle,
        sortByDate: props.sortByDate,
        isChecked: props.isChecked,
        handleFolderClick: props.getNextFolder,
        toggleCheckbox: props.toggleCheckbox,
        getItemName: props.getItemName,
        getItemIcon: props.getItemIcon,
        handleScroll: props.handleScroll,
        title: props.title,
        showTitles: props.showTitles,
        getItemId: props.getItemId,
        i18n: props.i18n
      })}

        <button class="uppy-u-reset uppy-c-btn uppy-c-btn-primary" onclick={props.done}>
          Select
        </button>
        <button class="uppy-u-reset uppy-c-btn uppy-c-btn-link" onclick={props.cancel}>
          Cancel
        </button>

  )
}
<commit_msg>Clear selection when provider tab is closed too<commit_after>const classNames = require('classnames')
const Breadcrumbs = require('./Breadcrumbs')
const Filter = require('./Filter')
const Table = require('./ItemList')
const FooterActions = require('./FooterActions')
const { h, Component } = require('preact')

module.exports = class Browser extends Component {
  componentWillUnmount () {
    this.props.cancel()
  }

  render (props) {
    let filteredFolders = props.folders
    let filteredFiles = props.files

    if (props.filterInput !== '') {
      filteredFolders = props.filterItems(props.folders)
      filteredFiles = props.filterItems(props.files)
    }

    const selected = props.currentSelection.length

    return (
      <div class={classNames('uppy-ProviderBrowser', `uppy-ProviderBrowser-viewType--${props.viewType}`)}>

{props.pluginIcon && props.pluginIcon()}

            {props.showBreadcrumbs && <Breadcrumbs
              getFolder={props.getFolder}
              directories={props.directories}
              title={props.title} />
            }
            <button type="button" onclick={props.logout} class="uppy-ProviderBrowser-userLogout">
              {props.i18n('logOut')}
            </button>

        { props.showFilter && <Filter {...props} /> }
        <Table
          columns={[{
            name: 'Name',
            key: 'title'
          }]}
          folders={filteredFolders}
          files={filteredFiles}
          activeRow={props.isActiveRow}
          sortByTitle={props.sortByTitle}
          sortByDate={props.sortByDate}
          isChecked={props.isChecked}
          handleFolderClick={props.getNextFolder}
          toggleCheckbox={props.toggleCheckbox}
          getItemName={props.getItemName}
          getItemIcon={props.getItemIcon}
          handleScroll={props.handleScroll}
          title={props.title}
          showTitles={props.showTitles}
          getItemId={props.getItemId}
          i18n={props.i18n}
        />
        {selected > 0 && <FooterActions selected={selected} {...props} />}

    )
  }
}
<|endoftext|>


<commit_before>E2.p = E2.plugins["aspect_ratio_generator"] = function(core, node)
{
	this.desc = 'Emits current renderer aspect ratio (width / height).';

	this.input_slots = [];

	this.output_slots = [ { name: 'aspect', dt: core.datatypes.FLOAT, desc: 'The current renderer aspect ratio.' } ];

	this.canvas = core.renderer.canvas;
};

E2.p.prototype.reset = function()
{
};

E2.p.prototype.update_output = function(slot)
{
	var c = this.canvas;

	// TODO: We've *got* to get rid of this. It locks up the UI thread badly.
	return c.width() / c.height();
};
<commit_msg>Optimization of the aspect ratio plugin: Removed dependence of jQuery to improve speed of aspect ratio calculation and minimize UI thread lockups.<commit_after>E2.p = E2.plugins["aspect_ratio_generator"] = function(core, node)
{
	this.desc = 'Emits current renderer aspect ratio (width / height).';

	this.input_slots = [];

	this.output_slots = [ { name: 'aspect', dt: core.datatypes.FLOAT, desc: 'The current renderer aspect ratio.' } ];

	this.canvas = core.renderer.canvas[0];
};

E2.p.prototype.reset = function()
{
};

E2.p.prototype.update_output = function(slot)
{
	var c = this.canvas;

	return c.width / c.height;
};
<|endoftext|>