<commit_before>import isRootNode from './is-root-node';
import isSlotNode from './is-slot-node';

export default function(root) {
  const slots = [];

  // IE complains if you call nextNode() on a tree walker that isn't an element.
  if (root.nodeType !== Node.ELEMENT_NODE) {
    return [];
  }

  // IE requies the last argument, so we must provide both the 3rd and last.
  const walker = document.createTreeWalker(root, Node.SHOW_ELEMENT, null, false);
  let inDescendantRoot = false;

  while (walker.nextNode()) {
    const { currentNode } = walker;
    const isCurrentSlot = isSlotNode(currentNode);

    if (inDescendantRoot && isCurrentSlot) {
      inDescendantRoot = false;
      continue;
    }

    if (isRootNode(currentNode)) {
      inDescendantRoot = true;
      continue;
    }

    if (!inDescendantRoot && isCurrentSlot) {
      slots.push(currentNode);
    }
  }

  return slots;
}
<commit_msg>fix(slotting): Node.SHOW_ELEMENT -> NodeFilter.SHOW_ELEMENT<commit_after>import isRootNode from './is-root-node';
import isSlotNode from './is-slot-node';

export default function(root) {
  const slots = [];

  // IE complains if you call nextNode() on a tree walker that isn't an element.
  if (root.nodeType !== Node.ELEMENT_NODE) {
    return [];
  }

  // IE requies the last argument, so we must provide both the 3rd and last.
  const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, null, false);
  let inDescendantRoot = false;

  while (walker.nextNode()) {
    const { currentNode } = walker;
    const isCurrentSlot = isSlotNode(currentNode);

    if (inDescendantRoot && isCurrentSlot) {
      inDescendantRoot = false;
      continue;
    }

    if (isRootNode(currentNode)) {
      inDescendantRoot = true;
      continue;
    }

    if (!inDescendantRoot && isCurrentSlot) {
      slots.push(currentNode);
    }
  }

  return slots;
}
<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');
var Reflux = require('reflux');
var moment = require('moment');

var RegionStore = require('stores/RegionStore');
var CampaignStore = require('stores/CampaignStore');

var api = require('data/api');
var builtins = require('dashboard/builtin');

var NavigationStore = Reflux.createStore({
  init: function () {
    this.campaigns = [];
    this.dashboards = [];
    this.customDashboards = [];
    this.loaded = false;

    Promise.all([
      CampaignStore.getCampaignsPromise(),
      api.office()
    ]).then(_.spread(this._loadDashboards));
  },

  getInitialState: function () {
    return {
      campaigns: this.campaigns,
      dashboards: this.dashboards,
      documents: this.documents,
      loaded: this.loaded
    };
  },

  // API
  getDashboard: function (slug) {
    var dashboard = _.find(this.dashboards, d => _.kebabCase(d.title) === slug)
    if (dashboard.id <= 0) {
      return new Promise(resolve => {
        resolve(dashboard)
      })
    } else {
      return api.get_chart({dashboard_id: dashboard.id}, null, {'cache-control': 'no-cache'}).then(res => {
        dashboard.charts = res.objects.map(chart => {
          var result = chart.chart_json;
          result.id = chart.id;
          return result;
        });
        return dashboard
      }, function (err) {
        console.log(err);
        dashboard.charts = [];
      });
    }
  },

  // Helpers
  _loadDashboards: function (campaigns, offices) {
    var allDashboards = builtins;

    campaigns = _(campaigns);
    var chainOffices = _(offices.objects);

    this.dashboards = _(allDashboards)
      .map(function (d) {
        // Take the first location alphabetically at the highest geographic level
        // available as the default location for this dashboard
        var location = chainOffices.min(_.property('id'));

        // Find the latest campaign for the chosen location
        var campaign = campaigns
          .filter(c => {
            return location.id === c.office_id;
          })
          .max(c => {
            return moment(c.start_date, 'YYYY-MM-DD').valueOf()
          });

        // Build the path for the dashboard
        var path = '';
        try {
          path = '/' + location.name + '/' + moment(campaign.start_date, 'YYYY-MM-DD').format('YYYY/MM');
        } catch (err) {
          path = '/'
        }
        // Patch the non-comformant API response
        d.charts = d.charts || d.dashboard_json;

        return _.assign({}, d, {
          href: '/datapoints/' + _.kebabCase(d.title) + path
        });
      })
      .reject(_.isNull)
      .value();

    this.campaigns = campaigns
      .map(c => {
        var m = moment(c.start_date, 'YYYY-MM-DD');
        var dt = m.format('YYYY/MM');
        var officeName = _.indexBy(offices.objects, 'id')[c.office_id].name;
        var title = officeName + ': ' + m.format('MMMM YYYY');

        var links = _.map(allDashboards, function (d) {
          return _.defaults({
            path: _.kebabCase(d.title) + '/' + officeName + '/' + dt
          }, d);
        });

        return _.defaults({
          title: title,
          dashboards: links
        }, c);
      });

    this.loaded = true;

    this.trigger({
      dashboards: this.dashboards,
      campaigns: this.campaigns,
      loaded: this.loaded
    });
  }
});

module.exports = NavigationStore;
<commit_msg>remove unused requirement<commit_after>'use strict';

var _ = require('lodash');
var Reflux = require('reflux');
var moment = require('moment');

var CampaignStore = require('stores/CampaignStore');

var api = require('data/api');
var builtins = require('dashboard/builtin');

var NavigationStore = Reflux.createStore({
  init: function () {
    this.campaigns = [];
    this.dashboards = [];
    this.customDashboards = [];
    this.loaded = false;

    Promise.all([
      CampaignStore.getCampaignsPromise(),
      api.office()
    ]).then(_.spread(this._loadDashboards));
  },

  getInitialState: function () {
    return {
      campaigns: this.campaigns,
      dashboards: this.dashboards,
      documents: this.documents,
      loaded: this.loaded
    };
  },

  // API
  getDashboard: function (slug) {
    var dashboard = _.find(this.dashboards, d => _.kebabCase(d.title) === slug)
    if (dashboard.id <= 0) {
      return new Promise(resolve => {
        resolve(dashboard)
      })
    } else {
      return api.get_chart({dashboard_id: dashboard.id}, null, {'cache-control': 'no-cache'}).then(res => {
        dashboard.charts = res.objects.map(chart => {
          var result = chart.chart_json;
          result.id = chart.id;
          return result;
        });
        return dashboard
      }, function (err) {
        console.log(err);
        dashboard.charts = [];
      });
    }
  },

  // Helpers
  _loadDashboards: function (campaigns, offices) {
    var allDashboards = builtins;

    campaigns = _(campaigns);
    var chainOffices = _(offices.objects);

    this.dashboards = _(allDashboards)
      .map(function (d) {
        // Take the first location alphabetically at the highest geographic level
        // available as the default location for this dashboard
        var location = chainOffices.min(_.property('id'));

        // Find the latest campaign for the chosen location
        var campaign = campaigns
          .filter(c => {
            return location.id === c.office_id;
          })
          .max(c => {
            return moment(c.start_date, 'YYYY-MM-DD').valueOf()
          });

        // Build the path for the dashboard
        var path = '';
        try {
          path = '/' + location.name + '/' + moment(campaign.start_date, 'YYYY-MM-DD').format('YYYY/MM');
        } catch (err) {
          path = '/'
        }
        // Patch the non-comformant API response
        d.charts = d.charts || d.dashboard_json;

        return _.assign({}, d, {
          href: '/datapoints/' + _.kebabCase(d.title) + path
        });
      })
      .reject(_.isNull)
      .value();

    this.campaigns = campaigns
      .map(c => {
        var m = moment(c.start_date, 'YYYY-MM-DD');
        var dt = m.format('YYYY/MM');
        var officeName = _.indexBy(offices.objects, 'id')[c.office_id].name;
        var title = officeName + ': ' + m.format('MMMM YYYY');

        var links = _.map(allDashboards, function (d) {
          return _.defaults({
            path: _.kebabCase(d.title) + '/' + officeName + '/' + dt
          }, d);
        });

        return _.defaults({
          title: title,
          dashboards: links
        }, c);
      });

    this.loaded = true;

    this.trigger({
      dashboards: this.dashboards,
      campaigns: this.campaigns,
      loaded: this.loaded
    });
  }
});

module.exports = NavigationStore;
<|endoftext|>


<commit_before>module.exports = {
	rules: {
		'body-leading-blank': [1, 'always'],
		'footer-leading-blank': [1, 'always'],
		'header-max-length': [2, 'always', 72],
		'scope-case': [2, 'always', 'lower-case'],
		'subject-case': [
			2,
			'never',
			['sentence-case', 'start-case', 'pascal-case', 'upper-case']
		],
		'subject-empty': [2, 'never'],
		'subject-full-stop': [2, 'never', '.'],
		'type-case': [2, 'always', 'lower-case'],
		'type-empty': [2, 'never'],
		'type-enum': [
			2,
			[
				'build',
				'chore',
				'ci',
				'docs',
				'feat',
				'fix',
				'perf',
				'refactor',
				'revert',
				'style',
				'test'
			]
		]
	}
};
<commit_msg>fix(config-conventional): add missing applicable attribute to type-enum<commit_after>module.exports = {
	rules: {
		'body-leading-blank': [1, 'always'],
		'footer-leading-blank': [1, 'always'],
		'header-max-length': [2, 'always', 72],
		'scope-case': [2, 'always', 'lower-case'],
		'subject-case': [
			2,
			'never',
			['sentence-case', 'start-case', 'pascal-case', 'upper-case']
		],
		'subject-empty': [2, 'never'],
		'subject-full-stop': [2, 'never', '.'],
		'type-case': [2, 'always', 'lower-case'],
		'type-empty': [2, 'never'],
		'type-enum': [
			2,
			'always',
			[
				'build',
				'chore',
				'ci',
				'docs',
				'feat',
				'fix',
				'perf',
				'refactor',
				'revert',
				'style',
				'test'
			]
		]
	}
};
<|endoftext|>


<commit_before>const getRepoFiles = require('./gitGetRepoFiles.js'),
getFileLogs = require('./gitGetFileLogs.js')

let getHotspots = function (settings, resolve, reject) {
    let resolveFilePromises = function(filesPromises) {
        return Promise.all(filesPromises)
    }

    let sortFiles = function(filesCount) {
        filesCount.sort(function (a, b) {
            a = a.count
            b = b.count

            return a > b ? -1 : (a < b ? 1 : 0)
        })

        return filesCount
    }

    let spliceFiles = function(filesCount) {
        if (settings.amount !== null) {
            filesCount.splice(settings.amount, filesCount.length - settings.amount)
        }

        return filesCount
    }

    getRepoFiles(settings.repo)
    .then(files => getFileLogs(files, settings))
    .then(filesPromises => resolveFilePromises(filesPromises))
    .then(filesCount => sortFiles(filesCount))
    .then(filesCount => spliceFiles(filesCount))
    .then(filesCount => resolve(filesCount))
    .catch(error => reject(error))
}

module.exports = getHotspots<commit_msg>added removing of undefined (filtered out) files<commit_after>const getRepoFiles = require('./gitGetRepoFiles.js'),
getFileLogs = require('./gitGetFileLogs.js')

let getHotspots = function (settings, resolve, reject) {
    let resolveFilePromises = function(filesPromises) {
        return Promise.all(filesPromises)
    }

    let sortFiles = function(filesCount) {
        filesCount.sort(function (a, b) {
            a = a.count
            b = b.count

            return a > b ? -1 : (a < b ? 1 : 0)
        })

        return filesCount
    }

    let spliceFiles = function(filesCount) {
        if (settings.amount !== null) {
            filesCount.splice(settings.amount, filesCount.length - settings.amount)
        }

        return filesCount
    }

    let filterUndefined = function (filesCount) {
        filesCount = filesCount.filter(element => element !== undefined)

        return filesCount;
    }

    getRepoFiles(settings.repo)
    .then(files => getFileLogs(files, settings))
    .then(filesPromises => resolveFilePromises(filesPromises))
    .then(filesCount => sortFiles(filesCount))
    .then(filesCount => spliceFiles(filesCount))
    .then(filesCount => filterUndefined(filesCount))
    .then(filesCount => resolve(filesCount))
    .catch(error => reject(error))
}

module.exports = getHotspots<|endoftext|>


<commit_before>import d3 from "d3";

/* util */

export const pathStringGenerator = d3.svg.line()
  .x((d) => { return d.x })
  .y((d) => { return d.y })
  .interpolate("basis");

export const drawDemesAndTransmissions = (latLongs, colorScale, g, map, nodes) => {

  // define markers that are appended to the definition part of the group
  let markerCount=0;
  const makeMarker = function (d){
    markerCount++;
    // console.log(markerCount, d);
    const mID = "marker"+markerCount.toString();
    g.append("defs").selectAll("marker")
      .data([mID])
      .enter().append("marker")
        .attr("id", mID)
        .attr("viewBox", "0 -5 10 10")
        .attr("refX", 0)
        .attr("refY", 0)
        // the next two lines set the marker size relative to the stroke-width
        .attr("markerWidth",  (x) => { return Math.max(1.5, 7.0 / d.data.total); })
        .attr("markerHeight", (x) => { return Math.max(1.5, 7.0 / d.data.total); })
        .attr("orient", "auto")
      .append("path")
        .attr("d", "M0,-5L10,0L0,5")
        .attr("stroke-width",0)
        .attr("fill-opacity",0.5)
        .attr("fill", function(){return d.data.color;});
    return "url(#"+mID+")";
  }

  /* we're ditching geodesic. That means we don't have inner parts anymore.
    538 actually solved that... http://bl.ocks.org/bycoffe/18441cddeb8fe147b719fab5e30b5d45
    then we want to deeplink to animation state... which this may help with: http://jsfiddle.net/henbox/b4bbgdnz/5/
  */
  // add transmission lines with mid markers at each inner point of the path

  const transmissions = g.selectAll("transmissions")
    .data(latLongs.transmissions)
    .enter()
    .append("path") /* instead of appending a geodesic path from the leaflet plugin data, we now draw a line directly between two points */
    .attr("d", (d) => {
      return pathStringGenerator(
        // extractLineSegmentForAnimationEffect(d.data.originToDestinationXYs)
        d.data.originToDestinationXYs
      )
    }) /* with the interpolation in the function above pathStringGenerator */
    .attr("fill","none")
    .attr("stroke-opacity", .6)
    .attr("stroke-linecap", "round")
    .attr("stroke", (d) => { return d.data.color }) /* colorScale(d.data.from); color path by contry in which the transmission arrived */
    .attr("stroke-width", (d) => { return d.data.total }) /* scale line by total number of transmissions */
    // .attr("marker-mid", makeMarker);

    let transmissionPathLengths = [];
    transmissions[0].forEach((d, i) => {

      /* https://developer.mozilla.org/en-US/docs/Web/API/SVGGeometryElement/getTotalLength */
      const totalPathLength = d.getTotalLength();

      /*
        1. Here, we make a mapping between time and geographic position for the transmission.
        2. In short, make the line visible in proportion to the user selected date range, which
            may not include the entire length of the line.
        3. .clamp(true)
            never return a value outside the date range
            this would put the transmission path outside the geographic target
      */
      const pathScale = d3.scale.linear()
                                .domain([
                                  nodes[latLongs.transmissions[i].data.demePairIndices[0]].attr.num_date, /* origin date */
                                  nodes[latLongs.transmissions[i].data.demePairIndices[1]].attr.num_date /* destination date */
                                ])
                                .range([0, totalPathLength])
                                .clamp(true);

      transmissionPathLengths.push({
        totalPathLength,
        pathScale,
      })
    })

  const demes = g.selectAll("demes")
    .data(latLongs.demes)
    .enter().append("circle")
    .style("stroke", "none")
    .style("fill-opacity", .6)
    .style("fill", (d) => { return d.color })
    .attr("r", (d) => { return 0 + Math.sqrt(d.total) * 4 })
    .attr("transform", (d) => {
      return "translate(" + d.coords.x + "," + d.coords.y + ")";
    });

  return {
    demes,
    transmissions,
    transmissionPathLengths
  };

}

export const updateOnMoveEnd = (d3elems, latLongs) => {
  /* map has moved or rescaled, make demes and transmissions line up */
  if (d3elems) {
    d3elems.demes
      .data(latLongs.demes)
      .attr("transform", (d) => {
        return "translate(" + d.coords.x + "," + d.coords.y + ")";
      })

    d3elems.transmissions
      .data(latLongs.transmissions)
      .attr("d", (d) => { return pathStringGenerator(d.data.originToDestinationXYs) })
  }
}

const getPointForLineSegment = (originX, originY, destinationX, destinationY, dist) => {
    const rad = Math.atan2(destinationY-originY, destinationX-originX);
    const newX = originX + dist * Math.cos(rad);
    const newY = originY + dist * Math.sin(rad);
    return {x: newX, y: newY}
}

const extractLineSegmentForAnimationEffect = (pair, controls, d, nodes, d3elems, i) => {

  if (!nodes[d.data.demePairIndices[0]]) { console.warn("No node found for this index, which is needed to compare user dates to transmission origin/destination dates, so we're returning the default x y pair. No smaller, interior line will appear. This occurred because the index accessor for the node array returned from getLatLongs was not a valid value. Try a console log upstream of where demePairIndices is returned from src/util/mapHelpersLatLong.js"); return pair; } /* handle weird data */

  const originDate = nodes[d.data.demePairIndices[0]].attr.num_date;
  const destinationDate = nodes[d.data.demePairIndices[1]].attr.num_date;
  const userDateMin = controls.dateScale(controls.dateFormat.parse(controls.dateMin));
  const userDateMax = controls.dateScale(controls.dateFormat.parse(controls.dateMax));

  /*
    entire line is either visible or invisible, so return pair
  */
  if (
    userDateMin < originDate && userDateMax > destinationDate || /* visible ie., user: jan-dec 2015 origin/dest june-july 2015, so completely within */
    userDateMin > destinationDate || /* invisible ie., user: jan-dec 2015 dest: feb 1980 */
    userDateMax < originDate /* invisibile ie., user: jan-dec 2015 origin: feb 2017 */
  ) {
    return pair;
  } else {

    const scaledOriginDist = d3elems.transmissionPathLengths[i].pathScale(userDateMin);
    const scaledDestinationDist = d3elems.transmissionPathLengths[i].pathScale(userDateMax);
    const scaledOriginXY = getPointForLineSegment(pair[0].x, pair[0].y, pair[1].x, pair[1].y, scaledOriginDist);
    const scaledDestinationXY = getPointForLineSegment(pair[0].x, pair[0].y, pair[1].x, pair[1].y, scaledDestinationDist);

    // console.log(i + " is " + nodes[d.data.demePairIndices[0]].attr.country + " to " + nodes[d.data.demePairIndices[1]].attr.country)
    // console.log(i + "---" + originDate + "---" + userDateMin + "---" + userDateMax + "---" + destinationDate);
    // console.log(i + "=== totalPathLength ==== " + d3elems.transmissionPathLengths[i].totalPathLength)
    // console.log(i + "=== origin =============", pair[0].x, pair[0].y)
    // console.log(i + "=== destination ========", pair[1].x, pair[1].y)
    // console.log(i + "=== scaled origin distance ======", scaledOriginDist)
    // console.log(i + "=== scaled origin x,y ======", scaledOriginXY)
    // console.log(i + "=== scaled destination distance ======", scaledDestinationDist)
    // console.log(i + "=== scaled destination x,y ======", scaledDestinationXY)

  }
    return [scaledOriginXY, scaledDestinationXY]
  }
}

export const updateVisibility = (d3elems, latLongs, controls, nodes) => {

  d3elems.demes
    .data(latLongs.demes)
    .transition(5)
    .style("fill", (d) => { return d.total > 0 ? d.color : "white" })
    .attr("r", (d) => {
      return 0 + Math.sqrt(d.total) * 4
    });

    console.log("UPDATING ====================================================")

  d3elems.transmissions
    .data(latLongs.transmissions)
    // .transition(5)
    .attr("d", (d, i) => {
      return pathStringGenerator(
        extractLineSegmentForAnimationEffect(
          d.data.originToDestinationXYs,
          controls,
          d,
          nodes,
          d3elems,
          i
        )
      )
    }) /* with the interpolation in the function above pathStringGenerator */
    .attr("stroke", (d) => { return d.data.total > 0 ? d.data.color : "white" })
    .attr("stroke-width", (d) => {
      return d.data.total
    })

}

/* template for an update helper */
export const updateFoo = (d3elems, latLongs) => {
  d3elems.demes
    .data(latLongs.demes)

  d3elems.transmissions
    .data(latLongs.transmissions)
}

// const translateAlong = (path) => {
//   var totalPathLength = path.getTotalLength();
//   return (datum, index) => {
//     return (t) => {
//       var p = path.getPointAtLength(t * totalPathLength);
//       return "translate(" + p.x + "," + p.y + ")";
//     };
//   };
// };

// const missiles = transmissionPaths.map((transmissionPath) => {
//
//   // console.log(transmissionPath)
//
//   const missile = g.append("circle")
//     .attr("r", 0)
//     .attr("fill", (d) => { return colorScale(transmissionPath.transmission.to) })
//     .attr("transform", `translate(
//       ${transmissionPath.partialTransmission[0].x},
//       ${transmissionPath.partialTransmission[0].y}
//     )`) /* begin the missile on the start of the line */
//     // .transition()
//     // .duration(5000)
//     // .attrTween("transform", translateAlong(transmissionPath.elem.node()));
//
//   return missile;
// })

// const setTipCoords = () => {
//   demes.attr("transform", (d) => {
//     return "translate(" + d.coords.x + "," + d.coords.y + ")";
//   });
// };

// const animateTransmissions = () => {
//   /* point along path interpolation https://bl.ocks.org/mbostock/1705868 */
//
// }

// setTipCoords();
// map.on("viewreset", setTipCoords); /* search: -Note (A) for an idea as to why this might not be working properly */
// animateTransmissions();

/*
  http://gis.stackexchange.com/questions/49114/d3-geo-path-to-draw-a-path-from-gis-coordinates
  https://bl.ocks.org/mbostock/3916621  // Compute point-interpolators at each distance.
  http://bl.ocks.org/mbostock/5851933 // draw line on map
  https://gist.github.com/mikeatlas/0b69b354a8d713989147 // polyline split if we don't use leaflet
  http://bl.ocks.org/mbostock/5928813
  http://bl.ocks.org/duopixel/4063326 // animate path in
  https://bl.ocks.org/mbostock/1705868 // point along path interpolation
  https://bl.ocks.org/mbostock/1313857 // point along path interpolation
  https://github.com/d3/d3-shape/blob/master/README.md#curves
  https://github.com/d3/d3-shape/blob/master/README.md#lines
*/

// since we're now using d3 we'll probably do something like http://stackoverflow.com/questions/11808860/arrow-triangles-on-my-svg-line/11809868#11809868
// decorator docs: https://github.com/bbecquet/Leaflet.PolylineDecorator
<commit_msg>Fix typo<commit_after>import d3 from "d3";

/* util */

export const pathStringGenerator = d3.svg.line()
  .x((d) => { return d.x })
  .y((d) => { return d.y })
  .interpolate("basis");

export const drawDemesAndTransmissions = (latLongs, colorScale, g, map, nodes) => {

  // define markers that are appended to the definition part of the group
  let markerCount=0;
  const makeMarker = function (d){
    markerCount++;
    // console.log(markerCount, d);
    const mID = "marker"+markerCount.toString();
    g.append("defs").selectAll("marker")
      .data([mID])
      .enter().append("marker")
        .attr("id", mID)
        .attr("viewBox", "0 -5 10 10")
        .attr("refX", 0)
        .attr("refY", 0)
        // the next two lines set the marker size relative to the stroke-width
        .attr("markerWidth",  (x) => { return Math.max(1.5, 7.0 / d.data.total); })
        .attr("markerHeight", (x) => { return Math.max(1.5, 7.0 / d.data.total); })
        .attr("orient", "auto")
      .append("path")
        .attr("d", "M0,-5L10,0L0,5")
        .attr("stroke-width",0)
        .attr("fill-opacity",0.5)
        .attr("fill", function(){return d.data.color;});
    return "url(#"+mID+")";
  }

  /* we're ditching geodesic. That means we don't have inner parts anymore.
    538 actually solved that... http://bl.ocks.org/bycoffe/18441cddeb8fe147b719fab5e30b5d45
    then we want to deeplink to animation state... which this may help with: http://jsfiddle.net/henbox/b4bbgdnz/5/
  */
  // add transmission lines with mid markers at each inner point of the path

  const transmissions = g.selectAll("transmissions")
    .data(latLongs.transmissions)
    .enter()
    .append("path") /* instead of appending a geodesic path from the leaflet plugin data, we now draw a line directly between two points */
    .attr("d", (d) => {
      return pathStringGenerator(
        // extractLineSegmentForAnimationEffect(d.data.originToDestinationXYs)
        d.data.originToDestinationXYs
      )
    }) /* with the interpolation in the function above pathStringGenerator */
    .attr("fill","none")
    .attr("stroke-opacity", .6)
    .attr("stroke-linecap", "round")
    .attr("stroke", (d) => { return d.data.color }) /* colorScale(d.data.from); color path by contry in which the transmission arrived */
    .attr("stroke-width", (d) => { return d.data.total }) /* scale line by total number of transmissions */
    // .attr("marker-mid", makeMarker);

    let transmissionPathLengths = [];
    transmissions[0].forEach((d, i) => {

      /* https://developer.mozilla.org/en-US/docs/Web/API/SVGGeometryElement/getTotalLength */
      const totalPathLength = d.getTotalLength();

      /*
        1. Here, we make a mapping between time and geographic position for the transmission.
        2. In short, make the line visible in proportion to the user selected date range, which
            may not include the entire length of the line.
        3. .clamp(true)
            never return a value outside the date range
            this would put the transmission path outside the geographic target
      */
      const pathScale = d3.scale.linear()
                                .domain([
                                  nodes[latLongs.transmissions[i].data.demePairIndices[0]].attr.num_date, /* origin date */
                                  nodes[latLongs.transmissions[i].data.demePairIndices[1]].attr.num_date /* destination date */
                                ])
                                .range([0, totalPathLength])
                                .clamp(true);

      transmissionPathLengths.push({
        totalPathLength,
        pathScale,
      })
    })

  const demes = g.selectAll("demes")
    .data(latLongs.demes)
    .enter().append("circle")
    .style("stroke", "none")
    .style("fill-opacity", .6)
    .style("fill", (d) => { return d.color })
    .attr("r", (d) => { return 0 + Math.sqrt(d.total) * 4 })
    .attr("transform", (d) => {
      return "translate(" + d.coords.x + "," + d.coords.y + ")";
    });

  return {
    demes,
    transmissions,
    transmissionPathLengths
  };

}

export const updateOnMoveEnd = (d3elems, latLongs) => {
  /* map has moved or rescaled, make demes and transmissions line up */
  if (d3elems) {
    d3elems.demes
      .data(latLongs.demes)
      .attr("transform", (d) => {
        return "translate(" + d.coords.x + "," + d.coords.y + ")";
      })

    d3elems.transmissions
      .data(latLongs.transmissions)
      .attr("d", (d) => { return pathStringGenerator(d.data.originToDestinationXYs) })
  }
}

const getPointForLineSegment = (originX, originY, destinationX, destinationY, dist) => {
    const rad = Math.atan2(destinationY-originY, destinationX-originX);
    const newX = originX + dist * Math.cos(rad);
    const newY = originY + dist * Math.sin(rad);
    return {x: newX, y: newY}
}

const extractLineSegmentForAnimationEffect = (pair, controls, d, nodes, d3elems, i) => {

  if (!nodes[d.data.demePairIndices[0]]) { console.warn("No node found for this index, which is needed to compare user dates to transmission origin/destination dates, so we're returning the default x y pair. No smaller, interior line will appear. This occurred because the index accessor for the node array returned from getLatLongs was not a valid value. Try a console log upstream of where demePairIndices is returned from src/util/mapHelpersLatLong.js"); return pair; } /* handle weird data */

  const originDate = nodes[d.data.demePairIndices[0]].attr.num_date;
  const destinationDate = nodes[d.data.demePairIndices[1]].attr.num_date;
  const userDateMin = controls.dateScale(controls.dateFormat.parse(controls.dateMin));
  const userDateMax = controls.dateScale(controls.dateFormat.parse(controls.dateMax));

  /*
    entire line is either visible or invisible, so return pair
  */
  if (
    userDateMin < originDate && userDateMax > destinationDate || /* visible ie., user: jan-dec 2015 origin/dest june-july 2015, so completely within */
    userDateMin > destinationDate || /* invisible ie., user: jan-dec 2015 dest: feb 1980 */
    userDateMax < originDate /* invisibile ie., user: jan-dec 2015 origin: feb 2017 */
  ) {
    return pair;
  } else {

    const scaledOriginDist = d3elems.transmissionPathLengths[i].pathScale(userDateMin);
    const scaledDestinationDist = d3elems.transmissionPathLengths[i].pathScale(userDateMax);
    const scaledOriginXY = getPointForLineSegment(pair[0].x, pair[0].y, pair[1].x, pair[1].y, scaledOriginDist);
    const scaledDestinationXY = getPointForLineSegment(pair[0].x, pair[0].y, pair[1].x, pair[1].y, scaledDestinationDist);

    // console.log(i + " is " + nodes[d.data.demePairIndices[0]].attr.country + " to " + nodes[d.data.demePairIndices[1]].attr.country)
    // console.log(i + "---" + originDate + "---" + userDateMin + "---" + userDateMax + "---" + destinationDate);
    // console.log(i + "=== totalPathLength ==== " + d3elems.transmissionPathLengths[i].totalPathLength)
    // console.log(i + "=== origin =============", pair[0].x, pair[0].y)
    // console.log(i + "=== destination ========", pair[1].x, pair[1].y)
    // console.log(i + "=== scaled origin distance ======", scaledOriginDist)
    // console.log(i + "=== scaled origin x,y ======", scaledOriginXY)
    // console.log(i + "=== scaled destination distance ======", scaledDestinationDist)
    // console.log(i + "=== scaled destination x,y ======", scaledDestinationXY)

    return [scaledOriginXY, scaledDestinationXY]
  }
}

export const updateVisibility = (d3elems, latLongs, controls, nodes) => {

  d3elems.demes
    .data(latLongs.demes)
    .transition(5)
    .style("fill", (d) => { return d.total > 0 ? d.color : "white" })
    .attr("r", (d) => {
      return 0 + Math.sqrt(d.total) * 4
    });

    console.log("UPDATING ====================================================")

  d3elems.transmissions
    .data(latLongs.transmissions)
    // .transition(5)
    .attr("d", (d, i) => {
      return pathStringGenerator(
        extractLineSegmentForAnimationEffect(
          d.data.originToDestinationXYs,
          controls,
          d,
          nodes,
          d3elems,
          i
        )
      )
    }) /* with the interpolation in the function above pathStringGenerator */
    .attr("stroke", (d) => { return d.data.total > 0 ? d.data.color : "white" })
    .attr("stroke-width", (d) => {
      return d.data.total
    })

}

/* template for an update helper */
export const updateFoo = (d3elems, latLongs) => {
  d3elems.demes
    .data(latLongs.demes)

  d3elems.transmissions
    .data(latLongs.transmissions)
}

// const translateAlong = (path) => {
//   var totalPathLength = path.getTotalLength();
//   return (datum, index) => {
//     return (t) => {
//       var p = path.getPointAtLength(t * totalPathLength);
//       return "translate(" + p.x + "," + p.y + ")";
//     };
//   };
// };

// const missiles = transmissionPaths.map((transmissionPath) => {
//
//   // console.log(transmissionPath)
//
//   const missile = g.append("circle")
//     .attr("r", 0)
//     .attr("fill", (d) => { return colorScale(transmissionPath.transmission.to) })
//     .attr("transform", `translate(
//       ${transmissionPath.partialTransmission[0].x},
//       ${transmissionPath.partialTransmission[0].y}
//     )`) /* begin the missile on the start of the line */
//     // .transition()
//     // .duration(5000)
//     // .attrTween("transform", translateAlong(transmissionPath.elem.node()));
//
//   return missile;
// })

// const setTipCoords = () => {
//   demes.attr("transform", (d) => {
//     return "translate(" + d.coords.x + "," + d.coords.y + ")";
//   });
// };

// const animateTransmissions = () => {
//   /* point along path interpolation https://bl.ocks.org/mbostock/1705868 */
//
// }

// setTipCoords();
// map.on("viewreset", setTipCoords); /* search: -Note (A) for an idea as to why this might not be working properly */
// animateTransmissions();

/*
  http://gis.stackexchange.com/questions/49114/d3-geo-path-to-draw-a-path-from-gis-coordinates
  https://bl.ocks.org/mbostock/3916621  // Compute point-interpolators at each distance.
  http://bl.ocks.org/mbostock/5851933 // draw line on map
  https://gist.github.com/mikeatlas/0b69b354a8d713989147 // polyline split if we don't use leaflet
  http://bl.ocks.org/mbostock/5928813
  http://bl.ocks.org/duopixel/4063326 // animate path in
  https://bl.ocks.org/mbostock/1705868 // point along path interpolation
  https://bl.ocks.org/mbostock/1313857 // point along path interpolation
  https://github.com/d3/d3-shape/blob/master/README.md#curves
  https://github.com/d3/d3-shape/blob/master/README.md#lines
*/

// since we're now using d3 we'll probably do something like http://stackoverflow.com/questions/11808860/arrow-triangles-on-my-svg-line/11809868#11809868
// decorator docs: https://github.com/bbecquet/Leaflet.PolylineDecorator
<|endoftext|>


<commit_before>/*
** Module: aireas2json
**
**
**
**
*/

// **********************************************************************************

var fs 		= require('fs'),
	path = require("path");
var pg = require('pg');

var aireasFolder, aireasUrl, aireasFileName, aireasLocalPathRoot, fileFolder,
	tmpFolder, tmpFolderName, localPath, fileFolderName, resultsFolder, resultsFolderName;
var dataRecords;

// **********************************************************************************

module.exports = {

	init: function (options) {

		var inFile = process.argv[3];

		sqlConnString = options.configParameter.databaseType + '://' +
			options.configParameter.databaseAccount + ':' +
			options.configParameter.databasePassword + '@' +
			options.configParameter.databaseServer + '/' +
			options.systemCode + '_' + options.configParameter.databaseName;

		aireasLocalPathRoot = options.systemFolderParent+'/aireas/';
		fileFolderName 		= 'aireas-hist';
		tmpFolderName 		= 'tmp';
		resultsFolderName 	= 'results';

		//aireasFileName 		= 'aireas.txt';
		aireasFileNameOutput= 'aireas.json';

		aireasFolder 		= aireasLocalPathRoot 	+ fileFolderName 	+ "/";
		tmpFolder 			= aireasFolder 			+ tmpFolderName 	+ "/";
		resultsFolder 		= aireasFolder 			+ resultsFolderName + "/";

		// create subfolders
		try {fs.mkdirSync(resultsFolder );} catch (e) {};//console.log('ERROR: no tmp folder found, batch run aborted.'); return } ;

		var executeFile = this.executeFile;

		fs.readdir(tmpFolder, function (err, files) {
    		if (err) {
        		throw err;
    		}

    		files.map(function (file) {
        		return path.join(tmpFolder, file);
    		}).filter(function (file) {
        		return fs.statSync(file).isFile();
    		}).forEach(function (file) {
				if (file == inFile) {
        		console.log("%s (%s)", file, path.extname(file));
				executeFile(file, path.extname(file))
				}
    		});
		});

	}, // end of init

	executeFile: function(file, extention) {

		var convertGPS2LatLng = function(gpsValue){
			var degrees = Math.floor(gpsValue /100);
			var minutes = gpsValue - (degrees*100);
			var result  = degrees + (minutes /60);
			return result;
		};

		var	createExportFile = function () {

		var outputArray;
		var outputRecord;
		var inputRecord;
		var filePath;
		var outputFileJson;

/*
		var currDate = new Date();
		var year = (currDate.getYear()+1900);

		var month = (currDate.getMonth()+1);
		var monthStr = "";
		if (month<10) {
			monthStr = "0" + month;
		} else {
			monthStr = "" + month;
		}

		var day = currDate.getDate();
		var dayStr = "";
		if (day<10) {
			dayStr = "0" + day;
		} else {
			dayStr = "" + day;
		}

		var hour = currDate.getHours();
		var hourStr = "";
		if (hour<10) {
			hourStr = "0" + hour;
		} else {
			hourStr = "" + hour;
		}

		var minute = currDate.getMinutes();
		var minuteStr = "";
		if (minute<10) {
			minuteStr = "0" + minute;
		} else {
			minuteStr = "" + minute;
		}
*/

//		filePath = resultsFolder + year + "/" + monthStr + "/" + dayStr + "/" + hourStr + "/" + minuteStr;
		filePath = resultsFolder;

//		try {fs.mkdirSync(resultsFolder + year + "/" );} catch (e) {} ;
//		try {fs.mkdirSync(resultsFolder + year + "/" + monthStr + "/");} catch (e) {} ;
//		try {fs.mkdirSync(resultsFolder + year + "/" + monthStr + "/" + dayStr + "/" );} catch (e) {} ;
//		try {fs.mkdirSync(resultsFolder + year + "/" + monthStr + "/" + dayStr + "/"  + hourStr + "/");} catch (e) {} ;
//		try {fs.mkdirSync(resultsFolder + year + "/" + monthStr + "/" + dayStr + "/" + hourStr + "/" + minuteStr);} catch (e) {} ;

		console.log("- creating: " , aireasFileNameOutput);
		outputFileJson = JSON.stringify(dataRecords);
//		this.writeFile (filePath, aireasFileNameOutput, outputFileJson );
		//writeFile (filePath, aireasFileNameOutput, outputFileJson );
		createSqlAireas(dataRecords);

	}

//	convertGPS2LatLng: function(gpsValue){
//		var degrees = Math.floor(gpsValue /100);
//		var minutes = gpsValue - (degrees*100);
//		var result  = degrees + (minutes /60);
//		return result;
//	},

		var writeFile = function(path, fileName, content ) {
			var _path = path;
			try {
				fs.mkdirSync(_path);
			} catch (e) {} ;
			fs.writeFileSync(_path + "/" + fileName, content);
		};

		var createSqlAireas = function(inputFileJson , outputFilePath) {

		var i;

// 		var inputFile = JSON.parse(inputFileJson);
 		var inputFile = inputFileJson;
		var outputFile = "";

	        var executeSql = function  (query, callback) {
        	        console.log('sql start: ');
                	var client = new pg.Client(sqlConnString);
                	client.connect(function(err) {
                        	if(err) {
                                	return console.error('could not connect to postgres', err);
                        	}
                        	client.query(query, function(err, result) {
                                	if(err) {
										console.log('error running query ' + query);
                                	return console.error('error running query', err);
                        	}
                        	console.log('sql result: ' + result);
                        	client.end();
                        	});
                	});
        	};

		var sqlCallBack = function (err, result) {
			if (err) {
				console.log('ERROR:','sql error:', err, result);
			} else {
				//console.log('Query','sql result:', result);
			}
		};

  //      	var writeFile = function(fileContent, filepath ) {
   //             	var _path = filepath;
   //             	fs.writeFileSync(_path, fileContent);
   //     	};

		for (i=0;i<inputFile.length;i++) {

			var inputRecord = inputFile[i];
			var outputRecord = "";

			//var _measureDate		 			= inputRecord.measureDate==""?"null":"'"+inputRecord.measureDate + "', ";

			outputRecord = "\nINSERT INTO aireas_hist ( airbox, retrieveddatechar, measuredatechar, retrieveddate, measuredate, " +
				" gpslat, gpslng, lat, lng, pm1, pm25, pm10, ufp, ozon, hum, celc, no2, " +
				" gpslatfloat, gpslngfloat, pm1float, pm25float, pm10float, ufpfloat, ozonfloat, humfloat, celcfloat, no2float, " +
				" geom28992, geom ) VALUES (" +
					"'" + 	inputRecord.airbox 			+ "', " +
					"'" + 	inputRecord.retrievedDate 	+ "', " +
					"'" + 	inputRecord.measureDate 	+ "', " +
					"'" +	inputRecord.retrievedDate 	+ "', " + 	// timestamp with time zone,
					"'" + 	inputRecord.measureDate		+ "', " +	// timestamp with time zone,
					"'" + 	inputRecord.gpsLat 			+ "', " +
					"'" + 	inputRecord.gpsLng 			+ "', " +
							inputRecord.lat 			+ ", "  +
							inputRecord.lng 			+ ", "  +
					"'" + 	inputRecord.PM1 			+ "', " +
					"'" + 	inputRecord.PM25 			+ "', " +
					"'" +	inputRecord.PM10 			+ "', " +
					"'" + 	inputRecord.UFP 			+ "', " +
					"'" + 	inputRecord.OZON 			+ "', " +
					"'" + 	inputRecord.HUM 			+ "', " +
					"'" + 	inputRecord.CELC 			+ "', " +
					"'" + 	inputRecord.NO2 			+ "', " +
							inputRecord.gpsLatFloat 	+ ", "  +
							inputRecord.gpsLngFloat 	+ ", "  +
							inputRecord.PM1Float 		+ ", "  +
							inputRecord.PM25Float 		+ ", "  +
							inputRecord.PM10Float 		+ ", "  +
							inputRecord.UFPFloat 		+ ", "  +
							inputRecord.OZONFloat 		+ ", "  +
							inputRecord.HUMFloat 		+ ", "  +
							inputRecord.CELCFloat 		+ ", "  +
							inputRecord.NO2Float 		+ ", "  +
							" ST_Transform(ST_SetSRID(ST_MakePoint(" + inputRecord.lng + ", " + inputRecord.lat + "), 4326), 28992 ), " +
							" ST_SetSRID(ST_MakePoint(" + inputRecord.lng + ", " + inputRecord.lat + "), 4326) );  \n ";

			outputFile=outputFile.concat(outputRecord);
		}
	console.log('Write: ' + outputFilePath);
		//writeFile(outputFile, outputFilePath);
		executeSql(outputFile, sqlCallBack);
		outputFile = null;  // clear memory

	};

//		localPath 		= tmpFolder + aireasFileName;
		var localPath 		= file;
    	console.log("Local AiREAS data: " + localPath );

    	var _datFile	= fs.readFileSync(localPath);

		var i, j, _dataRecord, _waardeDataRecord, inpRecordArray,
			inpRecordPM1, inpRecordPM25, inpRecordPM10,
			inpRecordUFP, inpRecordOZON, inpRecordHUM, inpRecordCELC;

//		var inRecord1 = "" + _datFile.toString();
//		var inRecord2 = inRecord1.replace(/\'/g,'"');
//		var inRecord = JSON.parse(inRecord2);

		var records1 = ""+_datFile;
		var records2 = records1.split('\n');

		console.log('Aantal records:' + records2.length)

//		{"airbox": "39_cal", "retrievedDate": "2015-07-06T18:57:06.596Z", "content":

		var firstRecParts = records2[0].split(',');
		var firstRecJson = firstRecParts[0] + ', '+ firstRecParts[1] + '} ';
		console.log(firstRecJson);
		var firstRec = JSON.parse(firstRecJson);
		console.log('Verwerk airbox %s van retrievedate %s', firstRec.airbox, firstRec.retrievedDate );

//		console.log(inRecord);
//		console.log(inRecord.airboxes);
//		console.log(inRecord.airboxes[0]);

//		tmpArray = inRecord.content.airboxes;
		tmpArray = records2;

/*
		var tmp0 = _datFile.toString();
		var dateRetrievedLength = tmp0.indexOf(' ');
		var dateRetrieved = tmp0.substring(0, dateRetrievedLength);
		var dateRetrievedDate = Date.parse(dateRetrieved);
		console.log('x'+dateRetrieved+'x');
		var tmp1 = tmp0.substr(dateRetrievedLength+1);
		//var tmp2 = tmp1.replace(/<p>/g,'');
		//var tmp3 = tmp2.replace(/<\/p>/g,'');
		//var tmpArray = tmp3.split(')');
		var tmp2 = tmp1.replace(/\[/,'');
		var tmpArray = tmp3.split('],');
*/

		dataRecords	= [];

		// Let op skip first and last record(s) for now!
		console.log('Aantal records: ' + tmpArray.length);

		for ( i=1; i<tmpArray.length-2;i++) {
			if (i>tmpArray.length - 5) {
				console.log(' %n %s ', i, _waardeDataRecord[9] );
			}
//			inpRecordArray 		= tmpArray[i].split(':(');
//			inpRecordArray 		= tmpArray[i].split('[');
			//inpRecordArray 		= tmpArray[i];
			inpRecordArray 		= tmpArray[i].split(',');

//			console.log(inpRecordArray);

			_dataRecord			= {};
//			_dataRecord.airBox	= inpRecordArray[0];
			_dataRecord.airbox	= firstRec.airbox;

//			inpMetingenArray 	= inpRecordArray[1].split(',');
			_waardeDataRecord	= inpRecordArray;

//			_waardeDataRecord 	= [];
//			for(j=0;j<inpMetingenArray.length;j++) {
//				_waardeDataRecord[j] = inpRecordArray[j];// parseFloat(inpMetingenArray[j]);
//			}

			//_dataRecord.airbox 	= _waardeDataRecord[0];

//			console.log(_dataRecord.airbox);

			_dataRecord.retrievedDate 	= firstRec.retrievedDate;
			_dataRecord.measureDate 	= _waardeDataRecord[9];
			_dataRecord.gpsLat 	= _waardeDataRecord[0];
			_dataRecord.gpsLng 	= _waardeDataRecord[1];
			_dataRecord.lat 	= convertGPS2LatLng(_waardeDataRecord[0]);
			_dataRecord.lng 	= convertGPS2LatLng(_waardeDataRecord[1]);
			_dataRecord.PM1 	= _waardeDataRecord[5];
			_dataRecord.PM25 	= _waardeDataRecord[6];
			_dataRecord.PM10 	= _waardeDataRecord[4];
			_dataRecord.UFP 	= _waardeDataRecord[2];
			_dataRecord.OZON 	= _waardeDataRecord[3];
			_dataRecord.HUM 	= _waardeDataRecord[7];
			_dataRecord.CELC 	= _waardeDataRecord[8];
			_dataRecord.NO2 	= 0; //_waardeDataRecord[9];

			_dataRecord.gpsLatFloat = parseFloat(_waardeDataRecord[0]);
			_dataRecord.gpsLngFloat	= parseFloat(_waardeDataRecord[1]);
			_dataRecord.PM1Float 	= parseFloat(_waardeDataRecord[5]);
			_dataRecord.PM25Float 	= parseFloat(_waardeDataRecord[6]);
			_dataRecord.PM10Float 	= parseFloat(_waardeDataRecord[4]);
			_dataRecord.UFPFloat 	= parseFloat(_waardeDataRecord[2]);
			if (_dataRecord.UFPFloat > 0) _dataRecord.UFPFloat = Math.round(_dataRecord.UFPFloat / 1000); // in units of 1000
			_dataRecord.OZONFloat 	= parseFloat(_waardeDataRecord[3]);
			_dataRecord.HUMFloat 	= parseFloat(_waardeDataRecord[7]);
			_dataRecord.CELCFloat 	= parseFloat(_waardeDataRecord[8]);
			_dataRecord.NO2Float 	= 0; //parseFloat(_waardeDataRecord[9]);

			dataRecords.push(_dataRecord);

		}

		createExportFile();

	},

} // end of module.exports
<commit_msg>aireas-hist-json<commit_after>/*
** Module: aireas2json
**
**
**
**
*/

// **********************************************************************************

var fs 		= require('fs'),
	path = require("path");
var pg = require('pg');

var aireasFolder, aireasUrl, aireasFileName, aireasLocalPathRoot, fileFolder,
	tmpFolder, tmpFolderName, localPath, fileFolderName, resultsFolder, resultsFolderName;
var dataRecords;

// **********************************************************************************

module.exports = {

	init: function (options) {

		var inFile = process.argv[3];

		sqlConnString = options.configParameter.databaseType + '://' +
			options.configParameter.databaseAccount + ':' +
			options.configParameter.databasePassword + '@' +
			options.configParameter.databaseServer + '/' +
			options.systemCode + '_' + options.configParameter.databaseName;

		aireasLocalPathRoot = options.systemFolderParent+'/aireas/';
		fileFolderName 		= 'aireas-hist';
		tmpFolderName 		= 'tmp';
		resultsFolderName 	= 'results';

		//aireasFileName 		= 'aireas.txt';
		aireasFileNameOutput= 'aireas.json';

		aireasFolder 		= aireasLocalPathRoot 	+ fileFolderName 	+ "/";
		tmpFolder 			= aireasFolder 			+ tmpFolderName 	+ "/";
		resultsFolder 		= aireasFolder 			+ resultsFolderName + "/";

		// create subfolders
		try {fs.mkdirSync(resultsFolder );} catch (e) {};//console.log('ERROR: no tmp folder found, batch run aborted.'); return } ;

		var executeFile = this.executeFile;

		fs.readdir(tmpFolder, function (err, files) {
    		if (err) {
        		throw err;
    		}

    		files.map(function (file) {
        		return path.join(tmpFolder, file);
    		}).filter(function (file) {
        		return fs.statSync(file).isFile();
    		}).forEach(function (file) {
				if (file == inFile) {
        		console.log("%s (%s)", file, path.extname(file));
				executeFile(file, path.extname(file))
				}
    		});
		});

	}, // end of init

	executeFile: function(file, extention) {

		var convertGPS2LatLng = function(gpsValue){
			var degrees = Math.floor(gpsValue /100);
			var minutes = gpsValue - (degrees*100);
			var result  = degrees + (minutes /60);
			return result;
		};

		var	createExportFile = function () {

		var outputArray;
		var outputRecord;
		var inputRecord;
		var filePath;
		var outputFileJson;

/*
		var currDate = new Date();
		var year = (currDate.getYear()+1900);

		var month = (currDate.getMonth()+1);
		var monthStr = "";
		if (month<10) {
			monthStr = "0" + month;
		} else {
			monthStr = "" + month;
		}

		var day = currDate.getDate();
		var dayStr = "";
		if (day<10) {
			dayStr = "0" + day;
		} else {
			dayStr = "" + day;
		}

		var hour = currDate.getHours();
		var hourStr = "";
		if (hour<10) {
			hourStr = "0" + hour;
		} else {
			hourStr = "" + hour;
		}

		var minute = currDate.getMinutes();
		var minuteStr = "";
		if (minute<10) {
			minuteStr = "0" + minute;
		} else {
			minuteStr = "" + minute;
		}
*/

//		filePath = resultsFolder + year + "/" + monthStr + "/" + dayStr + "/" + hourStr + "/" + minuteStr;
		filePath = resultsFolder;

//		try {fs.mkdirSync(resultsFolder + year + "/" );} catch (e) {} ;
//		try {fs.mkdirSync(resultsFolder + year + "/" + monthStr + "/");} catch (e) {} ;
//		try {fs.mkdirSync(resultsFolder + year + "/" + monthStr + "/" + dayStr + "/" );} catch (e) {} ;
//		try {fs.mkdirSync(resultsFolder + year + "/" + monthStr + "/" + dayStr + "/"  + hourStr + "/");} catch (e) {} ;
//		try {fs.mkdirSync(resultsFolder + year + "/" + monthStr + "/" + dayStr + "/" + hourStr + "/" + minuteStr);} catch (e) {} ;

		console.log("- creating: " , aireasFileNameOutput);
		outputFileJson = JSON.stringify(dataRecords);
//		this.writeFile (filePath, aireasFileNameOutput, outputFileJson );
		//writeFile (filePath, aireasFileNameOutput, outputFileJson );
		createSqlAireas(dataRecords);

	}

//	convertGPS2LatLng: function(gpsValue){
//		var degrees = Math.floor(gpsValue /100);
//		var minutes = gpsValue - (degrees*100);
//		var result  = degrees + (minutes /60);
//		return result;
//	},

		var writeFile = function(path, fileName, content ) {
			var _path = path;
			try {
				fs.mkdirSync(_path);
			} catch (e) {} ;
			fs.writeFileSync(_path + "/" + fileName, content);
		};

		var createSqlAireas = function(inputFileJson , outputFilePath) {

		var i;

// 		var inputFile = JSON.parse(inputFileJson);
 		var inputFile = inputFileJson;
		var outputFile = "";

	        var executeSql = function  (query, callback) {
        	        console.log('sql start: ');
                	var client = new pg.Client(sqlConnString);
                	client.connect(function(err) {
                        	if(err) {
                                	return console.error('could not connect to postgres', err);
                        	}
                        	client.query(query, function(err, result) {
                                	if(err) {
										console.log('error running query ' + query);
                                	return console.error('error running query', err);
                        	}
                        	console.log('sql result: ' + result);
                        	client.end();
                        	});
                	});
        	};

		var sqlCallBack = function (err, result) {
			if (err) {
				console.log('ERROR:','sql error:', err, result);
			} else {
				//console.log('Query','sql result:', result);
			}
		};

  //      	var writeFile = function(fileContent, filepath ) {
   //             	var _path = filepath;
   //             	fs.writeFileSync(_path, fileContent);
   //     	};

		for (i=0;i<inputFile.length;i++) {

			var inputRecord = inputFile[i];
			var outputRecord = "";

			//var _measureDate		 			= inputRecord.measureDate==""?"null":"'"+inputRecord.measureDate + "', ";

			outputRecord = "\nINSERT INTO aireas_hist ( airbox, retrieveddatechar, measuredatechar, retrieveddate, measuredate, " +
				" gpslat, gpslng, lat, lng, pm1, pm25, pm10, ufp, ozon, hum, celc, no2, " +
				" gpslatfloat, gpslngfloat, pm1float, pm25float, pm10float, ufpfloat, ozonfloat, humfloat, celcfloat, no2float, " +
				" geom28992, geom ) VALUES (" +
					"'" + 	inputRecord.airbox 			+ "', " +
					"'" + 	inputRecord.retrievedDate 	+ "', " +
					"'" + 	inputRecord.measureDate 	+ "', " +
					"'" +	inputRecord.retrievedDate 	+ "', " + 	// timestamp with time zone,
					"'" + 	inputRecord.measureDate		+ "', " +	// timestamp with time zone,
					"'" + 	inputRecord.gpsLat 			+ "', " +
					"'" + 	inputRecord.gpsLng 			+ "', " +
							inputRecord.lat 			+ ", "  +
							inputRecord.lng 			+ ", "  +
					"'" + 	inputRecord.PM1 			+ "', " +
					"'" + 	inputRecord.PM25 			+ "', " +
					"'" +	inputRecord.PM10 			+ "', " +
					"'" + 	inputRecord.UFP 			+ "', " +
					"'" + 	inputRecord.OZON 			+ "', " +
					"'" + 	inputRecord.HUM 			+ "', " +
					"'" + 	inputRecord.CELC 			+ "', " +
					"'" + 	inputRecord.NO2 			+ "', " +
							inputRecord.gpsLatFloat 	+ ", "  +
							inputRecord.gpsLngFloat 	+ ", "  +
							inputRecord.PM1Float 		+ ", "  +
							inputRecord.PM25Float 		+ ", "  +
							inputRecord.PM10Float 		+ ", "  +
							inputRecord.UFPFloat 		+ ", "  +
							inputRecord.OZONFloat 		+ ", "  +
							inputRecord.HUMFloat 		+ ", "  +
							inputRecord.CELCFloat 		+ ", "  +
							inputRecord.NO2Float 		+ ", "  +
							" ST_Transform(ST_SetSRID(ST_MakePoint(" + inputRecord.lng + ", " + inputRecord.lat + "), 4326), 28992 ), " +
							" ST_SetSRID(ST_MakePoint(" + inputRecord.lng + ", " + inputRecord.lat + "), 4326) );  \n ";

			console.log('Output %s', outputRecord)

			outputFile=outputFile.concat(outputRecord);
		}
	console.log('Write: ' + outputFilePath);
		//writeFile(outputFile, outputFilePath);
		executeSql(outputFile, sqlCallBack);
		outputFile = null;  // clear memory

	};

//		localPath 		= tmpFolder + aireasFileName;
		var localPath 		= file;
    	console.log("Local AiREAS data: " + localPath );

    	var _datFile	= fs.readFileSync(localPath);

		var i, j, _dataRecord, _waardeDataRecord, inpRecordArray,
			inpRecordPM1, inpRecordPM25, inpRecordPM10,
			inpRecordUFP, inpRecordOZON, inpRecordHUM, inpRecordCELC;

//		var inRecord1 = "" + _datFile.toString();
//		var inRecord2 = inRecord1.replace(/\'/g,'"');
//		var inRecord = JSON.parse(inRecord2);

		var records1 = ""+_datFile;
		var records2 = records1.split('\n');

		console.log('Aantal records:' + records2.length)

//		{"airbox": "39_cal", "retrievedDate": "2015-07-06T18:57:06.596Z", "content":

		var firstRecParts = records2[0].split(',');
		var firstRecJson = firstRecParts[0] + ', '+ firstRecParts[1] + '} ';
		console.log(firstRecJson);
		var firstRec = JSON.parse(firstRecJson);
		console.log('Verwerk airbox %s van retrievedate %s', firstRec.airbox, firstRec.retrievedDate );

//		console.log(inRecord);
//		console.log(inRecord.airboxes);
//		console.log(inRecord.airboxes[0]);

//		tmpArray = inRecord.content.airboxes;
		tmpArray = records2;

/*
		var tmp0 = _datFile.toString();
		var dateRetrievedLength = tmp0.indexOf(' ');
		var dateRetrieved = tmp0.substring(0, dateRetrievedLength);
		var dateRetrievedDate = Date.parse(dateRetrieved);
		console.log('x'+dateRetrieved+'x');
		var tmp1 = tmp0.substr(dateRetrievedLength+1);
		//var tmp2 = tmp1.replace(/<p>/g,'');
		//var tmp3 = tmp2.replace(/<\/p>/g,'');
		//var tmpArray = tmp3.split(')');
		var tmp2 = tmp1.replace(/\[/,'');
		var tmpArray = tmp3.split('],');
*/

		dataRecords	= [];

		// Let op skip first and last record(s) for now!
		console.log('Aantal records: ' + tmpArray.length);

		for ( i=1; i<tmpArray.length-2;i++) {
			if (i>tmpArray.length - 5) {
				console.log(' %n %s ', i, _waardeDataRecord[9] );
			}
//			inpRecordArray 		= tmpArray[i].split(':(');
//			inpRecordArray 		= tmpArray[i].split('[');
			//inpRecordArray 		= tmpArray[i];
			inpRecordArray 		= tmpArray[i].split(',');

//			console.log(inpRecordArray);

			_dataRecord			= {};
//			_dataRecord.airBox	= inpRecordArray[0];
			_dataRecord.airbox	= firstRec.airbox;

//			inpMetingenArray 	= inpRecordArray[1].split(',');
			_waardeDataRecord	= inpRecordArray;

//			_waardeDataRecord 	= [];
//			for(j=0;j<inpMetingenArray.length;j++) {
//				_waardeDataRecord[j] = inpRecordArray[j];// parseFloat(inpMetingenArray[j]);
//			}

			//_dataRecord.airbox 	= _waardeDataRecord[0];

//			console.log(_dataRecord.airbox);

			_dataRecord.retrievedDate 	= firstRec.retrievedDate;
			_dataRecord.measureDate 	= _waardeDataRecord[9];
			_dataRecord.gpsLat 	= _waardeDataRecord[0];
			_dataRecord.gpsLng 	= _waardeDataRecord[1];
			_dataRecord.lat 	= convertGPS2LatLng(_waardeDataRecord[0]);
			_dataRecord.lng 	= convertGPS2LatLng(_waardeDataRecord[1]);
			_dataRecord.PM1 	= _waardeDataRecord[5];
			_dataRecord.PM25 	= _waardeDataRecord[6];
			_dataRecord.PM10 	= _waardeDataRecord[4];
			_dataRecord.UFP 	= _waardeDataRecord[2];
			_dataRecord.OZON 	= _waardeDataRecord[3];
			_dataRecord.HUM 	= _waardeDataRecord[7];
			_dataRecord.CELC 	= _waardeDataRecord[8];
			_dataRecord.NO2 	= 0; //_waardeDataRecord[9];

			_dataRecord.gpsLatFloat = parseFloat(_waardeDataRecord[0]);
			_dataRecord.gpsLngFloat	= parseFloat(_waardeDataRecord[1]);
			_dataRecord.PM1Float 	= parseFloat(_waardeDataRecord[5]);
			_dataRecord.PM25Float 	= parseFloat(_waardeDataRecord[6]);
			_dataRecord.PM10Float 	= parseFloat(_waardeDataRecord[4]);
			_dataRecord.UFPFloat 	= parseFloat(_waardeDataRecord[2]);
			if (_dataRecord.UFPFloat > 0) _dataRecord.UFPFloat = Math.round(_dataRecord.UFPFloat / 1000); // in units of 1000
			_dataRecord.OZONFloat 	= parseFloat(_waardeDataRecord[3]);
			_dataRecord.HUMFloat 	= parseFloat(_waardeDataRecord[7]);
			_dataRecord.CELCFloat 	= parseFloat(_waardeDataRecord[8]);
			_dataRecord.NO2Float 	= 0; //parseFloat(_waardeDataRecord[9]);

			dataRecords.push(_dataRecord);

		}

		createExportFile();

	},

} // end of module.exports
<|endoftext|>


<commit_before>(function() {

var doAlert = false;
var storage = null;

/* * * * * Storage methods * * * * */

var loadValue = function(key,def) {
    var value = storage.get(key, null);
    if (key == 'data' && value && value.team) {
        value.team = value.team.map(function(x,n) {
            if (x && x.unit !== null && x.unit !== undefined && x.unit.constructor == Number) {
                x.unit = window.units[x.unit];
                // captain warnings
                if (n < 2 && x.unit && x.unit.number && captains[x.unit.number + 1] && captains[x.unit.number + 1].warning) {
                    noty({
                        text: captains[x.unit.number + 1].warning.replace(/%name%/g, window.units[x.unit.number].name),
                        type: 'warning',
                        layout: 'topRight',
                        theme: 'relax',
                        timeout: 5000
                    });
                }
            }
            return x;
        }).slice(0,6);
        if (isNaN(value.defense)) value.defense = 0;
    }
    if (value !== null) return value;
    return def;
};

var save = function(key,object) {
    if (key == 'data' && object.team) {
        object = JSON.parse(JSON.stringify(object));
        object.team = object.team.map(function(x) {
            if (x && x.unit && x.unit.constructor == Object)
                x.unit = x.unit.number;
            return x;
        }).slice(0,6);
    }
    storage.set(key, object);
};

/* * * * * Controller * * * * */

var StorageCtrl = function($scope, $storage) {

    storage = $storage;

    /* * * * * Initialization * * * * */

    $scope.options.crunchInhibitor = Infinity;

    var data = loadValue('data',{ });
    for (var d in data)
        $scope.data[d] = data[d];

    var options = loadValue('options',{ });
    for (var o in options) {
        if (o != 'slidersEnabled') continue;
        $scope.options[o] = options[o];
    }

    if ($scope.data.effect && !window.effects.hasOwnProperty($scope.data.effect))
        $scope.data.effect = null;

    if ($scope.data.ship && $scope.data.ship.constructor == Object)
        $scope.data.ship = [ 1, 5 ];

    $scope.options.crunchInhibitor = 0;

    if ($scope.data.team) {
        for (var i=0;i<6;++i)
            $scope.slotChanged(i);
    }

    /* * * * * Version control * * * * */

    var version = $storage.get('version', 11);
    if (version < 11) {
        doAlert = true;
        $storage.set('version', 11);
    }

    /* * * * * Save on changes * * * * */

    $scope.$watch('data',function() {
        if (!$scope.options.transientMode)
            save('data',$scope.data);
    },true);

    $scope.$watch('options',function() {
        if (!$scope.options.transientMode)
            save('options',$scope.options);
    },true);

    $scope.$watch('options.transientMode',function(mode) {
        if (mode === undefined || mode === null) return;
        if (!mode) save('data',$scope.data);
    });

    if (doAlert) {
        $scope.notify({
            text: 'Some stuff changed. Refreshing the page and/or clearing your browser\'s cache may be a smart idea.',
            timeout: 10000,
            type: 'error'
        });
    }

};

angular.module('optc')
    .controller('StorageCtrl', StorageCtrl);

})();
<commit_msg>minor fixes<commit_after>(function() {

var doAlert = false;
var storage = null;

/* * * * * Storage methods * * * * */

var loadValue = function(key,def) {
    var value = storage.get(key, null);
    if (key == 'data' && value && value.team) {
        value.team = value.team.map(function(x,n) {
            if (!x || !x.hasOwnProperty('unit') || x.unit === undefined) x.unit = null;
            if (x && x.unit !== null && x.unit !== undefined && x.unit.constructor == Number) {
                x.unit = window.units[x.unit];
                // captain warnings
                if (n < 2 && x.unit && x.unit.number && captains[x.unit.number + 1] && captains[x.unit.number + 1].warning) {
                    noty({
                        text: captains[x.unit.number + 1].warning.replace(/%name%/g, window.units[x.unit.number].name),
                        type: 'warning',
                        layout: 'topRight',
                        theme: 'relax',
                        timeout: 5000
                    });
                }
            }
            return x;
        }).slice(0,6);
        if (isNaN(value.defense)) value.defense = 0;
    }
    if (value !== null) return value;
    return def;
};

var save = function(key,object) {
    if (key == 'data' && object.team) {
        object = JSON.parse(JSON.stringify(object));
        object.team = object.team.map(function(x) {
            if (x && x.unit && x.unit.constructor == Object)
                x.unit = x.unit.number;
            return x;
        }).slice(0,6);
    }
    storage.set(key, object);
};

/* * * * * Controller * * * * */

var StorageCtrl = function($scope, $storage) {

    storage = $storage;

    /* * * * * Initialization * * * * */

    $scope.options.crunchInhibitor = Infinity;

    var data = loadValue('data',{ });
    for (var d in data)
        $scope.data[d] = data[d];

    var options = loadValue('options',{ });
    for (var o in options) {
        if (o != 'slidersEnabled') continue;
        $scope.options[o] = options[o];
    }

    if ($scope.data.effect && !window.effects.hasOwnProperty($scope.data.effect))
        $scope.data.effect = null;

    if ($scope.data.ship && $scope.data.ship.constructor == Object)
        $scope.data.ship = [ 1, 5 ];

    $scope.options.crunchInhibitor = 0;

    if ($scope.data.team) {
        for (var i=0;i<6;++i)
            $scope.slotChanged(i);
    }

    /* * * * * Version control * * * * */

    var version = $storage.get('version', 11);
    if (version < 11) {
        doAlert = true;
        $storage.set('version', 11);
    }

    /* * * * * Save on changes * * * * */

    $scope.$watch('data',function() {
        if (!$scope.options.transientMode)
            save('data',$scope.data);
    },true);

    $scope.$watch('options',function() {
        if (!$scope.options.transientMode)
            save('options',$scope.options);
    },true);

    $scope.$watch('options.transientMode',function(mode) {
        if (mode === undefined || mode === null) return;
        if (!mode) save('data',$scope.data);
    });

    if (doAlert) {
        $scope.notify({
            text: 'Some stuff changed. Refreshing the page and/or clearing your browser\'s cache may be a smart idea.',
            timeout: 10000,
            type: 'error'
        });
    }

};

angular.module('optc')
    .controller('StorageCtrl', StorageCtrl);

})();
<|endoftext|>


<commit_before>import Util from '../../core/util.js';
export class Grid extends Util {
  constructor(SL, config) {
    super(SL, config);
    this.name = 'Grid';
    this.configure();
  }
  reset() {
    super.reset();
    this.unregisterSnappers();
  }

  configure(config) {
    config = super.configure(config);

    // base definition
    this.definition = {
      offset: { x: 0, y: 0 },
      width: '100%',
      height: '100%',
      cell: {},
      cols: '+',
      rows: '+'
    };

    // use config to customize the definition
    if (config.offset) {
      this.definition.offset.x = config.offset.x || this.definition.offset.x;
      this.definition.offset.y = config.offset.y || this.definition.offset.y;
    }
    this.definition.width = config.width || this.definition.width;
    this.definition.height = config.height || this.definition.height;

    this.definition.cell.width = (config.cell && config.cell.width) || (config.cell && config.cell.size) || config.size || this.definition.cell.width;
    this.definition.cell.height = (config.cell && config.cell.height) || (config.cell && config.cell.size) || config.size || this.definition.cell.height;

    this.definition.cols = config.cols || this.definition.cols;
    this.definition.rows = config.rows || this.definition.rows;

    // set things up
    this.registerSnappers();
    this.renderGrid();

    return this.config;
  }

  getCurrentDefinition() {
    let currentDef = {};
    let Parser = this.SL.Utils.gets('ConfigParser');
    let View = this.SL.Paper.view;
    let Def = this.definition;
    let parsed;

    // parse grid offset
    currentDef.offset = { x: 0, y: 0 };
    parsed = Parser.parseNumber(Def.offset.x);
    currentDef.offset.x = parsed.value;
    if (parsed.percent) {
      currentDef.offset.x *= View.size.width;
    }
    parsed = Parser.parseNumber(Def.offset.y);
    currentDef.offset.y = parsed.value;
    if (parsed.percent) {
      currentDef.offset.y *= View.size.height;
    }

    // parse grid width
    currentDef.width = View.size.width;
    parsed = Parser.parseNumber(Def.width);
    if (!parsed.value) {
      parsed.value = 1.0;
      parsed.percent = true;
    }
    if (parsed.percent) {
      currentDef.width *= parsed.value;
    }
    else if (parsed.value) {
      currentDef.width = parsed.value;
    }

    // parse grid height
    currentDef.height = View.size.height;
    parsed = Parser.parseNumber(Def.height);
    if (!parsed.value) {
      parsed.value = 1.0;
      parsed.percent = true;
    }
    if (parsed.percent) {
      currentDef.height *= parsed.value;
    }
    else if (parsed.value) {
      currentDef.height = parsed.value;
    }

    // read row and col preferences
    let cols = Parser.parseNumber(Def.cols);
    let rows = Parser.parseNumber(Def.rows);
    currentDef.cell = {};

    // calculate cell width + cols
    parsed = Parser.parseNumber(Def.cell.width);
    currentDef.cell.width = parsed.value;
    if (!parsed.value) {
      if (cols.value) {
        currentDef.cell.width = currentDef.width / cols.value;
      }
    }
    else if (parsed.percent) {
      currentDef.cell.width = currentDef.width * parsed.value;
    }
    currentDef.cell.width = Parser.unparseNumber(currentDef.cell.width, parsed);

    // calculate cell height + rows
    parsed = Parser.parseNumber(Def.cell.height);
    currentDef.cell.height = parsed.value;
    if (!parsed.value) {
      if (rows.value) {
        currentDef.cell.height = currentDef.height / rows.value;
      }
    }
    else if (parsed.percent) {
      currentDef.cell.height = currentDef.height * parsed.value;
    }
    currentDef.cell.height = Parser.unparseNumber(currentDef.cell.height, parsed);

    // if width or height is missing, try to use the other
    currentDef.cell.width = currentDef.cell.width || currentDef.cell.height;
    currentDef.cell.height = currentDef.cell.height || currentDef.cell.width;

    // finalize number of cols
    currentDef.cols = currentDef.width / currentDef.cell.width;
    currentDef.cols = Parser.unparseNumber(currentDef.cols, cols);
    // finalize number of rows
    currentDef.rows = currentDef.height / currentDef.cell.height;
    currentDef.rows = Parser.unparseNumber(currentDef.rows, rows);

    return currentDef;
  }

  registerSnappers() {
    let Snap = this.SL.Utils.get('Snap');
    if (Snap) {
      if (!this.Snappers) {
        this.Snappers = {};
      }
      this.Snappers.point = Snap.addSnapper('point', {
        priority: 75,
        callback: (point, config) => {
          return this.snapPoint(point, config);
        }
      });
      this.Snappers.rectangle = Snap.addSnapper('rectangle', {
        priority: 75,
        callback: (rectangle, config) => {
          return this.snapRectangle(rectangle, config);
        }
      });
      this.Snappers.item = Snap.addSnapper('item', {
        priority: 75,
        callback: (item, config) => {
          return this.snapItem(item, config);
        }
      });
    }
  }
  unregisterSnappers() {
    let Snap = this.SL.Utils.get('Snap');
    if (!Snap || !this.Snappers) {
      return;
    }
    if (this.Snappers.point) {
      Snap.dropSnapper('point', this.Snappers.point.id);
      this.Snappers.point = undefined;
    }
    if (Snap && this.Snappers.rectangle) {
      Snap.dropSnapper('rectangle', this.Snappers.rectangle.id);
      this.Snappers.rectangle = undefined;
    }
    if (Snap && this.Snappers.item) {
      Snap.dropSnapper('item', this.Snappers.item.id);
      this.Snappers.item = undefined;
    }
  }
  snapPoint(point, config={}) {
    if (config && config.interactive) {
      return point;
    }
    let def = this.getCurrentDefinition();
    let min = { Col: -1, Row: -1 };
    let max = { Col: -1, Row: -1 };
    let Snap = this.SL.Utils.get('Snap');
    if (Snap) {
      let pointMin = Snap.PointMin(new paper.Point);
      min.Col = Math.ceil(pointMin.x / def.cell.width);
      min.Row = Math.ceil(pointMin.y / def.cell.height);

      let pointMax = Snap.PointMax(new paper.Point);
      max.Col = Math.floor(pointMax.x / def.cell.width);
      max.Row = Math.floor(pointMax.y / def.cell.height);
    }
    let newPoint = new paper.Point(def.offset).add(point);
    let target = {
      Col: Math.round(newPoint.x / def.cell.width),
      Row: Math.round(newPoint.y / def.cell.height)
    };
    if (min.Col >= 0 && target.Col < min.Col) {
      target.Col = min.Col;
    }
    else if (max.Col >= 0 && target.Col > max.Col) {
      target.Col = max.Col;
    }
    if (min.Row >= 0 && target.Row < min.Row) {
      target.Row = min.Row;
    }
    else if (max.Row >= 0 && target.Row > max.Row) {
      target.Row = max.Row;
    }
    newPoint.set({
      x: (target.Col * def.cell.width) - def.offset.x,
      y: (target.Row * def.cell.height) - def.offset.y
    });
    point.set(newPoint);
    return point;
  }
  snapRectangle(rectangle, config={}) {
    if (config && config.interactive && config.size === false) {
      return rectangle;
    }
    let interactive = !!(config && config.interactive);
    let Geo = this.SL.Utils.get('Geo');
    let anchor = config.anchor || rectangle.center;
    if (config.anchorEdge && Geo) {
      anchor = Geo.Direction.edgePoint(config.anchorEdge, rectangle);
    }
    let rectangleWidth = rectangle.width;
    let rectangleHeight = rectangle.height;
    let topLeft = new paper.Point({x: rectangle.left, y: rectangle.top});
    let bottomRight = new paper.Point({x: rectangle.right, y: rectangle.bottom});
    let pointMin, pointMax;
    let Snap = this.SL.Utils.get('Snap');
    if (Snap) {
      pointMin = Snap.PointMin(new paper.Point);
      pointMax = Snap.PointMax(new paper.Point);
    }
    let snapped = {
      left: undefined,
      top: undefined,
      right: undefined,
      bottom: undefined
    };

    // horizontal snapping
    if (anchor.x > rectangle.center.x) {
      // anchored at right, snap right edge first
      snapped.right = this.snapPoint(new paper.Point(bottomRight));
      if (!interactive) {
        snapped.left = this.snapPoint(new paper.Point(snapped.right.x-rectangleWidth, topLeft.y));
      }
      else {
        snapped.left = topLeft.clone();
      }
      if (pointMin != undefined && (snapped.left.x) < pointMin.x) {
        // if horizontal snapping by Right would exceed pointMin, re-snap horizontally based on Left
        snapped.left = this.snapPoint(new paper.Point(topLeft));
        if (!interactive) {
          snapped.right = this.snapPoint(new paper.Point(snapped.left.x+rectangleWidth, bottomRight.y));
        }
        else {
          snapped.right = bottomRight.clone();
        }
      }
    }
    else {
      // otherwise, snap left edge first
      // first try horizontal snapping based on Left
      snapped.left = this.snapPoint(new paper.Point(topLeft));
      if (!interactive) {
        snapped.right = this.snapPoint(new paper.Point(snapped.left.x+rectangleWidth, bottomRight.y));
      }
      else {
        snapped.right = bottomRight.clone();
      }
      if (pointMax != undefined && (snapped.left.x+rectangleWidth) > pointMax.x) {
        // if horizontal snapping by Left would exceed pointMax, re-snap horizontally based on Right
        snapped.right = this.snapPoint(new paper.Point(bottomRight));
        if (!interactive) {
          snapped.left = this.snapPoint(new paper.Point(snapped.right.x-rectangleWidth, topLeft.y));
        }
        else {
          snapped.left = topLeft.clone();
        }
      }
    }

    // vertical snapping
    if (anchor.y > rectangle.center.y) {
      // anchored at bottom, snap bottom edge first
      snapped.bottom = this.snapPoint(new paper.Point(bottomRight));
      if (!interactive) {
        snapped.top = this.snapPoint(new paper.Point(topLeft.x, snapped.bottom.y-rectangleHeight));
      }
      else {
        snapped.top = topLeft.clone();
      }
      if (pointMin != undefined && (snapped.top.y) < pointMin.y) {
        // if vertical snapping by Bottom would exceed pointMin, re-snap vertically based on Top
        snapped.top = this.snapPoint(new paper.Point(topLeft));
        if (!interactive) {
          snapped.bottom = this.snapPoint(new paper.Point(bottomRight.x, snapped.top.y+rectangleHeight));
        }
        else {
          snapped.bottom = bottomRight.clone();
        }
      }
    }
    else {
      // otherwise, snap top edge first
      // first try vertical snapping based on Top
      snapped.top = this.snapPoint(new paper.Point(topLeft));
      if (!interactive) {
        snapped.bottom = this.snapPoint(new paper.Point(bottomRight.x, snapped.top.y+rectangleHeight));
      }
      else {
        snapped.bottom = bottomRight.clone();
      }
      if (pointMax != undefined && (snapped.top.y+rectangleHeight) > pointMax.y) {
        // if vertical snapping by Top would exceed pointMax, re-snap vertically based on Bottom
        snapped.bottom = this.snapPoint(new paper.Point(bottomRight));
        if (!interactive) {
          snapped.top = this.snapPoint(new paper.Point(topLeft.x, snapped.bottom.y-rectangleHeight));
        }
        else {
          snapped.top = topLeft.clone();
        }
      }
    }

    // apply the snapped sides
    rectangle.set({
      top: snapped.top.y,
      left: snapped.left.x,
      bottom: snapped.bottom.y,
      right: snapped.right.x
    });
    return rectangle;
  }
  snapItem(item, config={}) {
    if (config && config.interactive) {
      return item;
    }
    let rotation = item.rotation;
    if (rotation) {
      item.rotate(-rotation);
    }

    if (item.segments) {
      for (let segment of item.segments) {
        let snapped = this.snapPoint(segment.point.clone(), config);
        segment.point.set(snapped);
      }
    }
    else if (item.bounds) {
      let snapped = this.snapRectangle(item.bounds.clone(), config);
      item.bounds.set(snapped);
    }

    if (rotation) {
      item.rotate(rotation);
    }
    return item;
  }

  renderGrid(style) {
    style = style || this.config.style || Grid.DEFAULT.style;
    let def = this.getCurrentDefinition();
    let grid = this.SL.Paper.generatePaperItem({Class:'UI', Layer:'BG', Grid:'GRID', Source: this}, paper.Group);
    let n, pt1, pt2;
    let strong = this.config.strong;
    let strongStyle = $.extend({}, style,
      { strokeWidth: (style.strokeWidth ? style.strokeWidth*2.0 : 2.0) },
      this.config.strongStyle);

    // render vertical lines
    n = 0;
    pt1 = new paper.Point(def.offset.x, def.offset.y);
    pt2 = new paper.Point(def.offset.x, def.offset.y+def.height);
    while (n <= def.cols) {
      pt2.x = pt1.x;
      let gridLine = this.SL.Paper.generatePaperItem({Class:['UI','UI.Grid'], Layer:'GROUPED', Grid:'COL-'+n}, paper.Path.Line, pt1, pt2);
      let applyStyle = style;
      if (strong && n % strong == 0) {
        applyStyle = strongStyle;
      }
      this.SL.Paper.applyStyle(gridLine, applyStyle);
      grid.addChild(gridLine);
      pt1.x += def.cell.width;
      n++;
    }

    // render horizontal lines
    n = 0;
    pt1 = new paper.Point(def.offset.x, def.offset.y);
    pt2 = new paper.Point(def.offset.x+def.width, def.offset.y);
    while (n <= def.rows) {
      pt2.y = pt1.y;
      let gridLine = this.SL.Paper.generatePaperItem({Class:['UI','UI.Grid'], Layer:'GROUPED', Grid:'ROW-'+n}, paper.Path.Line, pt1, pt2);
      let applyStyle = style;
      if (strong && n % strong == 0) {
        applyStyle = strongStyle;
      }
      this.SL.Paper.applyStyle(gridLine, applyStyle);
      grid.addChild(gridLine);
      pt1.y += def.cell.height;
      n++;
    }

    return grid;
  }
}
Grid.DEFAULT = {
  style: { strokeWidth: 1.5, strokeColor: (2.0/3.0), opacity: 0.25 }
};
<commit_msg>Pathched grid to not snapRectangle if config.size === false<commit_after>import Util from '../../core/util.js';
export class Grid extends Util {
  constructor(SL, config) {
    super(SL, config);
    this.name = 'Grid';
    this.configure();
  }
  reset() {
    super.reset();
    this.unregisterSnappers();
  }

  configure(config) {
    config = super.configure(config);

    // base definition
    this.definition = {
      offset: { x: 0, y: 0 },
      width: '100%',
      height: '100%',
      cell: {},
      cols: '+',
      rows: '+'
    };

    // use config to customize the definition
    if (config.offset) {
      this.definition.offset.x = config.offset.x || this.definition.offset.x;
      this.definition.offset.y = config.offset.y || this.definition.offset.y;
    }
    this.definition.width = config.width || this.definition.width;
    this.definition.height = config.height || this.definition.height;

    this.definition.cell.width = (config.cell && config.cell.width) || (config.cell && config.cell.size) || config.size || this.definition.cell.width;
    this.definition.cell.height = (config.cell && config.cell.height) || (config.cell && config.cell.size) || config.size || this.definition.cell.height;

    this.definition.cols = config.cols || this.definition.cols;
    this.definition.rows = config.rows || this.definition.rows;

    // set things up
    this.registerSnappers();
    this.renderGrid();

    return this.config;
  }

  getCurrentDefinition() {
    let currentDef = {};
    let Parser = this.SL.Utils.gets('ConfigParser');
    let View = this.SL.Paper.view;
    let Def = this.definition;
    let parsed;

    // parse grid offset
    currentDef.offset = { x: 0, y: 0 };
    parsed = Parser.parseNumber(Def.offset.x);
    currentDef.offset.x = parsed.value;
    if (parsed.percent) {
      currentDef.offset.x *= View.size.width;
    }
    parsed = Parser.parseNumber(Def.offset.y);
    currentDef.offset.y = parsed.value;
    if (parsed.percent) {
      currentDef.offset.y *= View.size.height;
    }

    // parse grid width
    currentDef.width = View.size.width;
    parsed = Parser.parseNumber(Def.width);
    if (!parsed.value) {
      parsed.value = 1.0;
      parsed.percent = true;
    }
    if (parsed.percent) {
      currentDef.width *= parsed.value;
    }
    else if (parsed.value) {
      currentDef.width = parsed.value;
    }

    // parse grid height
    currentDef.height = View.size.height;
    parsed = Parser.parseNumber(Def.height);
    if (!parsed.value) {
      parsed.value = 1.0;
      parsed.percent = true;
    }
    if (parsed.percent) {
      currentDef.height *= parsed.value;
    }
    else if (parsed.value) {
      currentDef.height = parsed.value;
    }

    // read row and col preferences
    let cols = Parser.parseNumber(Def.cols);
    let rows = Parser.parseNumber(Def.rows);
    currentDef.cell = {};

    // calculate cell width + cols
    parsed = Parser.parseNumber(Def.cell.width);
    currentDef.cell.width = parsed.value;
    if (!parsed.value) {
      if (cols.value) {
        currentDef.cell.width = currentDef.width / cols.value;
      }
    }
    else if (parsed.percent) {
      currentDef.cell.width = currentDef.width * parsed.value;
    }
    currentDef.cell.width = Parser.unparseNumber(currentDef.cell.width, parsed);

    // calculate cell height + rows
    parsed = Parser.parseNumber(Def.cell.height);
    currentDef.cell.height = parsed.value;
    if (!parsed.value) {
      if (rows.value) {
        currentDef.cell.height = currentDef.height / rows.value;
      }
    }
    else if (parsed.percent) {
      currentDef.cell.height = currentDef.height * parsed.value;
    }
    currentDef.cell.height = Parser.unparseNumber(currentDef.cell.height, parsed);

    // if width or height is missing, try to use the other
    currentDef.cell.width = currentDef.cell.width || currentDef.cell.height;
    currentDef.cell.height = currentDef.cell.height || currentDef.cell.width;

    // finalize number of cols
    currentDef.cols = currentDef.width / currentDef.cell.width;
    currentDef.cols = Parser.unparseNumber(currentDef.cols, cols);
    // finalize number of rows
    currentDef.rows = currentDef.height / currentDef.cell.height;
    currentDef.rows = Parser.unparseNumber(currentDef.rows, rows);

    return currentDef;
  }

  registerSnappers() {
    let Snap = this.SL.Utils.get('Snap');
    if (Snap) {
      if (!this.Snappers) {
        this.Snappers = {};
      }
      this.Snappers.point = Snap.addSnapper('point', {
        priority: 75,
        callback: (point, config) => {
          return this.snapPoint(point, config);
        }
      });
      this.Snappers.rectangle = Snap.addSnapper('rectangle', {
        priority: 75,
        callback: (rectangle, config) => {
          return this.snapRectangle(rectangle, config);
        }
      });
      this.Snappers.item = Snap.addSnapper('item', {
        priority: 75,
        callback: (item, config) => {
          return this.snapItem(item, config);
        }
      });
    }
  }
  unregisterSnappers() {
    let Snap = this.SL.Utils.get('Snap');
    if (!Snap || !this.Snappers) {
      return;
    }
    if (this.Snappers.point) {
      Snap.dropSnapper('point', this.Snappers.point.id);
      this.Snappers.point = undefined;
    }
    if (Snap && this.Snappers.rectangle) {
      Snap.dropSnapper('rectangle', this.Snappers.rectangle.id);
      this.Snappers.rectangle = undefined;
    }
    if (Snap && this.Snappers.item) {
      Snap.dropSnapper('item', this.Snappers.item.id);
      this.Snappers.item = undefined;
    }
  }
  snapPoint(point, config={}) {
    if (config && config.interactive) {
      return point;
    }
    let def = this.getCurrentDefinition();
    let min = { Col: -1, Row: -1 };
    let max = { Col: -1, Row: -1 };
    let Snap = this.SL.Utils.get('Snap');
    if (Snap) {
      let pointMin = Snap.PointMin(new paper.Point);
      min.Col = Math.ceil(pointMin.x / def.cell.width);
      min.Row = Math.ceil(pointMin.y / def.cell.height);

      let pointMax = Snap.PointMax(new paper.Point);
      max.Col = Math.floor(pointMax.x / def.cell.width);
      max.Row = Math.floor(pointMax.y / def.cell.height);
    }
    let newPoint = new paper.Point(def.offset).add(point);
    let target = {
      Col: Math.round(newPoint.x / def.cell.width),
      Row: Math.round(newPoint.y / def.cell.height)
    };
    if (min.Col >= 0 && target.Col < min.Col) {
      target.Col = min.Col;
    }
    else if (max.Col >= 0 && target.Col > max.Col) {
      target.Col = max.Col;
    }
    if (min.Row >= 0 && target.Row < min.Row) {
      target.Row = min.Row;
    }
    else if (max.Row >= 0 && target.Row > max.Row) {
      target.Row = max.Row;
    }
    newPoint.set({
      x: (target.Col * def.cell.width) - def.offset.x,
      y: (target.Row * def.cell.height) - def.offset.y
    });
    point.set(newPoint);
    return point;
  }
  snapRectangle(rectangle, config={}) {
    if (config && config.size === false) {
      return rectangle;
    }
    let interactive = !!(config && config.interactive);
    let Geo = this.SL.Utils.get('Geo');
    let anchor = config.anchor || rectangle.center;
    if (config.anchorEdge && Geo) {
      anchor = Geo.Direction.edgePoint(config.anchorEdge, rectangle);
    }
    let rectangleWidth = rectangle.width;
    let rectangleHeight = rectangle.height;
    let topLeft = new paper.Point({x: rectangle.left, y: rectangle.top});
    let bottomRight = new paper.Point({x: rectangle.right, y: rectangle.bottom});
    let pointMin, pointMax;
    let Snap = this.SL.Utils.get('Snap');
    if (Snap) {
      pointMin = Snap.PointMin(new paper.Point);
      pointMax = Snap.PointMax(new paper.Point);
    }
    let snapped = {
      left: undefined,
      top: undefined,
      right: undefined,
      bottom: undefined
    };

    // horizontal snapping
    if (anchor.x > rectangle.center.x) {
      // anchored at right, snap right edge first
      snapped.right = this.snapPoint(new paper.Point(bottomRight));
      if (!interactive) {
        snapped.left = this.snapPoint(new paper.Point(snapped.right.x-rectangleWidth, topLeft.y));
      }
      else {
        snapped.left = topLeft.clone();
      }
      if (pointMin != undefined && (snapped.left.x) < pointMin.x) {
        // if horizontal snapping by Right would exceed pointMin, re-snap horizontally based on Left
        snapped.left = this.snapPoint(new paper.Point(topLeft));
        if (!interactive) {
          snapped.right = this.snapPoint(new paper.Point(snapped.left.x+rectangleWidth, bottomRight.y));
        }
        else {
          snapped.right = bottomRight.clone();
        }
      }
    }
    else {
      // otherwise, snap left edge first
      // first try horizontal snapping based on Left
      snapped.left = this.snapPoint(new paper.Point(topLeft));
      if (!interactive) {
        snapped.right = this.snapPoint(new paper.Point(snapped.left.x+rectangleWidth, bottomRight.y));
      }
      else {
        snapped.right = bottomRight.clone();
      }
      if (pointMax != undefined && (snapped.left.x+rectangleWidth) > pointMax.x) {
        // if horizontal snapping by Left would exceed pointMax, re-snap horizontally based on Right
        snapped.right = this.snapPoint(new paper.Point(bottomRight));
        if (!interactive) {
          snapped.left = this.snapPoint(new paper.Point(snapped.right.x-rectangleWidth, topLeft.y));
        }
        else {
          snapped.left = topLeft.clone();
        }
      }
    }

    // vertical snapping
    if (anchor.y > rectangle.center.y) {
      // anchored at bottom, snap bottom edge first
      snapped.bottom = this.snapPoint(new paper.Point(bottomRight));
      if (!interactive) {
        snapped.top = this.snapPoint(new paper.Point(topLeft.x, snapped.bottom.y-rectangleHeight));
      }
      else {
        snapped.top = topLeft.clone();
      }
      if (pointMin != undefined && (snapped.top.y) < pointMin.y) {
        // if vertical snapping by Bottom would exceed pointMin, re-snap vertically based on Top
        snapped.top = this.snapPoint(new paper.Point(topLeft));
        if (!interactive) {
          snapped.bottom = this.snapPoint(new paper.Point(bottomRight.x, snapped.top.y+rectangleHeight));
        }
        else {
          snapped.bottom = bottomRight.clone();
        }
      }
    }
    else {
      // otherwise, snap top edge first
      // first try vertical snapping based on Top
      snapped.top = this.snapPoint(new paper.Point(topLeft));
      if (!interactive) {
        snapped.bottom = this.snapPoint(new paper.Point(bottomRight.x, snapped.top.y+rectangleHeight));
      }
      else {
        snapped.bottom = bottomRight.clone();
      }
      if (pointMax != undefined && (snapped.top.y+rectangleHeight) > pointMax.y) {
        // if vertical snapping by Top would exceed pointMax, re-snap vertically based on Bottom
        snapped.bottom = this.snapPoint(new paper.Point(bottomRight));
        if (!interactive) {
          snapped.top = this.snapPoint(new paper.Point(topLeft.x, snapped.bottom.y-rectangleHeight));
        }
        else {
          snapped.top = topLeft.clone();
        }
      }
    }

    // apply the snapped sides
    rectangle.set({
      top: snapped.top.y,
      left: snapped.left.x,
      bottom: snapped.bottom.y,
      right: snapped.right.x
    });
    return rectangle;
  }
  snapItem(item, config={}) {
    if (config && config.interactive) {
      return item;
    }
    let rotation = item.rotation;
    if (rotation) {
      item.rotate(-rotation);
    }

    if (item.segments) {
      for (let segment of item.segments) {
        let snapped = this.snapPoint(segment.point.clone(), config);
        segment.point.set(snapped);
      }
    }
    else if (item.bounds) {
      let snapped = this.snapRectangle(item.bounds.clone(), config);
      item.bounds.set(snapped);
    }

    if (rotation) {
      item.rotate(rotation);
    }
    return item;
  }

  renderGrid(style) {
    style = style || this.config.style || Grid.DEFAULT.style;
    let def = this.getCurrentDefinition();
    let grid = this.SL.Paper.generatePaperItem({Class:'UI', Layer:'BG', Grid:'GRID', Source: this}, paper.Group);
    let n, pt1, pt2;
    let strong = this.config.strong;
    let strongStyle = $.extend({}, style,
      { strokeWidth: (style.strokeWidth ? style.strokeWidth*2.0 : 2.0) },
      this.config.strongStyle);

    // render vertical lines
    n = 0;
    pt1 = new paper.Point(def.offset.x, def.offset.y);
    pt2 = new paper.Point(def.offset.x, def.offset.y+def.height);
    while (n <= def.cols) {
      pt2.x = pt1.x;
      let gridLine = this.SL.Paper.generatePaperItem({Class:['UI','UI.Grid'], Layer:'GROUPED', Grid:'COL-'+n}, paper.Path.Line, pt1, pt2);
      let applyStyle = style;
      if (strong && n % strong == 0) {
        applyStyle = strongStyle;
      }
      this.SL.Paper.applyStyle(gridLine, applyStyle);
      grid.addChild(gridLine);
      pt1.x += def.cell.width;
      n++;
    }

    // render horizontal lines
    n = 0;
    pt1 = new paper.Point(def.offset.x, def.offset.y);
    pt2 = new paper.Point(def.offset.x+def.width, def.offset.y);
    while (n <= def.rows) {
      pt2.y = pt1.y;
      let gridLine = this.SL.Paper.generatePaperItem({Class:['UI','UI.Grid'], Layer:'GROUPED', Grid:'ROW-'+n}, paper.Path.Line, pt1, pt2);
      let applyStyle = style;
      if (strong && n % strong == 0) {
        applyStyle = strongStyle;
      }
      this.SL.Paper.applyStyle(gridLine, applyStyle);
      grid.addChild(gridLine);
      pt1.y += def.cell.height;
      n++;
    }

    return grid;
  }
}
Grid.DEFAULT = {
  style: { strokeWidth: 1.5, strokeColor: (2.0/3.0), opacity: 0.25 }
};
<|endoftext|>


<commit_before>'use strict';

var bridge = require('../');
var expect = require('chai').expect;

describe('Bid', function() {
    it('has level and denonimation', function() {
        var bid = bridge.bid['1NT'];
        expect(bid.level).to.equal(1);
        expect(bid.denomination).to.equal('NT');

        bid = bridge.bid['7S'];
        expect(bid.level).to.equal(7);
        expect(bid.denomination).to.equal('S');
    });

    it('can be pass', function() {
        var bid = bridge.bid['-'];
        expect(bid.isPass).to.be.true;
    });

    it('can be double', function() {
        var bid = bridge.bid['X'];
        expect(bid.isDouble).to.be.true;
    });

    it('can be redouble', function() {
        var bid = bridge.bid['XX'];
        expect(bid.isRedouble).to.be.true;
    });

    it('has suit color', function() {
        expect(bridge.bid['1C'].isRed).to.be.false;
        expect(bridge.bid['1D'].isRed).to.be.true;
        expect(bridge.bid['1H'].isRed).to.be.true;
        expect(bridge.bid['1S'].isRed).to.be.false;
    });

    it('is ordered by denomination within level', function() {
        var club = bridge.bid['1C'];
        var diamond = bridge.bid['1D'];
        var heart = bridge.bid['1H'];
        var spade = bridge.bid['1S'];
        var notrump = bridge.bid['1NT'];
        expect(club.order).below(diamond.order);
        expect(diamond.order).below(heart.order);
        expect(heart.order).below(spade.order);
        expect(spade.order).below(notrump.order);
    });

    it('is ordered by level', function() {
        expect(bridge.bid['1C'].order).below(bridge.bid['2C'].order);
        expect(bridge.bid['2C'].order).below(bridge.bid['3C'].order);
        expect(bridge.bid['3C'].order).below(bridge.bid['4C'].order);
        expect(bridge.bid['4C'].order).below(bridge.bid['5C'].order);
        expect(bridge.bid['5C'].order).below(bridge.bid['6C'].order);
        expect(bridge.bid['6C'].order).below(bridge.bid['7C'].order);
    });

    it('1C is lowest', function() {
        expect(bridge.bid['1C'].order).to.equal(1);
    });

    it('7NT is highest', function() {
        expect(bridge.bid['7NT'].order).to.equal(35);
    });

});
<commit_msg>100% coverage!<commit_after>'use strict';

var bridge = require('../');
var expect = require('chai').expect;

describe('Bid', function() {
    it('has level and denonimation', function() {
        var bid = bridge.bid['1NT'];
        expect(bid.level).to.equal(1);
        expect(bid.denomination).to.equal('NT');

        bid = bridge.bid['7S'];
        expect(bid.level).to.equal(7);
        expect(bid.denomination).to.equal('S');
    });

    it('can be pass', function() {
        var bid = bridge.bid['-'];
        expect(bid.isPass).to.be.true;
    });

    it('can be double', function() {
        var bid = bridge.bid['X'];
        expect(bid.isDouble).to.be.true;
    });

    it('can be redouble', function() {
        var bid = bridge.bid['XX'];
        expect(bid.isRedouble).to.be.true;
    });

    it('has suit color', function() {
        expect(bridge.bid['1C'].isRed).to.be.false;
        expect(bridge.bid['1D'].isRed).to.be.true;
        expect(bridge.bid['1H'].isRed).to.be.true;
        expect(bridge.bid['1S'].isRed).to.be.false;
    });

    it('is ordered by denomination within level', function() {
        var club = bridge.bid['1C'];
        var diamond = bridge.bid['1D'];
        var heart = bridge.bid['1H'];
        var spade = bridge.bid['1S'];
        var notrump = bridge.bid['1NT'];
        expect(club.order).below(diamond.order);
        expect(diamond.order).below(heart.order);
        expect(heart.order).below(spade.order);
        expect(spade.order).below(notrump.order);
    });

    it('is ordered by level', function() {
        expect(bridge.bid['1C'].order).below(bridge.bid['2C'].order);
        expect(bridge.bid['2C'].order).below(bridge.bid['3C'].order);
        expect(bridge.bid['3C'].order).below(bridge.bid['4C'].order);
        expect(bridge.bid['4C'].order).below(bridge.bid['5C'].order);
        expect(bridge.bid['5C'].order).below(bridge.bid['6C'].order);
        expect(bridge.bid['6C'].order).below(bridge.bid['7C'].order);
    });

    it('1C is lowest', function() {
        expect(bridge.bid['1C'].order).to.equal(1);
    });

    it('7NT is highest', function() {
        expect(bridge.bid['7NT'].order).to.equal(35);
    });

    it('string representation is the bid', function() {
        expect(bridge.bid['3D'] + '').to.equal('3D');
        expect(bridge.bid.pass + '').to.equal('-');
        expect(bridge.bid.double + '').to.equal('X');
        expect(bridge.bid.redouble + '').to.equal('XX');
    });

});
<|endoftext|>