├── Commands
├── byCorrespondents.mmCommand
├── bySender.mmCommand
├── byTag.mmCommand
└── byTime.mmCommand
├── README.md
├── Support
├── HTML
│ ├── byCorrespondents
│ │ ├── byCorrespondents.js
│ │ └── index.html
│ ├── bySender
│ │ ├── bySender.js
│ │ └── index.html
│ ├── byTag
│ │ ├── byTag.js
│ │ └── index.html
│ ├── byTime
│ │ ├── byTime.js
│ │ └── index.html
│ ├── style.css
│ └── utilities
│ │ ├── d3.min.js
│ │ ├── jquery-1.12.0.min.js
│ │ ├── lodash-license.txt
│ │ ├── lodash.min.js
│ │ ├── saveSvgAsPng-license.txt
│ │ ├── saveSvgAsPng.js
│ │ ├── spectrum-license
│ │ ├── spectrum.css
│ │ ├── spectrum.js
│ │ └── url_object.js
└── bin
│ └── display
└── info.plist
/Commands/byCorrespondents.mmCommand:
--------------------------------------------------------------------------------
1 | {
2 | name = 'Emails by correspondents';
3 | input = 'formatted';
4 | output = 'actions';
5 | keyEquivalent = "^c";
6 | uuid = '5D36DE69-0233-41B6-A148-896D020500BD';
7 | formatString = '"${#any-address.#correspondent.name:${#any-address.#correspondent.address}}"';
8 | separatorString = '\n';
9 | executionMode = 'multipleMessages';
10 | command = '#!/bin/bash\n"${MM_BUNDLE_SUPPORT}/bin/display" byCorrespondents "correspondent"\n';
11 | }
12 |
--------------------------------------------------------------------------------
/Commands/bySender.mmCommand:
--------------------------------------------------------------------------------
1 | {
2 | name = 'Emails by sender';
3 | input = 'formatted';
4 | output = 'actions';
5 | keyEquivalent = "^b";
6 | uuid = '87660C0C-72A1-4CD0-B102-0632890942E4';
7 |
8 | formatString = '"${from.name:?${from.name/"/""/g}:${from.address/"/""/g}}","${from.address/"/""/g}","${subject/"/""/g}","${#mailer.name/"/""/g}",1';
9 | executionMode = 'multipleMessages';
10 | command = '#!/bin/bash\n"${MM_BUNDLE_SUPPORT}/bin/display" bySender "name,email,subjectLine,mailer,count"\n';
11 | }
12 |
--------------------------------------------------------------------------------
/Commands/byTag.mmCommand:
--------------------------------------------------------------------------------
1 | {
2 | name = 'Emails by tag';
3 | input = 'formatted';
4 | output = 'actions';
5 | keyEquivalent = "^g";
6 | uuid = '82B369F4-7281-4C55-93C3-71779ACBB439';
7 |
8 | formatString = '"${from.name:?${from.name/"/""/g}:${from.address/"/""/g}}","${from.address/"/""/g}","${subject/"/""/g}","${#mailer.name/"/""/g}",${##tags.tag.#name},1';
9 | executionMode = 'multipleMessages';
10 | command = '#!/bin/bash\n"${MM_BUNDLE_SUPPORT}/bin/display" byTag "name,email,subjectLine,mailer,tag,count"\n';
11 | }
12 |
--------------------------------------------------------------------------------
/Commands/byTime.mmCommand:
--------------------------------------------------------------------------------
1 | {
2 | name = 'Emails by time';
3 | input = 'formatted';
4 | output = 'actions';
5 | keyEquivalent = "^t";
6 | uuid = 'C6CA01E4-9817-488B-8C23-28DA34D68643';
7 |
8 | formatString = '"${from.name:?${from.name/"/""/g}:${from.address/"/""/g}}","${from.address/"/""/g}","${subject/"/""/g}","${#mailer.name/"/""/g}",${#date.#local},1';
9 | executionMode = 'multipleMessages';
10 | command = '#!/bin/bash\n"${MM_BUNDLE_SUPPORT}/bin/display" byTime "name,email,subjectLine,mailer,localDateTime,count"\n';
11 | }
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Visualize bundle for [MailMate](http://freron.com/ "MailMate")
2 |
3 | Visualize email history and trends.
4 |
5 | 
6 |
7 | 
8 |
9 | 
10 |
11 | 
12 |
13 | You can change colors.
14 |
15 | 
16 |
17 | And even plot a moving average instead of raw numbers.
18 |
19 | 
20 |
21 | # Installation
22 |
23 | You can install this bundle in MailMate by opening the preferences and going to the bundles tab. After installation it will be automatically updated for you.
24 |
25 | # Usage
26 |
27 | Select a few - or many! - messages in any folder or in your Inbox.
28 |
29 | With those messages selected, you have access to four visualization commands:
30 |
31 | - Emails by correspondents (shortcut: `^c`)
32 | - Emails by sender (shortcut: `^b`)
33 | - Emails by time (shortcut: `^t`)
34 | - Emails by tag (shortcut: `^g`)
35 |
36 | The plots will then be displayed in Safari.
37 |
38 | Hover over parts of the pie chart, bars in the bar chart, or dots in the scatterplot to display more information.
39 |
40 | Use the "Save as Image" buttons to produce `.png`s from the plots for your records.
41 |
42 | Have fun :)
43 |
44 | # License
45 |
46 | If not otherwise specified (see below), files in this repository fall under the following license:
47 |
48 | > Permission to copy, use, modify, sell and distribute this
49 | software is granted. This software is provided "as is" without
50 | express or implied warranty, and with no claim as to its
51 | suitability for any purpose.
52 |
53 | An exception is made for files in readable text which contain their own license information, or files where an accompanying file exists (in the same directory) with a “-license” suffix added to the base-name name of the original file, and an extension of txt, html, or similar.
54 |
55 | # Thanks
56 |
57 | To Benny, excellent developer of MailMate, for providing tons of help with the bundle.
58 |
--------------------------------------------------------------------------------
/Support/HTML/byCorrespondents/byCorrespondents.js:
--------------------------------------------------------------------------------
1 | // color palette
2 | var color = d3.scale.ordinal()
3 | .range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
4 |
5 | datafile = urlObject().parameters.csv_file
6 |
7 | d3.csv(datafile, function(data) {
8 | var emails = data
9 | pieBySender(emails)
10 | barBySender(emails)
11 | })
12 |
13 | // .o .o o8o
14 | // .8' .8' `"'
15 | // .888888888888' oo.ooooo. oooo .ooooo.
16 | // .8' .8' 888' `88b `888 d88' `88b
17 | // .888888888888' 888 888 888 888ooo888
18 | // .8' .8' 888 888 888 888 .o
19 | // .8' .8' 888bod8P' o888o `Y8bod8P'
20 | // 888
21 | // o888o
22 |
23 | function pieBySender(emails) {
24 |
25 | // aggregate the count of emails per sender
26 | var data = d3.nest()
27 | .key(function(d) { return d.correspondent; })
28 | .rollup(function(d) {
29 | return d3.sum(d, function(g) { return 1; })
30 | })
31 | .entries(emails);
32 |
33 | // sort the data in descending order of frequency
34 | data.sort(function compareNumbers(a, b) {
35 | return b.values - a.values;
36 | })
37 |
38 | // plot the data into an svg
39 | var width = 600,
40 | height = 500,
41 | radius = Math.min(width, height) / 2;
42 |
43 | var arc = d3.svg.arc()
44 | .outerRadius(radius - 10)
45 | .innerRadius(radius - 160)
46 |
47 | var pie = d3.layout.pie()
48 | .sort(null)
49 | .value(function(d) { return d.values; });
50 |
51 | var svg = d3.select("#pieBySender").append("svg")
52 | .attr('id', 'pie') // we'll use this to convert and download the image
53 | .attr("width", width)
54 | .attr("height", height)
55 | .append("g")
56 | .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
57 |
58 | var g = svg.selectAll("arc")
59 | .data(pie(data))
60 | .enter().append("g")
61 | .attr("class", "arc");
62 |
63 | g.append("path")
64 | .attr("d", arc)
65 | .style("fill", function(d) { return color(d.data.key); })
66 | .style('stroke', function(d) { return color(d.data.key); })
67 |
68 | // we will use this value to only automatically show names of people
69 | // who have frequencies above a certain threshold
70 | var totalEmails = d3.sum(data, function(d) { return d.values; })
71 |
72 | g.append("text")
73 | .attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
74 | .attr("dy", ".35em")
75 | .style("text-anchor", "middle")
76 | .style('fill', 'white')
77 | .style('font-family', 'Fira Sans')
78 | .style("font-size", 12)
79 | .filter(function(d) { return (d.data.values/totalEmails) > .06; })
80 | .text(function(d) { return d.data.key; })
81 | .attr('opacity', 0)
82 | .transition()
83 | .duration(600)
84 | .attr('opacity', 1)
85 |
86 | var tooltip = d3.select('#pieBySender').append('div')
87 | .style('position', 'absolute')
88 | .style('background', 'lightblue')
89 | .style('color', 'black')
90 | .style('padding', '0 10px')
91 | .style('opacity', 0)
92 |
93 | svg.selectAll('path')
94 | .on('mouseover', function(d) {
95 | d3.select(this).transition()
96 | .style('opacity', .8)
97 | tooltip.transition()
98 | .style('opacity', .9)
99 |
100 | tooltip.html(d.data.key + ", " + d.data.values)
101 | .style('left', (d3.event.pageX - 35) + 'px')
102 | .style('top', (d3.event.pageY - 30) + 'px')
103 | })
104 |
105 | .on('mouseout', function(d) {
106 | d3.select(this).transition()
107 | .style('opacity', 1)
108 | tooltip.transition()
109 | .style('opacity', 0)
110 | })
111 | }
112 |
113 | // .o .o .o8
114 | // .8' .8' "888
115 | // .888888888888' 888oooo. .oooo. oooo d8b
116 | // .8' .8' d88' `88b `P )88b `888""8P
117 | // .888888888888' 888 888 .oP"888 888
118 | // .8' .8' 888 888 d8( 888 888
119 | // .8' .8' `Y8bod8P' `Y888""8o d888b
120 |
121 | function barBySender(emails) {
122 | // aggregate the count of emails per sender
123 | var data = d3.nest()
124 | .key(function(d) { return d.correspondent; })
125 | .rollup(function(d) {
126 | return d3.sum(d, function(g) { return 1; })
127 | })
128 | .entries(emails);
129 |
130 | // sort the data in descending order of frequency
131 | data.sort(function compareNumbers(a, b) {
132 | return b.values - a.values;
133 | })
134 |
135 | // limit number of senders represented in bar graph to 50
136 | var originalSenderNumber = data.length
137 | if (originalSenderNumber > 50)
138 | {
139 | data = data.slice(0, 50)
140 | var excludedSenderNumber = originalSenderNumber - data.length
141 |
142 | if (excludedSenderNumber < 2) {
143 | var exSenderMessage = " sender couldn't fit in the bar graph and was excluded."
144 | } else {
145 | exSenderMessage = " correspondents couldn't fit in the bar graph and were excluded."
146 | }
147 |
148 | d3.select('#excludedSenders')
149 | .style('fill', '#000')
150 | .style('color', '#c43c35')
151 | .style('border-radius', '3px')
152 | .style('padding-left', '5px')
153 | .style('padding-right', '5px')
154 | .style('display', 'inline-block')
155 | .append('p')
156 | .html("" +
157 | excludedSenderNumber +
158 | "" +
159 | exSenderMessage)
160 | } else {
161 | d3.select('#excludedSenders').remove()
162 | }
163 |
164 | // o8o
165 | // `"'
166 | // ooo. .oo. .oo. .oooo. oooo d8b .oooooooo oooo ooo. .oo.
167 | // `888P"Y88bP"Y88b `P )88b `888""8P 888' `88b `888 `888P"Y88b
168 | // 888 888 888 .oP"888 888 888 888 888 888 888
169 | // 888 888 888 d8( 888 888 `88bod8P' 888 888 888
170 | // o888o o888o o888o `Y888""8o d888b `8oooooo. o888o o888o o888o
171 | // d" YD
172 | // "Y88888P'
173 |
174 | var margin = { top: 30, right: 40, bottom: 250, left: 70 }
175 |
176 |
177 | var width = 900 - margin.left - margin.right,
178 | height = 680 - margin.top - margin.bottom
179 |
180 | var xScale = d3.scale.ordinal().domain(d3.range(0,data.length)).rangeBands([0, width], .2);
181 |
182 | var maxFrequency = d3.max(data, function(d) { return d.values; })
183 |
184 | var yScale = d3.scale.linear().domain([0, maxFrequency]).range([0, height])
185 |
186 |
187 | // plot the data into an svg
188 | d3.selectAll('#barBySender').append('svg')
189 | .attr('id', 'barchart')
190 | .attr('width', width + margin.left + margin.right) // handling margins
191 | .attr('height', height + margin.top + margin.bottom) // handling margins
192 | .append('g')
193 | .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')' )
194 | .selectAll('rect')
195 | .data(data)
196 | .enter()
197 | .append('rect')
198 | .style('fill', function(d, i) { return color(i); })
199 | .attr('width', xScale.rangeBand())
200 | .attr('height', function(d) { return yScale(d.values); })
201 | .attr('y', function(d) { return height - yScale(d.values); })
202 | .attr('x', function(d,i) { return xScale(i); })
203 |
204 | var tooltip = d3.select('#barBySender').append('div')
205 | .style('position', 'absolute')
206 | .style('background', 'lightblue')
207 | .style('color', 'black')
208 | .style('padding', '0 10px')
209 | .style('opacity', 0)
210 |
211 | d3.selectAll('rect')
212 | .on('mouseover', function(d) {
213 | d3.select(this).transition()
214 | .style('opacity', .8)
215 | tooltip.transition()
216 | .style('opacity', .9)
217 |
218 | tooltip.html(d.key + ", " + d.values)
219 | .style('left', (d3.event.pageX - 15) + 'px')
220 | .style('top', (d3.event.pageY - 20) + 'px')
221 | })
222 | .on('mouseout', function(d) {
223 | d3.select(this).transition()
224 | .style('opacity', 1)
225 | tooltip.transition()
226 | .style('opacity', 0)
227 | })
228 |
229 | // create the vertical axis scale
230 | var yAxisScale = d3.scale.linear()
231 | .domain([0, maxFrequency])
232 | .range([height, 0])
233 | // create the vertical axis object
234 | var yAxis = d3.svg.axis()
235 | .scale(yAxisScale)
236 | .orient('left')
237 | .ticks(10)
238 |
239 | // create the vertical axis guide
240 | var yGuide = d3.select('#barchart').append('g')
241 | yAxis(yGuide)
242 | // handle positioning based on margins
243 | yGuide.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
244 |
245 | yGuide.selectAll('path')
246 | .style('fill', 'none')
247 | .style('stroke', '#000')
248 |
249 | yGuide.selectAll('line')
250 | .style('stroke', '#000')
251 |
252 | yGuide.selectAll('text')
253 | .style('font-size', '12px') // need to set these for savetopng to work
254 | .style('font-family', 'Fira Sans') // need to set these for savetopng to work
255 |
256 | // create the horizontal axis object
257 | var xAxis = d3.svg.axis()
258 | .scale(xScale)
259 | .orient('bottom')
260 | .tickFormat(function(d,i) { return data[i].key; })
261 |
262 | // create the horizontal axis guide
263 | var xGuide = d3.select('#barchart').append('g')
264 | xAxis(xGuide)
265 | // handle positioning based on margins
266 | xGuide.attr('transform', 'translate(' + margin.left + ',' + (height + margin.top) + ')' )
267 |
268 | xGuide.selectAll('path')
269 | .style('fill', 'none')
270 | .style('stroke', '#000')
271 |
272 | xGuide.selectAll('line')
273 | .style('stroke', '#000')
274 |
275 | xGuide.selectAll('text')
276 | .style('font-size', '12px') // need to set these for savetopng to work
277 | .style('font-family', 'Fira Sans') // need to set these for savetopng to work
278 | .style('text-anchor', 'start')
279 | .attr("dx", ".8em")
280 | .attr("dy", ".15em")
281 | .attr('transform', 'rotate(65)' )
282 |
283 | }
284 |
285 | // .oooo.o .oooo. oooo ooo .ooooo.
286 | // d88( "8 `P )88b `88. .8' d88' `88b
287 | // `"Y88b. .oP"888 `88..8' 888ooo888
288 | // o. )88b d8( 888 `888' 888 .o
289 | // 8""888P' `Y888""8o `8' `Y8bod8P'
290 |
291 | d3.select("#savePie").on("click", function(){
292 | saveSvgAsPng(document.getElementById("pie"), "pie_by_sender.png");
293 | });
294 |
295 | d3.select("#saveBar").on("click", function(){
296 | saveSvgAsPng(document.getElementById("barchart"), "bar_by_sender.png");
297 | });
298 |
--------------------------------------------------------------------------------
/Support/HTML/byCorrespondents/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
14 | Your email visualized by correspondents
15 |
16 |
17 |
18 | Glances at your email
19 |
20 | Correspondents are all non-user addresses. i.e., they are addresses that do not match your email as MailMate knows it.
21 |
22 | Hover over the piechart and bar plot for more information.
23 |
24 | Pie chart of messages by sender
25 |
26 |
27 |
28 | Bar chart of messages by correspondent
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Support/HTML/bySender/bySender.js:
--------------------------------------------------------------------------------
1 | // color palette
2 | var color = d3.scale.ordinal()
3 | .range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
4 |
5 | datafile = urlObject().parameters.csv_file
6 |
7 | d3.csv(datafile, function(data) {
8 | var emails = data
9 | pieBySender(emails)
10 | barBySender(emails)
11 | })
12 |
13 | // .o .o o8o
14 | // .8' .8' `"'
15 | // .888888888888' oo.ooooo. oooo .ooooo.
16 | // .8' .8' 888' `88b `888 d88' `88b
17 | // .888888888888' 888 888 888 888ooo888
18 | // .8' .8' 888 888 888 888 .o
19 | // .8' .8' 888bod8P' o888o `Y8bod8P'
20 | // 888
21 | // o888o
22 |
23 | function pieBySender(emails) {
24 |
25 | // aggregate the count of emails per sender
26 | var data = d3.nest()
27 | .key(function(d) { return d.name; })
28 | .rollup(function(d) {
29 | return d3.sum(d, function(g) { return g.count; })
30 | })
31 | .entries(emails);
32 |
33 | // sort the data in descending order of frequency
34 | data.sort(function compareNumbers(a, b) {
35 | return b.values - a.values;
36 | })
37 |
38 | // plot the data into an svg
39 | var width = 600,
40 | height = 500,
41 | radius = Math.min(width, height) / 2;
42 |
43 | var arc = d3.svg.arc()
44 | .outerRadius(radius - 10)
45 | .innerRadius(radius - 160)
46 |
47 | var pie = d3.layout.pie()
48 | .sort(null)
49 | .value(function(d) { return d.values; });
50 |
51 | var svg = d3.select("#pieBySender").append("svg")
52 | .attr('id', 'pie') // we'll use this to convert and download the image
53 | .attr("width", width)
54 | .attr("height", height)
55 | .append("g")
56 | .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
57 |
58 | var g = svg.selectAll("arc")
59 | .data(pie(data))
60 | .enter().append("g")
61 | .attr("class", "arc");
62 |
63 | g.append("path")
64 | .attr("d", arc)
65 | .style("fill", function(d) { return color(d.data.key); })
66 | .style('stroke', function(d) { return color(d.data.key); })
67 |
68 | // we will use this value to only automatically show names of people
69 | // who have frequencies above a certain threshold
70 | var totalEmails = d3.sum(data, function(d) { return d.values; })
71 |
72 | g.append("text")
73 | .attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
74 | .attr("dy", ".35em")
75 | .style("text-anchor", "middle")
76 | .style('fill', 'white')
77 | .style('font-family', 'Fira Sans')
78 | .style("font-size", 12)
79 | .filter(function(d) { return (d.data.values/totalEmails) > .06; })
80 | .text(function(d) { return d.data.key; })
81 | .attr('opacity', 0)
82 | .transition()
83 | .duration(600)
84 | .attr('opacity', 1)
85 |
86 | var tooltip = d3.select('#pieBySender').append('div')
87 | .style('position', 'absolute')
88 | .style('background', 'lightblue')
89 | .style('color', 'black')
90 | .style('padding', '0 10px')
91 | .style('opacity', 0)
92 |
93 | svg.selectAll('path')
94 | .on('mouseover', function(d) {
95 | d3.select(this).transition()
96 | .style('opacity', .8)
97 | tooltip.transition()
98 | .style('opacity', .9)
99 |
100 | tooltip.html(d.data.key + ", " + d.data.values)
101 | .style('left', (d3.event.pageX - 35) + 'px')
102 | .style('top', (d3.event.pageY - 30) + 'px')
103 | })
104 |
105 | .on('mouseout', function(d) {
106 | d3.select(this).transition()
107 | .style('opacity', 1)
108 | tooltip.transition()
109 | .style('opacity', 0)
110 | })
111 | }
112 |
113 | // .o .o .o8
114 | // .8' .8' "888
115 | // .888888888888' 888oooo. .oooo. oooo d8b
116 | // .8' .8' d88' `88b `P )88b `888""8P
117 | // .888888888888' 888 888 .oP"888 888
118 | // .8' .8' 888 888 d8( 888 888
119 | // .8' .8' `Y8bod8P' `Y888""8o d888b
120 |
121 | function barBySender(emails) {
122 | // aggregate the count of emails per sender
123 | var data = d3.nest()
124 | .key(function(d) { return d.name; })
125 | .rollup(function(d) {
126 | return d3.sum(d, function(g) { return g.count; })
127 | })
128 | .entries(emails);
129 |
130 | // rename variables in the aggregated set
131 | // data.forEach(function(d) {
132 | // d.name = d.key
133 | // d.count = d.values
134 | // })
135 |
136 | // sort the data in descending order of frequency
137 | data.sort(function compareNumbers(a, b) {
138 | return b.values - a.values;
139 | })
140 |
141 | // limit number of senders represented in bar graph to 50
142 | var originalSenderNumber = data.length
143 | if (originalSenderNumber > 50)
144 | {
145 | data = data.slice(0, 50)
146 | var excludedSenderNumber = originalSenderNumber - data.length
147 |
148 | if (excludedSenderNumber < 2) {
149 | var exSenderMessage = " sender couldn't fit in the bar graph and was excluded."
150 | } else {
151 | exSenderMessage = " senders couldn't fit in the bar graph and were excluded."
152 | }
153 |
154 | d3.select('#excludedSenders')
155 | .style('fill', '#000')
156 | .style('color', '#c43c35')
157 | .style('border-radius', '3px')
158 | .style('padding-left', '5px')
159 | .style('padding-right', '5px')
160 | .style('display', 'inline-block')
161 | .append('p')
162 | .html("" +
163 | excludedSenderNumber +
164 | "" +
165 | exSenderMessage)
166 | } else {
167 | d3.select('#excludedSenders').remove()
168 | }
169 |
170 | // o8o
171 | // `"'
172 | // ooo. .oo. .oo. .oooo. oooo d8b .oooooooo oooo ooo. .oo.
173 | // `888P"Y88bP"Y88b `P )88b `888""8P 888' `88b `888 `888P"Y88b
174 | // 888 888 888 .oP"888 888 888 888 888 888 888
175 | // 888 888 888 d8( 888 888 `88bod8P' 888 888 888
176 | // o888o o888o o888o `Y888""8o d888b `8oooooo. o888o o888o o888o
177 | // d" YD
178 | // "Y88888P'
179 |
180 | var margin = { top: 30, right: 40, bottom: 250, left: 70 }
181 |
182 |
183 | var width = 900 - margin.left - margin.right,
184 | height = 680 - margin.top - margin.bottom
185 |
186 | var xScale = d3.scale.ordinal().domain(d3.range(0,data.length)).rangeBands([0, width], .2);
187 |
188 | var maxFrequency = d3.max(data, function(d) { return d.values; })
189 |
190 | var yScale = d3.scale.linear().domain([0, maxFrequency]).range([0, height])
191 |
192 |
193 | // plot the data into an svg
194 | d3.selectAll('#barBySender').append('svg')
195 | .attr('id', 'barchart')
196 | .attr('width', width + margin.left + margin.right) // handling margins
197 | .attr('height', height + margin.top + margin.bottom) // handling margins
198 | .append('g')
199 | .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')' )
200 | .selectAll('rect')
201 | .data(data)
202 | .enter()
203 | .append('rect')
204 | .style('fill', function(d, i) { return color(i); })
205 | .attr('width', xScale.rangeBand())
206 | .attr('height', function(d) { return yScale(d.values); })
207 | .attr('y', function(d) { return height - yScale(d.values); })
208 | .attr('x', function(d,i) { return xScale(i); })
209 |
210 | var tooltip = d3.select('#barBySender').append('div')
211 | .style('position', 'absolute')
212 | .style('background', 'lightblue')
213 | .style('color', 'black')
214 | .style('padding', '0 10px')
215 | .style('opacity', 0)
216 |
217 | d3.selectAll('rect')
218 | .on('mouseover', function(d) {
219 | d3.select(this).transition()
220 | .style('opacity', .8)
221 | tooltip.transition()
222 | .style('opacity', .9)
223 |
224 | tooltip.html(d.key + ", " + d.values)
225 | .style('left', (d3.event.pageX - 15) + 'px')
226 | .style('top', (d3.event.pageY - 20) + 'px')
227 | })
228 | .on('mouseout', function(d) {
229 | d3.select(this).transition()
230 | .style('opacity', 1)
231 | tooltip.transition()
232 | .style('opacity', 0)
233 | })
234 |
235 | // create the vertical axis scale
236 | var yAxisScale = d3.scale.linear()
237 | .domain([0, maxFrequency])
238 | .range([height, 0])
239 | // create the vertical axis object
240 | var yAxis = d3.svg.axis()
241 | .scale(yAxisScale)
242 | .orient('left')
243 | .ticks(10)
244 |
245 | // create the vertical axis guide
246 | var yGuide = d3.select('#barchart').append('g')
247 | yAxis(yGuide)
248 | // handle positioning based on margins
249 | yGuide.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
250 |
251 | yGuide.selectAll('path')
252 | .style('fill', 'none')
253 | .style('stroke', '#000')
254 |
255 | yGuide.selectAll('line')
256 | .style('stroke', '#000')
257 |
258 | yGuide.selectAll('text')
259 | .style('font-size', '12px') // need to set these for savetopng to work
260 | .style('font-family', 'Fira Sans') // need to set these for savetopng to work
261 |
262 | // create the horizontal axis object
263 | var xAxis = d3.svg.axis()
264 | .scale(xScale)
265 | .orient('bottom')
266 | .tickFormat(function(d,i) { return data[i].key; })
267 |
268 | // create the horizontal axis guide
269 | var xGuide = d3.select('#barchart').append('g')
270 | xAxis(xGuide)
271 | // handle positioning based on margins
272 | xGuide.attr('transform', 'translate(' + margin.left + ',' + (height + margin.top) + ')' )
273 |
274 | xGuide.selectAll('path')
275 | .style('fill', 'none')
276 | .style('stroke', '#000')
277 |
278 | xGuide.selectAll('line')
279 | .style('stroke', '#000')
280 |
281 | xGuide.selectAll('text')
282 | .style('font-size', '12px') // need to set these for savetopng to work
283 | .style('font-family', 'Fira Sans') // need to set these for savetopng to work
284 | .style('text-anchor', 'start')
285 | .attr("dx", ".8em")
286 | .attr("dy", ".15em")
287 | .attr('transform', 'rotate(65)' )
288 |
289 | }
290 |
291 | // .oooo.o .oooo. oooo ooo .ooooo.
292 | // d88( "8 `P )88b `88. .8' d88' `88b
293 | // `"Y88b. .oP"888 `88..8' 888ooo888
294 | // o. )88b d8( 888 `888' 888 .o
295 | // 8""888P' `Y888""8o `8' `Y8bod8P'
296 |
297 | d3.select("#savePie").on("click", function(){
298 | saveSvgAsPng(document.getElementById("pie"), "pie_by_sender.png");
299 | });
300 |
301 | d3.select("#saveBar").on("click", function(){
302 | saveSvgAsPng(document.getElementById("barchart"), "bar_by_sender.png");
303 | });
--------------------------------------------------------------------------------
/Support/HTML/bySender/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
14 | Your email visualized by sender
15 |
16 |
17 |
18 | Glances at your email
19 |
20 | Hover over the piechart and bar plot for more information.
21 |
22 | Pie chart of messages by sender
23 |
24 |
25 |
26 | Bar chart of messages by sender
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/Support/HTML/byTag/byTag.js:
--------------------------------------------------------------------------------
1 | // color palette
2 | var color = d3.scale.ordinal()
3 | .range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
4 |
5 | datafile = urlObject().parameters.csv_file
6 |
7 | d3.csv(datafile, function(data) {
8 | var emails = data
9 | pieByTag(emails)
10 | barByTag(emails)
11 | })
12 |
13 | // .o .o o8o
14 | // .8' .8' `"'
15 | // .888888888888' oo.ooooo. oooo .ooooo.
16 | // .8' .8' 888' `88b `888 d88' `88b
17 | // .888888888888' 888 888 888 888ooo888
18 | // .8' .8' 888 888 888 888 .o
19 | // .8' .8' 888bod8P' o888o `Y8bod8P'
20 | // 888
21 | // o888o
22 |
23 | function pieByTag(emails) {
24 |
25 | // aggregate the count of emails per tag
26 | var data = d3.nest()
27 | .key(function(d) { return d.tag; })
28 | .rollup(function(d) {
29 | return d3.sum(d, function(g) { return g.count; })
30 | })
31 | .entries(emails);
32 |
33 | // sort the data in descending order of frequency
34 | data.sort(function compareNumbers(a, b) {
35 | return b.values - a.values;
36 | })
37 |
38 | // plot the data into an svg
39 | var width = 600,
40 | height = 500,
41 | radius = Math.min(width, height) / 2;
42 |
43 | var arc = d3.svg.arc()
44 | .outerRadius(radius - 10)
45 | .innerRadius(radius - 160)
46 |
47 | var pie = d3.layout.pie()
48 | .sort(null)
49 | .value(function(d) { return d.values; });
50 |
51 | var svg = d3.select("#pieByTag").append("svg")
52 | .attr('id', 'pie') // we'll use this to convert and download the image
53 | .attr("width", width)
54 | .attr("height", height)
55 | .append("g")
56 | .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
57 |
58 | var g = svg.selectAll("arc")
59 | .data(pie(data))
60 | .enter().append("g")
61 | .attr("class", "arc");
62 |
63 | g.append("path")
64 | .attr("d", arc)
65 | .style("fill", function(d) { return color(d.data.key); })
66 | .style('stroke', function(d) { return color(d.data.key); })
67 |
68 | // we will use this value to only automatically show names of tags
69 | // that have frequencies above a certain threshold
70 | var totalEmails = d3.sum(data, function(d) { return d.values; })
71 |
72 | g.append("text")
73 | .attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
74 | .attr("dy", ".35em")
75 | .style("text-anchor", "middle")
76 | .style('fill', 'white')
77 | .style('font-family', 'Fira Sans')
78 | .style("font-size", 12)
79 | .filter(function(d) { return (d.data.values/totalEmails) > .06; })
80 | .text(function(d) { return d.data.key; })
81 | .attr('opacity', 0)
82 | .transition()
83 | .duration(600)
84 | .attr('opacity', 1)
85 |
86 | var tooltip = d3.select('#pieByTag').append('div')
87 | .style('position', 'absolute')
88 | .style('background', 'lightblue')
89 | .style('color', 'black')
90 | .style('padding', '0 10px')
91 | .style('opacity', 0)
92 |
93 | svg.selectAll('path')
94 | .on('mouseover', function(d) {
95 | d3.select(this).transition()
96 | .style('opacity', .8)
97 | tooltip.transition()
98 | .style('opacity', .9)
99 |
100 | tooltip.html(d.data.key + ", " + d.data.values)
101 | .style('left', (d3.event.pageX - 35) + 'px')
102 | .style('top', (d3.event.pageY - 30) + 'px')
103 | })
104 |
105 | .on('mouseout', function(d) {
106 | d3.select(this).transition()
107 | .style('opacity', 1)
108 | tooltip.transition()
109 | .style('opacity', 0)
110 | })
111 | }
112 |
113 | // .o .o .o8
114 | // .8' .8' "888
115 | // .888888888888' 888oooo. .oooo. oooo d8b
116 | // .8' .8' d88' `88b `P )88b `888""8P
117 | // .888888888888' 888 888 .oP"888 888
118 | // .8' .8' 888 888 d8( 888 888
119 | // .8' .8' `Y8bod8P' `Y888""8o d888b
120 |
121 | function barByTag(emails) {
122 | // aggregate the count of emails per tag
123 | var data = d3.nest()
124 | .key(function(d) { return d.tag; })
125 | .rollup(function(d) {
126 | return d3.sum(d, function(g) { return g.count; })
127 | })
128 | .entries(emails);
129 |
130 | // rename variables in the aggregated set
131 | // data.forEach(function(d) {
132 | // d.tag = d.key
133 | // d.count = d.values
134 | // })
135 |
136 | // sort the data in descending order of frequency
137 | data.sort(function compareNumbers(a, b) {
138 | return b.values - a.values;
139 | })
140 |
141 | // limit number of tags represented in bar graph to 50
142 | var originalTagNumber = data.length
143 | if (originalTagNumber > 50)
144 | {
145 | data = data.slice(0, 50)
146 | var excludedTagNumber = originalTagNumber - data.length
147 |
148 | if (excludedTagNumber < 2) {
149 | var exTagMessage = " tags couldn't fit in the bar graph and was excluded."
150 | } else {
151 | exTagMessage = " tags couldn't fit in the bar graph and were excluded."
152 | }
153 |
154 | d3.select('#excludedTag')
155 | .style('fill', '#000')
156 | .style('color', '#c43c35')
157 | .style('border-radius', '3px')
158 | .style('padding-left', '5px')
159 | .style('padding-right', '5px')
160 | .style('display', 'inline-block')
161 | .append('p')
162 | .html("" +
163 | excludedTagNumber +
164 | "" +
165 | exTagMessage)
166 | } else {
167 | d3.select('#excludedTags').remove()
168 | }
169 |
170 | // o8o
171 | // `"'
172 | // ooo. .oo. .oo. .oooo. oooo d8b .oooooooo oooo ooo. .oo.
173 | // `888P"Y88bP"Y88b `P )88b `888""8P 888' `88b `888 `888P"Y88b
174 | // 888 888 888 .oP"888 888 888 888 888 888 888
175 | // 888 888 888 d8( 888 888 `88bod8P' 888 888 888
176 | // o888o o888o o888o `Y888""8o d888b `8oooooo. o888o o888o o888o
177 | // d" YD
178 | // "Y88888P'
179 |
180 | var margin = { top: 30, right: 40, bottom: 250, left: 70 }
181 |
182 |
183 | var width = 900 - margin.left - margin.right,
184 | height = 680 - margin.top - margin.bottom
185 |
186 | var xScale = d3.scale.ordinal().domain(d3.range(0,data.length)).rangeBands([0, width], .2);
187 |
188 | var maxFrequency = d3.max(data, function(d) { return d.values; })
189 |
190 | var yScale = d3.scale.linear().domain([0, maxFrequency]).range([0, height])
191 |
192 |
193 | // plot the data into an svg
194 | d3.selectAll('#barByTag').append('svg')
195 | .attr('id', 'barchart')
196 | .attr('width', width + margin.left + margin.right) // handling margins
197 | .attr('height', height + margin.top + margin.bottom) // handling margins
198 | .append('g')
199 | .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')' )
200 | .selectAll('rect')
201 | .data(data)
202 | .enter()
203 | .append('rect')
204 | .style('fill', function(d, i) { return color(i); })
205 | .attr('width', xScale.rangeBand())
206 | .attr('height', function(d) { return yScale(d.values); })
207 | .attr('y', function(d) { return height - yScale(d.values); })
208 | .attr('x', function(d,i) { return xScale(i); })
209 |
210 | var tooltip = d3.select('#barByTags').append('div')
211 | .style('position', 'absolute')
212 | .style('background', 'lightblue')
213 | .style('color', 'black')
214 | .style('padding', '0 10px')
215 | .style('opacity', 0)
216 |
217 | d3.selectAll('rect')
218 | .on('mouseover', function(d) {
219 | d3.select(this).transition()
220 | .style('opacity', .8)
221 | tooltip.transition()
222 | .style('opacity', .9)
223 |
224 | tooltip.html(d.key + ", " + d.values)
225 | .style('left', (d3.event.pageX - 15) + 'px')
226 | .style('top', (d3.event.pageY - 20) + 'px')
227 | })
228 | .on('mouseout', function(d) {
229 | d3.select(this).transition()
230 | .style('opacity', 1)
231 | tooltip.transition()
232 | .style('opacity', 0)
233 | })
234 |
235 | // create the vertical axis scale
236 | var yAxisScale = d3.scale.linear()
237 | .domain([0, maxFrequency])
238 | .range([height, 0])
239 | // create the vertical axis object
240 | var yAxis = d3.svg.axis()
241 | .scale(yAxisScale)
242 | .orient('left')
243 | .ticks(10)
244 |
245 | // create the vertical axis guide
246 | var yGuide = d3.select('#barchart').append('g')
247 | yAxis(yGuide)
248 | // handle positioning based on margins
249 | yGuide.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
250 |
251 | yGuide.selectAll('path')
252 | .style('fill', 'none')
253 | .style('stroke', '#000')
254 |
255 | yGuide.selectAll('line')
256 | .style('stroke', '#000')
257 |
258 | yGuide.selectAll('text')
259 | .style('font-size', '12px') // need to set these for savetopng to work
260 | .style('font-family', 'Fira Sans') // need to set these for savetopng to work
261 |
262 | // create the horizontal axis object
263 | var xAxis = d3.svg.axis()
264 | .scale(xScale)
265 | .orient('bottom')
266 | .tickFormat(function(d,i) { return data[i].key; })
267 |
268 | // create the horizontal axis guide
269 | var xGuide = d3.select('#barchart').append('g')
270 | xAxis(xGuide)
271 | // handle positioning based on margins
272 | xGuide.attr('transform', 'translate(' + margin.left + ',' + (height + margin.top) + ')' )
273 |
274 | xGuide.selectAll('path')
275 | .style('fill', 'none')
276 | .style('stroke', '#000')
277 |
278 | xGuide.selectAll('line')
279 | .style('stroke', '#000')
280 |
281 | xGuide.selectAll('text')
282 | .style('font-size', '12px') // need to set these for savetopng to work
283 | .style('font-family', 'Fira Sans') // need to set these for savetopng to work
284 | .style('text-anchor', 'start')
285 | .attr("dx", ".8em")
286 | .attr("dy", ".15em")
287 | .attr('transform', 'rotate(65)' )
288 |
289 | }
290 |
291 | // .oooo.o .oooo. oooo ooo .ooooo.
292 | // d88( "8 `P )88b `88. .8' d88' `88b
293 | // `"Y88b. .oP"888 `88..8' 888ooo888
294 | // o. )88b d8( 888 `888' 888 .o
295 | // 8""888P' `Y888""8o `8' `Y8bod8P'
296 |
297 | d3.select("#savePie").on("click", function(){
298 | saveSvgAsPng(document.getElementById("pie"), "pie_by_tag.png");
299 | });
300 |
301 | d3.select("#saveBar").on("click", function(){
302 | saveSvgAsPng(document.getElementById("barchart"), "bar_by_tag.png");
303 | });
304 |
--------------------------------------------------------------------------------
/Support/HTML/byTag/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
14 | Your email visualized by tag
15 |
16 |
17 |
18 | Glances at your email
19 |
20 | Hover over the piechart and bar plot for more information.
21 |
22 | Pie chart of messages by tag
23 |
24 |
25 |
26 | Bar chart of messages by tag
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/Support/HTML/byTime/byTime.js:
--------------------------------------------------------------------------------
1 | // color palette
2 | var color = d3.scale.ordinal()
3 | .range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
4 |
5 | // days of the week corresponding to the integer returned by [date].getDay()
6 | var days = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']
7 |
8 | // margin and dimensions needed for both scatterplot and linegraph
9 | var margin = { top: 70, right: 100, bottom: 60, left: 30 }
10 |
11 | var width = 900 - margin.left - margin.right,
12 | timeGraphWidth = width,
13 | timeGraphHeight = 200,
14 | height = 800 - timeGraphHeight - margin.top - margin.bottom
15 |
16 | datafile = urlObject().parameters.csv_file
17 |
18 | d3.csv(datafile, function(data) {
19 | // need emails to be global for function that redraws line
20 | emails = data
21 |
22 | // filter out emails with missing date headers
23 | // (which is a thing that happens)
24 | emails = sanitizeEmails(emails)
25 | pieByDays(emails)
26 | seriesTime(emails)
27 | lineTime(emails, 4, false)
28 | })
29 |
30 | // o8o
31 | // `"'
32 | // .oooo.o .oooo. ooo. .oo. oooo
33 | // d88( "8 `P )88b `888P"Y88b `888
34 | // `"Y88b. .oP"888 888 888 888
35 | // o. )88b d8( 888 888 888 888
36 | // 8""888P' `Y888""8o o888o o888o o888o
37 |
38 | function sanitizeEmails(emails) {
39 | var originalCount = emails.length
40 | emails = emails.filter(function(ems) {
41 | return ems.localDateTime != "";
42 | })
43 | var newCount = emails.length
44 | var excludedCount = originalCount - newCount
45 |
46 | if (excludedCount > 0) {
47 | if (excludedCount < 2) {
48 | var datemessage = " message was missing a date header and was excluded."
49 | } else {
50 | var datemessage = " messages were missing a date header and were excluded."
51 | }
52 |
53 | d3.select('#excludedEmails')
54 | .style('color', 'white')
55 | .style('background-color', '#c43c35')
56 | .style('text-transform', 'uppercase')
57 | .style('border-radius', '3px')
58 | .style('padding-left', '5px')
59 | .style('padding-right', '5px')
60 | .style('display', 'inline-block')
61 | .append('p')
62 | .html("" +
63 | excludedCount +
64 | "" +
65 | datemessage)
66 | } else { // no excluded emails, delete excludedEmails div
67 | d3.select('#excludedEmails').remove()
68 | }
69 |
70 | return emails
71 | }
72 |
73 | // .o .o o8o .o8
74 | // .8' .8' `"' "888
75 | // .888888888888' oo.ooooo. oooo .oooo888 .oooo. oooo ooo .oooo.o
76 | // .8' .8' 888' `88b `888 d88' `888 `P )88b `88. .8' d88( "8
77 | // .888888888888' 888 888 888 888 888 .oP"888 `88..8' `"Y88b.
78 | // .8' .8' 888 888 888 888 888 d8( 888 `888' o. )88b
79 | // .8' .8' 888bod8P' o888o `Y8bod88P" `Y888""8o .8' 8""888P'
80 | // 888 .o..P'
81 | // o888o `Y8P'
82 |
83 | function pieByDays(emails) {
84 | // some functions to parse the date
85 | parseDate = d3.time.format("%Y-%m-%d").parse
86 | parseTime = d3.time.format("%H:%M:%S").parse
87 |
88 | // create new value of day of week from date
89 | emails.forEach(function(d) {
90 | dateSplit = d.localDateTime.split(" ")
91 | d.date = parseDate(dateSplit[0])
92 | d.time = parseTime(dateSplit[1])
93 | d.dayOfWeekInt = d.date.getDay()
94 | })
95 |
96 | // aggregate frequencies by day of the week
97 | var data = d3.nest()
98 | .key(function(d) { return d.dayOfWeekInt; })
99 | .rollup(function(d) {
100 | return d3.sum(d, function(g) { return g.count; })
101 | })
102 | .entries(emails);
103 |
104 | // sort days of the week
105 | data.sort(function compareNumbers(a, b) {
106 | return a.key - b.key
107 | })
108 |
109 | // oooo .
110 | // `888 .o8
111 | // oo.ooooo. 888 .ooooo. .o888oo
112 | // 888' `88b 888 d88' `88b 888
113 | // 888 888 888 888 888 888
114 | // 888 888 888 888 888 888 .
115 | // 888bod8P' o888o `Y8bod8P' "888"
116 | // 888
117 | // o888o
118 |
119 | // plot the data into an svg
120 |
121 | var width = 600,
122 | height = 500,
123 | radius = Math.min(width, height) / 2
124 |
125 | var arc = d3.svg.arc()
126 | .outerRadius(radius - 10)
127 | .innerRadius(radius - 160)
128 |
129 | var pie = d3.layout.pie()
130 | .sort(null)
131 | .value(function(d) { return d.values; })
132 |
133 | var svg = d3.select('#pieByTime').append('svg')
134 | .attr('id', 'pie')
135 | .attr('width', width)
136 | .attr('height', height)
137 | .append('g')
138 | .attr('transform', 'translate(' + width/2 + ',' + height/2 + ')')
139 |
140 | var g = svg.selectAll('arc')
141 | .data(pie(data))
142 | .enter().append('g')
143 | .attr('class', 'arc')
144 |
145 | g.append('path')
146 | .attr('d', arc)
147 | .style('fill', function(d, i) { return color(i); })
148 | .style('stroke', function(d, i) { return color(i); })
149 |
150 | g.append('text')
151 | .attr('transform', function(d) { return 'translate(' + arc.centroid(d) + ')'; })
152 | .attr('dy', '.35em')
153 | .attr('text-anchor', 'middle')
154 | .attr('fill', 'white')
155 | .style('font-family', 'Fira Sans')
156 | .style('font-size', 12)
157 | .text(function(d) { return days[d.data.key]; })
158 | .attr('opacity', 0)
159 | .transition().duration(600)
160 | .attr('opacity', 1)
161 |
162 | var tooltip = d3.select('#pieByTime').append('div')
163 | .style('position', 'absolute')
164 | .style('background', 'lightblue')
165 | .style('color', 'black')
166 | .style('padding', '0 10px')
167 | .style('opacity', 0)
168 |
169 | svg.selectAll('path')
170 | .on('mouseover', function(d) {
171 | d3.select(this).transition()
172 | .style('opacity', .8)
173 | tooltip.transition()
174 | .style('opacity', .9)
175 | tooltip.html(d.data.values)
176 | .style('left', (d3.event.pageX - 45) + 'px')
177 | .style('top', (d3.event.pageY - 30) + 'px')
178 | })
179 |
180 | .on('mouseout', function(d) {
181 | d3.select(this).transition()
182 | .style('opacity', 1)
183 | tooltip.transition()
184 | .style('opacity', 0)
185 | })
186 |
187 | }
188 |
189 | // .o .o o8o
190 | // .8' .8' `"'
191 | // .888888888888' .oooo.o .ooooo. oooo d8b oooo .ooooo. .oooo.o
192 | // .8' .8' d88( "8 d88' `88b `888""8P `888 d88' `88b d88( "8
193 | // .888888888888' `"Y88b. 888ooo888 888 888 888ooo888 `"Y88b.
194 | // .8' .8' o. )88b 888 .o 888 888 888 .o o. )88b
195 | // .8' .8' 8""888P' `Y8bod8P' d888b o888o `Y8bod8P' 8""888P'
196 |
197 |
198 | function seriesTime(emails) {
199 |
200 | // o8o
201 | // `"'
202 | // ooo. .oo. .oo. .oooo. oooo d8b .oooooooo oooo ooo. .oo.
203 | // `888P"Y88bP"Y88b `P )88b `888""8P 888' `88b `888 `888P"Y88b
204 | // 888 888 888 .oP"888 888 888 888 888 888 888
205 | // 888 888 888 d8( 888 888 `88bod8P' 888 888 888
206 | // o888o o888o o888o `Y888""8o d888b `8oooooo. o888o o888o o888o
207 | // d" YD
208 | // "Y88888P'
209 |
210 | var formatDay_Time = d3.time.format("%H:%M") // tooltip time
211 | var formatWeek_Year = d3.time.format("%B %d, %Y") // tooltip date
212 |
213 | var x = d3.time.scale().range([0, width])
214 | var y = d3.time.scale().range([0, height])
215 |
216 | // Set the domains
217 | y.domain([new Date(1899, 12, 02, 0, 0, 0),
218 | new Date(1899, 12, 01, 0, 0, 1)])
219 | x.domain(d3.extent(emails, function(d) { return d.date; }))
220 |
221 | var xAxis = d3.svg.axis()
222 | .scale(x)
223 | .orient('bottom')
224 | .ticks(10)
225 | .outerTickSize(0)
226 |
227 | var yAxis = d3.svg.axis()
228 | .scale(y)
229 | .orient('right')
230 | .ticks(24)
231 | .outerTickSize(0)
232 | .tickFormat(formatDay_Time)
233 |
234 | var svg = d3.select('#timeseries').append('svg')
235 | .attr('id', 'timeseriesSVG')
236 | .style('background-color', 'white')
237 | .attr('width', width + margin.left + margin.right)
238 | .attr('height', height + timeGraphHeight + margin.top + margin.bottom)
239 | .append('g')
240 | .attr('class', "timeSeriesG")
241 | .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
242 |
243 | // Draw the Axes and the tick labels
244 | var hAxis = svg.append("g")
245 | .attr("transform", "translate(0," + (height+5) + ")")
246 | .call(xAxis)
247 | .selectAll('path')
248 | .style('fill', 'none')
249 | .style('stroke', '#000')
250 |
251 | svg.selectAll("text")
252 | .style('font-size', '12px') // need to set these for savetopng to work
253 | .style('font-family', 'Fira Sans') // need to set these for savetopng to work
254 | .style('text-anchor', 'start')
255 | .attr("dx", ".8em")
256 | .attr('transform', 'rotate(35)' )
257 |
258 | svg.append("g")
259 | .attr("class", "y axis")
260 | .attr("transform", "translate(" + (width + 15) + ",0)")
261 | .call(yAxis)
262 | .selectAll('path')
263 | .style('fill', 'none')
264 | .style('stroke', '#000')
265 |
266 | svg.selectAll("text")
267 | .style('font-size', '12px') // need to set these for savetopng to work
268 | .style('font-family', 'Fira Sans') // need to set these for savetopng to work
269 | .style('text-anchor', 'start')
270 |
271 | // draw the plotted circles
272 | svg.selectAll("dot")
273 | .data(emails)
274 | .enter().append("circle")
275 | .attr("class", "dot")
276 | .attr("r", 2 )
277 | .style("opacity", 0.9)
278 | .style("fill", "#33a3cc" )
279 | .attr("cx", function(d) { return x(d.date); })
280 | .attr("cy", function(d) { return y(d.time); })
281 |
282 | var seriesTooltip = d3.select('#timeseries').append('div')
283 | .style('position', 'absolute')
284 | .style('background', 'lightblue')
285 | .style('color', 'black')
286 | .style('padding', '5px 10px')
287 | .style('opacity', 0)
288 | .style('text-align', 'left')
289 |
290 | d3.selectAll('circle')
291 | .on('mouseover', function(d) {
292 | d3.select(this).transition().duration(100)
293 | .attr('r', 5)
294 | .style('fill', color(2))
295 |
296 | seriesTooltip.transition()
297 | .style('opacity', .9)
298 |
299 | seriesTooltip.html(d.name + "" + d.subjectLine + "" + formatWeek_Year(d.date) + "" + formatDay_Time(d.time))
300 | .style('left', (d3.event.pageX - 55) + 'px')
301 | .style('top', (d3.event.pageY - 90) + 'px')
302 | })
303 |
304 | .on('mouseout', function(d) {
305 | d3.select(this).transition().duration(100)
306 | .attr('r', 2)
307 | .style('fill', "#33a3cc")
308 |
309 | seriesTooltip.html()
310 | seriesTooltip.transition()
311 | .style('opacity', 0)
312 | })
313 |
314 | // extend axes with lines for separation
315 | d3.select('#timeseriesSVG').append('line')
316 | .style('stroke', 'black')
317 | .attr('x1', width + margin.left)
318 | .attr('y1', height + margin.top + 5)
319 | .attr('x2', width + margin.left + 80)
320 | .attr('y2', height + margin.top + 5)
321 |
322 | d3.select('#timeseriesSVG').append('line')
323 | .style('stroke', 'black')
324 | .attr('x1', width + margin.left + 15)
325 | .attr('y1', height + margin.top)
326 | .attr('x2', width + margin.left + 15)
327 | .attr('y2', height + margin.top + 55)
328 |
329 | }
330 |
331 | function lineTime(emails, movingMeanWindow, overrideWindow) {
332 |
333 | // oooo o8o
334 | // `888 `"'
335 | // 888 oooo ooo. .oo. .ooooo.
336 | // 888 `888 `888P"Y88b d88' `88b
337 | // 888 888 888 888 888ooo888
338 | // 888 888 888 888 888 .o
339 | // o888o o888o o888o o888o `Y8bod8P'
340 |
341 |
342 | d3.selectAll('#timeseriesSVG').append('g')
343 | .attr('id', 'lineGraph')
344 | .attr('transform', 'translate(' + margin.left + ',' + (height + margin.top + 55) + ')')
345 | .append('rect')
346 | .attr('height', timeGraphHeight)
347 | .attr('width', timeGraphWidth)
348 | .style('fill', 'none')
349 |
350 | // aggregate totals by day
351 | var lineData = d3.nest()
352 | .key(function(d) { return d.date; })
353 | .rollup(function(d) {
354 | return d3.sum(d, function(g) { return g.count; })
355 | })
356 | .entries(emails);
357 |
358 | lineData.forEach(function(d) {
359 | d.key = new Date(d.key)
360 | })
361 |
362 | // sort lineData by date
363 | lineData.sort(function compareNumbers(a, b) {
364 | return a.key - b.key
365 | })
366 |
367 | var earliest = d3.min(lineData, function(d) { return d.key; })
368 | var latest = d3.max(lineData, function(d) { return d.key; })
369 |
370 | var dirtyLineXRange = d3.time.scale().domain(d3.extent(lineData, function(d) { return d.key; })).range([0, timeGraphWidth])
371 |
372 | // fill in missing days with value of 0 emails
373 | var cleanedData = dirtyLineXRange.ticks(d3.time.day, 1).map(function(iteratedDay) {
374 | return _.find(lineData, {key: iteratedDay}) || {key: iteratedDay, values: 0}
375 | })
376 |
377 | // implement moving mean
378 | var movingMean = []
379 | var smooth = false
380 | if (overrideWindow == true) {
381 | smooth = true
382 | } else {
383 | if (cleanedData.length > 400 && cleanedData.length < 1000) {
384 | smooth = true
385 | } else if (cleanedData.length > 1000 && cleanedData.length < 3000) {
386 | smooth = true
387 | movingMeanWindow = 6
388 | } else if (cleanedData.length > 3000) {
389 | smooth = true
390 | movingMeanWindow = 6
391 | }
392 | }
393 |
394 | if (smooth == true) {
395 | // display value of moving mean to user
396 | document.getElementById('smoothingwindow').value = movingMeanWindow
397 |
398 | // create a moving mean array from cleanedData
399 | for (var ii = 0; ii <= cleanedData.length; ii++)
400 | {
401 | if (ii < (movingMeanWindow/2)) {
402 | var meanSlice = cleanedData.slice(0,
403 | movingMeanWindow - ii);
404 | } else if (ii > (cleanedData.length - movingMeanWindow)) {
405 | var meanSlice = cleanedData.slice(cleanedData.length - movingMeanWindow - ii,
406 | cleanedData.length);
407 | } else {
408 | var meanSlice = cleanedData.slice(ii - (movingMeanWindow/2),
409 | ii + (movingMeanWindow/2));
410 | }
411 |
412 | var mean = d3.sum(meanSlice, function(meanSlice) { return meanSlice.values }) / meanSlice.length
413 |
414 | movingMean.push(Math.round(mean * 100) / 100)
415 | }
416 |
417 | // transfer values of array to cleanedData array
418 | for (var ii = 0; ii < cleanedData.length; ii++)
419 | {
420 | cleanedData[ii].values = movingMean[ii]
421 | }
422 | }
423 |
424 | // continue with the scales and plotting the line
425 | var lineXRange = d3.time.scale().domain(d3.extent(cleanedData, function(d) { return d.key; })).range([0, timeGraphWidth])
426 | var lineYRange = d3.scale.linear().domain([0, d3.max(cleanedData, function(d) { return d.values; })]).range([timeGraphHeight, 0])
427 |
428 | lineYAxis = d3.svg.axis()
429 | .scale(lineYRange)
430 | .ticks(10)
431 | .tickPadding(10)
432 | .outerTickSize(0)
433 | .orient('right')
434 |
435 | var linePlotFunc = d3.svg.line()
436 | .x(function(d) { return lineXRange(d.key); })
437 | .y(function(d) { return lineYRange(d.values); })
438 | .interpolate('monotone')
439 |
440 | timeGraph = d3.select("#lineGraph")
441 |
442 | timeGraph.append('g')
443 | .attr('transform', 'translate(' + (timeGraphWidth + 15) + ',0)' )
444 | .call(lineYAxis)
445 | .selectAll('path')
446 | .style('fill', 'none')
447 | .style('stroke', '#000')
448 |
449 | timeGraph.append('path')
450 | .attr('id', 'lineplot')
451 | .attr('d', linePlotFunc(cleanedData) )
452 | .attr('stroke', "#33a3cc")
453 | .attr('fill', 'none')
454 | .style('stroke-width', 1.5)
455 |
456 | timeGraph.selectAll('text')
457 | .style('font-size', '12px') // need to set these for savetopng to work
458 | .style('font-family', '-apple-system') // need to set these for savetopng to work
459 | }
460 |
461 | // .oooo.o .oooo. oooo ooo .ooooo.
462 | // d88( "8 `P )88b `88. .8' d88' `88b
463 | // `"Y88b. .oP"888 `88..8' 888ooo888
464 | // o. )88b d8( 888 `888' 888 .o
465 | // 8""888P' `Y888""8o `8' `Y8bod8P'
466 |
467 | d3.select('#savePieTime').on('click', function() {
468 | saveSvgAsPng(document.getElementById('pie'), 'pie_by_days_of_week.png')
469 | })
470 |
471 | d3.select('#saveTimeSeries').on('click', function() {
472 | saveSvgAsPng(document.getElementById('timeseriesSVG'), 'email_time_series.png')
473 | })
474 |
475 |
476 | d3.select('#redrawLine').on('click', function() {
477 | var newWindow = document.getElementById('smoothingwindow').value
478 |
479 | if (newWindow < 0 || newWindow == "") {
480 | d3.select('#windowwarning').text("value must be a positive integer")
481 | } else {
482 | d3.select('#windowwarning').text("")
483 | d3.select('#lineGraph').remove()
484 | lineTime(emails, newWindow, true)
485 | }
486 | })
487 |
488 | // Initialize color picker and act on color changes
489 | $("#colorpicker").spectrum({
490 | showInput: true,
491 | showInitial: true,
492 | preferredFormat: "hex",
493 | color: "#33a3cc"
494 | });
495 |
496 | $("#colorpicker").on('change.spectrum', function(e, tinycolor) {
497 | var pickedColor = tinycolor.toHexString()
498 | console.log(pickedColor);
499 |
500 | d3.select("#timeseries").selectAll('circle')
501 | .style('fill', pickedColor)
502 |
503 | d3.select("#lineGraph").selectAll('#lineplot')
504 | .style('stroke', pickedColor)
505 | });
506 |
--------------------------------------------------------------------------------
/Support/HTML/byTime/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | Your email visualized
20 |
21 |
22 |
23 | Glances at your email
24 |
25 |
26 |
27 | Hover over the piechart and scatterplot for more information.
28 |
29 | Pie chart of messages by day of the week
30 |
31 |
32 |
33 | Time series/scatterplot of messages by date and time of day
34 |
35 | Choose your color:
36 |
37 |
38 | Size of moving mean window (set to 1 to disable):
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/Support/HTML/style.css:
--------------------------------------------------------------------------------
1 | @import url(http://fonts.googleapis.com/css?family=Fira+Sans:400,700,400italic,700italic);
2 |
3 | body {
4 | font-family: -apple-system, 'Fira Sans', 'Helvetica Neue', Helvetica, sans-serif;
5 | font-size: 12px;
6 | }
7 |
8 | p {
9 | font-size: 14px;
10 | }
11 |
12 | div#pieBySender, #barBySender, #pieByTime, #timeseries {
13 | text-align: center;
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/Support/HTML/utilities/lodash-license.txt:
--------------------------------------------------------------------------------
1 | Copyright 2012-2016 The Dojo Foundation
2 | Based on Underscore.js, copyright 2009-2016 Jeremy Ashkenas,
3 | DocumentCloud and Investigative Reporters & Editors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/Support/HTML/utilities/lodash.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * lodash 3.10.1 (Custom Build) lodash.com/license | Underscore.js 1.8.3 underscorejs.org/LICENSE
4 | * Build: `lodash modern -o ./lodash.js`
5 | */
6 | ;(function(){function n(n,t){if(n!==t){var r=null===n,e=n===w,u=n===n,o=null===t,i=t===w,f=t===t;if(n>t&&!o||!u||r&&!i&&f||e&&f)return 1;if(n=n&&9<=n&&13>=n||32==n||160==n||5760==n||6158==n||8192<=n&&(8202>=n||8232==n||8233==n||8239==n||8287==n||12288==n||65279==n);
8 | }function v(n,t){for(var r=-1,e=n.length,u=-1,o=[];++r=F&&gu&&lu?new Dn(t):null,c=t.length;a&&(i=Mn,f=false,t=a);n:for(;++oi(t,a,0)&&u.push(a);return u}function at(n,t){var r=true;return Su(n,function(n,e,u){return r=!!t(n,e,u)}),r}function ct(n,t,r,e){var u=e,o=u;return Su(n,function(n,i,f){i=+t(n,i,f),(r(i,u)||i===e&&i===o)&&(u=i,
14 | o=n)}),o}function lt(n,t){var r=[];return Su(n,function(n,e,u){t(n,e,u)&&r.push(n)}),r}function st(n,t,r,e){var u;return r(n,function(n,r,o){return t(n,r,o)?(u=e?r:n,false):void 0}),u}function pt(n,t,r,e){e||(e=[]);for(var u=-1,o=n.length;++ut&&(t=-t>u?0:u+t),r=r===w||r>u?u:+r||0,0>r&&(r+=u),u=t>r?0:r-t>>>0,t>>>=0,r=Be(u);++e=c)break n;o=e[o],u*="asc"===o||true===o?1:-1;break n}u=t.b-r.b}return u})}function $t(n,t){
21 | var r=0;return Su(n,function(n,e,u){r+=+t(n,e,u)||0}),r}function St(n,t){var e=-1,u=xr(),o=n.length,i=u===r,f=i&&o>=F,a=f&&gu&&lu?new Dn(void 0):null,c=[];a?(u=Mn,i=false):(f=false,a=t?[]:c);n:for(;++eu(a,s,0)&&((t||f)&&a.push(s),c.push(l))}return c}function Ft(n,t){for(var r=-1,e=t.length,u=Be(e);++r>>1,i=n[o];(r?i<=t:iu?w:o,u=1);++e=F)return t.plant(e).value();for(var u=0,n=r?o[u].apply(this,n):e;++uarguments.length;return typeof e=="function"&&o===w&&Oo(r)?n(r,e,u,i):Ot(r,wr(e,o,4),u,i,t)}}function sr(n,t,r,e,u,o,i,f,a,c){function l(){for(var m=arguments.length,b=m,j=Be(m);b--;)j[b]=arguments[b];if(e&&(j=Mt(j,e,u)),o&&(j=qt(j,o,i)),_||y){var b=l.placeholder,k=v(j,b),m=m-k.length;if(mt?0:t)):[]}function Pr(n,t,r){var e=n?n.length:0;return e?((r?Ur(n,t,r):null==t)&&(t=1),t=e-(+t||0),Et(n,0,0>t?0:t)):[]}function Kr(n){return n?n[0]:w}function Vr(n,t,e){var u=n?n.length:0;if(!u)return-1;if(typeof e=="number")e=0>e?bu(u+e,0):e;else if(e)return e=Lt(n,t),
42 | er?bu(u+r,0):r||0,typeof n=="string"||!Oo(n)&&be(n)?r<=u&&-1t?0:+t||0,e);++r=n&&(t=w),r}}function ae(n,t,r){function e(t,r){r&&iu(r),a=p=h=w,t&&(_=ho(),c=n.apply(s,f),p||a||(f=s=w))}function u(){var n=t-(ho()-l);0>=n||n>t?e(h,a):p=su(u,n)}function o(){e(g,p);
46 | }function i(){if(f=arguments,l=ho(),s=this,h=g&&(p||!y),false===v)var r=y&&!p;else{a||y||(_=l);var e=v-(l-_),i=0>=e||e>v;i?(a&&(a=iu(a)),_=l,c=n.apply(s,f)):a||(a=su(o,e))}return i&&p?p=iu(p):p||t===v||(p=su(u,t)),r&&(i=true,c=n.apply(s,f)),!i||p||a||(f=s=w),c}var f,a,c,l,s,p,h,_=0,v=false,g=true;if(typeof n!="function")throw new Ge(L);if(t=0>t?0:+t||0,true===r)var y=true,g=false;else ge(r)&&(y=!!r.leading,v="maxWait"in r&&bu(+r.maxWait||0,t),g="trailing"in r?!!r.trailing:g);return i.cancel=function(){p&&iu(p),a&&iu(a),
47 | _=0,a=p=h=w},i}function ce(n,t){function r(){var e=arguments,u=t?t.apply(this,e):e[0],o=r.cache;return o.has(u)?o.get(u):(e=n.apply(this,e),r.cache=o.set(u,e),e)}if(typeof n!="function"||t&&typeof t!="function")throw new Ge(L);return r.cache=new ce.Cache,r}function le(n,t){if(typeof n!="function")throw new Ge(L);return t=bu(t===w?n.length-1:+t||0,0),function(){for(var r=arguments,e=-1,u=bu(r.length-t,0),o=Be(u);++et}function pe(n){return h(n)&&Er(n)&&nu.call(n,"callee")&&!cu.call(n,"callee")}function he(n,t,r,e){return e=(r=typeof r=="function"?Bt(r,e,3):w)?r(n,t):w,e===w?dt(n,t,r):!!e}function _e(n){return h(n)&&typeof n.message=="string"&&ru.call(n)==P}function ve(n){return ge(n)&&ru.call(n)==K}function ge(n){var t=typeof n;return!!n&&("object"==t||"function"==t)}function ye(n){
49 | return null==n?false:ve(n)?uu.test(Qe.call(n)):h(n)&&Rn.test(n)}function de(n){return typeof n=="number"||h(n)&&ru.call(n)==V}function me(n){var t;if(!h(n)||ru.call(n)!=Z||pe(n)||!(nu.call(n,"constructor")||(t=n.constructor,typeof t!="function"||t instanceof t)))return false;var r;return ht(n,function(n,t){r=t}),r===w||nu.call(n,r)}function we(n){return ge(n)&&ru.call(n)==Y}function be(n){return typeof n=="string"||h(n)&&ru.call(n)==G}function xe(n){return h(n)&&Sr(n.length)&&!!Sn[ru.call(n)]}function Ae(n,t){
50 | return nt||!n||!mu(t))return r;do t%2&&(r+=n),t=yu(t/2),n+=n;while(t);return r}function We(n,t,r){var e=n;return(n=u(n))?(r?Ur(e,t,r):null==t)?n.slice(g(n),y(n)+1):(t+="",n.slice(o(n,t),i(n,t)+1)):n}function $e(n,t,r){return r&&Ur(n,t,r)&&(t=w),n=u(n),n.match(t||Wn)||[]}function Se(n,t,r){return r&&Ur(n,t,r)&&(t=w),h(n)?Ne(n):ut(n,t)}function Fe(n){
52 | return n}function Ne(n){return bt(ot(n,true))}function Te(n,t,r){if(null==r){var e=ge(t),u=e?zo(t):w;((u=u&&u.length?gt(t,u):w)?u.length:e)||(u=false,r=t,t=n,n=this)}u||(u=gt(t,zo(t)));var o=true,e=-1,i=ve(n),f=u.length;false===r?o=false:ge(r)&&"chain"in r&&(o=r.chain);for(;++e=$)return r}else n=0;return Lu(r,e)}}(),Mu=le(function(n,t){
55 | return h(n)&&Er(n)?ft(n,pt(t,false,true)):[]}),qu=tr(),Pu=tr(true),Ku=le(function(n){for(var t=n.length,e=t,u=Be(l),o=xr(),i=o===r,f=[];e--;){var a=n[e]=Er(a=n[e])?a:[];u[e]=i&&120<=a.length&&gu&&lu?new Dn(e&&a):null}var i=n[0],c=-1,l=i?i.length:0,s=u[0];n:for(;++c(s?Mn(s,a):o(f,a,0))){for(e=t;--e;){var p=u[e];if(0>(p?Mn(p,a):o(n[e],a,0)))continue n}s&&s.push(a),f.push(a)}return f}),Vu=le(function(t,r){r=pt(r);var e=rt(t,r);return It(t,r.sort(n)),e}),Zu=vr(),Yu=vr(true),Gu=le(function(n){return St(pt(n,false,true));
56 | }),Ju=le(function(n,t){return Er(n)?ft(n,t):[]}),Xu=le(Jr),Hu=le(function(n){var t=n.length,r=2--n?t.apply(this,arguments):void 0}},Nn.ary=function(n,t,r){return r&&Ur(n,t,r)&&(t=w),t=n&&null==t?n.length:bu(+t||0,0),gr(n,E,w,w,w,w,t)},Nn.assign=Co,Nn.at=no,Nn.before=fe,Nn.bind=_o,Nn.bindAll=vo,Nn.bindKey=go,Nn.callback=Se,Nn.chain=Qr,Nn.chunk=function(n,t,r){t=(r?Ur(n,t,r):null==t)?1:bu(yu(t)||1,1),r=0;for(var e=n?n.length:0,u=-1,o=Be(vu(e/t));rr&&(r=-r>u?0:u+r),e=e===w||e>u?u:+e||0,0>e&&(e+=u),u=r>e?0:e>>>0,r>>>=0;rt?0:t)):[]},Nn.takeRight=function(n,t,r){var e=n?n.length:0;return e?((r?Ur(n,t,r):null==t)&&(t=1),t=e-(+t||0),Et(n,0>t?0:t)):[]},Nn.takeRightWhile=function(n,t,r){
71 | return n&&n.length?Nt(n,wr(t,r,3),false,true):[]},Nn.takeWhile=function(n,t,r){return n&&n.length?Nt(n,wr(t,r,3)):[]},Nn.tap=function(n,t,r){return t.call(r,n),n},Nn.throttle=function(n,t,r){var e=true,u=true;if(typeof n!="function")throw new Ge(L);return false===r?e=false:ge(r)&&(e="leading"in r?!!r.leading:e,u="trailing"in r?!!r.trailing:u),ae(n,t,{leading:e,maxWait:+t,trailing:u})},Nn.thru=ne,Nn.times=function(n,t,r){if(n=yu(n),1>n||!mu(n))return[];var e=-1,u=Be(xu(n,4294967295));for(t=Bt(t,r,1);++ee?u[e]=t(e):t(e);
72 | return u},Nn.toArray=je,Nn.toPlainObject=ke,Nn.transform=function(n,t,r,e){var u=Oo(n)||xe(n);return t=wr(t,e,4),null==r&&(u||ge(n)?(e=n.constructor,r=u?Oo(n)?new e:[]:$u(ve(e)?e.prototype:w)):r={}),(u?Pn:_t)(n,function(n,e,u){return t(r,n,e,u)}),r},Nn.union=Gu,Nn.uniq=Gr,Nn.unzip=Jr,Nn.unzipWith=Xr,Nn.values=Ee,Nn.valuesIn=function(n){return Ft(n,Re(n))},Nn.where=function(n,t){return re(n,bt(t))},Nn.without=Ju,Nn.wrap=function(n,t){return t=null==t?Fe:t,gr(t,R,w,[n],[])},Nn.xor=function(){for(var n=-1,t=arguments.length;++nr?0:+r||0,e),r-=t.length,0<=r&&n.indexOf(t,r)==r},Nn.escape=function(n){return(n=u(n))&&hn.test(n)?n.replace(sn,c):n},Nn.escapeRegExp=function(n){return(n=u(n))&&bn.test(n)?n.replace(wn,l):n||"(?:)"},Nn.every=te,Nn.find=ro,Nn.findIndex=qu,Nn.findKey=$o,Nn.findLast=eo,
75 | Nn.findLastIndex=Pu,Nn.findLastKey=So,Nn.findWhere=function(n,t){return ro(n,bt(t))},Nn.first=Kr,Nn.floor=ni,Nn.get=function(n,t,r){return n=null==n?w:yt(n,Dr(t),t+""),n===w?r:n},Nn.gt=se,Nn.gte=function(n,t){return n>=t},Nn.has=function(n,t){if(null==n)return false;var r=nu.call(n,t);if(!r&&!Wr(t)){if(t=Dr(t),n=1==t.length?n:yt(n,Et(t,0,-1)),null==n)return false;t=Zr(t),r=nu.call(n,t)}return r||Sr(n.length)&&Cr(t,n.length)&&(Oo(n)||pe(n))},Nn.identity=Fe,Nn.includes=ee,Nn.indexOf=Vr,Nn.inRange=function(n,t,r){
76 | return t=+t||0,r===w?(r=t,t=0):r=+r||0,n>=xu(t,r)&&nr?bu(e+r,0):xu(r||0,e-1))+1;else if(r)return u=Lt(n,t,true)-1,n=n[u],(t===t?t===n:n!==n)?u:-1;
78 | if(t!==t)return p(n,u,true);for(;u--;)if(n[u]===t)return u;return-1},Nn.lt=Ae,Nn.lte=function(n,t){return n<=t},Nn.max=ti,Nn.min=ri,Nn.noConflict=function(){return Zn._=eu,this},Nn.noop=Le,Nn.now=ho,Nn.pad=function(n,t,r){n=u(n),t=+t;var e=n.length;return er?0:+r||0,n.length),n.lastIndexOf(t,r)==r},Nn.sum=function(n,t,r){if(r&&Ur(n,t,r)&&(t=w),t=wr(t,r,3),1==t.length){n=Oo(n)?n:zr(n),r=n.length;for(var e=0;r--;)e+=+t(n[r])||0;n=e}else n=$t(n,t);return n},Nn.template=function(n,t,r){var e=Nn.templateSettings;r&&Ur(n,t,r)&&(t=r=w),n=u(n),t=nt(tt({},r||t),e,Qn),r=nt(tt({},t.imports),e.imports,Qn);
81 | var o,i,f=zo(r),a=Ft(r,f),c=0;r=t.interpolate||Cn;var l="__p+='";r=Ze((t.escape||Cn).source+"|"+r.source+"|"+(r===gn?jn:Cn).source+"|"+(t.evaluate||Cn).source+"|$","g");var p="sourceURL"in t?"//# sourceURL="+t.sourceURL+"\n":"";if(n.replace(r,function(t,r,e,u,f,a){return e||(e=u),l+=n.slice(c,a).replace(Un,s),r&&(o=true,l+="'+__e("+r+")+'"),f&&(i=true,l+="';"+f+";\n__p+='"),e&&(l+="'+((__t=("+e+"))==null?'':__t)+'"),c=a+t.length,t}),l+="';",(t=t.variable)||(l="with(obj){"+l+"}"),l=(i?l.replace(fn,""):l).replace(an,"$1").replace(cn,"$1;"),
82 | l="function("+(t||"obj")+"){"+(t?"":"obj||(obj={});")+"var __t,__p=''"+(o?",__e=_.escape":"")+(i?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":";")+l+"return __p}",t=Jo(function(){return qe(f,p+"return "+l).apply(w,a)}),t.source=l,_e(t))throw t;return t},Nn.trim=We,Nn.trimLeft=function(n,t,r){var e=n;return(n=u(n))?n.slice((r?Ur(e,t,r):null==t)?g(n):o(n,t+"")):n},Nn.trimRight=function(n,t,r){var e=n;return(n=u(n))?(r?Ur(e,t,r):null==t)?n.slice(0,y(n)+1):n.slice(0,i(n,t+"")+1):n;
83 | },Nn.trunc=function(n,t,r){r&&Ur(n,t,r)&&(t=w);var e=U;if(r=W,null!=t)if(ge(t)){var o="separator"in t?t.separator:o,e="length"in t?+t.length||0:e;r="omission"in t?u(t.omission):r}else e=+t||0;if(n=u(n),e>=n.length)return n;if(e-=r.length,1>e)return r;if(t=n.slice(0,e),null==o)return t+r;if(we(o)){if(n.slice(e).search(o)){var i,f=n.slice(0,e);for(o.global||(o=Ze(o.source,(kn.exec(o)||"")+"g")),o.lastIndex=0;n=o.exec(f);)i=n.index;t=t.slice(0,null==i?e:i)}}else n.indexOf(o,e)!=e&&(o=t.lastIndexOf(o),
84 | -1u.__dir__?"Right":"")}),u},zn.prototype[n+"Right"]=function(t){return this.reverse()[n](t).reverse()}}),Pn(["filter","map","takeWhile"],function(n,t){
86 | var r=t+1,e=r!=T;zn.prototype[n]=function(n,t){var u=this.clone();return u.__iteratees__.push({iteratee:wr(n,t,1),type:r}),u.__filtered__=u.__filtered__||e,u}}),Pn(["first","last"],function(n,t){var r="take"+(t?"Right":"");zn.prototype[n]=function(){return this[r](1).value()[0]}}),Pn(["initial","rest"],function(n,t){var r="drop"+(t?"":"Right");zn.prototype[n]=function(){return this.__filtered__?new zn(this):this[r](1)}}),Pn(["pluck","where"],function(n,t){var r=t?"filter":"map",e=t?bt:ze;zn.prototype[n]=function(n){
87 | return this[r](e(n))}}),zn.prototype.compact=function(){return this.filter(Fe)},zn.prototype.reject=function(n,t){return n=wr(n,t,1),this.filter(function(t){return!n(t)})},zn.prototype.slice=function(n,t){n=null==n?0:+n||0;var r=this;return r.__filtered__&&(0t)?new zn(r):(0>n?r=r.takeRight(-n):n&&(r=r.drop(n)),t!==w&&(t=+t||0,r=0>t?r.dropRight(-t):r.take(t-n)),r)},zn.prototype.takeRightWhile=function(n,t){return this.reverse().takeWhile(n,t).reverse()},zn.prototype.toArray=function(){return this.take(Ru);
88 | },_t(zn.prototype,function(n,t){var r=/^(?:filter|map|reject)|While$/.test(t),e=/^(?:first|last)$/.test(t),u=Nn[e?"take"+("last"==t?"Right":""):t];u&&(Nn.prototype[t]=function(){function t(n){return e&&i?u(n,1)[0]:u.apply(w,Jn([n],o))}var o=e?[1]:arguments,i=this.__chain__,f=this.__wrapped__,a=!!this.__actions__.length,c=f instanceof zn,l=o[0],s=c||Oo(f);return s&&r&&typeof l=="function"&&1!=l.length&&(c=s=false),l={func:ne,args:[t],thisArg:w},a=c&&!a,e&&!i?a?(f=f.clone(),f.__actions__.push(l),n.call(f)):u.call(w,this.value())[0]:!e&&s?(f=a?f:new zn(this),
89 | f=n.apply(f,o),f.__actions__.push(l),new Ln(f,i)):this.thru(t)})}),Pn("join pop push replace shift sort splice split unshift".split(" "),function(n){var t=(/^(?:replace|split)$/.test(n)?He:Je)[n],r=/^(?:push|sort|unshift)$/.test(n)?"tap":"thru",e=/^(?:join|pop|replace|shift)$/.test(n);Nn.prototype[n]=function(){var n=arguments;return e&&!this.__chain__?t.apply(this.value(),n):this[r](function(r){return t.apply(r,n)})}}),_t(zn.prototype,function(n,t){var r=Nn[t];if(r){var e=r.name+"";(Wu[e]||(Wu[e]=[])).push({
90 | name:t,func:r})}}),Wu[sr(w,A).name]=[{name:"wrapper",func:w}],zn.prototype.clone=function(){var n=new zn(this.__wrapped__);return n.__actions__=qn(this.__actions__),n.__dir__=this.__dir__,n.__filtered__=this.__filtered__,n.__iteratees__=qn(this.__iteratees__),n.__takeCount__=this.__takeCount__,n.__views__=qn(this.__views__),n},zn.prototype.reverse=function(){if(this.__filtered__){var n=new zn(this);n.__dir__=-1,n.__filtered__=true}else n=this.clone(),n.__dir__*=-1;return n},zn.prototype.value=function(){
91 | var n,t=this.__wrapped__.value(),r=this.__dir__,e=Oo(t),u=0>r,o=e?t.length:0;n=o;for(var i=this.__views__,f=0,a=-1,c=i.length;++a"'`]/g,pn=RegExp(ln.source),hn=RegExp(sn.source),_n=/<%-([\s\S]+?)%>/g,vn=/<%([\s\S]+?)%>/g,gn=/<%=([\s\S]+?)%>/g,yn=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\n\\]|\\.)*?\1)\]/,dn=/^\w*$/,mn=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\n\\]|\\.)*?)\2)\]/g,wn=/^[:!,]|[\\^$.*+?()[\]{}|\/]|(^[0-9a-fA-Fnrtuvx])|([\n\r\u2028\u2029])/g,bn=RegExp(wn.source),xn=/[\u0300-\u036f\ufe20-\ufe23]/g,An=/\\(\\)?/g,jn=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,kn=/\w*$/,In=/^0[xX]/,Rn=/^\[object .+?Constructor\]$/,On=/^\d+$/,En=/[\xc0-\xd6\xd8-\xde\xdf-\xf6\xf8-\xff]/g,Cn=/($^)/,Un=/['\n\r\u2028\u2029\\]/g,Wn=RegExp("[A-Z\\xc0-\\xd6\\xd8-\\xde]+(?=[A-Z\\xc0-\\xd6\\xd8-\\xde][a-z\\xdf-\\xf6\\xf8-\\xff]+)|[A-Z\\xc0-\\xd6\\xd8-\\xde]?[a-z\\xdf-\\xf6\\xf8-\\xff]+|[A-Z\\xc0-\\xd6\\xd8-\\xde]+|[0-9]+","g"),$n="Array ArrayBuffer Date Error Float32Array Float64Array Function Int8Array Int16Array Int32Array Math Number Object RegExp Set String _ clearTimeout isFinite parseFloat parseInt setTimeout TypeError Uint8Array Uint8ClampedArray Uint16Array Uint32Array WeakMap".split(" "),Sn={};
94 | Sn[X]=Sn[H]=Sn[Q]=Sn[nn]=Sn[tn]=Sn[rn]=Sn[en]=Sn[un]=Sn[on]=true,Sn[B]=Sn[D]=Sn[J]=Sn[M]=Sn[q]=Sn[P]=Sn[K]=Sn["[object Map]"]=Sn[V]=Sn[Z]=Sn[Y]=Sn["[object Set]"]=Sn[G]=Sn["[object WeakMap]"]=false;var Fn={};Fn[B]=Fn[D]=Fn[J]=Fn[M]=Fn[q]=Fn[X]=Fn[H]=Fn[Q]=Fn[nn]=Fn[tn]=Fn[V]=Fn[Z]=Fn[Y]=Fn[G]=Fn[rn]=Fn[en]=Fn[un]=Fn[on]=true,Fn[P]=Fn[K]=Fn["[object Map]"]=Fn["[object Set]"]=Fn["[object WeakMap]"]=false;var Nn={"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A","\xe0":"a","\xe1":"a","\xe2":"a",
95 | "\xe3":"a","\xe4":"a","\xe5":"a","\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I","\xcd":"I","\xce":"I","\xcf":"I","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O","\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u","\xfb":"u","\xfc":"u","\xdd":"Y",
96 | "\xfd":"y","\xff":"y","\xc6":"Ae","\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss"},Tn={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},Ln={"&":"&","<":"<",">":">",""":'"',"'":"'","`":"`"},zn={"function":true,object:true},Bn={0:"x30",1:"x31",2:"x32",3:"x33",4:"x34",5:"x35",6:"x36",7:"x37",8:"x38",9:"x39",A:"x41",B:"x42",C:"x43",D:"x44",E:"x45",F:"x46",a:"x61",b:"x62",c:"x63",d:"x64",e:"x65",f:"x66",n:"x6e",r:"x72",t:"x74",u:"x75",v:"x76",x:"x78"},Dn={"\\":"\\",
97 | "'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},Mn=zn[typeof exports]&&exports&&!exports.nodeType&&exports,qn=zn[typeof module]&&module&&!module.nodeType&&module,Pn=zn[typeof self]&&self&&self.Object&&self,Kn=zn[typeof window]&&window&&window.Object&&window,Vn=qn&&qn.exports===Mn&&Mn,Zn=Mn&&qn&&typeof global=="object"&&global&&global.Object&&global||Kn!==(this&&this.window)&&Kn||Pn||this,Yn=m();typeof define=="function"&&typeof define.amd=="object"&&define.amd?(Zn._=Yn, define(function(){
98 | return Yn})):Mn&&qn?Vn?(qn.exports=Yn)._=Yn:Mn._=Yn:Zn._=Yn}).call(this);
--------------------------------------------------------------------------------
/Support/HTML/utilities/saveSvgAsPng-license.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Eric Shull
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Support/HTML/utilities/saveSvgAsPng.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var out$ = typeof exports != 'undefined' && exports || typeof define != 'undefined' && {} || this;
3 |
4 | var doctype = ']>';
5 |
6 | function isElement(obj) {
7 | return obj instanceof HTMLElement || obj instanceof SVGElement;
8 | }
9 |
10 | function requireDomNode(el) {
11 | if (!isElement(el)) {
12 | throw new Error('an HTMLElement or SVGElement is required; got ' + el);
13 | }
14 | }
15 |
16 | function isExternal(url) {
17 | return url && url.lastIndexOf('http',0) == 0 && url.lastIndexOf(window.location.host) == -1;
18 | }
19 |
20 | function inlineImages(el, callback) {
21 | requireDomNode(el);
22 |
23 | var images = el.querySelectorAll('image'),
24 | left = images.length,
25 | checkDone = function() {
26 | if (left === 0) {
27 | callback();
28 | }
29 | };
30 |
31 | checkDone();
32 | for (var i = 0; i < images.length; i++) {
33 | (function(image) {
34 | var href = image.getAttributeNS("http://www.w3.org/1999/xlink", "href");
35 | if (href) {
36 | if (isExternal(href.value)) {
37 | console.warn("Cannot render embedded images linking to external hosts: "+href.value);
38 | return;
39 | }
40 | }
41 | var canvas = document.createElement('canvas');
42 | var ctx = canvas.getContext('2d');
43 | var img = new Image();
44 | href = href || image.getAttribute('href');
45 | if (href) {
46 | img.src = href;
47 | img.onload = function() {
48 | canvas.width = img.width;
49 | canvas.height = img.height;
50 | ctx.drawImage(img, 0, 0);
51 | image.setAttributeNS("http://www.w3.org/1999/xlink", "href", canvas.toDataURL('image/png'));
52 | left--;
53 | checkDone();
54 | }
55 | img.onerror = function() {
56 | console.log("Could not load "+href);
57 | left--;
58 | checkDone();
59 | }
60 | } else {
61 | left--;
62 | checkDone();
63 | }
64 | })(images[i]);
65 | }
66 | }
67 |
68 | function styles(el, selectorRemap) {
69 | var css = "";
70 | var sheets = document.styleSheets;
71 | for (var i = 0; i < sheets.length; i++) {
72 | try {
73 | var rules = sheets[i].cssRules;
74 | } catch (e) {
75 | console.warn("Stylesheet could not be loaded: "+sheets[i].href);
76 | continue;
77 | }
78 |
79 | if (rules != null) {
80 | for (var j = 0; j < rules.length; j++) {
81 | var rule = rules[j];
82 | if (typeof(rule.style) != "undefined") {
83 | var match, selectorText;
84 |
85 | try {
86 | selectorText = rule.selectorText;
87 | } catch(err) {
88 | console.warn('The following CSS rule has an invalid selector: "' + rule + '"', err);
89 | }
90 |
91 | try {
92 | if (selectorText) {
93 | match = el.querySelector(selectorText);
94 | }
95 | } catch(err) {
96 | console.warn('Invalid CSS selector "' + selectorText + '"', err);
97 | }
98 |
99 | if (match) {
100 | var selector = selectorRemap ? selectorRemap(rule.selectorText) : rule.selectorText;
101 | css += selector + " { " + rule.style.cssText + " }\n";
102 | } else if(rule.cssText.match(/^@font-face/)) {
103 | css += rule.cssText + '\n';
104 | }
105 | }
106 | }
107 | }
108 | }
109 | return css;
110 | }
111 |
112 | function getDimension(el, clone, dim) {
113 | var v = (el.viewBox && el.viewBox.baseVal && el.viewBox.baseVal[dim]) ||
114 | (clone.getAttribute(dim) !== null && !clone.getAttribute(dim).match(/%$/) && parseInt(clone.getAttribute(dim))) ||
115 | el.getBoundingClientRect()[dim] ||
116 | parseInt(clone.style[dim]) ||
117 | parseInt(window.getComputedStyle(el).getPropertyValue(dim));
118 | return (typeof v === 'undefined' || v === null || isNaN(parseFloat(v))) ? 0 : v;
119 | }
120 |
121 | function reEncode(data) {
122 | data = encodeURIComponent(data);
123 | data = data.replace(/%([0-9A-F]{2})/g, function(match, p1) {
124 | var c = String.fromCharCode('0x'+p1);
125 | return c === '%' ? '%25' : c;
126 | });
127 | return decodeURIComponent(data);
128 | }
129 |
130 | out$.svgAsDataUri = function(el, options, cb) {
131 | requireDomNode(el);
132 |
133 | options = options || {};
134 | options.scale = options.scale || 1;
135 | options.responsive = options.responsive || false;
136 | var xmlns = "http://www.w3.org/2000/xmlns/";
137 |
138 | inlineImages(el, function() {
139 | var outer = document.createElement("div");
140 | var clone = el.cloneNode(true);
141 | var width, height;
142 | if(el.tagName == 'svg') {
143 | width = options.width || getDimension(el, clone, 'width');
144 | height = options.height || getDimension(el, clone, 'height');
145 | } else if(el.getBBox) {
146 | var box = el.getBBox();
147 | width = box.x + box.width;
148 | height = box.y + box.height;
149 | clone.setAttribute('transform', clone.getAttribute('transform').replace(/translate\(.*?\)/, ''));
150 |
151 | var svg = document.createElementNS('http://www.w3.org/2000/svg','svg')
152 | svg.appendChild(clone)
153 | clone = svg;
154 | } else {
155 | console.error('Attempted to render non-SVG element', el);
156 | return;
157 | }
158 |
159 | clone.setAttribute("version", "1.1");
160 | if (!clone.getAttribute('xmlns')) {
161 | clone.setAttributeNS(xmlns, "xmlns", "http://www.w3.org/2000/svg");
162 | }
163 | if (!clone.getAttribute('xmlns:xlink')) {
164 | clone.setAttributeNS(xmlns, "xmlns:xlink", "http://www.w3.org/1999/xlink");
165 | }
166 |
167 | if (options.responsive) {
168 | clone.removeAttribute('width');
169 | clone.removeAttribute('height');
170 | clone.setAttribute('preserveAspectRatio', 'xMinYMin meet');
171 | } else {
172 | clone.setAttribute("width", width * options.scale);
173 | clone.setAttribute("height", height * options.scale);
174 | }
175 |
176 | clone.setAttribute("viewBox", [
177 | options.left || 0,
178 | options.top || 0,
179 | width,
180 | height
181 | ].join(" "));
182 |
183 | var fos = clone.querySelectorAll('foreignObject > *');
184 | for (var i = 0; i < fos.length; i++) {
185 | if (!fos[i].getAttributeNS('xml', 'xmlns')) {
186 | fos[i].setAttributeNS(xmlns, "xmlns", "http://www.w3.org/1999/xhtml");
187 | }
188 | }
189 |
190 | outer.appendChild(clone);
191 |
192 | var css = styles(el, options.selectorRemap);
193 | var s = document.createElement('style');
194 | s.setAttribute('type', 'text/css');
195 | s.innerHTML = "";
196 | var defs = document.createElement('defs');
197 | defs.appendChild(s);
198 | clone.insertBefore(defs, clone.firstChild);
199 |
200 | var svg = doctype + outer.innerHTML;
201 | var uri = 'data:image/svg+xml;base64,' + window.btoa(reEncode(svg));
202 | if (cb) {
203 | cb(uri);
204 | }
205 | });
206 | }
207 |
208 | out$.svgAsPngUri = function(el, options, cb) {
209 | requireDomNode(el);
210 |
211 | options.encoderType = options.encoderType || 'image/png';
212 | options.encoderOptions = options.encoderOptions || 0.8;
213 |
214 | out$.svgAsDataUri(el, options, function(uri) {
215 | var image = new Image();
216 | image.onload = function() {
217 | var canvas = document.createElement('canvas');
218 | canvas.width = image.width;
219 | canvas.height = image.height;
220 | var context = canvas.getContext('2d');
221 | if(options && options.backgroundColor){
222 | context.fillStyle = options.backgroundColor;
223 | context.fillRect(0, 0, canvas.width, canvas.height);
224 | }
225 | context.drawImage(image, 0, 0);
226 | var a = document.createElement('a'), png;
227 | try {
228 | png = canvas.toDataURL(options.encoderType, options.encoderOptions);
229 | } catch (e) {
230 | if ((typeof SecurityError !== 'undefined' && e instanceof SecurityError) || e.name == "SecurityError") {
231 | console.error("Rendered SVG images cannot be downloaded in this browser.");
232 | return;
233 | } else {
234 | throw e;
235 | }
236 | }
237 | cb(png);
238 | }
239 | image.onerror = function() {
240 | console.error(
241 | 'There was an error loading the data URI as an image on the following SVG\n',
242 | window.atob(uri.slice(26)), '\n',
243 | "Open the following link to see browser's diagnosis\n",
244 | uri);
245 | }
246 | image.src = uri;
247 | });
248 | }
249 |
250 | function download(name, uri) {
251 | var a = document.createElement('a');
252 | a.download = name;
253 | a.href = uri;
254 | document.body.appendChild(a);
255 | a.click();
256 | a.parentNode.removeChild(a);
257 | }
258 |
259 | out$.saveSvg = function(el, name, options) {
260 | requireDomNode(el);
261 |
262 | options = options || {};
263 | out$.svgAsDataUri(el, options, function(uri) {
264 | download(name, uri);
265 | });
266 | }
267 |
268 | out$.saveSvgAsPng = function(el, name, options) {
269 | requireDomNode(el);
270 |
271 | options = options || {};
272 | out$.svgAsPngUri(el, options, function(uri) {
273 | download(name, uri);
274 | });
275 | }
276 |
277 | // if define is defined create as an AMD module
278 | if (typeof define !== 'undefined') {
279 | define(function() {
280 | return out$;
281 | });
282 | }
283 | })();
284 |
--------------------------------------------------------------------------------
/Support/HTML/utilities/spectrum-license:
--------------------------------------------------------------------------------
1 | Permission is hereby granted, free of charge, to any person obtaining
2 | a copy of this software and associated documentation files (the
3 | "Software"), to deal in the Software without restriction, including
4 | without limitation the rights to use, copy, modify, merge, publish,
5 | distribute, sublicense, and/or sell copies of the Software, and to
6 | permit persons to whom the Software is furnished to do so, subject to
7 | the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be
10 | included in all copies or substantial portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/Support/HTML/utilities/spectrum.css:
--------------------------------------------------------------------------------
1 | /***
2 | Spectrum Colorpicker v1.8.0
3 | https://github.com/bgrins/spectrum
4 | Author: Brian Grinstead
5 | License: MIT
6 | ***/
7 |
8 | .sp-container {
9 | position:absolute;
10 | top:0;
11 | left:0;
12 | display:inline-block;
13 | *display: inline;
14 | *zoom: 1;
15 | /* https://github.com/bgrins/spectrum/issues/40 */
16 | z-index: 9999994;
17 | overflow: hidden;
18 | }
19 | .sp-container.sp-flat {
20 | position: relative;
21 | }
22 |
23 | /* Fix for * { box-sizing: border-box; } */
24 | .sp-container,
25 | .sp-container * {
26 | -webkit-box-sizing: content-box;
27 | -moz-box-sizing: content-box;
28 | box-sizing: content-box;
29 | }
30 |
31 | /* http://ansciath.tumblr.com/post/7347495869/css-aspect-ratio */
32 | .sp-top {
33 | position:relative;
34 | width: 100%;
35 | display:inline-block;
36 | }
37 | .sp-top-inner {
38 | position:absolute;
39 | top:0;
40 | left:0;
41 | bottom:0;
42 | right:0;
43 | }
44 | .sp-color {
45 | position: absolute;
46 | top:0;
47 | left:0;
48 | bottom:0;
49 | right:20%;
50 | }
51 | .sp-hue {
52 | position: absolute;
53 | top:0;
54 | right:0;
55 | bottom:0;
56 | left:84%;
57 | height: 100%;
58 | }
59 |
60 | .sp-clear-enabled .sp-hue {
61 | top:33px;
62 | height: 77.5%;
63 | }
64 |
65 | .sp-fill {
66 | padding-top: 80%;
67 | }
68 | .sp-sat, .sp-val {
69 | position: absolute;
70 | top:0;
71 | left:0;
72 | right:0;
73 | bottom:0;
74 | }
75 |
76 | .sp-alpha-enabled .sp-top {
77 | margin-bottom: 18px;
78 | }
79 | .sp-alpha-enabled .sp-alpha {
80 | display: block;
81 | }
82 | .sp-alpha-handle {
83 | position:absolute;
84 | top:-4px;
85 | bottom: -4px;
86 | width: 6px;
87 | left: 50%;
88 | cursor: pointer;
89 | border: 1px solid black;
90 | background: white;
91 | opacity: .8;
92 | }
93 | .sp-alpha {
94 | display: none;
95 | position: absolute;
96 | bottom: -14px;
97 | right: 0;
98 | left: 0;
99 | height: 8px;
100 | }
101 | .sp-alpha-inner {
102 | border: solid 1px #333;
103 | }
104 |
105 | .sp-clear {
106 | display: none;
107 | }
108 |
109 | .sp-clear.sp-clear-display {
110 | background-position: center;
111 | }
112 |
113 | .sp-clear-enabled .sp-clear {
114 | display: block;
115 | position:absolute;
116 | top:0px;
117 | right:0;
118 | bottom:0;
119 | left:84%;
120 | height: 28px;
121 | }
122 |
123 | /* Don't allow text selection */
124 | .sp-container, .sp-replacer, .sp-preview, .sp-dragger, .sp-slider, .sp-alpha, .sp-clear, .sp-alpha-handle, .sp-container.sp-dragging .sp-input, .sp-container button {
125 | -webkit-user-select:none;
126 | -moz-user-select: -moz-none;
127 | -o-user-select:none;
128 | user-select: none;
129 | }
130 |
131 | .sp-container.sp-input-disabled .sp-input-container {
132 | display: none;
133 | }
134 | .sp-container.sp-buttons-disabled .sp-button-container {
135 | display: none;
136 | }
137 | .sp-container.sp-palette-buttons-disabled .sp-palette-button-container {
138 | display: none;
139 | }
140 | .sp-palette-only .sp-picker-container {
141 | display: none;
142 | }
143 | .sp-palette-disabled .sp-palette-container {
144 | display: none;
145 | }
146 |
147 | .sp-initial-disabled .sp-initial {
148 | display: none;
149 | }
150 |
151 |
152 | /* Gradients for hue, saturation and value instead of images. Not pretty... but it works */
153 | .sp-sat {
154 | background-image: -webkit-gradient(linear, 0 0, 100% 0, from(#FFF), to(rgba(204, 154, 129, 0)));
155 | background-image: -webkit-linear-gradient(left, #FFF, rgba(204, 154, 129, 0));
156 | background-image: -moz-linear-gradient(left, #fff, rgba(204, 154, 129, 0));
157 | background-image: -o-linear-gradient(left, #fff, rgba(204, 154, 129, 0));
158 | background-image: -ms-linear-gradient(left, #fff, rgba(204, 154, 129, 0));
159 | background-image: linear-gradient(to right, #fff, rgba(204, 154, 129, 0));
160 | -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr=#FFFFFFFF, endColorstr=#00CC9A81)";
161 | filter : progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr='#FFFFFFFF', endColorstr='#00CC9A81');
162 | }
163 | .sp-val {
164 | background-image: -webkit-gradient(linear, 0 100%, 0 0, from(#000000), to(rgba(204, 154, 129, 0)));
165 | background-image: -webkit-linear-gradient(bottom, #000000, rgba(204, 154, 129, 0));
166 | background-image: -moz-linear-gradient(bottom, #000, rgba(204, 154, 129, 0));
167 | background-image: -o-linear-gradient(bottom, #000, rgba(204, 154, 129, 0));
168 | background-image: -ms-linear-gradient(bottom, #000, rgba(204, 154, 129, 0));
169 | background-image: linear-gradient(to top, #000, rgba(204, 154, 129, 0));
170 | -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#00CC9A81, endColorstr=#FF000000)";
171 | filter : progid:DXImageTransform.Microsoft.gradient(startColorstr='#00CC9A81', endColorstr='#FF000000');
172 | }
173 |
174 | .sp-hue {
175 | background: -moz-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
176 | background: -ms-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
177 | background: -o-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
178 | background: -webkit-gradient(linear, left top, left bottom, from(#ff0000), color-stop(0.17, #ffff00), color-stop(0.33, #00ff00), color-stop(0.5, #00ffff), color-stop(0.67, #0000ff), color-stop(0.83, #ff00ff), to(#ff0000));
179 | background: -webkit-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
180 | background: linear-gradient(to bottom, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
181 | }
182 |
183 | /* IE filters do not support multiple color stops.
184 | Generate 6 divs, line them up, and do two color gradients for each.
185 | Yes, really.
186 | */
187 | .sp-1 {
188 | height:17%;
189 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0000', endColorstr='#ffff00');
190 | }
191 | .sp-2 {
192 | height:16%;
193 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff00', endColorstr='#00ff00');
194 | }
195 | .sp-3 {
196 | height:17%;
197 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ff00', endColorstr='#00ffff');
198 | }
199 | .sp-4 {
200 | height:17%;
201 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ffff', endColorstr='#0000ff');
202 | }
203 | .sp-5 {
204 | height:16%;
205 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0000ff', endColorstr='#ff00ff');
206 | }
207 | .sp-6 {
208 | height:17%;
209 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff00ff', endColorstr='#ff0000');
210 | }
211 |
212 | .sp-hidden {
213 | display: none !important;
214 | }
215 |
216 | /* Clearfix hack */
217 | .sp-cf:before, .sp-cf:after { content: ""; display: table; }
218 | .sp-cf:after { clear: both; }
219 | .sp-cf { *zoom: 1; }
220 |
221 | /* Mobile devices, make hue slider bigger so it is easier to slide */
222 | @media (max-device-width: 480px) {
223 | .sp-color { right: 40%; }
224 | .sp-hue { left: 63%; }
225 | .sp-fill { padding-top: 60%; }
226 | }
227 | .sp-dragger {
228 | border-radius: 5px;
229 | height: 5px;
230 | width: 5px;
231 | border: 1px solid #fff;
232 | background: #000;
233 | cursor: pointer;
234 | position:absolute;
235 | top:0;
236 | left: 0;
237 | }
238 | .sp-slider {
239 | position: absolute;
240 | top:0;
241 | cursor:pointer;
242 | height: 3px;
243 | left: -1px;
244 | right: -1px;
245 | border: 1px solid #000;
246 | background: white;
247 | opacity: .8;
248 | }
249 |
250 | /*
251 | Theme authors:
252 | Here are the basic themeable display options (colors, fonts, global widths).
253 | See http://bgrins.github.io/spectrum/themes/ for instructions.
254 | */
255 |
256 | .sp-container {
257 | border-radius: 0;
258 | background-color: #ECECEC;
259 | border: solid 1px #f0c49B;
260 | padding: 0;
261 | }
262 | .sp-container, .sp-container button, .sp-container input, .sp-color, .sp-hue, .sp-clear {
263 | font: normal 12px "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif;
264 | -webkit-box-sizing: border-box;
265 | -moz-box-sizing: border-box;
266 | -ms-box-sizing: border-box;
267 | box-sizing: border-box;
268 | }
269 | .sp-top {
270 | margin-bottom: 3px;
271 | }
272 | .sp-color, .sp-hue, .sp-clear {
273 | border: solid 1px #666;
274 | }
275 |
276 | /* Input */
277 | .sp-input-container {
278 | float:right;
279 | width: 100px;
280 | margin-bottom: 4px;
281 | }
282 | .sp-initial-disabled .sp-input-container {
283 | width: 100%;
284 | }
285 | .sp-input {
286 | font-size: 12px !important;
287 | border: 1px inset;
288 | padding: 4px 5px;
289 | margin: 0;
290 | width: 100%;
291 | background:transparent;
292 | border-radius: 3px;
293 | color: #222;
294 | }
295 | .sp-input:focus {
296 | border: 1px solid orange;
297 | }
298 | .sp-input.sp-validation-error {
299 | border: 1px solid red;
300 | background: #fdd;
301 | }
302 | .sp-picker-container , .sp-palette-container {
303 | float:left;
304 | position: relative;
305 | padding: 10px;
306 | padding-bottom: 300px;
307 | margin-bottom: -290px;
308 | }
309 | .sp-picker-container {
310 | width: 172px;
311 | border-left: solid 1px #fff;
312 | }
313 |
314 | /* Palettes */
315 | .sp-palette-container {
316 | border-right: solid 1px #ccc;
317 | }
318 |
319 | .sp-palette-only .sp-palette-container {
320 | border: 0;
321 | }
322 |
323 | .sp-palette .sp-thumb-el {
324 | display: block;
325 | position:relative;
326 | float:left;
327 | width: 24px;
328 | height: 15px;
329 | margin: 3px;
330 | cursor: pointer;
331 | border:solid 2px transparent;
332 | }
333 | .sp-palette .sp-thumb-el:hover, .sp-palette .sp-thumb-el.sp-thumb-active {
334 | border-color: orange;
335 | }
336 | .sp-thumb-el {
337 | position:relative;
338 | }
339 |
340 | /* Initial */
341 | .sp-initial {
342 | float: left;
343 | border: solid 1px #333;
344 | }
345 | .sp-initial span {
346 | width: 30px;
347 | height: 25px;
348 | border:none;
349 | display:block;
350 | float:left;
351 | margin:0;
352 | }
353 |
354 | .sp-initial .sp-clear-display {
355 | background-position: center;
356 | }
357 |
358 | /* Buttons */
359 | .sp-palette-button-container,
360 | .sp-button-container {
361 | float: right;
362 | }
363 |
364 | /* Replacer (the little preview div that shows up instead of the ) */
365 | .sp-replacer {
366 | margin:0;
367 | overflow:hidden;
368 | cursor:pointer;
369 | padding: 4px;
370 | display:inline-block;
371 | *zoom: 1;
372 | *display: inline;
373 | border: solid 1px #91765d;
374 | background: #eee;
375 | color: #333;
376 | vertical-align: middle;
377 | }
378 | .sp-replacer:hover, .sp-replacer.sp-active {
379 | border-color: #F0C49B;
380 | color: #111;
381 | }
382 | .sp-replacer.sp-disabled {
383 | cursor:default;
384 | border-color: silver;
385 | color: silver;
386 | }
387 | .sp-dd {
388 | padding: 2px 0;
389 | height: 16px;
390 | line-height: 16px;
391 | float:left;
392 | font-size:10px;
393 | }
394 | .sp-preview {
395 | position:relative;
396 | width:25px;
397 | height: 20px;
398 | border: solid 1px #222;
399 | margin-right: 5px;
400 | float:left;
401 | z-index: 0;
402 | }
403 |
404 | .sp-palette {
405 | *width: 220px;
406 | max-width: 220px;
407 | }
408 | .sp-palette .sp-thumb-el {
409 | width:16px;
410 | height: 16px;
411 | margin:2px 1px;
412 | border: solid 1px #d0d0d0;
413 | }
414 |
415 | .sp-container {
416 | padding-bottom:0;
417 | }
418 |
419 |
420 | /* Buttons: http://hellohappy.org/css3-buttons/ */
421 | .sp-container button {
422 | background-color: #eeeeee;
423 | background-image: -webkit-linear-gradient(top, #eeeeee, #cccccc);
424 | background-image: -moz-linear-gradient(top, #eeeeee, #cccccc);
425 | background-image: -ms-linear-gradient(top, #eeeeee, #cccccc);
426 | background-image: -o-linear-gradient(top, #eeeeee, #cccccc);
427 | background-image: linear-gradient(to bottom, #eeeeee, #cccccc);
428 | border: 1px solid #ccc;
429 | border-bottom: 1px solid #bbb;
430 | border-radius: 3px;
431 | color: #333;
432 | font-size: 14px;
433 | line-height: 1;
434 | padding: 5px 4px;
435 | text-align: center;
436 | text-shadow: 0 1px 0 #eee;
437 | vertical-align: middle;
438 | }
439 | .sp-container button:hover {
440 | background-color: #dddddd;
441 | background-image: -webkit-linear-gradient(top, #dddddd, #bbbbbb);
442 | background-image: -moz-linear-gradient(top, #dddddd, #bbbbbb);
443 | background-image: -ms-linear-gradient(top, #dddddd, #bbbbbb);
444 | background-image: -o-linear-gradient(top, #dddddd, #bbbbbb);
445 | background-image: linear-gradient(to bottom, #dddddd, #bbbbbb);
446 | border: 1px solid #bbb;
447 | border-bottom: 1px solid #999;
448 | cursor: pointer;
449 | text-shadow: 0 1px 0 #ddd;
450 | }
451 | .sp-container button:active {
452 | border: 1px solid #aaa;
453 | border-bottom: 1px solid #888;
454 | -webkit-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
455 | -moz-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
456 | -ms-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
457 | -o-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
458 | box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
459 | }
460 | .sp-cancel {
461 | font-size: 11px;
462 | color: #d93f3f !important;
463 | margin:0;
464 | padding:2px;
465 | margin-right: 5px;
466 | vertical-align: middle;
467 | text-decoration:none;
468 |
469 | }
470 | .sp-cancel:hover {
471 | color: #d93f3f !important;
472 | text-decoration: underline;
473 | }
474 |
475 |
476 | .sp-palette span:hover, .sp-palette span.sp-thumb-active {
477 | border-color: #000;
478 | }
479 |
480 | .sp-preview, .sp-alpha, .sp-thumb-el {
481 | position:relative;
482 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==);
483 | }
484 | .sp-preview-inner, .sp-alpha-inner, .sp-thumb-inner {
485 | display:block;
486 | position:absolute;
487 | top:0;left:0;bottom:0;right:0;
488 | }
489 |
490 | .sp-palette .sp-thumb-inner {
491 | background-position: 50% 50%;
492 | background-repeat: no-repeat;
493 | }
494 |
495 | .sp-palette .sp-thumb-light.sp-thumb-active .sp-thumb-inner {
496 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAIVJREFUeNpiYBhsgJFMffxAXABlN5JruT4Q3wfi/0DsT64h8UD8HmpIPCWG/KemIfOJCUB+Aoacx6EGBZyHBqI+WsDCwuQ9mhxeg2A210Ntfo8klk9sOMijaURm7yc1UP2RNCMbKE9ODK1HM6iegYLkfx8pligC9lCD7KmRof0ZhjQACDAAceovrtpVBRkAAAAASUVORK5CYII=);
497 | }
498 |
499 | .sp-palette .sp-thumb-dark.sp-thumb-active .sp-thumb-inner {
500 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAMdJREFUOE+tkgsNwzAMRMugEAahEAahEAZhEAqlEAZhEAohEAYh81X2dIm8fKpEspLGvudPOsUYpxE2BIJCroJmEW9qJ+MKaBFhEMNabSy9oIcIPwrB+afvAUFoK4H0tMaQ3XtlrggDhOVVMuT4E5MMG0FBbCEYzjYT7OxLEvIHQLY2zWwQ3D+9luyOQTfKDiFD3iUIfPk8VqrKjgAiSfGFPecrg6HN6m/iBcwiDAo7WiBeawa+Kwh7tZoSCGLMqwlSAzVDhoK+6vH4G0P5wdkAAAAASUVORK5CYII=);
501 | }
502 |
503 | .sp-clear-display {
504 | background-repeat:no-repeat;
505 | background-position: center;
506 | background-image: url(data:image/gif;base64,R0lGODlhFAAUAPcAAAAAAJmZmZ2dnZ6enqKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq/Hx8fLy8vT09PX19ff39/j4+Pn5+fr6+vv7+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAP8ALAAAAAAUABQAAAihAP9FoPCvoMGDBy08+EdhQAIJCCMybCDAAYUEARBAlFiQQoMABQhKUJBxY0SPICEYHBnggEmDKAuoPMjS5cGYMxHW3IiT478JJA8M/CjTZ0GgLRekNGpwAsYABHIypcAgQMsITDtWJYBR6NSqMico9cqR6tKfY7GeBCuVwlipDNmefAtTrkSzB1RaIAoXodsABiZAEFB06gIBWC1mLVgBa0AAOw==);
507 | }
508 |
--------------------------------------------------------------------------------
/Support/HTML/utilities/url_object.js:
--------------------------------------------------------------------------------
1 | /* This is a gist from https://gist.github.com/aymanfarhat/5608517 */
2 | function urlObject(options) {
3 | "use strict";
4 | /*global window, document*/
5 |
6 | var url_search_arr,
7 | option_key,
8 | i,
9 | urlObj,
10 | get_param,
11 | key,
12 | val,
13 | url_query,
14 | url_get_params = {},
15 | a = document.createElement('a'),
16 | default_options = {
17 | 'url': window.location.href,
18 | 'unescape': true,
19 | 'convert_num': true
20 | };
21 |
22 | if (typeof options !== "object") {
23 | options = default_options;
24 | } else {
25 | for (option_key in default_options) {
26 | if (default_options.hasOwnProperty(option_key)) {
27 | if (options[option_key] === undefined) {
28 | options[option_key] = default_options[option_key];
29 | }
30 | }
31 | }
32 | }
33 |
34 | a.href = options.url;
35 | url_query = a.search.substring(1);
36 | url_search_arr = url_query.split('&');
37 |
38 | if (url_search_arr[0].length > 1) {
39 | for (i = 0; i < url_search_arr.length; i += 1) {
40 | get_param = url_search_arr[i].split("=");
41 |
42 | if (options.unescape) {
43 | key = decodeURI(get_param[0]);
44 | val = decodeURI(get_param[1]);
45 | } else {
46 | key = get_param[0];
47 | val = get_param[1];
48 | }
49 |
50 | if (options.convert_num) {
51 | if (val.match(/^\d+$/)) {
52 | val = parseInt(val, 10);
53 | } else if (val.match(/^\d+\.\d+$/)) {
54 | val = parseFloat(val);
55 | }
56 | }
57 |
58 | if (url_get_params[key] === undefined) {
59 | url_get_params[key] = val;
60 | } else if (typeof url_get_params[key] === "string") {
61 | url_get_params[key] = [url_get_params[key], val];
62 | } else {
63 | url_get_params[key].push(val);
64 | }
65 |
66 | get_param = [];
67 | }
68 | }
69 |
70 | urlObj = {
71 | protocol: a.protocol,
72 | hostname: a.hostname,
73 | host: a.host,
74 | port: a.port,
75 | hash: a.hash.substr(1),
76 | pathname: a.pathname,
77 | search: a.search,
78 | parameters: url_get_params
79 | };
80 |
81 | return urlObj;
82 | }
83 |
--------------------------------------------------------------------------------
/Support/bin/display:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | DISPLAY_TYPE=$1
4 | KEYS=$2
5 |
6 | SAFE_FOLDER="${TMPDIR}/${MM_BUNDLE_ITEM_UUID}"
7 | mkdir -p ${SAFE_FOLDER}
8 | FILEPATH="${SAFE_FOLDER}/data.csv"
9 |
10 | echo "${KEYS}" > ${FILEPATH}
11 | cat >> "${FILEPATH}"
12 |
13 | HTMLPATH="${MM_BUNDLE_SUPPORT}/HTML/${DISPLAY_TYPE}/index.html"
14 |
15 | cat << END
16 | { actions = (
17 | {
18 | type = showAsHTML;
19 | filePath = '${HTMLPATH}';
20 | parameters = {
21 | "csv_file" = "${FILEPATH}";
22 | };
23 | },
24 | );
25 | }
26 | END
27 |
--------------------------------------------------------------------------------
/info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | contactEmailRot13
6 | qri@ffbyvzna.pbz
7 | contactName
8 | Sherif Soliman
9 | description
10 | Visualize email history and trends
11 | name
12 | Visualize
13 | uuid
14 | CDE5EA87-EAC3-42D6-8E00-B8049F55682C
15 |
16 |
17 |
--------------------------------------------------------------------------------