//////////////////////////////////////////////////////////////////////////////// // // EftExperiment.cpp - Embedded Figures Test Results Analyzer // // Experimental program to compare with EftExperiment.hs // // Copyright (C) 2008 Alan G. Carter // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see http://www.gnu.org/licenses/. // //////////////////////////////////////////////////////////////////////////////// #include "wx/wxprec.h" #ifdef __BORLANDC__ #pragma hdrstop #endif #ifndef WX_PRECOMP #include "wx/wx.h" #endif #include #include #include #include #include #include #include #include using namespace std; //////////////////////////////////////////////////////////////////////////////// enum { ID_QUIT = 1, ID_OPEN }; typedef enum { GRAPH_DISTRIB_BOTH, GRAPH_DISTRIB_NAUSEOUS, GRAPH_DISTRIB_NON_NAUSEOUS, GRAPH_DISTRIB_DRUG, GRAPH_STRESSORS_BY_AV_SCORE, GRAPH_EXERCISES_BY_AV_SCORE, GRAPH_AGE_BY_AV_SCORE } GraphType; typedef enum { DRUG_SSRI = 0, DRUG_BENZODIAZEPINES, DRUG_RITALIN, DRUG_ALCOHOL, DRUG_TOBACCO, DRUG_MARIJUANA, DRUG_COCAINE, DRUG_MDMA, DRUG_CAFFEINE, DRUG_MAX } DrugType; //////////////////////////////////////////////////////////////////////////////// class EftApp; class StatsFrame; class Display; class GraphFrame; class GraphPanel; class DistribPanel; class DrugDistribPanel; class StressorsByAvScorePanel; class ExercisesByAvScorePanel; class AgeByAvScorePanel; class Entry; //////////////////////////////////////////////////////////////////////////////// class EftApp: public wxApp { public: virtual bool OnInit(); }; //////////////////////////////////////////////////////////////////////////////// class StatsFrame: public wxFrame { public: StatsFrame(void); void OnQuit(wxCommandEvent &); void OnOpen(wxCommandEvent &); void DeregisterFrame(wxFrame *TheFrame); private: void Tokenize(const string &Data, const string &Delimiters, vector &Tokens); void Purge(void); void Parse(const string &Line); int EncodeChoice(string TheChoice); void AddReport(string ThePrompt, Display *&TheDisplay); vector Frames; map Entries; wxGridSizer *GridSizer; Display *TotalDisplay; Display *ValidDisplay; Display *SinglesDisplay; Display *PairsDisplay; Display *GeeksDisplay; Display *NonGeeksDisplay; Display *MalesDisplay; Display *NonMalesDisplay; Display *NauseatorsDisplay; Display *NonNauseatorsDisplay; Display *DrugDisplays[DRUG_MAX]; DECLARE_EVENT_TABLE() }; //////////////////////////////////////////////////////////////////////////////// class Display : public wxTextCtrl { public: Display(StatsFrame *Parent); void SetInt(int Value); }; //////////////////////////////////////////////////////////////////////////////// class GraphFrame: public wxFrame { public: GraphFrame(StatsFrame *TheParent, GraphType TheType, map *TheData); void OnClose(wxCloseEvent& Event); private: StatsFrame *Parent; wxPanel *Panel; DECLARE_EVENT_TABLE() }; //////////////////////////////////////////////////////////////////////////////// class GraphPanel: public wxPanel { public: GraphPanel(wxFrame* Parent, map *TheData); void ChooseColoursDrawAxes(wxDC &Dc); void DrawScoreScaleY(wxDC &Dc, bool Both); virtual void OnPaint(wxPaintEvent &Event) = 0; protected: map *Data; DECLARE_EVENT_TABLE() }; //////////////////////////////////////////////////////////////////////////////// class DistribPanel: public GraphPanel { public: DistribPanel(wxFrame* Parent, map *TheData, GraphType TheType); void OnPaint(wxPaintEvent &Event); private: GraphType Type; }; //////////////////////////////////////////////////////////////////////////////// class DrugDistribPanel: public GraphPanel { public: DrugDistribPanel(wxFrame* Parent, map *TheData); void OnPaint(wxPaintEvent &Event); }; //////////////////////////////////////////////////////////////////////////////// class StressorsByAvScorePanel: public GraphPanel { public: StressorsByAvScorePanel(wxFrame* Parent, map *TheData); void OnPaint(wxPaintEvent &Event); }; //////////////////////////////////////////////////////////////////////////////// class ExercisesByAvScorePanel: public GraphPanel { public: ExercisesByAvScorePanel(wxFrame* Parent, map *TheData); void OnPaint(wxPaintEvent &Event); }; //////////////////////////////////////////////////////////////////////////////// class AgeByAvScorePanel: public GraphPanel { public: AgeByAvScorePanel(wxFrame* Parent, map *TheData); void OnPaint(wxPaintEvent &Event); }; //////////////////////////////////////////////////////////////////////////////// class Entry { public: Entry(void); int GetStressScore(void); int GetExerciseScore(void); string Id; bool IsMale; int AgeGroup; string Occupation; bool IsGeek; bool EveningWalks; bool CulturalActivities; bool ImaginaryFriend; bool History; bool Cooking; bool Meditation; bool ChangeRoutes; bool ChangeJob; bool MoveHome; bool FallInLove; bool BreakUp; bool NewCar; bool Vacation; bool SeeOldFriends; bool GetMoreSleep; bool Disaster; int IEnjoyMyJob; int MyJobIsWellDefined; int MyCoworkersAreCooperative; int MyWorkplaceIsStressful; int MyJobIsStressful; int MoraleIsGoodWhereIWork; int IGetFrustratedAtWork; bool IsNauseous; bool Drugs[DRUG_MAX]; string OtherMeds; string OtherDrugs; int Before; int After; }; //////////////////////////////////////////////////////////////////////////////// // // Constants // //////////////////////////////////////////////////////////////////////////////// wxColour *DrugColours[DRUG_MAX]; string DrugNames[DRUG_MAX]; //////////////////////////////////////////////////////////////////////////////// // // Class EftApp // //////////////////////////////////////////////////////////////////////////////// IMPLEMENT_APP(EftApp) bool EftApp::OnInit() { StatsFrame *Frame = new StatsFrame(); Frame->Show(true); SetTopWindow(Frame); return true; } //////////////////////////////////////////////////////////////////////////////// // // Class StatsFrame // //////////////////////////////////////////////////////////////////////////////// BEGIN_EVENT_TABLE(StatsFrame, wxFrame) EVT_MENU (ID_QUIT, StatsFrame::OnQuit) EVT_MENU (ID_OPEN, StatsFrame::OnOpen) END_EVENT_TABLE() //////////////////////////////////////////////////////////////////////////////// StatsFrame::StatsFrame(void) : wxFrame(0, wxID_ANY,"EFT Analyzer", wxDefaultPosition, wxSize(600, 450)) { DrugColours[DRUG_SSRI] = new wxColour( 0, 0, 255); DrugColours[DRUG_BENZODIAZEPINES] = new wxColour( 0, 255, 0); DrugColours[DRUG_RITALIN] = new wxColour(255, 0, 0); DrugColours[DRUG_ALCOHOL] = new wxColour(255, 255, 0); DrugColours[DRUG_TOBACCO] = new wxColour(255, 0, 255); DrugColours[DRUG_MARIJUANA] = new wxColour( 0, 255, 255); DrugColours[DRUG_COCAINE] = new wxColour( 0, 0, 127); DrugColours[DRUG_MDMA] = new wxColour( 0, 127, 0); DrugColours[DRUG_CAFFEINE] = new wxColour(127, 0, 0); DrugNames[DRUG_SSRI] = "SSRIs"; DrugNames[DRUG_BENZODIAZEPINES] = "Benzodiazepines"; DrugNames[DRUG_RITALIN] = "Ritalin"; DrugNames[DRUG_ALCOHOL] = "Alcohol"; DrugNames[DRUG_TOBACCO] = "Tobacco"; DrugNames[DRUG_MARIJUANA] = "Marijuana"; DrugNames[DRUG_COCAINE] = "Cocaine"; DrugNames[DRUG_MDMA] = "MDMA"; DrugNames[DRUG_CAFFEINE] = "Caffeine"; wxMenu *MenuFile = new wxMenu; MenuFile->Append(ID_OPEN, "Open..."); MenuFile->Append(ID_QUIT, "Exit"); wxMenuBar *MenuBar = new wxMenuBar; MenuBar->Append(MenuFile, "File"); SetMenuBar(MenuBar); GridSizer = new wxGridSizer(2, 0, 5); AddReport("Total Entries", TotalDisplay); AddReport("Valid Entries", ValidDisplay); AddReport("Single Values", SinglesDisplay); AddReport("Paired Values", PairsDisplay); AddReport("Geeks", GeeksDisplay); AddReport("Non Geeks", NonGeeksDisplay); AddReport("Male", MalesDisplay); AddReport("Female", NonMalesDisplay); AddReport("Nauseators", NauseatorsDisplay); AddReport("Non Nauseators", NonNauseatorsDisplay); for(unsigned int Count = 0; Count < DRUG_MAX; Count++) AddReport(DrugNames[Count].c_str(), DrugDisplays[Count]); SetSizer(GridSizer); GridSizer->SetSizeHints(this); Frames.push_back(new GraphFrame(this, GRAPH_DISTRIB_BOTH, &Entries)); Frames.push_back(new GraphFrame(this, GRAPH_DISTRIB_NAUSEOUS, &Entries)); Frames.push_back(new GraphFrame(this, GRAPH_DISTRIB_NON_NAUSEOUS, &Entries)); Frames.push_back(new GraphFrame(this, GRAPH_DISTRIB_DRUG, &Entries)); Frames.push_back(new GraphFrame(this, GRAPH_STRESSORS_BY_AV_SCORE, &Entries)); Frames.push_back(new GraphFrame(this, GRAPH_EXERCISES_BY_AV_SCORE, &Entries)); Frames.push_back(new GraphFrame(this, GRAPH_AGE_BY_AV_SCORE, &Entries)); } //////////////////////////////////////////////////////////////////////////////// void StatsFrame::OnOpen(wxCommandEvent &) { // Clear the current data structures. Purge(); // Pick a file to open. wxFileDialog FileDialog(this, "EFT Results File", "", "", "*.txt"); if(FileDialog.ShowModal() != wxID_OK) return; // Get the user's filename. string FileName = FileDialog.GetPath().c_str(); if(FileName.length() == 0) return; // Open the file. ifstream FileStream(FileName.c_str()); if(FileStream == 0) return; // Throw away the first line. string Line; if(!getline(FileStream, Line)) return; // Read and parse the remaining lines. while(getline(FileStream, Line)) Parse(Line); // Report the total number of entries - an entry == many lines. ostringstream FormatBuffer; FormatBuffer << Entries.size(); TotalDisplay->SetValue(FormatBuffer.str()); // Now remove invalid entries. Valid entries have values: // // 100 < Before < 10000 // 0 <= After < 10000 // // After may be 0 to indicate no value. int ValidCount = 0; int SinglesCount = 0; int PairsCount = 0; int GeeksCount = 0; int NonGeeksCount = 0; int MalesCount = 0; int NonMalesCount = 0; int NauseatorsCount = 0; int NonNauseatorsCount = 0; int DrugCounts[DRUG_MAX]; for(unsigned int Count = 0; Count < DRUG_MAX; Count++) DrugCounts[Count] = 0; for(map::iterator It = Entries.begin(); It != Entries.end(); It++) { if(It->second->Before <= 100 || It->second->Before > 9999 || It->second->After < 0 || It->second->After > 9999) { delete It->second; Entries.erase(It); } else { ValidCount++; if(It->second->After == 0) SinglesCount++; else PairsCount++; if(It->second->IsGeek) GeeksCount++; else NonGeeksCount++; if(It->second->IsMale) MalesCount++; else NonMalesCount++; if(It->second->IsNauseous) NauseatorsCount++; else NonNauseatorsCount++; for(unsigned int Count = 0; Count < DRUG_MAX; Count++) if(It->second->Drugs[Count]) DrugCounts[Count]++; } } // Report the other statistics. ValidDisplay->SetInt(ValidCount); SinglesDisplay->SetInt(SinglesCount); PairsDisplay->SetInt(PairsCount); GeeksDisplay->SetInt(GeeksCount); NonGeeksDisplay->SetInt(NonGeeksCount); MalesDisplay->SetInt(MalesCount); NonMalesDisplay->SetInt(NonMalesCount); NauseatorsDisplay->SetInt(NauseatorsCount); NonNauseatorsDisplay->SetInt(NonNauseatorsCount); for(unsigned int Count = 0; Count < DRUG_MAX; Count++) DrugDisplays[Count]->SetInt(DrugCounts[Count]); // With new entries, refresh all existing frames. for(unsigned int Count = 0; Count < Frames.size(); Count++) Frames[Count]->Refresh(); Refresh(); } //////////////////////////////////////////////////////////////////////////////// void StatsFrame::OnQuit(wxCommandEvent &) { for(unsigned int Count = 0; Count < Frames.size(); Count++) Frames[Count]->Close(); Close(true); } //////////////////////////////////////////////////////////////////////////////// void StatsFrame::DeregisterFrame(wxFrame *TheFrame) { Frames.erase(remove(Frames.begin(), Frames.end(), TheFrame), Frames.end()); } //////////////////////////////////////////////////////////////////////////////// void StatsFrame::Tokenize(const string &Data, const string &Delimiters, vector &Tokens) { // Clear any previous use. Tokens.clear(); // Skip delimiters at beginning. string::size_type LastPos = Data.find_first_not_of(Delimiters, 0); // Find first "non-delimiter". string::size_type Pos = Data.find_first_of(Delimiters, LastPos); while(string::npos != Pos || string::npos != LastPos) { // found a token, add it to the vector. Tokens.push_back(Data.substr(LastPos, Pos - LastPos)); // skip delimiters. Note the "not_of" LastPos = Data.find_first_not_of(Delimiters, Pos); // find next "non-delimiter" Pos = Data.find_first_of(Delimiters, LastPos); } } //////////////////////////////////////////////////////////////////////////////// void StatsFrame::Purge(void) { for(map::iterator It = Entries.begin(); It != Entries.end(); It++) delete It->second; Entries.clear(); } //////////////////////////////////////////////////////////////////////////////// void StatsFrame::Parse(const string &Line) { vector Tokens; Tokenize(Line, "\t", Tokens); // The fourth field contains the value. We don't need to do anything if the // value isn't there! if(Tokens.size() != 4) return; // The first field is a unique line ID, which we don't need. The second is // the record ID the line belongs to. See if we have a record in the map<>, // and if not create one. if(Entries.find(Tokens[1]) == Entries.end()) Entries[Tokens[1]] = new Entry(); Entry *TheEntry = Entries[Tokens[1]]; // The third field is the question. The questions are collected into groups // whose values (in the fourth field) are handled in different ways. if(Tokens[2] == "Gender") { TheEntry->IsMale = Tokens[3] == "Male"; } else if(Tokens[2] == "Age") { if(Tokens[3] == "0 - 9") TheEntry->AgeGroup = 0; else if(Tokens[3] == "10 - 19") TheEntry->AgeGroup = 1; else if(Tokens[3] == "20 - 29") TheEntry->AgeGroup = 2; else if(Tokens[3] == "30 - 39") TheEntry->AgeGroup = 3; else if(Tokens[3] == "40 - 49") TheEntry->AgeGroup = 4; else if(Tokens[3] == "50 - 59") TheEntry->AgeGroup = 5; else if(Tokens[3] == "60 - 69") TheEntry->AgeGroup = 6; else if(Tokens[3] == "70 - 79") TheEntry->AgeGroup = 7; else if(Tokens[3] == "80 - 89") TheEntry->AgeGroup = 8; else if(Tokens[3] == "90 - 99") TheEntry->AgeGroup = 9; else if(Tokens[3] == "100+") TheEntry->AgeGroup = 10; } else if(Tokens[2] == "Occupation") { string Occ = TheEntry->Occupation = Tokens[3]; // Normalize the occupations to lower case. for(unsigned int Count = 0; Count < Occ.length(); Count++) Occ[Count] = tolower(Occ[Count]); // Look for geek implying substrings. Not ideal but a guess. if(Occ.find("software") != string::npos || Occ.find("programm") != string::npos || Occ.find("system") != string::npos || Occ.find("sysadmin") != string::npos || Occ.find("sysadm") != string::npos || Occ.find("devel") != string::npos || Occ.find("engr") != string::npos || Occ.find("sd") != string::npos || Occ.find("engineer") != string::npos || Occ.find("computer") != string::npos || Occ.find("mathemat") != string::npos || Occ.find("comp-sci") != string::npos || Occ.find("csstudent") != string::npos || Occ.find("dba") != string::npos || Occ.find("web") != string::npos) TheEntry->IsGeek = true; } else if(Tokens[2] == "New Activities") { if(Tokens[3].find("Evening Walks") != string::npos) TheEntry->EveningWalks = true; if(Tokens[3].find("Cultural Activities") != string::npos) TheEntry->CulturalActivities = true; if(Tokens[3].find("Imaginary Friend") != string::npos) TheEntry->ImaginaryFriend = true; if(Tokens[3].find("History") != string::npos) TheEntry->History = true; if(Tokens[3].find("Cooking") != string::npos) TheEntry->Cooking = true; if(Tokens[3].find("Meditation") != string::npos) TheEntry->Meditation = true; if(Tokens[3].find("Change Routes") != string::npos) TheEntry->ChangeRoutes = true; if(Tokens[3].find("Change Job") != string::npos) TheEntry->ChangeJob = true; if(Tokens[3].find("Move Home") != string::npos) TheEntry->MoveHome = true; if(Tokens[3].find("Fall In Love") != string::npos) TheEntry->FallInLove = true; if(Tokens[3].find("Break Up") != string::npos) TheEntry->BreakUp = true; if(Tokens[3].find("New Car") != string::npos) TheEntry->NewCar = true; if(Tokens[3].find("Vacation") != string::npos) TheEntry->Vacation = true; if(Tokens[3].find("See Old Friends") != string::npos) TheEntry->SeeOldFriends = true; if(Tokens[3].find("Get More Sleep") != string::npos) TheEntry->GetMoreSleep = true; if(Tokens[3].find("Disaster") != string::npos) TheEntry->Disaster = true; } else if(Tokens[2] == "I Enjoy My Job") TheEntry->IEnjoyMyJob = EncodeChoice(Tokens[3]); else if(Tokens[2] == "My Job Is Well Defined") TheEntry->MyJobIsWellDefined = EncodeChoice(Tokens[3]); else if(Tokens[2] == "My Co-workers Are Co-operative") TheEntry->MyCoworkersAreCooperative = EncodeChoice(Tokens[3]); else if(Tokens[2] == "My Workplace Is Stressful") TheEntry->MyWorkplaceIsStressful = EncodeChoice(Tokens[3]) * -1; else if(Tokens[2] == "My Job Is Stressful") TheEntry->MyJobIsStressful = EncodeChoice(Tokens[3]) * -1; else if(Tokens[2] == "Morale Is Good Where I Work") TheEntry->MoraleIsGoodWhereIWork = EncodeChoice(Tokens[3]); else if(Tokens[2] == "I Get Frustrated At Work") TheEntry->IGetFrustratedAtWork = EncodeChoice(Tokens[3]) * -1; else if(Tokens[2] == "I Feel Nauseous When Very Bored") TheEntry->IsNauseous = (EncodeChoice(Tokens[3]) > 0); else if(Tokens[2] == "Prescription Meds" || Tokens[2] == "Non-Prescription Drugs") { for(unsigned int Count = 0; Count < DRUG_MAX; Count++) if(Tokens[3].find(DrugNames[Count]) != string::npos) TheEntry->Drugs[Count] = true; } else if(Tokens[2] == "Other Meds") TheEntry->OtherMeds = Tokens[3]; else if(Tokens[2] == "Other Drugs") { string Temp = TheEntry->OtherDrugs = Tokens[3]; for(unsigned int Count = 0; Count < Temp.length(); Count++) Temp[Count] = tolower(Temp[Count]); if(Temp.find("caff") != string::npos) TheEntry->Drugs[DRUG_CAFFEINE] = true; } else if(Tokens[2] == "Before") TheEntry->Before = atoi(Tokens[3].c_str()); else if(Tokens[2] == "After") TheEntry->After = atoi(Tokens[3].c_str()); } //////////////////////////////////////////////////////////////////////////////// int StatsFrame::EncodeChoice(string TheChoice) { if(TheChoice == "Strongly Agree") return 2; if(TheChoice == "Agree") return 1; if(TheChoice == "Disagree") return -1; if(TheChoice == "Strongly Disagree") return -2; // Placate compiler return 0; } //////////////////////////////////////////////////////////////////////////////// void StatsFrame::AddReport(string ThePrompt, Display *&TheDisplay) { GridSizer->Add(new wxStaticText(this, wxID_ANY, ThePrompt.c_str())); GridSizer->Add(TheDisplay = new Display(this)); } //////////////////////////////////////////////////////////////////////////////// // // Class Display // //////////////////////////////////////////////////////////////////////////////// Display::Display(StatsFrame *Parent) : wxTextCtrl(Parent, wxID_ANY, "0", wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxTE_RIGHT) { } //////////////////////////////////////////////////////////////////////////////// void Display::SetInt(int Value) { ostringstream Buffer; Buffer << Value; SetValue(Buffer.str()); } //////////////////////////////////////////////////////////////////////////////// // // Class GraphFrame // //////////////////////////////////////////////////////////////////////////////// BEGIN_EVENT_TABLE(GraphFrame, wxFrame) EVT_CLOSE(GraphFrame::OnClose) END_EVENT_TABLE() //////////////////////////////////////////////////////////////////////////////// GraphFrame::GraphFrame(StatsFrame *TheParent, GraphType TheType, map *TheData) : wxFrame(TheParent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize) { Parent = TheParent; wxBoxSizer *TopSizer = new wxBoxSizer(wxVERTICAL); if(TheType == GRAPH_DISTRIB_BOTH) TopSizer->Add(new DistribPanel(this, TheData, GRAPH_DISTRIB_BOTH), 1, wxEXPAND); else if(TheType == GRAPH_DISTRIB_NAUSEOUS) TopSizer->Add(new DistribPanel(this, TheData, GRAPH_DISTRIB_NAUSEOUS), 1, wxEXPAND); else if(TheType == GRAPH_DISTRIB_NON_NAUSEOUS) TopSizer->Add(new DistribPanel(this, TheData, GRAPH_DISTRIB_NON_NAUSEOUS), 1, wxEXPAND); else if(TheType == GRAPH_DISTRIB_DRUG) TopSizer->Add(new DrugDistribPanel(this, TheData), 1, wxEXPAND); else if(TheType == GRAPH_STRESSORS_BY_AV_SCORE) TopSizer->Add(new StressorsByAvScorePanel(this, TheData), 1, wxEXPAND); else if(TheType == GRAPH_EXERCISES_BY_AV_SCORE) TopSizer->Add(new ExercisesByAvScorePanel(this, TheData), 1, wxEXPAND); else if(TheType == GRAPH_AGE_BY_AV_SCORE) TopSizer->Add(new AgeByAvScorePanel(this, TheData), 1, wxEXPAND); SetSizer(TopSizer); TopSizer->SetSizeHints(this); Show(); } //////////////////////////////////////////////////////////////////////////////// void GraphFrame::OnClose(wxCloseEvent &Event) { Parent->DeregisterFrame(this); Destroy(); } //////////////////////////////////////////////////////////////////////////////// // // Class GraphPanel // //////////////////////////////////////////////////////////////////////////////// BEGIN_EVENT_TABLE(GraphPanel, wxPanel) EVT_PAINT(GraphPanel::OnPaint) END_EVENT_TABLE() //////////////////////////////////////////////////////////////////////////////// GraphPanel::GraphPanel(wxFrame* Parent, map *TheData) : wxPanel(Parent, wxID_ANY, wxDefaultPosition, wxSize(600, 600)) { Data = TheData; SetBackgroundColour(wxColour(255, 255, 255)); ClearBackground(); } //////////////////////////////////////////////////////////////////////////////// void GraphPanel::ChooseColoursDrawAxes(wxDC &Dc) { Dc.SetPen(*wxBLACK_PEN); Dc.SetBrush(*wxGREY_BRUSH); Dc.SetTextForeground(*wxBLACK); Dc.DrawLine(100, 500, 600, 500); Dc.DrawLine(100, 0, 100, 500); } //////////////////////////////////////////////////////////////////////////////// void GraphPanel::DrawScoreScaleY(wxDC &Dc, bool Both) { int Offset; ostringstream Buffer; for(int Count = 0; Count < 10; Count++) { Buffer.str(""); Buffer << Count; Dc.DrawText(Buffer.str().c_str(), 60, 500 - (Count * 50)); } if(Both) { Offset = (500 - Dc.GetTextExtent("Red: Before Green: After (s)").GetWidth()) / 2; Dc.DrawRotatedText("Red: Before Green: After (s)", 30, 500 - Offset, 90.0); } else { Offset = (500 - Dc.GetTextExtent("Before (s)").GetWidth()) / 2; Dc.DrawRotatedText("Before (s)", 30, 500 - Offset, 90.0); } } //////////////////////////////////////////////////////////////////////////////// // // Class DistribPanel // //////////////////////////////////////////////////////////////////////////////// static int ScalingTable[] = { 5, 1, 10, 1, 20, 2, 25, 5, 30, 3, 50, 5, 100, 10, 200, 20, 250, 25, 500, 50, 0, 0 }; //////////////////////////////////////////////////////////////////////////////// DistribPanel::DistribPanel(wxFrame* Parent, map *TheData, GraphType TheType): GraphPanel(Parent, TheData) { Type = TheType; if(Type == GRAPH_DISTRIB_BOTH) Parent->SetTitle("Frequency Distribution"); else if(Type == GRAPH_DISTRIB_NAUSEOUS) Parent->SetTitle("Frequency Distribution (Nauseators)"); else if(Type == GRAPH_DISTRIB_NON_NAUSEOUS) Parent->SetTitle("Frequency Distribution (Non-Nauseators)"); } //////////////////////////////////////////////////////////////////////////////// void DistribPanel::OnPaint(wxPaintEvent &) { wxPaintDC Dc(this); // We can't draw the Y axis without data to scale to! if(Data->size() == 0) return; ChooseColoursDrawAxes(Dc); // Go through the entries counting the number of entries in each 1s band. int Counts[10]; int ScalingCounts[10]; for(int i = 0; i < 10; i++) Counts[i] = ScalingCounts[i] = 0; for(map::iterator It = Data->begin(); It != Data->end(); It++) { ScalingCounts[It->second->Before / 1000]++; if((Type == GRAPH_DISTRIB_BOTH) || (Type == GRAPH_DISTRIB_NAUSEOUS) && (It->second->IsNauseous) || (Type == GRAPH_DISTRIB_NON_NAUSEOUS) && (!It->second->IsNauseous)) Counts[It->second->Before / 1000]++; } // Find the biggest band so we can scale the Y axis. int MaxNumber = 0; int MaxScale = 0; int Division = 0; for(int Count = 0; Count < 10; Count++) if(ScalingCounts[Count] > MaxNumber) MaxNumber = ScalingCounts[Count]; // Look through the scaling table to find the smallest subjectively approved // scale that will accomodate the biggest band. // LATER: Do better if the table is exhausted. int *Cursor; for(Cursor = ScalingTable; *Cursor < MaxNumber; Cursor += 2) if(*Cursor == 0) return; MaxScale = *Cursor; Division = *(Cursor + 1); int PixelsPerNumber = 500 / MaxScale; int PixelsPerDivision = PixelsPerNumber * Division; // Draw the X axis, scale and legend. int Offset; ostringstream Buffer; for(int Count = 0; Count < 10; Count++) { Buffer.str(""); Buffer << Count; Offset = (50 - Dc.GetTextExtent(Buffer.str().c_str()).GetWidth()) / 2; Dc.DrawText(Buffer.str().c_str(), 100 + (Count * 50) + Offset, 500 + 20); } Offset = (500 - Dc.GetTextExtent("Before (s)").GetWidth()) / 2; Dc.DrawText("Before (s)", 100 + Offset, 500 + 40); // Draw the Y axis, scale and legend. for(int Count = 0; Count < MaxScale / Division; Count++) { Buffer.str(""); Buffer << Count * Division; Dc.DrawText(Buffer.str().c_str(), 60, 500 - (Count * PixelsPerDivision)); } Offset = (500 - Dc.GetTextExtent("Respondents").GetWidth()) / 2; Dc.DrawRotatedText("Respondents", 30, 500 - Offset, 90.0); // Draw the blocks. for(int Count = 0; Count < 10; Count++) { Dc.DrawRectangle(100 + (Count * 50), 500 - Counts[Count] * PixelsPerNumber, 50, Counts[Count] * PixelsPerNumber); } } //////////////////////////////////////////////////////////////////////////////// // // Class DrugDistribPanel // //////////////////////////////////////////////////////////////////////////////// DrugDistribPanel::DrugDistribPanel(wxFrame* Parent, map *TheData): GraphPanel(Parent, TheData) { Parent->SetTitle("Frequency Distribution Per Drug"); } //////////////////////////////////////////////////////////////////////////////// void DrugDistribPanel::OnPaint(wxPaintEvent &) { wxPaintDC Dc(this); // We can't draw the Y axis without data to scale to! if(Data->size() == 0) return; ChooseColoursDrawAxes(Dc); // Go through the entries counting the number of entries in each 1s band. int Counts[10][DRUG_MAX]; for(unsigned int i = 0; i < 10; i++) for(unsigned int j = 0; j < DRUG_MAX; j++) Counts[i][j] = 0; for(map::iterator It = Data->begin(); It != Data->end(); It++) for(unsigned int Drug = 0; Drug < DRUG_MAX; Drug++) if(It->second->Drugs[Drug]) Counts[It->second->Before / 1000][Drug]++; // Find the biggest band so we can scale the Y axis. int MaxNumber = 0; int MaxScale = 0; int Division = 0; for(int Count = 0; Count < 10; Count++) for(int Drug = 0; Drug < DRUG_MAX; Drug++) if(Counts[Count][Drug] > MaxNumber) MaxNumber = Counts[Count][Drug]; // Look through the scaling table to find the smallest subjectively approved // scale that will accomodate the biggest band. // LATER: Do better if the table is exhausted. int *Cursor; for(Cursor = ScalingTable; *Cursor < MaxNumber; Cursor += 2) if(*Cursor == 0) return; MaxScale = *Cursor; Division = *(Cursor + 1); int PixelsPerNumber = 500 / MaxScale; int PixelsPerDivision = PixelsPerNumber * Division; // Draw the X axis, scale and legend. int Offset; ostringstream Buffer; for(int Count = 0; Count < 10; Count++) { Buffer.str(""); Buffer << Count; Offset = (50 - Dc.GetTextExtent(Buffer.str().c_str()).GetWidth()) / 2; Dc.DrawText(Buffer.str().c_str(), 100 + (Count * 50) + Offset, 500 + 20); } Offset = (500 - Dc.GetTextExtent("Before (s)").GetWidth()) / 2; Dc.DrawText("Before (s)", 100 + Offset, 500 + 40); // Draw the Y axis, scale and legend. for(int Count = 0; Count < MaxScale / Division; Count++) { Buffer.str(""); Buffer << Count * Division; Dc.DrawText(Buffer.str().c_str(), 60, 500 - (Count * PixelsPerDivision)); } Offset = (500 - Dc.GetTextExtent("Respondents").GetWidth()) / 2; Dc.DrawRotatedText("Respondents", 30, 500 - Offset, 90.0); // Draw the blocks. for(unsigned int Drug = 0; Drug < DRUG_MAX; Drug++) { wxPoint Points[10]; for(int Count = 0; Count < 10; Count++) { Points[Count].x = 100 + (Count * 50) + 25; Points[Count].y = 500 - Counts[Count][Drug] * PixelsPerNumber; } Dc.SetPen(wxPen(*(DrugColours[Drug]))); Dc.SetTextForeground(*(DrugColours[Drug])); Dc.DrawText(DrugNames[Drug].c_str(), 400, 100 + (Drug * 30)); Dc.DrawSpline(10, Points); } } //////////////////////////////////////////////////////////////////////////////// // // Class StressorsByAvScorePanel // //////////////////////////////////////////////////////////////////////////////// StressorsByAvScorePanel::StressorsByAvScorePanel(wxFrame* Parent, map *TheData): GraphPanel(Parent, TheData) { Parent->SetTitle("Stressors By Average Score"); } //////////////////////////////////////////////////////////////////////////////// void StressorsByAvScorePanel::OnPaint(wxPaintEvent &) { wxPaintDC Dc(this); ChooseColoursDrawAxes(Dc); // Draw the X axis, scale and legend. int Offset; int Division = 500 / 30; ostringstream Buffer; for(int Count = 0; Count < 7; Count++) { Buffer.str(""); Buffer << (Count - 3) * 5; Offset = (Dc.GetTextExtent(Buffer.str().c_str()).GetWidth()) / 2; Dc.DrawText(Buffer.str().c_str(), 100 + (Count * Division * 5) - Offset, 500 + 20); } Offset = (500 - Dc.GetTextExtent("Chill Points").GetWidth()) / 2; Dc.DrawText("Chill Points", 100 + Offset, 500 + 40); // Draw the Y scale and legend. DrawScoreScaleY(Dc, false); // Go through the entries and calculate the mean for each stress score. map TotalScores; map TotalCounts; map Means; map SummedSquares; for(map::iterator It = Data->begin(); It != Data->end(); It++) { TotalScores[It->second->GetStressScore()] += It->second->Before; TotalCounts[It->second->GetStressScore()]++; } for(map::iterator It = TotalCounts.begin(); It != TotalCounts.end(); It++) Means[It->first] = TotalScores[It->first] / It->second; // Now make a second pass to calculate the standard deviation. for(map::iterator It = Data->begin(); It != Data->end(); It++) { int Temp = It->second->Before - Means[It->second->GetStressScore()]; SummedSquares[It->second->GetStressScore()] += (Temp * Temp); } // Now draw the means and standard deviations. for(map::iterator It = TotalCounts.begin(); It != TotalCounts.end(); It++) { int X = It->first; int Y = TotalScores[It->first] / It->second; int D = (int)sqrt(SummedSquares[It->first] / It->second); Dc.DrawCircle(100 + 250 + (X * Division), 500 - (Y / 20), 5); Dc.DrawLine(100 + 250 + (X * Division), 500 - ((Y + D) / 20), 100 + 250 + (X * Division), 500 - ((Y - D) / 20)); } } //////////////////////////////////////////////////////////////////////////////// // // Class ExercisesByAvScorePanel // //////////////////////////////////////////////////////////////////////////////// ExercisesByAvScorePanel::ExercisesByAvScorePanel(wxFrame* Parent, map *TheData): GraphPanel(Parent, TheData) { Parent->SetTitle("Exercises By Average Score"); } //////////////////////////////////////////////////////////////////////////////// void ExercisesByAvScorePanel::OnPaint(wxPaintEvent &) { wxPaintDC Dc(this); ChooseColoursDrawAxes(Dc); // Draw the X axis, scale and legend. int Offset; int Division = 500 / 18; ostringstream Buffer; for(int Count = 0; Count < 18; Count++) { Buffer.str(""); Buffer << Count; Offset = (Dc.GetTextExtent(Buffer.str().c_str()).GetWidth()) / 2; Dc.DrawText(Buffer.str().c_str(), 100 + (Count * Division) + (Division / 2) - Offset, 500 + 20); } Offset = (500 - Dc.GetTextExtent("Number of Exercises").GetWidth()) / 2; Dc.DrawText("Number of Exercises", 100 + Offset, 500 + 40); // Draw the Y scale and legend. DrawScoreScaleY(Dc, true); // Go through the entries and calculate the mean before score for each number // of exercises. map TotalScores; map TotalCounts; map Means; map SummedSquares; for(map::iterator It = Data->begin(); It != Data->end(); It++) { if(It->second->After > 0) { TotalScores[It->second->GetExerciseScore()] += It->second->Before; TotalCounts[It->second->GetExerciseScore()]++; } } for(map::iterator It = TotalCounts.begin(); It != TotalCounts.end(); It++) Means[It->first] = TotalScores[It->first] / It->second; // Now make a second pass to calculate the before score standard deviation. for(map::iterator It = Data->begin(); It != Data->end(); It++) { if(It->second->After > 0) { int Temp = It->second->Before - Means[It->second->GetExerciseScore()]; SummedSquares[It->second->GetExerciseScore()] += (Temp * Temp); } } // Now draw the before score means and standard deviations in red. Dc.SetPen(*wxRED_PEN); Dc.SetBrush(*wxRED_BRUSH); for(map::iterator It = TotalCounts.begin(); It != TotalCounts.end(); It++) { int X = It->first; int Y = TotalScores[It->first] / It->second; int D = (int)sqrt(SummedSquares[It->first] / It->second); Dc.DrawCircle(100 + (X * Division) + (Division / 2) - 3, 500 - (Y / 20), 3); Dc.DrawLine(100 + (X * Division) + (Division / 2) - 3, 500 - ((Y + D) / 20), 100 + (X * Division) + (Division / 2) - 3, 500 - ((Y - D) / 20)); } // Go through the entries and calculate the mean after score for each number // of exercises. TotalScores.clear(); TotalCounts.clear(); Means.clear(); SummedSquares.clear(); for(map::iterator It = Data->begin(); It != Data->end(); It++) { if(It->second->After > 0) { TotalScores[It->second->GetExerciseScore()] += It->second->After; TotalCounts[It->second->GetExerciseScore()]++; } } for(map::iterator It = TotalCounts.begin(); It != TotalCounts.end(); It++) Means[It->first] = TotalScores[It->first] / It->second; // Now make a second pass to calculate the before score standard deviation. for(map::iterator It = Data->begin(); It != Data->end(); It++) { if(It->second->After > 0) { int Temp = It->second->After - Means[It->second->GetExerciseScore()]; SummedSquares[It->second->GetExerciseScore()] += (Temp * Temp); } } // Now draw the before score means and standard deviations in red. Dc.SetPen(*wxGREEN_PEN); Dc.SetBrush(*wxGREEN_BRUSH); for(map::iterator It = TotalCounts.begin(); It != TotalCounts.end(); It++) { int X = It->first; int Y = TotalScores[It->first] / It->second; int D = (int)sqrt(SummedSquares[It->first] / It->second); Dc.DrawCircle(100 + (X * Division) + (Division / 2) + 3, 500 - (Y / 20), 3); Dc.DrawLine(100 + (X * Division) + (Division / 2) + 3, 500 - ((Y + D) / 20), 100 + (X * Division) + (Division / 2) + 3, 500 - ((Y - D) / 20)); } } //////////////////////////////////////////////////////////////////////////////// // // Class AgeByAvScorePanel // //////////////////////////////////////////////////////////////////////////////// AgeByAvScorePanel::AgeByAvScorePanel(wxFrame* Parent, map *TheData): GraphPanel(Parent, TheData) { Parent->SetTitle("Age By Average Score"); } //////////////////////////////////////////////////////////////////////////////// void AgeByAvScorePanel::OnPaint(wxPaintEvent &) { wxPaintDC Dc(this); ChooseColoursDrawAxes(Dc); // Draw the X axis, scale and legend. int Offset; ostringstream Buffer; for(int Count = 0; Count < 10; Count++) { Buffer.str(""); Buffer << Count; Offset = (50 - Dc.GetTextExtent(Buffer.str().c_str()).GetWidth()) / 2; Dc.DrawText(Buffer.str().c_str(), 100 + (Count * 50) + Offset, 500 + 20); } Offset = (500 - Dc.GetTextExtent("Age (Decade)").GetWidth()) / 2; Dc.DrawText("Age (Decade)", 100 + Offset, 500 + 40); // Draw the Y scale and legend. DrawScoreScaleY(Dc, false); // Go through the entries and calculate the mean for each stress score. int TotalScores[10]; int TotalCounts[10]; int Means[10]; int SummedSquares[10]; for(int Count = 0; Count < 10; Count++) TotalScores[Count] = TotalCounts[Count] = Means[Count] = SummedSquares[Count] = 0; for(map::iterator It = Data->begin(); It != Data->end(); It++) { if(It->second->AgeGroup < 10) { TotalScores[It->second->AgeGroup] += It->second->Before; TotalCounts[It->second->AgeGroup]++; } } for(int Count = 0; Count < 10; Count++) if(TotalCounts[Count] > 0) Means[Count] = TotalScores[Count] / TotalCounts[Count]; // Now make a second pass to calculate the standard deviation. for(map::iterator It = Data->begin(); It != Data->end(); It++) { if(It->second->AgeGroup < 10) { int Temp = It->second->Before - Means[It->second->AgeGroup]; SummedSquares[It->second->AgeGroup] += (Temp * Temp); } } // Now draw the means and standard deviations. for(int Count = 0; Count < 10; Count++) { if(TotalCounts[Count] > 0) { int Y = (TotalScores[Count] / TotalCounts[Count]) / 20; int D = (int)sqrt(SummedSquares[Count] / TotalCounts[Count]) / 20; Dc.DrawCircle(125 + (Count * 50), 500 - Y, 5); Dc.DrawLine(125 + (Count * 50), 500 - (Y + D), 125 + (Count * 50), 500 - (Y - D)); } } } //////////////////////////////////////////////////////////////////////////////// // // Class Entry // //////////////////////////////////////////////////////////////////////////////// Entry::Entry(void) { IsMale = true; AgeGroup = 0; Occupation = ""; IsGeek = false; EveningWalks = false; CulturalActivities = false; ImaginaryFriend = false; History = false; Cooking = false; Meditation = false; ChangeRoutes = false; ChangeJob = false; MoveHome = false; FallInLove = false; BreakUp = false; NewCar = false; Vacation = false; SeeOldFriends = false; GetMoreSleep = false; Disaster = false; IEnjoyMyJob = 0; MyJobIsWellDefined = 0; MyCoworkersAreCooperative = 0; MyWorkplaceIsStressful = 0; MyJobIsStressful = 0; MoraleIsGoodWhereIWork = 0; IGetFrustratedAtWork = 0; IsNauseous = false; for(unsigned int Count = 0; Count < DRUG_MAX; Count++) Drugs[Count] = false; OtherMeds = ""; OtherDrugs = ""; Before = 0; After = 0; } //////////////////////////////////////////////////////////////////////////////// int Entry::GetStressScore(void) { return IEnjoyMyJob + MyJobIsWellDefined + MyCoworkersAreCooperative + MyWorkplaceIsStressful + MyJobIsStressful + MoraleIsGoodWhereIWork + IGetFrustratedAtWork; } //////////////////////////////////////////////////////////////////////////////// int Entry::GetExerciseScore(void) { int Count = 0; if(EveningWalks) Count++; if(CulturalActivities) Count++; if(ImaginaryFriend) Count++; if(History) Count++; if(Cooking) Count++; if(Meditation) Count++; if(ChangeRoutes) Count++; if(ChangeJob) Count++; if(MoveHome) Count++; if(FallInLove) Count++; if(BreakUp) Count++; if(NewCar) Count++; if(Vacation) Count++; if(SeeOldFriends) Count++; if(GetMoreSleep) Count++; if(Disaster) Count++; return Count; } ////////////////////////////////////////////////////////////////////////////////