<commit_before>// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License.  You may obtain a copy
// of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
// License for the specific language governing permissions and limitations under
// the License.

(function($) {
  $.couch = $.couch || {}
  $.fn.extend($.couch, {

    allDbs: function(options) {
      $.ajax({
        type: "GET", url: "/_all_dbs",
        complete: function(req) {
          var resp = $.httpData(req, "json");
          if (req.status == 200 && options.success) {
            options.success(resp);
          } else if (options.error) {
            options.error(req.status, resp.error, resp.reason);
          } else {
            alert("An error occurred retrieving the list of all databases: " +
              resp.reason);
          }
        }
      });
    },

    db: function(name) {
      return {
        name: name,
        uri: "/" + encodeURIComponent(name) + "/",

        compact: function(options) {
          $.ajax({
            type: "POST", url: this.uri + "_compact", dataType: "json",
            complete: function(req) {
              var resp = $.httpData(req, "json");
              if (req.status == 202 && options.success) {
                options.success(resp);
              } else if (options.error) {
                options.error(req.status, resp.error, resp.reason);
              } else {
                alert("The database could not be compacted: " + resp.reason);
              }
            }
          });
        },
        create: function(options) {
          $.ajax({
            type: "PUT", url: this.uri, dataType: "json",
            complete: function(req) {
              var resp = $.httpData(req, "json");
              if (req.status == 201 && options.success) {
                options.success(resp);
              } else if (options.error) {
                options.error(req.status, resp.error, resp.reason);
              } else {
                alert("The database could not be created: " + resp.reason);
              }
            }
          });
        },
        drop: function(options) {
          $.ajax({
            type: "DELETE", url: this.uri, dataType: "json",
            complete: function(req) {
              var resp = $.httpData(req, "json");
              if (req.status == 202 && options.success) {
                options.success(resp);
              } else if (options.error) {
                options.error(req.status, resp.error, resp.reason);
              } else {
                alert("The database could not be deleted: " + resp.reason);
              }
            }
          });
        },
        info: function(options) {
          $.ajax({
            type: "GET", url: this.uri, dataType: "json",
            complete: function(req) {
              var resp = $.httpData(req, "json");
              if (req.status == 200 && options.success) {
                options.success(resp);
              } else  if (options.error) {
                options.error(req.status, resp.error, resp.reason);
              } else {
                alert("Database information could not be retrieved: " +
                  resp.reason);
              }
            }
          });
        },
        allDocs: function(options) {
          $.ajax({
            type: "GET", url: this.uri + "_all_docs" + encodeOptions(options),
            dataType: "json",
            complete: function(req) {
              var resp = $.httpData(req, "json");
              if (req.status == 200 && options.success) {
                options.success(resp);
              } else if (options.error) {
                options.error(req.status, resp.error, resp.reason);
              } else {
                alert("An error occurred retrieving a list of all documents: " +
                  resp.reason);
              }
            },
          });
        },
        openDoc: function(docId, options) {
          $.ajax({
            type: "GET",
            url: this.uri + encodeURIComponent(docId) + encodeOptions(options),
            dataType: "json",
            complete: function(req) {
              var resp = $.httpData(req, "json");
              if (req.status == 200 && options.success) {
                options.success(resp);
              } else if (options.error) {
                options.error(req.status, resp.error, resp.reason);
              } else {
                alert("The document could not be retrieved: " + resp.reason);
              }
            }
          });
        },
        saveDoc: function(doc, options) {
          if (doc._id === undefined) {
            var method = "POST";
            var uri = this.uri;
          } else {
            var method = "PUT";
            var uri = this.uri  + encodeURIComponent(doc._id);
          }
          $.ajax({
            type: method, url: uri + encodeOptions(options),
            dataType: "json", data: toJSON(doc),
            contentType: "application/json",
            complete: function(req) {
              var resp = $.httpData(req, "json")
              doc._id = resp.id;
              doc._rev = resp.rev;
              if (req.status == 201 && options.success) {
                options.success(resp);
              } else if (options.error) {
                options.error(req.status, resp.error, resp.reason);
              } else {
                alert("The document could not be saved: " + resp.reason);
              }
            }
          });
        },
        removeDoc: function(doc, options) {
          $.ajax({
            type: "DELETE",
            url: this.uri + encodeURIComponent(doc._id) + encodeOptions({rev: doc._rev}),
            dataType: "json",
            complete: function(req) {
              var resp = $.httpData(req, "json");
              if (req.status == 202 && options.success) {
                options.success(resp);
              } else if (options.error) {
                options.error(req.status, resp.error, resp.reason);
              } else {
                alert("The document could not be deleted: " + resp.reason);
              }
            }
          });
        },
        query: function(fun, options) {
          if (typeof(fun) != "string")
            fun = fun.toSource ? fun.toSource() : "(" + fun.toString() + ")";
          $.ajax({
            type: "POST", url: this.uri + "_temp_view" + encodeOptions(options),
            contentType: "application/json",
            data: toJSON({language:"javascript", map:fun}),
            dataType: "json",
            complete: function(req) {
              var resp = $.httpData(req, "json");
              if (req.status == 200 && options.success) {
                options.success(resp);
              } else if (options.error) {
                options.error(req.status, resp.error, resp.reason);
              } else {
                alert("An error occurred querying the database: " + resp.reason);
              }
            }
          });
        },
        view: function(name, options) {
          $.ajax({
            type: "GET", url: this.uri + "_view/" + name + encodeOptions(options),
            dataType: "json",
            complete: function(req) {
              var resp = $.httpData(req, "json");
              if (req.status == 200 && options.success) {
                options.success(resp);
              } else if (options.error) {
                options.error(req.status, resp.error, resp.reason);
              } else {
                alert("An error occurred accessing the view: " + resp.reason);
              }
            }
          });
        }
      };
    },

    info: function(options) {
      $.ajax({
        type: "GET", url: "/", dataType: "json",
        complete: function(req) {
          var resp = $.httpData(req, "json");
          if (req.status == 200 && options.success) {
            options.success(resp);
          } else if (options.error) {
            options.error(req.status, resp.error, resp.reason);
          } else {
            alert("Server information could not be retrieved: " + resp.reason);
          }
        }
      });
    },

    replicate: function(source, target, options) {
      $.ajax({
        type: "POST", url: "/_replicate", dataType: "json",
        data: JSON.stringify({source: source, target: target}),
        contentType: "application/json",
        complete: function(req) {
          var resp = $.httpData(req, "json");
          if (req.status == 200 && options.success) {
            options.success(resp);
          } else if (options.error) {
            options.error(req.status, resp.error, resp.reason);
          } else {
            alert("Replication failed: " + resp.reason);
          }
        }
      });
    }

  });

  // Convert a options object to an url query string.
  // ex: {key:'value',key2:'value2'} becomes '?key="value"&key2="value2"'
  function encodeOptions(options) {
    var buf = []
    if (typeof(options) == "object" && options !== null) {
      for (var name in options) {
        if (name == "error" || name == "success") continue;
        var value = options[name];
        if (name == "key" || name == "startkey" || name == "endkey") {
          value = toJSON(value);
        }
        buf.push(encodeURIComponent(name) + "=" + encodeURIComponent(value));
      }
    }
    return buf.length ? "?" + buf.join("&") : "";
  }

  function toJSON(obj) {
    return obj !== null ? JSON.stringify(obj) : null;
  }

})(jQuery);
<commit_msg>Initial set of changes to Futon to support the recent introduction map/reduce.<commit_after>// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License.  You may obtain a copy
// of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
// License for the specific language governing permissions and limitations under
// the License.

(function($) {
  $.couch = $.couch || {}
  $.fn.extend($.couch, {

    allDbs: function(options) {
      options = options || {};
      $.ajax({
        type: "GET", url: "/_all_dbs",
        complete: function(req) {
          var resp = $.httpData(req, "json");
          if (req.status == 200 && options.success) {
            options.success(resp);
          } else if (options.error) {
            options.error(req.status, resp.error, resp.reason);
          } else {
            alert("An error occurred retrieving the list of all databases: " +
              resp.reason);
          }
        }
      });
    },

    db: function(name) {
      return {
        name: name,
        uri: "/" + encodeURIComponent(name) + "/",

        compact: function(options) {
          options = options || {};
          $.ajax({
            type: "POST", url: this.uri + "_compact", dataType: "json",
            complete: function(req) {
              var resp = $.httpData(req, "json");
              if (req.status == 202 && options.success) {
                options.success(resp);
              } else if (options.error) {
                options.error(req.status, resp.error, resp.reason);
              } else {
                alert("The database could not be compacted: " + resp.reason);
              }
            }
          });
        },
        create: function(options) {
          options = options || {};
          $.ajax({
            type: "PUT", url: this.uri, dataType: "json",
            complete: function(req) {
              var resp = $.httpData(req, "json");
              if (req.status == 201 && options.success) {
                options.success(resp);
              } else if (options.error) {
                options.error(req.status, resp.error, resp.reason);
              } else {
                alert("The database could not be created: " + resp.reason);
              }
            }
          });
        },
        drop: function(options) {
          options = options || {};
          $.ajax({
            type: "DELETE", url: this.uri, dataType: "json",
            complete: function(req) {
              var resp = $.httpData(req, "json");
              if (req.status == 202 && options.success) {
                options.success(resp);
              } else if (options.error) {
                options.error(req.status, resp.error, resp.reason);
              } else {
                alert("The database could not be deleted: " + resp.reason);
              }
            }
          });
        },
        info: function(options) {
          options = options || {};
          $.ajax({
            type: "GET", url: this.uri, dataType: "json",
            complete: function(req) {
              var resp = $.httpData(req, "json");
              if (req.status == 200 && options.success) {
                options.success(resp);
              } else  if (options.error) {
                options.error(req.status, resp.error, resp.reason);
              } else {
                alert("Database information could not be retrieved: " +
                  resp.reason);
              }
            }
          });
        },
        allDocs: function(options) {
          options = options || {};
          $.ajax({
            type: "GET", url: this.uri + "_all_docs" + encodeOptions(options),
            dataType: "json",
            complete: function(req) {
              var resp = $.httpData(req, "json");
              if (req.status == 200 && options.success) {
                options.success(resp);
              } else if (options.error) {
                options.error(req.status, resp.error, resp.reason);
              } else {
                alert("An error occurred retrieving a list of all documents: " +
                  resp.reason);
              }
            },
          });
        },
        openDoc: function(docId, options) {
          options = options || {};
          $.ajax({
            type: "GET",
            url: this.uri + encodeURIComponent(docId) + encodeOptions(options),
            dataType: "json",
            complete: function(req) {
              var resp = $.httpData(req, "json");
              if (req.status == 200 && options.success) {
                options.success(resp);
              } else if (options.error) {
                options.error(req.status, resp.error, resp.reason);
              } else {
                alert("The document could not be retrieved: " + resp.reason);
              }
            }
          });
        },
        saveDoc: function(doc, options) {
          options = options || {};
          if (doc._id === undefined) {
            var method = "POST";
            var uri = this.uri;
          } else {
            var method = "PUT";
            var uri = this.uri  + encodeURIComponent(doc._id);
          }
          $.ajax({
            type: method, url: uri + encodeOptions(options),
            dataType: "json", data: toJSON(doc),
            contentType: "application/json",
            complete: function(req) {
              var resp = $.httpData(req, "json")
              doc._id = resp.id;
              doc._rev = resp.rev;
              if (req.status == 201 && options.success) {
                options.success(resp);
              } else if (options.error) {
                options.error(req.status, resp.error, resp.reason);
              } else {
                alert("The document could not be saved: " + resp.reason);
              }
            }
          });
        },
        removeDoc: function(doc, options) {
          options = options || {};
          $.ajax({
            type: "DELETE",
            url: this.uri + encodeURIComponent(doc._id) + encodeOptions({rev: doc._rev}),
            dataType: "json",
            complete: function(req) {
              var resp = $.httpData(req, "json");
              if (req.status == 202 && options.success) {
                options.success(resp);
              } else if (options.error) {
                options.error(req.status, resp.error, resp.reason);
              } else {
                alert("The document could not be deleted: " + resp.reason);
              }
            }
          });
        },
        query: function(mapFun, reduceFun, language, options) {
          options = options || {};
          language = language || "javascript"
          if (typeof(mapFun) != "string") {
            mapFun = mapFun.toSource ? mapFun.toSource() : "(" + mapFun.toString() + ")";
          }
          var body = {language: language, map: mapFun};
          if (reduceFun != null) {
            if (typeof(reduceFun) != "string")
              reduceFun = reduceFun.toSource ? reduceFun.toSource() : "(" + reduceFun.toString() + ")";
            body.reduce = reduceFun;
          }
          $.ajax({
            type: "POST", url: this.uri + "_temp_view" + encodeOptions(options),
            contentType: "application/json",
            data: toJSON(body), dataType: "json",
            complete: function(req) {
              var resp = $.httpData(req, "json");
              if (req.status == 200 && options.success) {
                options.success(resp);
              } else if (options.error) {
                options.error(req.status, resp.error, resp.reason);
              } else {
                alert("An error occurred querying the database: " + resp.reason);
              }
            }
          });
        },
        view: function(name, options) {
          options = options || {};
          $.ajax({
            type: "GET", url: this.uri + "_view/" + name + encodeOptions(options),
            dataType: "json",
            complete: function(req) {
              var resp = $.httpData(req, "json");
              if (req.status == 200 && options.success) {
                options.success(resp);
              } else if (options.error) {
                options.error(req.status, resp.error, resp.reason);
              } else {
                alert("An error occurred accessing the view: " + resp.reason);
              }
            }
          });
        }
      };
    },

    info: function(options) {
      options = options || {};
      $.ajax({
        type: "GET", url: "/", dataType: "json",
        complete: function(req) {
          var resp = $.httpData(req, "json");
          if (req.status == 200 && options.success) {
            options.success(resp);
          } else if (options.error) {
            options.error(req.status, resp.error, resp.reason);
          } else {
            alert("Server information could not be retrieved: " + resp.reason);
          }
        }
      });
    },

    replicate: function(source, target, options) {
      options = options || {};
      $.ajax({
        type: "POST", url: "/_replicate", dataType: "json",
        data: JSON.stringify({source: source, target: target}),
        contentType: "application/json",
        complete: function(req) {
          var resp = $.httpData(req, "json");
          if (req.status == 200 && options.success) {
            options.success(resp);
          } else if (options.error) {
            options.error(req.status, resp.error, resp.reason);
          } else {
            alert("Replication failed: " + resp.reason);
          }
        }
      });
    }

  });

  // Convert a options object to an url query string.
  // ex: {key:'value',key2:'value2'} becomes '?key="value"&key2="value2"'
  function encodeOptions(options) {
    var buf = []
    if (typeof(options) == "object" && options !== null) {
      for (var name in options) {
        if (name == "error" || name == "success") continue;
        var value = options[name];
        if (name == "key" || name == "startkey" || name == "endkey") {
          value = toJSON(value);
        }
        buf.push(encodeURIComponent(name) + "=" + encodeURIComponent(value));
      }
    }
    return buf.length ? "?" + buf.join("&") : "";
  }

  function toJSON(obj) {
    return obj !== null ? JSON.stringify(obj) : null;
  }

})(jQuery);
<|endoftext|>


<commit_before>(function($){
	// This function adds noise to the background-color attribute of an element
	$.fn.noisy = function(options) {
		return this.each(function() {
	 		var canvas = document.createElement('canvas'),
	 		    ctx = canvas.getContext("2d");

	 		var defaults = {
	 			// How many percent of the image that is filled with noise,
	 			//   represented by a number between 0 and 1 inclusive
	 			noise:              1,

	 			// The width and height of the background image in pixels
	 			tileSize:           200,

	 			// An empty string is transparent
	 			backgroundColor:    '',

	 			// The maximum noise particle opacity,
	 			//   represented by a number between 0 and 1 inclusive
	 			maxNoiseOpacity:    20/255,

	 			// A string linking to the image used if there's no canvas support
	 			fallbackImage:      undefined,

	 			// Specifies wheter the particles are grayscale or colorful
	 			monochromatic:      false
	 		};
	 		options = $.extend(defaults, options);

	 		// Use fallback image if canvas isn't supported
	 		if ((options.fallbackImage !== undefined) && !ctx) {
	 			$(this).css('background-image', options.fallbackImage);
	 			return;
	 		}

	 		canvas.width = canvas.height = options.tileSize;
	 		var imgData = ctx.createImageData(canvas.width, canvas.height);

	 		var rand = function(min, max) {
	 			return Math.floor(Math.random()*(max-min)+min);
	 		};

	 		// Add black and white pixels at random positions to the canvas
	 		for (var i = 0; i < options.noise * Math.pow(options.tileSize, 2); i++) {
	 			var x = rand(1, canvas.width),
	 			    y = rand(1, canvas.height),
	 			    index = (x + y * imgData.width) * 4;

	 			if (options.monochromatic) var randColorChannel = rand(0, 255);

	 			imgData.data[index  ] = randColorChannel;                                           // red
	 			imgData.data[index+1] = options.monochromatic ? randColorChannel : rand(0, 255);    // green
	 			imgData.data[index+2] = options.monochromatic ? randColorChannel : rand(0, 255);    // blue
	 			imgData.data[index+3] = rand(0, 255*options.maxNoiseOpacity);                       // alpha
	 		}

	 		ctx.putImageData(imgData, 0, 0);
	 		$(this).css('background', 'url(' + canvas.toDataURL('image/png') + ')' + options.backgroundColor);
		});
	};
})(jQuery);<commit_msg>'x,y of the random noise particles was excluding the 0th row/column of the image' ~Ignition<commit_after>(function($){
	// This function adds noise to the background-color attribute of an element
	$.fn.noisy = function(options) {
		return this.each(function() {
	 		var canvas = document.createElement('canvas'),
	 		    ctx = canvas.getContext("2d");

	 		var defaults = {
	 			// How many percent of the image that is filled with noise,
	 			//   represented by a number between 0 and 1 inclusive
	 			noise:              1,

	 			// The width and height of the background image in pixels
	 			tileSize:           200,

	 			// An empty string is transparent
	 			backgroundColor:    '',

	 			// The maximum noise particle opacity,
	 			//   represented by a number between 0 and 1 inclusive
	 			maxNoiseOpacity:    20/255,

	 			// A string linking to the image used if there's no canvas support
	 			fallbackImage:      undefined,

	 			// Specifies wheter the particles are grayscale or colorful
	 			monochromatic:      false
	 		};
	 		options = $.extend(defaults, options);

	 		// Use fallback image if canvas isn't supported
	 		if ((options.fallbackImage !== undefined) && !ctx) {
	 			$(this).css('background-image', options.fallbackImage);
	 			return;
	 		}

	 		canvas.width = canvas.height = options.tileSize;
	 		var imgData = ctx.createImageData(canvas.width, canvas.height);

	 		var rand = function(min, max) {
	 			return Math.floor(Math.random()*(max-min)+min);
	 		};

	 		// Add black and white pixels at random positions to the canvas
	 		for (var i = 0; i < options.noise * Math.pow(options.tileSize, 2); i++) {
	 			var x = rand(0, canvas.width),
	 			    y = rand(0, canvas.height),
	 			    index = (x + y * imgData.width) * 4;

	 			if (options.monochromatic) var randColorChannel = rand(0, 255);

	 			imgData.data[index  ] = randColorChannel;                                           // red
	 			imgData.data[index+1] = options.monochromatic ? randColorChannel : rand(0, 255);    // green
	 			imgData.data[index+2] = options.monochromatic ? randColorChannel : rand(0, 255);    // blue
	 			imgData.data[index+3] = rand(0, 255*options.maxNoiseOpacity);                       // alpha
	 		}

	 		ctx.putImageData(imgData, 0, 0);
	 		$(this).css('background', 'url(' + canvas.toDataURL('image/png') + ')' + options.backgroundColor);
		});
	};
})(jQuery);<|endoftext|>


<commit_before>// Peity jQuery plugin version 0.6.0
// (c) 2011 Ben Pickles
//
// http://benpickles.github.com/peity/
//
// Released under MIT license.
(function($, document) {
  var peity = $.fn.peity = function(type, options) {
    if (document.createElement("canvas").getContext) {
      this.each(function() {
        $(this).change(function() {
          var defaults = peity.defaults[type];
          var opts = $.extend({}, defaults, options)
          var self = this

          $.each($(this).data(), function(name, value) {
            if (defaults[name] && (!options || !options[name])) opts[name] = value
          })

          $.each(opts, function(name, value) {
            if ($.isFunction(value)) opts[name] = value.call(self)
          })

          var value = $(this).html();
          peity.graphers[type].call(this, opts)
          $(this).trigger("change.peity", value)
        }).trigger("change");
      });
    }

    return this;
  };

  peity.graphers = {};
  peity.defaults = {};

  peity.add = function(type, defaults, grapher){
    peity.graphers[type] = grapher;
    peity.defaults[type] = defaults;
  };

  var devicePixelRatio = window.devicePixelRatio || 1

  function createCanvas(width, height) {
    var canvas = document.createElement("canvas")
    canvas.setAttribute("width", width * devicePixelRatio)
    canvas.setAttribute("height", height * devicePixelRatio)

    if (devicePixelRatio != 1) {
      var style = "width:" + width + "px;height:" + height + "px"
      canvas.setAttribute("style", style)
    }

    return canvas
  }

  peity.add(
    'pie',
    {
      colours: ['#FFF4DD', '#FF9900'],
      delimiter: '/',
      diameter: 16
    },
    function(opts) {
      var $this = $(this)
      var values = $this.text().split(opts.delimiter)
      var v1 = parseFloat(values[0]);
      var v2 = parseFloat(values[1]);
      var adjust = -Math.PI / 2;
      var slice = (v1 / v2) * Math.PI * 2;

      var canvas = createCanvas(opts.diameter, opts.diameter)
      var context = canvas.getContext("2d");
      var centre = canvas.width / 2;

      // Plate.
      context.beginPath();
      context.moveTo(centre, centre);
      context.arc(centre, centre, centre, slice + adjust, (slice == 0) ? Math.PI * 2 : adjust, false);
      context.fillStyle = opts.colours[0];
      context.fill();

      // Slice of pie.
      context.beginPath();
      context.moveTo(centre, centre);
      context.arc(centre, centre, centre, adjust, slice + adjust, false);
      context.fillStyle = opts.colours[1];
      context.fill();

      $this.wrapInner($("<span>").hide()).append(canvas)
  });

  peity.add(
    "line",
    {
      colour: "#c6d9fd",
      strokeColour: "#4d89f9",
      strokeWidth: 1,
      delimiter: ",",
      height: 16,
      max: null,
      min: 0,
      width: 32
    },
    function(opts) {
      var $this = $(this)
      var canvas = createCanvas(opts.width, opts.height)
      var values = $this.text().split(opts.delimiter)
      if (values.length == 1) values.push(values[0])
      var max = Math.max.apply(Math, values.concat([opts.max]));
      var min = Math.min.apply(Math, values.concat([opts.min]))

      var context = canvas.getContext("2d");
      var width = canvas.width
      var height = canvas.height
      var xQuotient = width / (values.length - 1)
      var yQuotient = height / (max - min)

      var coords = [];
      var i;

      context.beginPath();
      context.moveTo(0, height + (min * yQuotient))

      for (i = 0; i < values.length; i++) {
        var x = i * xQuotient
        var y = height - (yQuotient * (values[i] - min))

        coords.push({ x: x, y: y });
        context.lineTo(x, y);
      }

      context.lineTo(width, height + (min * yQuotient))
      context.fillStyle = opts.colour;
      context.fill();

      if (opts.strokeWidth) {
        context.beginPath();
        context.moveTo(0, coords[0].y);
        for (i = 0; i < coords.length; i++) {
          context.lineTo(coords[i].x, coords[i].y);
        }
        context.lineWidth = opts.strokeWidth * devicePixelRatio;
        context.strokeStyle = opts.strokeColour;
        context.stroke();
      }

      $this.wrapInner($("<span>").hide()).append(canvas)
    }
  );

  peity.add(
    'bar',
    {
      colour: "#4D89F9",
      delimiter: ",",
      height: 16,
      max: null,
      min: 0,
      width: 32
    },
    function(opts) {
      var $this = $(this)
      var values = $this.text().split(opts.delimiter)
      var max = Math.max.apply(Math, values.concat([opts.max]));
      var min = Math.min.apply(Math, values.concat([opts.min]))

      var canvas = createCanvas(opts.width, opts.height)
      var context = canvas.getContext("2d");

      var width = canvas.width
      var height = canvas.height
      var yQuotient = height / (max - min)
      var space = devicePixelRatio / 2
      var xQuotient = (width + space) / values.length

      context.fillStyle = opts.colour;

      for (var i = 0; i < values.length; i++) {
        var x = i * xQuotient
        var y = height - (yQuotient * (values[i] - min))

        context.fillRect(x, y, xQuotient - space, yQuotient * values[i])
      }

      $this.wrapInner($("<span>").hide()).append(canvas)
    }
  );
})(jQuery, document);
<commit_msg>Super tiny micro optimisation when minified.<commit_after>// Peity jQuery plugin version 0.6.0
// (c) 2011 Ben Pickles
//
// http://benpickles.github.com/peity
//
// Released under MIT license.
(function($, document) {
  var peity = $.fn.peity = function(type, options) {
    if (document.createElement("canvas").getContext) {
      this.each(function() {
        $(this).change(function() {
          var defaults = peity.defaults[type];
          var opts = $.extend({}, defaults, options)
          var self = this

          $.each($(this).data(), function(name, value) {
            if (defaults[name] && (!options || !options[name])) opts[name] = value
          })

          $.each(opts, function(name, value) {
            if ($.isFunction(value)) opts[name] = value.call(self)
          })

          var value = $(self).html()
          peity.graphers[type].call(self, opts)
          $(self).trigger("change.peity", value)
        }).trigger("change");
      });
    }

    return this;
  };

  peity.graphers = {};
  peity.defaults = {};

  peity.add = function(type, defaults, grapher){
    peity.graphers[type] = grapher;
    peity.defaults[type] = defaults;
  };

  var devicePixelRatio = window.devicePixelRatio || 1

  function createCanvas(width, height) {
    var canvas = document.createElement("canvas")
    canvas.setAttribute("width", width * devicePixelRatio)
    canvas.setAttribute("height", height * devicePixelRatio)

    if (devicePixelRatio != 1) {
      var style = "width:" + width + "px;height:" + height + "px"
      canvas.setAttribute("style", style)
    }

    return canvas
  }

  peity.add(
    'pie',
    {
      colours: ['#FFF4DD', '#FF9900'],
      delimiter: '/',
      diameter: 16
    },
    function(opts) {
      var $this = $(this)
      var values = $this.text().split(opts.delimiter)
      var v1 = parseFloat(values[0]);
      var v2 = parseFloat(values[1]);
      var adjust = -Math.PI / 2;
      var slice = (v1 / v2) * Math.PI * 2;

      var canvas = createCanvas(opts.diameter, opts.diameter)
      var context = canvas.getContext("2d");
      var centre = canvas.width / 2;

      // Plate.
      context.beginPath();
      context.moveTo(centre, centre);
      context.arc(centre, centre, centre, slice + adjust, (slice == 0) ? Math.PI * 2 : adjust, false);
      context.fillStyle = opts.colours[0];
      context.fill();

      // Slice of pie.
      context.beginPath();
      context.moveTo(centre, centre);
      context.arc(centre, centre, centre, adjust, slice + adjust, false);
      context.fillStyle = opts.colours[1];
      context.fill();

      $this.wrapInner($("<span>").hide()).append(canvas)
  });

  peity.add(
    "line",
    {
      colour: "#c6d9fd",
      strokeColour: "#4d89f9",
      strokeWidth: 1,
      delimiter: ",",
      height: 16,
      max: null,
      min: 0,
      width: 32
    },
    function(opts) {
      var $this = $(this)
      var canvas = createCanvas(opts.width, opts.height)
      var values = $this.text().split(opts.delimiter)
      if (values.length == 1) values.push(values[0])
      var max = Math.max.apply(Math, values.concat([opts.max]));
      var min = Math.min.apply(Math, values.concat([opts.min]))

      var context = canvas.getContext("2d");
      var width = canvas.width
      var height = canvas.height
      var xQuotient = width / (values.length - 1)
      var yQuotient = height / (max - min)

      var coords = [];
      var i;

      context.beginPath();
      context.moveTo(0, height + (min * yQuotient))

      for (i = 0; i < values.length; i++) {
        var x = i * xQuotient
        var y = height - (yQuotient * (values[i] - min))

        coords.push({ x: x, y: y });
        context.lineTo(x, y);
      }

      context.lineTo(width, height + (min * yQuotient))
      context.fillStyle = opts.colour;
      context.fill();

      if (opts.strokeWidth) {
        context.beginPath();
        context.moveTo(0, coords[0].y);
        for (i = 0; i < coords.length; i++) {
          context.lineTo(coords[i].x, coords[i].y);
        }
        context.lineWidth = opts.strokeWidth * devicePixelRatio;
        context.strokeStyle = opts.strokeColour;
        context.stroke();
      }

      $this.wrapInner($("<span>").hide()).append(canvas)
    }
  );

  peity.add(
    'bar',
    {
      colour: "#4D89F9",
      delimiter: ",",
      height: 16,
      max: null,
      min: 0,
      width: 32
    },
    function(opts) {
      var $this = $(this)
      var values = $this.text().split(opts.delimiter)
      var max = Math.max.apply(Math, values.concat([opts.max]));
      var min = Math.min.apply(Math, values.concat([opts.min]))

      var canvas = createCanvas(opts.width, opts.height)
      var context = canvas.getContext("2d");

      var width = canvas.width
      var height = canvas.height
      var yQuotient = height / (max - min)
      var space = devicePixelRatio / 2
      var xQuotient = (width + space) / values.length

      context.fillStyle = opts.colour;

      for (var i = 0; i < values.length; i++) {
        var x = i * xQuotient
        var y = height - (yQuotient * (values[i] - min))

        context.fillRect(x, y, xQuotient - space, yQuotient * values[i])
      }

      $this.wrapInner($("<span>").hide()).append(canvas)
    }
  );
})(jQuery, document);
<|endoftext|>


<commit_before>// Copyright 2002-2013, University of Colorado Boulder

/**
 * A single- or double-headed arrow. This is a convenience class, most of the
 * work is done in ArrowShape.
 *
 * @author John Blanco
 * @author Chris Malley
 * @author Aaron Davis
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Shape = require( 'KITE/Shape' );
  var ArrowShape = require( 'SCENERY_PHET/ArrowShape' );

  /**
   * @param {number} tailX
   * @param {number} tailY
   * @param {number} tipX
   * @param {number} tipY
   * @param {Object} [options]
   * @constructor
   */
  function ArrowNode( tailX, tailY, tipX, tipY, options ) {

    var arrowNode = this;

    // default options
    options = _.extend( {
      headHeight: 10,
      headWidth: 10,
      tailWidth: 5,
      isHeadDynamic: false,
      scaleTailToo: false,
      fractionalHeadHeight: 0.5, // head will be scaled when head size is less than fractionalHeadHeight * arrow length
      doubleHead: false, // true puts heads on both ends of the arrow, false puts a head at the tip
      fill: 'black',
      stroke: 'black',
      lineWidth: 1
    }, options );

    // @private
    this.updateShapePoints = function() {
      arrowNode.shapePoints = ArrowShape.getArrowShapePoints( arrowNode.tailX, arrowNode.tailY, arrowNode.tipX, arrowNode.tipY, arrowNode.shapePoints, options );
    };

    // @private
    this.setDoubleHeaded = function( doubleHead ) {
      options.doubleHead = doubleHead;
    };

    Path.call( this, null );
    this.shapePoints = [];
    this.setTailAndTip( tailX, tailY, tipX, tipY );

    // things you're likely to mess up, add more as needed
    assert && assert( options.headWidth > options.tailWidth );

    this.mutate( options );
  }

  return inherit( Path, ArrowNode, {

    /**
     * Initialize the shape. Only called if the number of points in the shape changes.
     * @private
     */
    initializeShape: function() {
      this.arrowShape = new Shape();

      var thisNode = this;
      this.arrowShape.moveToPoint( this.shapePoints[ 0 ] );
      var tail = _.tail( this.shapePoints );
      _.each( tail, function( element ) {
        thisNode.arrowShape.lineToPoint( element );
      } );
      this.arrowShape.close();

      this.shape = this.arrowShape;
    },

    /**
     * Set the tail and tip locations to update the arrow shape
     * @public
     */
    setTailAndTip: function( tailX, tailY, tipX, tipY ) {

      this.tailX = tailX; // @public {read-only}
      this.tailY = tailY; // @public {read-only}
      this.tipX = tipX; // @public {read-only}
      this.tipY = tipY; // @public {read-only}

      var numberOfPoints = this.shapePoints.length;
      this.updateShapePoints();

      if ( !this.arrowShape || this.shapePoints.length !== numberOfPoints ) {
        this.initializeShape();
      }
      else {
        this.arrowShape.invalidatePoints();
      }
    },

    setDoubleHead: function( doubleHead ) {
      this.setDoubleHeaded( doubleHead );
      this.updateShapePoints();
      this.initializeShape();
    }
  } );
} );
<commit_msg>more clean up in ArrowNode, see #28<commit_after>// Copyright 2002-2013, University of Colorado Boulder

/**
 * A single- or double-headed arrow. This is a convenience class, most of the
 * work is done in ArrowShape.
 *
 * @author John Blanco
 * @author Chris Malley
 * @author Aaron Davis
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Shape = require( 'KITE/Shape' );
  var ArrowShape = require( 'SCENERY_PHET/ArrowShape' );

  /**
   * @param {number} tailX
   * @param {number} tailY
   * @param {number} tipX
   * @param {number} tipY
   * @param {Object} [options]
   * @constructor
   */
  function ArrowNode( tailX, tailY, tipX, tipY, options ) {

    var arrowNode = this;

    // default options
    options = _.extend( {
      headHeight: 10,
      headWidth: 10,
      tailWidth: 5,
      isHeadDynamic: false,
      scaleTailToo: false,
      fractionalHeadHeight: 0.5, // head will be scaled when head size is less than fractionalHeadHeight * arrow length
      doubleHead: false, // true puts heads on both ends of the arrow, false puts a head at the tip
      fill: 'black',
      stroke: 'black',
      lineWidth: 1
    }, options );

    // @private
    this.updateShapePoints = function() {
      var numberOfPoints = this.shapePoints.length;
      arrowNode.shapePoints = ArrowShape.getArrowShapePoints( arrowNode.tailX, arrowNode.tailY, arrowNode.tipX, arrowNode.tipY, arrowNode.shapePoints, options );
      return arrowNode.shapePoints.length !== numberOfPoints;
    };

    // @private
    this.setDoubleHeaded = function( doubleHead ) {
      options.doubleHead = doubleHead;
    };

    Path.call( this, null );
    this.shapePoints = [];
    this.setTailAndTip( tailX, tailY, tipX, tipY );

    // things you're likely to mess up, add more as needed
    assert && assert( options.headWidth > options.tailWidth );

    this.mutate( options );
  }

  return inherit( Path, ArrowNode, {

    /**
     * Initialize or update the shape. Only called if the number of points in the shape changes.
     * @private
     */
    updateShape: function() {

      var shape = new Shape();

      if ( this.shapePoints.length > 1 ) {
        shape.moveToPoint( this.shapePoints[ 0 ] );
        for ( var i = 1; i < this.shapePoints.length; i++ ) {
          shape.lineToPoint( this.shapePoints[ i ] );
        }
        shape.close();
      }

      this.shape = shape;
    },

    /**
     * Set the tail and tip locations to update the arrow shape
     * @public
     */
    setTailAndTip: function( tailX, tailY, tipX, tipY ) {

      this.tailX = tailX; // @public {read-only}
      this.tailY = tailY; // @public {read-only}
      this.tipX = tipX; // @public {read-only}
      this.tipY = tipY; // @public {read-only}

      var numberOfPointsChanged = this.updateShapePoints();

      if ( !this.shape || numberOfPointsChanged ) {
        this.updateShape();
      }
      else {
        this.shape.invalidatePoints();
      }
    },

    setDoubleHead: function( doubleHead ) {
      this.setDoubleHeaded( doubleHead );
      this.updateShapePoints();
      this.updateShape();
    }
  } );
} );
<|endoftext|>


<commit_before>var Constants = {
    DEFAULT_SIZE : {
        height : 32,
        width : 32
    },

    MAX_HEIGHT : 128,
    MAX_WIDTH : 128,

    DEFAULT_PEN_COLOR : '#000000',
    TRANSPARENT_COLOR : 'TRANSPARENT',

    /*
     * Fake semi-transparent color used to highlight transparent
     * strokes and rectangles:
     */
    SELECTION_TRANSPARENT_COLOR: 'rgba(255, 255, 255, 0.6)',

    /*
     * When a tool is hovering the drawing canvas, we highlight the eventual
     * pixel target with this color:
     */
    TOOL_TARGET_HIGHLIGHT_COLOR: 'rgba(255, 255, 255, 0.2)',

    /*
     * Default entry point for piskel web service:
     */
    PISKEL_SERVICE_URL: 'http://3.piskel-app.appspot.com',

    GRID_STROKE_WIDTH: 1,
    GRID_STROKE_COLOR: "lightgray",

    LEFT_BUTTON : "left_button_1",
    RIGHT_BUTTON : "right_button_2",
};<commit_msg>extra comma<commit_after>var Constants = {
    DEFAULT_SIZE : {
        height : 32,
        width : 32
    },

    MAX_HEIGHT : 128,
    MAX_WIDTH : 128,

    DEFAULT_PEN_COLOR : '#000000',
    TRANSPARENT_COLOR : 'TRANSPARENT',

    /*
     * Fake semi-transparent color used to highlight transparent
     * strokes and rectangles:
     */
    SELECTION_TRANSPARENT_COLOR: 'rgba(255, 255, 255, 0.6)',

    /*
     * When a tool is hovering the drawing canvas, we highlight the eventual
     * pixel target with this color:
     */
    TOOL_TARGET_HIGHLIGHT_COLOR: 'rgba(255, 255, 255, 0.2)',

    /*
     * Default entry point for piskel web service:
     */
    PISKEL_SERVICE_URL: 'http://3.piskel-app.appspot.com',

    GRID_STROKE_WIDTH: 1,
    GRID_STROKE_COLOR: "lightgray",

    LEFT_BUTTON : "left_button_1",
    RIGHT_BUTTON : "right_button_2"
};<|endoftext|>


<commit_before>/*global module: false, require: false, URL: false, Blob: false */
'use strict';

var React = require('react');
var Rx = require('rx');
require('rx/dist/rx.time');
var {Table, Pagination} = require('react-data-components-bd2k');
var {Button, Row, Col} = require('react-bootstrap');
var VariantSearch = require('./VariantSearch');
var SelectField = require('./SelectField');
var DisclaimerModal = require('./DisclaimerModal');
var ColumnCheckbox = require('./ColumnCheckbox');
var _ = require('underscore');
var cx = require('classnames');
var hgvs = require('./hgvs');
var PureRenderMixin = require('./PureRenderMixin'); // deep-equals version of PRM

var filterDisplay = v => v == null ? 'Any' : v;
var filterAny = v => v === 'Any' ? null : v;
var addAny = opts => ['Any', ...opts];

var pluralize = (n, s) => n === 1 ? s : s + 's';

var merge = (...args) => _.extend({}, ...args);

var Lollipop = require('./d3Lollipop');

function setPages({data, count}, pageLength) {
    return {
        data,
        count,
        totalPages: Math.ceil(count / pageLength)
    };
}

// Wrap Table with a version having PureRenderMixin
var FastTable = React.createClass({
    mixins: [PureRenderMixin],
    render: function () {
        var {dataArray, ...props} = this.props;
        var trimmedData = _.map(dataArray, r =>
            _.mapObject(r, v => v.length > 30 ? v.slice(0, 30) + "..." : v)
        );
        return
;
    }
});

// Merge new state (e.g. initialState) with existing state,
// deep-merging columnSelect.
function mergeState(state, newState) {
    var {columnSelection, sourceSelection, ...otherProps} = newState,
        cs = {...state.columnSelection, ...columnSelection},
        ss = {...state.sourceSelection, ...sourceSelection};
    return {...state, columnSelection: cs, sourceSelection: ss, ...otherProps};
}

var DataTable = React.createClass({
    mixins: [PureRenderMixin],
    componentWillMount: function () {
        var q = this.fetchq = new Rx.Subject();
        this.subs = q.map(this.props.fetch).debounce(100).switchLatest().subscribe(
            resp => this.setState(setPages(resp, this.state.pageLength)), // set data, count, totalPages
            () => this.setState({error: 'Problem connecting to server'}));
        var qLollipop = this.fetchqLollipop = new Rx.Subject();
        this.subs = qLollipop.map(this.props.fetch).debounce(100).switchLatest().subscribe(
            resp => this.setState(setPages(resp, this.state.pageLength)), // set data, count, totalPages
            () => this.setState({error: 'Problem connecting to server'}));
    },
    componentWillUnmount: function () {
        window.removeEventListener('resize', this.handleResize);
        this.subs.dispose();
    },
    componentDidMount: function () {
        window.addEventListener('resize', this.handleResize);
        this.fetch(this.state);
    },
    getInitialState: function () {
        return mergeState({
            data: [],
            lollipopOpen: false,
            filtersOpen: false,
            filterValues: {},
            search: '',
            columnSelection: this.props.columnSelection,
            sourceSelection: this.props.sourceSelection,
            pageLength: 20,
            page: 0,
            windowWidth: window.innerWidth
        }, this.props.initialState);
    },
    componentWillReceiveProps: function(newProps) {
        var newState = mergeState(this.state, newProps.initialState);
        newState.sourceSelection = newProps.sourceSelection;
        newState.columnSelection = newProps.columnSelection;
        this.setStateFetch(newState);
    },
    handleResize: function(e) {
        this.setState({windowWidth: window.innerWidth});
    },
    setFilters: function (obj) {
        var {filterValues} = this.state,
            newFilterValues = merge(filterValues, obj);

        this.setStateFetch({
          filterValues: newFilterValues,
          page: 0
        });
    },
    createDownload: function () {
        var {search, sortBy, filterValues, columnSelection, sourceSelection} = this.state;
        return this.props.url(merge({
            format: 'csv',
            pageLength: null,
            page: null,
            sortBy,
            search,
            searchColumn: _.keys(_.pick(columnSelection, v => v)),
            source: _.keys(_.pick(sourceSelection, v => v)),
            filterValues}, hgvs.filters(search, filterValues)));
    },
    fetch: function (state) {
        var {pageLength, search, page, sortBy,
            filterValues, columnSelection, sourceSelection} = state;
        this.fetchq.onNext(merge({
            pageLength,
            page,
            sortBy,
            search,
            searchColumn: _.keys(_.pick(columnSelection, v => v)),
            source: _.keys(_.pick(sourceSelection, v => v)),
            filterValues}, hgvs.filters(search, filterValues)));
    },
    fetchLollipopData: function(state) {
        var {search, sortBy, filterValues} = state;
        this.fetchq.onNext(merge({
            pageLength: null,
            page: null,
            sortBy,
            search,
            filterValues}, hgvs.filters(search, filterValues)));
    },
    // helper function that sets state, fetches new data,
    // and updates url.
    setStateFetch: function (opts) {
        var newState = {...this.state, ...opts};
        this.setState(newState);
        this.fetch(newState);
        this.props.onChange(newState);
    },
    toggleLollipop: function () {
        this.setState({lollipopOpen: !this.state.lollipopOpen});
    },
    toggleFilters: function () {
        this.setState({filtersOpen: !this.state.filtersOpen});
    },
    onChangePage: function (pageNumber) {
        this.setStateFetch({page: pageNumber});
    },
    onSort: function(sortBy) {
        this.setStateFetch({sortBy});
    },
    onPageLengthChange: function(txt) {
        var length = parseInt(txt),
            {page, pageLength} = this.state,
            newPage = Math.floor((page * pageLength) / length);

        this.setStateFetch({page: newPage, pageLength: length});
    },
    render: function () {
        var {filterValues, filtersOpen, lollipopOpen, search, data, columnSelection,
            page, totalPages, count, error} = this.state;
        var {columns, filterColumns, className, advancedFilters, downloadButton, lollipopButton, onToggleMode} = this.props;
        var renderColumns = _.filter(columns, c => columnSelection[c.prop]);
        var filterFormEls = _.map(filterColumns, ({name, prop, values}) =>
            <SelectField onChange={v => this.setFilters({[prop]: filterAny(v)})}
                         key={prop} label={`${name} is: `} value={filterDisplay(filterValues[prop])}
                         options={addAny(values)}/>);

        return (error ?
{error}
 :

                <Row id="show-hide" className="btm-buffer">
                    <Col sm={12}>
                        <Button className="btn-sm rgt-buffer"
                                onClick={this.toggleFilters}>{(filtersOpen ? 'Hide' : 'Show' ) + ' Filters'}
                        </Button>

                        {lollipopButton(this.toggleLollipop, lollipopOpen)}
                    </Col>
                </Row>

                <Row id="filters" className="">
                    <Col sm={12}>
                        {filtersOpen &&
{filterFormEls}
}
                        {filtersOpen &&

                            {advancedFilters}

}
                    </Col>
                </Row>
                <Row id="lollipop-chart" className="">
                    <Col sm={12}>
                        {lollipopOpen && this.state.windowWidth > 991 && this.state.data.length > 0 &&
                        <Lollipop data={this.state.data} onHeaderClick={this.props.onHeaderClick} onRowClick={this.props.onRowClick}/> }

                        {lollipopOpen && this.state.windowWidth <= 991 &&

Please use a larger screen size to view this interactive chart.
}

                    </Col>
                </Row>
                <Row id="download" className="btm-buffer ">
                    <Col sm={6}>

                                    {count} matching {pluralize(count, 'variant')}

                                {downloadButton(this.createDownload)}

                    </Col>
                    <Col sm={3} smOffset={3}>

                            <SelectField
                                label="Page size:"
                                value={this.state.pageLength}
                                options={this.props.pageLengthOptions}
                                onChange={this.onPageLengthChange}
                            />

                    </Col>
                </Row>
                <Row id='variant-search-row' className="btm-buffer">
                    <Col sm={5}>
                        <VariantSearch
                            id='variants-search'
                            value={search}
                            onChange={v => {
                                // reset the page number to zero on new searches
                                this.setStateFetch({search: v, page: 0});
                            }}
                        />
                    </Col>
                    <Col sm={3}>
                        <DisclaimerModal research_mode onToggleMode={onToggleMode} text='Go to research pages'/>
                    </Col>
                    <Col sm={4}>
                        <Pagination
                            className="pagination pull-right-sm"
                            currentPage={page}
                            totalPages={totalPages}
                            onChangePage={this.onChangePage} />
                    </Col>
                </Row>
                <Row>
                    <Col className="table-responsive" sm={12}>
                        <FastTable
                            className={cx(className, "table table-hover table-bordered table-condensed")}
                            dataArray={data}
                            columns={renderColumns}
                            keys={this.props.keys}
                            buildRowOptions={this.props.buildRowOptions}
                            onRowClick={this.props.onRowClick}
                            buildHeader={this.props.buildHeader}
                            sortBy={this.state.sortBy}
                            onSort={this.onSort} />
                    </Col>
                </Row>

        );
    }
});

module.exports = DataTable;
<commit_msg>Fix issue where sorting made the filters reset<commit_after>/*global module: false, require: false, URL: false, Blob: false */
'use strict';

var React = require('react');
var Rx = require('rx');
require('rx/dist/rx.time');
var {Table, Pagination} = require('react-data-components-bd2k');
var {Button, Row, Col} = require('react-bootstrap');
var VariantSearch = require('./VariantSearch');
var SelectField = require('./SelectField');
var DisclaimerModal = require('./DisclaimerModal');
var ColumnCheckbox = require('./ColumnCheckbox');
var _ = require('underscore');
var cx = require('classnames');
var hgvs = require('./hgvs');
var PureRenderMixin = require('./PureRenderMixin'); // deep-equals version of PRM

var filterDisplay = v => v == null ? 'Any' : v;
var filterAny = v => v === 'Any' ? null : v;
var addAny = opts => ['Any', ...opts];

var pluralize = (n, s) => n === 1 ? s : s + 's';

var merge = (...args) => _.extend({}, ...args);

var Lollipop = require('./d3Lollipop');

function setPages({data, count}, pageLength) {
    return {
        data,
        count,
        totalPages: Math.ceil(count / pageLength)
    };
}

// Wrap Table with a version having PureRenderMixin
var FastTable = React.createClass({
    mixins: [PureRenderMixin],
    render: function () {
        var {dataArray, ...props} = this.props;
        var trimmedData = _.map(dataArray, r =>
            _.mapObject(r, v => v.length > 30 ? v.slice(0, 30) + "..." : v)
        );
        return
;
    }
});

// Merge new state (e.g. initialState) with existing state,
// deep-merging fields that are objects.
function mergeState(state, newState) {
    var {columnSelection, sourceSelection, filterValues, ...otherProps} = newState,
        cs = {...state.columnSelection, ...columnSelection},
        ss = {...state.sourceSelection, ...sourceSelection},
        fv = {...state.filterValues, ...filterValues};
    return {...state, columnSelection: cs, sourceSelection: ss, filterValues: fv, ...otherProps};
}

var DataTable = React.createClass({
    mixins: [PureRenderMixin],
    componentWillMount: function () {
        var q = this.fetchq = new Rx.Subject();
        this.subs = q.map(this.props.fetch).debounce(100).switchLatest().subscribe(
            resp => this.setState(setPages(resp, this.state.pageLength)), // set data, count, totalPages
            () => this.setState({error: 'Problem connecting to server'}));
        var qLollipop = this.fetchqLollipop = new Rx.Subject();
        this.subs = qLollipop.map(this.props.fetch).debounce(100).switchLatest().subscribe(
            resp => this.setState(setPages(resp, this.state.pageLength)), // set data, count, totalPages
            () => this.setState({error: 'Problem connecting to server'}));
    },
    componentWillUnmount: function () {
        window.removeEventListener('resize', this.handleResize);
        this.subs.dispose();
    },
    componentDidMount: function () {
        window.addEventListener('resize', this.handleResize);
        this.fetch(this.state);
    },
    getInitialState: function () {
        return mergeState({
            data: [],
            lollipopOpen: false,
            filtersOpen: false,
            filterValues: {},
            search: '',
            columnSelection: this.props.columnSelection,
            sourceSelection: this.props.sourceSelection,
            pageLength: 20,
            page: 0,
            windowWidth: window.innerWidth
        }, this.props.initialState);
    },
    componentWillReceiveProps: function(newProps) {
        var newState = mergeState(this.state, newProps.initialState);
        newState.sourceSelection = newProps.sourceSelection;
        newState.columnSelection = newProps.columnSelection;
        this.setStateFetch(newState);
    },
    handleResize: function(e) {
        this.setState({windowWidth: window.innerWidth});
    },
    setFilters: function (obj) {
        var {filterValues} = this.state,
            newFilterValues = merge(filterValues, obj);

        this.setStateFetch({
          filterValues: newFilterValues,
          page: 0
        });
    },
    createDownload: function () {
        var {search, sortBy, filterValues, columnSelection, sourceSelection} = this.state;
        return this.props.url(merge({
            format: 'csv',
            pageLength: null,
            page: null,
            sortBy,
            search,
            searchColumn: _.keys(_.pick(columnSelection, v => v)),
            source: _.keys(_.pick(sourceSelection, v => v)),
            filterValues}, hgvs.filters(search, filterValues)));
    },
    fetch: function (state) {
        var {pageLength, search, page, sortBy,
            filterValues, columnSelection, sourceSelection} = state;
        this.fetchq.onNext(merge({
            pageLength,
            page,
            sortBy,
            search,
            searchColumn: _.keys(_.pick(columnSelection, v => v)),
            source: _.keys(_.pick(sourceSelection, v => v)),
            filterValues}, hgvs.filters(search, filterValues)));
    },
    fetchLollipopData: function(state) {
        var {search, sortBy, filterValues} = state;
        this.fetchq.onNext(merge({
            pageLength: null,
            page: null,
            sortBy,
            search,
            filterValues}, hgvs.filters(search, filterValues)));
    },
    // helper function that sets state, fetches new data,
    // and updates url.
    setStateFetch: function (opts) {
        var newState = mergeState(this.state, opts);
        this.setState(newState);
        this.fetch(newState);
        this.props.onChange(newState);
    },
    toggleLollipop: function () {
        this.setState({lollipopOpen: !this.state.lollipopOpen});
    },
    toggleFilters: function () {
        this.setState({filtersOpen: !this.state.filtersOpen});
    },
    onChangePage: function (pageNumber) {
        this.setStateFetch({page: pageNumber});
    },
    onSort: function(sortBy) {
        this.setStateFetch({sortBy});
    },
    onPageLengthChange: function(txt) {
        var length = parseInt(txt),
            {page, pageLength} = this.state,
            newPage = Math.floor((page * pageLength) / length);

        this.setStateFetch({page: newPage, pageLength: length});
    },
    render: function () {
        var {filterValues, filtersOpen, lollipopOpen, search, data, columnSelection,
            page, totalPages, count, error} = this.state;
        var {columns, filterColumns, className, advancedFilters, downloadButton, lollipopButton, onToggleMode} = this.props;
        var renderColumns = _.filter(columns, c => columnSelection[c.prop]);
        var filterFormEls = _.map(filterColumns, ({name, prop, values}) =>
            <SelectField onChange={v => this.setFilters({[prop]: filterAny(v)})}
                         key={prop} label={`${name} is: `} value={filterDisplay(filterValues[prop])}
                         options={addAny(values)}/>);

        return (error ?
{error}
 :

                <Row id="show-hide" className="btm-buffer">
                    <Col sm={12}>
                        <Button className="btn-sm rgt-buffer"
                                onClick={this.toggleFilters}>{(filtersOpen ? 'Hide' : 'Show' ) + ' Filters'}
                        </Button>

                        {lollipopButton(this.toggleLollipop, lollipopOpen)}
                    </Col>
                </Row>

                <Row id="filters" className="">
                    <Col sm={12}>
                        {filtersOpen &&
{filterFormEls}
}
                        {filtersOpen &&

                            {advancedFilters}

}
                    </Col>
                </Row>
                <Row id="lollipop-chart" className="">
                    <Col sm={12}>
                        {lollipopOpen && this.state.windowWidth > 991 && this.state.data.length > 0 &&
                        <Lollipop data={this.state.data} onHeaderClick={this.props.onHeaderClick} onRowClick={this.props.onRowClick}/> }

                        {lollipopOpen && this.state.windowWidth <= 991 &&

Please use a larger screen size to view this interactive chart.
}

                    </Col>
                </Row>
                <Row id="download" className="btm-buffer ">
                    <Col sm={6}>

                                    {count} matching {pluralize(count, 'variant')}

                                {downloadButton(this.createDownload)}

                    </Col>
                    <Col sm={3} smOffset={3}>

                            <SelectField
                                label="Page size:"
                                value={this.state.pageLength}
                                options={this.props.pageLengthOptions}
                                onChange={this.onPageLengthChange}
                            />

                    </Col>
                </Row>
                <Row id='variant-search-row' className="btm-buffer">
                    <Col sm={5}>
                        <VariantSearch
                            id='variants-search'
                            value={search}
                            onChange={v => {
                                // reset the page number to zero on new searches
                                this.setStateFetch({search: v, page: 0});
                            }}
                        />
                    </Col>
                    <Col sm={3}>
                        <DisclaimerModal research_mode onToggleMode={onToggleMode} text='Go to research pages'/>
                    </Col>
                    <Col sm={4}>
                        <Pagination
                            className="pagination pull-right-sm"
                            currentPage={page}
                            totalPages={totalPages}
                            onChangePage={this.onChangePage} />
                    </Col>
                </Row>
                <Row>
                    <Col className="table-responsive" sm={12}>
                        <FastTable
                            className={cx(className, "table table-hover table-bordered table-condensed")}
                            dataArray={data}
                            columns={renderColumns}
                            keys={this.props.keys}
                            buildRowOptions={this.props.buildRowOptions}
                            onRowClick={this.props.onRowClick}
                            buildHeader={this.props.buildHeader}
                            sortBy={this.state.sortBy}
                            onSort={this.onSort} />
                    </Col>
                </Row>

        );
    }
});

module.exports = DataTable;
<|endoftext|>


<commit_before>// Copyright 2018, University of Colorado Boulder

/**
 * Depicts a draggable meter node with an arbitrary number of probes which can optionally travel with the meter.
 * This type is also set up for usage in a toolbox, including dragging out of a toolbox and dropping back in a toolbox.
 * Migrated from wave-interference on Aug 21, 2018, see https://github.com/phetsims/wave-interference/issues/24
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var DragListener = require( 'SCENERY/listeners/DragListener' );
  var Emitter = require( 'AXON/Emitter' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );

  /**
   * @param {Node} backgroundNode - node that is shown for the main body
   * @param {Object} [options]
   * @constructor
   */
  function MeterNode( backgroundNode, options ) {

    var self = this;
    options = _.extend( {

      // This function is called when the a wave detector drag ends.  It can be used to drop it back into a toolbox.
      end: function() {}
    }, options );
    Node.call( this );

    // @private {boolean} - true if the probes are being dragged with the wave detector tool
    this.synchronizeProbeLocations = true;

    // @private {Node}
    this.backgroundNode = backgroundNode;

    // @private
    this.backgroundDragListener = new DragListener( {
      translateNode: true,
      drag: function() {
        if ( self.synchronizeProbeLocations ) {
          self.alignProbes();
        }
      },
      end: function() {
        options.end();
        self.synchronizeProbeLocations = false;
      }
    } );
    this.backgroundNode.addInputListener( this.backgroundDragListener );
    this.addChild( this.backgroundNode );

    // @public (listen-only) {Emitter}
    this.alignProbesEmitter = new Emitter();

    this.alignProbes();

    // Mutate after backgroundNode is added as a child
    this.mutate( options );
  }

  inherit( Node, MeterNode, {

    /**
     * Put the probes into their standard position relative to the graph body.
     * @public
     */
    alignProbes: function() {
      this.alignProbesEmitter.emit();
    },

    /**
     * Gets the region of the background in global coordinates.  This can be used to determine if the MeterNode should
     * be dropped back in a toolbox.
     * @returns {Bounds2}
     */
    getBackgroundNodeGlobalBounds: function() {
      return this.localToGlobalBounds( this.backgroundNode.bounds );
    },

    /**
     * Forward an event from the toolbox to start dragging the node in the play area.
     * @param {Object} event
     */
    startDrag: function( event ) {
      this.synchronizeProbeLocations = true;
      this.backgroundDragListener.press( event, this.backgroundNode );
    }
  } );

  return sceneryPhet.register( 'MeterNode', MeterNode );
} );<commit_msg>Added docs and changed default for MeterNode.synchronizeProbeLocations, see https://github.com/phetsims/scenery-phet/issues/395<commit_after>// Copyright 2018, University of Colorado Boulder

/**
 * Depicts a draggable meter node with an arbitrary number of probes which can optionally travel with the meter.
 * This type is also set up for usage in a toolbox, including dragging out of a toolbox and dropping back in a toolbox.
 * Migrated from wave-interference on Aug 21, 2018, see https://github.com/phetsims/wave-interference/issues/24
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var DragListener = require( 'SCENERY/listeners/DragListener' );
  var Emitter = require( 'AXON/Emitter' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );

  /**
   * @param {Node} backgroundNode - node that is shown for the main body
   * @param {Object} [options]
   * @constructor
   */
  function MeterNode( backgroundNode, options ) {

    var self = this;
    options = _.extend( {

      // This function is called when the a wave detector drag ends.  It can be used to drop it back into a toolbox.
      end: function() {}
    }, options );
    Node.call( this );

    // @private {boolean} - true if dragging the MeterNode also causes attached probes to translate
    this.synchronizeProbeLocations = false;

    // @private {Node}
    this.backgroundNode = backgroundNode;

    // @private
    this.backgroundDragListener = new DragListener( {
      translateNode: true,
      drag: function() {
        if ( self.synchronizeProbeLocations ) {
          self.alignProbes();
        }
      },
      end: function() {
        options.end();
        self.synchronizeProbeLocations = false;
      }
    } );
    this.backgroundNode.addInputListener( this.backgroundDragListener );
    this.addChild( this.backgroundNode );

    // @public (listen-only) {Emitter}
    this.alignProbesEmitter = new Emitter();

    this.alignProbes();

    // Mutate after backgroundNode is added as a child
    this.mutate( options );
  }

  inherit( Node, MeterNode, {

    /**
     * Put the probes into their standard position relative to the graph body.
     * @public
     */
    alignProbes: function() {
      this.alignProbesEmitter.emit();
    },

    /**
     * Gets the region of the background in global coordinates.  This can be used to determine if the MeterNode should
     * be dropped back in a toolbox.
     * @returns {Bounds2}
     */
    getBackgroundNodeGlobalBounds: function() {
      return this.localToGlobalBounds( this.backgroundNode.bounds );
    },

    /**
     * Forward an event from the toolbox to start dragging the node in the play area.
     * @param {Object} event
     */
    startDrag: function( event ) {
      this.synchronizeProbeLocations = true;
      this.backgroundDragListener.press( event, this.backgroundNode );
    }
  } );

  return sceneryPhet.register( 'MeterNode', MeterNode );
} );<|endoftext|>


<commit_before>// Copyright 2015-2020, University of Colorado Boulder

/**
 * A physical-looking probe with a handle and a semicircular sensor region, used in simulations like Bending Light and
 * Beer's Law Lab to show how much light is being received. It is typically connected to a body with readouts with a
 * curved wire. The origin is in the center of the circular part of the sensor (which is not vertically symmetrical).
 *
 * This code was generalized from Bending Light, see https://github.com/phetsims/bending-light/issues/165
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Chandrashekar Bemagoni (Actual Concepts)
 * @author Chris Malley (PixelZoom, Inc.)
 */

import DerivedProperty from '../../axon/js/DerivedProperty.js';
import Ray2 from '../../dot/js/Ray2.js';
import Vector2 from '../../dot/js/Vector2.js';
import EllipticalArc from '../../kite/js/segments/EllipticalArc.js';
import Shape from '../../kite/js/Shape.js';
import InstanceRegistry from '../../phet-core/js/documentation/InstanceRegistry.js';
import merge from '../../phet-core/js/merge.js';
import Circle from '../../scenery/js/nodes/Circle.js';
import Line from '../../scenery/js/nodes/Line.js';
import Node from '../../scenery/js/nodes/Node.js';
import Path from '../../scenery/js/nodes/Path.js';
import LinearGradient from '../../scenery/js/util/LinearGradient.js';
import PaintColorProperty from '../../scenery/js/util/PaintColorProperty.js';
import RadialGradient from '../../scenery/js/util/RadialGradient.js';
import sceneryPhet from './sceneryPhet.js';

// constants
const DEFAULT_OPTIONS = {
  radius: 50,
  innerRadius: 35,
  handleWidth: 50,
  handleHeight: 30,
  handleCornerRadius: 10,

  /**
   * in radians, the angle of the incoming light.  0 is from the right, PI/2 from the bottom, PI from the left, etc.
   * The default is from the upper-left.  Generally, it is difficult to know the global rotation of the ProbeNode
   * and automatically update the lightAngle when the global rotation changes, so this is up to the developer
   * to set properly.  The light in PhET simulations often comes from the top-left, so please set this value
   * accordingly depending on the context of how the probe is embedded in the simulation.
   */
  lightAngle: 1.35 * Math.PI,
  color: '#008541', // {Color|string} darkish green

  // {function(radius: number): Node} The circular part of the ProbeNode is called the sensor, where it receives light
  // or has crosshairs, etc. or null for an empty region.
  sensorTypeFunction: glass()
};
assert && Object.freeze( DEFAULT_OPTIONS );

class ProbeNode extends Node {

  /**
   * @param {Object} [options]
   */
  constructor( options ) {

    options = merge( {}, DEFAULT_OPTIONS, options );

    super();

    // To improve readability
    const radius = options.radius;

    // the top of the handle, below the circle at the top of the sensor
    const handleBottom = radius + options.handleHeight;

    // The shape of the outer body, circular at top with a handle at the bottom
    const arcExtent = 0.8;
    const handleWidth = options.handleWidth;
    const innerRadius = Math.min( options.innerRadius, options.radius );
    const cornerRadius = options.handleCornerRadius;

    const neckCornerRadius = 10;

    // We must know where the elliptical arc begins, so create an explicit EllipticalArc for that
    // Note: This elliptical arc must match the ellipticalArc call below
    const ellipticalArcStart = new EllipticalArc( new Vector2( 0, 0 ), radius, radius, 0, Math.PI * arcExtent, Math.PI * ( 1 - arcExtent ), false ).start;

    function createSensorShape() {
      return new Shape()

        // start in the bottom center
        .moveTo( 0, handleBottom )

        // Kite Shape automatically lineTo's to the first point of an arc, so no need to lineTo ourselves
        .arc( -handleWidth / 2 + cornerRadius, handleBottom - cornerRadius, cornerRadius, Math.PI / 2, Math.PI, false )
        .lineTo( -handleWidth / 2, radius + neckCornerRadius )
        .quadraticCurveTo( -handleWidth / 2, radius, ellipticalArcStart.x, ellipticalArcStart.y )

        // Top arc
        // Note: his elliptical arc must match the EllipticalArc above
        .ellipticalArc( 0, 0, radius, radius, 0, Math.PI * arcExtent, Math.PI * ( 1 - arcExtent ), false )

        .quadraticCurveTo( handleWidth / 2, radius, +handleWidth / 2, radius + neckCornerRadius )
        .arc( handleWidth / 2 - cornerRadius, handleBottom - cornerRadius, cornerRadius, 0, Math.PI / 2, false )

        .lineTo( 0, handleBottom );
    }

    const sensorShape = createSensorShape()
      .moveTo( innerRadius, 0 )
      .arc( 0, 0, innerRadius, Math.PI * 2, 0, true )
      .close();

    // The light angle is variable so that you can create a probe node that is pointing up or to the side
    const lightAngle = options.lightAngle;

    const center = sensorShape.bounds.center;
    const v1 = Vector2.createPolar( 1, lightAngle );
    const intersections = sensorShape.intersection( new Ray2( center, v1 ) );

    // take last intersection or zero point, see https://github.com/phetsims/scenery-phet/issues/294
    const lastIntersection = intersections[ intersections.length - 1 ];
    const lastIntersectionPoint = lastIntersection ? lastIntersection.point : Vector2.ZERO;
    const gradientSource = lastIntersectionPoint.plus( v1.timesScalar( 1 ) );

    const v2 = Vector2.createPolar( 1, lightAngle + Math.PI );
    const intersections2 = sensorShape.intersection( new Ray2( center, v2 ) );

    // take last intersection or zero point, see https://github.com/phetsims/scenery-phet/issues/294
    const lastIntersection2 = intersections2[ intersections2.length - 1 ];
    const lastIntersectionPoint2 = lastIntersection2 ? lastIntersection2.point : Vector2.ZERO;
    const gradientDestination = lastIntersectionPoint2.plus( v2.timesScalar( 1 ) );

    // @private {Property.<Color>}
    this.brighter5 = new PaintColorProperty( options.color, { luminanceFactor: 0.5 } );
    this.brighter4 = new PaintColorProperty( options.color, { luminanceFactor: 0.4 } );
    this.brighter3 = new PaintColorProperty( options.color, { luminanceFactor: 0.3 } );
    this.brighter2 = new PaintColorProperty( options.color, { luminanceFactor: 0.2 } );
    this.darker2 = new PaintColorProperty( options.color, { luminanceFactor: -0.2 } );
    this.darker3 = new PaintColorProperty( options.color, { luminanceFactor: -0.3 } );

    const outerShapePath = new Path( sensorShape, {
      stroke: new LinearGradient( gradientSource.x, gradientSource.y, gradientDestination.x, gradientDestination.y )
        .addColorStop( 0.0, this.brighter2 ) // highlight
        .addColorStop( 1.0, this.darker2 ), // shadow
      fill: new LinearGradient( gradientSource.x, gradientSource.y, gradientDestination.x, gradientDestination.y )
        .addColorStop( 0.0, this.brighter5 ) // highlight
        .addColorStop( 0.03, this.brighter4 )
        .addColorStop( 0.07, this.brighter4 )
        .addColorStop( 0.11, this.brighter2 )
        .addColorStop( 0.3, options.color )
        .addColorStop( 0.8, this.darker2 ) // shadows
        .addColorStop( 1.0, this.darker3 ),
      lineWidth: 2
    } );

    // the front flat "surface" of the sensor, makes it look 3d by putting a shiny glare on the top edge
    const innerPath = new Path( sensorShape, {
      fill: options.color,

      // y scale is an empirical function of handle height, to keep bevel at bottom of handle from changing size
      scale: new Vector2( 0.9, 0.93 + ( 0.01 * options.handleHeight / DEFAULT_OPTIONS.handleHeight ) ),
      centerX: outerShapePath.centerX,
      stroke: new DerivedProperty( [ this.brighter3 ], function( color ) {
        return color.withAlpha( 0.5 );
      } ),
      lineWidth: 1.2,
      y: 2 // Shift it down a bit to make the face look a bit more 3d
    } );

    const children = [];
    if ( options.sensorTypeFunction ) {
      children.push( options.sensorTypeFunction( radius ) );
    }
    children.push(
      outerShapePath,
      innerPath
      //new Circle( 3, { center: gradientSource, fill: 'blue' } ),
      //new Circle( 3, { center: gradientDestination, fill: 'red' } )
    );

    // Allow the client to add child nodes
    options.children = children.concat( options.children || [] );

    // Allow the client to override mouse and touch area, but fall back to the outline
    const outline = createSensorShape().close();
    options.mouseArea = options.mouseArea || outline;
    options.touchArea = options.touchArea || outline;

    this.mutate( options );

    // support for binder documentation, stripped out in builds and only runs when ?binder is specified
    assert && phet.chipper.queryParameters.binder && InstanceRegistry.registerDataURL( 'scenery-phet', 'ProbeNode', this );
  }

  /**
   * @public
   * @override
   */
  dispose() {
    this.brighter5.dispose();
    this.brighter4.dispose();
    this.brighter3.dispose();
    this.brighter2.dispose();
    this.darker2.dispose();
    this.darker3.dispose();

    super.dispose();
  }
}

/**
 * Creates a value for options.sensorTypeFunction. Shows a shiny reflective interior in the central circle.
 * @param {Object} [options]
 * @returns {function(radius: number): Node}
 * @public
 * @static
 */
function glass( options ) {

  options = merge( {
    centerColor: 'white',
    middleColor: '#E6F5FF', // light blue
    edgeColor: '#C2E7FF'    // slightly darker blue, like glass
  }, options );

  return function( radius ) {
    return new Circle( radius, {
      fill: new RadialGradient( -radius * 0.15, -radius * 0.15, 0, -radius * 0.15, -radius * 0.20, radius * 0.60 )
        .addColorStop( 0, options.centerColor )
        .addColorStop( 0.4, options.middleColor ) // light blue
        .addColorStop( 1, options.edgeColor ) // slightly darker blue, like glass
    } );
  };
}

/**
 * Creates a value for options.sensorTypeFunction. Shows a crosshairs in the central circle.
 * @param {Object} [options]
 * @returns {function(radius: number): Node}
 * @public
 * @static
 */
function crosshairs( options ) {

  options = merge( {
    stroke: 'black',
    lineWidth: 3,

    // The amount of blank space visible at the intersection of the 2 crosshairs lines
    intersectionRadius: 8
  }, options );

  return function( radius ) {
    const lineOptions = { stroke: options.stroke, lineWidth: options.lineWidth };
    return new Node( {
      children: [
        new Line( -radius, 0, -options.intersectionRadius, 0, lineOptions ),
        new Line( +radius, 0, +options.intersectionRadius, 0, lineOptions ),
        new Line( 0, -radius, 0, -options.intersectionRadius, lineOptions ),
        new Line( 0, +radius, 0, +options.intersectionRadius, lineOptions ) ]
    } );
  };
}

// @public {read-only}, make the defaults publicly available to clients in case they need to make
// customizations, such as 0.9 x the default width
ProbeNode.DEFAULT_OPTIONS = DEFAULT_OPTIONS;

// @public pre-defined functions for creating a value for options.sensorTypeFunction
ProbeNode.glass = glass;
ProbeNode.crosshairs = crosshairs;

sceneryPhet.register( 'ProbeNode', ProbeNode );
export default ProbeNode;<commit_msg>fix "sensor" vs "probe" naming, https://github.com/phetsims/scenery-phet/issues/665<commit_after>// Copyright 2015-2020, University of Colorado Boulder

/**
 * ProbeNode is a physical-looking probe with a handle and a circular sensor region. It is used in simulations like
 * Bending Light and Beer's Law Lab to show how much light is being received. It is typically connected to a body
 * with readouts via a wire. The origin is in the center of the sensor.
 *
 * This code was generalized from Bending Light, see https://github.com/phetsims/bending-light/issues/165
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Chandrashekar Bemagoni (Actual Concepts)
 * @author Chris Malley (PixelZoom, Inc.)
 */

import DerivedProperty from '../../axon/js/DerivedProperty.js';
import Ray2 from '../../dot/js/Ray2.js';
import Vector2 from '../../dot/js/Vector2.js';
import EllipticalArc from '../../kite/js/segments/EllipticalArc.js';
import Shape from '../../kite/js/Shape.js';
import InstanceRegistry from '../../phet-core/js/documentation/InstanceRegistry.js';
import merge from '../../phet-core/js/merge.js';
import Circle from '../../scenery/js/nodes/Circle.js';
import Line from '../../scenery/js/nodes/Line.js';
import Node from '../../scenery/js/nodes/Node.js';
import Path from '../../scenery/js/nodes/Path.js';
import LinearGradient from '../../scenery/js/util/LinearGradient.js';
import PaintColorProperty from '../../scenery/js/util/PaintColorProperty.js';
import RadialGradient from '../../scenery/js/util/RadialGradient.js';
import sceneryPhet from './sceneryPhet.js';

// constants
const DEFAULT_OPTIONS = {
  radius: 50,
  innerRadius: 35,
  handleWidth: 50,
  handleHeight: 30,
  handleCornerRadius: 10,

  /**
   * in radians, the angle of the incoming light.  0 is from the right, PI/2 from the bottom, PI from the left, etc.
   * The default is from the upper-left.  Generally, it is difficult to know the global rotation of the ProbeNode
   * and automatically update the lightAngle when the global rotation changes, so this is up to the developer
   * to set properly.  The light in PhET simulations often comes from the top-left, so please set this value
   * accordingly depending on the context of how the probe is embedded in the simulation.
   */
  lightAngle: 1.35 * Math.PI,
  color: '#008541', // {Color|string} darkish green

  // {null|function(radius:number):Node} Determines what is displayed in the sensor area, the circular cut-out part of
  // the ProbeNode. Set this to null to display nothing in the sensor.
  sensorTypeFunction: glass()
};
assert && Object.freeze( DEFAULT_OPTIONS );

class ProbeNode extends Node {

  /**
   * @param {Object} [options]
   */
  constructor( options ) {

    options = merge( {}, DEFAULT_OPTIONS, options );

    super();

    // To improve readability
    const radius = options.radius;

    // y coordinate of the bottom of the handle, relative to the origin (center of the sensor)
    const handleBottom = radius + options.handleHeight;

    // Constants that determine the outer Shape of the probe
    const arcExtent = 0.8;
    const handleWidth = options.handleWidth;
    const innerRadius = Math.min( options.innerRadius, options.radius );
    const cornerRadius = options.handleCornerRadius;
    const neckCornerRadius = 10;

    // We must know where the elliptical arc begins, so create an explicit EllipticalArc for that
    // Note: This elliptical arc must match the ellipticalArc call below
    const ellipticalArcStart = new EllipticalArc( new Vector2( 0, 0 ), radius, radius, 0, Math.PI * arcExtent, Math.PI * ( 1 - arcExtent ), false ).start;

    // Creates the Shape for the outside edge of the probe, circular at top with handle at the bottom.
    function createOuterProbeShape() {
      return new Shape()

        // start in the bottom center
        .moveTo( 0, handleBottom )

        // Kite Shape automatically lineTo's to the first point of an arc, so no need to lineTo ourselves
        .arc( -handleWidth / 2 + cornerRadius, handleBottom - cornerRadius, cornerRadius, Math.PI / 2, Math.PI, false )
        .lineTo( -handleWidth / 2, radius + neckCornerRadius )
        .quadraticCurveTo( -handleWidth / 2, radius, ellipticalArcStart.x, ellipticalArcStart.y )

        // Top arc
        // Note: his elliptical arc must match the EllipticalArc above
        .ellipticalArc( 0, 0, radius, radius, 0, Math.PI * arcExtent, Math.PI * ( 1 - arcExtent ), false )

        .quadraticCurveTo( handleWidth / 2, radius, +handleWidth / 2, radius + neckCornerRadius )
        .arc( handleWidth / 2 - cornerRadius, handleBottom - cornerRadius, cornerRadius, 0, Math.PI / 2, false )

        .lineTo( 0, handleBottom );
    }

    // Start with the outer Shape of the probe, and cut out the sensor area.
    const probeShape = createOuterProbeShape()
      .moveTo( innerRadius, 0 )
      .arc( 0, 0, innerRadius, Math.PI * 2, 0, true )
      .close();

    // The light angle is variable so that you can create a probe node that is pointing up or to the side
    const lightAngle = options.lightAngle;

    const center = probeShape.bounds.center;
    const v1 = Vector2.createPolar( 1, lightAngle );
    const intersections = probeShape.intersection( new Ray2( center, v1 ) );

    // take last intersection or zero point, see https://github.com/phetsims/scenery-phet/issues/294
    const lastIntersection = intersections[ intersections.length - 1 ];
    const lastIntersectionPoint = lastIntersection ? lastIntersection.point : Vector2.ZERO;
    const gradientSource = lastIntersectionPoint.plus( v1.timesScalar( 1 ) );

    const v2 = Vector2.createPolar( 1, lightAngle + Math.PI );
    const intersections2 = probeShape.intersection( new Ray2( center, v2 ) );

    // take last intersection or zero point, see https://github.com/phetsims/scenery-phet/issues/294
    const lastIntersection2 = intersections2[ intersections2.length - 1 ];
    const lastIntersectionPoint2 = lastIntersection2 ? lastIntersection2.point : Vector2.ZERO;
    const gradientDestination = lastIntersectionPoint2.plus( v2.timesScalar( 1 ) );

    // @private {Property.<Color>}
    this.brighter5 = new PaintColorProperty( options.color, { luminanceFactor: 0.5 } );
    this.brighter4 = new PaintColorProperty( options.color, { luminanceFactor: 0.4 } );
    this.brighter3 = new PaintColorProperty( options.color, { luminanceFactor: 0.3 } );
    this.brighter2 = new PaintColorProperty( options.color, { luminanceFactor: 0.2 } );
    this.darker2 = new PaintColorProperty( options.color, { luminanceFactor: -0.2 } );
    this.darker3 = new PaintColorProperty( options.color, { luminanceFactor: -0.3 } );

    // The main path of the probe
    const mainPath = new Path( probeShape, {
      stroke: new LinearGradient( gradientSource.x, gradientSource.y, gradientDestination.x, gradientDestination.y )
        .addColorStop( 0.0, this.brighter2 ) // highlight
        .addColorStop( 1.0, this.darker2 ), // shadow
      fill: new LinearGradient( gradientSource.x, gradientSource.y, gradientDestination.x, gradientDestination.y )
        .addColorStop( 0.0, this.brighter5 ) // highlight
        .addColorStop( 0.03, this.brighter4 )
        .addColorStop( 0.07, this.brighter4 )
        .addColorStop( 0.11, this.brighter2 )
        .addColorStop( 0.3, options.color )
        .addColorStop( 0.8, this.darker2 ) // shadows
        .addColorStop( 1.0, this.darker3 ),
      lineWidth: 2
    } );

    // The front flat "surface" of the probe, makes it look 3d by giving the probe a beveled appearance and putting
    // a shiny glare on the top edge.
    const frontPath = new Path( probeShape, {
      fill: options.color,

      // y scale is an empirical function of handle height, to keep bevel at bottom of handle from changing size
      scale: new Vector2( 0.9, 0.93 + ( 0.01 * options.handleHeight / DEFAULT_OPTIONS.handleHeight ) ),
      centerX: mainPath.centerX,
      stroke: new DerivedProperty( [ this.brighter3 ], function( color ) {
        return color.withAlpha( 0.5 );
      } ),
      lineWidth: 1.2,
      y: 2 // Shift it down a bit to make the face look a bit more 3d
    } );

    const children = [];
    if ( options.sensorTypeFunction ) {
      children.push( options.sensorTypeFunction( radius ) );
    }
    children.push(
      mainPath,
      frontPath
      //new Circle( 3, { center: gradientSource, fill: 'blue' } ),
      //new Circle( 3, { center: gradientDestination, fill: 'red' } )
    );

    // Allow the client to add child nodes
    options.children = children.concat( options.children || [] );

    // Allow the client to override mouse and touch area, but fall back to the outline
    const outline = createOuterProbeShape().close();
    options.mouseArea = options.mouseArea || outline;
    options.touchArea = options.touchArea || outline;

    this.mutate( options );

    // support for binder documentation, stripped out in builds and only runs when ?binder is specified
    assert && phet.chipper.queryParameters.binder && InstanceRegistry.registerDataURL( 'scenery-phet', 'ProbeNode', this );
  }

  /**
   * @public
   * @override
   */
  dispose() {
    this.brighter5.dispose();
    this.brighter4.dispose();
    this.brighter3.dispose();
    this.brighter2.dispose();
    this.darker2.dispose();
    this.darker3.dispose();

    super.dispose();
  }
}

/**
 * Creates a value for options.sensorTypeFunction. Shows a shiny reflective interior in the sensor area.
 * @param {Object} [options]
 * @returns {function(radius: number): Node}
 * @public
 * @static
 */
function glass( options ) {

  options = merge( {
    centerColor: 'white',
    middleColor: '#E6F5FF', // light blue
    edgeColor: '#C2E7FF'    // slightly darker blue, like glass
  }, options );

  return function( radius ) {
    return new Circle( radius, {
      fill: new RadialGradient( -radius * 0.15, -radius * 0.15, 0, -radius * 0.15, -radius * 0.20, radius * 0.60 )
        .addColorStop( 0, options.centerColor )
        .addColorStop( 0.4, options.middleColor ) // light blue
        .addColorStop( 1, options.edgeColor ) // slightly darker blue, like glass
    } );
  };
}

/**
 * Creates a value for options.sensorTypeFunction. Shows a crosshairs in the sensor area.
 * @param {Object} [options]
 * @returns {function(radius: number): Node}
 * @public
 * @static
 */
function crosshairs( options ) {

  options = merge( {
    stroke: 'black',
    lineWidth: 3,

    // The amount of blank space visible at the intersection of the 2 crosshairs lines
    intersectionRadius: 8
  }, options );

  return function( radius ) {
    const lineOptions = { stroke: options.stroke, lineWidth: options.lineWidth };
    return new Node( {
      children: [
        new Line( -radius, 0, -options.intersectionRadius, 0, lineOptions ),
        new Line( +radius, 0, +options.intersectionRadius, 0, lineOptions ),
        new Line( 0, -radius, 0, -options.intersectionRadius, lineOptions ),
        new Line( 0, +radius, 0, +options.intersectionRadius, lineOptions ) ]
    } );
  };
}

// @public {read-only}, make the defaults publicly available to clients in case they need to make
// customizations, such as 0.9 x the default width
ProbeNode.DEFAULT_OPTIONS = DEFAULT_OPTIONS;

// @public pre-defined functions for creating a value for options.sensorTypeFunction
ProbeNode.glass = glass;
ProbeNode.crosshairs = crosshairs;

sceneryPhet.register( 'ProbeNode', ProbeNode );
export default ProbeNode;<|endoftext|>