Life as Clay

Posts Tagged ‘Code

Processing class for finding normal vectors

leave a comment »

Processing uses the PVector class to store vector data. The .normalize() method will normalize any PVector to a unit vector. What I needed to do was to find the normal of a vector — a vector perpendicular to the original vector. This is easy enough to do with some simple math. However, I wanted to encapsulate the logic into a class so that I could store multiple perpendicular vectors (to an original vector) and different locations along the original vector, and at different magnitudes. That is what this class does:

 

class PerpVectorPack {
  // Store the original data
  PVector originalVector;

  // Keep track of values used to calculate points
  ArrayList<Float>   percentOfOriginalVector;
  ArrayList<Float>   magnitudeOfPerpendicularVector;
  ArrayList<Boolean> onRightSide;

  // Storage of calculated perpendicular line points
  ArrayList pointsOnLine;
  ArrayList perpVectorPoints;


  // Constructors
  PerpVectorPack(PVector origVector) {
    initializeLists();
    originalVector = origVector.get();
  }

  PerpVectorPack(PVector origVector, float perpVectDist, float perpVectMag, boolean rightSide) {    
    initializeLists();
    originalVector = origVector.get();
    addPerpVector(perpVectDist, perpVectMag, rightSide);
  }

  private void initializeLists() {
    pointsOnLine                    = new ArrayList();
    perpVectorPoints                = new ArrayList();

    percentOfOriginalVector         = new ArrayList<Float>();
    magnitudeOfPerpendicularVector  = new ArrayList<Float>();
    onRightSide                     = new ArrayList<Boolean>();
  }


  // Methods
  public void addPerpVector(float perpVectDist, float perpVectMag, boolean rightSide) {

    // Keep track of the values used to create the perpendicular points
    percentOfOriginalVector.add(perpVectDist);
    magnitudeOfPerpendicularVector.add(perpVectMag);
    onRightSide.add(rightSide);

    PVector full    = originalVector.get();
    float   fullMag = full.mag();

    // Determine where along the original vector the perpendicular vector should lie (lay?)
    full.normalize();
    full.mult(perpVectDist * fullMag);

    // Set the first local variable
    pointsOnLine.add(full.get());

    // Get the normal of the original full vector and normalize it
    PVector perpVector  = new PVector(-originalVector.y, originalVector.x);
    perpVector.normalize();

    // Determine if it should be on the right or left side of the original vector
    int dir = 1;
    if (!rightSide) dir = -1;

    // Make it the proper length
    perpVector.mult(fullMag * perpVectMag * dir);

    // Offset it from the origin
    perpVector.add(full);

    // Set the second local variable
    perpVectorPoints.add(perpVector.get());
  }

  public PVector ptOnOriginalLine() { 
    return ptOnOriginalLine(0);
  };
  
  public PVector ptOnOriginalLine(int idx) {
    PVector thePoint = (PVector)pointsOnLine.get(idx);
    return thePoint.get();
  }

  public PVector perpVectorPt() { 
    return perpVectorPt(0);
  };
  
  public PVector perpVectorPt(int idx) {
    PVector thePoint = (PVector)perpVectorPoints.get(idx);
    return thePoint.get();
  }
}

Written by Clay

February 21, 2013 at 22:44

Posted in Code, Processing, Resource

Tagged with ,

Paperclip, S3 & Delayed Job in Rails on Heroku – Jan 2012

with 2 comments

Edit: I forgot to mention here that with Paperclip 2.4.5 you have to use the ‘paperclip-aws’ gem in order for Paperclip to work with Amazon’s newer ‘aws-sdk’ gem. That is no longer true with Paperclip 2.5.

I followed this good tutorial on how to push paperclip image processing to the background with delayed_job. My Rails app is deployed to Heroku on the cedar stack. I’ve had problems with the NoMethod errors using delayed_job 3.0.0, so I downgraded to 2.1.4. Also using paperclip version 2.4.5. In the end, I found that I could ditch the struct presented in the aforementioned tutorial and just call handle_asynchronously on my reprocessing method. This is what the codes looks like:


class Image < ActiveRecord::Base
  attr_accessible :profile_pic,
                  :caption,
                  :note,
                  :pic_file_name,
                  :pic_content_type,
                  :pic_file_size,
                  :pic_updated_at,
                  :pic,
                  :pic_attributes,
                  :processing
  
  belongs_to :imageable, :polymorphic => true
  
  # Added for paperclip-aws
  def self.s3_config
      @@s3_config ||= YAML.load(ERB.new(File.read("#{Rails.root}/config/s3.yml")).result)[Rails.env]    
  end
  
  has_attached_file :pic,
                    :styles => { 
                      :large => "500x500>",
                      :thumb => "100x100>", 
                      :tiny => "50>x50>", 
                      :smallest => "24>x24>" },   
                      :default_url => '/:attachment/:style/missing.png',               
                      
                      # Added for paperclip-aws
                      :storage => :aws,
                      :s3_permissions => :authenticated_read,
                      :path => "images/:id/:style/:filename",
                      :s3_credentials => {
                        :access_key_id => self.s3_config['access_key_id'],
                        :secret_access_key => self.s3_config['secret_access_key']
                      },
                      :bucket => self.s3_config['bucket'],
                      :s3_protocol => "https"    
                      
  validates_attachment_content_type :pic, :content_type => [ /^image\/(?:jpeg|gif|png)$/, nil ]
  
  # How to implement on Heroku with processing in the background
  # http://madeofcode.com/posts/42-paperclip-s3-delayed-job-in-rails
  
  # cancel post-processing now, and set flag...
     before_pic_post_process do |image|
       if !image.processing && image.pic_changed?
         image.processing = true
         false # halts processing
       end
     end
     
     # call method from after_save that will be processed in the background
     after_save do |image| 
       if image.processing
         processImageJob(image)
       end
     end
     
     def processImageJob(image)
         image.regenerate_styles!
     end
     handle_asynchronously :processImageJob
     
     # generate styles (downloads original first)
     def regenerate_styles!
       self.pic.reprocess! 
       self.processing = false   
       self.save(:validations => false)
     end
     
     # detect if our source file has changed
     def pic_changed?
       self.pic_file_size_changed? || 
       self.pic_file_name_changed? ||
       self.pic_content_type_changed? || 
       self.pic_updated_at_changed?
     end
                      
end

Written by Clay

January 12, 2012 at 14:29

Posted in Code, Rails, Ruby

Tagged with , , , , ,

Javascript snippet to display elapsed time in words

leave a comment »

Send in two strings in the format indicated and return a string that represents the time elapsed between the two inputs, in words.

function findDuration(startTime, endTime) {
	
	// Accepts input of two time stamps in this format:
	// 2011-11-22T12:40:00-05:00
	
	var year_start    	= parseInt(startTime.substring(0,4), 10);
	var month_start   	= parseInt(startTime.substring(5,7), 10);
	var day_start     	= parseInt(startTime.substring(8,10), 10);
	var hours_start   	= parseInt(startTime.substring(11,13), 10);
	var minutes_start 	= parseInt(startTime.substring(14,16), 10);
	
	var year_end    	= parseInt(endTime.substring(0,4), 10);
	var month_end   	= parseInt(endTime.substring(5,7), 10);
	var day_end     	= parseInt(endTime.substring(8,10), 10);
	var hours_end   	= parseInt(endTime.substring(11,13), 10);
	var minutes_end 	= parseInt(endTime.substring(14,16), 10);
	
    var years_total    	= 0;
    var months_total   	= 0;
    var days_total     	= 0;
    var hours_total    	= 0;
    var minutes_total  	= 0;
    
    var earlier_date = new Date();
    earlier_date.setFullYear(year_start);
    earlier_date.setMonth(month_start - 1);
    earlier_date.setDate(day_start);
    earlier_date.setHours(hours_start);
    earlier_date.setMinutes(minutes_start);
    
    var later_date = new Date();
    later_date.setFullYear(year_end);
    later_date.setMonth(month_end - 1);
    later_date.setDate(day_end);
    later_date.setHours(hours_end);
    later_date.setMinutes(minutes_end);
    
    var nTotalDiff 	= later_date.getTime() - earlier_date.getTime();
    var oDiff 		= new Object();
    
    oDiff.days 		= Math.floor(nTotalDiff/1000/60/60/24);
    nTotalDiff 	   -= oDiff.days*1000*60*60*24;
    
    oDiff.hours 	= Math.floor(nTotalDiff/1000/60/60);
    nTotalDiff 	   -= oDiff.hours*1000*60*60;
    
    oDiff.minutes 	= Math.floor(nTotalDiff/1000/60);
    nTotalDiff 	   -= oDiff.minutes*1000*60;
    
    oDiff.seconds 	= Math.floor(nTotalDiff/1000);
    
    oDiff.years 	= 0;
    if (oDiff.days > 364) {
        oDiff.years = Math.floor(oDiff.days / 365);
        oDiff.days 	= oDiff.days - (oDiff.years * 365) - 1;
    }
    
    var diff_string = '';
    if (oDiff.years > 0) {
        if (oDiff.years == 1) {
            diff_string += oDiff.years + " year ";
        } else {
            diff_string += oDiff.years + " years ";
        }
    }
    if (oDiff.days > 0) {
        if (oDiff.days == 1) {
            diff_string += oDiff.days + " day ";
        } else {
            diff_string += oDiff.days + " days ";
        }
    }
    if (oDiff.hours > 0) {
        if (oDiff.hours == 1) {
            diff_string += oDiff.hours + " hour ";
        } else {
            diff_string += oDiff.hours + " hours ";
        }
    }
    if (oDiff.minutes > 0) {
        if (oDiff.minutes == 1) {
            diff_string += oDiff.minutes + " minute ";
        } else {
            diff_string += oDiff.minutes + " minutes";
        }
    }

    // Add support for seconds here, if you want it.
    
    return diff_string;
}

Written by Clay

November 25, 2011 at 09:58

Posted in Code, javascript

Tagged with , ,

Parsing US Census data with Ruby

leave a comment »

I am a fan of population pyramids as a visualization of age demographics. They are simple and effective. Sometimes getting the data to generate them isn’t….

I’m building a simple Rails app for a client who needs county-level population data. It doesn’t have to be precise. I went with the 2009 estimates from the Census bureau, located here:
http://www.census.gov/popest/counties/asrh/CC-EST2009-alldata.html

There were a few issues:
1. Each state’s county-level data is in a different file. You can order a CD that has all of the data in a single file, but I didn’t have time to do that.
2. There’s a ton of data that I didn’t need in each file. All I needed were the county totals and the totals for male/female for each age group.
3. The data is in rows — a different row for each age group. I needed the data in columns, with one row for each county.

What I did:
1. Downloaded all 51 of the files (DC has its own file) and put them in a folder on my desktop.
2. Changed the extension on them to .csv (because it changed to .txt when I downloaded, for some reason).
3. Wrote the Ruby script below, which extracts the data that I need and puts it into a single .csv file.
4. Saved the script to the same folder as the Census files as script.rb.
5. Opened terminal and ran ruby script.rb
4. Reconciled the data with my Excel sheet that I was using to collect county-level data so that I could seed my Rails app’s counties table.

I comment my code pretty heavily when I write it so that I can look back and remember what I did. I’ve been burned in the past by not commenting thoroughly.

Here’s the script:

# Script to pull population data out of census files for each county
# Original data files are here: http://www.census.gov/popest/counties/asrh/CC-EST2009-alldata.html

require 'CSV'

# These are the FIPS codes for each state which are used in file names.
state_fips_codes = ["01", "02", "04", "05", "06", "08", "09", "10", "11", "12", "13", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "44", "45", "46", "47", "48", "49", "50", "51", "53", "54", "55", "56"]

add_header = true

# This is the file that all of the data will go into...
File.open("county_age_data.csv", "w") do |output_file|
  
  # counter for iterating through the files
  n = 0
  
  state_fips_codes.length.times do  # This loop goes through all of the files
    
    # Read in the relevant file
    state_data = CSV.parse(File.read("cc-est2009-alldata-" + state_fips_codes[n] + ".csv"))
    puts "Processing data for " + state_data[1][3]

    # Add the header to the file
    if add_header == true
      output_file.puts "state_fips,county_fips,state_name,county_name,:census_total_pop,:census_total_male,:census_grp01_male,:census_grp02_male,:census_grp03_male,:census_grp04_male,:census_grp05_male,:census_grp06_male,:census_grp07_male,:census_grp08_male,:census_grp09_male,:census_grp10_male,:census_grp11_male,:census_grp12_male,:census_grp13_male,:census_grp14_male,:census_grp15_male,:census_grp16_male,:census_grp17_male,:census_grp18_male,:census_total_female,:census_grp01_female,:census_grp02_female,:census_grp03_female,:census_grp04_female,:census_grp05_female,:census_grp06_female,:census_grp07_female,:census_grp08_female,:census_grp09_female,:census_grp10_female,:census_grp11_female,:census_grp12_female,:census_grp13_female,:census_grp14_female,:census_grp15_female,:census_grp16_female,:census_grp17_female,:census_grp18_female"
      add_header = false
    end

    # Iterate through the loaded file.
    row = 1
    while row < state_data.length
      if state_data[row][5].to_i == 12 # Only take data from year coded 12 (2009 estimates)

        ########## MAIN LOGIC #############
        if state_data[row][6].to_i == 0 # It's a new county because this is the group 0 row with totals

          # Reset the data arrays
          row_data    = []
          male_data   = []
          female_data = []

          row_data.push state_data[row][1].to_s     # state_fips
          row_data.push state_data[row][2].to_s     # county_fips
          row_data.push state_data[row][3].to_s     # state_name
          row_data.push state_data[row][4].to_s     # county_name
          row_data.push state_data[row][7].to_s     # census_total_pop

          male_data.push state_data[row][8].to_s    # census_total_male
          female_data.push state_data[row][9].to_s  # census_total_female

        elsif state_data[row][6].to_i == 18 # It's the last row -- push the data into the array

          # push in the final data points
          male_data.push state_data[row][8].to_s    # census_grp18_male
          female_data.push state_data[row][9].to_s  # census_grp18_female

          # append all of the data to the row_data array
          row_data += male_data
          row_data += female_data

          # write the data to the file
          output_file.puts row_data.join(",")

        else # It's not the first or last row

          male_data.push state_data[row][8].to_s    # census_grpXX_male
          female_data.push state_data[row][9].to_s  # census_grpXX_female

        end # End of the if statement for pushing data into arrays
        ################ END OF MAIN LOGIC ####################
        
      end       # End of the if statement to check if it is year 10
      
      row += 1  # Look at the next row
    end

    n += 1      # Increment the counter
  end           # End of loop for going through each file
end

Written by Clay

July 15, 2010 at 21:17

Posted in Code, Ruby

Tagged with , , ,

Resource: Rails country seeds with ISO codes

leave a comment »

In case you need to seed your Rails app with countries. You can use the ISO codes to display flag icons, like these linked ones from famfamfam.com: link.

 countries = Country.create([
                               {:name => 'Åland Islands', :iso_code => 'AX'},
                               {:name => 'Albania', :iso_code => 'AL'},
                               {:name => 'Algeria', :iso_code => 'DZ'},
                               {:name => 'American Samoa', :iso_code => 'AS'},
                               {:name => 'Andorra', :iso_code => 'AD'},
                               {:name => 'Angola', :iso_code => 'AO'},
                               {:name => 'Anguilla', :iso_code => 'AI'},
                               {:name => 'Antarctica', :iso_code => 'AQ'},
                               {:name => 'Antigua and Barbuda', :iso_code => 'AG'},
                               {:name => 'Argentina', :iso_code => 'AR'},
                               {:name => 'Armenia', :iso_code => 'AM'},
                               {:name => 'Aruba', :iso_code => 'AW'},
                               {:name => 'Australia', :iso_code => 'AU'},
                               {:name => 'Austria', :iso_code => 'AT'},
                               {:name => 'Azerbaijan', :iso_code => 'AZ'},
                               {:name => 'Bahamas', :iso_code => 'BS'},
                               {:name => 'Bahrain', :iso_code => 'BH'},
                               {:name => 'Bangladesh', :iso_code => 'BD'},
                               {:name => 'Barbados', :iso_code => 'BB'},
                               {:name => 'Belarus', :iso_code => 'BY'},
                               {:name => 'Belgium', :iso_code => 'BE'},
                               {:name => 'Belize', :iso_code => 'BZ'},
                               {:name => 'Benin', :iso_code => 'BJ'},
                               {:name => 'Bermuda', :iso_code => 'BM'},
                               {:name => 'Bhutan', :iso_code => 'BT'},
                               {:name => 'Bolivia', :iso_code => 'BO'},
                               {:name => 'Bosnia and Herzegovina', :iso_code => 'BA'},
                               {:name => 'Botswana', :iso_code => 'BW'},
                               {:name => 'Bouvet Island', :iso_code => 'BV'},
                               {:name => 'Brazil', :iso_code => 'BR'},
                               {:name => 'British Indian Ocean Territory', :iso_code => 'IO'},
                               {:name => 'Brunei Darussalam', :iso_code => 'BN'},
                               {:name => 'Bulgaria', :iso_code => 'BG'},
                               {:name => 'Burkina Faso', :iso_code => 'BF'},
                               {:name => 'Burundi', :iso_code => 'BI'},
                               {:name => 'Cambodia', :iso_code => 'KH'},
                               {:name => 'Cameroon', :iso_code => 'CM'},
                               {:name => 'Canada', :iso_code => 'CA'},
                               {:name => 'Cape Verde', :iso_code => 'CV'},
                               {:name => 'Cayman Islands', :iso_code => 'KY'},
                               {:name => 'Central African Republic', :iso_code => 'CF'},
                               {:name => 'Chad', :iso_code => 'TD'},
                               {:name => 'Chile', :iso_code => 'CL'},
                               {:name => 'China', :iso_code => 'CN'},
                               {:name => 'Christmas Island', :iso_code => 'CX'},
                               {:name => 'Cocos (Keeling) Islands', :iso_code => 'CC'},
                               {:name => 'Colombia', :iso_code => 'CO'},
                               {:name => 'Comoros', :iso_code => 'KM'},
                               {:name => 'Congo', :iso_code => 'CG'},
                               {:name => 'Congo, The Democratic Republic of The', :iso_code => 'CD'},
                               {:name => 'Cook Islands', :iso_code => 'CK'},
                               {:name => 'Costa Rica', :iso_code => 'CR'},
                               {:name => 'Côte D\'Ivoire', :iso_code => 'CI'},
                               {:name => 'Croatia', :iso_code => 'HR'},
                               {:name => 'Cuba', :iso_code => 'CU'},
                               {:name => 'Cyprus', :iso_code => 'CY'},
                               {:name => 'Czech Republic', :iso_code => 'CZ'},
                               {:name => 'Denmark', :iso_code => 'DK'},
                               {:name => 'Djibouti', :iso_code => 'DJ'},
                               {:name => 'Dominica', :iso_code => 'DM'},
                               {:name => 'Dominican Republic', :iso_code => 'DO'},
                               {:name => 'Ecuador', :iso_code => 'EC'},
                               {:name => 'Egypt', :iso_code => 'EG'},
                               {:name => 'El Salvador', :iso_code => 'SV'},
                               {:name => 'Equatorial Guinea', :iso_code => 'GQ'},
                               {:name => 'Eritrea', :iso_code => 'ER'},
                               {:name => 'Estonia', :iso_code => 'EE'},
                               {:name => 'Ethiopia', :iso_code => 'ET'},
                               {:name => 'Falkland Islands (Malvinas)', :iso_code => 'FK'},
                               {:name => 'Faroe Islands', :iso_code => 'FO'},
                               {:name => 'Fiji', :iso_code => 'FJ'},
                               {:name => 'Finland', :iso_code => 'FI'},
                               {:name => 'France', :iso_code => 'FR'},
                               {:name => 'French Guiana', :iso_code => 'GF'},
                               {:name => 'French Polynesia', :iso_code => 'PF'},
                               {:name => 'French Southern Territories', :iso_code => 'TF'},
                               {:name => 'Gabon', :iso_code => 'GA'},
                               {:name => 'Gambia', :iso_code => 'GM'},
                               {:name => 'Georgia', :iso_code => 'GE'},
                               {:name => 'Germany', :iso_code => 'DE'},
                               {:name => 'Ghana', :iso_code => 'GH'},
                               {:name => 'Gibraltar', :iso_code => 'GI'},
                               {:name => 'Greece', :iso_code => 'GR'},
                               {:name => 'Greenland', :iso_code => 'GL'},
                               {:name => 'Grenada', :iso_code => 'GD'},
                               {:name => 'Guadeloupe', :iso_code => 'GP'},
                               {:name => 'Guam', :iso_code => 'GU'},
                               {:name => 'Guatemala', :iso_code => 'GT'},
                               {:name => 'Guernsey', :iso_code => 'GG'},
                               {:name => 'Guinea', :iso_code => 'GN'},
                               {:name => 'Guinea-Bissau', :iso_code => 'GW'},
                               {:name => 'Guyana', :iso_code => 'GY'},
                               {:name => 'Haiti', :iso_code => 'HT'},
                               {:name => 'Heard Island and Mcdonald Islands', :iso_code => 'HM'},
                               {:name => 'Holy See (Vatican City State)', :iso_code => 'VA'},
                               {:name => 'Honduras', :iso_code => 'HN'},
                               {:name => 'Hong Kong', :iso_code => 'HK'},
                               {:name => 'Hungary', :iso_code => 'HU'},
                               {:name => 'Iceland', :iso_code => 'IS'},
                               {:name => 'India', :iso_code => 'IN'},
                               {:name => 'Indonesia', :iso_code => 'ID'},
                               {:name => 'Iran, Islamic Republic of', :iso_code => 'IR'},
                               {:name => 'Iraq', :iso_code => 'IQ'},
                               {:name => 'Ireland', :iso_code => 'IE'},
                               {:name => 'Isle of Man', :iso_code => 'IM'},
                               {:name => 'Israel', :iso_code => 'IL'},
                               {:name => 'Italy', :iso_code => 'IT'},
                               {:name => 'Jamaica', :iso_code => 'JM'},
                               {:name => 'Japan', :iso_code => 'JP'},
                               {:name => 'Jersey', :iso_code => 'JE'},
                               {:name => 'Jordan', :iso_code => 'JO'},
                               {:name => 'Kazakhstan', :iso_code => 'KZ'},
                               {:name => 'Kenya', :iso_code => 'KE'},
                               {:name => 'Kiribati', :iso_code => 'KI'},
                               {:name => 'Korea, Democratic People\'s Republic of', :iso_code => 'KP'},
                               {:name => 'Korea, Republic of', :iso_code => 'KR'},
                               {:name => 'Kuwait', :iso_code => 'KW'},
                               {:name => 'Kyrgyzstan', :iso_code => 'KG'},
                               {:name => 'Lao People\'s Democratic Republic', :iso_code => 'LA'},
                               {:name => 'Latvia', :iso_code => 'LV'},
                               {:name => 'Lebanon', :iso_code => 'LB'},
                               {:name => 'Lesotho', :iso_code => 'LS'},
                               {:name => 'Liberia', :iso_code => 'LR'},
                               {:name => 'Libyan Arab Jamahiriya', :iso_code => 'LY'},
                               {:name => 'Liechtenstein', :iso_code => 'LI'},
                               {:name => 'Lithuania', :iso_code => 'LT'},
                               {:name => 'Luxembourg', :iso_code => 'LU'},
                               {:name => 'Macao', :iso_code => 'MO'},
                               {:name => 'Macedonia, The Former Yugoslav Republic of', :iso_code => 'MK'},
                               {:name => 'Madagascar', :iso_code => 'MG'},
                               {:name => 'Malawi', :iso_code => 'MW'},
                               {:name => 'Malaysia', :iso_code => 'MY'},
                               {:name => 'Maldives', :iso_code => 'MV'},
                               {:name => 'Mali', :iso_code => 'ML'},
                               {:name => 'Malta', :iso_code => 'MT'},
                               {:name => 'Marshall Islands', :iso_code => 'MH'},
                               {:name => 'Martinique', :iso_code => 'MQ'},
                               {:name => 'Mauritania', :iso_code => 'MR'},
                               {:name => 'Mauritius', :iso_code => 'MU'},
                               {:name => 'Mayotte', :iso_code => 'YT'},
                               {:name => 'Mexico', :iso_code => 'MX'},
                               {:name => 'Micronesia, Federated States of', :iso_code => 'FM'},
                               {:name => 'Moldova, Republic of', :iso_code => 'MD'},
                               {:name => 'Monaco', :iso_code => 'MC'},
                               {:name => 'Mongolia', :iso_code => 'MN'},
                               {:name => 'Montenegro', :iso_code => 'ME'},
                               {:name => 'Montserrat', :iso_code => 'MS'},
                               {:name => 'Morocco', :iso_code => 'MA'},
                               {:name => 'Mozambique', :iso_code => 'MZ'},
                               {:name => 'Myanmar', :iso_code => 'MM'},
                               {:name => 'Namibia', :iso_code => 'NA'},
                               {:name => 'Nauru', :iso_code => 'NR'},
                               {:name => 'Nepal', :iso_code => 'NP'},
                               {:name => 'Netherlands', :iso_code => 'NL'},
                               {:name => 'Netherlands Antilles', :iso_code => 'AN'},
                               {:name => 'New Caledonia', :iso_code => 'NC'},
                               {:name => 'New Zealand', :iso_code => 'NZ'},
                               {:name => 'Nicaragua', :iso_code => 'NI'},
                               {:name => 'Niger', :iso_code => 'NE'},
                               {:name => 'Nigeria', :iso_code => 'NG'},
                               {:name => 'Niue', :iso_code => 'NU'},
                               {:name => 'Norfolk Island', :iso_code => 'NF'},
                               {:name => 'Northern Mariana Islands', :iso_code => 'MP'},
                               {:name => 'Norway', :iso_code => 'NO'},
                               {:name => 'Oman', :iso_code => 'OM'},
                               {:name => 'Pakistan', :iso_code => 'PK'},
                               {:name => 'Palau', :iso_code => 'PW'},
                               {:name => 'Palestinian Territory, Occupied', :iso_code => 'PS'},
                               {:name => 'Panama', :iso_code => 'PA'},
                               {:name => 'Papua New Guinea', :iso_code => 'PG'},
                               {:name => 'Paraguay', :iso_code => 'PY'},
                               {:name => 'Peru', :iso_code => 'PE'},
                               {:name => 'Philippines', :iso_code => 'PH'},
                               {:name => 'Pitcairn', :iso_code => 'PN'},
                               {:name => 'Poland', :iso_code => 'PL'},
                               {:name => 'Portugal', :iso_code => 'PT'},
                               {:name => 'Puerto Rico', :iso_code => 'PR'},
                               {:name => 'Qatar', :iso_code => 'QA'},
                               {:name => 'Réunion', :iso_code => 'RE'},
                               {:name => 'Romania', :iso_code => 'RO'},
                               {:name => 'Russian Federation', :iso_code => 'RU'},
                               {:name => 'Rwanda', :iso_code => 'RW'},
                               {:name => 'Saint Barthélemy', :iso_code => 'BL'},
                               {:name => 'Saint Helena, Ascension and Tristan Da Cunha', :iso_code => 'SH'},
                               {:name => 'Saint Kitts and Nevis', :iso_code => 'KN'},
                               {:name => 'Saint Lucia', :iso_code => 'LC'},
                               {:name => 'Saint Martin', :iso_code => 'MF'},
                               {:name => 'Saint Pierre and Miquelon', :iso_code => 'PM'},
                               {:name => 'Saint Vincent and The Grenadines', :iso_code => 'VC'},
                               {:name => 'Samoa', :iso_code => 'WS'},
                               {:name => 'San Marino', :iso_code => 'SM'},
                               {:name => 'Sao Tome and Principe', :iso_code => 'ST'},
                               {:name => 'Saudi Arabia', :iso_code => 'SA'},
                               {:name => 'Senegal', :iso_code => 'SN'},
                               {:name => 'Serbia', :iso_code => 'RS'},
                               {:name => 'Seychelles', :iso_code => 'SC'},
                               {:name => 'Sierra Leone', :iso_code => 'SL'},
                               {:name => 'Singapore', :iso_code => 'SG'},
                               {:name => 'Slovakia', :iso_code => 'SK'},
                               {:name => 'Slovenia', :iso_code => 'SI'},
                               {:name => 'Solomon Islands', :iso_code => 'SB'},
                               {:name => 'Somalia', :iso_code => 'SO'},
                               {:name => 'South Africa', :iso_code => 'ZA'},
                               {:name => 'South Georgia and The South Sandwich Islands', :iso_code => 'GS'},
                               {:name => 'Spain', :iso_code => 'ES'},
                               {:name => 'Sri Lanka', :iso_code => 'LK'},
                               {:name => 'Sudan', :iso_code => 'SD'},
                               {:name => 'Suriname', :iso_code => 'SR'},
                               {:name => 'Svalbard and Jan Mayen', :iso_code => 'SJ'},
                               {:name => 'Swaziland', :iso_code => 'SZ'},
                               {:name => 'Sweden', :iso_code => 'SE'},
                               {:name => 'Switzerland', :iso_code => 'CH'},
                               {:name => 'Syrian Arab Republic', :iso_code => 'SY'},
                               {:name => 'Taiwan, Province of China', :iso_code => 'TW'},
                               {:name => 'Tajikistan', :iso_code => 'TJ'},
                               {:name => 'Tanzania, United Republic of', :iso_code => 'TZ'},
                               {:name => 'Thailand', :iso_code => 'TH'},
                               {:name => 'Timor-Leste', :iso_code => 'TL'},
                               {:name => 'Togo', :iso_code => 'TG'},
                               {:name => 'Tokelau', :iso_code => 'TK'},
                               {:name => 'Tonga', :iso_code => 'TO'},
                               {:name => 'Trinidad and Tobago', :iso_code => 'TT'},
                               {:name => 'Tunisia', :iso_code => 'TN'},
                               {:name => 'Turkey', :iso_code => 'TR'},
                               {:name => 'Turkmenistan', :iso_code => 'TM'},
                               {:name => 'Turks and Caicos Islands', :iso_code => 'TC'},
                               {:name => 'Tuvalu', :iso_code => 'TV'},
                               {:name => 'Uganda', :iso_code => 'UG'},
                               {:name => 'Ukraine', :iso_code => 'UA'},
                               {:name => 'United Arab Emirates', :iso_code => 'AE'},
                               {:name => 'United Kingdom', :iso_code => 'GB'},
                               {:name => 'United States', :iso_code => 'US'},
                               {:name => 'United States Minor Outlying Islands', :iso_code => 'UM'},
                               {:name => 'Uruguay', :iso_code => 'UY'},
                               {:name => 'Uzbekistan', :iso_code => 'UZ'},
                               {:name => 'Vanuatu', :iso_code => 'VU'},
                               {:name => 'Venezuela, Bolivarian Republic of', :iso_code => 'VE'},
                               {:name => 'Viet Nam', :iso_code => 'VN'},
                               {:name => 'Virgin Islands, British', :iso_code => 'VG'},
                               {:name => 'Virgin Islands, U.S.', :iso_code => 'VI'},
                               {:name => 'Wallis and Futuna', :iso_code => 'WF'},
                               {:name => 'Western Sahara', :iso_code => 'EH'},
                               {:name => 'Yemen', :iso_code => 'YE'},
                               {:name => 'Zambia', :iso_code => 'ZM'},
                               {:name => 'Zimbabwe', :iso_code => 'ZW'}
 ])

Written by Clay

July 13, 2010 at 17:54

Posted in Code, Resource, Ruby

Tagged with , , ,

Resource: Rails database seeds for US States, including ANSI codes

with one comment

In case you need to add US States to your Rails app. The data are from the US Census website. I’m not sure what the purpose of the “Geographic Names Information System Identifier (GNISID)” is (the number after the State name), but it’s included here in case you need to tie back to the census system.

states = State.create([
                        { :name => 'Alabama', :abbr => 'AL', :ansi_code => '1', :statens => '1779775'},
                        { :name => 'Alaska', :abbr => 'AK', :ansi_code => '2', :statens => '1785533'},
                        { :name => 'Arizona', :abbr => 'AZ', :ansi_code => '4', :statens => '1779777'},
                        { :name => 'Arkansas', :abbr => 'AR', :ansi_code => '5', :statens => '68085'},
                        { :name => 'California', :abbr => 'CA', :ansi_code => '6', :statens => '1779778'},
                        { :name => 'Colorado', :abbr => 'CO', :ansi_code => '8', :statens => '1779779'},
                        { :name => 'Connecticut', :abbr => 'CT', :ansi_code => '9', :statens => '1779780'},
                        { :name => 'Delaware', :abbr => 'DE', :ansi_code => '10', :statens => '1779781'},
                        { :name => 'District of Columbia', :abbr => 'DC', :ansi_code => '11', :statens => '1702382'},
                        { :name => 'Florida', :abbr => 'FL', :ansi_code => '12', :statens => '294478'},
                        { :name => 'Georgia', :abbr => 'GA', :ansi_code => '13', :statens => '1705317'},
                        { :name => 'Hawaii', :abbr => 'HI', :ansi_code => '15', :statens => '1779782'},
                        { :name => 'Idaho', :abbr => 'ID', :ansi_code => '16', :statens => '1779783'},
                        { :name => 'Illinois', :abbr => 'IL', :ansi_code => '17', :statens => '1779784'},
                        { :name => 'Indiana', :abbr => 'IN', :ansi_code => '18', :statens => '448508'},
                        { :name => 'Iowa', :abbr => 'IA', :ansi_code => '19', :statens => '1779785'},
                        { :name => 'Kansas', :abbr => 'KS', :ansi_code => '20', :statens => '481813'},
                        { :name => 'Kentucky', :abbr => 'KY', :ansi_code => '21', :statens => '1779786'},
                        { :name => 'Louisiana', :abbr => 'LA', :ansi_code => '22', :statens => '1629543'},
                        { :name => 'Maine', :abbr => 'ME', :ansi_code => '23', :statens => '1779787'},
                        { :name => 'Maryland', :abbr => 'MD', :ansi_code => '24', :statens => '1714934'},
                        { :name => 'Massachusetts', :abbr => 'MA', :ansi_code => '25', :statens => '606926'},
                        { :name => 'Michigan', :abbr => 'MI', :ansi_code => '26', :statens => '1779789'},
                        { :name => 'Minnesota', :abbr => 'MN', :ansi_code => '27', :statens => '662849'},
                        { :name => 'Mississippi', :abbr => 'MS', :ansi_code => '28', :statens => '1779790'},
                        { :name => 'Missouri', :abbr => 'MO', :ansi_code => '29', :statens => '1779791'},
                        { :name => 'Montana', :abbr => 'MT', :ansi_code => '30', :statens => '767982'},
                        { :name => 'Nebraska', :abbr => 'NE', :ansi_code => '31', :statens => '1779792'},
                        { :name => 'Nevada', :abbr => 'NV', :ansi_code => '32', :statens => '1779793'},
                        { :name => 'New Hampshire', :abbr => 'NH', :ansi_code => '33', :statens => '1779794'},
                        { :name => 'New Jersey', :abbr => 'NJ', :ansi_code => '34', :statens => '1779795'},
                        { :name => 'New Mexico', :abbr => 'NM', :ansi_code => '35', :statens => '897535'},
                        { :name => 'New York', :abbr => 'NY', :ansi_code => '36', :statens => '1779796'},
                        { :name => 'North Carolina', :abbr => 'NC', :ansi_code => '37', :statens => '1027616'},
                        { :name => 'North Dakota', :abbr => 'ND', :ansi_code => '38', :statens => '1779797'},
                        { :name => 'Ohio', :abbr => 'OH', :ansi_code => '39', :statens => '1085497'},
                        { :name => 'Oklahoma', :abbr => 'OK', :ansi_code => '40', :statens => '1102857'},
                        { :name => 'Oregon', :abbr => 'OR', :ansi_code => '41', :statens => '1155107'},
                        { :name => 'Pennsylvania', :abbr => 'PA', :ansi_code => '42', :statens => '1779798'},
                        { :name => 'Rhode Island', :abbr => 'RI', :ansi_code => '44', :statens => '1219835'},
                        { :name => 'South Carolina', :abbr => 'SC', :ansi_code => '45', :statens => '1779799'},
                        { :name => 'South Dakota', :abbr => 'SD', :ansi_code => '46', :statens => '1785534'},
                        { :name => 'Tennessee', :abbr => 'TN', :ansi_code => '47', :statens => '1325873'},
                        { :name => 'Texas', :abbr => 'TX', :ansi_code => '48', :statens => '1779801'},
                        { :name => 'Utah', :abbr => 'UT', :ansi_code => '49', :statens => '1455989'},
                        { :name => 'Vermont', :abbr => 'VT', :ansi_code => '50', :statens => '1779802'},
                        { :name => 'Virginia', :abbr => 'VA', :ansi_code => '51', :statens => '1779803'},
                        { :name => 'Washington', :abbr => 'WA', :ansi_code => '53', :statens => '1779804'},
                        { :name => 'West Virginia', :abbr => 'WV', :ansi_code => '54', :statens => '1779805'},
                        { :name => 'Wisconsin', :abbr => 'WI', :ansi_code => '55', :statens => '1779806'},
                        { :name => 'Wyoming', :abbr => 'WY', :ansi_code => '56', :statens => '1779807'}
])

Written by Clay

July 13, 2010 at 17:49

Posted in Code, Resource, Ruby

Tagged with , , ,

Using Nokogiri and Ruby on Rails to grab citations from PubMed

with 12 comments

Update: I put a working example up on github (Rails 3.0.4 and Nokogiri 1.4.4): https://github.com/ccheaton/pubnoko

##############################

Following on the last post… I needed to provide to a client a custom database of PubMed citations, links, datasets, and other information related to a product area I was researching. The hardest part about this was understanding PubMed’s eutils and their .xml formats.

I did not want to manually enter the citation information. I wanted to avoid Zotero and other similar services because I wanted to integrate the data across domains and use the database that I was developing for them as the basis for a paper I wrote. For the PubMed citations, what I wanted to be able to do was to go to PubMed, perform a search, find an interesting citation, copy the PubMed URL, paste it into my Rails application, which would use the URL to query PubMed in XML format and populate my app’s database with that entry.

I have two models that play into this interaction: Article and Journal. I should note that I’m using Nokogiri to parse the XML and thoughtbot’s Paperclip gem to attach articles when they are available freely. This was done using Rails 2.3.5 and Ruby 1.8.7.

Here’s how the model files look:

journal.rb

class Journal < ActiveRecord::Base   attr_accessible :name, :description, :url, :short_name      has_many :articles      validates_uniqueness_of :name   validates_presence_of :short_name   validates_presence_of :name      before_save :make_short_name      def best_name     if !self.short_name.nil? && self.short_name.length > 0
      return self.short_name
    else
      return self.name
    end
  end

  private

  # Makes sure that a new journal has a short name in case one isn't entered.
  def make_short_name
    if self.short_name.nil? || self.short_name.length == 0
      self.short_name = self.name
    end
  end
end

The Article model is a little more complicated. The logic that I’m using the parse the XML isn’t great, but I’ve found that the structure changes from article to article. I’m still refining it and will update it when I’m finished, after I’ve found the 90% of article that auto-import.

article.rb

class Article < ActiveRecord::Base   require 'open-uri'   attr_accessible :pubmedlink,                    :article_title,                    :abstract,                    :authors,                    :affiliations,                    :pubdate,                    :pubmedid,                    :journal_id,                    :journal_volume,                    :journal_issue,                    :journal_pages,                   :fetched,                   :thearticle_file_name,                   :thearticle_content_type,                   :thearticle_file_size,                   :thearticle_updated_at,                   :thearticle                      has_attached_file :thearticle,                     :path => ":rails_root/public/system/uploads/:class/:id/:basename.:extension",
                    :url => "/system/uploads/:class/:id/:basename.:extension"
  belongs_to :journal
  before_save :make_pubmedid
  validates_uniqueness_of :article_title

  def citation
    cit = ""
    if self.journal_id != nil && self.journal_id > 0
      cit += self.journal.best_name + ". " + self.pubdate.year.to_s + " " + self.pubdate.strftime("%b")
    end
    if self.journal_volume != nil && self.journal_volume.length > 0
      cit += "; " + self.journal_volume
    end
    if self.journal_issue != nil && self.journal_issue.length > 0
      cit += "(" + self.journal_issue + ")"
    end
    if self.journal_pages != nil && self.journal_pages.length > 0
      cit += ": " + self.journal_pages
    end
      return cit
  end

  private

  def make_pubmedid
    if self.pubmedlink != nil && self.pubmedlink.length > 0 && self.fetched == false
      regex = Regexp.new(/[0-9]{4,10}/)
      matchdata = regex.match(self.pubmedlink)
      self.pubmedid = matchdata[0]
      pull_pubmed_data(matchdata[0])
      self.fetched = true
    end
  end

  def pull_pubmed_data(theID)
    @doc = Nokogiri::XML(open("http://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&id=" + theID.to_s + "&retmode=xml").read)

    # The title and abstract
    self.article_title = @doc.xpath("//ArticleTitle").collect(&:text).to_s
    self.abstract = @doc.xpath("//AbstractText").collect(&:text).to_s

    # Setting up the authors
    first_names = @doc.xpath("//ForeName").collect(&:text)
    last_names = @doc.xpath("//LastName").collect(&:text)
    full_names = []

    i = 0
    last_names.length.times do
      the_name = first_names[i].to_s + " " + last_names[i].to_s
      full_names.push the_name
      i += 1
    end
    self.authors = full_names.join(", ")

    # Affiliations
    self.affiliations = @doc.xpath("//Affiliation").collect(&:text).to_s

    # Publication Date - Check if the complete date is at the top. If not use the pub med date.
    theyear = @doc.xpath("//PubDate/Year").collect(&:text)
    theyear = theyear[0]
    themonth = @doc.xpath("//PubDate/Month").collect(&:text)
    themonth = themonth[0]
    theday = @doc.xpath("//PubDate/Day").collect(&:text)

    if theyear.nil? || themonth.nil? || theday.nil? || theyear.length == 0 || themonth.length == 0 || theday.length == 0

      theyear = @doc.xpath("//PubMedPubDate[@PubStatus='pubmed']/Year").collect(&:text)
      theyear = theyear[0]

      themonth = @doc.xpath("//PubMedPubDate[@PubStatus='pubmed']/Month").collect(&:text)
      themonth = themonth[0].to_i
        month_done = 1
      if themonth < 10
        themonth = "0" + themonth.to_s
        month_done = 1
      end

      theday = @doc.xpath("//PubMedPubDate[@PubStatus='pubmed']/Day").collect(&:text)
    end

    if month_done != 1

      themonth = case themonth || "01"
        when "Jan"  then "01"
        when "Feb"  then "02"
        when "Mar"  then "03"
        when "Apr"  then "04"
        when "May"  then "05"
        when "Jun"  then "06"
        when "Jul"  then "07"
        when "Aug"  then "08"
        when "Sep"  then "09"
        when "Oct"  then "10"
        when "Nov"  then "11"
        when "Dec"  then "12"
      end

    end

    if theday.length == 0
      theday = "01"
    elsif theday[0].to_i < 10       theday = "0" + theday[0].to_s     else       theday = theday[0].to_s     end          thedate = theyear.to_s + "-" + themonth.to_s + "-" + theday.to_s     puts "thedate: " + thedate.to_s     self.pubdate = Date.new(theyear.to_i, themonth.to_i, theday.to_i)          # Either referencing the proper journal or creating a new one     thejournal = @doc.xpath("//Journal/Title").collect(&:text)     thejournal = thejournal[0]          theshortname = @doc.xpath("//MedlineTA").collect(&:text)     if theshortname.length == 0       theshortname = ""     else       theshortname = theshortname[0]     end          thejournalid = Journal.find(:first, :conditions => ['lower(name) = ?', thejournal.downcase])

    if !thejournalid.nil?
      self.journal_id = thejournalid.id
    else
      @journal = Journal.new(:name => thejournal.to_s, :short_name => theshortname.to_s)
      @journal.save
      thenewjournal = Journal.find(:first, :order => 'created_at DESC')
      self.journal_id = thenewjournal.id
    end

    # Save the volume, issue, and pages
    thevolume = @doc.xpath("//JournalIssue/Volume").collect(&:text)
    thevolume = thevolume[0].to_s if thevolume.length > 0
    self.journal_volume = thevolume if thevolume.length > 0

    theissue = @doc.xpath("//JournalIssue/Issue").collect(&:text)
    theissue = theissue[0].to_s if theissue.length > 0
    self.journal_issue = theissue if theissue.length > 0

    thepag = @doc.xpath("//Pagination/MedlinePgn").collect(&:text)
    thepag = thepag[0].to_s if thepag.length > 0
    self.journal_pages = thepag if thepag.length > 0

  end

end

The journals_controller.rb file is pretty standard. So is the articles_controller.rb file. Here it is as an example:

articles_controller.rb

class ArticlesController < ApplicationController   def index     @articles = Article.all(:order => "pubdate DESC")
  end

  def show
    @article = Article.find(params[:id])
  end

  def new
    @article = Article.new
    @journals = Journal.all(:order => :name)
  end

  def create
    @article = Article.new(params[:article])
    if @article.save
      flash[:notice] = "Successfully created article."
      redirect_to @article
    else
      render :action => 'new'
    end
  end

  def edit
    @article = Article.find(params[:id])
    @journals = Journal.all(:order => :name)
  end

  def update
    @article = Article.find(params[:id])
    if @article.update_attributes(params[:article])
      flash[:notice] = "Successfully updated article."
      redirect_to @article
    else
      render :action => 'edit'
    end
  end

  def destroy
    @article = Article.find(params[:id])
    @article.destroy
    flash[:notice] = "Successfully destroyed article."
    redirect_to articles_url
  end
end

Here are the view files for articles:

app/views/articles/index.html.erb

<div class="toolbar">
<ul></ul>
</div>
<table>
<tbody>
<tr>
<th></th>
<th>Article Title</th>
<th></th>
</tr>
<tr class="<%= cycle('rowA', 'rowB') %>">
<td></td>
<td><strong> </strong>

		  <em> </em></td>
<td class="deletelink"></td>
</tr>
</tbody>
</table>

app/views/articles/show.html.erb

<div class="toolbar">
<ul></ul>
</div>
	  <strong>PubMed Link:</strong>







	  <strong>View the Article:</strong>





  <strong>Article Title:</strong>



  <strong>Abstract:</strong>



  <strong>Authors:</strong>



  <strong>Affiliations:</strong>



  <strong>Pubdate:</strong>



  <strong>Pubmedid:</strong>



  <strong>Journal:</strong>



  <strong>Journal Volume:</strong>



  <strong>Journal Issue:</strong>



  <strong>Journal Pages:</strong>



app/views/articles/_form.html.erb



<h3>Enter a link to an abstract on PubMed and click Submit or fill out the information below</h3>











		There currently is no file attached.


<hr />












































The Paperclip gem requires :html => { :multipart => true } at the top of the _form file. It’s a very simple gem to use and very powerful. There are Google Groups for both Paperclip and Nokogiri, should you need additional assistance setting them up.

Leave me a comment if this helps you with either Nokogiri or with extracting data from PubMed!

Written by Clay

July 9, 2010 at 18:18

Posted in Code, Ruby, Technology

Tagged with , , , ,

Flex Builder 3, AS 3, and embedding fonts on a Mac

leave a comment »

For whatever reason, the common examples for how to embed fonts using Actionscript 3 do not work on the Mac, when coding in Flex Builder 3. There are many examples for how to do this, on the web and in books, but they just don’t work on OS X. Bummer.

The good news is that I found a working example. The bad news is that it was in the Internet Archive. I’m reposting it here in case somebody comes along and needs assistance. Here is the link to the Internet Archive. Here’s the example described:

… here i will show you the way you can embed fonts in AS3 project without passing through SWF files.

First of all, I recommend you to use a AssetManager class to have an easy access to all you need (these includes fonts obviously!).

And now let’s see how can we build this class (we take for example that we must embed two different fonts)

public final class AssetManager
{
[Embed(source="/assets/fonts/Arista.ttf", fontName="arista")]
public static var arista:Class;

[Embed(source="/assets/fonts/kroe0656.ttf", fontName="kroeger")]
public static const kroeger:Class;
}

… now where we want to use these fonts we may write this …

import flash.text.Font;
Font.registerFont(AssetManager.arista);
var myTF:TextField = new TextField();
var tf:TextFormat = new TextFormat();
tf.size = 48;
tf.font = "arista";
myTF.embedFonts = true;
myTF.antiAliasType = AntiAliasType.ADVANCED;
myTF.defaultTextFormat = tf;
myTF.text = "Light the moon";

I hope this helps somebody!

Written by Clay

January 25, 2010 at 19:30

Learning Actionscript 3 with Adobe Flash Builder / Flex Builder

with one comment

I tried to learn Flash several times over the past few years but I always gave up because the Adobe Flash CS3 interface is littered with a bunch of stuff that distracts me. As a brief aside, I decided that I wanted to learn Actionscript several years ago, when Jenova Chen posted his thesis game, Flow, to the web. This game later became Flow on the PS3. I was wowed with the graphics and overall feeling of the game, so I contacted him to ask how he made it. His response: it was procedurally generated in Flash and programmed with Actionscript. I bought Flash, got distracted to 100 other things, tried to learn Actionscript several times, and never made any progress.

The good news is that there’s a far better way to work on Actionscript: get yourself a copy of Adobe Flex Builder, which soon will be renamed to Adobe Flash Builder to distinguish it from the free Flex SDK.  The cool thing is that you can get a copy of it for free if you are a student or if you are unemployed by the current recession. Just visit this link. You also can download a trial version through Adobe’s website.

I mentioned in a previous post that setting up and understanding IDEs is difficult. Flex Builder / Flash Builder is simpler than most. This tutorial is meant to show you what to do after installing the application.

  1. Download it from Adobe or get the free student edition. You also can get the Flash Builder 4 Beta 2 trial for 60 days.
  2. Install it.
  3. Now, remember… this brief tutorial is geared towards learning Actionscript, NOT Flex… I can’t help with that.
  4. Launch the software.
  5. Select File > New > Actionscript Project
  6. Name the project something that has two words, like “Arrow Field” (the example later) and click ok.
  7. You’re now looking at the main class for the project. This is the code that will run automatically when you run something that you build in Flash Builder / Flex Builder. It looks like this:
package
{
	import flash.display.Sprite;

	public class main extends Sprite
	{
		public function main()
		{

		}
	}
}

To get started, I’m going to give you some instructions and code so that you can get a small project up and running. This is partially derived from the Actionscript 3.0 Animation book by Keith Peters. It’s a good book.

  1. In the left hand pane you’ll see a hierarchical list of the files in the project you just created. Right-click on the parent Folder and select Properties.
  2. Click where it says “Actionscript Compiler”
  3. Enter this is the “Additional Compiler Arguments” field: -default-size 800 600 -default-frame-rate 30 -default-background-color 0xffffff
  4. Hit ok to exit the dialog box.
  5. If you named your project something that was one word, the names of the functions in the primary .as file will be that word. If you named the project with two words, the primary .as file should be called main.as. Paste this code into main.as:
package
{
	import flash.display.Sprite;
	import flash.events.Event;

	public class main extends Sprite
	{
		private var arrowArray:Array = new Array();

		public function main()
		{
			init();
		}

		private function init():void
		{
			// This creates the arrows; parameters are (# across, # down)
			createArrows(20,20);

			// This makes the arrows move when the mouse is over the movie
			addEventListener(Event.ENTER_FRAME, onEnterFrame);
		}

		private function createArrows(xnum:Number, ynum:Number):void
		{
			// Create an array of arrows
			var totalArrows:Number = xnum * ynum;
			for (var i:Number = 0; i <= totalArrows; i++)
			{
				var arrow:Arrow = new Arrow();
				arrowArray.push(arrow);
			}

			// Call the function to draw the arrows here
			drawArrows(arrowArray, xnum, ynum);
		}

		private function drawArrows(drawArray:Array, xnum:Number, ynum:Number):void
		{
			// Pass an array to this to draw the arrows in the array
			var startxpos:Number = 25;
			var startypos:Number = 25;

			var currentxpos:Number = startxpos;
			var currentypos:Number = startypos;

			var xshift:Number = (stage.stageWidth - 25) / xnum;
			var yshift:Number = (stage.stageHeight - 25) / ynum;

			var xcount:Number = 0;
			var ycount:Number = 0;

			// Iterate through each arrow in the array
			for (var i:Number = 0; i < drawArray.length - 1; i++)
			{
				var current:Arrow = drawArray[i];
				addChild(current);

				// set the x value
				if (xcount < xnum)
				{
					// Place it at the current x and y position
					current.x = currentxpos;
					current.y = currentypos;

					// Increment the x position
					currentxpos = currentxpos + xshift;

					// Increment the counter so we know how many we have placed on the x
					xcount +=1;
				} else {
					// Set the x back to 0 because we are starting a new row
					xcount = 0;

					// Set the x position back to the starting position
					currentxpos = startxpos;

					// Increment to the new row on the y
					currentypos = currentypos + yshift;

					// Place the arrow
					current.x = currentxpos;
					current.y = currentypos;

					// Increment the x position for the next one
					currentxpos = currentxpos + xshift;

					// Increment the number across the x
					xcount +=1;
				}

				// This code just changes the arrow size, based on how many there are
				if (xnum == ynum)
				{
					current.scaleX = 1/xnum;
					current.scaleY = 1/xnum;
				} else if (xnum < ynum) {
					current.scaleX = 1/ynum;
					current.scaleY = 1/ynum;
				} else {
					current.scaleX = 1/xnum;
					current.scaleY = 1/xnum;
				}
			}
		}

		public function onEnterFrame(event:Event):void
		{
			// this loops goes through each arrow in the array
			for (var i:Number = 0; i < arrowArray.length - 1; i++)
			{
				// Get an arrow from the array
				var current:Arrow = arrowArray[i];

				// Determine the distance between the mouse and the arrow
				var dx:Number = mouseX - current.x;
				var dy:Number = mouseY - current.y;

				// Use the distances to calculate an angle
				var radians:Number = Math.atan2(dy, dx);

				// Use the angle to set the rotation of the arrow
				current.rotation = radians * 180 / Math.PI;

			}
		}
	}
}
  1. Now you’ll create a class file to hold the code for the simple class. Objects created look like simple arrows. The code you just pasted in will make them follow the mouse around. Go to File > New > Actionscript Class.
  2. Name it Arrow. Click ok and ignore the warnings. The Package Explorer pane on the left now will show a file called Arrow.as. It will open automatically, too.
  3. Click Arrow.as to get into the class file. Paste in this code:
package
{
	import flash.display.Sprite;

	public class Arrow extends Sprite
	{
		public function Arrow()
		{
			init();
		}
		public function init():void {
			graphics.lineStyle(4, 0, 1);
			graphics.moveTo(0, 0);
			graphics.lineTo(-100, 100);
			graphics.moveTo(0, 0);
			graphics.lineTo(-100, -100);
		}
	}
}
  1. Now click the “Run” button on the toolbar at the top or select “Run main” from the Run menu. The field of arrows should open in the browser.
  2. Notice the comments in the code – tweaking some of the numbers and/or variables might help you understand what’s going on.  It’s pretty simple code, but has a few twists thrown in so that you can see a few techniques for making things happen.

One thing that you will notice is that the behavior is generated by an array or Arrow objects. An array is created with the total number of arrows that you specify. They are positioned by iterating through the array. Their rotation is set by a separate iteration through the array.

I hope that this helped get you started! Good luck!

Written by Clay

January 22, 2010 at 21:55

Cocoa Programming for Mac OS X: Chapter 12 Challenge

with 10 comments

Pesky!

This is another challenge that is easy but can trip you up when you test it… The challenge reads:

Create a nib file with a custom About panel. Add an outlet to AppController to point to the new window. Also add a showAboutPanel: method. Load the nib by using NSBundle, and make AppController File’s Owner.

The easy part of this is that you don’t have to create a subclass like you did with PreferenceController. Read the bits about NSBundle and you’ll see that all you have to do is to modify the File’s Owner (AppController) and create the nib/xib file. That’s simple enough.

I initially didn’t understand the purpose of creating an IBOutlet for the About panel. After testing the application, I realized that the implementation of showAboutPanel: allowed for multiple panels to be opened.

Pesky! Read the rest of the post to see the code and instructions.

Read the rest of this entry »

Written by Clay

January 5, 2010 at 22:12

Posted in Cocoa, Code, Objective-C

Tagged with , , ,

Cocoa Programming for Mac OS X: Chapter 11 – Core Data – Depreciated Data Bindings

with 3 comments

Working through the Cocoa book, I successfully built and ran the example Core Data application. All was good until I noticed a build warning, “The ‘data’ binding is depreciated in Mac OS X versions 10.6 and later.” I looked at the discussion forum for the book (which isn’t much of a discussion forum – can’t provide links to other sites) and saw another mention of the problem. The issue comes from using the data binding on the Image Well, as described at the top of page 179 (in the 3rd edition of the book):

Bind the Data (not Value) of the image view to Cars. Choose the controller key selection and the keypath photo.

So, what’s the alternative that doesn’t use depreciated code? I don’t know for certain, but there’s discussion of it at the Mac Rumors forum. It may no longer be possible to create a Core Data application without writing code to implement a Value Transformer, if you want to convert from NSData to NSImage.

I’m sure there are good reasons behind codebase changes like this one. I look forward to understanding enough Obj-C and Cocoa to know what they are.

(Oddly, after doing nothing other than closing and reopening Xcode, I no longer get the build warning, which is even more confusing.)

Written by Clay

December 21, 2009 at 14:27

Posted in Cocoa, Objective-C

Tagged with , , ,

Cocoa Programming for Mac OS X: Chapter 8, Challenge 2

with 3 comments

This challenge is simple — type in the code provided, add the bindings in Interface Builder, and go…  that is, until the “make sure to add sorting!” comment at the end.

Sorting the RaiseMan application that does not use NSArrayController requires only a careful reading of the “For the More Curious: Sorting without NSArrayController” section in Chapter 8. The key sentence here, which I missed several times, is, “An optional table view dataSource method is triggered when the user clicks the header of a column with a sort descriptor:

There are a couple of things to notice and do:

1. tableView:sortDescriptorsDidChange: is a delegate method… so this means that you have to set MyDocument to be the delegate of the table view. Presumably you already did that if you set up the bindings correctly.

2. You need to enter the code that appears just above the “Challenge 1” header on page 135 into MyDocument.m – this is the delegate method called when you click the column header.

3. You cannot use the delegate method code as it appears in the book if you entered the code in Challenge 2, because you have a pointer to an IBOutlet object called tableView and this delegate method waits for a call from the interface element, not from your pointer, if that makes sense. Think about it this way — the flow of information happens in two directions: that coming in from the interface to the method, and that going from the method back to the interface. You’ll receive a compile warning if you call it tableView. Change it to aTableView. (You probably noticed similar changes in the code under Challenge 2.)  When referring to the incoming messages, those should be called aTableView. When referring to outgoing messages, those should be tableView because that is what your IBOutlet pointer is called. Therefore, the final line, [tableView reloadData] needs to remain tableView and not change to aTableView because you now want to call on the IBOutlet pointer to send instructions back to the interface.

4. The line [myArray sortUsingDescriptors:newDescriptors]; needs to contain the name of your array… employees. It should read: [employees sortUsingDescriptors:newDescriptors]; so that it sorts your array.

The final point tripped me up longer than any of the rest of it because I thought that the purpose of the exercise was to avoid using key coding. I was wrong. The point of the exercise is to not use NSArrayController. To sort your columns, you have to have sort descriptors. Those are entered in the same spot as in the previous challenge and the chapter exercise. In other words, when you have the name column in the table selected, the Interface Builder Table Column Attributes should look like this:

The same discussion about compare: vs. localizedStandardCompare: is valid here, so you can change it to the latter if you don’t want Z to appear before a when you sort. Look here for more info. The full code follows the break:

Read the rest of this entry »

Written by Clay

December 17, 2009 at 20:52

Posted in Cocoa, Code, Objective-C

Tagged with , , ,

Cocoa Programming for Mac OS X: Chapter 8, Challenge 1

with 13 comments

Alright! Another challenge where I knew exactly what I wanted to do and spent a lot of time wrestling with the interface.

The challenge is to change the sorting on the RaiseMan column to show the names in the order of the number of characters in them. The hint is to use a key path and the “length” method of strings. Seems straightforward. In code, to find the length of the personName string, I would simply write [personName length]. Let’s see…  key values, paths, selectors, etc…..  What??

The obvious thing to do here is to return to the previous chapter and read about key paths. Except, that doesn’t help at all and may lead to confusion. (I would reference my previous post about the confusion I experience when trying to migrate coding concepts to Interface Builder.)

There’s no point in drawing this out. The proper answer is simply to make the sort key personName.length and the selector to be compare:, like this:

You have to use compare: instead of caseInsensitiveCompare: because there is no case to an integer — no lowercase and/or uppercase to compare.

I tried several other approaches, including different Selectors, and the notation discussed in Chapter 7, such as “… valueForKeyPath@”personName.length];”. I think that my general lack of knowledge of C and my inexperience with Obj-C led me to try everything but the obvious. You see, the discussion of key paths in Chapter 7 only mentioned that they could be used to find attributes of objects. Length is, of course, an attribute of a string, but the challenge mentions length not as an attribute, but as a method of Strings. I took that to mean as a method of NSString. That returned me to the initial problem – how to return the value from [personName length] to the sort key.

In the end, I don’t think that this challenge is much of a challenge because I don’t recall ever learning that I could use a method with dot notation to return an attribute as a key path. My suggestion to the author would be to specifically mention this in the “For the More Curious: Key Paths” section of chapter 7, alongside the mention of “Properties and Their Attributes,” where the author specifically derides the dot syntax and says that he won’t be using it in the book.

Still, though, I haven’t crossed the mental bridge from length being a method and length being a property and/or attribute of NSString. It must be a getter method for NSString, and hence, works with the dot syntax? The Apple developer documentation states that length is a primitive method — does this mean it can be accessed as a getter method?

While I now know that I can use this syntax for strings, I have yet to find a description of why it works or how it applies to other classes and methods. The challenge, therefore, became a guessing game and the learning lesson became frustration.

Written by Clay

December 17, 2009 at 17:53

Posted in Cocoa, Code, Objective-C

Tagged with , , ,

Cocoa Programming for Mac OS X Chapter 6: Challenge: Make a Data Source

with 2 comments

It’s been busy around here. I’ve started and stopped this challenge a few times over the course of a week, but finally returned to it yesterday evening. The time away from it actually made it easier. Armed with the knowledge that most of what I needed to know was in the chapter and that the challenge would be easier than the teachings in the chapter, I dove in and tried to keep it simple. I haven’t made it editable yet, but probably will tackle that tonight, since I think it will be simple. The final app looks like this:

Read the rest of this entry »

Written by Clay

December 15, 2009 at 13:58

Posted in Cocoa, Code, Objective-C

Tagged with , , ,

When random is funny

leave a comment »

I had to chuckle… Continuing the Population Generator, I grabbed lists of the most common names in the United States (1000 for male and female, plus 1000 surnames) and wrote a function to assign a name to everybody in my pseudo populations. On the first test of this, what do I get out of all of the randomness but baby Anthony Hopkins. That’s not to mention Cindy Bird and Heath Byrd… Random isn’t feeling so random.

Anthony HopkinsI encountered an error when trying to import a file into Processing. This silly dialog box popped up, which also made me chuckle, because it reminds me of some of the temp dialog boxes that I used to include in Access apps that I wrote. From time to time I forgot to remove them or change the text and I always had a confused/bemused person calling to ask what it meant.

funny dialogIt lied, though… It deleted the file in place and I had to recreate it.

Written by Clay

November 6, 2009 at 18:15