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

/**
 * Conductivity tester. Light bulb connected to a battery, with draggable probes.
 * When the probes are both immersed in solution, the circuit is completed, and the bulb glows.
 *
 * @author <NAME> (Mlearner)
 * @author <NAME> (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var Circle = require( 'SCENERY/nodes/Circle' );
  var Dimension2 = require( 'DOT/Dimension2' );
  var Image = require( 'SCENERY/nodes/Image' );
  var inherit = require( 'PHET_CORE/inherit' );
  var LightBulbNode = require( 'SCENERY_PHET/LightBulbNode' );
  var MinusNode = require( 'SCENERY_PHET/MinusNode' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var PlusNode = require( 'SCENERY_PHET/PlusNode' );
  var Property = require( 'AXON/Property' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Shape = require( 'KITE/Shape' );
  var SimpleDragHandler = require( 'SCENERY/input/SimpleDragHandler' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Util = require( 'DOT/Util' );
  var Vector2 = require( 'DOT/Vector2' );

  // images
  var batteryImage = require( 'image!SCENERY_PHET/battery-D-cell.png' );

  // strings
  var shortCircuitString = require( 'string!SCENERY_PHET/shortCircuit' );

  // constants
  var SHOW_TESTER_ORIGIN = false; // draws a red circle at the tester's origin, for debugging
  var SHOW_PROBE_ORIGIN = false; // draws a red circle at the origin of probes, for debugging

  /**
   * @param {Property.<number>} brightnessProperty brightness of bulb varies from 0 (off) to 1 (full on)
   * @param {Property.<Vector2>} locationProperty location of the tester, at bottom-center of the bulb
   * @param {Property.<Vector2>} positiveProbeLocationProperty location of bottom-center of the positive probe
   * @param {Property.<Vector2>} negativeProbeLocationProperty location of bottom-center of the negative probe
   * @param {Object} [options]
   * @constructor
   */
  function ConductivityTesterNode( brightnessProperty, locationProperty, positiveProbeLocationProperty, negativeProbeLocationProperty, options ) {

    var thisNode = this;

    options = _.extend( {
      interactive: true, // set to false if you're creating an icon
      bulbImageScale: 0.33,
      batteryImageScale: 0.6,
      // common to both probes
      probeSize: new Dimension2( 20, 68 ),
      probeLineWidth: 0.5,
      probeDragYRange: null, // {DOT.Range} y-axis drag range, relative to locationProperty. null means no constraint.
      probeCursor: 'pointer',
      // positive probe
      positiveProbeFill: 'red',
      positiveProbeStroke: 'black',
      positiveLabelFill: 'white',
      // negative probe
      negativeProbeFill: 'black',
      negativeProbeStroke: 'black',
      negativeLabelFill: 'white',
      // wires
      wireStroke: 'black',
      wireLineWidth: 1.5,
      bulbToBatteryWireLength: 40, // length of the wire between bulb and battery
      // short-circuit indicator
      shortCircuitFont: new PhetFont( 14 ),
      shortCircuitFill: 'black'
    }, options );

    // @private bulb, origin at bottom center of base
    this.lightBulbNode = new LightBulbNode( brightnessProperty, {
      bulbImageScale: options.bulbImageScale
    } );

    // @private short-circuit indicator, centered above the light bulb
    this.shortCircuitNode = new Text( shortCircuitString, {
      font: options.shortCircuitFont,
      fill: options.shortCircuitFill,
      centerX: this.lightBulbNode.centerX,
      bottom: this.lightBulbNode.top,
      visible: false // initial state is no short circuit
    } );

    // battery
    var battery = new Image( batteryImage, {
      scale: options.batteryImageScale,
      left: options.bulbToBatteryWireLength,
      centerY: 0
    } );

    // wire from bulb base to battery
    var bulbBatteryWire = new Path( new Shape().moveTo( 0, 0 ).lineTo( options.bulbToBatteryWireLength, 0 ), {
      stroke: options.wireStroke,
      lineWidth: options.wireLineWidth
    } );

    // apparatus (bulb + battery), origin at bottom center of bulb's base
    var apparatusNode = new Node( {
      children: [
        bulbBatteryWire,
        battery,
        this.lightBulbNode,
        this.shortCircuitNode
      ]
    } );
    if ( SHOW_TESTER_ORIGIN ) {
      apparatusNode.addChild( new Circle( 2, { fill: 'red' } ) );
    }

    // wire from battery terminal to positive probe
    var positiveWire = new WireNode(
      battery.getGlobalBounds().right,
      battery.getGlobalBounds().centerY,
      positiveProbeLocationProperty.get().x - locationProperty.get().x,
      positiveProbeLocationProperty.get().y - locationProperty.get().y - options.probeSize.height,
      { stroke: options.wireStroke, lineWidth: options.wireLineWidth }
    );

    // wire from base of bulb (origin) to negative probe
    var negativeWire = new WireNode(
      -5, -5, // specific to bulb image file
      negativeProbeLocationProperty.get().x - locationProperty.get().x,
      negativeProbeLocationProperty.get().y - locationProperty.get().y - options.probeSize.height,
      { stroke: options.wireStroke, lineWidth: options.wireLineWidth }
    );

    // drag handler for probes
    var probeDragHandler = new SimpleDragHandler( {

      clickYOffset: 0,

      start: function( e ) {
        this.clickYOffset = e.currentTarget.globalToParentPoint( e.pointer.point ).y - e.currentTarget.y;
      },

      // probes move together
      drag: function( e ) {
        var y = e.currentTarget.globalToParentPoint( e.pointer.point ).y + locationProperty.get().y - this.clickYOffset;
        if ( options.probeDragYRange ) {
          y = Util.clamp( y, locationProperty.get().y + options.probeDragYRange.min, locationProperty.get().y + options.probeDragYRange.max );
        }
        positiveProbeLocationProperty.set( new Vector2( positiveProbeLocationProperty.get().x, y ) );
        negativeProbeLocationProperty.set( new Vector2( negativeProbeLocationProperty.get().x, y ) );
      }
    } );

    // probes
    var positiveProbe = new ProbeNode( new PlusNode( { fill: options.positiveLabelFill } ), {
      size: options.probeSize,
      fill: options.positiveProbeFill,
      stroke: options.positiveProbeStroke,
      lineWidth: options.probeLineWidth
    } );
    var negativeProbe = new ProbeNode( new MinusNode( { fill: options.negativeLabelFill } ), {
      size: options.probeSize,
      fill: options.negativeProbeFill,
      stroke: options.negativeProbeStroke,
      lineWidth: options.probeLineWidth
    } );
    if ( options.interactive ) {
      positiveProbe.cursor = options.probeCursor;
      positiveProbe.addInputListener( probeDragHandler );
      negativeProbe.cursor = options.probeCursor;
      negativeProbe.addInputListener( probeDragHandler );
    }

    Node.call( this, { children: [ positiveWire, negativeWire, positiveProbe, negativeProbe, apparatusNode ] } );

    // when the location changes ...
    this.locationObserver = function( location, oldLocation ) {
      // move the entire tester
      thisNode.translation = location;
      // probes move with the tester
      var dx = oldLocation ? ( location.x - oldLocation.x ) : 0;
      var dy = oldLocation ? ( location.y - oldLocation.y ) : 0;
      positiveProbeLocationProperty.set( new Vector2( positiveProbeLocationProperty.get().x + dx, positiveProbeLocationProperty.get().y + dy ) );
      negativeProbeLocationProperty.set( new Vector2( negativeProbeLocationProperty.get().x + dx, negativeProbeLocationProperty.get().y + dy ) );
    };
    this.locationProperty = locationProperty;
    this.locationProperty.link( this.locationObserver );

    // @private update positive wire if end point was changed
    this.positiveProbeObserver = function( positiveProbeLocation ) {
      positiveProbe.centerX = positiveProbeLocation.x - thisNode.locationProperty.get().x;
      positiveProbe.bottom = positiveProbeLocation.y - thisNode.locationProperty.get().y;
      positiveWire.setEndPoint( positiveProbe.x, positiveProbe.y - options.probeSize.height );
    };
    this.positiveProbeLocationProperty = positiveProbeLocationProperty; // @private
    this.positiveProbeLocationProperty.link( this.positiveProbeObserver );

    // @private update negative wire if end point was changed
    this.negativeProbeObserver = function( negativeProbeLocation ) {
      negativeProbe.centerX = negativeProbeLocation.x - thisNode.locationProperty.get().x;
      negativeProbe.bottom = negativeProbeLocation.y - thisNode.locationProperty.get().y;
      negativeWire.setEndPoint( negativeProbe.x, negativeProbe.y - options.probeSize.height );
    };
    this.negativeProbeLocationProperty = negativeProbeLocationProperty; // @private
    this.negativeProbeLocationProperty.link( this.negativeProbeObserver );

    // Since locationProperty determines translation, avoid options related to translation!
    this.mutate( options );
  }

  inherit( Node, ConductivityTesterNode, {

    // Ensures that this object is eligible for GC
    dispose: function() {

      // unlink from axon properties
      this.locationProperty.unlink( this.locationObserver );
      this.positiveProbeLocationProperty.unlink( this.positiveProbeObserver );
      this.negativeProbeLocationProperty.unlink( this.negativeProbeObserver );

      // dispose of sub-components
      this.lightBulbNode.dispose();
      this.lightBulbNode = null;
    },

    // @override
    setVisible: function( visible ) {
      Node.prototype.setVisible.call( this, visible );
      this.lightBulbNode.visible = visible; // to prevent light from updating when invisible
    },

    /**
     * Determines whether 'Short circuit' is shown above the light bulb. Note that it is the client's responsibility
     * to ensure that the bulb's brightness (as set by brightnessProperty) is appropriate for a short circuit.
     * @param {boolean} value
     */
    set shortCircuit( value ) { this.shortCircuitNode.visible = value; },

    /**
     * Is 'Short circuit' shown above the light bulb?
     * @returns {boolean}
     */
    get shortCircuit() { return this.shortCircuitNode.visible; }
  } );

  /**
   * Conductivity probe, origin at bottom center.
   *
   * @param {Node} labelNode
   * @param {Object} [options]
   * @constructor
   */
  function ProbeNode( labelNode, options ) {

    options = _.extend( {
      size: new Dimension2( 20, 60 ),
      fill: 'white',
      stroke: 'black',
      lineWidth: 1.5
    }, options );

    Node.call( this );

    // plate
    var plateNode = new Rectangle( -options.size.width / 2, -options.size.height, options.size.width, options.size.height, {
      fill: options.fill,
      stroke: options.stroke,
      lineWidth: options.lineWidth
    } );

    // scale the label to fix, place it towards bottom center
    labelNode.setScaleMagnitude( 0.5 * options.size.width / labelNode.width );
    labelNode.centerX = plateNode.centerX;
    labelNode.bottom = plateNode.bottom - 10;

    // rendering order
    this.addChild( plateNode );
    this.addChild( labelNode );
    if ( SHOW_PROBE_ORIGIN ) {
      this.addChild( new Circle( 2, { fill: 'red' } ) );
    }

    // expand touch area
    this.touchArea = this.localBounds.dilatedXY( 10, 10 );

    this.mutate( options );
  }

  inherit( Node, ProbeNode );

  /**
   * Wires that connect to the probes.
   *
   * @param startX
   * @param startY
   * @param endX
   * @param endY
   * @param {Object} [options]
   * @constructor
   */
  function WireNode( startX, startY, endX, endY, options ) {

    Path.call( this );

    this.startPoint = { x: startX, y: startY }; // @private
    // control point offsets for when probe is to left of light bulb
    this.controlPointOffset = { x: 30, y: -50 }; // @private
    if ( endX < startX ) {
      // probe is to right of light bulb, flip sign on control point x-offset
      this.controlPointOffset.x = -this.controlPointOffset.x;
    }

    this.setEndPoint( endX, endY );

    this.mutate( options );
  }

  inherit( Path, WireNode, {

    // Sets the end point coordinates, the point attached to the probe.
    setEndPoint: function( endX, endY ) {

      var startX = this.startPoint.x;
      var startY = this.startPoint.y;
      var controlPointXOffset = this.controlPointOffset.x;
      var controlPointYOffset = this.controlPointOffset.y;

      this.setShape( new Shape()
          .moveTo( startX, startY )
          .cubicCurveTo( startX + controlPointXOffset, startY, endX, endY + controlPointYOffset, endX, endY )
      );
    }
  } );

  /**
   * Convenience function for creating an icon.
   * @param {number} brightness 0-1 (off to full on)
   * @param {number} positiveProbeXOffset x-offset of the positive probe, relative to the bulb's tip
   * @param {number} negativeProbeXOffset x-offset of the negative probe, relative to the bulb's tip
   * @param {number} bothProbesYOffset y-offset of both probes, relative to the bulb's tip
   * @param {Object} [options] same options as ConductivityTesterNode constructor
   * @returns {ConductivityTesterNode}
   */
  ConductivityTesterNode.createIcon = function( brightness, positiveProbeXOffset, negativeProbeXOffset, bothProbesYOffset, options ) {

    options = options || {};
    options.interactive = false;

    return new ConductivityTesterNode(
      new Property( brightness ),
      new Property( new Vector2( 0, 0 ) ),
      new Property( new Vector2( positiveProbeXOffset, bothProbesYOffset ) ),
      new Property( new Vector2( negativeProbeXOffset, bothProbesYOffset ) ),
      options
    );
  };

  return ConductivityTesterNode;
} );
<commit_msg>#132 add TODO items, document coordinate frame for options<commit_after>// Copyright 2002-2015, University of Colorado Boulder

/**
 * Conductivity tester. Light bulb connected to a battery, with draggable probes.
 * When the probes are both immersed in solution, the circuit is completed, and the bulb glows.
 *
 * @author <NAME> (Mlearner)
 * @author <NAME> (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var Circle = require( 'SCENERY/nodes/Circle' );
  var Dimension2 = require( 'DOT/Dimension2' );
  var Image = require( 'SCENERY/nodes/Image' );
  var inherit = require( 'PHET_CORE/inherit' );
  var LightBulbNode = require( 'SCENERY_PHET/LightBulbNode' );
  var MinusNode = require( 'SCENERY_PHET/MinusNode' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var PlusNode = require( 'SCENERY_PHET/PlusNode' );
  var Property = require( 'AXON/Property' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Shape = require( 'KITE/Shape' );
  var SimpleDragHandler = require( 'SCENERY/input/SimpleDragHandler' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Util = require( 'DOT/Util' );
  var Vector2 = require( 'DOT/Vector2' );

  // images
  var batteryImage = require( 'image!SCENERY_PHET/battery-D-cell.png' );

  // strings
  var shortCircuitString = require( 'string!SCENERY_PHET/shortCircuit' );

  // constants
  var SHOW_TESTER_ORIGIN = false; // draws a red circle at the tester's origin, for debugging
  var SHOW_PROBE_ORIGIN = false; // draws a red circle at the origin of probes, for debugging

  /**
   * @param {Property.<number>} brightnessProperty brightness of bulb varies from 0 (off) to 1 (full on)
   * @param {Property.<Vector2>} locationProperty location of the tester, at bottom-center of the bulb
   * @param {Property.<Vector2>} positiveProbeLocationProperty location of bottom-center of the positive probe
   * @param {Property.<Vector2>} negativeProbeLocationProperty location of bottom-center of the negative probe
   * @param {Object} [options]
   * @constructor
   */
  function ConductivityTesterNode( brightnessProperty, locationProperty, positiveProbeLocationProperty, negativeProbeLocationProperty, options ) {

    var thisNode = this;

    options = _.extend( {
      interactive: true, // set to false if you're creating an icon
      bulbImageScale: 0.33,
      batteryImageScale: 0.6,
      //TODO add modelViewTransform: ModelViewTransform.createIdentity(), apply where needed
      // common to both probes
      probeSize: new Dimension2( 20, 68 ), // {Dimension2} probe dimensions, in view coordinates
      probeLineWidth: 0.5,
      probeDragYRange: null, // {DOT.Range} y-axis drag range, relative to locationProperty, in view coordinates. null means no constraint.
      probeCursor: 'pointer',
      // positive probe
      positiveProbeFill: 'red',
      positiveProbeStroke: 'black',
      positiveLabelFill: 'white',
      // negative probe
      negativeProbeFill: 'black',
      negativeProbeStroke: 'black',
      negativeLabelFill: 'white',
      // wires
      wireStroke: 'black',
      wireLineWidth: 1.5,
      bulbToBatteryWireLength: 40, // length of the wire between bulb and battery, in view coordinates
      // short-circuit indicator
      shortCircuitFont: new PhetFont( 14 ),
      shortCircuitFill: 'black'
    }, options );

    // @private bulb, origin at bottom center of base
    this.lightBulbNode = new LightBulbNode( brightnessProperty, {
      bulbImageScale: options.bulbImageScale
    } );

    // @private short-circuit indicator, centered above the light bulb
    this.shortCircuitNode = new Text( shortCircuitString, {
      font: options.shortCircuitFont,
      fill: options.shortCircuitFill,
      centerX: this.lightBulbNode.centerX,
      bottom: this.lightBulbNode.top,  //TODO this will be wrong if initial brightnessProperty is > 0, because lightBulbNode will have rays
      visible: false // initial state is no short circuit
    } );

    // battery
    var battery = new Image( batteryImage, {
      scale: options.batteryImageScale,
      left: options.bulbToBatteryWireLength,
      centerY: 0
    } );

    // wire from bulb base to battery
    var bulbBatteryWire = new Path( new Shape().moveTo( 0, 0 ).lineTo( options.bulbToBatteryWireLength, 0 ), {
      stroke: options.wireStroke,
      lineWidth: options.wireLineWidth
    } );

    // apparatus (bulb + battery), origin at bottom center of bulb's base
    var apparatusNode = new Node( {
      children: [
        bulbBatteryWire,
        battery,
        this.lightBulbNode,
        this.shortCircuitNode
      ]
    } );
    if ( SHOW_TESTER_ORIGIN ) {
      apparatusNode.addChild( new Circle( 2, { fill: 'red' } ) );
    }

    // wire from battery terminal to positive probe
    var positiveWire = new WireNode(
      battery.getGlobalBounds().right,
      battery.getGlobalBounds().centerY,
      positiveProbeLocationProperty.get().x - locationProperty.get().x,
      positiveProbeLocationProperty.get().y - locationProperty.get().y - options.probeSize.height,
      { stroke: options.wireStroke, lineWidth: options.wireLineWidth }
    );

    // wire from base of bulb (origin) to negative probe
    var negativeWire = new WireNode(
      -5, -5, // specific to bulb image file
      negativeProbeLocationProperty.get().x - locationProperty.get().x,
      negativeProbeLocationProperty.get().y - locationProperty.get().y - options.probeSize.height,
      { stroke: options.wireStroke, lineWidth: options.wireLineWidth }
    );

    // drag handler for probes
    var probeDragHandler = new SimpleDragHandler( {

      clickYOffset: 0,

      start: function( e ) {
        this.clickYOffset = e.currentTarget.globalToParentPoint( e.pointer.point ).y - e.currentTarget.y;
      },

      // probes move together
      drag: function( e ) {
        var y = e.currentTarget.globalToParentPoint( e.pointer.point ).y + locationProperty.get().y - this.clickYOffset;
        if ( options.probeDragYRange ) {
          y = Util.clamp( y, locationProperty.get().y + options.probeDragYRange.min, locationProperty.get().y + options.probeDragYRange.max );
        }
        positiveProbeLocationProperty.set( new Vector2( positiveProbeLocationProperty.get().x, y ) );
        negativeProbeLocationProperty.set( new Vector2( negativeProbeLocationProperty.get().x, y ) );
      }
    } );

    // probes
    var positiveProbe = new ProbeNode( new PlusNode( { fill: options.positiveLabelFill } ), {
      size: options.probeSize,
      fill: options.positiveProbeFill,
      stroke: options.positiveProbeStroke,
      lineWidth: options.probeLineWidth
    } );
    var negativeProbe = new ProbeNode( new MinusNode( { fill: options.negativeLabelFill } ), {
      size: options.probeSize,
      fill: options.negativeProbeFill,
      stroke: options.negativeProbeStroke,
      lineWidth: options.probeLineWidth
    } );
    if ( options.interactive ) {
      positiveProbe.cursor = options.probeCursor;
      positiveProbe.addInputListener( probeDragHandler );
      negativeProbe.cursor = options.probeCursor;
      negativeProbe.addInputListener( probeDragHandler );
    }

    Node.call( this, { children: [ positiveWire, negativeWire, positiveProbe, negativeProbe, apparatusNode ] } );

    // when the location changes ...
    this.locationObserver = function( location, oldLocation ) {
      // move the entire tester
      thisNode.translation = location;
      // probes move with the tester
      var dx = oldLocation ? ( location.x - oldLocation.x ) : 0;
      var dy = oldLocation ? ( location.y - oldLocation.y ) : 0;
      positiveProbeLocationProperty.set( new Vector2( positiveProbeLocationProperty.get().x + dx, positiveProbeLocationProperty.get().y + dy ) );
      negativeProbeLocationProperty.set( new Vector2( negativeProbeLocationProperty.get().x + dx, negativeProbeLocationProperty.get().y + dy ) );
    };
    this.locationProperty = locationProperty;
    this.locationProperty.link( this.locationObserver );

    // @private update positive wire if end point was changed
    this.positiveProbeObserver = function( positiveProbeLocation ) {
      positiveProbe.centerX = positiveProbeLocation.x - thisNode.locationProperty.get().x;
      positiveProbe.bottom = positiveProbeLocation.y - thisNode.locationProperty.get().y;
      positiveWire.setEndPoint( positiveProbe.x, positiveProbe.y - options.probeSize.height );
    };
    this.positiveProbeLocationProperty = positiveProbeLocationProperty; // @private
    this.positiveProbeLocationProperty.link( this.positiveProbeObserver );

    // @private update negative wire if end point was changed
    this.negativeProbeObserver = function( negativeProbeLocation ) {
      negativeProbe.centerX = negativeProbeLocation.x - thisNode.locationProperty.get().x;
      negativeProbe.bottom = negativeProbeLocation.y - thisNode.locationProperty.get().y;
      negativeWire.setEndPoint( negativeProbe.x, negativeProbe.y - options.probeSize.height );
    };
    this.negativeProbeLocationProperty = negativeProbeLocationProperty; // @private
    this.negativeProbeLocationProperty.link( this.negativeProbeObserver );

    // Since locationProperty determines translation, avoid options related to translation!
    this.mutate( options );
  }

  inherit( Node, ConductivityTesterNode, {

    // Ensures that this object is eligible for GC
    dispose: function() {

      // unlink from axon properties
      this.locationProperty.unlink( this.locationObserver );
      this.positiveProbeLocationProperty.unlink( this.positiveProbeObserver );
      this.negativeProbeLocationProperty.unlink( this.negativeProbeObserver );

      // dispose of sub-components
      this.lightBulbNode.dispose();
      this.lightBulbNode = null;
    },

    // @override
    setVisible: function( visible ) {
      Node.prototype.setVisible.call( this, visible );
      this.lightBulbNode.visible = visible; // to prevent light from updating when invisible
    },

    /**
     * Determines whether 'Short circuit' is shown above the light bulb. Note that it is the client's responsibility
     * to ensure that the bulb's brightness (as set by brightnessProperty) is appropriate for a short circuit.
     * @param {boolean} value
     */
    set shortCircuit( value ) { this.shortCircuitNode.visible = value; },

    /**
     * Is 'Short circuit' shown above the light bulb?
     * @returns {boolean}
     */
    get shortCircuit() { return this.shortCircuitNode.visible; }
  } );

  /**
   * Conductivity probe, origin at bottom center.
   *
   * @param {Node} labelNode
   * @param {Object} [options]
   * @constructor
   */
  function ProbeNode( labelNode, options ) {

    options = _.extend( {
      size: new Dimension2( 20, 60 ),
      fill: 'white',
      stroke: 'black',
      lineWidth: 1.5
    }, options );

    Node.call( this );

    // plate
    var plateNode = new Rectangle( -options.size.width / 2, -options.size.height, options.size.width, options.size.height, {
      fill: options.fill,
      stroke: options.stroke,
      lineWidth: options.lineWidth
    } );

    // scale the label to fix, place it towards bottom center
    labelNode.setScaleMagnitude( 0.5 * options.size.width / labelNode.width );
    labelNode.centerX = plateNode.centerX;
    labelNode.bottom = plateNode.bottom - 10;

    // rendering order
    this.addChild( plateNode );
    this.addChild( labelNode );
    if ( SHOW_PROBE_ORIGIN ) {
      this.addChild( new Circle( 2, { fill: 'red' } ) );
    }

    // expand touch area
    this.touchArea = this.localBounds.dilatedXY( 10, 10 );

    this.mutate( options );
  }

  inherit( Node, ProbeNode );

  /**
   * Wires that connect to the probes.
   *
   * @param startX
   * @param startY
   * @param endX
   * @param endY
   * @param {Object} [options]
   * @constructor
   */
  function WireNode( startX, startY, endX, endY, options ) {

    Path.call( this );

    this.startPoint = { x: startX, y: startY }; // @private
    // control point offsets for when probe is to left of light bulb
    this.controlPointOffset = { x: 30, y: -50 }; // @private
    if ( endX < startX ) {
      // probe is to right of light bulb, flip sign on control point x-offset
      this.controlPointOffset.x = -this.controlPointOffset.x;
    }

    this.setEndPoint( endX, endY );

    this.mutate( options );
  }

  inherit( Path, WireNode, {

    // Sets the end point coordinates, the point attached to the probe.
    setEndPoint: function( endX, endY ) {

      var startX = this.startPoint.x;
      var startY = this.startPoint.y;
      var controlPointXOffset = this.controlPointOffset.x;
      var controlPointYOffset = this.controlPointOffset.y;

      this.setShape( new Shape()
          .moveTo( startX, startY )
          .cubicCurveTo( startX + controlPointXOffset, startY, endX, endY + controlPointYOffset, endX, endY )
      );
    }
  } );

  /**
   * Convenience function for creating an icon.
   * @param {number} brightness 0-1 (off to full on)
   * @param {number} positiveProbeXOffset x-offset of the positive probe, relative to the bulb's tip
   * @param {number} negativeProbeXOffset x-offset of the negative probe, relative to the bulb's tip
   * @param {number} bothProbesYOffset y-offset of both probes, relative to the bulb's tip
   * @param {Object} [options] same options as ConductivityTesterNode constructor
   * @returns {ConductivityTesterNode}
   */
  ConductivityTesterNode.createIcon = function( brightness, positiveProbeXOffset, negativeProbeXOffset, bothProbesYOffset, options ) {

    options = options || {};
    options.interactive = false;

    return new ConductivityTesterNode(
      new Property( brightness ),
      new Property( new Vector2( 0, 0 ) ),
      new Property( new Vector2( positiveProbeXOffset, bothProbesYOffset ) ),
      new Property( new Vector2( negativeProbeXOffset, bothProbesYOffset ) ),
      options
    );
  };

  return ConductivityTesterNode;
} );

{'fraction_non_alphanumeric': 0.10467370234762008, 'fraction_numerical': 0.005707516691794099, 'mean_word_length': 3.1438345976498585, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 16, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1049250', 'n_tokens_mistral': 8433, 'n_tokens_neox': 8020, 'n_words': 2194}