diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..20fe7b7
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,80 @@
+# Changelog (Version 3)
+
+## Version 3.2 (3/7/2023)
+
+The Morea team is delighted to announce the release of Version 3.2. This release focuses on improved support for new Morea users, and includes:
+
+* Addition of a "template-module" to the morea template repository.
+* Documentation explaining module design and implementation using the new template-module. For details, see [Module design](https://morea-framework.github.io/docs/instructors/module-design).
+* Addition of a "_module-icons" directory to the morea template repository, making over 50 PNG from past Morea modules available. For details, see [Module Icons documentation](https://morea-framework.github.io/docs/instructors/icons#module-icons).
+* Documentation on how to use Gitpod for Morea development. For details, see [Choosing local vs. cloud development](https://morea-framework.github.io/docs/instructors/quick-start#choose-local-or-cloud-development)
+
+Have fun and let us know if you run into problems.
+
+## Version 3.1
+
+The Morea team is delighted to announce the release of Version 3.1. This release adds a number of useful new features to Morea, including:
+
+* Update of Bootstrap to 5.1.3. This enables style customization using [Bootstrap CSS Variables](https://getbootstrap.com/docs/5.0/customize/css-variables/).
+
+* Morea 3.1 sites now load [FontAwesome](https://fontawesome.com/). This makes a gazillion icons available. For documentation, see the [Morea User Guide Chapter on Icons](/docs/instructors/icons).
+
+* You can now easily create "Admonitions" or "Callouts". See the [Morea User Guide Chapter on Admonitions](/docs/instructors/admonitions) for details.
+
+* Morea 3.1 sites now load [Anchor.js](https://www.bryanbraun.com/anchorjs/). This provides deep linking into sections of pages.
+
+Many users might decide to wait to obtain these features until the next time they create a Morea site. If you want to upgrade an existing site to Version 3.1, then we provide guidance in the [Morea User Guide Chapter on Updating](/docs/instructors/updating).
+
+Have fun and let us know if you run into problems.
+
+
+
+## Version 3.0 (8/1/21)
+
+The Morea team is delighted to announce a new major release of the system. This release should be mostly backward compatible with existing Morea instructor content, though there are extensive changes (and simplifications!) to the way sites are developed and managed.
+
+### Summary of changes
+
+Here is a summary of the changes from Version 2:
+
+1. Morea 3 uses the latest releases of Jekyll (Version 4.2.0), Bootstrap (5), Bootswatch (5), FullCalendar (5.8), and ChartJS (3.5).
+
+2. Create a new Morea 3 site using the GitHub "template" mechanism. Templates have advantages over the previous approach of cloning: you can create a private repository even though the template is a public repository, and you can create multiple repositories in a single organization from the same template.
+
+3. Morea 3 sites are automatically built and deployed via GitHub Actions whenever there is a commit to the main branch. Unlike Morea 2, Morea 3 does not use custom scripts (such as `morea-run-local.sh`, `morea-publish.sh`, etc.).
+
+4. Morea 3 does not require local management of multiple branches. Unlike Morea 2, there are no "src" and "gh-pages" subdirectories.
+
+5. To build and run a Morea 3 site locally, you no longer use a custom script (`morea-run-local.sh`). Instead, you use the standard Jekyll command `bundle exec jekyll serve`.
+
+6. To publish a Morea 3 site, you no longer use a special script (`morea-publish.sh`). Instead, you just commit your changes and push your repository to GitHub. Morea 3 uses GitHub Actions to automatically build and publish the site whenever there is a commit to the main branch in GitHub.
+
+5. Morea 3 is (finally!) cross-platform: there should be no significant differences between development on MacOS, Linux, and Windows platforms.
+
+Astute Morea users will also observe that https://morea-framework.github.io has been reimplemented using Docusaurus. This will make documentation easier to maintain.
+
+### Migrating your V2 content to V3.0
+
+Instructors who have existing Morea V2 sites for a course taught in a prior semester and who wish to build a new Morea site for a future semester will need to migrate their content. Here are the issues that have been discovered with this migration:
+
+In Morea 3, Assessments are no longer "page fragments" (like Outcomes), but rather their own page. This leads to several breaking changes:
+
+* The morea_summary field is now required for Assessment pages.
+* The URL for interior linking to an Assessment page has changed. Please see [this page](/docs/instructors/linking-in-morea).
+
+Morea 3 updates JSChart from Version 1 to Version 3. As a result:
+
+* Pages that embed JSChart graphs must be updated to the JSChart 3 API. For example, see the [example Morea assessment page](https://morea-framework.github.io/morea/morea/example-javascript/assessment-javascript-1.html).
+
+Morea 3 updates Bootstrap from Version 3 to Version 5. As a result:
+
+* Pages that embed Bootstrap CSS and HTML must be updated to Bootstrap Version 5.
+
+
+### Getting started with 3.0
+
+If you are a current user of Morea, please read the Getting Started section of the Instructor Guide and try building a new site using your old Morea files. If you run into problems, please create an issue or send an email so we can address it.
+
+A list of known issues and desired enhancements for Morea 3.0 is available at [https://github.com/morea-framework/morea/issues](https://github.com/morea-framework/morea/issues).
+
+Have fun and let us know if you run into problems.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..dca6ebd
--- /dev/null
+++ b/README.md
@@ -0,0 +1,6 @@
+# Morea Template
+
+This repository contains a template Morea course site.
+
+For documentation on how to use Morea, please see https://morea-framework.github.io.
+
diff --git a/VERSION.md b/VERSION.md
new file mode 100644
index 0000000..a3ec5a4
--- /dev/null
+++ b/VERSION.md
@@ -0,0 +1 @@
+3.2
diff --git a/assessments/index.html b/assessments/index.html
new file mode 100644
index 0000000..0ef46db
--- /dev/null
+++ b/assessments/index.html
@@ -0,0 +1,260 @@
+
+
+
This page collects together all of the “experiences” associated with individual modules.
+
+
In this site, experiences represent “active” learning opportunities, as opposed to readings, which represent “passive” learning opportunities. In many courses, readings and experiences together constitute the “assignments”.
ICS 199 is a four week overview of selected topics in computer science, including Javascript, Open Source Software, and Ethics.
+
+
Who should take this course
+
+
This course is intended for undergraduates in computer science or computer engineering who have a working knowledge of at least one programming language and who want to get a taste of important topics in the field.
+
+
Pedagogy
+
+
ICS 199 is structured as a sequential series of modules, each taking approximately a week to complete. Each module has the following structure:
+
+
+
Prerequisites, describing skills you should have prior to starting the module.
Assessments, which help you determine if you have acquired mastery of the material.
+
+
+
About the instructor
+
+
Philip Johnson is a Professor of Information and Computer Sciences at the University of Hawaii. His research interests include climate change, educational technology, software engineering, and serious games. He is currently an active developer of RadGrad, an open source software system based on Javascript, Typescript, React, and Meteor.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/js/Chart.js b/js/Chart.js
new file mode 100755
index 0000000..2c2b608
--- /dev/null
+++ b/js/Chart.js
@@ -0,0 +1,3379 @@
+/*!
+ * Chart.js
+ * http://chartjs.org/
+ * Version: 1.0.1-beta.4
+ *
+ * Copyright 2014 Nick Downie
+ * Released under the MIT license
+ * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
+ */
+
+
+(function(){
+
+ "use strict";
+
+ //Declare root variable - window in the browser, global on the server
+ var root = this,
+ previous = root.Chart;
+
+ //Occupy the global variable of Chart, and create a simple base class
+ var Chart = function(context){
+ var chart = this;
+ this.canvas = context.canvas;
+
+ this.ctx = context;
+
+ //Variables global to the chart
+ var width = this.width = context.canvas.width;
+ var height = this.height = context.canvas.height;
+ this.aspectRatio = this.width / this.height;
+ //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
+ helpers.retinaScale(this);
+
+ return this;
+ };
+ //Globally expose the defaults to allow for user updating/changing
+ Chart.defaults = {
+ global: {
+ // Boolean - Whether to animate the chart
+ animation: true,
+
+ // Number - Number of animation steps
+ animationSteps: 60,
+
+ // String - Animation easing effect
+ animationEasing: "easeOutQuart",
+
+ // Boolean - If we should show the scale at all
+ showScale: true,
+
+ // Boolean - If we want to override with a hard coded scale
+ scaleOverride: false,
+
+ // ** Required if scaleOverride is true **
+ // Number - The number of steps in a hard coded scale
+ scaleSteps: null,
+ // Number - The value jump in the hard coded scale
+ scaleStepWidth: null,
+ // Number - The scale starting value
+ scaleStartValue: null,
+
+ // String - Colour of the scale line
+ scaleLineColor: "rgba(0,0,0,.1)",
+
+ // Number - Pixel width of the scale line
+ scaleLineWidth: 1,
+
+ // Boolean - Whether to show labels on the scale
+ scaleShowLabels: true,
+
+ // Interpolated JS string - can access value
+ scaleLabel: "<%=value%>",
+
+ // Boolean - Whether the scale should stick to integers, and not show any floats even if drawing space is there
+ scaleIntegersOnly: true,
+
+ // Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
+ scaleBeginAtZero: false,
+
+ // String - Scale label font declaration for the scale label
+ scaleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
+
+ // Number - Scale label font size in pixels
+ scaleFontSize: 12,
+
+ // String - Scale label font weight style
+ scaleFontStyle: "normal",
+
+ // String - Scale label font colour
+ scaleFontColor: "#666",
+
+ // Boolean - whether or not the chart should be responsive and resize when the browser does.
+ responsive: false,
+
+ // Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container
+ maintainAspectRatio: true,
+
+ // Boolean - Determines whether to draw tooltips on the canvas or not - attaches events to touchmove & mousemove
+ showTooltips: true,
+
+ // Array - Array of string names to attach tooltip events
+ tooltipEvents: ["mousemove", "touchstart", "touchmove", "mouseout"],
+
+ // String - Tooltip background colour
+ tooltipFillColor: "rgba(0,0,0,0.8)",
+
+ // String - Tooltip label font declaration for the scale label
+ tooltipFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
+
+ // Number - Tooltip label font size in pixels
+ tooltipFontSize: 14,
+
+ // String - Tooltip font weight style
+ tooltipFontStyle: "normal",
+
+ // String - Tooltip label font colour
+ tooltipFontColor: "#fff",
+
+ // String - Tooltip title font declaration for the scale label
+ tooltipTitleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
+
+ // Number - Tooltip title font size in pixels
+ tooltipTitleFontSize: 14,
+
+ // String - Tooltip title font weight style
+ tooltipTitleFontStyle: "bold",
+
+ // String - Tooltip title font colour
+ tooltipTitleFontColor: "#fff",
+
+ // Number - pixel width of padding around tooltip text
+ tooltipYPadding: 6,
+
+ // Number - pixel width of padding around tooltip text
+ tooltipXPadding: 6,
+
+ // Number - Size of the caret on the tooltip
+ tooltipCaretSize: 8,
+
+ // Number - Pixel radius of the tooltip border
+ tooltipCornerRadius: 6,
+
+ // Number - Pixel offset from point x to tooltip edge
+ tooltipXOffset: 10,
+
+ // String - Template string for single tooltips
+ tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= value %>",
+
+ // String - Template string for single tooltips
+ multiTooltipTemplate: "<%= value %>",
+
+ // String - Colour behind the legend colour block
+ multiTooltipKeyBackground: '#fff',
+
+ // Function - Will fire on animation progression.
+ onAnimationProgress: function(){},
+
+ // Function - Will fire on animation completion.
+ onAnimationComplete: function(){}
+
+ }
+ };
+
+ //Create a dictionary of chart types, to allow for extension of existing types
+ Chart.types = {};
+
+ //Global Chart helpers object for utility methods and classes
+ var helpers = Chart.helpers = {};
+
+ //-- Basic js utility methods
+ var each = helpers.each = function(loopable,callback,self){
+ var additionalArgs = Array.prototype.slice.call(arguments, 3);
+ // Check to see if null or undefined firstly.
+ if (loopable){
+ if (loopable.length === +loopable.length){
+ var i;
+ for (i=0; i= 0; i--) {
+ var currentItem = arrayToSearch[i];
+ if (filterCallback(currentItem)){
+ return currentItem;
+ }
+ };
+ },
+ inherits = helpers.inherits = function(extensions){
+ //Basic javascript inheritance based on the model created in Backbone.js
+ var parent = this;
+ var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function(){ return parent.apply(this, arguments); };
+
+ var Surrogate = function(){ this.constructor = ChartElement;};
+ Surrogate.prototype = parent.prototype;
+ ChartElement.prototype = new Surrogate();
+
+ ChartElement.extend = inherits;
+
+ if (extensions) extend(ChartElement.prototype, extensions);
+
+ ChartElement.__super__ = parent.prototype;
+
+ return ChartElement;
+ },
+ noop = helpers.noop = function(){},
+ uid = helpers.uid = (function(){
+ var id=0;
+ return function(){
+ return "chart-" + id++;
+ };
+ })(),
+ warn = helpers.warn = function(str){
+ //Method for warning of errors
+ if (window.console && typeof window.console.warn == "function") console.warn(str);
+ },
+ amd = helpers.amd = (typeof define == 'function' && define.amd),
+ //-- Math methods
+ isNumber = helpers.isNumber = function(n){
+ return !isNaN(parseFloat(n)) && isFinite(n);
+ },
+ max = helpers.max = function(array){
+ return Math.max.apply( Math, array );
+ },
+ min = helpers.min = function(array){
+ return Math.min.apply( Math, array );
+ },
+ cap = helpers.cap = function(valueToCap,maxValue,minValue){
+ if(isNumber(maxValue)) {
+ if( valueToCap > maxValue ) {
+ return maxValue;
+ }
+ }
+ else if(isNumber(minValue)){
+ if ( valueToCap < minValue ){
+ return minValue;
+ }
+ }
+ return valueToCap;
+ },
+ getDecimalPlaces = helpers.getDecimalPlaces = function(num){
+ if (num%1!==0 && isNumber(num)){
+ return num.toString().split(".")[1].length;
+ }
+ else {
+ return 0;
+ }
+ },
+ toRadians = helpers.radians = function(degrees){
+ return degrees * (Math.PI/180);
+ },
+ // Gets the angle from vertical upright to the point about a centre.
+ getAngleFromPoint = helpers.getAngleFromPoint = function(centrePoint, anglePoint){
+ var distanceFromXCenter = anglePoint.x - centrePoint.x,
+ distanceFromYCenter = anglePoint.y - centrePoint.y,
+ radialDistanceFromCenter = Math.sqrt( distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
+
+
+ var angle = Math.PI * 2 + Math.atan2(distanceFromYCenter, distanceFromXCenter);
+
+ //If the segment is in the top left quadrant, we need to add another rotation to the angle
+ if (distanceFromXCenter < 0 && distanceFromYCenter < 0){
+ angle += Math.PI*2;
+ }
+
+ return {
+ angle: angle,
+ distance: radialDistanceFromCenter
+ };
+ },
+ aliasPixel = helpers.aliasPixel = function(pixelWidth){
+ return (pixelWidth % 2 === 0) ? 0 : 0.5;
+ },
+ splineCurve = helpers.splineCurve = function(FirstPoint,MiddlePoint,AfterPoint,t){
+ //Props to Rob Spencer at scaled innovation for his post on splining between points
+ //http://scaledinnovation.com/analytics/splines/aboutSplines.html
+ var d01=Math.sqrt(Math.pow(MiddlePoint.x-FirstPoint.x,2)+Math.pow(MiddlePoint.y-FirstPoint.y,2)),
+ d12=Math.sqrt(Math.pow(AfterPoint.x-MiddlePoint.x,2)+Math.pow(AfterPoint.y-MiddlePoint.y,2)),
+ fa=t*d01/(d01+d12),// scaling factor for triangle Ta
+ fb=t*d12/(d01+d12);
+ return {
+ inner : {
+ x : MiddlePoint.x-fa*(AfterPoint.x-FirstPoint.x),
+ y : MiddlePoint.y-fa*(AfterPoint.y-FirstPoint.y)
+ },
+ outer : {
+ x: MiddlePoint.x+fb*(AfterPoint.x-FirstPoint.x),
+ y : MiddlePoint.y+fb*(AfterPoint.y-FirstPoint.y)
+ }
+ };
+ },
+ calculateOrderOfMagnitude = helpers.calculateOrderOfMagnitude = function(val){
+ return Math.floor(Math.log(val) / Math.LN10);
+ },
+ calculateScaleRange = helpers.calculateScaleRange = function(valuesArray, drawingSize, textSize, startFromZero, integersOnly){
+
+ //Set a minimum step of two - a point at the top of the graph, and a point at the base
+ var minSteps = 2,
+ maxSteps = Math.floor(drawingSize/(textSize * 1.5)),
+ skipFitting = (minSteps >= maxSteps);
+
+ var maxValue = max(valuesArray),
+ minValue = min(valuesArray);
+
+ // We need some degree of seperation here to calculate the scales if all the values are the same
+ // Adding/minusing 0.5 will give us a range of 1.
+ if (maxValue === minValue){
+ maxValue += 0.5;
+ // So we don't end up with a graph with a negative start value if we've said always start from zero
+ if (minValue >= 0.5 && !startFromZero){
+ minValue -= 0.5;
+ }
+ else{
+ // Make up a whole number above the values
+ maxValue += 0.5;
+ }
+ }
+
+ var valueRange = Math.abs(maxValue - minValue),
+ rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange),
+ graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
+ graphMin = (startFromZero) ? 0 : Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
+ graphRange = graphMax - graphMin,
+ stepValue = Math.pow(10, rangeOrderOfMagnitude),
+ numberOfSteps = Math.round(graphRange / stepValue);
+
+ //If we have more space on the graph we'll use it to give more definition to the data
+ while((numberOfSteps > maxSteps || (numberOfSteps * 2) < maxSteps) && !skipFitting) {
+ if(numberOfSteps > maxSteps){
+ stepValue *=2;
+ numberOfSteps = Math.round(graphRange/stepValue);
+ // Don't ever deal with a decimal number of steps - cancel fitting and just use the minimum number of steps.
+ if (numberOfSteps % 1 !== 0){
+ skipFitting = true;
+ }
+ }
+ //We can fit in double the amount of scale points on the scale
+ else{
+ //If user has declared ints only, and the step value isn't a decimal
+ if (integersOnly && rangeOrderOfMagnitude >= 0){
+ //If the user has said integers only, we need to check that making the scale more granular wouldn't make it a float
+ if(stepValue/2 % 1 === 0){
+ stepValue /=2;
+ numberOfSteps = Math.round(graphRange/stepValue);
+ }
+ //If it would make it a float break out of the loop
+ else{
+ break;
+ }
+ }
+ //If the scale doesn't have to be an int, make the scale more granular anyway.
+ else{
+ stepValue /=2;
+ numberOfSteps = Math.round(graphRange/stepValue);
+ }
+
+ }
+ }
+
+ if (skipFitting){
+ numberOfSteps = minSteps;
+ stepValue = graphRange / numberOfSteps;
+ }
+
+ return {
+ steps : numberOfSteps,
+ stepValue : stepValue,
+ min : graphMin,
+ max : graphMin + (numberOfSteps * stepValue)
+ };
+
+ },
+ /* jshint ignore:start */
+ // Blows up jshint errors based on the new Function constructor
+ //Templating methods
+ //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/
+ template = helpers.template = function(templateString, valuesObject){
+ // If templateString is function rather than string-template - call the function for valuesObject
+ if(templateString instanceof Function){
+ return templateString(valuesObject);
+ }
+
+ var cache = {};
+ function tmpl(str, data){
+ // Figure out if we're getting a template, or if we need to
+ // load the template - and be sure to cache the result.
+ var fn = !/\W/.test(str) ?
+ cache[str] = cache[str] :
+
+ // Generate a reusable function that will serve as a template
+ // generator (and which will be cached).
+ new Function("obj",
+ "var p=[],print=function(){p.push.apply(p,arguments);};" +
+
+ // Introduce the data as local variables using with(){}
+ "with(obj){p.push('" +
+
+ // Convert the template into pure JavaScript
+ str
+ .replace(/[\r\t\n]/g, " ")
+ .split("<%").join("\t")
+ .replace(/((^|%>)[^\t]*)'/g, "$1\r")
+ .replace(/\t=(.*?)%>/g, "',$1,'")
+ .split("\t").join("');")
+ .split("%>").join("p.push('")
+ .split("\r").join("\\'") +
+ "');}return p.join('');"
+ );
+
+ // Provide some basic currying to the user
+ return data ? fn( data ) : fn;
+ }
+ return tmpl(templateString,valuesObject);
+ },
+ /* jshint ignore:end */
+ generateLabels = helpers.generateLabels = function(templateString,numberOfSteps,graphMin,stepValue){
+ var labelsArray = new Array(numberOfSteps);
+ if (labelTemplateString){
+ each(labelsArray,function(val,index){
+ labelsArray[index] = template(templateString,{value: (graphMin + (stepValue*(index+1)))});
+ });
+ }
+ return labelsArray;
+ },
+ //--Animation methods
+ //Easing functions adapted from Robert Penner's easing equations
+ //http://www.robertpenner.com/easing/
+ easingEffects = helpers.easingEffects = {
+ linear: function (t) {
+ return t;
+ },
+ easeInQuad: function (t) {
+ return t * t;
+ },
+ easeOutQuad: function (t) {
+ return -1 * t * (t - 2);
+ },
+ easeInOutQuad: function (t) {
+ if ((t /= 1 / 2) < 1) return 1 / 2 * t * t;
+ return -1 / 2 * ((--t) * (t - 2) - 1);
+ },
+ easeInCubic: function (t) {
+ return t * t * t;
+ },
+ easeOutCubic: function (t) {
+ return 1 * ((t = t / 1 - 1) * t * t + 1);
+ },
+ easeInOutCubic: function (t) {
+ if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t;
+ return 1 / 2 * ((t -= 2) * t * t + 2);
+ },
+ easeInQuart: function (t) {
+ return t * t * t * t;
+ },
+ easeOutQuart: function (t) {
+ return -1 * ((t = t / 1 - 1) * t * t * t - 1);
+ },
+ easeInOutQuart: function (t) {
+ if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t;
+ return -1 / 2 * ((t -= 2) * t * t * t - 2);
+ },
+ easeInQuint: function (t) {
+ return 1 * (t /= 1) * t * t * t * t;
+ },
+ easeOutQuint: function (t) {
+ return 1 * ((t = t / 1 - 1) * t * t * t * t + 1);
+ },
+ easeInOutQuint: function (t) {
+ if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t * t;
+ return 1 / 2 * ((t -= 2) * t * t * t * t + 2);
+ },
+ easeInSine: function (t) {
+ return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1;
+ },
+ easeOutSine: function (t) {
+ return 1 * Math.sin(t / 1 * (Math.PI / 2));
+ },
+ easeInOutSine: function (t) {
+ return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1);
+ },
+ easeInExpo: function (t) {
+ return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1));
+ },
+ easeOutExpo: function (t) {
+ return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1);
+ },
+ easeInOutExpo: function (t) {
+ if (t === 0) return 0;
+ if (t === 1) return 1;
+ if ((t /= 1 / 2) < 1) return 1 / 2 * Math.pow(2, 10 * (t - 1));
+ return 1 / 2 * (-Math.pow(2, -10 * --t) + 2);
+ },
+ easeInCirc: function (t) {
+ if (t >= 1) return t;
+ return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1);
+ },
+ easeOutCirc: function (t) {
+ return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t);
+ },
+ easeInOutCirc: function (t) {
+ if ((t /= 1 / 2) < 1) return -1 / 2 * (Math.sqrt(1 - t * t) - 1);
+ return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);
+ },
+ easeInElastic: function (t) {
+ var s = 1.70158;
+ var p = 0;
+ var a = 1;
+ if (t === 0) return 0;
+ if ((t /= 1) == 1) return 1;
+ if (!p) p = 1 * 0.3;
+ if (a < Math.abs(1)) {
+ a = 1;
+ s = p / 4;
+ } else s = p / (2 * Math.PI) * Math.asin(1 / a);
+ return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
+ },
+ easeOutElastic: function (t) {
+ var s = 1.70158;
+ var p = 0;
+ var a = 1;
+ if (t === 0) return 0;
+ if ((t /= 1) == 1) return 1;
+ if (!p) p = 1 * 0.3;
+ if (a < Math.abs(1)) {
+ a = 1;
+ s = p / 4;
+ } else s = p / (2 * Math.PI) * Math.asin(1 / a);
+ return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1;
+ },
+ easeInOutElastic: function (t) {
+ var s = 1.70158;
+ var p = 0;
+ var a = 1;
+ if (t === 0) return 0;
+ if ((t /= 1 / 2) == 2) return 1;
+ if (!p) p = 1 * (0.3 * 1.5);
+ if (a < Math.abs(1)) {
+ a = 1;
+ s = p / 4;
+ } else s = p / (2 * Math.PI) * Math.asin(1 / a);
+ if (t < 1) return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
+ return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1;
+ },
+ easeInBack: function (t) {
+ var s = 1.70158;
+ return 1 * (t /= 1) * t * ((s + 1) * t - s);
+ },
+ easeOutBack: function (t) {
+ var s = 1.70158;
+ return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1);
+ },
+ easeInOutBack: function (t) {
+ var s = 1.70158;
+ if ((t /= 1 / 2) < 1) return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s));
+ return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
+ },
+ easeInBounce: function (t) {
+ return 1 - easingEffects.easeOutBounce(1 - t);
+ },
+ easeOutBounce: function (t) {
+ if ((t /= 1) < (1 / 2.75)) {
+ return 1 * (7.5625 * t * t);
+ } else if (t < (2 / 2.75)) {
+ return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75);
+ } else if (t < (2.5 / 2.75)) {
+ return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375);
+ } else {
+ return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375);
+ }
+ },
+ easeInOutBounce: function (t) {
+ if (t < 1 / 2) return easingEffects.easeInBounce(t * 2) * 0.5;
+ return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5;
+ }
+ },
+ //Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
+ requestAnimFrame = helpers.requestAnimFrame = (function(){
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function(callback) {
+ return window.setTimeout(callback, 1000 / 60);
+ };
+ })(),
+ cancelAnimFrame = helpers.cancelAnimFrame = (function(){
+ return window.cancelAnimationFrame ||
+ window.webkitCancelAnimationFrame ||
+ window.mozCancelAnimationFrame ||
+ window.oCancelAnimationFrame ||
+ window.msCancelAnimationFrame ||
+ function(callback) {
+ return window.clearTimeout(callback, 1000 / 60);
+ };
+ })(),
+ animationLoop = helpers.animationLoop = function(callback,totalSteps,easingString,onProgress,onComplete,chartInstance){
+
+ var currentStep = 0,
+ easingFunction = easingEffects[easingString] || easingEffects.linear;
+
+ var animationFrame = function(){
+ currentStep++;
+ var stepDecimal = currentStep/totalSteps;
+ var easeDecimal = easingFunction(stepDecimal);
+
+ callback.call(chartInstance,easeDecimal,stepDecimal, currentStep);
+ onProgress.call(chartInstance,easeDecimal,stepDecimal);
+ if (currentStep < totalSteps){
+ chartInstance.animationFrame = requestAnimFrame(animationFrame);
+ } else{
+ onComplete.apply(chartInstance);
+ }
+ };
+ requestAnimFrame(animationFrame);
+ },
+ //-- DOM methods
+ getRelativePosition = helpers.getRelativePosition = function(evt){
+ var mouseX, mouseY;
+ var e = evt.originalEvent || evt,
+ canvas = evt.currentTarget || evt.srcElement,
+ boundingRect = canvas.getBoundingClientRect();
+
+ if (e.touches){
+ mouseX = e.touches[0].clientX - boundingRect.left;
+ mouseY = e.touches[0].clientY - boundingRect.top;
+
+ }
+ else{
+ mouseX = e.clientX - boundingRect.left;
+ mouseY = e.clientY - boundingRect.top;
+ }
+
+ return {
+ x : mouseX,
+ y : mouseY
+ };
+
+ },
+ addEvent = helpers.addEvent = function(node,eventType,method){
+ if (node.addEventListener){
+ node.addEventListener(eventType,method);
+ } else if (node.attachEvent){
+ node.attachEvent("on"+eventType, method);
+ } else {
+ node["on"+eventType] = method;
+ }
+ },
+ removeEvent = helpers.removeEvent = function(node, eventType, handler){
+ if (node.removeEventListener){
+ node.removeEventListener(eventType, handler, false);
+ } else if (node.detachEvent){
+ node.detachEvent("on"+eventType,handler);
+ } else{
+ node["on" + eventType] = noop;
+ }
+ },
+ bindEvents = helpers.bindEvents = function(chartInstance, arrayOfEvents, handler){
+ // Create the events object if it's not already present
+ if (!chartInstance.events) chartInstance.events = {};
+
+ each(arrayOfEvents,function(eventName){
+ chartInstance.events[eventName] = function(){
+ handler.apply(chartInstance, arguments);
+ };
+ addEvent(chartInstance.chart.canvas,eventName,chartInstance.events[eventName]);
+ });
+ },
+ unbindEvents = helpers.unbindEvents = function (chartInstance, arrayOfEvents) {
+ each(arrayOfEvents, function(handler,eventName){
+ removeEvent(chartInstance.chart.canvas, eventName, handler);
+ });
+ },
+ getMaximumWidth = helpers.getMaximumWidth = function(domNode){
+ var container = domNode.parentNode;
+ // TODO = check cross browser stuff with this.
+ return container.clientWidth;
+ },
+ getMaximumHeight = helpers.getMaximumHeight = function(domNode){
+ var container = domNode.parentNode;
+ // TODO = check cross browser stuff with this.
+ return container.clientHeight;
+ },
+ getMaximumSize = helpers.getMaximumSize = helpers.getMaximumWidth, // legacy support
+ retinaScale = helpers.retinaScale = function(chart){
+ var ctx = chart.ctx,
+ width = chart.canvas.width,
+ height = chart.canvas.height;
+
+ if (window.devicePixelRatio) {
+ ctx.canvas.style.width = width + "px";
+ ctx.canvas.style.height = height + "px";
+ ctx.canvas.height = height * window.devicePixelRatio;
+ ctx.canvas.width = width * window.devicePixelRatio;
+ ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
+ }
+ },
+ //-- Canvas methods
+ clear = helpers.clear = function(chart){
+ chart.ctx.clearRect(0,0,chart.width,chart.height);
+ },
+ fontString = helpers.fontString = function(pixelSize,fontStyle,fontFamily){
+ return fontStyle + " " + pixelSize+"px " + fontFamily;
+ },
+ longestText = helpers.longestText = function(ctx,font,arrayOfStrings){
+ ctx.font = font;
+ var longest = 0;
+ each(arrayOfStrings,function(string){
+ var textWidth = ctx.measureText(string).width;
+ longest = (textWidth > longest) ? textWidth : longest;
+ });
+ return longest;
+ },
+ drawRoundedRectangle = helpers.drawRoundedRectangle = function(ctx,x,y,width,height,radius){
+ ctx.beginPath();
+ ctx.moveTo(x + radius, y);
+ ctx.lineTo(x + width - radius, y);
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
+ ctx.lineTo(x + width, y + height - radius);
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
+ ctx.lineTo(x + radius, y + height);
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
+ ctx.lineTo(x, y + radius);
+ ctx.quadraticCurveTo(x, y, x + radius, y);
+ ctx.closePath();
+ };
+
+
+ //Store a reference to each instance - allowing us to globally resize chart instances on window resize.
+ //Destroy method on the chart will remove the instance of the chart from this reference.
+ Chart.instances = {};
+
+ Chart.Type = function(data,options,chart){
+ this.options = options;
+ this.chart = chart;
+ this.id = uid();
+ //Add the chart instance to the global namespace
+ Chart.instances[this.id] = this;
+
+ // Initialize is always called when a chart type is created
+ // By default it is a no op, but it should be extended
+ if (options.responsive){
+ this.resize();
+ }
+ this.initialize.call(this,data);
+ };
+
+ //Core methods that'll be a part of every chart type
+ extend(Chart.Type.prototype,{
+ initialize : function(){return this;},
+ clear : function(){
+ clear(this.chart);
+ return this;
+ },
+ stop : function(){
+ // Stops any current animation loop occuring
+ helpers.cancelAnimFrame.call(root, this.animationFrame);
+ return this;
+ },
+ resize : function(callback){
+ this.stop();
+ var canvas = this.chart.canvas,
+ newWidth = getMaximumWidth(this.chart.canvas),
+ newHeight = this.options.maintainAspectRatio ? newWidth / this.chart.aspectRatio : getMaximumHeight(this.chart.canvas);
+
+ canvas.width = this.chart.width = newWidth;
+ canvas.height = this.chart.height = newHeight;
+
+ retinaScale(this.chart);
+
+ if (typeof callback === "function"){
+ callback.apply(this, Array.prototype.slice.call(arguments, 1));
+ }
+ return this;
+ },
+ reflow : noop,
+ render : function(reflow){
+ if (reflow){
+ this.reflow();
+ }
+ if (this.options.animation && !reflow){
+ helpers.animationLoop(
+ this.draw,
+ this.options.animationSteps,
+ this.options.animationEasing,
+ this.options.onAnimationProgress,
+ this.options.onAnimationComplete,
+ this
+ );
+ }
+ else{
+ this.draw();
+ this.options.onAnimationComplete.call(this);
+ }
+ return this;
+ },
+ generateLegend : function(){
+ return template(this.options.legendTemplate,this);
+ },
+ destroy : function(){
+ this.clear();
+ unbindEvents(this, this.events);
+ delete Chart.instances[this.id];
+ },
+ showTooltip : function(ChartElements, forceRedraw){
+ // Only redraw the chart if we've actually changed what we're hovering on.
+ if (typeof this.activeElements === 'undefined') this.activeElements = [];
+
+ var isChanged = (function(Elements){
+ var changed = false;
+
+ if (Elements.length !== this.activeElements.length){
+ changed = true;
+ return changed;
+ }
+
+ each(Elements, function(element, index){
+ if (element !== this.activeElements[index]){
+ changed = true;
+ }
+ }, this);
+ return changed;
+ }).call(this, ChartElements);
+
+ if (!isChanged && !forceRedraw){
+ return;
+ }
+ else{
+ this.activeElements = ChartElements;
+ }
+ this.draw();
+ if (ChartElements.length > 0){
+ // If we have multiple datasets, show a MultiTooltip for all of the data points at that index
+ if (this.datasets && this.datasets.length > 1) {
+ var dataArray,
+ dataIndex;
+
+ for (var i = this.datasets.length - 1; i >= 0; i--) {
+ dataArray = this.datasets[i].points || this.datasets[i].bars || this.datasets[i].segments;
+ dataIndex = indexOf(dataArray, ChartElements[0]);
+ if (dataIndex !== -1){
+ break;
+ }
+ }
+ var tooltipLabels = [],
+ tooltipColors = [],
+ medianPosition = (function(index) {
+
+ // Get all the points at that particular index
+ var Elements = [],
+ dataCollection,
+ xPositions = [],
+ yPositions = [],
+ xMax,
+ yMax,
+ xMin,
+ yMin;
+ helpers.each(this.datasets, function(dataset){
+ dataCollection = dataset.points || dataset.bars || dataset.segments;
+ if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()){
+ Elements.push(dataCollection[dataIndex]);
+ }
+ });
+
+ helpers.each(Elements, function(element) {
+ xPositions.push(element.x);
+ yPositions.push(element.y);
+
+
+ //Include any colour information about the element
+ tooltipLabels.push(helpers.template(this.options.multiTooltipTemplate, element));
+ tooltipColors.push({
+ fill: element._saved.fillColor || element.fillColor,
+ stroke: element._saved.strokeColor || element.strokeColor
+ });
+
+ }, this);
+
+ yMin = min(yPositions);
+ yMax = max(yPositions);
+
+ xMin = min(xPositions);
+ xMax = max(xPositions);
+
+ return {
+ x: (xMin > this.chart.width/2) ? xMin : xMax,
+ y: (yMin + yMax)/2
+ };
+ }).call(this, dataIndex);
+
+ new Chart.MultiTooltip({
+ x: medianPosition.x,
+ y: medianPosition.y,
+ xPadding: this.options.tooltipXPadding,
+ yPadding: this.options.tooltipYPadding,
+ xOffset: this.options.tooltipXOffset,
+ fillColor: this.options.tooltipFillColor,
+ textColor: this.options.tooltipFontColor,
+ fontFamily: this.options.tooltipFontFamily,
+ fontStyle: this.options.tooltipFontStyle,
+ fontSize: this.options.tooltipFontSize,
+ titleTextColor: this.options.tooltipTitleFontColor,
+ titleFontFamily: this.options.tooltipTitleFontFamily,
+ titleFontStyle: this.options.tooltipTitleFontStyle,
+ titleFontSize: this.options.tooltipTitleFontSize,
+ cornerRadius: this.options.tooltipCornerRadius,
+ labels: tooltipLabels,
+ legendColors: tooltipColors,
+ legendColorBackground : this.options.multiTooltipKeyBackground,
+ title: ChartElements[0].label,
+ chart: this.chart,
+ ctx: this.chart.ctx
+ }).draw();
+
+ } else {
+ each(ChartElements, function(Element) {
+ var tooltipPosition = Element.tooltipPosition();
+ new Chart.Tooltip({
+ x: Math.round(tooltipPosition.x),
+ y: Math.round(tooltipPosition.y),
+ xPadding: this.options.tooltipXPadding,
+ yPadding: this.options.tooltipYPadding,
+ fillColor: this.options.tooltipFillColor,
+ textColor: this.options.tooltipFontColor,
+ fontFamily: this.options.tooltipFontFamily,
+ fontStyle: this.options.tooltipFontStyle,
+ fontSize: this.options.tooltipFontSize,
+ caretHeight: this.options.tooltipCaretSize,
+ cornerRadius: this.options.tooltipCornerRadius,
+ text: template(this.options.tooltipTemplate, Element),
+ chart: this.chart
+ }).draw();
+ }, this);
+ }
+ }
+ return this;
+ },
+ toBase64Image : function(){
+ return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments);
+ }
+ });
+
+ Chart.Type.extend = function(extensions){
+
+ var parent = this;
+
+ var ChartType = function(){
+ return parent.apply(this,arguments);
+ };
+
+ //Copy the prototype object of the this class
+ ChartType.prototype = clone(parent.prototype);
+ //Now overwrite some of the properties in the base class with the new extensions
+ extend(ChartType.prototype, extensions);
+
+ ChartType.extend = Chart.Type.extend;
+
+ if (extensions.name || parent.prototype.name){
+
+ var chartName = extensions.name || parent.prototype.name;
+ //Assign any potential default values of the new chart type
+
+ //If none are defined, we'll use a clone of the chart type this is being extended from.
+ //I.e. if we extend a line chart, we'll use the defaults from the line chart if our new chart
+ //doesn't define some defaults of their own.
+
+ var baseDefaults = (Chart.defaults[parent.prototype.name]) ? clone(Chart.defaults[parent.prototype.name]) : {};
+
+ Chart.defaults[chartName] = extend(baseDefaults,extensions.defaults);
+
+ Chart.types[chartName] = ChartType;
+
+ //Register this new chart type in the Chart prototype
+ Chart.prototype[chartName] = function(data,options){
+ var config = merge(Chart.defaults.global, Chart.defaults[chartName], options || {});
+ return new ChartType(data,config,this);
+ };
+ } else{
+ warn("Name not provided for this chart, so it hasn't been registered");
+ }
+ return parent;
+ };
+
+ Chart.Element = function(configuration){
+ extend(this,configuration);
+ this.initialize.apply(this,arguments);
+ this.save();
+ };
+ extend(Chart.Element.prototype,{
+ initialize : function(){},
+ restore : function(props){
+ if (!props){
+ extend(this,this._saved);
+ } else {
+ each(props,function(key){
+ this[key] = this._saved[key];
+ },this);
+ }
+ return this;
+ },
+ save : function(){
+ this._saved = clone(this);
+ delete this._saved._saved;
+ return this;
+ },
+ update : function(newProps){
+ each(newProps,function(value,key){
+ this._saved[key] = this[key];
+ this[key] = value;
+ },this);
+ return this;
+ },
+ transition : function(props,ease){
+ each(props,function(value,key){
+ this[key] = ((value - this._saved[key]) * ease) + this._saved[key];
+ },this);
+ return this;
+ },
+ tooltipPosition : function(){
+ return {
+ x : this.x,
+ y : this.y
+ };
+ },
+ hasValue: function(){
+ return isNumber(this.value);
+ }
+ });
+
+ Chart.Element.extend = inherits;
+
+
+ Chart.Point = Chart.Element.extend({
+ display: true,
+ inRange: function(chartX,chartY){
+ var hitDetectionRange = this.hitDetectionRadius + this.radius;
+ return ((Math.pow(chartX-this.x, 2)+Math.pow(chartY-this.y, 2)) < Math.pow(hitDetectionRange,2));
+ },
+ draw : function(){
+ if (this.display){
+ var ctx = this.ctx;
+ ctx.beginPath();
+
+ ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2);
+ ctx.closePath();
+
+ ctx.strokeStyle = this.strokeColor;
+ ctx.lineWidth = this.strokeWidth;
+
+ ctx.fillStyle = this.fillColor;
+
+ ctx.fill();
+ ctx.stroke();
+ }
+
+
+ //Quick debug for bezier curve splining
+ //Highlights control points and the line between them.
+ //Handy for dev - stripped in the min version.
+
+ // ctx.save();
+ // ctx.fillStyle = "black";
+ // ctx.strokeStyle = "black"
+ // ctx.beginPath();
+ // ctx.arc(this.controlPoints.inner.x,this.controlPoints.inner.y, 2, 0, Math.PI*2);
+ // ctx.fill();
+
+ // ctx.beginPath();
+ // ctx.arc(this.controlPoints.outer.x,this.controlPoints.outer.y, 2, 0, Math.PI*2);
+ // ctx.fill();
+
+ // ctx.moveTo(this.controlPoints.inner.x,this.controlPoints.inner.y);
+ // ctx.lineTo(this.x, this.y);
+ // ctx.lineTo(this.controlPoints.outer.x,this.controlPoints.outer.y);
+ // ctx.stroke();
+
+ // ctx.restore();
+
+
+
+ }
+ });
+
+ Chart.Arc = Chart.Element.extend({
+ inRange : function(chartX,chartY){
+
+ var pointRelativePosition = helpers.getAngleFromPoint(this, {
+ x: chartX,
+ y: chartY
+ });
+
+ //Check if within the range of the open/close angle
+ var betweenAngles = (pointRelativePosition.angle >= this.startAngle && pointRelativePosition.angle <= this.endAngle),
+ withinRadius = (pointRelativePosition.distance >= this.innerRadius && pointRelativePosition.distance <= this.outerRadius);
+
+ return (betweenAngles && withinRadius);
+ //Ensure within the outside of the arc centre, but inside arc outer
+ },
+ tooltipPosition : function(){
+ var centreAngle = this.startAngle + ((this.endAngle - this.startAngle) / 2),
+ rangeFromCentre = (this.outerRadius - this.innerRadius) / 2 + this.innerRadius;
+ return {
+ x : this.x + (Math.cos(centreAngle) * rangeFromCentre),
+ y : this.y + (Math.sin(centreAngle) * rangeFromCentre)
+ };
+ },
+ draw : function(animationPercent){
+
+ var easingDecimal = animationPercent || 1;
+
+ var ctx = this.ctx;
+
+ ctx.beginPath();
+
+ ctx.arc(this.x, this.y, this.outerRadius, this.startAngle, this.endAngle);
+
+ ctx.arc(this.x, this.y, this.innerRadius, this.endAngle, this.startAngle, true);
+
+ ctx.closePath();
+ ctx.strokeStyle = this.strokeColor;
+ ctx.lineWidth = this.strokeWidth;
+
+ ctx.fillStyle = this.fillColor;
+
+ ctx.fill();
+ ctx.lineJoin = 'bevel';
+
+ if (this.showStroke){
+ ctx.stroke();
+ }
+ }
+ });
+
+ Chart.Rectangle = Chart.Element.extend({
+ draw : function(){
+ var ctx = this.ctx,
+ halfWidth = this.width/2,
+ leftX = this.x - halfWidth,
+ rightX = this.x + halfWidth,
+ top = this.base - (this.base - this.y),
+ halfStroke = this.strokeWidth / 2;
+
+ // Canvas doesn't allow us to stroke inside the width so we can
+ // adjust the sizes to fit if we're setting a stroke on the line
+ if (this.showStroke){
+ leftX += halfStroke;
+ rightX -= halfStroke;
+ top += halfStroke;
+ }
+
+ ctx.beginPath();
+
+ ctx.fillStyle = this.fillColor;
+ ctx.strokeStyle = this.strokeColor;
+ ctx.lineWidth = this.strokeWidth;
+
+ // It'd be nice to keep this class totally generic to any rectangle
+ // and simply specify which border to miss out.
+ ctx.moveTo(leftX, this.base);
+ ctx.lineTo(leftX, top);
+ ctx.lineTo(rightX, top);
+ ctx.lineTo(rightX, this.base);
+ ctx.fill();
+ if (this.showStroke){
+ ctx.stroke();
+ }
+ },
+ height : function(){
+ return this.base - this.y;
+ },
+ inRange : function(chartX,chartY){
+ return (chartX >= this.x - this.width/2 && chartX <= this.x + this.width/2) && (chartY >= this.y && chartY <= this.base);
+ }
+ });
+
+ Chart.Tooltip = Chart.Element.extend({
+ draw : function(){
+
+ var ctx = this.chart.ctx;
+
+ ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
+
+ this.xAlign = "center";
+ this.yAlign = "above";
+
+ //Distance between the actual element.y position and the start of the tooltip caret
+ var caretPadding = 2;
+
+ var tooltipWidth = ctx.measureText(this.text).width + 2*this.xPadding,
+ tooltipRectHeight = this.fontSize + 2*this.yPadding,
+ tooltipHeight = tooltipRectHeight + this.caretHeight + caretPadding;
+
+ if (this.x + tooltipWidth/2 >this.chart.width){
+ this.xAlign = "left";
+ } else if (this.x - tooltipWidth/2 < 0){
+ this.xAlign = "right";
+ }
+
+ if (this.y - tooltipHeight < 0){
+ this.yAlign = "below";
+ }
+
+
+ var tooltipX = this.x - tooltipWidth/2,
+ tooltipY = this.y - tooltipHeight;
+
+ ctx.fillStyle = this.fillColor;
+
+ switch(this.yAlign)
+ {
+ case "above":
+ //Draw a caret above the x/y
+ ctx.beginPath();
+ ctx.moveTo(this.x,this.y - caretPadding);
+ ctx.lineTo(this.x + this.caretHeight, this.y - (caretPadding + this.caretHeight));
+ ctx.lineTo(this.x - this.caretHeight, this.y - (caretPadding + this.caretHeight));
+ ctx.closePath();
+ ctx.fill();
+ break;
+ case "below":
+ tooltipY = this.y + caretPadding + this.caretHeight;
+ //Draw a caret below the x/y
+ ctx.beginPath();
+ ctx.moveTo(this.x, this.y + caretPadding);
+ ctx.lineTo(this.x + this.caretHeight, this.y + caretPadding + this.caretHeight);
+ ctx.lineTo(this.x - this.caretHeight, this.y + caretPadding + this.caretHeight);
+ ctx.closePath();
+ ctx.fill();
+ break;
+ }
+
+ switch(this.xAlign)
+ {
+ case "left":
+ tooltipX = this.x - tooltipWidth + (this.cornerRadius + this.caretHeight);
+ break;
+ case "right":
+ tooltipX = this.x - (this.cornerRadius + this.caretHeight);
+ break;
+ }
+
+ drawRoundedRectangle(ctx,tooltipX,tooltipY,tooltipWidth,tooltipRectHeight,this.cornerRadius);
+
+ ctx.fill();
+
+ ctx.fillStyle = this.textColor;
+ ctx.textAlign = "center";
+ ctx.textBaseline = "middle";
+ ctx.fillText(this.text, tooltipX + tooltipWidth/2, tooltipY + tooltipRectHeight/2);
+ }
+ });
+
+ Chart.MultiTooltip = Chart.Element.extend({
+ initialize : function(){
+ this.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
+
+ this.titleFont = fontString(this.titleFontSize,this.titleFontStyle,this.titleFontFamily);
+
+ this.height = (this.labels.length * this.fontSize) + ((this.labels.length-1) * (this.fontSize/2)) + (this.yPadding*2) + this.titleFontSize *1.5;
+
+ this.ctx.font = this.titleFont;
+
+ var titleWidth = this.ctx.measureText(this.title).width,
+ //Label has a legend square as well so account for this.
+ labelWidth = longestText(this.ctx,this.font,this.labels) + this.fontSize + 3,
+ longestTextWidth = max([labelWidth,titleWidth]);
+
+ this.width = longestTextWidth + (this.xPadding*2);
+
+
+ var halfHeight = this.height/2;
+
+ //Check to ensure the height will fit on the canvas
+ //The three is to buffer form the very
+ if (this.y - halfHeight < 0 ){
+ this.y = halfHeight;
+ } else if (this.y + halfHeight > this.chart.height){
+ this.y = this.chart.height - halfHeight;
+ }
+
+ //Decide whether to align left or right based on position on canvas
+ if (this.x > this.chart.width/2){
+ this.x -= this.xOffset + this.width;
+ } else {
+ this.x += this.xOffset;
+ }
+
+
+ },
+ getLineHeight : function(index){
+ var baseLineHeight = this.y - (this.height/2) + this.yPadding,
+ afterTitleIndex = index-1;
+
+ //If the index is zero, we're getting the title
+ if (index === 0){
+ return baseLineHeight + this.titleFontSize/2;
+ } else{
+ return baseLineHeight + ((this.fontSize*1.5*afterTitleIndex) + this.fontSize/2) + this.titleFontSize * 1.5;
+ }
+
+ },
+ draw : function(){
+ drawRoundedRectangle(this.ctx,this.x,this.y - this.height/2,this.width,this.height,this.cornerRadius);
+ var ctx = this.ctx;
+ ctx.fillStyle = this.fillColor;
+ ctx.fill();
+ ctx.closePath();
+
+ ctx.textAlign = "left";
+ ctx.textBaseline = "middle";
+ ctx.fillStyle = this.titleTextColor;
+ ctx.font = this.titleFont;
+
+ ctx.fillText(this.title,this.x + this.xPadding, this.getLineHeight(0));
+
+ ctx.font = this.font;
+ helpers.each(this.labels,function(label,index){
+ ctx.fillStyle = this.textColor;
+ ctx.fillText(label,this.x + this.xPadding + this.fontSize + 3, this.getLineHeight(index + 1));
+
+ //A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas)
+ //ctx.clearRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
+ //Instead we'll make a white filled block to put the legendColour palette over.
+
+ ctx.fillStyle = this.legendColorBackground;
+ ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
+
+ ctx.fillStyle = this.legendColors[index].fill;
+ ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
+
+
+ },this);
+ }
+ });
+
+ Chart.Scale = Chart.Element.extend({
+ initialize : function(){
+ this.fit();
+ },
+ buildYLabels : function(){
+ this.yLabels = [];
+
+ var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
+
+ for (var i=0; i<=this.steps; i++){
+ this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
+ }
+ this.yLabelWidth = (this.display && this.showLabels) ? longestText(this.ctx,this.font,this.yLabels) : 0;
+ },
+ addXLabel : function(label){
+ this.xLabels.push(label);
+ this.valuesCount++;
+ this.fit();
+ },
+ removeXLabel : function(){
+ this.xLabels.shift();
+ this.valuesCount--;
+ this.fit();
+ },
+ // Fitting loop to rotate x Labels and figure out what fits there, and also calculate how many Y steps to use
+ fit: function(){
+ // First we need the width of the yLabels, assuming the xLabels aren't rotated
+
+ // To do that we need the base line at the top and base of the chart, assuming there is no x label rotation
+ this.startPoint = (this.display) ? this.fontSize : 0;
+ this.endPoint = (this.display) ? this.height - (this.fontSize * 1.5) - 5 : this.height; // -5 to pad labels
+
+ // Apply padding settings to the start and end point.
+ this.startPoint += this.padding;
+ this.endPoint -= this.padding;
+
+ // Cache the starting height, so can determine if we need to recalculate the scale yAxis
+ var cachedHeight = this.endPoint - this.startPoint,
+ cachedYLabelWidth;
+
+ // Build the current yLabels so we have an idea of what size they'll be to start
+ /*
+ * This sets what is returned from calculateScaleRange as static properties of this class:
+ *
+ this.steps;
+ this.stepValue;
+ this.min;
+ this.max;
+ *
+ */
+ this.calculateYRange(cachedHeight);
+
+ // With these properties set we can now build the array of yLabels
+ // and also the width of the largest yLabel
+ this.buildYLabels();
+
+ this.calculateXLabelRotation();
+
+ while((cachedHeight > this.endPoint - this.startPoint)){
+ cachedHeight = this.endPoint - this.startPoint;
+ cachedYLabelWidth = this.yLabelWidth;
+
+ this.calculateYRange(cachedHeight);
+ this.buildYLabels();
+
+ // Only go through the xLabel loop again if the yLabel width has changed
+ if (cachedYLabelWidth < this.yLabelWidth){
+ this.calculateXLabelRotation();
+ }
+ }
+
+ },
+ calculateXLabelRotation : function(){
+ //Get the width of each grid by calculating the difference
+ //between x offsets between 0 and 1.
+
+ this.ctx.font = this.font;
+
+ var firstWidth = this.ctx.measureText(this.xLabels[0]).width,
+ lastWidth = this.ctx.measureText(this.xLabels[this.xLabels.length - 1]).width,
+ firstRotated,
+ lastRotated;
+
+
+ this.xScalePaddingRight = lastWidth/2 + 3;
+ this.xScalePaddingLeft = (firstWidth/2 > this.yLabelWidth + 10) ? firstWidth/2 : this.yLabelWidth + 10;
+
+ this.xLabelRotation = 0;
+ if (this.display){
+ var originalLabelWidth = longestText(this.ctx,this.font,this.xLabels),
+ cosRotation,
+ firstRotatedWidth;
+ this.xLabelWidth = originalLabelWidth;
+ //Allow 3 pixels x2 padding either side for label readability
+ var xGridWidth = Math.floor(this.calculateX(1) - this.calculateX(0)) - 6;
+
+ //Max label rotate should be 90 - also act as a loop counter
+ while ((this.xLabelWidth > xGridWidth && this.xLabelRotation === 0) || (this.xLabelWidth > xGridWidth && this.xLabelRotation <= 90 && this.xLabelRotation > 0)){
+ cosRotation = Math.cos(toRadians(this.xLabelRotation));
+
+ firstRotated = cosRotation * firstWidth;
+ lastRotated = cosRotation * lastWidth;
+
+ // We're right aligning the text now.
+ if (firstRotated + this.fontSize / 2 > this.yLabelWidth + 8){
+ this.xScalePaddingLeft = firstRotated + this.fontSize / 2;
+ }
+ this.xScalePaddingRight = this.fontSize/2;
+
+
+ this.xLabelRotation++;
+ this.xLabelWidth = cosRotation * originalLabelWidth;
+
+ }
+ if (this.xLabelRotation > 0){
+ this.endPoint -= Math.sin(toRadians(this.xLabelRotation))*originalLabelWidth + 3;
+ }
+ }
+ else{
+ this.xLabelWidth = 0;
+ this.xScalePaddingRight = this.padding;
+ this.xScalePaddingLeft = this.padding;
+ }
+
+ },
+ // Needs to be overidden in each Chart type
+ // Otherwise we need to pass all the data into the scale class
+ calculateYRange: noop,
+ drawingArea: function(){
+ return this.startPoint - this.endPoint;
+ },
+ calculateY : function(value){
+ var scalingFactor = this.drawingArea() / (this.min - this.max);
+ return this.endPoint - (scalingFactor * (value - this.min));
+ },
+ calculateX : function(index){
+ var isRotated = (this.xLabelRotation > 0),
+ // innerWidth = (this.offsetGridLines) ? this.width - offsetLeft - this.padding : this.width - (offsetLeft + halfLabelWidth * 2) - this.padding,
+ innerWidth = this.width - (this.xScalePaddingLeft + this.xScalePaddingRight),
+ valueWidth = innerWidth/(this.valuesCount - ((this.offsetGridLines) ? 0 : 1)),
+ valueOffset = (valueWidth * index) + this.xScalePaddingLeft;
+
+ if (this.offsetGridLines){
+ valueOffset += (valueWidth/2);
+ }
+
+ return Math.round(valueOffset);
+ },
+ update : function(newProps){
+ helpers.extend(this, newProps);
+ this.fit();
+ },
+ draw : function(){
+ var ctx = this.ctx,
+ yLabelGap = (this.endPoint - this.startPoint) / this.steps,
+ xStart = Math.round(this.xScalePaddingLeft);
+ if (this.display){
+ ctx.fillStyle = this.textColor;
+ ctx.font = this.font;
+ each(this.yLabels,function(labelString,index){
+ var yLabelCenter = this.endPoint - (yLabelGap * index),
+ linePositionY = Math.round(yLabelCenter);
+
+ ctx.textAlign = "right";
+ ctx.textBaseline = "middle";
+ if (this.showLabels){
+ ctx.fillText(labelString,xStart - 10,yLabelCenter);
+ }
+ ctx.beginPath();
+ if (index > 0){
+ // This is a grid line in the centre, so drop that
+ ctx.lineWidth = this.gridLineWidth;
+ ctx.strokeStyle = this.gridLineColor;
+ } else {
+ // This is the first line on the scale
+ ctx.lineWidth = this.lineWidth;
+ ctx.strokeStyle = this.lineColor;
+ }
+
+ linePositionY += helpers.aliasPixel(ctx.lineWidth);
+
+ ctx.moveTo(xStart, linePositionY);
+ ctx.lineTo(this.width, linePositionY);
+ ctx.stroke();
+ ctx.closePath();
+
+ ctx.lineWidth = this.lineWidth;
+ ctx.strokeStyle = this.lineColor;
+ ctx.beginPath();
+ ctx.moveTo(xStart - 5, linePositionY);
+ ctx.lineTo(xStart, linePositionY);
+ ctx.stroke();
+ ctx.closePath();
+
+ },this);
+
+ each(this.xLabels,function(label,index){
+ var xPos = this.calculateX(index) + aliasPixel(this.lineWidth),
+ // Check to see if line/bar here and decide where to place the line
+ linePos = this.calculateX(index - (this.offsetGridLines ? 0.5 : 0)) + aliasPixel(this.lineWidth),
+ isRotated = (this.xLabelRotation > 0);
+
+ ctx.beginPath();
+
+ if (index > 0){
+ // This is a grid line in the centre, so drop that
+ ctx.lineWidth = this.gridLineWidth;
+ ctx.strokeStyle = this.gridLineColor;
+ } else {
+ // This is the first line on the scale
+ ctx.lineWidth = this.lineWidth;
+ ctx.strokeStyle = this.lineColor;
+ }
+ ctx.moveTo(linePos,this.endPoint);
+ ctx.lineTo(linePos,this.startPoint - 3);
+ ctx.stroke();
+ ctx.closePath();
+
+
+ ctx.lineWidth = this.lineWidth;
+ ctx.strokeStyle = this.lineColor;
+
+
+ // Small lines at the bottom of the base grid line
+ ctx.beginPath();
+ ctx.moveTo(linePos,this.endPoint);
+ ctx.lineTo(linePos,this.endPoint + 5);
+ ctx.stroke();
+ ctx.closePath();
+
+ ctx.save();
+ ctx.translate(xPos,(isRotated) ? this.endPoint + 12 : this.endPoint + 8);
+ ctx.rotate(toRadians(this.xLabelRotation)*-1);
+ ctx.font = this.font;
+ ctx.textAlign = (isRotated) ? "right" : "center";
+ ctx.textBaseline = (isRotated) ? "middle" : "top";
+ ctx.fillText(label, 0, 0);
+ ctx.restore();
+ },this);
+
+ }
+ }
+
+ });
+
+ Chart.RadialScale = Chart.Element.extend({
+ initialize: function(){
+ this.size = min([this.height, this.width]);
+ this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
+ },
+ calculateCenterOffset: function(value){
+ // Take into account half font size + the yPadding of the top value
+ var scalingFactor = this.drawingArea / (this.max - this.min);
+
+ return (value - this.min) * scalingFactor;
+ },
+ update : function(){
+ if (!this.lineArc){
+ this.setScaleSize();
+ } else {
+ this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
+ }
+ this.buildYLabels();
+ },
+ buildYLabels: function(){
+ this.yLabels = [];
+
+ var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
+
+ for (var i=0; i<=this.steps; i++){
+ this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
+ }
+ },
+ getCircumference : function(){
+ return ((Math.PI*2) / this.valuesCount);
+ },
+ setScaleSize: function(){
+ /*
+ * Right, this is really confusing and there is a lot of maths going on here
+ * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
+ *
+ * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
+ *
+ * Solution:
+ *
+ * We assume the radius of the polygon is half the size of the canvas at first
+ * at each index we check if the text overlaps.
+ *
+ * Where it does, we store that angle and that index.
+ *
+ * After finding the largest index and angle we calculate how much we need to remove
+ * from the shape radius to move the point inwards by that x.
+ *
+ * We average the left and right distances to get the maximum shape radius that can fit in the box
+ * along with labels.
+ *
+ * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
+ * on each side, removing that from the size, halving it and adding the left x protrusion width.
+ *
+ * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
+ * and position it in the most space efficient manner
+ *
+ * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
+ */
+
+
+ // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
+ // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
+ var largestPossibleRadius = min([(this.height/2 - this.pointLabelFontSize - 5), this.width/2]),
+ pointPosition,
+ i,
+ textWidth,
+ halfTextWidth,
+ furthestRight = this.width,
+ furthestRightIndex,
+ furthestRightAngle,
+ furthestLeft = 0,
+ furthestLeftIndex,
+ furthestLeftAngle,
+ xProtrusionLeft,
+ xProtrusionRight,
+ radiusReductionRight,
+ radiusReductionLeft,
+ maxWidthRadius;
+ this.ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
+ for (i=0;i furthestRight) {
+ furthestRight = pointPosition.x + halfTextWidth;
+ furthestRightIndex = i;
+ }
+ if (pointPosition.x - halfTextWidth < furthestLeft) {
+ furthestLeft = pointPosition.x - halfTextWidth;
+ furthestLeftIndex = i;
+ }
+ }
+ else if (i < this.valuesCount/2) {
+ // Less than half the values means we'll left align the text
+ if (pointPosition.x + textWidth > furthestRight) {
+ furthestRight = pointPosition.x + textWidth;
+ furthestRightIndex = i;
+ }
+ }
+ else if (i > this.valuesCount/2){
+ // More than half the values means we'll right align the text
+ if (pointPosition.x - textWidth < furthestLeft) {
+ furthestLeft = pointPosition.x - textWidth;
+ furthestLeftIndex = i;
+ }
+ }
+ }
+
+ xProtrusionLeft = furthestLeft;
+
+ xProtrusionRight = Math.ceil(furthestRight - this.width);
+
+ furthestRightAngle = this.getIndexAngle(furthestRightIndex);
+
+ furthestLeftAngle = this.getIndexAngle(furthestLeftIndex);
+
+ radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI/2);
+
+ radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI/2);
+
+ // Ensure we actually need to reduce the size of the chart
+ radiusReductionRight = (isNumber(radiusReductionRight)) ? radiusReductionRight : 0;
+ radiusReductionLeft = (isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0;
+
+ this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight)/2;
+
+ //this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2])
+ this.setCenterPoint(radiusReductionLeft, radiusReductionRight);
+
+ },
+ setCenterPoint: function(leftMovement, rightMovement){
+
+ var maxRight = this.width - rightMovement - this.drawingArea,
+ maxLeft = leftMovement + this.drawingArea;
+
+ this.xCenter = (maxLeft + maxRight)/2;
+ // Always vertically in the centre as the text height doesn't change
+ this.yCenter = (this.height/2);
+ },
+
+ getIndexAngle : function(index){
+ var angleMultiplier = (Math.PI * 2) / this.valuesCount;
+ // Start from the top instead of right, so remove a quarter of the circle
+
+ return index * angleMultiplier - (Math.PI/2);
+ },
+ getPointPosition : function(index, distanceFromCenter){
+ var thisAngle = this.getIndexAngle(index);
+ return {
+ x : (Math.cos(thisAngle) * distanceFromCenter) + this.xCenter,
+ y : (Math.sin(thisAngle) * distanceFromCenter) + this.yCenter
+ };
+ },
+ draw: function(){
+ if (this.display){
+ var ctx = this.ctx;
+ each(this.yLabels, function(label, index){
+ // Don't draw a centre value
+ if (index > 0){
+ var yCenterOffset = index * (this.drawingArea/this.steps),
+ yHeight = this.yCenter - yCenterOffset,
+ pointPosition;
+
+ // Draw circular lines around the scale
+ if (this.lineWidth > 0){
+ ctx.strokeStyle = this.lineColor;
+ ctx.lineWidth = this.lineWidth;
+
+ if(this.lineArc){
+ ctx.beginPath();
+ ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI*2);
+ ctx.closePath();
+ ctx.stroke();
+ } else{
+ ctx.beginPath();
+ for (var i=0;i= 0; i--) {
+ if (this.angleLineWidth > 0){
+ var outerPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max));
+ ctx.beginPath();
+ ctx.moveTo(this.xCenter, this.yCenter);
+ ctx.lineTo(outerPosition.x, outerPosition.y);
+ ctx.stroke();
+ ctx.closePath();
+ }
+ // Extra 3px out for some label spacing
+ var pointLabelPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max) + 5);
+ ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
+ ctx.fillStyle = this.pointLabelFontColor;
+
+ var labelsCount = this.labels.length,
+ halfLabelsCount = this.labels.length/2,
+ quarterLabelsCount = halfLabelsCount/2,
+ upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount),
+ exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount);
+ if (i === 0){
+ ctx.textAlign = 'center';
+ } else if(i === halfLabelsCount){
+ ctx.textAlign = 'center';
+ } else if (i < halfLabelsCount){
+ ctx.textAlign = 'left';
+ } else {
+ ctx.textAlign = 'right';
+ }
+
+ // Set the correct text baseline based on outer positioning
+ if (exactQuarter){
+ ctx.textBaseline = 'middle';
+ } else if (upperHalf){
+ ctx.textBaseline = 'bottom';
+ } else {
+ ctx.textBaseline = 'top';
+ }
+
+ ctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y);
+ }
+ }
+ }
+ }
+ });
+
+ // Attach global event to resize each chart instance when the browser resizes
+ helpers.addEvent(window, "resize", (function(){
+ // Basic debounce of resize function so it doesn't hurt performance when resizing browser.
+ var timeout;
+ return function(){
+ clearTimeout(timeout);
+ timeout = setTimeout(function(){
+ each(Chart.instances,function(instance){
+ // If the responsive flag is set in the chart instance config
+ // Cascade the resize event down to the chart.
+ if (instance.options.responsive){
+ instance.resize(instance.render, true);
+ }
+ });
+ }, 50);
+ };
+ })());
+
+
+ if (amd) {
+ define(function(){
+ return Chart;
+ });
+ } else if (typeof module === 'object' && module.exports) {
+ module.exports = Chart;
+ }
+
+ root.Chart = Chart;
+
+ Chart.noConflict = function(){
+ root.Chart = previous;
+ return Chart;
+ };
+
+}).call(this);
+
+(function(){
+ "use strict";
+
+ var root = this,
+ Chart = root.Chart,
+ helpers = Chart.helpers;
+
+
+ var defaultConfig = {
+ //Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
+ scaleBeginAtZero : true,
+
+ //Boolean - Whether grid lines are shown across the chart
+ scaleShowGridLines : true,
+
+ //String - Colour of the grid lines
+ scaleGridLineColor : "rgba(0,0,0,.05)",
+
+ //Number - Width of the grid lines
+ scaleGridLineWidth : 1,
+
+ //Boolean - If there is a stroke on each bar
+ barShowStroke : true,
+
+ //Number - Pixel width of the bar stroke
+ barStrokeWidth : 2,
+
+ //Number - Spacing between each of the X value sets
+ barValueSpacing : 5,
+
+ //Number - Spacing between data sets within X values
+ barDatasetSpacing : 1,
+
+ //String - A legend template
+ legendTemplate : "
How to participate effectively in open source software development.
+
+
+
+
Dates: Mon, Jul 19 - Fri, Jul 23
+
+
+
+
+
+
+
+
+
+
Learning Outcomes
+
+
+
+
Use and develop open source software appropriately
+
+
+
+
+
You can choose a suitable open source license for your projects.
+
You can create software projects that satisfy the three prime directives and thus can support an open source community.
+
You can interact with the open source community via appropriate questions and forums.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Readings
+
+
+
+
+
+
+
+
+
+
Guided tour of the open source software module
+
Why, what, how, and when you will learn about open source software.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Open Source Software
+
History of open source, licenses, prime directives, how to participate effectively
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Understanding Open Source Licensing
+
Short summary of copyright, copyleft, and the motivation for open source licenses.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Choose a (open source) license
+
Simple overview of several open sources licenses and their implications.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
How to ask questions the smart way
+
The classic guide to how to participate in the open source community effectively
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
The Cathedral and the Bazaar
+
“I anatomize a successful open-source project, fetchmail, that
+was run as a deliberate test of some surprising theories about
+software engineering suggested by the history of Linux.”
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Experiential Learning
+
+
+
+
+
+
+
+
+
E07: Reflect on smart questions
+
Use Stack Overflow to find smart and not-smart questions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Assessments
+
+
+
+
+
+
+
+
+
A02: Assess your ability to write effectively about open source software
+
Can you explain what, why, where, and how?
+
+
+
+
+ Outcome(s) assessed:
+
+ Use and develop open source software appropriately
+
+
A03: Assess your ability to reason about ethical issues in software development
+
+
Overview
+
+
After taking part in this assessment, you will have a better understanding of whetheryou can effectively reason about ethical issues in software development through reference to the ACM Code of Ethics and other ethics literature.
+
+
Outcomes
+
+
The following outcomes will be assessed: Behave ethically as a software engineer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/morea/example-ethics/ethics.png b/morea/example-ethics/ethics.png
new file mode 100644
index 0000000..819a649
Binary files /dev/null and b/morea/example-ethics/ethics.png differ
diff --git a/morea/example-ethics/experience-se-ethics-codes.html b/morea/example-ethics/experience-se-ethics-codes.html
new file mode 100644
index 0000000..9f6b08d
--- /dev/null
+++ b/morea/example-ethics/experience-se-ethics-codes.html
@@ -0,0 +1,180 @@
+
+
+
+ ECON 627 Fall 2024 | E08: What are the professional codes of software engineering ethics?
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Each professional society of engineers adopts and enforces its own codes of practice,
+including codes for ethical practice; for computer science in general, the standard is the ACM Code of Ethics. ACM also publishes a code specific for software engineering: the Software Engineering Code of Ethics and Professional Practice.
+
+
These codes are not meant to serve as formal checklists or exhaustive accounts of how to
+be an ethical engineer in any given situation; the latter can only be determined through
+the engineer’s skillful, sincere and habitual practice of ethical reflection, analysis and
+deliberation in his or her professional life. Ethical codes are just one tool that help us to
+“develop an ‘eye’ for what would be appropriate” in various circumstances. As another
+scholar puts it, “The principles of the code do not constitute an algorithmic Turing
+machine that solves ethical problems. Professional judgments are still necessary.”
+
+
Good judgment, what Aristotle called phronesis or ‘practical wisdom’, is something that is
+acquired through a combination of experience, good habits, and conscious attention to
+ethical concerns. Ethical rules and codes are no substitute for it, nor are they meant to
+be. In fact, such codes can only be used effectively by persons with good judgment. The
+codes aim simply to express as fully as possible the scope of professional actions governed
+by ethics and to indicate the specific ethical duties that engineers of the highest
+professional standing expect their present and future colleagues to respect, and to fulfill.
+
+
In describing the character of exemplary software engineers, one scholar identifies seven
+qualities of ‘superprofessionals’ who embody the highest ideals of their field: A strong sense
+of individual responsibility, acute awareness of the world around them, brutal honesty, resilience
+under pressure, a heightened sense of fairness, attention to detail while maintaining perspective,
+and pragmatism in applying professional standards. Each of these qualities contributes
+to the practical wisdom that allows us to apply ethical codes intelligently and successfully.
+Yet “Superprofessionals behave ethically not because it’s prescribed by a code of conduct,
+but because not doing so would violate their personal professional standards.”
To reiterate: the codes do not “constitute an algorithmic Turing machine that solves ethical problems.” They are written by humans, and so they may themselves contain “bugs”. Take a moment to consider the potential shortcomings of these code. Are there ethical obligations you adhere to that are not listed in these codes? Do you find shortcomings with any of the ethical standards listed in the codes?
There is a second way in which we need to broaden our understanding of engineering
+ethics. Ethics is not just about avoiding harms, as a narrow focus on preventing
+catastrophic events might make us believe. Ethics is just as much about doing good.
+‘Doing good’ is not something that matters only to missionaries, social workers and
+philanthropists. To live a ‘good life’ is to make a positive contribution to the world
+through your existence, to be able to say at the end of your life that in your short time
+here, you made the world at least somewhat better than it would have been without you in
+it. This is also how we think about the lives of those who have left us: when we mourn
+our friends and loved ones, we comfort ourselves by remembering the unique comforts
+and joys they brought to our lives, and the lives of others; we remember the creative work
+they left behind, the problems they helped us solve, and the beautiful acts they performed,
+great and small. Could a life about which these things could not be said still be a good life?
+
+
If the good life requires making a positive contribution to the world in which others live,
+then it would be perverse if we accomplished none of that in our professional lives, where
+we spend many or most of our waking hours, and to which we devote a large proportion
+of our intellectual and creative energies. Excellent doctors contribute health and vitality
+to their patients and medical knowledge to their interns and colleagues; excellent
+professors cultivate knowledge, insight, skill and confidence in their students and contribute
+the benefits of their research to the wider community; excellent lawyers contribute balance,
+fairness and intellectual vigor to a larger system of justice.
+
+
Questions for discussion:
+
+
+
+
What sorts of things can excellent software engineers contribute to
+the good life?
+
+
+
What kinds of character traits, qualities, behaviors and/or habits do
+you think mark the kinds of people who tend to contribute most in these ways?
We noted above that failures of critical software systems can result in catastrophic loss
+of life or injury to the public. If such failures result, directly or indirectly, from software
+engineers’ choices to ignore their professional obligations, then these harms are clearly
+the consequences of unethical professional behavior. Those responsible each bear the
+moral weight of this avoidable human suffering, whether or not this also results in legal,
+criminal or professional punishment.
+
+
But what other kinds of harms do software engineers have an ethical duty to consider, and
+to try to prevent? Consider the following scenario:
+
+
Case Study 1: College for Sarah
+
+
+
Mike is a father of 3, and in order to save for their college educations, he has
+been working two jobs since his kids were born. His daughter Sarah has
+worked as hard as she can in high school to get high grades and SAT scores;
+as a result of her hard work she has been accepted to a prestigious IvyLeague
+college, and the deposit for her first year is due today. If the deposit
+goes unpaid, Sarah loses her spot in the freshman class. Mike paid the bill
+last week, but today he gets an email from the college admissions office
+saying that his payment was rejected for insufficient funds by his bank, and
+if he does not make the payment by the end of the day, Sarah will lose her
+place and be unable to attend in the Fall.
+
+
Panicked, Mike calls the bank – he
+had more than enough money in his savings to cover the bill, so he cannot
+understand what has happened. The bank confirms that his account had
+plenty of funds the day before, but cannot tell him why the funds are gone
+now or why the payment was rejected. They tell him there must be some
+‘software glitch’ involved and that they will open an investigation, but that
+it will take weeks to resolve. They will only restore the funds in his account
+once the investigation is completed and the cause found. Mike has no other
+way to get the money for the deposit on such short notice, and has to tell
+Sarah that he couldn’t cover the bill despite his earlier promise, and that she
+won’t be attending college in the Fall.
+
+
+
+
Questions for discussion:
+
+
+
Clearly harm has occurred, but does this scenario represent unethical behavior on the part of a software engineer? Come up with one example of a “software glitch” which does not result from unethical behavior on the part of a software engineer, and one that does. Justify your examples by reference to either the ACM Code of Ethics or the Software Engineering Code of Ethics and Professional Practice.
+
+
+
Case Study 2: Errand Whiz
+
+
+
Karen is a young lawyer at a prestigious firm with an incredibly hectic and
+stressful schedule, who needs to organize what little free time she has more
+efficiently. She has just downloaded a new app called Errand Whiz onto her
+iPhone; this app merges information from Karen’s to-do list, information on
+her purchasing habits from retail stores she shops at, and GPS software to
+produce the most efficient map and directions for running errands on her days
+off. Based on what it knows about what she needs to purchase and her general
+shopping habits, it tells Karen what locations of her favorite stores to visit on
+a given day, in what order and by what routes – this way she can get her
+errands done in the least amount of time, traveling the least number of miles.
+
+
To accomplish this, the app aggregates information not only about where she
+lives and shops, but also tracks what she typically buys in each store, how
+much she buys, what she typically pays for each item. This collected data is
+not stored on Karen’s phone, but on a separate server that the app links to
+when it needs to create a shopping map. The app encourages users to log in
+via Facebook, as the developers have made a deal with Facebook to sell this
+data to third-party advertisers, for the purpose of targeting Facebook ads to
+Karen and her friends.
+
+
+
Questions for discussion:
+
+
+
+
In what ways could Karen potentially be harmed by this app,
+depending on how it is designed and how her shopping data is handled and used?
+Identify a few harmful scenarios you can think of, and the types of harm she could
+suffer in each.
+
+
+
Which if any of these harms could result from ethical failings on
+the part of the people who developed Errand Whiz? How, specifically?
Ideally, these scenarios have helped to broaden your understanding of the ethical scope
+of software engineering. In considering and protecting the ‘health, safety and welfare’ of
+the public, we must not limit our thinking to those contexts in which our design choices
+or coding practices have the potential to cause someone’s death, or cause them direct
+physical injury. The harms that people can suffer as a result of failures by software engineers to
+consider their ethical obligation to the public are far more numerous and more complex than we
+might think.
Divide up into groups. The instructor will assign your group to argue as either “delete facebook” or “don’t delete facebook”.
+
+
For the first 10 minutes, review the Facebook Case Study and the articles in it to provide evidence for your position.
+
+
For the second 10 minutes, work together in your group to develop reasons for your assigned position to delete facebook or not delete facebook. Justify your position by reference to the ACM Code of Ethics or more general ethical principles (avoiding harm and doing good).
+
+
Once groups have developed their positions, we will have an in-class debate.
(Adapted with permission from “An Introduction to Software Engineering Ethics” by Shannon Vallor and Arvind Narayanan.)
+
+
Ethics in the broadest sense refers to the concern that humans have always had for
+figuring out how best to live. The philosopher Socrates is quoted as saying in 399 B.C.,
+“the most important thing is not life, but the good life.” We would all like to avoid a life
+that is shameful and sad, wholly lacking in achievement, love, kindness, beauty, pleasure
+or grace. Yet what is the best way to achieve the opposite of this – a life that is not only
+acceptable, but even excellent and worthy of admiration? This is the question that the
+study of ethics attempts to answer.
+
+
Today, the study of ethics can be found in many different places. As an academic field of
+study, it belongs primarily to the discipline of philosophy, where scholars teach and
+publish research about the nature and structure of ethical norms. In community life, ethics
+is pursued through diverse cultural, political and religious ideals and practices. On a
+personal level, it can be expressed in an individual’s self-reflection and continual strivings
+to become a better person. In work life, it is often formulated in formal codes or standards
+to which all members of a profession are held, such as those of medical ethics. Professional
+ethics is also taught in dedicated courses, such as business ethics. It can also be infused
+into courses such as this one.
+
+
What is ethics doing in a course for software engineers?
+
+
Like medical, legal and business ethics, engineering ethics is a well-developed area of
+professional ethics in the modern West. The first codes of engineering ethics were
+formally adopted by American engineering societies in 1912-1914. In 1946 the National
+Society of Professional Engineers (NSPE) adopted their first formal Canons of Ethics. In
+2000 ABET, the organization that accredits university programs and degrees in
+engineering, began to formally require the study of engineering ethics in all accredited
+programs: “Engineering programs must demonstrate that their graduates have an
+understanding of professional and ethical responsibility.” Professional engineers today,
+then, are expected to both learn about and live up to ethical standards as a condition of their
+membership in the profession.
+
+
But the average computer/software engineering student might still be confused about
+how and why this requirement should apply to them. Software engineering is a relatively
+young practice and compared with other engineering disciplines, its culture of
+professionalism is still developing. This is reinforced by the fact that most engineering
+ethics textbooks focus primarily on ethical issues faced by civil, mechanical or elecrical
+engineers. The classic case studies of engineering ethics depict catastrophic losses of life
+or injury as a result of ethical lapses in these fields: the Challenger explosion, the Ford
+Pinto fires, the Union Carbide/Bhopal disaster, the collapse of the Hyatt walkway in
+Kansas City. When we think about the engineer’s most basic ethical duty to “hold
+paramount the safety, health, and welfare of the public,”3 it is clear why these cases are
+chosen - they powerfully illustrate the importance of an engineer’s ethical obligations,
+and the potentially devastating consequences of failing to live up to them.
+
+
But software engineers build lines of code, not cars, rockets or bridges full of vulnerable
+human beings. Where is the comparison here? Well, one answer might already have
+occurred to you. How many cars or rockets are made today that do not depend upon
+critical software for their safe operation? How many bridges are built today without the
+use of sophisticated computer programs to calculate expected load, geophysical strain,
+material strength and design resilience? A failure of these critical software systems can
+result in death or grievous injury just as easily as a missing bolt or a poorly designed gas
+tank. This by itself is more than enough reason for software engineers to take seriously
+the ethics of their professional lives. Is it the only reason? What might be some others?
+
+
Consider the following:
+The software development and deployment process in the Internet era has some
+peculiarities that make the ethical issues for software engineers even more acute in some
+ways than for other types of engineers. First, the shortened lifecycle has weakened and in
+some cases obliterated software review by management and legal teams. In the extreme,
+for Web applications like Facebook, it is normal for individual engineers or small groups
+of engineers to code and deploy features directly, and indeed the culture takes pride in
+this. Even where more traditional development practices prevail, at least some
+deployments like bug fixes are shipped with only technical (and not ethical) oversight. At
+any rate, engineers at least retain the ability to deploy code directly to end users, an ability
+that can easily be abused.
+
+
All of this is in stark contrast to say, a civil engineering project with a years-long (or
+decades-long) lifecycle and multiple layers of oversight. Nor does such a project offer a
+malicious engineer any real means to obfuscate her output to sneak past standards and
+safety checks.
+
+
Second is the issue of scale, perhaps the defining feature of the software revolution.
+Typically the entire world is part of the addressable market. Of course, it is scale that has
+led to the potential for individual engineers to create great good, but with it naturally
+comes the ability to cause great harm, especially when combined with the first factor
+above.
+
+
+
+
Often, in today’s world, engineers
+must grapple with these questions instead of relying on management or anyone else.
+Finally, the lack of geographic constraints means that engineers are generally culturally
+unfamiliar with some or most of their users. The cost-cutting imperative often leaves little
+room for user studies or consultations with experts that would allow software
+development firms to acquire this familiarity. This leads to the potential for privacy
+violations, cultural offenses, and other such types of harm.
+
+
For example, people in many countries are notoriously sensitive to the representation of
+disputed border territories on maps. In one recent example, an error in Google maps led
+to Nicaragua dispatching forces to its border with Costa Rica. Google then worked with
+US State Department officials to correct the error.
+
+
On top of these considerations, software engineers share with everyone a basic human
+desire to flourish and do well in life and work. What does that have to do with ethics?
+Imagine a future where you are faced with a moral quandary arising from a project you
+are working on that presents serious risks to users. In that scenario, will you act in a way
+that you would be comfortable with if it later became public knowledge? Would it matter
+to you whether your family was proud or shamed by your publicly exposed actions?
+Would it matter to you whether, looking back, you saw this as one of your better moments
+as a human being, or one of your worst? Could you trust anyone to whom these outcomes
+didn’t matter?
+
+
Thus ethical obligations have both a professional and a personal dimension. Each are
+essential to consider; without a sense of personal ethics, one would be indifferent to their
+effect on the lives of others in circumstances where one’s professional code is silent. To
+understand what’s dangerous about this, consider any case in human history when a
+perpetrator of some grossly negligent, immoral or inhumane conduct tries to evade their
+responsibility by saying, ‘I was just following orders!’ So personal ethics helps us to be
+sure that we take full responsibility for our moral choices and their consequences.
+
+
But for professionals who serve the public or whose work impacts public welfare, a
+personal code of ethics is just not enough. Without a sense of professional ethics, one
+might be tempted to justify conduct in one’s own mind that could never be justified in
+front of others. Additionally, professional ethics is where one learns to see how broader
+ethical standards/values (like honesty, integrity, compassion and fairness) apply to one’s
+particular type of work. For example, wanting to have integrity is great – but what does
+integrity look like in a software engineer? What sort of specific coding practices
+demonstrate integrity, or a lack of it? This is something that professional codes of ethics
+can help us learn to see. Finally, being a professional means being a part of a moral
+community of others who share the same profound responsibilities we do. We can draw
+strength, courage, and wisdom from those members of our professional community who
+have navigated the same types of moral dilemmas, struggled with the same sorts of tough
+decisions, faced up to the same types of consequences, and ultimately earned the respect
+and admiration of their peers and the public.
Admonitions, otherwise known as “call outs”, are a very useful documentation pattern. In Morea, you can create admonitions in a few different ways by combining Bootstrap alerts, font awesome icons, and markdown. Here are a few examples to get you started.
+
+
The approach below involves the following:
+
+
+
Create a div with the Bootstrap alert class, and decide what kind of alert (warning, danger, etc.) The alert type sets the background color (danger is red, success is green, etc.)
+
Set markdown equal to “1” (so that the interior of the div is parsed as markdown),
+
Use a font awesome icon appropriate to the type of admonition. If you don’t like the ones I use below, find your own at the Font Awesome Icon Search Page.
+
Make a bold faced title with a horizontal line underneath.
+
Add the body text as markdown underneath.
+
+
+
Danger
+
+
Here’s one way to produce a “danger” admonition, which is in red.
+
+
+
+
<divclass="alert alert-danger"role="alert"markdown="1">
+<iclass="fa-solid fa-circle-exclamation fa-xl"></i> **Danger: the following is not recommended.**
+<hr/>
+
+You really don't want to do the following:
+ * Stay up too late.
+ * Get up too early.
+ * Drink coffee after lunch.
+</div>
+
+
+
+
Which looks like this:
+
+
+
Danger: the following is not recommended.
+
+
+
You really don’t want to do the following:
+
+
Stay up too late.
+
Get up too early.
+
Drink coffee after lunch.
+
+
+
+
Warning
+
+
Less troublesome is a “warning”, which is in yellow.
+
+
+
+
<divclass="alert alert-danger"role="alert"markdown="1">
+<iclass="fa-solid fa-circle-exclamation fa-xl"></i> **Danger: the following is not recommended.**
+<hr/>
+
+You really don't want to do the following:
+ * Stay up too late.
+ * Get up too early.
+ * Drink coffee after lunch.
+</div>
+
+
+
+
Which looks like this:
+
+
+
Warning: the following is not recommended.
+
+
+
Unless you know better, you might want to avoid the following:
+
+
Stay up too late.
+
Get up too early.
+
Drink coffee after lunch.
+
+
+
+
Success
+
+
To convey something positive, you can use the success alert, which is green:
+
+
+
+
<divclass="alert alert-success"role="alert"markdown="1">
+<iclass="fa-solid fa-circle-check fa-xl"></i> **Well done!**
+<hr/>
+
+You did everthing right!
+ * Went to bed early.
+ * Got up late.
+ * Switched to water after lunch.
+</div>
+
+
+
+
Which looks like this:
+
+
+
Well done!
+
+
+
You did everthing right!
+
+
Went to bed early.
+
Got up late.
+
Switched to water after lunch.
+
+
+
+
Info
+
+
Red, yellow, and green have connotations. A blue background is less judgemental:
+
+
+
+
<divclass="alert alert-info"role="alert"markdown="1">
+<iclass="fa-solid fa-circle-info fa-xl"></i> **For more information**
+<hr/>
+
+For more information, see the [Font Awesome Icon Search Page](https://fontawesome.com/search?)
+</div>
+
For this assignment, you will begin the process of creating a high quality
+software engineering environment on your computer.
+
+
Task 1: Obtain appropriate hardware
+
+
You must have an appropriate laptop.
+
+
Task 2: Install an appropriate operating system
+
+
An up to date release of Mac OS/X or a Linux-based OS are the preferred operating systems for this class.
+
+
If you must use Windows, you must use Windows 10 so that our web application tech stack (Meteor) will install correctly. If needed, I can sign you up for MSDNAA, which will provide you with a free copy of Windows 10. Please send me a DM on Discord for details.
+
+
Task 3: Install Chrome browser
+
+
If you have not already, please download and install the Chrome browser.
+
+
We will use that as the standard browser for development in this class. This is because we will be using the Chrome Developer Tools package that is provided as part of that browser, which includes custom extensions for Meteor.
+
+
Submission instructions
+
+
You are expected to bring your laptop to class every day. During a class period on or after the date associated with this experience on the Schedule page, I will review your environment in class.
For this experience, join the ICS 314 Discord server. You will receive an invitation to this server via email.
+
+
Please read through all channels in the Welcome category.
+
+
It is your responsibility to receive information about this class promptly. I highly recommend that you install a native client for Discord on your phone and your laptop and set it to display notifications.
+
+
Submission instructions
+
+
By the time and date indicated on the Schedule page, you must have:
+
+
+
Joined the ICS 314 Discord server
+
Performed all setup actions specified in the Welcome category channels.
Some of you may not be familiar with the command line. If that applies to you, please consider investing a few hours into the free Code Academy course “Learn the Command Line”.
+
+
There is no need to upgrade to the “Pro” version for 314, just do the “free” exercises.
+
+
Submission instructions
+
+
You do not have to submit anything for this experience. But you will be expected to have basic command line skills in this class.
Break up into small groups and meet in one of the #class-breakout channels assigned to you by the instructor. Elect one person to be the scribe and take notes. Use a shared google doc or something similar so that everyone can see the notes as they are being written.
+
+
Decide on your team name (very important!)
+
+
Answer the following questions:
+
+
+
+
What does your group consider to be the 3-6 most important goals for ICS 314?
+
+
+
What learning strategies will be effective in achieving these goals?
+
+
+
What risk factors might prevent you from achieving these goals?
+
+
+
+
After approximately 10 minutes, we will gather together and a spokesperson from each group will present the results of their work.
+
+
Please enable your video camera while you are in your breakout room! It makes the experience much better for everyone and can even raise the quality of your work.
A very significant essay in hacker culture is “How to ask questions the smart
+way“, which provides
+important tips on how to utilize the online community efficiently and
+effectively. This page provides tips on how to watch screencasts the smart
+way. This issue is important, because this class uses a “flipped” pedagogy, in
+which lectures are not provided face-to-face but rather online through YouTube
+videos.
+
+
Two primary benefits of the “flipped” approach are:
+
+
+
Precious class time can be spent on group work activities where face-to-face interaction is most valuable.
+
Screencasts can be viewed by students at the times and places of their choosing. Sections of screencasts can be re-viewed as many times as each student desires.
+
+
+
After several years using this approach, student reaction is generally
+positive. At the same time, this new format does create certain challenges
+for efficient and effective learning. Here are some heuristics we’ve
+discovered for addressing these challenges.
+
+
(1) Don’t multi-task
+
+
Since you can watch the screencasts at a time and place of your own choosing,
+it is tempting to watch them while also doing other activities: watching TV,
+texting, talking to friends, playing online games, reading FaceBook. However,
+your goal is not to simply have watched the material, but to have
+actually learned the material, and there is mounting scientific evidence showing
+that multi-tasking impairs learning
+ability.
+
+
For these reasons, it is in your best
+interest to block all other activities while watching the screencast. Since
+most screencasts are less than 20 minutes long, this should not be a huge imposition.
+
+
(2) Take notes
+
+
In a traditional classroom, the lecture is ephemeral and thus written notes
+provides a necessary means to capture the information. (Other mechanisms
+include recording the lecture on your phone, or even videotaping the lecture
+with your phone.)
+
+
When the lecture is already recorded and available at any
+time as a screencast, it might seem reasonable to not take notes because the
+material is always accessible online.
Put another way, your
+ultimate goal is to not have to re-watch the lecture every time you need the
+information, and note-taking helps you achieve that goal more quickly and
+efficiently.
+
+
Since you are not multi-tasking (see heuristic (1)), you will have the time and
+space necessary to take notes on the lecture.
+
+
Finally, by recording the
+elapsed time into the lecture associated with the material represented in the
+note, you can use your notes to later rewatch portions of the material quickly
+and efficiently as needed. You can also use your notes to record
+questions you have about the material for later discussion, as presented next.
+
+
(3) Use the mailing list to ask questions and discuss the material
+
+
One of the most significant pedagogical drawbacks associated with the
+“flipped” classroom is the inability of the instructor and students to engage
+in real-time, interactive conversation about the material as it is introduced,
+and the inability of the instructor to modify the presentation in real-time to
+adapt to student reaction.
+
+
Screencasts can further inhibit conversation about
+the material if students conclude that their questions could be resolved
+simply by re-watching the screencast at some future date.
+
+
To learn most
+efficiently, you should record questions you have as you watch the screencast,
+then post those questions to the discussion group.
+
+
(4) Watch in full screen, high definition
+
+
Many of the screencasts involve demonstrations of user interface tools or
+other technologies. You should watch those lectures in full screen mode in
+the highest resolution possible (typically 1080). That way, you will clearly
+see all user interface elements and be able to understand the procedures most
+easily.
+
+
Watching in full screen mode also prevents you from seeing other
+windows, thus helping to minimize multi-tasking. For example, here are two
+images taken from the same screencast, one viewed at 720p and one at 1080p.
+Click on each image to display it full size, and notice how much sharper the
+1080p screencast is:
+
+
1080p
+
+
+
+
720p
+
+
+
+
I have noticed that, at least with the Chrome
+browser, sometimes you can select 1080p in YouTube and the screencast is still
+displayed in a lower resolution. In Firefox, selecting the 1080p resolution
+results in the video quality “gear” icon rotating until 1080p resolution is
+ready for viewing. So, if you have problems with Chrome, you might try
+Firefox.
+
+
Suggestions?
+
+
If you have other tips for watching screencasts the smart way, please email
+me.
Here are the results of this assessment, which took place on July 16, 2021:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Feedback
+
+
Some comments regarding this assessment:
+
+
+
It appears that a significant number of students will need to improve their study habits. Please don’t hesitate to contact me or the TA if you are having problems!
+
Those who did not perform well often forgot one or more of the following:
+
+
Syntax errors in their program
+
Inability to use JSFiddle effectively.
+
Lack of understanding of loops.
+
+
+
+
+
Outcomes
+
+
The following outcomes were assessed: Competent with elementary Javascript
E05: isUnique (Cracking the Coding Interview, Problem 1)
+
+
Cracking the Coding Interview (CTCI) is widely regarded as a preeminent resource to prepare for modern software development position interviews at leading high tech companies.
+
+
The goal of this experience is to continue to get familiar with writing very simple Javascript code using JSFiddle, and to get you acquainted with the “Workout of the Day” (WOD) pedagogical technique used in this course.
+
+
To do this, you will solve the very first problem in CTCI, called “isUnique”:
+
+
1.1. Is Unique: Implement an algorithm to determine if a string has all unique characters.
+
+
+
For example:
+
+
console.log(isUnique('abcde'));// prints true
+console.log(isUnique('abcdea'));// prints false
+
+
+
Prelude
+
+
You should probably have finished the previous practice WOD.
+
+
Review the first few sections of the AirBnB Javascript Style Guide. For example, be sure to use let and const, not var.
+
+
Next, login to JSFiddle (creating an account if you haven’t already).
+
+
Check the settings pane to make sure the console window is displayed in the editor, and the layout is to your liking (I prefer “Tabs (rows)”).
+
+
To make the result window dark for your new JSFiddle, add the following to the CSS pane:
+
+
body { background-color: #1f2227; }
+
+
+
You may also want to reduce the height of the result window, so that more space is provided for the Javascript window.
+
+
Demonstration
+
+
Once you’ve finished trying to do it, watch me do it:
+
+
+
+
+
+
Submission Instructions
+
+
By the time and date indicated on the Schedule page, submit this assignment via Laulima.
+
+
Your submission should contain:
+
+
+
+
A link to the JSFiddle. Make sure you include the complete URL so that I can click on it in my mailer.
+
+
+
The number of times you have attempted this assignment so far, and for each time, how long it took you.
+
+
+
+
You will receive full credit for this practice WOD as long as you have attempted it at least once and submitted your work on Laulima with all required data before the due date. Your code does not have to run perfectly for you to receive full credit.
In this experience, you will start getting familiar with the syntax of Javascript by practicing the creation of simple Javascript code using FreeCodeCamp.
Second, start doing the 110 exercises in “Basic Javascript” (that’s the first subsection inside “Javascript Algorithms and Data Structures Certification” section. They say it takes 10 hours, I think you can do it more quickly, but it will be several hours of work. Don’t be turned off by the “simplicity”—the goal here is to get you typing Javascript and try to get your fingers to understand the language. When you are done, you should have checked off all of the Basic Javascript curriculum, here’s what the first part should look like:
+
+
+
+
+
+
+
+
Now move on and complete the 31 exercises in the “ES6” curriculum. Here’s what the first part should look like when you’ve completed all 31 exercises:
+
+
+
+
+
+
+
+
Phew! That was a lot of work but you’ve now got a pretty decent introduction to Javascript under your belt!
+
+
Submission instructions
+
+
Please complete the Introduction to Javascript and ES6 sections by the date and time indicated on the Schedule page. I will verify in class that you have completed the course by asking you to display your FreeCodeCamp learn page. All 106 Basic Javascript exercises and all 26 ES6 exercises must be completed.
So. What do you think about Javascript so far? If you’re a complete newbie, how does it compare to other programming languages that you know? If you have prior experience, did you learn new things from this module, perhaps with respect to ES6? Do you think Javascript is a good or bad programming language from a software engineering perspective?
+
+
What about athletic software engineering? Did you find the practice WODs to be useful? What do you think about this style of learning? Is it stressful? Is it enjoyable? Do you think it will work for you?
+
+
GitHub Gist Example
+
+
Here’s an example GitHub gist:
+
+
+
+
+
MathJax Example
+
+
This section demonstrates the use of MathJax for rendering mathematical notation in markdown files.
+
+
For example, the following markdoswn:
+
+
When \\(a \ne 0\\), there are two solutions to \\(ax^2 + bx + c = 0\\) and they are
+$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$
+
+
+
when rendered, produces the following text:
+
+
When \(a \ne 0\), there are two solutions to \(ax^2 + bx + c = 0\) and they are
+\(x = {-b \pm \sqrt{b^2-4ac} \over 2a}.\)
+
+
You can also create a block of math using the \\[ delimiter. For example, the following markdown:
+
+
\\[ \frac{1}{n^{2}} \\]
+
+
+
Produces the following block:
+
+
\[ \frac{1}{n^{2}} \]
+
+
Submission Instructions
+
+
By the time and date indicated on the Schedule page, write a technical essay regarding this module. You can use the issues above as a starting point, but write a stand-alone essay: don’t just answer them like it’s a homework assignment! Once you’ve finished the essay submit it using Laulima.
+
+
Please note the following:
+
+
+
+
Your submission should be a URL providing a direct link to your essay (not a link to the home page of your portfolio, nor a link to the essays directory page). If the link does not work, you will not get credit for your essay.
+
+
+
An entry for your essay must also appear in the Essays page of your portfolio. Check this before submission. If it is not listed, it’s probably because your YAML front matter is incorrect. See the Essay Content section of the TechFolio documentation for details.
+
+
+
Be sure that your essay is ready for evaluation before submitting it via Laulima. I often click on the link as soon as I receive the submission. If the essay is empty or only partially complete when I click on the link, you might not receive credit.
+
+
+
Your essay must be original content, written during this semester reflecting your current views and writing style. You cannot submit essays that you wrote previously for this assignment. The purpose of this assignment is for you to practice writing right now, this semester.
Divide up into teams of two. You will each complete this WOD on your own computer and create your own JSFiddle to hold your work, but you must work “synchronously”–i.e. both of you must type each line of code or perform each action at the same time, and thus you will both complete this WOD at the same time. This means you must talk to each other continuously about what you are doing.
+
+
Try to not share your screen, because you will not be allowed to share your screen during the WOD, so this is good practice. It also encourages your partner to actually think and not just copy your code. However, if it gets too hard to explain something to your partner, you can briefly share your screen with them to explain what you mean.
+
+
Your task is to implement a function called TemperatureConverter. It takes two parameters:
+
+
+
+
temperature: This is an integer indicating the temperature value in either F or C, depending upon the following argument.
+
+
+
temperatureType: A parameter which should be either the string “C” or “F”. TempType indicates whether the preceding argument is in fahrenheit or celsius units.
+
+
+
+
Given these two arguments, your function should compute and return the corresponding value in the other temperature unit. The formulas are:
You can assume your function will always be passed an integer and a string. However, if temperature type is not “F” or “C”, then the program should return the string “Illegal temperature type”.
+
+
Here are some examples:
+
+
console.log(temperatureConverter(212, "F")); // 100
+console.log(temperatureConverter(0, "C")); // 32
+console.log(temperatureConverter(0, "X")); // Illegal temperature type
+
+
+
Ready? Let’s begin:
+
+
+
+
Login to JSFiddle.
+
+
+
Create a Javascript function called “TemperatureConverter”. The function should process its arguments and return a result as specified above. If you declare a variable, be sure to use let or const, not var.
+
+
+
Informally test your program by running it and inspecting the output. Check that 0 degrees celsius equals 32 degrees fahrenheit and 100 degrees celsius equals 212 degrees fahrenheit, and that an illegal argument type prints out the appropriate string. Try using the “Tidy” command to re-indent your code to make sure your braces are aligned correctly.
+
+
+
Press “Save” when you are finished to create a URL to your completed JSFiddle.
+
+
+
Raise your hands to let me know that both of you have finished.
In How to ask questions the smart way, Eric Raymond provides guidelines for effective interaction with the open source community. If you haven’t yet read this essay, please do so before proceeding.
+
+
Communication is one of the most important software engineering skills to develop, and asking questions the “smart way” is one of the most important communication skills of all for a software engineer.
+
+
For this experience, you will use StackOverflow to provide examples of the positive outcomes that can potentially occur when software engineers follow the guidelines, and the negative outcomes that can potentially occur when software engineers don’t.
+
+
First, search StackOverflow for a question submitted by a developer that demonstrates the “smart way”. Such a question should follow the precepts established by Raymond. In addition, the answers provided by the community should demonstrate that asking a question the smart way leads to both efficient and effective help.
+
+
Next, you will search for a question submitted by a different developer that demonstrates the “not smart way”. In other words, this question violates the principles established by Raymond. In addition, the answers provided by the community should demonstrate that asking a question in a “not smart” way does not lead to both effective and efficient help
+
+
The goal of this exercise is not to “prove” that asking questions the smart way is always better (although it would be quite interesting to design an empirical study using StackOverflow to actually gather data on whether “smart” questions do indeed, on average, lead to more effective and efficient answers). Instead, the goal of this exercise is to help you form a deeper understanding of what constitutes “smart” and “not smart” questions so that you are more likely to ask smart ones in the future.
+
+
Write a technical essay that discusses why smart questions are important for smart software engineers, how the chosen questions fulfill (or not) the precepts for smart questions, how the responses reflect the smartness (or lack thereof), and the insights you gained as a result of this experience.
+
+
Be sure that your essay includes a textual summary of both the “smart” and “not so smart” questions, as well as a link to the StackOverFlow pages where they are located. Don’t just put the URL to the questions and force the reader to visit StackOverFlow to read the question there, then switch back to your essay to continue. Your essay should contain enough detail about the two questions so that the reader doesn’t need to visit StackOverFlow to make sense of your essay.
+
+
Be sure all URLs in your essay are clickable. This requires learning Markdown in TechFolios.
+
+
If you have previously taken this class, you must find new questions and answers to analyze.
+
+
Submission instructions
+
+
By the time and date indicated on the Schedule page, write a technical essay regarding this module. You can use the issues above as a starting point, but write a stand-alone essay: don’t just answer them like it’s a homework assignment! Once you’ve finished the essay submit it using Laulima.
+
+
Please note the following:
+
+
+
+
Your submission should be a URL providing a direct link to your essay (not a link to the home page of your portfolio, nor a link to the essays directory page). If the link does not work, you will not get credit for your essay.
+
+
+
An entry for your essay must also appear in the Essays page of your portfolio. Check this before submission. If it is not listed, it’s probably because your YAML front matter is incorrect. See the Essay Content section of the TechFolio documentation for details.
+
+
+
Be sure that your essay is ready for evaluation before submitting it via Laulima. I often click on the link as soon as I receive the submission. If the essay is empty or only partially complete when I click on the link, you might not receive credit.
+
+
+
Your essay must be original content, written during this semester reflecting your current views and writing style. You cannot submit essays that you wrote previously for this assignment. The purpose of this assignment is for you to practice writing right now, this semester.
ICS 199 is a four week overview of selected topics in computer science, including Javascript, Open Source Software, and Ethics.
+
+
Who should take this course
+
+
This course is intended for undergraduates in computer science or computer engineering who have a working knowledge of at least one programming language and who want to get a taste of important topics in the field.
+
+
Pedagogy
+
+
ICS 199 is structured as a sequential series of modules, each taking approximately a week to complete. Each module has the following structure:
+
+
+
Prerequisites, describing skills you should have prior to starting the module.
Assessments, which help you determine if you have acquired mastery of the material.
+
+
+
About the instructor
+
+
Philip Johnson is a Professor of Information and Computer Sciences at the University of Hawaii. His research interests include climate change, educational technology, software engineering, and serious games. He is currently an active developer of RadGrad, an open source software system based on Javascript, Typescript, React, and Meteor.
diff --git a/morea/overview-assessments.html b/morea/overview-assessments.html
new file mode 100644
index 0000000..6133a69
--- /dev/null
+++ b/morea/overview-assessments.html
@@ -0,0 +1,3 @@
+
This page collects together the “assessments” associated with individual modules.
+
+
Assessments enable both students and instructors to determine if the students have achieved the learning objectives associated with the module.
diff --git a/morea/overview-experiences.html b/morea/overview-experiences.html
new file mode 100644
index 0000000..7b75ee0
--- /dev/null
+++ b/morea/overview-experiences.html
@@ -0,0 +1,3 @@
+
This page collects together all of the “experiences” associated with individual modules.
+
+
In this site, experiences represent “active” learning opportunities, as opposed to readings, which represent “passive” learning opportunities. In many courses, readings and experiences together constitute the “assignments”.
diff --git a/morea/overview-modules.html b/morea/overview-modules.html
new file mode 100644
index 0000000..680aa63
--- /dev/null
+++ b/morea/overview-modules.html
@@ -0,0 +1 @@
+
For any module not marked as “Coming Soon”, click on it to go to a page containing details.
diff --git a/morea/overview-outcomes.html b/morea/overview-outcomes.html
new file mode 100644
index 0000000..a9675e8
--- /dev/null
+++ b/morea/overview-outcomes.html
@@ -0,0 +1 @@
+
This page collects together all of the “outcomes” associated with individual modules. Outcomes identify what students will know and be able to do if they master the material.
diff --git a/morea/overview-prerequisites.html b/morea/overview-prerequisites.html
new file mode 100644
index 0000000..51df09c
--- /dev/null
+++ b/morea/overview-prerequisites.html
@@ -0,0 +1 @@
+
Prerequisites are modules from other courses that provide helpful or important background material for the current module.
diff --git a/morea/overview-readings.html b/morea/overview-readings.html
new file mode 100644
index 0000000..53a111f
--- /dev/null
+++ b/morea/overview-readings.html
@@ -0,0 +1,3 @@
+
This page collects together all of the “readings” associated with individual modules.
+
+
In this site, readings represent “passive” learning opportunities, as opposed to experiences, which represent “active” learning opportunities. In many courses, readings and experiences together constitute the “assignments”.
diff --git a/morea/prerequisites/211-oop.html b/morea/prerequisites/211-oop.html
new file mode 100644
index 0000000..782c121
--- /dev/null
+++ b/morea/prerequisites/211-oop.html
@@ -0,0 +1 @@
+
Object-oriented programming and Java Class Hierarchy.
This page collects together all of the “outcomes” associated with individual modules. Outcomes identify what students will know and be able to do if they master the material.
+
+
+
+
+
+
Understand how to succeed in ICS 199
+
+
+
+
+
You understand the goals, structures, and procedures for learning in ICS 199.
+
Your computer and its software is adequate to support the experiences you will have in this course.
This page collects together all of the “readings” associated with individual modules.
+
+
In this site, readings represent “passive” learning opportunities, as opposed to experiences, which represent “active” learning opportunities. In many courses, readings and experiences together constitute the “assignments”.
Why, what, how, and when you will learn about open source software.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Open Source Software
+
History of open source, licenses, prime directives, how to participate effectively
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Understanding Open Source Licensing
+
Short summary of copyright, copyleft, and the motivation for open source licenses.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Choose a (open source) license
+
Simple overview of several open sources licenses and their implications.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
How to ask questions the smart way
+
The classic guide to how to participate in the open source community effectively
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
The Cathedral and the Bazaar
+
“I anatomize a successful open-source project, fetchmail, that
+was run as a deliberate test of some surprising theories about
+software engineering suggested by the history of Linux.”