Quiz And Survey Master (Formerly Quiz Master Next) - Version 0.7.1

Version Description

Upgrade to fix minor bugs, view new section how-to page, and new stats on main page.

Download this release

Release Info

Developer fpcorso
Plugin Icon 128x128 Quiz And Survey Master (Formerly Quiz Master Next)
Version 0.7.1
Comparing to
See all releases

Code changes from version 0.6.2 to 0.7.1

includes/jquery_sparkline.js ADDED
@@ -0,0 +1,3054 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ *
3
+ * jquery.sparkline.js
4
+ *
5
+ * v2.1.2
6
+ * (c) Splunk, Inc
7
+ * Contact: Gareth Watts (gareth@splunk.com)
8
+ * http://omnipotent.net/jquery.sparkline/
9
+ *
10
+ * Generates inline sparkline charts from data supplied either to the method
11
+ * or inline in HTML
12
+ *
13
+ * Compatible with Internet Explorer 6.0+ and modern browsers equipped with the canvas tag
14
+ * (Firefox 2.0+, Safari, Opera, etc)
15
+ *
16
+ * License: New BSD License
17
+ *
18
+ * Copyright (c) 2012, Splunk Inc.
19
+ * All rights reserved.
20
+ *
21
+ * Redistribution and use in source and binary forms, with or without modification,
22
+ * are permitted provided that the following conditions are met:
23
+ *
24
+ * * Redistributions of source code must retain the above copyright notice,
25
+ * this list of conditions and the following disclaimer.
26
+ * * Redistributions in binary form must reproduce the above copyright notice,
27
+ * this list of conditions and the following disclaimer in the documentation
28
+ * and/or other materials provided with the distribution.
29
+ * * Neither the name of Splunk Inc nor the names of its contributors may
30
+ * be used to endorse or promote products derived from this software without
31
+ * specific prior written permission.
32
+ *
33
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
34
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
35
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
36
+ * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
37
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
38
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
39
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
40
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
41
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42
+ *
43
+ *
44
+ * Usage:
45
+ * $(selector).sparkline(values, options)
46
+ *
47
+ * If values is undefined or set to 'html' then the data values are read from the specified tag:
48
+ * <p>Sparkline: <span class="sparkline">1,4,6,6,8,5,3,5</span></p>
49
+ * $('.sparkline').sparkline();
50
+ * There must be no spaces in the enclosed data set
51
+ *
52
+ * Otherwise values must be an array of numbers or null values
53
+ * <p>Sparkline: <span id="sparkline1">This text replaced if the browser is compatible</span></p>
54
+ * $('#sparkline1').sparkline([1,4,6,6,8,5,3,5])
55
+ * $('#sparkline2').sparkline([1,4,6,null,null,5,3,5])
56
+ *
57
+ * Values can also be specified in an HTML comment, or as a values attribute:
58
+ * <p>Sparkline: <span class="sparkline"><!--1,4,6,6,8,5,3,5 --></span></p>
59
+ * <p>Sparkline: <span class="sparkline" values="1,4,6,6,8,5,3,5"></span></p>
60
+ * $('.sparkline').sparkline();
61
+ *
62
+ * For line charts, x values can also be specified:
63
+ * <p>Sparkline: <span class="sparkline">1:1,2.7:4,3.4:6,5:6,6:8,8.7:5,9:3,10:5</span></p>
64
+ * $('#sparkline1').sparkline([ [1,1], [2.7,4], [3.4,6], [5,6], [6,8], [8.7,5], [9,3], [10,5] ])
65
+ *
66
+ * By default, options should be passed in as teh second argument to the sparkline function:
67
+ * $('.sparkline').sparkline([1,2,3,4], {type: 'bar'})
68
+ *
69
+ * Options can also be set by passing them on the tag itself. This feature is disabled by default though
70
+ * as there's a slight performance overhead:
71
+ * $('.sparkline').sparkline([1,2,3,4], {enableTagOptions: true})
72
+ * <p>Sparkline: <span class="sparkline" sparkType="bar" sparkBarColor="red">loading</span></p>
73
+ * Prefix all options supplied as tag attribute with "spark" (configurable by setting tagOptionPrefix)
74
+ *
75
+ * Supported options:
76
+ * lineColor - Color of the line used for the chart
77
+ * fillColor - Color used to fill in the chart - Set to '' or false for a transparent chart
78
+ * width - Width of the chart - Defaults to 3 times the number of values in pixels
79
+ * height - Height of the chart - Defaults to the height of the containing element
80
+ * chartRangeMin - Specify the minimum value to use for the Y range of the chart - Defaults to the minimum value supplied
81
+ * chartRangeMax - Specify the maximum value to use for the Y range of the chart - Defaults to the maximum value supplied
82
+ * chartRangeClip - Clip out of range values to the max/min specified by chartRangeMin and chartRangeMax
83
+ * chartRangeMinX - Specify the minimum value to use for the X range of the chart - Defaults to the minimum value supplied
84
+ * chartRangeMaxX - Specify the maximum value to use for the X range of the chart - Defaults to the maximum value supplied
85
+ * composite - If true then don't erase any existing chart attached to the tag, but draw
86
+ * another chart over the top - Note that width and height are ignored if an
87
+ * existing chart is detected.
88
+ * tagValuesAttribute - Name of tag attribute to check for data values - Defaults to 'values'
89
+ * enableTagOptions - Whether to check tags for sparkline options
90
+ * tagOptionPrefix - Prefix used for options supplied as tag attributes - Defaults to 'spark'
91
+ * disableHiddenCheck - If set to true, then the plugin will assume that charts will never be drawn into a
92
+ * hidden dom element, avoding a browser reflow
93
+ * disableInteraction - If set to true then all mouseover/click interaction behaviour will be disabled,
94
+ * making the plugin perform much like it did in 1.x
95
+ * disableTooltips - If set to true then tooltips will be disabled - Defaults to false (tooltips enabled)
96
+ * disableHighlight - If set to true then highlighting of selected chart elements on mouseover will be disabled
97
+ * defaults to false (highlights enabled)
98
+ * highlightLighten - Factor to lighten/darken highlighted chart values by - Defaults to 1.4 for a 40% increase
99
+ * tooltipContainer - Specify which DOM element the tooltip should be rendered into - defaults to document.body
100
+ * tooltipClassname - Optional CSS classname to apply to tooltips - If not specified then a default style will be applied
101
+ * tooltipOffsetX - How many pixels away from the mouse pointer to render the tooltip on the X axis
102
+ * tooltipOffsetY - How many pixels away from the mouse pointer to render the tooltip on the r axis
103
+ * tooltipFormatter - Optional callback that allows you to override the HTML displayed in the tooltip
104
+ * callback is given arguments of (sparkline, options, fields)
105
+ * tooltipChartTitle - If specified then the tooltip uses the string specified by this setting as a title
106
+ * tooltipFormat - A format string or SPFormat object (or an array thereof for multiple entries)
107
+ * to control the format of the tooltip
108
+ * tooltipPrefix - A string to prepend to each field displayed in a tooltip
109
+ * tooltipSuffix - A string to append to each field displayed in a tooltip
110
+ * tooltipSkipNull - If true then null values will not have a tooltip displayed (defaults to true)
111
+ * tooltipValueLookups - An object or range map to map field values to tooltip strings
112
+ * (eg. to map -1 to "Lost", 0 to "Draw", and 1 to "Win")
113
+ * numberFormatter - Optional callback for formatting numbers in tooltips
114
+ * numberDigitGroupSep - Character to use for group separator in numbers "1,234" - Defaults to ","
115
+ * numberDecimalMark - Character to use for the decimal point when formatting numbers - Defaults to "."
116
+ * numberDigitGroupCount - Number of digits between group separator - Defaults to 3
117
+ *
118
+ * There are 7 types of sparkline, selected by supplying a "type" option of 'line' (default),
119
+ * 'bar', 'tristate', 'bullet', 'discrete', 'pie' or 'box'
120
+ * line - Line chart. Options:
121
+ * spotColor - Set to '' to not end each line in a circular spot
122
+ * minSpotColor - If set, color of spot at minimum value
123
+ * maxSpotColor - If set, color of spot at maximum value
124
+ * spotRadius - Radius in pixels
125
+ * lineWidth - Width of line in pixels
126
+ * normalRangeMin
127
+ * normalRangeMax - If set draws a filled horizontal bar between these two values marking the "normal"
128
+ * or expected range of values
129
+ * normalRangeColor - Color to use for the above bar
130
+ * drawNormalOnTop - Draw the normal range above the chart fill color if true
131
+ * defaultPixelsPerValue - Defaults to 3 pixels of width for each value in the chart
132
+ * highlightSpotColor - The color to use for drawing a highlight spot on mouseover - Set to null to disable
133
+ * highlightLineColor - The color to use for drawing a highlight line on mouseover - Set to null to disable
134
+ * valueSpots - Specify which points to draw spots on, and in which color. Accepts a range map
135
+ *
136
+ * bar - Bar chart. Options:
137
+ * barColor - Color of bars for postive values
138
+ * negBarColor - Color of bars for negative values
139
+ * zeroColor - Color of bars with zero values
140
+ * nullColor - Color of bars with null values - Defaults to omitting the bar entirely
141
+ * barWidth - Width of bars in pixels
142
+ * colorMap - Optional mappnig of values to colors to override the *BarColor values above
143
+ * can be an Array of values to control the color of individual bars or a range map
144
+ * to specify colors for individual ranges of values
145
+ * barSpacing - Gap between bars in pixels
146
+ * zeroAxis - Centers the y-axis around zero if true
147
+ *
148
+ * tristate - Charts values of win (>0), lose (<0) or draw (=0)
149
+ * posBarColor - Color of win values
150
+ * negBarColor - Color of lose values
151
+ * zeroBarColor - Color of draw values
152
+ * barWidth - Width of bars in pixels
153
+ * barSpacing - Gap between bars in pixels
154
+ * colorMap - Optional mappnig of values to colors to override the *BarColor values above
155
+ * can be an Array of values to control the color of individual bars or a range map
156
+ * to specify colors for individual ranges of values
157
+ *
158
+ * discrete - Options:
159
+ * lineHeight - Height of each line in pixels - Defaults to 30% of the graph height
160
+ * thesholdValue - Values less than this value will be drawn using thresholdColor instead of lineColor
161
+ * thresholdColor
162
+ *
163
+ * bullet - Values for bullet graphs msut be in the order: target, performance, range1, range2, range3, ...
164
+ * options:
165
+ * targetColor - The color of the vertical target marker
166
+ * targetWidth - The width of the target marker in pixels
167
+ * performanceColor - The color of the performance measure horizontal bar
168
+ * rangeColors - Colors to use for each qualitative range background color
169
+ *
170
+ * pie - Pie chart. Options:
171
+ * sliceColors - An array of colors to use for pie slices
172
+ * offset - Angle in degrees to offset the first slice - Try -90 or +90
173
+ * borderWidth - Width of border to draw around the pie chart, in pixels - Defaults to 0 (no border)
174
+ * borderColor - Color to use for the pie chart border - Defaults to #000
175
+ *
176
+ * box - Box plot. Options:
177
+ * raw - Set to true to supply pre-computed plot points as values
178
+ * values should be: low_outlier, low_whisker, q1, median, q3, high_whisker, high_outlier
179
+ * When set to false you can supply any number of values and the box plot will
180
+ * be computed for you. Default is false.
181
+ * showOutliers - Set to true (default) to display outliers as circles
182
+ * outlierIQR - Interquartile range used to determine outliers. Default 1.5
183
+ * boxLineColor - Outline color of the box
184
+ * boxFillColor - Fill color for the box
185
+ * whiskerColor - Line color used for whiskers
186
+ * outlierLineColor - Outline color of outlier circles
187
+ * outlierFillColor - Fill color of the outlier circles
188
+ * spotRadius - Radius of outlier circles
189
+ * medianColor - Line color of the median line
190
+ * target - Draw a target cross hair at the supplied value (default undefined)
191
+ *
192
+ *
193
+ *
194
+ * Examples:
195
+ * $('#sparkline1').sparkline(myvalues, { lineColor: '#f00', fillColor: false });
196
+ * $('.barsparks').sparkline('html', { type:'bar', height:'40px', barWidth:5 });
197
+ * $('#tristate').sparkline([1,1,-1,1,0,0,-1], { type:'tristate' }):
198
+ * $('#discrete').sparkline([1,3,4,5,5,3,4,5], { type:'discrete' });
199
+ * $('#bullet').sparkline([10,12,12,9,7], { type:'bullet' });
200
+ * $('#pie').sparkline([1,1,2], { type:'pie' });
201
+ */
202
+
203
+ /*jslint regexp: true, browser: true, jquery: true, white: true, nomen: false, plusplus: false, maxerr: 500, indent: 4 */
204
+
205
+ (function(document, Math, undefined) { // performance/minified-size optimization
206
+ (function(factory) {
207
+ if(typeof define === 'function' && define.amd) {
208
+ define(['jquery'], factory);
209
+ } else if (jQuery && !jQuery.fn.sparkline) {
210
+ factory(jQuery);
211
+ }
212
+ }
213
+ (function($) {
214
+ 'use strict';
215
+
216
+ var UNSET_OPTION = {},
217
+ getDefaults, createClass, SPFormat, clipval, quartile, normalizeValue, normalizeValues,
218
+ remove, isNumber, all, sum, addCSS, ensureArray, formatNumber, RangeMap,
219
+ MouseHandler, Tooltip, barHighlightMixin,
220
+ line, bar, tristate, discrete, bullet, pie, box, defaultStyles, initStyles,
221
+ VShape, VCanvas_base, VCanvas_canvas, VCanvas_vml, pending, shapeCount = 0;
222
+
223
+ /**
224
+ * Default configuration settings
225
+ */
226
+ getDefaults = function () {
227
+ return {
228
+ // Settings common to most/all chart types
229
+ common: {
230
+ type: 'line',
231
+ lineColor: '#00f',
232
+ fillColor: '#cdf',
233
+ defaultPixelsPerValue: 3,
234
+ width: 'auto',
235
+ height: 'auto',
236
+ composite: false,
237
+ tagValuesAttribute: 'values',
238
+ tagOptionsPrefix: 'spark',
239
+ enableTagOptions: false,
240
+ enableHighlight: true,
241
+ highlightLighten: 1.4,
242
+ tooltipSkipNull: true,
243
+ tooltipPrefix: '',
244
+ tooltipSuffix: '',
245
+ disableHiddenCheck: false,
246
+ numberFormatter: false,
247
+ numberDigitGroupCount: 3,
248
+ numberDigitGroupSep: ',',
249
+ numberDecimalMark: '.',
250
+ disableTooltips: false,
251
+ disableInteraction: false
252
+ },
253
+ // Defaults for line charts
254
+ line: {
255
+ spotColor: '#f80',
256
+ highlightSpotColor: '#5f5',
257
+ highlightLineColor: '#f22',
258
+ spotRadius: 1.5,
259
+ minSpotColor: '#f80',
260
+ maxSpotColor: '#f80',
261
+ lineWidth: 1,
262
+ normalRangeMin: undefined,
263
+ normalRangeMax: undefined,
264
+ normalRangeColor: '#ccc',
265
+ drawNormalOnTop: false,
266
+ chartRangeMin: undefined,
267
+ chartRangeMax: undefined,
268
+ chartRangeMinX: undefined,
269
+ chartRangeMaxX: undefined,
270
+ tooltipFormat: new SPFormat('<span style="color: {{color}}">&#9679;</span> {{prefix}}{{y}}{{suffix}}')
271
+ },
272
+ // Defaults for bar charts
273
+ bar: {
274
+ barColor: '#3366cc',
275
+ negBarColor: '#f44',
276
+ stackedBarColor: ['#3366cc', '#dc3912', '#ff9900', '#109618', '#66aa00',
277
+ '#dd4477', '#0099c6', '#990099'],
278
+ zeroColor: undefined,
279
+ nullColor: undefined,
280
+ zeroAxis: true,
281
+ barWidth: 4,
282
+ barSpacing: 1,
283
+ chartRangeMax: undefined,
284
+ chartRangeMin: undefined,
285
+ chartRangeClip: false,
286
+ colorMap: undefined,
287
+ tooltipFormat: new SPFormat('<span style="color: {{color}}">&#9679;</span> {{prefix}}{{value}}{{suffix}}')
288
+ },
289
+ // Defaults for tristate charts
290
+ tristate: {
291
+ barWidth: 4,
292
+ barSpacing: 1,
293
+ posBarColor: '#6f6',
294
+ negBarColor: '#f44',
295
+ zeroBarColor: '#999',
296
+ colorMap: {},
297
+ tooltipFormat: new SPFormat('<span style="color: {{color}}">&#9679;</span> {{value:map}}'),
298
+ tooltipValueLookups: { map: { '-1': 'Loss', '0': 'Draw', '1': 'Win' } }
299
+ },
300
+ // Defaults for discrete charts
301
+ discrete: {
302
+ lineHeight: 'auto',
303
+ thresholdColor: undefined,
304
+ thresholdValue: 0,
305
+ chartRangeMax: undefined,
306
+ chartRangeMin: undefined,
307
+ chartRangeClip: false,
308
+ tooltipFormat: new SPFormat('{{prefix}}{{value}}{{suffix}}')
309
+ },
310
+ // Defaults for bullet charts
311
+ bullet: {
312
+ targetColor: '#f33',
313
+ targetWidth: 3, // width of the target bar in pixels
314
+ performanceColor: '#33f',
315
+ rangeColors: ['#d3dafe', '#a8b6ff', '#7f94ff'],
316
+ base: undefined, // set this to a number to change the base start number
317
+ tooltipFormat: new SPFormat('{{fieldkey:fields}} - {{value}}'),
318
+ tooltipValueLookups: { fields: {r: 'Range', p: 'Performance', t: 'Target'} }
319
+ },
320
+ // Defaults for pie charts
321
+ pie: {
322
+ offset: 0,
323
+ sliceColors: ['#3366cc', '#dc3912', '#ff9900', '#109618', '#66aa00',
324
+ '#dd4477', '#0099c6', '#990099'],
325
+ borderWidth: 0,
326
+ borderColor: '#000',
327
+ tooltipFormat: new SPFormat('<span style="color: {{color}}">&#9679;</span> {{value}} ({{percent.1}}%)')
328
+ },
329
+ // Defaults for box plots
330
+ box: {
331
+ raw: false,
332
+ boxLineColor: '#000',
333
+ boxFillColor: '#cdf',
334
+ whiskerColor: '#000',
335
+ outlierLineColor: '#333',
336
+ outlierFillColor: '#fff',
337
+ medianColor: '#f00',
338
+ showOutliers: true,
339
+ outlierIQR: 1.5,
340
+ spotRadius: 1.5,
341
+ target: undefined,
342
+ targetColor: '#4a2',
343
+ chartRangeMax: undefined,
344
+ chartRangeMin: undefined,
345
+ tooltipFormat: new SPFormat('{{field:fields}}: {{value}}'),
346
+ tooltipFormatFieldlistKey: 'field',
347
+ tooltipValueLookups: { fields: { lq: 'Lower Quartile', med: 'Median',
348
+ uq: 'Upper Quartile', lo: 'Left Outlier', ro: 'Right Outlier',
349
+ lw: 'Left Whisker', rw: 'Right Whisker'} }
350
+ }
351
+ };
352
+ };
353
+
354
+ // You can have tooltips use a css class other than jqstooltip by specifying tooltipClassname
355
+ defaultStyles = '.jqstooltip { ' +
356
+ 'position: absolute;' +
357
+ 'left: 0px;' +
358
+ 'top: 0px;' +
359
+ 'visibility: hidden;' +
360
+ 'background: rgb(0, 0, 0) transparent;' +
361
+ 'background-color: rgba(0,0,0,0.6);' +
362
+ 'filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000);' +
363
+ '-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000)";' +
364
+ 'color: white;' +
365
+ 'font: 10px arial, san serif;' +
366
+ 'text-align: left;' +
367
+ 'white-space: nowrap;' +
368
+ 'padding: 5px;' +
369
+ 'border: 1px solid white;' +
370
+ 'z-index: 10000;' +
371
+ '}' +
372
+ '.jqsfield { ' +
373
+ 'color: white;' +
374
+ 'font: 10px arial, san serif;' +
375
+ 'text-align: left;' +
376
+ '}';
377
+
378
+ /**
379
+ * Utilities
380
+ */
381
+
382
+ createClass = function (/* [baseclass, [mixin, ...]], definition */) {
383
+ var Class, args;
384
+ Class = function () {
385
+ this.init.apply(this, arguments);
386
+ };
387
+ if (arguments.length > 1) {
388
+ if (arguments[0]) {
389
+ Class.prototype = $.extend(new arguments[0](), arguments[arguments.length - 1]);
390
+ Class._super = arguments[0].prototype;
391
+ } else {
392
+ Class.prototype = arguments[arguments.length - 1];
393
+ }
394
+ if (arguments.length > 2) {
395
+ args = Array.prototype.slice.call(arguments, 1, -1);
396
+ args.unshift(Class.prototype);
397
+ $.extend.apply($, args);
398
+ }
399
+ } else {
400
+ Class.prototype = arguments[0];
401
+ }
402
+ Class.prototype.cls = Class;
403
+ return Class;
404
+ };
405
+
406
+ /**
407
+ * Wraps a format string for tooltips
408
+ * {{x}}
409
+ * {{x.2}
410
+ * {{x:months}}
411
+ */
412
+ $.SPFormatClass = SPFormat = createClass({
413
+ fre: /\{\{([\w.]+?)(:(.+?))?\}\}/g,
414
+ precre: /(\w+)\.(\d+)/,
415
+
416
+ init: function (format, fclass) {
417
+ this.format = format;
418
+ this.fclass = fclass;
419
+ },
420
+
421
+ render: function (fieldset, lookups, options) {
422
+ var self = this,
423
+ fields = fieldset,
424
+ match, token, lookupkey, fieldvalue, prec;
425
+ return this.format.replace(this.fre, function () {
426
+ var lookup;
427
+ token = arguments[1];
428
+ lookupkey = arguments[3];
429
+ match = self.precre.exec(token);
430
+ if (match) {
431
+ prec = match[2];
432
+ token = match[1];
433
+ } else {
434
+ prec = false;
435
+ }
436
+ fieldvalue = fields[token];
437
+ if (fieldvalue === undefined) {
438
+ return '';
439
+ }
440
+ if (lookupkey && lookups && lookups[lookupkey]) {
441
+ lookup = lookups[lookupkey];
442
+ if (lookup.get) { // RangeMap
443
+ return lookups[lookupkey].get(fieldvalue) || fieldvalue;
444
+ } else {
445
+ return lookups[lookupkey][fieldvalue] || fieldvalue;
446
+ }
447
+ }
448
+ if (isNumber(fieldvalue)) {
449
+ if (options.get('numberFormatter')) {
450
+ fieldvalue = options.get('numberFormatter')(fieldvalue);
451
+ } else {
452
+ fieldvalue = formatNumber(fieldvalue, prec,
453
+ options.get('numberDigitGroupCount'),
454
+ options.get('numberDigitGroupSep'),
455
+ options.get('numberDecimalMark'));
456
+ }
457
+ }
458
+ return fieldvalue;
459
+ });
460
+ }
461
+ });
462
+
463
+ // convience method to avoid needing the new operator
464
+ $.spformat = function(format, fclass) {
465
+ return new SPFormat(format, fclass);
466
+ };
467
+
468
+ clipval = function (val, min, max) {
469
+ if (val < min) {
470
+ return min;
471
+ }
472
+ if (val > max) {
473
+ return max;
474
+ }
475
+ return val;
476
+ };
477
+
478
+ quartile = function (values, q) {
479
+ var vl;
480
+ if (q === 2) {
481
+ vl = Math.floor(values.length / 2);
482
+ return values.length % 2 ? values[vl] : (values[vl-1] + values[vl]) / 2;
483
+ } else {
484
+ if (values.length % 2 ) { // odd
485
+ vl = (values.length * q + q) / 4;
486
+ return vl % 1 ? (values[Math.floor(vl)] + values[Math.floor(vl) - 1]) / 2 : values[vl-1];
487
+ } else { //even
488
+ vl = (values.length * q + 2) / 4;
489
+ return vl % 1 ? (values[Math.floor(vl)] + values[Math.floor(vl) - 1]) / 2 : values[vl-1];
490
+
491
+ }
492
+ }
493
+ };
494
+
495
+ normalizeValue = function (val) {
496
+ var nf;
497
+ switch (val) {
498
+ case 'undefined':
499
+ val = undefined;
500
+ break;
501
+ case 'null':
502
+ val = null;
503
+ break;
504
+ case 'true':
505
+ val = true;
506
+ break;
507
+ case 'false':
508
+ val = false;
509
+ break;
510
+ default:
511
+ nf = parseFloat(val);
512
+ if (val == nf) {
513
+ val = nf;
514
+ }
515
+ }
516
+ return val;
517
+ };
518
+
519
+ normalizeValues = function (vals) {
520
+ var i, result = [];
521
+ for (i = vals.length; i--;) {
522
+ result[i] = normalizeValue(vals[i]);
523
+ }
524
+ return result;
525
+ };
526
+
527
+ remove = function (vals, filter) {
528
+ var i, vl, result = [];
529
+ for (i = 0, vl = vals.length; i < vl; i++) {
530
+ if (vals[i] !== filter) {
531
+ result.push(vals[i]);
532
+ }
533
+ }
534
+ return result;
535
+ };
536
+
537
+ isNumber = function (num) {
538
+ return !isNaN(parseFloat(num)) && isFinite(num);
539
+ };
540
+
541
+ formatNumber = function (num, prec, groupsize, groupsep, decsep) {
542
+ var p, i;
543
+ num = (prec === false ? parseFloat(num).toString() : num.toFixed(prec)).split('');
544
+ p = (p = $.inArray('.', num)) < 0 ? num.length : p;
545
+ if (p < num.length) {
546
+ num[p] = decsep;
547
+ }
548
+ for (i = p - groupsize; i > 0; i -= groupsize) {
549
+ num.splice(i, 0, groupsep);
550
+ }
551
+ return num.join('');
552
+ };
553
+
554
+ // determine if all values of an array match a value
555
+ // returns true if the array is empty
556
+ all = function (val, arr, ignoreNull) {
557
+ var i;
558
+ for (i = arr.length; i--; ) {
559
+ if (ignoreNull && arr[i] === null) continue;
560
+ if (arr[i] !== val) {
561
+ return false;
562
+ }
563
+ }
564
+ return true;
565
+ };
566
+
567
+ // sums the numeric values in an array, ignoring other values
568
+ sum = function (vals) {
569
+ var total = 0, i;
570
+ for (i = vals.length; i--;) {
571
+ total += typeof vals[i] === 'number' ? vals[i] : 0;
572
+ }
573
+ return total;
574
+ };
575
+
576
+ ensureArray = function (val) {
577
+ return $.isArray(val) ? val : [val];
578
+ };
579
+
580
+ // http://paulirish.com/2008/bookmarklet-inject-new-css-rules/
581
+ addCSS = function(css) {
582
+ var tag;
583
+ //if ('\v' == 'v') /* ie only */ {
584
+ if (document.createStyleSheet) {
585
+ document.createStyleSheet().cssText = css;
586
+ } else {
587
+ tag = document.createElement('style');
588
+ tag.type = 'text/css';
589
+ document.getElementsByTagName('head')[0].appendChild(tag);
590
+ tag[(typeof document.body.style.WebkitAppearance == 'string') /* webkit only */ ? 'innerText' : 'innerHTML'] = css;
591
+ }
592
+ };
593
+
594
+ // Provide a cross-browser interface to a few simple drawing primitives
595
+ $.fn.simpledraw = function (width, height, useExisting, interact) {
596
+ var target, mhandler;
597
+ if (useExisting && (target = this.data('_jqs_vcanvas'))) {
598
+ return target;
599
+ }
600
+
601
+ if ($.fn.sparkline.canvas === false) {
602
+ // We've already determined that neither Canvas nor VML are available
603
+ return false;
604
+
605
+ } else if ($.fn.sparkline.canvas === undefined) {
606
+ // No function defined yet -- need to see if we support Canvas or VML
607
+ var el = document.createElement('canvas');
608
+ if (!!(el.getContext && el.getContext('2d'))) {
609
+ // Canvas is available
610
+ $.fn.sparkline.canvas = function(width, height, target, interact) {
611
+ return new VCanvas_canvas(width, height, target, interact);
612
+ };
613
+ } else if (document.namespaces && !document.namespaces.v) {
614
+ // VML is available
615
+ document.namespaces.add('v', 'urn:schemas-microsoft-com:vml', '#default#VML');
616
+ $.fn.sparkline.canvas = function(width, height, target, interact) {
617
+ return new VCanvas_vml(width, height, target);
618
+ };
619
+ } else {
620
+ // Neither Canvas nor VML are available
621
+ $.fn.sparkline.canvas = false;
622
+ return false;
623
+ }
624
+ }
625
+
626
+ if (width === undefined) {
627
+ width = $(this).innerWidth();
628
+ }
629
+ if (height === undefined) {
630
+ height = $(this).innerHeight();
631
+ }
632
+
633
+ target = $.fn.sparkline.canvas(width, height, this, interact);
634
+
635
+ mhandler = $(this).data('_jqs_mhandler');
636
+ if (mhandler) {
637
+ mhandler.registerCanvas(target);
638
+ }
639
+ return target;
640
+ };
641
+
642
+ $.fn.cleardraw = function () {
643
+ var target = this.data('_jqs_vcanvas');
644
+ if (target) {
645
+ target.reset();
646
+ }
647
+ };
648
+
649
+ $.RangeMapClass = RangeMap = createClass({
650
+ init: function (map) {
651
+ var key, range, rangelist = [];
652
+ for (key in map) {
653
+ if (map.hasOwnProperty(key) && typeof key === 'string' && key.indexOf(':') > -1) {
654
+ range = key.split(':');
655
+ range[0] = range[0].length === 0 ? -Infinity : parseFloat(range[0]);
656
+ range[1] = range[1].length === 0 ? Infinity : parseFloat(range[1]);
657
+ range[2] = map[key];
658
+ rangelist.push(range);
659
+ }
660
+ }
661
+ this.map = map;
662
+ this.rangelist = rangelist || false;
663
+ },
664
+
665
+ get: function (value) {
666
+ var rangelist = this.rangelist,
667
+ i, range, result;
668
+ if ((result = this.map[value]) !== undefined) {
669
+ return result;
670
+ }
671
+ if (rangelist) {
672
+ for (i = rangelist.length; i--;) {
673
+ range = rangelist[i];
674
+ if (range[0] <= value && range[1] >= value) {
675
+ return range[2];
676
+ }
677
+ }
678
+ }
679
+ return undefined;
680
+ }
681
+ });
682
+
683
+ // Convenience function
684
+ $.range_map = function(map) {
685
+ return new RangeMap(map);
686
+ };
687
+
688
+ MouseHandler = createClass({
689
+ init: function (el, options) {
690
+ var $el = $(el);
691
+ this.$el = $el;
692
+ this.options = options;
693
+ this.currentPageX = 0;
694
+ this.currentPageY = 0;
695
+ this.el = el;
696
+ this.splist = [];
697
+ this.tooltip = null;
698
+ this.over = false;
699
+ this.displayTooltips = !options.get('disableTooltips');
700
+ this.highlightEnabled = !options.get('disableHighlight');
701
+ },
702
+
703
+ registerSparkline: function (sp) {
704
+ this.splist.push(sp);
705
+ if (this.over) {
706
+ this.updateDisplay();
707
+ }
708
+ },
709
+
710
+ registerCanvas: function (canvas) {
711
+ var $canvas = $(canvas.canvas);
712
+ this.canvas = canvas;
713
+ this.$canvas = $canvas;
714
+ $canvas.mouseenter($.proxy(this.mouseenter, this));
715
+ $canvas.mouseleave($.proxy(this.mouseleave, this));
716
+ $canvas.click($.proxy(this.mouseclick, this));
717
+ },
718
+
719
+ reset: function (removeTooltip) {
720
+ this.splist = [];
721
+ if (this.tooltip && removeTooltip) {
722
+ this.tooltip.remove();
723
+ this.tooltip = undefined;
724
+ }
725
+ },
726
+
727
+ mouseclick: function (e) {
728
+ var clickEvent = $.Event('sparklineClick');
729
+ clickEvent.originalEvent = e;
730
+ clickEvent.sparklines = this.splist;
731
+ this.$el.trigger(clickEvent);
732
+ },
733
+
734
+ mouseenter: function (e) {
735
+ $(document.body).unbind('mousemove.jqs');
736
+ $(document.body).bind('mousemove.jqs', $.proxy(this.mousemove, this));
737
+ this.over = true;
738
+ this.currentPageX = e.pageX;
739
+ this.currentPageY = e.pageY;
740
+ this.currentEl = e.target;
741
+ if (!this.tooltip && this.displayTooltips) {
742
+ this.tooltip = new Tooltip(this.options);
743
+ this.tooltip.updatePosition(e.pageX, e.pageY);
744
+ }
745
+ this.updateDisplay();
746
+ },
747
+
748
+ mouseleave: function () {
749
+ $(document.body).unbind('mousemove.jqs');
750
+ var splist = this.splist,
751
+ spcount = splist.length,
752
+ needsRefresh = false,
753
+ sp, i;
754
+ this.over = false;
755
+ this.currentEl = null;
756
+
757
+ if (this.tooltip) {
758
+ this.tooltip.remove();
759
+ this.tooltip = null;
760
+ }
761
+
762
+ for (i = 0; i < spcount; i++) {
763
+ sp = splist[i];
764
+ if (sp.clearRegionHighlight()) {
765
+ needsRefresh = true;
766
+ }
767
+ }
768
+
769
+ if (needsRefresh) {
770
+ this.canvas.render();
771
+ }
772
+ },
773
+
774
+ mousemove: function (e) {
775
+ this.currentPageX = e.pageX;
776
+ this.currentPageY = e.pageY;
777
+ this.currentEl = e.target;
778
+ if (this.tooltip) {
779
+ this.tooltip.updatePosition(e.pageX, e.pageY);
780
+ }
781
+ this.updateDisplay();
782
+ },
783
+
784
+ updateDisplay: function () {
785
+ var splist = this.splist,
786
+ spcount = splist.length,
787
+ needsRefresh = false,
788
+ offset = this.$canvas.offset(),
789
+ localX = this.currentPageX - offset.left,
790
+ localY = this.currentPageY - offset.top,
791
+ tooltiphtml, sp, i, result, changeEvent;
792
+ if (!this.over) {
793
+ return;
794
+ }
795
+ for (i = 0; i < spcount; i++) {
796
+ sp = splist[i];
797
+ result = sp.setRegionHighlight(this.currentEl, localX, localY);
798
+ if (result) {
799
+ needsRefresh = true;
800
+ }
801
+ }
802
+ if (needsRefresh) {
803
+ changeEvent = $.Event('sparklineRegionChange');
804
+ changeEvent.sparklines = this.splist;
805
+ this.$el.trigger(changeEvent);
806
+ if (this.tooltip) {
807
+ tooltiphtml = '';
808
+ for (i = 0; i < spcount; i++) {
809
+ sp = splist[i];
810
+ tooltiphtml += sp.getCurrentRegionTooltip();
811
+ }
812
+ this.tooltip.setContent(tooltiphtml);
813
+ }
814
+ if (!this.disableHighlight) {
815
+ this.canvas.render();
816
+ }
817
+ }
818
+ if (result === null) {
819
+ this.mouseleave();
820
+ }
821
+ }
822
+ });
823
+
824
+
825
+ Tooltip = createClass({
826
+ sizeStyle: 'position: static !important;' +
827
+ 'display: block !important;' +
828
+ 'visibility: hidden !important;' +
829
+ 'float: left !important;',
830
+
831
+ init: function (options) {
832
+ var tooltipClassname = options.get('tooltipClassname', 'jqstooltip'),
833
+ sizetipStyle = this.sizeStyle,
834
+ offset;
835
+ this.container = options.get('tooltipContainer') || document.body;
836
+ this.tooltipOffsetX = options.get('tooltipOffsetX', 10);
837
+ this.tooltipOffsetY = options.get('tooltipOffsetY', 12);
838
+ // remove any previous lingering tooltip
839
+ $('#jqssizetip').remove();
840
+ $('#jqstooltip').remove();
841
+ this.sizetip = $('<div/>', {
842
+ id: 'jqssizetip',
843
+ style: sizetipStyle,
844
+ 'class': tooltipClassname
845
+ });
846
+ this.tooltip = $('<div/>', {
847
+ id: 'jqstooltip',
848
+ 'class': tooltipClassname
849
+ }).appendTo(this.container);
850
+ // account for the container's location
851
+ offset = this.tooltip.offset();
852
+ this.offsetLeft = offset.left;
853
+ this.offsetTop = offset.top;
854
+ this.hidden = true;
855
+ $(window).unbind('resize.jqs scroll.jqs');
856
+ $(window).bind('resize.jqs scroll.jqs', $.proxy(this.updateWindowDims, this));
857
+ this.updateWindowDims();
858
+ },
859
+
860
+ updateWindowDims: function () {
861
+ this.scrollTop = $(window).scrollTop();
862
+ this.scrollLeft = $(window).scrollLeft();
863
+ this.scrollRight = this.scrollLeft + $(window).width();
864
+ this.updatePosition();
865
+ },
866
+
867
+ getSize: function (content) {
868
+ this.sizetip.html(content).appendTo(this.container);
869
+ this.width = this.sizetip.width() + 1;
870
+ this.height = this.sizetip.height();
871
+ this.sizetip.remove();
872
+ },
873
+
874
+ setContent: function (content) {
875
+ if (!content) {
876
+ this.tooltip.css('visibility', 'hidden');
877
+ this.hidden = true;
878
+ return;
879
+ }
880
+ this.getSize(content);
881
+ this.tooltip.html(content)
882
+ .css({
883
+ 'width': this.width,
884
+ 'height': this.height,
885
+ 'visibility': 'visible'
886
+ });
887
+ if (this.hidden) {
888
+ this.hidden = false;
889
+ this.updatePosition();
890
+ }
891
+ },
892
+
893
+ updatePosition: function (x, y) {
894
+ if (x === undefined) {
895
+ if (this.mousex === undefined) {
896
+ return;
897
+ }
898
+ x = this.mousex - this.offsetLeft;
899
+ y = this.mousey - this.offsetTop;
900
+
901
+ } else {
902
+ this.mousex = x = x - this.offsetLeft;
903
+ this.mousey = y = y - this.offsetTop;
904
+ }
905
+ if (!this.height || !this.width || this.hidden) {
906
+ return;
907
+ }
908
+
909
+ y -= this.height + this.tooltipOffsetY;
910
+ x += this.tooltipOffsetX;
911
+
912
+ if (y < this.scrollTop) {
913
+ y = this.scrollTop;
914
+ }
915
+ if (x < this.scrollLeft) {
916
+ x = this.scrollLeft;
917
+ } else if (x + this.width > this.scrollRight) {
918
+ x = this.scrollRight - this.width;
919
+ }
920
+
921
+ this.tooltip.css({
922
+ 'left': x,
923
+ 'top': y
924
+ });
925
+ },
926
+
927
+ remove: function () {
928
+ this.tooltip.remove();
929
+ this.sizetip.remove();
930
+ this.sizetip = this.tooltip = undefined;
931
+ $(window).unbind('resize.jqs scroll.jqs');
932
+ }
933
+ });
934
+
935
+ initStyles = function() {
936
+ addCSS(defaultStyles);
937
+ };
938
+
939
+ $(initStyles);
940
+
941
+ pending = [];
942
+ $.fn.sparkline = function (userValues, userOptions) {
943
+ return this.each(function () {
944
+ var options = new $.fn.sparkline.options(this, userOptions),
945
+ $this = $(this),
946
+ render, i;
947
+ render = function () {
948
+ var values, width, height, tmp, mhandler, sp, vals;
949
+ if (userValues === 'html' || userValues === undefined) {
950
+ vals = this.getAttribute(options.get('tagValuesAttribute'));
951
+ if (vals === undefined || vals === null) {
952
+ vals = $this.html();
953
+ }
954
+ values = vals.replace(/(^\s*<!--)|(-->\s*$)|\s+/g, '').split(',');
955
+ } else {
956
+ values = userValues;
957
+ }
958
+
959
+ width = options.get('width') === 'auto' ? values.length * options.get('defaultPixelsPerValue') : options.get('width');
960
+ if (options.get('height') === 'auto') {
961
+ if (!options.get('composite') || !$.data(this, '_jqs_vcanvas')) {
962
+ // must be a better way to get the line height
963
+ tmp = document.createElement('span');
964
+ tmp.innerHTML = 'a';
965
+ $this.html(tmp);
966
+ height = $(tmp).innerHeight() || $(tmp).height();
967
+ $(tmp).remove();
968
+ tmp = null;
969
+ }
970
+ } else {
971
+ height = options.get('height');
972
+ }
973
+
974
+ if (!options.get('disableInteraction')) {
975
+ mhandler = $.data(this, '_jqs_mhandler');
976
+ if (!mhandler) {
977
+ mhandler = new MouseHandler(this, options);
978
+ $.data(this, '_jqs_mhandler', mhandler);
979
+ } else if (!options.get('composite')) {
980
+ mhandler.reset();
981
+ }
982
+ } else {
983
+ mhandler = false;
984
+ }
985
+
986
+ if (options.get('composite') && !$.data(this, '_jqs_vcanvas')) {
987
+ if (!$.data(this, '_jqs_errnotify')) {
988
+ alert('Attempted to attach a composite sparkline to an element with no existing sparkline');
989
+ $.data(this, '_jqs_errnotify', true);
990
+ }
991
+ return;
992
+ }
993
+
994
+ sp = new $.fn.sparkline[options.get('type')](this, values, options, width, height);
995
+
996
+ sp.render();
997
+
998
+ if (mhandler) {
999
+ mhandler.registerSparkline(sp);
1000
+ }
1001
+ };
1002
+ if (($(this).html() && !options.get('disableHiddenCheck') && $(this).is(':hidden')) || !$(this).parents('body').length) {
1003
+ if (!options.get('composite') && $.data(this, '_jqs_pending')) {
1004
+ // remove any existing references to the element
1005
+ for (i = pending.length; i; i--) {
1006
+ if (pending[i - 1][0] == this) {
1007
+ pending.splice(i - 1, 1);
1008
+ }
1009
+ }
1010
+ }
1011
+ pending.push([this, render]);
1012
+ $.data(this, '_jqs_pending', true);
1013
+ } else {
1014
+ render.call(this);
1015
+ }
1016
+ });
1017
+ };
1018
+
1019
+ $.fn.sparkline.defaults = getDefaults();
1020
+
1021
+
1022
+ $.sparkline_display_visible = function () {
1023
+ var el, i, pl;
1024
+ var done = [];
1025
+ for (i = 0, pl = pending.length; i < pl; i++) {
1026
+ el = pending[i][0];
1027
+ if ($(el).is(':visible') && !$(el).parents().is(':hidden')) {
1028
+ pending[i][1].call(el);
1029
+ $.data(pending[i][0], '_jqs_pending', false);
1030
+ done.push(i);
1031
+ } else if (!$(el).closest('html').length && !$.data(el, '_jqs_pending')) {
1032
+ // element has been inserted and removed from the DOM
1033
+ // If it was not yet inserted into the dom then the .data request
1034
+ // will return true.
1035
+ // removing from the dom causes the data to be removed.
1036
+ $.data(pending[i][0], '_jqs_pending', false);
1037
+ done.push(i);
1038
+ }
1039
+ }
1040
+ for (i = done.length; i; i--) {
1041
+ pending.splice(done[i - 1], 1);
1042
+ }
1043
+ };
1044
+
1045
+
1046
+ /**
1047
+ * User option handler
1048
+ */
1049
+ $.fn.sparkline.options = createClass({
1050
+ init: function (tag, userOptions) {
1051
+ var extendedOptions, defaults, base, tagOptionType;
1052
+ this.userOptions = userOptions = userOptions || {};
1053
+ this.tag = tag;
1054
+ this.tagValCache = {};
1055
+ defaults = $.fn.sparkline.defaults;
1056
+ base = defaults.common;
1057
+ this.tagOptionsPrefix = userOptions.enableTagOptions && (userOptions.tagOptionsPrefix || base.tagOptionsPrefix);
1058
+
1059
+ tagOptionType = this.getTagSetting('type');
1060
+ if (tagOptionType === UNSET_OPTION) {
1061
+ extendedOptions = defaults[userOptions.type || base.type];
1062
+ } else {
1063
+ extendedOptions = defaults[tagOptionType];
1064
+ }
1065
+ this.mergedOptions = $.extend({}, base, extendedOptions, userOptions);
1066
+ },
1067
+
1068
+
1069
+ getTagSetting: function (key) {
1070
+ var prefix = this.tagOptionsPrefix,
1071
+ val, i, pairs, keyval;
1072
+ if (prefix === false || prefix === undefined) {
1073
+ return UNSET_OPTION;
1074
+ }
1075
+ if (this.tagValCache.hasOwnProperty(key)) {
1076
+ val = this.tagValCache.key;
1077
+ } else {
1078
+ val = this.tag.getAttribute(prefix + key);
1079
+ if (val === undefined || val === null) {
1080
+ val = UNSET_OPTION;
1081
+ } else if (val.substr(0, 1) === '[') {
1082
+ val = val.substr(1, val.length - 2).split(',');
1083
+ for (i = val.length; i--;) {
1084
+ val[i] = normalizeValue(val[i].replace(/(^\s*)|(\s*$)/g, ''));
1085
+ }
1086
+ } else if (val.substr(0, 1) === '{') {
1087
+ pairs = val.substr(1, val.length - 2).split(',');
1088
+ val = {};
1089
+ for (i = pairs.length; i--;) {
1090
+ keyval = pairs[i].split(':', 2);
1091
+ val[keyval[0].replace(/(^\s*)|(\s*$)/g, '')] = normalizeValue(keyval[1].replace(/(^\s*)|(\s*$)/g, ''));
1092
+ }
1093
+ } else {
1094
+ val = normalizeValue(val);
1095
+ }
1096
+ this.tagValCache.key = val;
1097
+ }
1098
+ return val;
1099
+ },
1100
+
1101
+ get: function (key, defaultval) {
1102
+ var tagOption = this.getTagSetting(key),
1103
+ result;
1104
+ if (tagOption !== UNSET_OPTION) {
1105
+ return tagOption;
1106
+ }
1107
+ return (result = this.mergedOptions[key]) === undefined ? defaultval : result;
1108
+ }
1109
+ });
1110
+
1111
+
1112
+ $.fn.sparkline._base = createClass({
1113
+ disabled: false,
1114
+
1115
+ init: function (el, values, options, width, height) {
1116
+ this.el = el;
1117
+ this.$el = $(el);
1118
+ this.values = values;
1119
+ this.options = options;
1120
+ this.width = width;
1121
+ this.height = height;
1122
+ this.currentRegion = undefined;
1123
+ },
1124
+
1125
+ /**
1126
+ * Setup the canvas
1127
+ */
1128
+ initTarget: function () {
1129
+ var interactive = !this.options.get('disableInteraction');
1130
+ if (!(this.target = this.$el.simpledraw(this.width, this.height, this.options.get('composite'), interactive))) {
1131
+ this.disabled = true;
1132
+ } else {
1133
+ this.canvasWidth = this.target.pixelWidth;
1134
+ this.canvasHeight = this.target.pixelHeight;
1135
+ }
1136
+ },
1137
+
1138
+ /**
1139
+ * Actually render the chart to the canvas
1140
+ */
1141
+ render: function () {
1142
+ if (this.disabled) {
1143
+ this.el.innerHTML = '';
1144
+ return false;
1145
+ }
1146
+ return true;
1147
+ },
1148
+
1149
+ /**
1150
+ * Return a region id for a given x/y co-ordinate
1151
+ */
1152
+ getRegion: function (x, y) {
1153
+ },
1154
+
1155
+ /**
1156
+ * Highlight an item based on the moused-over x,y co-ordinate
1157
+ */
1158
+ setRegionHighlight: function (el, x, y) {
1159
+ var currentRegion = this.currentRegion,
1160
+ highlightEnabled = !this.options.get('disableHighlight'),
1161
+ newRegion;
1162
+ if (x > this.canvasWidth || y > this.canvasHeight || x < 0 || y < 0) {
1163
+ return null;
1164
+ }
1165
+ newRegion = this.getRegion(el, x, y);
1166
+ if (currentRegion !== newRegion) {
1167
+ if (currentRegion !== undefined && highlightEnabled) {
1168
+ this.removeHighlight();
1169
+ }
1170
+ this.currentRegion = newRegion;
1171
+ if (newRegion !== undefined && highlightEnabled) {
1172
+ this.renderHighlight();
1173
+ }
1174
+ return true;
1175
+ }
1176
+ return false;
1177
+ },
1178
+
1179
+ /**
1180
+ * Reset any currently highlighted item
1181
+ */
1182
+ clearRegionHighlight: function () {
1183
+ if (this.currentRegion !== undefined) {
1184
+ this.removeHighlight();
1185
+ this.currentRegion = undefined;
1186
+ return true;
1187
+ }
1188
+ return false;
1189
+ },
1190
+
1191
+ renderHighlight: function () {
1192
+ this.changeHighlight(true);
1193
+ },
1194
+
1195
+ removeHighlight: function () {
1196
+ this.changeHighlight(false);
1197
+ },
1198
+
1199
+ changeHighlight: function (highlight) {},
1200
+
1201
+ /**
1202
+ * Fetch the HTML to display as a tooltip
1203
+ */
1204
+ getCurrentRegionTooltip: function () {
1205
+ var options = this.options,
1206
+ header = '',
1207
+ entries = [],
1208
+ fields, formats, formatlen, fclass, text, i,
1209
+ showFields, showFieldsKey, newFields, fv,
1210
+ formatter, format, fieldlen, j;
1211
+ if (this.currentRegion === undefined) {
1212
+ return '';
1213
+ }
1214
+ fields = this.getCurrentRegionFields();
1215
+ formatter = options.get('tooltipFormatter');
1216
+ if (formatter) {
1217
+ return formatter(this, options, fields);
1218
+ }
1219
+ if (options.get('tooltipChartTitle')) {
1220
+ header += '<div class="jqs jqstitle">' + options.get('tooltipChartTitle') + '</div>\n';
1221
+ }
1222
+ formats = this.options.get('tooltipFormat');
1223
+ if (!formats) {
1224
+ return '';
1225
+ }
1226
+ if (!$.isArray(formats)) {
1227
+ formats = [formats];
1228
+ }
1229
+ if (!$.isArray(fields)) {
1230
+ fields = [fields];
1231
+ }
1232
+ showFields = this.options.get('tooltipFormatFieldlist');
1233
+ showFieldsKey = this.options.get('tooltipFormatFieldlistKey');
1234
+ if (showFields && showFieldsKey) {
1235
+ // user-selected ordering of fields
1236
+ newFields = [];
1237
+ for (i = fields.length; i--;) {
1238
+ fv = fields[i][showFieldsKey];
1239
+ if ((j = $.inArray(fv, showFields)) != -1) {
1240
+ newFields[j] = fields[i];
1241
+ }
1242
+ }
1243
+ fields = newFields;
1244
+ }
1245
+ formatlen = formats.length;
1246
+ fieldlen = fields.length;
1247
+ for (i = 0; i < formatlen; i++) {
1248
+ format = formats[i];
1249
+ if (typeof format === 'string') {
1250
+ format = new SPFormat(format);
1251
+ }
1252
+ fclass = format.fclass || 'jqsfield';
1253
+ for (j = 0; j < fieldlen; j++) {
1254
+ if (!fields[j].isNull || !options.get('tooltipSkipNull')) {
1255
+ $.extend(fields[j], {
1256
+ prefix: options.get('tooltipPrefix'),
1257
+ suffix: options.get('tooltipSuffix')
1258
+ });
1259
+ text = format.render(fields[j], options.get('tooltipValueLookups'), options);
1260
+ entries.push('<div class="' + fclass + '">' + text + '</div>');
1261
+ }
1262
+ }
1263
+ }
1264
+ if (entries.length) {
1265
+ return header + entries.join('\n');
1266
+ }
1267
+ return '';
1268
+ },
1269
+
1270
+ getCurrentRegionFields: function () {},
1271
+
1272
+ calcHighlightColor: function (color, options) {
1273
+ var highlightColor = options.get('highlightColor'),
1274
+ lighten = options.get('highlightLighten'),
1275
+ parse, mult, rgbnew, i;
1276
+ if (highlightColor) {
1277
+ return highlightColor;
1278
+ }
1279
+ if (lighten) {
1280
+ // extract RGB values
1281
+ parse = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(color) || /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(color);
1282
+ if (parse) {
1283
+ rgbnew = [];
1284
+ mult = color.length === 4 ? 16 : 1;
1285
+ for (i = 0; i < 3; i++) {
1286
+ rgbnew[i] = clipval(Math.round(parseInt(parse[i + 1], 16) * mult * lighten), 0, 255);
1287
+ }
1288
+ return 'rgb(' + rgbnew.join(',') + ')';
1289
+ }
1290
+
1291
+ }
1292
+ return color;
1293
+ }
1294
+
1295
+ });
1296
+
1297
+ barHighlightMixin = {
1298
+ changeHighlight: function (highlight) {
1299
+ var currentRegion = this.currentRegion,
1300
+ target = this.target,
1301
+ shapeids = this.regionShapes[currentRegion],
1302
+ newShapes;
1303
+ // will be null if the region value was null
1304
+ if (shapeids) {
1305
+ newShapes = this.renderRegion(currentRegion, highlight);
1306
+ if ($.isArray(newShapes) || $.isArray(shapeids)) {
1307
+ target.replaceWithShapes(shapeids, newShapes);
1308
+ this.regionShapes[currentRegion] = $.map(newShapes, function (newShape) {
1309
+ return newShape.id;
1310
+ });
1311
+ } else {
1312
+ target.replaceWithShape(shapeids, newShapes);
1313
+ this.regionShapes[currentRegion] = newShapes.id;
1314
+ }
1315
+ }
1316
+ },
1317
+
1318
+ render: function () {
1319
+ var values = this.values,
1320
+ target = this.target,
1321
+ regionShapes = this.regionShapes,
1322
+ shapes, ids, i, j;
1323
+
1324
+ if (!this.cls._super.render.call(this)) {
1325
+ return;
1326
+ }
1327
+ for (i = values.length; i--;) {
1328
+ shapes = this.renderRegion(i);
1329
+ if (shapes) {
1330
+ if ($.isArray(shapes)) {
1331
+ ids = [];
1332
+ for (j = shapes.length; j--;) {
1333
+ shapes[j].append();
1334
+ ids.push(shapes[j].id);
1335
+ }
1336
+ regionShapes[i] = ids;
1337
+ } else {
1338
+ shapes.append();
1339
+ regionShapes[i] = shapes.id; // store just the shapeid
1340
+ }
1341
+ } else {
1342
+ // null value
1343
+ regionShapes[i] = null;
1344
+ }
1345
+ }
1346
+ target.render();
1347
+ }
1348
+ };
1349
+
1350
+ /**
1351
+ * Line charts
1352
+ */
1353
+ $.fn.sparkline.line = line = createClass($.fn.sparkline._base, {
1354
+ type: 'line',
1355
+
1356
+ init: function (el, values, options, width, height) {
1357
+ line._super.init.call(this, el, values, options, width, height);
1358
+ this.vertices = [];
1359
+ this.regionMap = [];
1360
+ this.xvalues = [];
1361
+ this.yvalues = [];
1362
+ this.yminmax = [];
1363
+ this.hightlightSpotId = null;
1364
+ this.lastShapeId = null;
1365
+ this.initTarget();
1366
+ },
1367
+
1368
+ getRegion: function (el, x, y) {
1369
+ var i,
1370
+ regionMap = this.regionMap; // maps regions to value positions
1371
+ for (i = regionMap.length; i--;) {
1372
+ if (regionMap[i] !== null && x >= regionMap[i][0] && x <= regionMap[i][1]) {
1373
+ return regionMap[i][2];
1374
+ }
1375
+ }
1376
+ return undefined;
1377
+ },
1378
+
1379
+ getCurrentRegionFields: function () {
1380
+ var currentRegion = this.currentRegion;
1381
+ return {
1382
+ isNull: this.yvalues[currentRegion] === null,
1383
+ x: this.xvalues[currentRegion],
1384
+ y: this.yvalues[currentRegion],
1385
+ color: this.options.get('lineColor'),
1386
+ fillColor: this.options.get('fillColor'),
1387
+ offset: currentRegion
1388
+ };
1389
+ },
1390
+
1391
+ renderHighlight: function () {
1392
+ var currentRegion = this.currentRegion,
1393
+ target = this.target,
1394
+ vertex = this.vertices[currentRegion],
1395
+ options = this.options,
1396
+ spotRadius = options.get('spotRadius'),
1397
+ highlightSpotColor = options.get('highlightSpotColor'),
1398
+ highlightLineColor = options.get('highlightLineColor'),
1399
+ highlightSpot, highlightLine;
1400
+
1401
+ if (!vertex) {
1402
+ return;
1403
+ }
1404
+ if (spotRadius && highlightSpotColor) {
1405
+ highlightSpot = target.drawCircle(vertex[0], vertex[1],
1406
+ spotRadius, undefined, highlightSpotColor);
1407
+ this.highlightSpotId = highlightSpot.id;
1408
+ target.insertAfterShape(this.lastShapeId, highlightSpot);
1409
+ }
1410
+ if (highlightLineColor) {
1411
+ highlightLine = target.drawLine(vertex[0], this.canvasTop, vertex[0],
1412
+ this.canvasTop + this.canvasHeight, highlightLineColor);
1413
+ this.highlightLineId = highlightLine.id;
1414
+ target.insertAfterShape(this.lastShapeId, highlightLine);
1415
+ }
1416
+ },
1417
+
1418
+ removeHighlight: function () {
1419
+ var target = this.target;
1420
+ if (this.highlightSpotId) {
1421
+ target.removeShapeId(this.highlightSpotId);
1422
+ this.highlightSpotId = null;
1423
+ }
1424
+ if (this.highlightLineId) {
1425
+ target.removeShapeId(this.highlightLineId);
1426
+ this.highlightLineId = null;
1427
+ }
1428
+ },
1429
+
1430
+ scanValues: function () {
1431
+ var values = this.values,
1432
+ valcount = values.length,
1433
+ xvalues = this.xvalues,
1434
+ yvalues = this.yvalues,
1435
+ yminmax = this.yminmax,
1436
+ i, val, isStr, isArray, sp;
1437
+ for (i = 0; i < valcount; i++) {
1438
+ val = values[i];
1439
+ isStr = typeof(values[i]) === 'string';
1440
+ isArray = typeof(values[i]) === 'object' && values[i] instanceof Array;
1441
+ sp = isStr && values[i].split(':');
1442
+ if (isStr && sp.length === 2) { // x:y
1443
+ xvalues.push(Number(sp[0]));
1444
+ yvalues.push(Number(sp[1]));
1445
+ yminmax.push(Number(sp[1]));
1446
+ } else if (isArray) {
1447
+ xvalues.push(val[0]);
1448
+ yvalues.push(val[1]);
1449
+ yminmax.push(val[1]);
1450
+ } else {
1451
+ xvalues.push(i);
1452
+ if (values[i] === null || values[i] === 'null') {
1453
+ yvalues.push(null);
1454
+ } else {
1455
+ yvalues.push(Number(val));
1456
+ yminmax.push(Number(val));
1457
+ }
1458
+ }
1459
+ }
1460
+ if (this.options.get('xvalues')) {
1461
+ xvalues = this.options.get('xvalues');
1462
+ }
1463
+
1464
+ this.maxy = this.maxyorg = Math.max.apply(Math, yminmax);
1465
+ this.miny = this.minyorg = Math.min.apply(Math, yminmax);
1466
+
1467
+ this.maxx = Math.max.apply(Math, xvalues);
1468
+ this.minx = Math.min.apply(Math, xvalues);
1469
+
1470
+ this.xvalues = xvalues;
1471
+ this.yvalues = yvalues;
1472
+ this.yminmax = yminmax;
1473
+
1474
+ },
1475
+
1476
+ processRangeOptions: function () {
1477
+ var options = this.options,
1478
+ normalRangeMin = options.get('normalRangeMin'),
1479
+ normalRangeMax = options.get('normalRangeMax');
1480
+
1481
+ if (normalRangeMin !== undefined) {
1482
+ if (normalRangeMin < this.miny) {
1483
+ this.miny = normalRangeMin;
1484
+ }
1485
+ if (normalRangeMax > this.maxy) {
1486
+ this.maxy = normalRangeMax;
1487
+ }
1488
+ }
1489
+ if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < this.miny)) {
1490
+ this.miny = options.get('chartRangeMin');
1491
+ }
1492
+ if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > this.maxy)) {
1493
+ this.maxy = options.get('chartRangeMax');
1494
+ }
1495
+ if (options.get('chartRangeMinX') !== undefined && (options.get('chartRangeClipX') || options.get('chartRangeMinX') < this.minx)) {
1496
+ this.minx = options.get('chartRangeMinX');
1497
+ }
1498
+ if (options.get('chartRangeMaxX') !== undefined && (options.get('chartRangeClipX') || options.get('chartRangeMaxX') > this.maxx)) {
1499
+ this.maxx = options.get('chartRangeMaxX');
1500
+ }
1501
+
1502
+ },
1503
+
1504
+ drawNormalRange: function (canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey) {
1505
+ var normalRangeMin = this.options.get('normalRangeMin'),
1506
+ normalRangeMax = this.options.get('normalRangeMax'),
1507
+ ytop = canvasTop + Math.round(canvasHeight - (canvasHeight * ((normalRangeMax - this.miny) / rangey))),
1508
+ height = Math.round((canvasHeight * (normalRangeMax - normalRangeMin)) / rangey);
1509
+ this.target.drawRect(canvasLeft, ytop, canvasWidth, height, undefined, this.options.get('normalRangeColor')).append();
1510
+ },
1511
+
1512
+ render: function () {
1513
+ var options = this.options,
1514
+ target = this.target,
1515
+ canvasWidth = this.canvasWidth,
1516
+ canvasHeight = this.canvasHeight,
1517
+ vertices = this.vertices,
1518
+ spotRadius = options.get('spotRadius'),
1519
+ regionMap = this.regionMap,
1520
+ rangex, rangey, yvallast,
1521
+ canvasTop, canvasLeft,
1522
+ vertex, path, paths, x, y, xnext, xpos, xposnext,
1523
+ last, next, yvalcount, lineShapes, fillShapes, plen,
1524
+ valueSpots, hlSpotsEnabled, color, xvalues, yvalues, i;
1525
+
1526
+ if (!line._super.render.call(this)) {
1527
+ return;
1528
+ }
1529
+
1530
+ this.scanValues();
1531
+ this.processRangeOptions();
1532
+
1533
+ xvalues = this.xvalues;
1534
+ yvalues = this.yvalues;
1535
+
1536
+ if (!this.yminmax.length || this.yvalues.length < 2) {
1537
+ // empty or all null valuess
1538
+ return;
1539
+ }
1540
+
1541
+ canvasTop = canvasLeft = 0;
1542
+
1543
+ rangex = this.maxx - this.minx === 0 ? 1 : this.maxx - this.minx;
1544
+ rangey = this.maxy - this.miny === 0 ? 1 : this.maxy - this.miny;
1545
+ yvallast = this.yvalues.length - 1;
1546
+
1547
+ if (spotRadius && (canvasWidth < (spotRadius * 4) || canvasHeight < (spotRadius * 4))) {
1548
+ spotRadius = 0;
1549
+ }
1550
+ if (spotRadius) {
1551
+ // adjust the canvas size as required so that spots will fit
1552
+ hlSpotsEnabled = options.get('highlightSpotColor') && !options.get('disableInteraction');
1553
+ if (hlSpotsEnabled || options.get('minSpotColor') || (options.get('spotColor') && yvalues[yvallast] === this.miny)) {
1554
+ canvasHeight -= Math.ceil(spotRadius);
1555
+ }
1556
+ if (hlSpotsEnabled || options.get('maxSpotColor') || (options.get('spotColor') && yvalues[yvallast] === this.maxy)) {
1557
+ canvasHeight -= Math.ceil(spotRadius);
1558
+ canvasTop += Math.ceil(spotRadius);
1559
+ }
1560
+ if (hlSpotsEnabled ||
1561
+ ((options.get('minSpotColor') || options.get('maxSpotColor')) && (yvalues[0] === this.miny || yvalues[0] === this.maxy))) {
1562
+ canvasLeft += Math.ceil(spotRadius);
1563
+ canvasWidth -= Math.ceil(spotRadius);
1564
+ }
1565
+ if (hlSpotsEnabled || options.get('spotColor') ||
1566
+ (options.get('minSpotColor') || options.get('maxSpotColor') &&
1567
+ (yvalues[yvallast] === this.miny || yvalues[yvallast] === this.maxy))) {
1568
+ canvasWidth -= Math.ceil(spotRadius);
1569
+ }
1570
+ }
1571
+
1572
+
1573
+ canvasHeight--;
1574
+
1575
+ if (options.get('normalRangeMin') !== undefined && !options.get('drawNormalOnTop')) {
1576
+ this.drawNormalRange(canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey);
1577
+ }
1578
+
1579
+ path = [];
1580
+ paths = [path];
1581
+ last = next = null;
1582
+ yvalcount = yvalues.length;
1583
+ for (i = 0; i < yvalcount; i++) {
1584
+ x = xvalues[i];
1585
+ xnext = xvalues[i + 1];
1586
+ y = yvalues[i];
1587
+ xpos = canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex));
1588
+ xposnext = i < yvalcount - 1 ? canvasLeft + Math.round((xnext - this.minx) * (canvasWidth / rangex)) : canvasWidth;
1589
+ next = xpos + ((xposnext - xpos) / 2);
1590
+ regionMap[i] = [last || 0, next, i];
1591
+ last = next;
1592
+ if (y === null) {
1593
+ if (i) {
1594
+ if (yvalues[i - 1] !== null) {
1595
+ path = [];
1596
+ paths.push(path);
1597
+ }
1598
+ vertices.push(null);
1599
+ }
1600
+ } else {
1601
+ if (y < this.miny) {
1602
+ y = this.miny;
1603
+ }
1604
+ if (y > this.maxy) {
1605
+ y = this.maxy;
1606
+ }
1607
+ if (!path.length) {
1608
+ // previous value was null
1609
+ path.push([xpos, canvasTop + canvasHeight]);
1610
+ }
1611
+ vertex = [xpos, canvasTop + Math.round(canvasHeight - (canvasHeight * ((y - this.miny) / rangey)))];
1612
+ path.push(vertex);
1613
+ vertices.push(vertex);
1614
+ }
1615
+ }
1616
+
1617
+ lineShapes = [];
1618
+ fillShapes = [];
1619
+ plen = paths.length;
1620
+ for (i = 0; i < plen; i++) {
1621
+ path = paths[i];
1622
+ if (path.length) {
1623
+ if (options.get('fillColor')) {
1624
+ path.push([path[path.length - 1][0], (canvasTop + canvasHeight)]);
1625
+ fillShapes.push(path.slice(0));
1626
+ path.pop();
1627
+ }
1628
+ // if there's only a single point in this path, then we want to display it
1629
+ // as a vertical line which means we keep path[0] as is
1630
+ if (path.length > 2) {
1631
+ // else we want the first value
1632
+ path[0] = [path[0][0], path[1][1]];
1633
+ }
1634
+ lineShapes.push(path);
1635
+ }
1636
+ }
1637
+
1638
+ // draw the fill first, then optionally the normal range, then the line on top of that
1639
+ plen = fillShapes.length;
1640
+ for (i = 0; i < plen; i++) {
1641
+ target.drawShape(fillShapes[i],
1642
+ options.get('fillColor'), options.get('fillColor')).append();
1643
+ }
1644
+
1645
+ if (options.get('normalRangeMin') !== undefined && options.get('drawNormalOnTop')) {
1646
+ this.drawNormalRange(canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey);
1647
+ }
1648
+
1649
+ plen = lineShapes.length;
1650
+ for (i = 0; i < plen; i++) {
1651
+ target.drawShape(lineShapes[i], options.get('lineColor'), undefined,
1652
+ options.get('lineWidth')).append();
1653
+ }
1654
+
1655
+ if (spotRadius && options.get('valueSpots')) {
1656
+ valueSpots = options.get('valueSpots');
1657
+ if (valueSpots.get === undefined) {
1658
+ valueSpots = new RangeMap(valueSpots);
1659
+ }
1660
+ for (i = 0; i < yvalcount; i++) {
1661
+ color = valueSpots.get(yvalues[i]);
1662
+ if (color) {
1663
+ target.drawCircle(canvasLeft + Math.round((xvalues[i] - this.minx) * (canvasWidth / rangex)),
1664
+ canvasTop + Math.round(canvasHeight - (canvasHeight * ((yvalues[i] - this.miny) / rangey))),
1665
+ spotRadius, undefined,
1666
+ color).append();
1667
+ }
1668
+ }
1669
+
1670
+ }
1671
+ if (spotRadius && options.get('spotColor') && yvalues[yvallast] !== null) {
1672
+ target.drawCircle(canvasLeft + Math.round((xvalues[xvalues.length - 1] - this.minx) * (canvasWidth / rangex)),
1673
+ canvasTop + Math.round(canvasHeight - (canvasHeight * ((yvalues[yvallast] - this.miny) / rangey))),
1674
+ spotRadius, undefined,
1675
+ options.get('spotColor')).append();
1676
+ }
1677
+ if (this.maxy !== this.minyorg) {
1678
+ if (spotRadius && options.get('minSpotColor')) {
1679
+ x = xvalues[$.inArray(this.minyorg, yvalues)];
1680
+ target.drawCircle(canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex)),
1681
+ canvasTop + Math.round(canvasHeight - (canvasHeight * ((this.minyorg - this.miny) / rangey))),
1682
+ spotRadius, undefined,
1683
+ options.get('minSpotColor')).append();
1684
+ }
1685
+ if (spotRadius && options.get('maxSpotColor')) {
1686
+ x = xvalues[$.inArray(this.maxyorg, yvalues)];
1687
+ target.drawCircle(canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex)),
1688
+ canvasTop + Math.round(canvasHeight - (canvasHeight * ((this.maxyorg - this.miny) / rangey))),
1689
+ spotRadius, undefined,
1690
+ options.get('maxSpotColor')).append();
1691
+ }
1692
+ }
1693
+
1694
+ this.lastShapeId = target.getLastShapeId();
1695
+ this.canvasTop = canvasTop;
1696
+ target.render();
1697
+ }
1698
+ });
1699
+
1700
+ /**
1701
+ * Bar charts
1702
+ */
1703
+ $.fn.sparkline.bar = bar = createClass($.fn.sparkline._base, barHighlightMixin, {
1704
+ type: 'bar',
1705
+
1706
+ init: function (el, values, options, width, height) {
1707
+ var barWidth = parseInt(options.get('barWidth'), 10),
1708
+ barSpacing = parseInt(options.get('barSpacing'), 10),
1709
+ chartRangeMin = options.get('chartRangeMin'),
1710
+ chartRangeMax = options.get('chartRangeMax'),
1711
+ chartRangeClip = options.get('chartRangeClip'),
1712
+ stackMin = Infinity,
1713
+ stackMax = -Infinity,
1714
+ isStackString, groupMin, groupMax, stackRanges,
1715
+ numValues, i, vlen, range, zeroAxis, xaxisOffset, min, max, clipMin, clipMax,
1716
+ stacked, vlist, j, slen, svals, val, yoffset, yMaxCalc, canvasHeightEf;
1717
+ bar._super.init.call(this, el, values, options, width, height);
1718
+
1719
+ // scan values to determine whether to stack bars
1720
+ for (i = 0, vlen = values.length; i < vlen; i++) {
1721
+ val = values[i];
1722
+ isStackString = typeof(val) === 'string' && val.indexOf(':') > -1;
1723
+ if (isStackString || $.isArray(val)) {
1724
+ stacked = true;
1725
+ if (isStackString) {
1726
+ val = values[i] = normalizeValues(val.split(':'));
1727
+ }
1728
+ val = remove(val, null); // min/max will treat null as zero
1729
+ groupMin = Math.min.apply(Math, val);
1730
+ groupMax = Math.max.apply(Math, val);
1731
+ if (groupMin < stackMin) {
1732
+ stackMin = groupMin;
1733
+ }
1734
+ if (groupMax > stackMax) {
1735
+ stackMax = groupMax;
1736
+ }
1737
+ }
1738
+ }
1739
+
1740
+ this.stacked = stacked;
1741
+ this.regionShapes = {};
1742
+ this.barWidth = barWidth;
1743
+ this.barSpacing = barSpacing;
1744
+ this.totalBarWidth = barWidth + barSpacing;
1745
+ this.width = width = (values.length * barWidth) + ((values.length - 1) * barSpacing);
1746
+
1747
+ this.initTarget();
1748
+
1749
+ if (chartRangeClip) {
1750
+ clipMin = chartRangeMin === undefined ? -Infinity : chartRangeMin;
1751
+ clipMax = chartRangeMax === undefined ? Infinity : chartRangeMax;
1752
+ }
1753
+
1754
+ numValues = [];
1755
+ stackRanges = stacked ? [] : numValues;
1756
+ var stackTotals = [];
1757
+ var stackRangesNeg = [];
1758
+ for (i = 0, vlen = values.length; i < vlen; i++) {
1759
+ if (stacked) {
1760
+ vlist = values[i];
1761
+ values[i] = svals = [];
1762
+ stackTotals[i] = 0;
1763
+ stackRanges[i] = stackRangesNeg[i] = 0;
1764
+ for (j = 0, slen = vlist.length; j < slen; j++) {
1765
+ val = svals[j] = chartRangeClip ? clipval(vlist[j], clipMin, clipMax) : vlist[j];
1766
+ if (val !== null) {
1767
+ if (val > 0) {
1768
+ stackTotals[i] += val;
1769
+ }
1770
+ if (stackMin < 0 && stackMax > 0) {
1771
+ if (val < 0) {
1772
+ stackRangesNeg[i] += Math.abs(val);
1773
+ } else {
1774
+ stackRanges[i] += val;
1775
+ }
1776
+ } else {
1777
+ stackRanges[i] += Math.abs(val - (val < 0 ? stackMax : stackMin));
1778
+ }
1779
+ numValues.push(val);
1780
+ }
1781
+ }
1782
+ } else {
1783
+ val = chartRangeClip ? clipval(values[i], clipMin, clipMax) : values[i];
1784
+ val = values[i] = normalizeValue(val);
1785
+ if (val !== null) {
1786
+ numValues.push(val);
1787
+ }
1788
+ }
1789
+ }
1790
+ this.max = max = Math.max.apply(Math, numValues);
1791
+ this.min = min = Math.min.apply(Math, numValues);
1792
+ this.stackMax = stackMax = stacked ? Math.max.apply(Math, stackTotals) : max;
1793
+ this.stackMin = stackMin = stacked ? Math.min.apply(Math, numValues) : min;
1794
+
1795
+ if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < min)) {
1796
+ min = options.get('chartRangeMin');
1797
+ }
1798
+ if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > max)) {
1799
+ max = options.get('chartRangeMax');
1800
+ }
1801
+
1802
+ this.zeroAxis = zeroAxis = options.get('zeroAxis', true);
1803
+ if (min <= 0 && max >= 0 && zeroAxis) {
1804
+ xaxisOffset = 0;
1805
+ } else if (zeroAxis == false) {
1806
+ xaxisOffset = min;
1807
+ } else if (min > 0) {
1808
+ xaxisOffset = min;
1809
+ } else {
1810
+ xaxisOffset = max;
1811
+ }
1812
+ this.xaxisOffset = xaxisOffset;
1813
+
1814
+ range = stacked ? (Math.max.apply(Math, stackRanges) + Math.max.apply(Math, stackRangesNeg)) : max - min;
1815
+
1816
+ // as we plot zero/min values a single pixel line, we add a pixel to all other
1817
+ // values - Reduce the effective canvas size to suit
1818
+ this.canvasHeightEf = (zeroAxis && min < 0) ? this.canvasHeight - 2 : this.canvasHeight - 1;
1819
+
1820
+ if (min < xaxisOffset) {
1821
+ yMaxCalc = (stacked && max >= 0) ? stackMax : max;
1822
+ yoffset = (yMaxCalc - xaxisOffset) / range * this.canvasHeight;
1823
+ if (yoffset !== Math.ceil(yoffset)) {
1824
+ this.canvasHeightEf -= 2;
1825
+ yoffset = Math.ceil(yoffset);
1826
+ }
1827
+ } else {
1828
+ yoffset = this.canvasHeight;
1829
+ }
1830
+ this.yoffset = yoffset;
1831
+
1832
+ if ($.isArray(options.get('colorMap'))) {
1833
+ this.colorMapByIndex = options.get('colorMap');
1834
+ this.colorMapByValue = null;
1835
+ } else {
1836
+ this.colorMapByIndex = null;
1837
+ this.colorMapByValue = options.get('colorMap');
1838
+ if (this.colorMapByValue && this.colorMapByValue.get === undefined) {
1839
+ this.colorMapByValue = new RangeMap(this.colorMapByValue);
1840
+ }
1841
+ }
1842
+
1843
+ this.range = range;
1844
+ },
1845
+
1846
+ getRegion: function (el, x, y) {
1847
+ var result = Math.floor(x / this.totalBarWidth);
1848
+ return (result < 0 || result >= this.values.length) ? undefined : result;
1849
+ },
1850
+
1851
+ getCurrentRegionFields: function () {
1852
+ var currentRegion = this.currentRegion,
1853
+ values = ensureArray(this.values[currentRegion]),
1854
+ result = [],
1855
+ value, i;
1856
+ for (i = values.length; i--;) {
1857
+ value = values[i];
1858
+ result.push({
1859
+ isNull: value === null,
1860
+ value: value,
1861
+ color: this.calcColor(i, value, currentRegion),
1862
+ offset: currentRegion
1863
+ });
1864
+ }
1865
+ return result;
1866
+ },
1867
+
1868
+ calcColor: function (stacknum, value, valuenum) {
1869
+ var colorMapByIndex = this.colorMapByIndex,
1870
+ colorMapByValue = this.colorMapByValue,
1871
+ options = this.options,
1872
+ color, newColor;
1873
+ if (this.stacked) {
1874
+ color = options.get('stackedBarColor');
1875
+ } else {
1876
+ color = (value < 0) ? options.get('negBarColor') : options.get('barColor');
1877
+ }
1878
+ if (value === 0 && options.get('zeroColor') !== undefined) {
1879
+ color = options.get('zeroColor');
1880
+ }
1881
+ if (colorMapByValue && (newColor = colorMapByValue.get(value))) {
1882
+ color = newColor;
1883
+ } else if (colorMapByIndex && colorMapByIndex.length > valuenum) {
1884
+ color = colorMapByIndex[valuenum];
1885
+ }
1886
+ return $.isArray(color) ? color[stacknum % color.length] : color;
1887
+ },
1888
+
1889
+ /**
1890
+ * Render bar(s) for a region
1891
+ */
1892
+ renderRegion: function (valuenum, highlight) {
1893
+ var vals = this.values[valuenum],
1894
+ options = this.options,
1895
+ xaxisOffset = this.xaxisOffset,
1896
+ result = [],
1897
+ range = this.range,
1898
+ stacked = this.stacked,
1899
+ target = this.target,
1900
+ x = valuenum * this.totalBarWidth,
1901
+ canvasHeightEf = this.canvasHeightEf,
1902
+ yoffset = this.yoffset,
1903
+ y, height, color, isNull, yoffsetNeg, i, valcount, val, minPlotted, allMin;
1904
+
1905
+ vals = $.isArray(vals) ? vals : [vals];
1906
+ valcount = vals.length;
1907
+ val = vals[0];
1908
+ isNull = all(null, vals);
1909
+ allMin = all(xaxisOffset, vals, true);
1910
+
1911
+ if (isNull) {
1912
+ if (options.get('nullColor')) {
1913
+ color = highlight ? options.get('nullColor') : this.calcHighlightColor(options.get('nullColor'), options);
1914
+ y = (yoffset > 0) ? yoffset - 1 : yoffset;
1915
+ return target.drawRect(x, y, this.barWidth - 1, 0, color, color);
1916
+ } else {
1917
+ return undefined;
1918
+ }
1919
+ }
1920
+ yoffsetNeg = yoffset;
1921
+ for (i = 0; i < valcount; i++) {
1922
+ val = vals[i];
1923
+
1924
+ if (stacked && val === xaxisOffset) {
1925
+ if (!allMin || minPlotted) {
1926
+ continue;
1927
+ }
1928
+ minPlotted = true;
1929
+ }
1930
+
1931
+ if (range > 0) {
1932
+ height = Math.floor(canvasHeightEf * ((Math.abs(val - xaxisOffset) / range))) + 1;
1933
+ } else {
1934
+ height = 1;
1935
+ }
1936
+ if (val < xaxisOffset || (val === xaxisOffset && yoffset === 0)) {
1937
+ y = yoffsetNeg;
1938
+ yoffsetNeg += height;
1939
+ } else {
1940
+ y = yoffset - height;
1941
+ yoffset -= height;
1942
+ }
1943
+ color = this.calcColor(i, val, valuenum);
1944
+ if (highlight) {
1945
+ color = this.calcHighlightColor(color, options);
1946
+ }
1947
+ result.push(target.drawRect(x, y, this.barWidth - 1, height - 1, color, color));
1948
+ }
1949
+ if (result.length === 1) {
1950
+ return result[0];
1951
+ }
1952
+ return result;
1953
+ }
1954
+ });
1955
+
1956
+ /**
1957
+ * Tristate charts
1958
+ */
1959
+ $.fn.sparkline.tristate = tristate = createClass($.fn.sparkline._base, barHighlightMixin, {
1960
+ type: 'tristate',
1961
+
1962
+ init: function (el, values, options, width, height) {
1963
+ var barWidth = parseInt(options.get('barWidth'), 10),
1964
+ barSpacing = parseInt(options.get('barSpacing'), 10);
1965
+ tristate._super.init.call(this, el, values, options, width, height);
1966
+
1967
+ this.regionShapes = {};
1968
+ this.barWidth = barWidth;
1969
+ this.barSpacing = barSpacing;
1970
+ this.totalBarWidth = barWidth + barSpacing;
1971
+ this.values = $.map(values, Number);
1972
+ this.width = width = (values.length * barWidth) + ((values.length - 1) * barSpacing);
1973
+
1974
+ if ($.isArray(options.get('colorMap'))) {
1975
+ this.colorMapByIndex = options.get('colorMap');
1976
+ this.colorMapByValue = null;
1977
+ } else {
1978
+ this.colorMapByIndex = null;
1979
+ this.colorMapByValue = options.get('colorMap');
1980
+ if (this.colorMapByValue && this.colorMapByValue.get === undefined) {
1981
+ this.colorMapByValue = new RangeMap(this.colorMapByValue);
1982
+ }
1983
+ }
1984
+ this.initTarget();
1985
+ },
1986
+
1987
+ getRegion: function (el, x, y) {
1988
+ return Math.floor(x / this.totalBarWidth);
1989
+ },
1990
+
1991
+ getCurrentRegionFields: function () {
1992
+ var currentRegion = this.currentRegion;
1993
+ return {
1994
+ isNull: this.values[currentRegion] === undefined,
1995
+ value: this.values[currentRegion],
1996
+ color: this.calcColor(this.values[currentRegion], currentRegion),
1997
+ offset: currentRegion
1998
+ };
1999
+ },
2000
+
2001
+ calcColor: function (value, valuenum) {
2002
+ var values = this.values,
2003
+ options = this.options,
2004
+ colorMapByIndex = this.colorMapByIndex,
2005
+ colorMapByValue = this.colorMapByValue,
2006
+ color, newColor;
2007
+
2008
+ if (colorMapByValue && (newColor = colorMapByValue.get(value))) {
2009
+ color = newColor;
2010
+ } else if (colorMapByIndex && colorMapByIndex.length > valuenum) {
2011
+ color = colorMapByIndex[valuenum];
2012
+ } else if (values[valuenum] < 0) {
2013
+ color = options.get('negBarColor');
2014
+ } else if (values[valuenum] > 0) {
2015
+ color = options.get('posBarColor');
2016
+ } else {
2017
+ color = options.get('zeroBarColor');
2018
+ }
2019
+ return color;
2020
+ },
2021
+
2022
+ renderRegion: function (valuenum, highlight) {
2023
+ var values = this.values,
2024
+ options = this.options,
2025
+ target = this.target,
2026
+ canvasHeight, height, halfHeight,
2027
+ x, y, color;
2028
+
2029
+ canvasHeight = target.pixelHeight;
2030
+ halfHeight = Math.round(canvasHeight / 2);
2031
+
2032
+ x = valuenum * this.totalBarWidth;
2033
+ if (values[valuenum] < 0) {
2034
+ y = halfHeight;
2035
+ height = halfHeight - 1;
2036
+ } else if (values[valuenum] > 0) {
2037
+ y = 0;
2038
+ height = halfHeight - 1;
2039
+ } else {
2040
+ y = halfHeight - 1;
2041
+ height = 2;
2042
+ }
2043
+ color = this.calcColor(values[valuenum], valuenum);
2044
+ if (color === null) {
2045
+ return;
2046
+ }
2047
+ if (highlight) {
2048
+ color = this.calcHighlightColor(color, options);
2049
+ }
2050
+ return target.drawRect(x, y, this.barWidth - 1, height - 1, color, color);
2051
+ }
2052
+ });
2053
+
2054
+ /**
2055
+ * Discrete charts
2056
+ */
2057
+ $.fn.sparkline.discrete = discrete = createClass($.fn.sparkline._base, barHighlightMixin, {
2058
+ type: 'discrete',
2059
+
2060
+ init: function (el, values, options, width, height) {
2061
+ discrete._super.init.call(this, el, values, options, width, height);
2062
+
2063
+ this.regionShapes = {};
2064
+ this.values = values = $.map(values, Number);
2065
+ this.min = Math.min.apply(Math, values);
2066
+ this.max = Math.max.apply(Math, values);
2067
+ this.range = this.max - this.min;
2068
+ this.width = width = options.get('width') === 'auto' ? values.length * 2 : this.width;
2069
+ this.interval = Math.floor(width / values.length);
2070
+ this.itemWidth = width / values.length;
2071
+ if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < this.min)) {
2072
+ this.min = options.get('chartRangeMin');
2073
+ }
2074
+ if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > this.max)) {
2075
+ this.max = options.get('chartRangeMax');
2076
+ }
2077
+ this.initTarget();
2078
+ if (this.target) {
2079
+ this.lineHeight = options.get('lineHeight') === 'auto' ? Math.round(this.canvasHeight * 0.3) : options.get('lineHeight');
2080
+ }
2081
+ },
2082
+
2083
+ getRegion: function (el, x, y) {
2084
+ return Math.floor(x / this.itemWidth);
2085
+ },
2086
+
2087
+ getCurrentRegionFields: function () {
2088
+ var currentRegion = this.currentRegion;
2089
+ return {
2090
+ isNull: this.values[currentRegion] === undefined,
2091
+ value: this.values[currentRegion],
2092
+ offset: currentRegion
2093
+ };
2094
+ },
2095
+
2096
+ renderRegion: function (valuenum, highlight) {
2097
+ var values = this.values,
2098
+ options = this.options,
2099
+ min = this.min,
2100
+ max = this.max,
2101
+ range = this.range,
2102
+ interval = this.interval,
2103
+ target = this.target,
2104
+ canvasHeight = this.canvasHeight,
2105
+ lineHeight = this.lineHeight,
2106
+ pheight = canvasHeight - lineHeight,
2107
+ ytop, val, color, x;
2108
+
2109
+ val = clipval(values[valuenum], min, max);
2110
+ x = valuenum * interval;
2111
+ ytop = Math.round(pheight - pheight * ((val - min) / range));
2112
+ color = (options.get('thresholdColor') && val < options.get('thresholdValue')) ? options.get('thresholdColor') : options.get('lineColor');
2113
+ if (highlight) {
2114
+ color = this.calcHighlightColor(color, options);
2115
+ }
2116
+ return target.drawLine(x, ytop, x, ytop + lineHeight, color);
2117
+ }
2118
+ });
2119
+
2120
+ /**
2121
+ * Bullet charts
2122
+ */
2123
+ $.fn.sparkline.bullet = bullet = createClass($.fn.sparkline._base, {
2124
+ type: 'bullet',
2125
+
2126
+ init: function (el, values, options, width, height) {
2127
+ var min, max, vals;
2128
+ bullet._super.init.call(this, el, values, options, width, height);
2129
+
2130
+ // values: target, performance, range1, range2, range3
2131
+ this.values = values = normalizeValues(values);
2132
+ // target or performance could be null
2133
+ vals = values.slice();
2134
+ vals[0] = vals[0] === null ? vals[2] : vals[0];
2135
+ vals[1] = values[1] === null ? vals[2] : vals[1];
2136
+ min = Math.min.apply(Math, values);
2137
+ max = Math.max.apply(Math, values);
2138
+ if (options.get('base') === undefined) {
2139
+ min = min < 0 ? min : 0;
2140
+ } else {
2141
+ min = options.get('base');
2142
+ }
2143
+ this.min = min;
2144
+ this.max = max;
2145
+ this.range = max - min;
2146
+ this.shapes = {};
2147
+ this.valueShapes = {};
2148
+ this.regiondata = {};
2149
+ this.width = width = options.get('width') === 'auto' ? '4.0em' : width;
2150
+ this.target = this.$el.simpledraw(width, height, options.get('composite'));
2151
+ if (!values.length) {
2152
+ this.disabled = true;
2153
+ }
2154
+ this.initTarget();
2155
+ },
2156
+
2157
+ getRegion: function (el, x, y) {
2158
+ var shapeid = this.target.getShapeAt(el, x, y);
2159
+ return (shapeid !== undefined && this.shapes[shapeid] !== undefined) ? this.shapes[shapeid] : undefined;
2160
+ },
2161
+
2162
+ getCurrentRegionFields: function () {
2163
+ var currentRegion = this.currentRegion;
2164
+ return {
2165
+ fieldkey: currentRegion.substr(0, 1),
2166
+ value: this.values[currentRegion.substr(1)],
2167
+ region: currentRegion
2168
+ };
2169
+ },
2170
+
2171
+ changeHighlight: function (highlight) {
2172
+ var currentRegion = this.currentRegion,
2173
+ shapeid = this.valueShapes[currentRegion],
2174
+ shape;
2175
+ delete this.shapes[shapeid];
2176
+ switch (currentRegion.substr(0, 1)) {
2177
+ case 'r':
2178
+ shape = this.renderRange(currentRegion.substr(1), highlight);
2179
+ break;
2180
+ case 'p':
2181
+ shape = this.renderPerformance(highlight);
2182
+ break;
2183
+ case 't':
2184
+ shape = this.renderTarget(highlight);
2185
+ break;
2186
+ }
2187
+ this.valueShapes[currentRegion] = shape.id;
2188
+ this.shapes[shape.id] = currentRegion;
2189
+ this.target.replaceWithShape(shapeid, shape);
2190
+ },
2191
+
2192
+ renderRange: function (rn, highlight) {
2193
+ var rangeval = this.values[rn],
2194
+ rangewidth = Math.round(this.canvasWidth * ((rangeval - this.min) / this.range)),
2195
+ color = this.options.get('rangeColors')[rn - 2];
2196
+ if (highlight) {
2197
+ color = this.calcHighlightColor(color, this.options);
2198
+ }
2199
+ return this.target.drawRect(0, 0, rangewidth - 1, this.canvasHeight - 1, color, color);
2200
+ },
2201
+
2202
+ renderPerformance: function (highlight) {
2203
+ var perfval = this.values[1],
2204
+ perfwidth = Math.round(this.canvasWidth * ((perfval - this.min) / this.range)),
2205
+ color = this.options.get('performanceColor');
2206
+ if (highlight) {
2207
+ color = this.calcHighlightColor(color, this.options);
2208
+ }
2209
+ return this.target.drawRect(0, Math.round(this.canvasHeight * 0.3), perfwidth - 1,
2210
+ Math.round(this.canvasHeight * 0.4) - 1, color, color);
2211
+ },
2212
+
2213
+ renderTarget: function (highlight) {
2214
+ var targetval = this.values[0],
2215
+ x = Math.round(this.canvasWidth * ((targetval - this.min) / this.range) - (this.options.get('targetWidth') / 2)),
2216
+ targettop = Math.round(this.canvasHeight * 0.10),
2217
+ targetheight = this.canvasHeight - (targettop * 2),
2218
+ color = this.options.get('targetColor');
2219
+ if (highlight) {
2220
+ color = this.calcHighlightColor(color, this.options);
2221
+ }
2222
+ return this.target.drawRect(x, targettop, this.options.get('targetWidth') - 1, targetheight - 1, color, color);
2223
+ },
2224
+
2225
+ render: function () {
2226
+ var vlen = this.values.length,
2227
+ target = this.target,
2228
+ i, shape;
2229
+ if (!bullet._super.render.call(this)) {
2230
+ return;
2231
+ }
2232
+ for (i = 2; i < vlen; i++) {
2233
+ shape = this.renderRange(i).append();
2234
+ this.shapes[shape.id] = 'r' + i;
2235
+ this.valueShapes['r' + i] = shape.id;
2236
+ }
2237
+ if (this.values[1] !== null) {
2238
+ shape = this.renderPerformance().append();
2239
+ this.shapes[shape.id] = 'p1';
2240
+ this.valueShapes.p1 = shape.id;
2241
+ }
2242
+ if (this.values[0] !== null) {
2243
+ shape = this.renderTarget().append();
2244
+ this.shapes[shape.id] = 't0';
2245
+ this.valueShapes.t0 = shape.id;
2246
+ }
2247
+ target.render();
2248
+ }
2249
+ });
2250
+
2251
+ /**
2252
+ * Pie charts
2253
+ */
2254
+ $.fn.sparkline.pie = pie = createClass($.fn.sparkline._base, {
2255
+ type: 'pie',
2256
+
2257
+ init: function (el, values, options, width, height) {
2258
+ var total = 0, i;
2259
+
2260
+ pie._super.init.call(this, el, values, options, width, height);
2261
+
2262
+ this.shapes = {}; // map shape ids to value offsets
2263
+ this.valueShapes = {}; // maps value offsets to shape ids
2264
+ this.values = values = $.map(values, Number);
2265
+
2266
+ if (options.get('width') === 'auto') {
2267
+ this.width = this.height;
2268
+ }
2269
+
2270
+ if (values.length > 0) {
2271
+ for (i = values.length; i--;) {
2272
+ total += values[i];
2273
+ }
2274
+ }
2275
+ this.total = total;
2276
+ this.initTarget();
2277
+ this.radius = Math.floor(Math.min(this.canvasWidth, this.canvasHeight) / 2);
2278
+ },
2279
+
2280
+ getRegion: function (el, x, y) {
2281
+ var shapeid = this.target.getShapeAt(el, x, y);
2282
+ return (shapeid !== undefined && this.shapes[shapeid] !== undefined) ? this.shapes[shapeid] : undefined;
2283
+ },
2284
+
2285
+ getCurrentRegionFields: function () {
2286
+ var currentRegion = this.currentRegion;
2287
+ return {
2288
+ isNull: this.values[currentRegion] === undefined,
2289
+ value: this.values[currentRegion],
2290
+ percent: this.values[currentRegion] / this.total * 100,
2291
+ color: this.options.get('sliceColors')[currentRegion % this.options.get('sliceColors').length],
2292
+ offset: currentRegion
2293
+ };
2294
+ },
2295
+
2296
+ changeHighlight: function (highlight) {
2297
+ var currentRegion = this.currentRegion,
2298
+ newslice = this.renderSlice(currentRegion, highlight),
2299
+ shapeid = this.valueShapes[currentRegion];
2300
+ delete this.shapes[shapeid];
2301
+ this.target.replaceWithShape(shapeid, newslice);
2302
+ this.valueShapes[currentRegion] = newslice.id;
2303
+ this.shapes[newslice.id] = currentRegion;
2304
+ },
2305
+
2306
+ renderSlice: function (valuenum, highlight) {
2307
+ var target = this.target,
2308
+ options = this.options,
2309
+ radius = this.radius,
2310
+ borderWidth = options.get('borderWidth'),
2311
+ offset = options.get('offset'),
2312
+ circle = 2 * Math.PI,
2313
+ values = this.values,
2314
+ total = this.total,
2315
+ next = offset ? (2*Math.PI)*(offset/360) : 0,
2316
+ start, end, i, vlen, color;
2317
+
2318
+ vlen = values.length;
2319
+ for (i = 0; i < vlen; i++) {
2320
+ start = next;
2321
+ end = next;
2322
+ if (total > 0) { // avoid divide by zero
2323
+ end = next + (circle * (values[i] / total));
2324
+ }
2325
+ if (valuenum === i) {
2326
+ color = options.get('sliceColors')[i % options.get('sliceColors').length];
2327
+ if (highlight) {
2328
+ color = this.calcHighlightColor(color, options);
2329
+ }
2330
+
2331
+ return target.drawPieSlice(radius, radius, radius - borderWidth, start, end, undefined, color);
2332
+ }
2333
+ next = end;
2334
+ }
2335
+ },
2336
+
2337
+ render: function () {
2338
+ var target = this.target,
2339
+ values = this.values,
2340
+ options = this.options,
2341
+ radius = this.radius,
2342
+ borderWidth = options.get('borderWidth'),
2343
+ shape, i;
2344
+
2345
+ if (!pie._super.render.call(this)) {
2346
+ return;
2347
+ }
2348
+ if (borderWidth) {
2349
+ target.drawCircle(radius, radius, Math.floor(radius - (borderWidth / 2)),
2350
+ options.get('borderColor'), undefined, borderWidth).append();
2351
+ }
2352
+ for (i = values.length; i--;) {
2353
+ if (values[i]) { // don't render zero values
2354
+ shape = this.renderSlice(i).append();
2355
+ this.valueShapes[i] = shape.id; // store just the shapeid
2356
+ this.shapes[shape.id] = i;
2357
+ }
2358
+ }
2359
+ target.render();
2360
+ }
2361
+ });
2362
+
2363
+ /**
2364
+ * Box plots
2365
+ */
2366
+ $.fn.sparkline.box = box = createClass($.fn.sparkline._base, {
2367
+ type: 'box',
2368
+
2369
+ init: function (el, values, options, width, height) {
2370
+ box._super.init.call(this, el, values, options, width, height);
2371
+ this.values = $.map(values, Number);
2372
+ this.width = options.get('width') === 'auto' ? '4.0em' : width;
2373
+ this.initTarget();
2374
+ if (!this.values.length) {
2375
+ this.disabled = 1;
2376
+ }
2377
+ },
2378
+
2379
+ /**
2380
+ * Simulate a single region
2381
+ */
2382
+ getRegion: function () {
2383
+ return 1;
2384
+ },
2385
+
2386
+ getCurrentRegionFields: function () {
2387
+ var result = [
2388
+ { field: 'lq', value: this.quartiles[0] },
2389
+ { field: 'med', value: this.quartiles[1] },
2390
+ { field: 'uq', value: this.quartiles[2] }
2391
+ ];
2392
+ if (this.loutlier !== undefined) {
2393
+ result.push({ field: 'lo', value: this.loutlier});
2394
+ }
2395
+ if (this.routlier !== undefined) {
2396
+ result.push({ field: 'ro', value: this.routlier});
2397
+ }
2398
+ if (this.lwhisker !== undefined) {
2399
+ result.push({ field: 'lw', value: this.lwhisker});
2400
+ }
2401
+ if (this.rwhisker !== undefined) {
2402
+ result.push({ field: 'rw', value: this.rwhisker});
2403
+ }
2404
+ return result;
2405
+ },
2406
+
2407
+ render: function () {
2408
+ var target = this.target,
2409
+ values = this.values,
2410
+ vlen = values.length,
2411
+ options = this.options,
2412
+ canvasWidth = this.canvasWidth,
2413
+ canvasHeight = this.canvasHeight,
2414
+ minValue = options.get('chartRangeMin') === undefined ? Math.min.apply(Math, values) : options.get('chartRangeMin'),
2415
+ maxValue = options.get('chartRangeMax') === undefined ? Math.max.apply(Math, values) : options.get('chartRangeMax'),
2416
+ canvasLeft = 0,
2417
+ lwhisker, loutlier, iqr, q1, q2, q3, rwhisker, routlier, i,
2418
+ size, unitSize;
2419
+
2420
+ if (!box._super.render.call(this)) {
2421
+ return;
2422
+ }
2423
+
2424
+ if (options.get('raw')) {
2425
+ if (options.get('showOutliers') && values.length > 5) {
2426
+ loutlier = values[0];
2427
+ lwhisker = values[1];
2428
+ q1 = values[2];
2429
+ q2 = values[3];
2430
+ q3 = values[4];
2431
+ rwhisker = values[5];
2432
+ routlier = values[6];
2433
+ } else {
2434
+ lwhisker = values[0];
2435
+ q1 = values[1];
2436
+ q2 = values[2];
2437
+ q3 = values[3];
2438
+ rwhisker = values[4];
2439
+ }
2440
+ } else {
2441
+ values.sort(function (a, b) { return a - b; });
2442
+ q1 = quartile(values, 1);
2443
+ q2 = quartile(values, 2);
2444
+ q3 = quartile(values, 3);
2445
+ iqr = q3 - q1;
2446
+ if (options.get('showOutliers')) {
2447
+ lwhisker = rwhisker = undefined;
2448
+ for (i = 0; i < vlen; i++) {
2449
+ if (lwhisker === undefined && values[i] > q1 - (iqr * options.get('outlierIQR'))) {
2450
+ lwhisker = values[i];
2451
+ }
2452
+ if (values[i] < q3 + (iqr * options.get('outlierIQR'))) {
2453
+ rwhisker = values[i];
2454
+ }
2455
+ }
2456
+ loutlier = values[0];
2457
+ routlier = values[vlen - 1];
2458
+ } else {
2459
+ lwhisker = values[0];
2460
+ rwhisker = values[vlen - 1];
2461
+ }
2462
+ }
2463
+ this.quartiles = [q1, q2, q3];
2464
+ this.lwhisker = lwhisker;
2465
+ this.rwhisker = rwhisker;
2466
+ this.loutlier = loutlier;
2467
+ this.routlier = routlier;
2468
+
2469
+ unitSize = canvasWidth / (maxValue - minValue + 1);
2470
+ if (options.get('showOutliers')) {
2471
+ canvasLeft = Math.ceil(options.get('spotRadius'));
2472
+ canvasWidth -= 2 * Math.ceil(options.get('spotRadius'));
2473
+ unitSize = canvasWidth / (maxValue - minValue + 1);
2474
+ if (loutlier < lwhisker) {
2475
+ target.drawCircle((loutlier - minValue) * unitSize + canvasLeft,
2476
+ canvasHeight / 2,
2477
+ options.get('spotRadius'),
2478
+ options.get('outlierLineColor'),
2479
+ options.get('outlierFillColor')).append();
2480
+ }
2481
+ if (routlier > rwhisker) {
2482
+ target.drawCircle((routlier - minValue) * unitSize + canvasLeft,
2483
+ canvasHeight / 2,
2484
+ options.get('spotRadius'),
2485
+ options.get('outlierLineColor'),
2486
+ options.get('outlierFillColor')).append();
2487
+ }
2488
+ }
2489
+
2490
+ // box
2491
+ target.drawRect(
2492
+ Math.round((q1 - minValue) * unitSize + canvasLeft),
2493
+ Math.round(canvasHeight * 0.1),
2494
+ Math.round((q3 - q1) * unitSize),
2495
+ Math.round(canvasHeight * 0.8),
2496
+ options.get('boxLineColor'),
2497
+ options.get('boxFillColor')).append();
2498
+ // left whisker
2499
+ target.drawLine(
2500
+ Math.round((lwhisker - minValue) * unitSize + canvasLeft),
2501
+ Math.round(canvasHeight / 2),
2502
+ Math.round((q1 - minValue) * unitSize + canvasLeft),
2503
+ Math.round(canvasHeight / 2),
2504
+ options.get('lineColor')).append();
2505
+ target.drawLine(
2506
+ Math.round((lwhisker - minValue) * unitSize + canvasLeft),
2507
+ Math.round(canvasHeight / 4),
2508
+ Math.round((lwhisker - minValue) * unitSize + canvasLeft),
2509
+ Math.round(canvasHeight - canvasHeight / 4),
2510
+ options.get('whiskerColor')).append();
2511
+ // right whisker
2512
+ target.drawLine(Math.round((rwhisker - minValue) * unitSize + canvasLeft),
2513
+ Math.round(canvasHeight / 2),
2514
+ Math.round((q3 - minValue) * unitSize + canvasLeft),
2515
+ Math.round(canvasHeight / 2),
2516
+ options.get('lineColor')).append();
2517
+ target.drawLine(
2518
+ Math.round((rwhisker - minValue) * unitSize + canvasLeft),
2519
+ Math.round(canvasHeight / 4),
2520
+ Math.round((rwhisker - minValue) * unitSize + canvasLeft),
2521
+ Math.round(canvasHeight - canvasHeight / 4),
2522
+ options.get('whiskerColor')).append();
2523
+ // median line
2524
+ target.drawLine(
2525
+ Math.round((q2 - minValue) * unitSize + canvasLeft),
2526
+ Math.round(canvasHeight * 0.1),
2527
+ Math.round((q2 - minValue) * unitSize + canvasLeft),
2528
+ Math.round(canvasHeight * 0.9),
2529
+ options.get('medianColor')).append();
2530
+ if (options.get('target')) {
2531
+ size = Math.ceil(options.get('spotRadius'));
2532
+ target.drawLine(
2533
+ Math.round((options.get('target') - minValue) * unitSize + canvasLeft),
2534
+ Math.round((canvasHeight / 2) - size),
2535
+ Math.round((options.get('target') - minValue) * unitSize + canvasLeft),
2536
+ Math.round((canvasHeight / 2) + size),
2537
+ options.get('targetColor')).append();
2538
+ target.drawLine(
2539
+ Math.round((options.get('target') - minValue) * unitSize + canvasLeft - size),
2540
+ Math.round(canvasHeight / 2),
2541
+ Math.round((options.get('target') - minValue) * unitSize + canvasLeft + size),
2542
+ Math.round(canvasHeight / 2),
2543
+ options.get('targetColor')).append();
2544
+ }
2545
+ target.render();
2546
+ }
2547
+ });
2548
+
2549
+ // Setup a very simple "virtual canvas" to make drawing the few shapes we need easier
2550
+ // This is accessible as $(foo).simpledraw()
2551
+
2552
+ VShape = createClass({
2553
+ init: function (target, id, type, args) {
2554
+ this.target = target;
2555
+ this.id = id;
2556
+ this.type = type;
2557
+ this.args = args;
2558
+ },
2559
+ append: function () {
2560
+ this.target.appendShape(this);
2561
+ return this;
2562
+ }
2563
+ });
2564
+
2565
+ VCanvas_base = createClass({
2566
+ _pxregex: /(\d+)(px)?\s*$/i,
2567
+
2568
+ init: function (width, height, target) {
2569
+ if (!width) {
2570
+ return;
2571
+ }
2572
+ this.width = width;
2573
+ this.height = height;
2574
+ this.target = target;
2575
+ this.lastShapeId = null;
2576
+ if (target[0]) {
2577
+ target = target[0];
2578
+ }
2579
+ $.data(target, '_jqs_vcanvas', this);
2580
+ },
2581
+
2582
+ drawLine: function (x1, y1, x2, y2, lineColor, lineWidth) {
2583
+ return this.drawShape([[x1, y1], [x2, y2]], lineColor, lineWidth);
2584
+ },
2585
+
2586
+ drawShape: function (path, lineColor, fillColor, lineWidth) {
2587
+ return this._genShape('Shape', [path, lineColor, fillColor, lineWidth]);
2588
+ },
2589
+
2590
+ drawCircle: function (x, y, radius, lineColor, fillColor, lineWidth) {
2591
+ return this._genShape('Circle', [x, y, radius, lineColor, fillColor, lineWidth]);
2592
+ },
2593
+
2594
+ drawPieSlice: function (x, y, radius, startAngle, endAngle, lineColor, fillColor) {
2595
+ return this._genShape('PieSlice', [x, y, radius, startAngle, endAngle, lineColor, fillColor]);
2596
+ },
2597
+
2598
+ drawRect: function (x, y, width, height, lineColor, fillColor) {
2599
+ return this._genShape('Rect', [x, y, width, height, lineColor, fillColor]);
2600
+ },
2601
+
2602
+ getElement: function () {
2603
+ return this.canvas;
2604
+ },
2605
+
2606
+ /**
2607
+ * Return the most recently inserted shape id
2608
+ */
2609
+ getLastShapeId: function () {
2610
+ return this.lastShapeId;
2611
+ },
2612
+
2613
+ /**
2614
+ * Clear and reset the canvas
2615
+ */
2616
+ reset: function () {
2617
+ alert('reset not implemented');
2618
+ },
2619
+
2620
+ _insert: function (el, target) {
2621
+ $(target).html(el);
2622
+ },
2623
+
2624
+ /**
2625
+ * Calculate the pixel dimensions of the canvas
2626
+ */
2627
+ _calculatePixelDims: function (width, height, canvas) {
2628
+ // XXX This should probably be a configurable option
2629
+ var match;
2630
+ match = this._pxregex.exec(height);
2631
+ if (match) {
2632
+ this.pixelHeight = match[1];
2633
+ } else {
2634
+ this.pixelHeight = $(canvas).height();
2635
+ }
2636
+ match = this._pxregex.exec(width);
2637
+ if (match) {
2638
+ this.pixelWidth = match[1];
2639
+ } else {
2640
+ this.pixelWidth = $(canvas).width();
2641
+ }
2642
+ },
2643
+
2644
+ /**
2645
+ * Generate a shape object and id for later rendering
2646
+ */
2647
+ _genShape: function (shapetype, shapeargs) {
2648
+ var id = shapeCount++;
2649
+ shapeargs.unshift(id);
2650
+ return new VShape(this, id, shapetype, shapeargs);
2651
+ },
2652
+
2653
+ /**
2654
+ * Add a shape to the end of the render queue
2655
+ */
2656
+ appendShape: function (shape) {
2657
+ alert('appendShape not implemented');
2658
+ },
2659
+
2660
+ /**
2661
+ * Replace one shape with another
2662
+ */
2663
+ replaceWithShape: function (shapeid, shape) {
2664
+ alert('replaceWithShape not implemented');
2665
+ },
2666
+
2667
+ /**
2668
+ * Insert one shape after another in the render queue
2669
+ */
2670
+ insertAfterShape: function (shapeid, shape) {
2671
+ alert('insertAfterShape not implemented');
2672
+ },
2673
+
2674
+ /**
2675
+ * Remove a shape from the queue
2676
+ */
2677
+ removeShapeId: function (shapeid) {
2678
+ alert('removeShapeId not implemented');
2679
+ },
2680
+
2681
+ /**
2682
+ * Find a shape at the specified x/y co-ordinates
2683
+ */
2684
+ getShapeAt: function (el, x, y) {
2685
+ alert('getShapeAt not implemented');
2686
+ },
2687
+
2688
+ /**
2689
+ * Render all queued shapes onto the canvas
2690
+ */
2691
+ render: function () {
2692
+ alert('render not implemented');
2693
+ }
2694
+ });
2695
+
2696
+ VCanvas_canvas = createClass(VCanvas_base, {
2697
+ init: function (width, height, target, interact) {
2698
+ VCanvas_canvas._super.init.call(this, width, height, target);
2699
+ this.canvas = document.createElement('canvas');
2700
+ if (target[0]) {
2701
+ target = target[0];
2702
+ }
2703
+ $.data(target, '_jqs_vcanvas', this);
2704
+ $(this.canvas).css({ display: 'inline-block', width: width, height: height, verticalAlign: 'top' });
2705
+ this._insert(this.canvas, target);
2706
+ this._calculatePixelDims(width, height, this.canvas);
2707
+ this.canvas.width = this.pixelWidth;
2708
+ this.canvas.height = this.pixelHeight;
2709
+ this.interact = interact;
2710
+ this.shapes = {};
2711
+ this.shapeseq = [];
2712
+ this.currentTargetShapeId = undefined;
2713
+ $(this.canvas).css({width: this.pixelWidth, height: this.pixelHeight});
2714
+ },
2715
+
2716
+ _getContext: function (lineColor, fillColor, lineWidth) {
2717
+ var context = this.canvas.getContext('2d');
2718
+ if (lineColor !== undefined) {
2719
+ context.strokeStyle = lineColor;
2720
+ }
2721
+ context.lineWidth = lineWidth === undefined ? 1 : lineWidth;
2722
+ if (fillColor !== undefined) {
2723
+ context.fillStyle = fillColor;
2724
+ }
2725
+ return context;
2726
+ },
2727
+
2728
+ reset: function () {
2729
+ var context = this._getContext();
2730
+ context.clearRect(0, 0, this.pixelWidth, this.pixelHeight);
2731
+ this.shapes = {};
2732
+ this.shapeseq = [];
2733
+ this.currentTargetShapeId = undefined;
2734
+ },
2735
+
2736
+ _drawShape: function (shapeid, path, lineColor, fillColor, lineWidth) {
2737
+ var context = this._getContext(lineColor, fillColor, lineWidth),
2738
+ i, plen;
2739
+ context.beginPath();
2740
+ context.moveTo(path[0][0] + 0.5, path[0][1] + 0.5);
2741
+ for (i = 1, plen = path.length; i < plen; i++) {
2742
+ context.lineTo(path[i][0] + 0.5, path[i][1] + 0.5); // the 0.5 offset gives us crisp pixel-width lines
2743
+ }
2744
+ if (lineColor !== undefined) {
2745
+ context.stroke();
2746
+ }
2747
+ if (fillColor !== undefined) {
2748
+ context.fill();
2749
+ }
2750
+ if (this.targetX !== undefined && this.targetY !== undefined &&
2751
+ context.isPointInPath(this.targetX, this.targetY)) {
2752
+ this.currentTargetShapeId = shapeid;
2753
+ }
2754
+ },
2755
+
2756
+ _drawCircle: function (shapeid, x, y, radius, lineColor, fillColor, lineWidth) {
2757
+ var context = this._getContext(lineColor, fillColor, lineWidth);
2758
+ context.beginPath();
2759
+ context.arc(x, y, radius, 0, 2 * Math.PI, false);
2760
+ if (this.targetX !== undefined && this.targetY !== undefined &&
2761
+ context.isPointInPath(this.targetX, this.targetY)) {
2762
+ this.currentTargetShapeId = shapeid;
2763
+ }
2764
+ if (lineColor !== undefined) {
2765
+ context.stroke();
2766
+ }
2767
+ if (fillColor !== undefined) {
2768
+ context.fill();
2769
+ }
2770
+ },
2771
+
2772
+ _drawPieSlice: function (shapeid, x, y, radius, startAngle, endAngle, lineColor, fillColor) {
2773
+ var context = this._getContext(lineColor, fillColor);
2774
+ context.beginPath();
2775
+ context.moveTo(x, y);
2776
+ context.arc(x, y, radius, startAngle, endAngle, false);
2777
+ context.lineTo(x, y);
2778
+ context.closePath();
2779
+ if (lineColor !== undefined) {
2780
+ context.stroke();
2781
+ }
2782
+ if (fillColor) {
2783
+ context.fill();
2784
+ }
2785
+ if (this.targetX !== undefined && this.targetY !== undefined &&
2786
+ context.isPointInPath(this.targetX, this.targetY)) {
2787
+ this.currentTargetShapeId = shapeid;
2788
+ }
2789
+ },
2790
+
2791
+ _drawRect: function (shapeid, x, y, width, height, lineColor, fillColor) {
2792
+ return this._drawShape(shapeid, [[x, y], [x + width, y], [x + width, y + height], [x, y + height], [x, y]], lineColor, fillColor);
2793
+ },
2794
+
2795
+ appendShape: function (shape) {
2796
+ this.shapes[shape.id] = shape;
2797
+ this.shapeseq.push(shape.id);
2798
+ this.lastShapeId = shape.id;
2799
+ return shape.id;
2800
+ },
2801
+
2802
+ replaceWithShape: function (shapeid, shape) {
2803
+ var shapeseq = this.shapeseq,
2804
+ i;
2805
+ this.shapes[shape.id] = shape;
2806
+ for (i = shapeseq.length; i--;) {
2807
+ if (shapeseq[i] == shapeid) {
2808
+ shapeseq[i] = shape.id;
2809
+ }
2810
+ }
2811
+ delete this.shapes[shapeid];
2812
+ },
2813
+
2814
+ replaceWithShapes: function (shapeids, shapes) {
2815
+ var shapeseq = this.shapeseq,
2816
+ shapemap = {},
2817
+ sid, i, first;
2818
+
2819
+ for (i = shapeids.length; i--;) {
2820
+ shapemap[shapeids[i]] = true;
2821
+ }
2822
+ for (i = shapeseq.length; i--;) {
2823
+ sid = shapeseq[i];
2824
+ if (shapemap[sid]) {
2825
+ shapeseq.splice(i, 1);
2826
+ delete this.shapes[sid];
2827
+ first = i;
2828
+ }
2829
+ }
2830
+ for (i = shapes.length; i--;) {
2831
+ shapeseq.splice(first, 0, shapes[i].id);
2832
+ this.shapes[shapes[i].id] = shapes[i];
2833
+ }
2834
+
2835
+ },
2836
+
2837
+ insertAfterShape: function (shapeid, shape) {
2838
+ var shapeseq = this.shapeseq,
2839
+ i;
2840
+ for (i = shapeseq.length; i--;) {
2841
+ if (shapeseq[i] === shapeid) {
2842
+ shapeseq.splice(i + 1, 0, shape.id);
2843
+ this.shapes[shape.id] = shape;
2844
+ return;
2845
+ }
2846
+ }
2847
+ },
2848
+
2849
+ removeShapeId: function (shapeid) {
2850
+ var shapeseq = this.shapeseq,
2851
+ i;
2852
+ for (i = shapeseq.length; i--;) {
2853
+ if (shapeseq[i] === shapeid) {
2854
+ shapeseq.splice(i, 1);
2855
+ break;
2856
+ }
2857
+ }
2858
+ delete this.shapes[shapeid];
2859
+ },
2860
+
2861
+ getShapeAt: function (el, x, y) {
2862
+ this.targetX = x;
2863
+ this.targetY = y;
2864
+ this.render();
2865
+ return this.currentTargetShapeId;
2866
+ },
2867
+
2868
+ render: function () {
2869
+ var shapeseq = this.shapeseq,
2870
+ shapes = this.shapes,
2871
+ shapeCount = shapeseq.length,
2872
+ context = this._getContext(),
2873
+ shapeid, shape, i;
2874
+ context.clearRect(0, 0, this.pixelWidth, this.pixelHeight);
2875
+ for (i = 0; i < shapeCount; i++) {
2876
+ shapeid = shapeseq[i];
2877
+ shape = shapes[shapeid];
2878
+ this['_draw' + shape.type].apply(this, shape.args);
2879
+ }
2880
+ if (!this.interact) {
2881
+ // not interactive so no need to keep the shapes array
2882
+ this.shapes = {};
2883
+ this.shapeseq = [];
2884
+ }
2885
+ }
2886
+
2887
+ });
2888
+
2889
+ VCanvas_vml = createClass(VCanvas_base, {
2890
+ init: function (width, height, target) {
2891
+ var groupel;
2892
+ VCanvas_vml._super.init.call(this, width, height, target);
2893
+ if (target[0]) {
2894
+ target = target[0];
2895
+ }
2896
+ $.data(target, '_jqs_vcanvas', this);
2897
+ this.canvas = document.createElement('span');
2898
+ $(this.canvas).css({ display: 'inline-block', position: 'relative', overflow: 'hidden', width: width, height: height, margin: '0px', padding: '0px', verticalAlign: 'top'});
2899
+ this._insert(this.canvas, target);
2900
+ this._calculatePixelDims(width, height, this.canvas);
2901
+ this.canvas.width = this.pixelWidth;
2902
+ this.canvas.height = this.pixelHeight;
2903
+ groupel = '<v:group coordorigin="0 0" coordsize="' + this.pixelWidth + ' ' + this.pixelHeight + '"' +
2904
+ ' style="position:absolute;top:0;left:0;width:' + this.pixelWidth + 'px;height=' + this.pixelHeight + 'px;"></v:group>';
2905
+ this.canvas.insertAdjacentHTML('beforeEnd', groupel);
2906
+ this.group = $(this.canvas).children()[0];
2907
+ this.rendered = false;
2908
+ this.prerender = '';
2909
+ },
2910
+
2911
+ _drawShape: function (shapeid, path, lineColor, fillColor, lineWidth) {
2912
+ var vpath = [],
2913
+ initial, stroke, fill, closed, vel, plen, i;
2914
+ for (i = 0, plen = path.length; i < plen; i++) {
2915
+ vpath[i] = '' + (path[i][0]) + ',' + (path[i][1]);
2916
+ }
2917
+ initial = vpath.splice(0, 1);
2918
+ lineWidth = lineWidth === undefined ? 1 : lineWidth;
2919
+ stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="' + lineWidth + 'px" strokeColor="' + lineColor + '" ';
2920
+ fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" ';
2921
+ closed = vpath[0] === vpath[vpath.length - 1] ? 'x ' : '';
2922
+ vel = '<v:shape coordorigin="0 0" coordsize="' + this.pixelWidth + ' ' + this.pixelHeight + '" ' +
2923
+ ' id="jqsshape' + shapeid + '" ' +
2924
+ stroke +
2925
+ fill +
2926
+ ' style="position:absolute;left:0px;top:0px;height:' + this.pixelHeight + 'px;width:' + this.pixelWidth + 'px;padding:0px;margin:0px;" ' +
2927
+ ' path="m ' + initial + ' l ' + vpath.join(', ') + ' ' + closed + 'e">' +
2928
+ ' </v:shape>';
2929
+ return vel;
2930
+ },
2931
+
2932
+ _drawCircle: function (shapeid, x, y, radius, lineColor, fillColor, lineWidth) {
2933
+ var stroke, fill, vel;
2934
+ x -= radius;
2935
+ y -= radius;
2936
+ stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="' + lineWidth + 'px" strokeColor="' + lineColor + '" ';
2937
+ fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" ';
2938
+ vel = '<v:oval ' +
2939
+ ' id="jqsshape' + shapeid + '" ' +
2940
+ stroke +
2941
+ fill +
2942
+ ' style="position:absolute;top:' + y + 'px; left:' + x + 'px; width:' + (radius * 2) + 'px; height:' + (radius * 2) + 'px"></v:oval>';
2943
+ return vel;
2944
+
2945
+ },
2946
+
2947
+ _drawPieSlice: function (shapeid, x, y, radius, startAngle, endAngle, lineColor, fillColor) {
2948
+ var vpath, startx, starty, endx, endy, stroke, fill, vel;
2949
+ if (startAngle === endAngle) {
2950
+ return ''; // VML seems to have problem when start angle equals end angle.
2951
+ }
2952
+ if ((endAngle - startAngle) === (2 * Math.PI)) {
2953
+ startAngle = 0.0; // VML seems to have a problem when drawing a full circle that doesn't start 0
2954
+ endAngle = (2 * Math.PI);
2955
+ }
2956
+
2957
+ startx = x + Math.round(Math.cos(startAngle) * radius);
2958
+ starty = y + Math.round(Math.sin(startAngle) * radius);
2959
+ endx = x + Math.round(Math.cos(endAngle) * radius);
2960
+ endy = y + Math.round(Math.sin(endAngle) * radius);
2961
+
2962
+ if (startx === endx && starty === endy) {
2963
+ if ((endAngle - startAngle) < Math.PI) {
2964
+ // Prevent very small slices from being mistaken as a whole pie
2965
+ return '';
2966
+ }
2967
+ // essentially going to be the entire circle, so ignore startAngle
2968
+ startx = endx = x + radius;
2969
+ starty = endy = y;
2970
+ }
2971
+
2972
+ if (startx === endx && starty === endy && (endAngle - startAngle) < Math.PI) {
2973
+ return '';
2974
+ }
2975
+
2976
+ vpath = [x - radius, y - radius, x + radius, y + radius, startx, starty, endx, endy];
2977
+ stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="1px" strokeColor="' + lineColor + '" ';
2978
+ fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" ';
2979
+ vel = '<v:shape coordorigin="0 0" coordsize="' + this.pixelWidth + ' ' + this.pixelHeight + '" ' +
2980
+ ' id="jqsshape' + shapeid + '" ' +
2981
+ stroke +
2982
+ fill +
2983
+ ' style="position:absolute;left:0px;top:0px;height:' + this.pixelHeight + 'px;width:' + this.pixelWidth + 'px;padding:0px;margin:0px;" ' +
2984
+ ' path="m ' + x + ',' + y + ' wa ' + vpath.join(', ') + ' x e">' +
2985
+ ' </v:shape>';
2986
+ return vel;
2987
+ },
2988
+
2989
+ _drawRect: function (shapeid, x, y, width, height, lineColor, fillColor) {
2990
+ return this._drawShape(shapeid, [[x, y], [x, y + height], [x + width, y + height], [x + width, y], [x, y]], lineColor, fillColor);
2991
+ },
2992
+
2993
+ reset: function () {
2994
+ this.group.innerHTML = '';
2995
+ },
2996
+
2997
+ appendShape: function (shape) {
2998
+ var vel = this['_draw' + shape.type].apply(this, shape.args);
2999
+ if (this.rendered) {
3000
+ this.group.insertAdjacentHTML('beforeEnd', vel);
3001
+ } else {
3002
+ this.prerender += vel;
3003
+ }
3004
+ this.lastShapeId = shape.id;
3005
+ return shape.id;
3006
+ },
3007
+
3008
+ replaceWithShape: function (shapeid, shape) {
3009
+ var existing = $('#jqsshape' + shapeid),
3010
+ vel = this['_draw' + shape.type].apply(this, shape.args);
3011
+ existing[0].outerHTML = vel;
3012
+ },
3013
+
3014
+ replaceWithShapes: function (shapeids, shapes) {
3015
+ // replace the first shapeid with all the new shapes then toast the remaining old shapes
3016
+ var existing = $('#jqsshape' + shapeids[0]),
3017
+ replace = '',
3018
+ slen = shapes.length,
3019
+ i;
3020
+ for (i = 0; i < slen; i++) {
3021
+ replace += this['_draw' + shapes[i].type].apply(this, shapes[i].args);
3022
+ }
3023
+ existing[0].outerHTML = replace;
3024
+ for (i = 1; i < shapeids.length; i++) {
3025
+ $('#jqsshape' + shapeids[i]).remove();
3026
+ }
3027
+ },
3028
+
3029
+ insertAfterShape: function (shapeid, shape) {
3030
+ var existing = $('#jqsshape' + shapeid),
3031
+ vel = this['_draw' + shape.type].apply(this, shape.args);
3032
+ existing[0].insertAdjacentHTML('afterEnd', vel);
3033
+ },
3034
+
3035
+ removeShapeId: function (shapeid) {
3036
+ var existing = $('#jqsshape' + shapeid);
3037
+ this.group.removeChild(existing[0]);
3038
+ },
3039
+
3040
+ getShapeAt: function (el, x, y) {
3041
+ var shapeid = el.id.substr(8);
3042
+ return shapeid;
3043
+ },
3044
+
3045
+ render: function () {
3046
+ if (!this.rendered) {
3047
+ // batch the intial render into a single repaint
3048
+ this.group.innerHTML = this.prerender;
3049
+ this.rendered = true;
3050
+ }
3051
+ }
3052
+ });
3053
+
3054
+ }))}(document, Math));
includes/mlw_dashboard.php CHANGED
@@ -8,14 +8,18 @@ Copyright 2013, My Local Webstop (email : fpcorso@mylocalwebstop.com)
8
 
9
  function mlw_generate_quiz_dashboard(){
10
  $mlw_quiz_version = get_option('mlw_quiz_master_version');
11
- add_meta_box("wpss_mrts", 'Quiz Stats', "mlw_dashboard_box", "quiz_wpss");
 
 
12
  add_meta_box("wpss_mrts", 'Help', "mlw_dashboard_box_two", "quiz_wpss2");
 
13
  ?>
14
  <!-- css -->
15
  <link type="text/css" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/themes/redmond/jquery-ui.css" rel="stylesheet" />
16
  <!-- jquery scripts -->
17
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.0/jquery.min.js"></script>
18
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js"></script>
 
19
  <script type="text/javascript">
20
  var $j = jQuery.noConflict();
21
  // increase the default animation speed to exaggerate the effect
@@ -37,6 +41,9 @@ function mlw_generate_quiz_dashboard(){
37
  return false;
38
  } );
39
  });
 
 
 
40
  </script>
41
  <style type="text/css">
42
  textarea{
@@ -65,12 +72,18 @@ function mlw_generate_quiz_dashboard(){
65
  </div>
66
 
67
  <!--<div style="clear:both"></div>-->
68
-
 
 
 
 
 
69
  <div id="dialog" title="Help">
70
  <h3><b>Help</b></h3>
71
  <p>This page is the main admin page for the Quiz Master Next.</p>
72
- <p>The first widget lists all the statistics collected so far.</p>
73
  <p>The second widget gives tips to better use the plugin.</p>
 
74
  </div>
75
 
76
  </div>
@@ -79,6 +92,87 @@ function mlw_generate_quiz_dashboard(){
79
 
80
  function mlw_dashboard_box()
81
  {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  global $wpdb;
83
  $sql = "SELECT SUM(quiz_views) AS QuizViews FROM " . $wpdb->prefix . "mlw_quizzes";
84
  $mlw_quiz_views = $wpdb->get_results($sql);
@@ -127,45 +221,8 @@ function mlw_dashboard_box()
127
  $mlw_quiz_most_taken = $mlw_eaches->quiz_name;
128
  break;
129
  }
130
-
131
- $sql = "SELECT quiz_name FROM " . $wpdb->prefix . "mlw_results WHERE (time_taken_real BETWEEN '".date("Y-m-d")." 00:00:00' AND '".date("Y-m-d")." 23:59:59')";
132
- $mlw_quiz_taken_today = $wpdb->get_results($sql);
133
- $mlw_quiz_taken_today = $wpdb->num_rows;
134
-
135
- $mlw_yesterday = mktime(0, 0, 0, date("m") , date("d")-1, date("Y"));
136
- $mlw_yesterday = date("Y-m-d", $mlw_yesterday);
137
- $sql = "SELECT quiz_name FROM " . $wpdb->prefix . "mlw_results WHERE (time_taken_real BETWEEN '".$mlw_yesterday." 00:00:00' AND '".$mlw_yesterday." 23:59:59')";
138
- $mlw_quiz_taken_yesterday = $wpdb->get_results($sql);
139
- $mlw_quiz_taken_yesterday = $wpdb->num_rows;
140
-
141
- $mlw_last_week = mktime(0, 0, 0, date("m") , date("d")-7, date("Y"));
142
- $mlw_last_week = date("Y-m-d", $mlw_last_week);
143
- $sql = "SELECT quiz_name FROM " . $wpdb->prefix . "mlw_results WHERE (time_taken_real BETWEEN '".$mlw_last_week." 00:00:00' AND '".date("Y-m-d")." 23:59:59')";
144
- $mlw_quiz_taken_week = $wpdb->get_results($sql);
145
- $mlw_quiz_taken_week = $wpdb->num_rows;
146
-
147
- $mlw_last_month = mktime(0, 0, 0, date("m") , date("d")-30, date("Y"));
148
- $mlw_last_month = date("Y-m-d", $mlw_last_month);
149
- $sql = "SELECT quiz_name FROM " . $wpdb->prefix . "mlw_results WHERE (time_taken_real BETWEEN '".$mlw_last_month." 00:00:00' AND '".date("Y-m-d")." 23:59:59')";
150
- $mlw_quiz_taken_month = $wpdb->get_results($sql);
151
- $mlw_quiz_taken_month = $wpdb->num_rows;
152
  ?>
153
  <div>
154
- <table cellpadding="10">
155
- <tr>
156
- <td>Taken Today: </td>
157
- <td style="padding: 10px; text-align: right;"><?php echo $mlw_quiz_taken_today; ?></td>
158
- <td>Taken Yesterday: </td>
159
- <td style="padding: 10px; text-align: right;"><?php echo $mlw_quiz_taken_yesterday; ?></td>
160
- </tr>
161
- <tr>
162
- <td>Taken Last 7 Days: </td>
163
- <td style="padding: 10px; text-align: right;"><?php echo $mlw_quiz_taken_week; ?></td>
164
- <td>Taken Last 30 Days: </td>
165
- <td style="padding: 10px; text-align: right;"><?php echo $mlw_quiz_taken_month; ?></td>
166
- </tr>
167
- </table>
168
- <br />
169
  <table width='100%'>
170
  <tr>
171
  <td align='left'>Total Times All Quizzes Have Been Viewed</td>
@@ -193,37 +250,6 @@ function mlw_dashboard_box()
193
  </tr>
194
  </table>
195
  </div>
196
- <?php
197
- }
198
-
199
- function mlw_dashboard_box_two()
200
- {
201
- ?>
202
- <div>
203
- <table width='100%'>
204
- <tr>
205
- <td align='left'>There is a (?) next to the title of each page. Click on it to bring up the help for that page.</td>
206
- </tr>
207
- <tr>
208
- <td align='left'></td>
209
- </tr>
210
- <tr>
211
- <td align='left'></td>
212
- </tr>
213
- <tr>
214
- <td align='left'></td>
215
- </tr>
216
- <tr>
217
- <td align='left'></td>
218
- </tr>
219
- <tr>
220
- <td align='left'></td>
221
- </tr>
222
- <tr>
223
- <td align='left'></td>
224
- </tr>
225
- </table>
226
- </div>
227
- <?php
228
  }
229
  ?>
8
 
9
  function mlw_generate_quiz_dashboard(){
10
  $mlw_quiz_version = get_option('mlw_quiz_master_version');
11
+
12
+ ///Creates the widgets
13
+ add_meta_box("wpss_mrts", 'Quiz Weekly Stats', "mlw_dashboard_box", "quiz_wpss");
14
  add_meta_box("wpss_mrts", 'Help', "mlw_dashboard_box_two", "quiz_wpss2");
15
+ add_meta_box("wpss_mrts", 'Quiz Total Stats', "mlw_dashboard_box_three", "quiz_wpss3");
16
  ?>
17
  <!-- css -->
18
  <link type="text/css" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/themes/redmond/jquery-ui.css" rel="stylesheet" />
19
  <!-- jquery scripts -->
20
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.0/jquery.min.js"></script>
21
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js"></script>
22
+ <script type="text/javascript" src="<?php echo plugin_dir_url( $file ); ?>quiz-master-next/includes/jquery_sparkline.js"></script>
23
  <script type="text/javascript">
24
  var $j = jQuery.noConflict();
25
  // increase the default animation speed to exaggerate the effect
41
  return false;
42
  } );
43
  });
44
+ $j(function() {
45
+ $j('.inlinesparkline').sparkline('html', {type: 'line', width: '400', height: '200'});
46
+ });
47
  </script>
48
  <style type="text/css">
49
  textarea{
72
  </div>
73
 
74
  <!--<div style="clear:both"></div>-->
75
+
76
+ <div style="float:left; width:60%;" class="inner-sidebar1">
77
+ <?php do_meta_boxes('quiz_wpss3','advanced',''); ?>
78
+ </div>
79
+
80
+ <!--<div style="clear:both"></div>-->
81
  <div id="dialog" title="Help">
82
  <h3><b>Help</b></h3>
83
  <p>This page is the main admin page for the Quiz Master Next.</p>
84
+ <p>The first widget shows the times all quizzes have been taken over the last week.</p>
85
  <p>The second widget gives tips to better use the plugin.</p>
86
+ <p>The third widget lists the total quiz statistics.</p>
87
  </div>
88
 
89
  </div>
92
 
93
  function mlw_dashboard_box()
94
  {
95
+ //Gather the weekly stats, one variable for each day for the graph
96
+ global $wpdb;
97
+ $sql = "SELECT quiz_name FROM " . $wpdb->prefix . "mlw_results WHERE (time_taken_real BETWEEN '".date("Y-m-d")." 00:00:00' AND '".date("Y-m-d")." 23:59:59')";
98
+ $mlw_quiz_taken_today = $wpdb->get_results($sql);
99
+ $mlw_quiz_taken_today = $wpdb->num_rows;
100
+
101
+ $mlw_yesterday = mktime(0, 0, 0, date("m") , date("d")-1, date("Y"));
102
+ $mlw_yesterday = date("Y-m-d", $mlw_yesterday);
103
+ $sql = "SELECT quiz_name FROM " . $wpdb->prefix . "mlw_results WHERE (time_taken_real BETWEEN '".$mlw_yesterday." 00:00:00' AND '".$mlw_yesterday." 23:59:59')";
104
+ $mlw_quiz_taken_yesterday = $wpdb->get_results($sql);
105
+ $mlw_quiz_taken_yesterday = $wpdb->num_rows;
106
+
107
+ $mlw_three_days_ago = mktime(0, 0, 0, date("m") , date("d")-2, date("Y"));
108
+ $mlw_three_days_ago = date("Y-m-d", $mlw_three_days_ago);
109
+ $sql = "SELECT quiz_name FROM " . $wpdb->prefix . "mlw_results WHERE (time_taken_real BETWEEN '".$mlw_three_days_ago." 00:00:00' AND '".$mlw_three_days_ago." 23:59:59')";
110
+ $mlw_quiz_taken_three_days = $wpdb->get_results($sql);
111
+ $mlw_quiz_taken_three_days = $wpdb->num_rows;
112
+
113
+ $mlw_four_days_ago = mktime(0, 0, 0, date("m") , date("d")-3, date("Y"));
114
+ $mlw_four_days_ago = date("Y-m-d", $mlw_four_days_ago);
115
+ $sql = "SELECT quiz_name FROM " . $wpdb->prefix . "mlw_results WHERE (time_taken_real BETWEEN '".$mlw_four_days_ago." 00:00:00' AND '".$mlw_four_days_ago." 23:59:59')";
116
+ $mlw_quiz_taken_four_days = $wpdb->get_results($sql);
117
+ $mlw_quiz_taken_four_days = $wpdb->num_rows;
118
+
119
+ $mlw_five_days_ago = mktime(0, 0, 0, date("m") , date("d")-4, date("Y"));
120
+ $mlw_five_days_ago = date("Y-m-d", $mlw_five_days_ago);
121
+ $sql = "SELECT quiz_name FROM " . $wpdb->prefix . "mlw_results WHERE (time_taken_real BETWEEN '".$mlw_five_days_ago." 00:00:00' AND '".$mlw_five_days_ago." 23:59:59')";
122
+ $mlw_quiz_taken_five_days = $wpdb->get_results($sql);
123
+ $mlw_quiz_taken_five_days = $wpdb->num_rows;
124
+
125
+ $mlw_six_days_ago = mktime(0, 0, 0, date("m") , date("d")-5, date("Y"));
126
+ $mlw_six_days_ago = date("Y-m-d", $mlw_six_days_ago);
127
+ $sql = "SELECT quiz_name FROM " . $wpdb->prefix . "mlw_results WHERE (time_taken_real BETWEEN '".$mlw_six_days_ago." 00:00:00' AND '".$mlw_six_days_ago." 23:59:59')";
128
+ $mlw_quiz_taken_six_days = $wpdb->get_results($sql);
129
+ $mlw_quiz_taken_six_days = $wpdb->num_rows;
130
+
131
+ $mlw_last_week = mktime(0, 0, 0, date("m") , date("d")-6, date("Y"));
132
+ $mlw_last_week = date("Y-m-d", $mlw_last_week);
133
+ $sql = "SELECT quiz_name FROM " . $wpdb->prefix . "mlw_results WHERE (time_taken_real BETWEEN '".$mlw_last_week." 00:00:00' AND '".$mlw_last_week." 23:59:59')";
134
+ $mlw_quiz_taken_week = $wpdb->get_results($sql);
135
+ $mlw_quiz_taken_week = $wpdb->num_rows;
136
+ ?>
137
+ <div>
138
+ <span class="inlinesparkline"><?php echo $mlw_quiz_taken_week.",".$mlw_quiz_taken_six_days.",".$mlw_quiz_taken_five_days.",".$mlw_quiz_taken_four_days.",".$mlw_quiz_taken_three_days.",".$mlw_quiz_taken_yesterday.",".$mlw_quiz_taken_today; ?></span>
139
+ </div>
140
+ <?php
141
+ }
142
+
143
+ function mlw_dashboard_box_two()
144
+ {
145
+ ?>
146
+ <div>
147
+ <table width='100%'>
148
+ <tr>
149
+ <td align='left'>There is a (?) next to the title of each page. Click on it to bring up the help for that page.</td>
150
+ </tr>
151
+ <tr>
152
+ <td align='left'></td>
153
+ </tr>
154
+ <tr>
155
+ <td align='left'></td>
156
+ </tr>
157
+ <tr>
158
+ <td align='left'></td>
159
+ </tr>
160
+ <tr>
161
+ <td align='left'></td>
162
+ </tr>
163
+ <tr>
164
+ <td align='left'></td>
165
+ </tr>
166
+ <tr>
167
+ <td align='left'></td>
168
+ </tr>
169
+ </table>
170
+ </div>
171
+ <?php
172
+ }
173
+ function mlw_dashboard_box_three()
174
+ {
175
+ //Gather some other useful stats
176
  global $wpdb;
177
  $sql = "SELECT SUM(quiz_views) AS QuizViews FROM " . $wpdb->prefix . "mlw_quizzes";
178
  $mlw_quiz_views = $wpdb->get_results($sql);
221
  $mlw_quiz_most_taken = $mlw_eaches->quiz_name;
222
  break;
223
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  ?>
225
  <div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
  <table width='100%'>
227
  <tr>
228
  <td align='left'>Total Times All Quizzes Have Been Viewed</td>
250
  </tr>
251
  </table>
252
  </div>
253
+ <?php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
  }
255
  ?>
includes/mlw_help.php ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ This page shows the user how-to's for using the plugin
4
+ */
5
+ /*
6
+ Copyright 2013, My Local Webstop (email : fpcorso@mylocalwebstop.com)
7
+ */
8
+
9
+ function mlw_generate_help_page()
10
+ {
11
+ ?>
12
+ <!-- css -->
13
+ <link type="text/css" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/themes/redmond/jquery-ui.css" rel="stylesheet" />
14
+ <!-- jquery scripts -->
15
+ <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.0/jquery.min.js"></script>
16
+ <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js"></script>
17
+ <script type="text/javascript">
18
+ var $j = jQuery.noConflict();
19
+ // increase the default animation speed to exaggerate the effect
20
+ $j.fx.speeds._default = 1000;
21
+ $j(function() {
22
+ $j('#dialog').dialog({
23
+ autoOpen: false,
24
+ show: 'blind',
25
+ hide: 'explode',
26
+ buttons: {
27
+ Ok: function() {
28
+ $j(this).dialog('close');
29
+ }
30
+ }
31
+ });
32
+
33
+ $j('#opener').click(function() {
34
+ $j('#dialog').dialog('open');
35
+ return false;
36
+ } );
37
+ });
38
+ $j(function() {
39
+ $j( document ).tooltip();
40
+ });
41
+ $j(function() {
42
+ $j("button").button();
43
+ });
44
+ $j(function() {
45
+ $j("#accordion").accordion({
46
+ heightStyle: "content"
47
+ });
48
+
49
+ });
50
+ </script>
51
+ <style>
52
+ label {
53
+ display: inline-block;
54
+ width: 5em;
55
+ }
56
+ </style>
57
+ <style type="text/css">
58
+ div.mlw_quiz_options input[type='text'] {
59
+ border-color:#000000;
60
+ color:#3300CC;
61
+ cursor:hand;
62
+ }
63
+ </style>
64
+ <div class="wrap">
65
+ <div class='mlw_quiz_options'>
66
+ <h2>How-To<a id="opener" href="">(?)</a></h2>
67
+ <div id="accordion">
68
+ <h3><a href="#">How To Create A Quiz</a></h3>
69
+ <div>
70
+ In order to create a quiz, test, or survey you must first click on the Quizzes link from the side menu. Once you are on the page, you will see a Create New Quiz button. Click on this button.
71
+ Doing so will open a pop-up that will ask for the name of the quiz you would like to create. Once you entered the name in, click on the button that says Create Quiz. You should your
72
+ new quiz added to the table.
73
+ </div>
74
+ <h3><a href="#">How To Add A Question To Your Quiz</a></h3>
75
+ <div>
76
+ In order to add a question, you must first click on the Quizzes link from the side menu. Once you are on the page, click the edit link on the quiz you wish to add a question to. Once you are on the
77
+ Quiz Options page, navigate to the Quiz Questions tab. On the Quiz Questions tab, click on the button that says Add Question. Doing so will open a pop-up that will ask for the question, answers, hint,
78
+ correct answer, points, and whether you want a comment box. Once you fill in all the necessary information, click the button that says Create Question. Your question will be added to the list of questions
79
+ on the Quiz Questions tab.
80
+ </div>
81
+ <h3><a href="#">How To Edit The Text Shown Before A Quiz Or After Quiz Has Been Taken</a></h3>
82
+ <div>
83
+ First, go to the Quizzes page. From there, click edit on the quiz you would like to edit. Once the Quiz Options page loads, click on the Quiz Text tab. This tab is used to edit all the text that can be customized
84
+ on the quiz. At the top of the page, you will see a list of variables. If you put a variable in a section of text, it will be replaced by its corresponding values when the quiz is taken by the user. Go to
85
+ the section labeled Message Templates. In this section you will see a text box for the Message Displayed Before Quiz and the text box for the Message Displayed After Quiz. By customizing these boxes, you will
86
+ edit the text shown to the user before the quiz and after the quiz has been taken. Once finished, click the Save Templates button.
87
+ </div>
88
+ </div>
89
+ <div id="dialog" title="Help">
90
+ <h3><b>Help</b></h3>
91
+ <p>This page contains numerous how-to's for using the plugin.</p>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ <?php
96
+ }
97
+ ?>
includes/mlw_main_page.php CHANGED
@@ -194,16 +194,16 @@ function quiz_wpss_mrt_meta_box2()
194
  <div>
195
  <table width='100%'>
196
  <tr>
197
- <td align='left'>0.6.2 (November 10, 2013)</td>
198
  </tr>
199
  <tr>
200
- <td align='left'>* Bug Fixes</td>
201
  </tr>
202
  <tr>
203
- <td align='left'>* Added More Stats</td>
204
  </tr>
205
  <tr>
206
- <td align='left'>* Started Redesigning Plugin Dashboard</td>
207
  </tr>
208
  </table>
209
  </div>
194
  <div>
195
  <table width='100%'>
196
  <tr>
197
+ <td align='left'>0.7.1 (November 19, 2013)</td>
198
  </tr>
199
  <tr>
200
+ <td align='left'>* Changed Design For How-To Page</td>
201
  </tr>
202
  <tr>
203
+ <td align='left'>* Added New Section In How-To Page</td>
204
  </tr>
205
  <tr>
206
+ <td align='left'>* Added New Widget On Plugin Dashboard For Daily Stats</td>
207
  </tr>
208
  </table>
209
  </div>
includes/mlw_quiz_admin.php CHANGED
@@ -8,7 +8,7 @@ Copyright 2013, My Local Webstop (email : fpcorso@mylocalwebstop.com)
8
 
9
  function mlw_generate_quiz_admin()
10
  {
11
- $data = "0.6.2";
12
  if ( ! get_option('mlw_quiz_master_version'))
13
  {
14
  add_option('mlw_quiz_master_version' , $data);
8
 
9
  function mlw_generate_quiz_admin()
10
  {
11
+ $data = "0.7.1";
12
  if ( ! get_option('mlw_quiz_master_version'))
13
  {
14
  add_option('mlw_quiz_master_version' , $data);
includes/mlw_quiz_install.php CHANGED
@@ -275,7 +275,7 @@ function mlw_quiz_activate()
275
  require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
276
  dbDelta($sql);
277
  }
278
- $data = "0.6.2";
279
  if ( ! get_option('mlw_quiz_master_version'))
280
  {
281
  add_option('mlw_quiz_master_version' , $data);
275
  require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
276
  dbDelta($sql);
277
  }
278
+ $data = "0.7.1";
279
  if ( ! get_option('mlw_quiz_master_version'))
280
  {
281
  add_option('mlw_quiz_master_version' , $data);
mlw_quizmaster2.php CHANGED
@@ -3,7 +3,7 @@
3
  /*
4
  Plugin Name: Quiz Master Next
5
  Description: Use this plugin to add multiple quizzes, tests, or surveys to your website.
6
- Version: 0.6.2
7
  Author: Frank Corso
8
  Author URI: http://www.mylocalwebstop.com/
9
  Plugin URI: http://www.mylocalwebstop.com/
@@ -32,6 +32,7 @@ include("includes/mlw_results.php");
32
  include("includes/mlw_results_details.php");
33
  include("includes/mlw_tools.php");
34
  include("includes/mlw_leaderboard.php");
 
35
 
36
 
37
  ///Activation Actions
@@ -53,6 +54,7 @@ function mlw_add_menu()
53
  add_submenu_page(__FILE__, 'Quiz Results', 'Quiz Results', 8, 'mlw_quiz_results', 'mlw_generate_quiz_results');
54
  add_submenu_page(__FILE__, 'Quiz Result Details', 'Quiz Result Details', 8, 'mlw_quiz_result_details', 'mlw_generate_result_details');
55
  add_submenu_page(__FILE__, 'Tools', 'Tools', 8, 'mlw_quiz_tools', 'mlw_generate_quiz_tools');
 
56
  add_submenu_page(__FILE__, 'Support', 'Support', 8, 'mlw_quiz_support', 'mlw_generate_main_page');
57
  }
58
  }
3
  /*
4
  Plugin Name: Quiz Master Next
5
  Description: Use this plugin to add multiple quizzes, tests, or surveys to your website.
6
+ Version: 0.7.1
7
  Author: Frank Corso
8
  Author URI: http://www.mylocalwebstop.com/
9
  Plugin URI: http://www.mylocalwebstop.com/
32
  include("includes/mlw_results_details.php");
33
  include("includes/mlw_tools.php");
34
  include("includes/mlw_leaderboard.php");
35
+ include("includes/mlw_help.php");
36
 
37
 
38
  ///Activation Actions
54
  add_submenu_page(__FILE__, 'Quiz Results', 'Quiz Results', 8, 'mlw_quiz_results', 'mlw_generate_quiz_results');
55
  add_submenu_page(__FILE__, 'Quiz Result Details', 'Quiz Result Details', 8, 'mlw_quiz_result_details', 'mlw_generate_result_details');
56
  add_submenu_page(__FILE__, 'Tools', 'Tools', 8, 'mlw_quiz_tools', 'mlw_generate_quiz_tools');
57
+ add_submenu_page(__FILE__, 'How-To', 'How-To', 8, 'mlw_how_to', 'mlw_generate_help_page');
58
  add_submenu_page(__FILE__, 'Support', 'Support', 8, 'mlw_quiz_support', 'mlw_generate_main_page');
59
  }
60
  }
readme.txt CHANGED
@@ -3,16 +3,13 @@ Contributors: fpcorso
3
  Tags: quiz, test, score, survey
4
  Requires at least: 3.0.1
5
  Tested up to: 3.7.1
6
- Stable tag: 0.6.2
7
- License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
10
  Use this plugin to add multiple quizzes, tests, or surveys to your website.
11
 
12
  == Description ==
13
 
14
- This is the successor plugin to the wildly popular Quiz Master plugin. Re-wrote from the ground up.
15
-
16
  Use this plugin to add multiple quizzes or tests to your website. This plugin allows for unlimited quizzes each with unlimited amount of questions. The plugin allows you to create the quiz, add it to any page using a customized shortcode, allows the user to take the quiz, and then saves the results.
17
 
18
  Features include:
@@ -50,6 +47,16 @@ Feel free to use the support option on the main page of the plugin or from the c
50
 
51
  == Changelog ==
52
 
 
 
 
 
 
 
 
 
 
 
53
  = 0.6.2 (November 10, 2013) =
54
  * Bug Fixes
55
  * Added More Stats
@@ -112,6 +119,15 @@ Feel free to use the support option on the main page of the plugin or from the c
112
 
113
  == Upgrade Notice ==
114
 
 
 
 
 
 
 
 
 
 
115
  = 0.6 =
116
  Upgrade to enjoy several new features including saved results and leaderboards!
117
 
3
  Tags: quiz, test, score, survey
4
  Requires at least: 3.0.1
5
  Tested up to: 3.7.1
6
+ Stable tag: 0.7.1
 
7
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
8
 
9
  Use this plugin to add multiple quizzes, tests, or surveys to your website.
10
 
11
  == Description ==
12
 
 
 
13
  Use this plugin to add multiple quizzes or tests to your website. This plugin allows for unlimited quizzes each with unlimited amount of questions. The plugin allows you to create the quiz, add it to any page using a customized shortcode, allows the user to take the quiz, and then saves the results.
14
 
15
  Features include:
47
 
48
  == Changelog ==
49
 
50
+ = 0.7.1 (November 19, 2013) =
51
+ * Changed Design For How-To Page
52
+ * Added New Section In How-To Page
53
+ * Added New Widget On Plugin Dashboard For Daily Stats
54
+
55
+ = 0.7 (November 15, 2013) =
56
+ * Bug Fixes
57
+ * Begun Work On New How-To Page
58
+ * Continued Redesigning Plugin Dashboard
59
+
60
  = 0.6.2 (November 10, 2013) =
61
  * Bug Fixes
62
  * Added More Stats
119
 
120
  == Upgrade Notice ==
121
 
122
+ = 0.7.1 =
123
+ Upgrade to fix minor bugs, view new section how-to page, and new stats on main page.
124
+
125
+ = 0.7 =
126
+ Upgrade to fix minor bugs, view new how-to page, and new stats on main page.
127
+
128
+ = 0.6.2 =
129
+ Upgrade to fix some minor bug fixes.
130
+
131
  = 0.6 =
132
  Upgrade to enjoy several new features including saved results and leaderboards!
133