From 5c7d016f1caf478d154941af880f4f24611cd465 Mon Sep 17 00:00:00 2001 From: VIthulan Date: Tue, 9 Feb 2016 18:18:54 +0530 Subject: [PATCH 1/9] commit 1 --- .../src/main/webapp/WEB-INF/beans.xml | 1 + .../loganalyzer/css/custom-theme.css | 37 +++++++++++++++++++ .../loganalyzer/site/search/search.jag | 5 +++ .../loganalyzer/site/search/search.js | 3 +- 4 files changed, 45 insertions(+), 1 deletion(-) diff --git a/modules/components/org.wso2.carbon.la.restapi/src/main/webapp/WEB-INF/beans.xml b/modules/components/org.wso2.carbon.la.restapi/src/main/webapp/WEB-INF/beans.xml index 9e784b9..ba10145 100644 --- a/modules/components/org.wso2.carbon.la.restapi/src/main/webapp/WEB-INF/beans.xml +++ b/modules/components/org.wso2.carbon.la.restapi/src/main/webapp/WEB-INF/beans.xml @@ -41,6 +41,7 @@ http://cxf.apache.org/schemas/jaxrs.xsd"> + diff --git a/modules/jaggeryapps/loganalyzer/css/custom-theme.css b/modules/jaggeryapps/loganalyzer/css/custom-theme.css index 4c2a6a4..fb30cb9 100644 --- a/modules/jaggeryapps/loganalyzer/css/custom-theme.css +++ b/modules/jaggeryapps/loganalyzer/css/custom-theme.css @@ -492,6 +492,7 @@ a.ctrl-filter-category:hover { height: 917px; border-right: 1px solid black; } + .right-panel{ position: relative; float: left; @@ -499,7 +500,43 @@ a.ctrl-filter-category:hover { width: 80%; height: 917px; } +.dropbtn { + background-color: #6c5c76; + color: white; + padding: 16px; + font-size: 16px; + border: none; + cursor: pointer; +} + +.dropdown { + position: relative; + display: inline-block; +} + +.dropdown-content { + display: none; + position: absolute; + background-color: #f9f9f9; + min-width: 160px; + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); +} +.dropdown-content a { + color: black; + padding: 12px 16px; + text-decoration: none; + display: block; +} +.dropdown-content a:hover {background-color: #f1f1f1} + +.dropdown:hover .dropdown-content { + display: block; +} + +.dropdown:hover .dropbtn { + background-color: #3e8e41; +} #search-field-area, #search-btn-area, #drop-down-area, #save-options{ float:left; } diff --git a/modules/jaggeryapps/loganalyzer/site/search/search.jag b/modules/jaggeryapps/loganalyzer/site/search/search.jag index 5c874b7..f0c8495 100644 --- a/modules/jaggeryapps/loganalyzer/site/search/search.jag +++ b/modules/jaggeryapps/loganalyzer/site/search/search.jag @@ -175,6 +175,9 @@
+
+ +
+ + +
+ + + + + + +
Fields
+
+ +
+
+

+

+ + + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + From bc3ad2d22cfd2e7ae5a31dcabf1594f0dea5a34a Mon Sep 17 00:00:00 2001 From: VIthulan Date: Fri, 12 Feb 2016 19:38:13 +0530 Subject: [PATCH 3/9] stacked bar chat and group by date created. --- .../carbon/la/restapi/DashboardApiV10.java | 409 +- .../org/wso2/carbon/la/restapi/util/Util.java | 9 + .../jaggeryapps/loganalyzer/css/custom.css | 4 + .../jaggeryapps/loganalyzer/js/VizGrammar.js | 1637 ++ modules/jaggeryapps/loganalyzer/js/vega.js | 20654 ++++++++++++++++ .../jaggeryapps/loganalyzer/js/visualize.js | 216 +- .../loganalyzer/site/dashboard/visualize.jag | 26 +- 7 files changed, 22933 insertions(+), 22 deletions(-) create mode 100644 modules/jaggeryapps/loganalyzer/js/VizGrammar.js create mode 100644 modules/jaggeryapps/loganalyzer/js/vega.js diff --git a/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/DashboardApiV10.java b/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/DashboardApiV10.java index 97ebdf6..379b2af 100644 --- a/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/DashboardApiV10.java +++ b/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/DashboardApiV10.java @@ -12,6 +12,7 @@ import org.apache.http.HttpHeaders; import org.wso2.carbon.analytics.api.AnalyticsDataAPI; import org.wso2.carbon.analytics.dataservice.commons.AnalyticsDataResponse; +import org.wso2.carbon.analytics.dataservice.commons.SearchResultEntry; import org.wso2.carbon.analytics.datasource.commons.AnalyticsSchema; import org.wso2.carbon.analytics.datasource.commons.ColumnDefinition; import org.wso2.carbon.analytics.datasource.commons.Record; @@ -36,6 +37,8 @@ import javax.ws.rs.core.StreamingOutput; import javax.xml.bind.DatatypeConverter; import java.io.*; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.*; import com.google.gson.Gson; @@ -151,7 +154,9 @@ public void write(OutputStream outputStream) int i=1; for(Map.Entry entry:counter.entrySet()){ - recordWriter.write("\""+entry.getKey()+" : "+entry.getValue()+"||%\""); + recordWriter.write("[[\""+entry.getKey()+"\"],[\""+entry.getValue()+"\"]]"); + + // recordWriter.write("\""+entry.getKey()+" : "+entry.getValue()+"||%\""); if(i column = new ArrayList<>(); + column.add(col); + + List searchResults = analyticsDataAPI.search(username, + query.getTableName(), searchQuery, + query.getStart(), query.getCount()); + List ids = Util.getRecordIds(searchResults); + analyticsDataResponse = analyticsDataAPI.get(username,query.getTableName(),1,column,ids); + final List> iterators = Util.getRecordIterators(analyticsDataResponse,analyticsDataAPI); + return new StreamingOutput() { + @Override + public void write(OutputStream outputStream) + throws IOException, WebApplicationException { + Writer recordWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); + Map values; + Map counter = new HashMap(); + + int count=0; + String val; + recordWriter.write("["); + for (Iterator iterator : iterators) { + while (iterator.hasNext()) { + RecordBean recordBean = Util.createRecordBean(iterator.next()); + values = recordBean.getValues(); + + if(values.get(col)==null) { + + if (!counter.containsKey("NULLVALUE")) { + counter.put("NULLVALUE", 1); + } else { + count = counter.get("NULLVALUE"); + count++; + counter.put("NULLVALUE", count); + } + + } + else { + val=values.get(col).toString(); + if (!counter.containsKey(val)) { + counter.put(val, 1); + } else { + count = counter.get(val); + count++; + counter.put(val, count); + } + } + //values.get(query.getQuery()); + + // recordWriter.write(gson.toJson(values.get(query.getQuery()))); + + // recordWriter.write(recordBean.toString()); + // if (iterator.hasNext()) { + //recordWriter.write(","); + //} + if (log.isDebugEnabled()) { + log.debug("Retrieved -- Record Id: " + recordBean.getId() + " values :" + + recordBean.toString()); + } + } + } + int i=1; + for(Map.Entry entry:counter.entrySet()){ + + recordWriter.write("[[\""+entry.getKey()+"\"],[\""+entry.getValue()+"\"]]"); + //recordWriter.write("\""+entry.getKey()+" : "+entry.getValue()+"||%\""); + if(i column = new ArrayList<>(); + column.add(col); + column.add(timestamp); + + List searchResults = analyticsDataAPI.search(username, + query.getTableName(), searchQuery, + query.getStart(), query.getCount()); + List ids = Util.getRecordIds(searchResults); + analyticsDataResponse = analyticsDataAPI.get(username,query.getTableName(),1,column,ids); + final List> iterators = Util.getRecordIterators(analyticsDataResponse,analyticsDataAPI); + return new StreamingOutput() { + @Override + public void write(OutputStream outputStream) + throws IOException, WebApplicationException { + Writer recordWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); + Map values; + + Map> stamper= new HashMap<>(); + int count=0; + String val; + String time; + recordWriter.write("["); + for (Iterator iterator : iterators) { + while (iterator.hasNext()) { + RecordBean recordBean = Util.createRecordBean(iterator.next()); + values = recordBean.getValues(); + String temp[]; + time = values.get(timestamp).toString(); + temp = time.split(" "); + time = temp[0]; + if (values.get(col) != null){ + val = values.get(col).toString(); + + if (!stamper.containsKey(time)) { + Map counter = new HashMap(); + counter.put(val, 1); + stamper.put(time, counter); + } else { + Map counter = stamper.get(time); + if(!counter.containsKey(val)){ + counter.put(val,1); + stamper.put(time,counter); + } + else { + count = counter.get(val); + count++; + counter.put(val, count); + stamper.put(time, counter); + } + } + + } + if (log.isDebugEnabled()) { + log.debug("Retrieved -- Record Id: " + recordBean.getId() + " values :" + + recordBean.toString()); + } + } + } + int i=1; + int j=1; + for(Map.Entry> entry:stamper.entrySet()){ + Map counter = entry.getValue(); + for(Map.Entry infom:counter.entrySet()){ + recordWriter.write("[[\""+entry.getKey()+"\"],[\""+infom.getKey()+"\"],[\""+infom.getValue()+"\"]]"); + if(i column = new ArrayList<>(); + column.add(col); + column.add(timestamp); + + List searchResults = analyticsDataAPI.search(username, + query.getTableName(), searchQuery, + query.getStart(), query.getCount()); + List ids = Util.getRecordIds(searchResults); + analyticsDataResponse = analyticsDataAPI.get(username,query.getTableName(),1,column,ids); + final List> iterators = Util.getRecordIterators(analyticsDataResponse,analyticsDataAPI); + return new StreamingOutput() { + @Override + public void write(OutputStream outputStream) + throws IOException, WebApplicationException { + Writer recordWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); + Map values; + + Map> stamper= new HashMap<>(); + int count=0; + String val; + String time; + recordWriter.write("["); + for (Iterator iterator : iterators) { + while (iterator.hasNext()) { + RecordBean recordBean = Util.createRecordBean(iterator.next()); + values = recordBean.getValues(); + String temp[]; + time = values.get(timestamp).toString(); + temp = time.split(" "); + time = temp[0]; + + Date date = null; + try { + date = format.parse(time); + //System.out.println(date); + } catch (ParseException e) { + e.printStackTrace(); + } + long epoch = date.getTime(); + if (values.get(col) != null){ + val = values.get(col).toString(); + + if (!stamper.containsKey(epoch)) { + Map counter = new HashMap(); + counter.put(val, 1); + stamper.put(epoch, counter); + } else { + Map counter = stamper.get(epoch); + if(!counter.containsKey(val)){ + counter.put(val,1); + stamper.put(epoch,counter); + } + else { + count = counter.get(val); + count++; + counter.put(val, count); + stamper.put(epoch, counter); + } + } + + } + if (log.isDebugEnabled()) { + log.debug("Retrieved -- Record Id: " + recordBean.getId() + " values :" + + recordBean.toString()); + } + } + } + + Map > sortedMap = new TreeMap>(stamper); + int i=1; + int j=1; + long lastDay =0; + long presentDay=0; + long k=1; + for(Map.Entry> entry:sortedMap.entrySet()) { + + /* + Map countero = entry.getValue(); + for(Map.Entry ing:countero.entrySet()){ + long temp = entry.getKey()/1000L; + Date expiry = new Date(temp*1000L); + String str_date = format.format(expiry); + recordWriter.write("[\"" + str_date + "\"],[\""+ing.getValue()+"\"]"); + } + if (j < sortedMap.size()) { + recordWriter.write(","); + j++; + } + */ + + + + if (j > 1) { + presentDay = entry.getKey(); + long dif = presentDay - lastDay; + k = dif / dayGap; + + } + if(k>1){ + int m = (int)k; + long newDate = lastDay; + while(k>1){ + newDate = newDate+dayGap; + long temp = newDate/1000L; + Date expiry = new Date(temp*1000L); + String str_date = format.format(expiry); + recordWriter.write("[[\"" + str_date + "\"],[\"" + "No Entry" + "\"],[\"" + 0 + "\"]]"); + k--; + if(k!=1){ + recordWriter.write(","); + } + } + recordWriter.write(","); + k=1; + lastDay=newDate; + } + if (k == 1){ + Map counter = entry.getValue(); + for (Map.Entry infom : counter.entrySet()) { + long temp = entry.getKey()/1000L; + Date expiry = new Date(temp*1000L); + String str_date = format.format(expiry); + recordWriter.write("[[\"" + str_date + "\"],[\"" + infom.getKey() + "\"],[\"" + infom.getValue() + "\"]]"); + if (i < counter.size()) { + recordWriter.write(","); + i++; + } + } + i = 1; + if (j < sortedMap.size()) { + recordWriter.write(","); + j++; + } + lastDay = entry.getKey(); + //recordWriter.write("[[\""+entry.getKey()+"\"],[\""+entry.getValue()+"\"]]"); + //recordWriter.write("\""+entry.getKey()+" : "+entry.getValue()+"||%\""); + } + + } + recordWriter.write("]"); + recordWriter.flush(); + } + }; + } + else{ + String msg = String.format("Error occurred while retrieving field data"); + log.error(msg); + return new StreamingOutput() { + @Override + public void write(OutputStream outputStream) throws IOException, WebApplicationException { + Writer recordWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); + recordWriter.write("Error in reading records"); + recordWriter.flush(); + } + }; + } + + } + } diff --git a/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/util/Util.java b/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/util/Util.java index 328f4c6..2b1b807 100644 --- a/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/util/Util.java +++ b/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/util/Util.java @@ -2,6 +2,7 @@ import org.apache.commons.collections.IteratorUtils; import org.wso2.carbon.analytics.dataservice.commons.AnalyticsDataResponse; +import org.wso2.carbon.analytics.dataservice.commons.SearchResultEntry; import org.wso2.carbon.analytics.dataservice.core.SecureAnalyticsDataService; import org.wso2.carbon.analytics.datasource.commons.Record; import org.wso2.carbon.analytics.datasource.commons.RecordGroup; @@ -36,4 +37,12 @@ public static RecordBean createRecordBean(Record record) { recordBean.setValues(record.getValues()); return recordBean; } + + public static List getRecordIds(List searchResults) { + List ids = new ArrayList<>(); + for (SearchResultEntry searchResult : searchResults) { + ids.add(searchResult.getId()); + } + return ids; + } } diff --git a/modules/jaggeryapps/loganalyzer/css/custom.css b/modules/jaggeryapps/loganalyzer/css/custom.css index 31eb8b9..d2dd6c5 100644 --- a/modules/jaggeryapps/loganalyzer/css/custom.css +++ b/modules/jaggeryapps/loganalyzer/css/custom.css @@ -1286,6 +1286,10 @@ a.cu-level3-btn:hover { width:250px !important; font-weight:100 !important; } +.select{ + width:250px !important; + font-weight:100 !important; +} .select2-container--default .select2-selection--single { border-radius:0px !important; diff --git a/modules/jaggeryapps/loganalyzer/js/VizGrammar.js b/modules/jaggeryapps/loganalyzer/js/VizGrammar.js new file mode 100644 index 0000000..c2af281 --- /dev/null +++ b/modules/jaggeryapps/loganalyzer/js/VizGrammar.js @@ -0,0 +1,1637 @@ +/* + * Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License./ + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var area = function(dataTable, config) { + this.metadata = dataTable[0].metadata; + var marks =[]; + this.spec = {}; + + config = checkConfig(config, this.metadata); + this.config = config; + dataTable[0].name= config.title; + + var xScale = { + "name": "x", + "type": this.metadata.types[config.x], + "range": "width", + "zero": config.zero, + "domain": {"data": config.title, "field": this.metadata.names[config.x]} + }; + + var yScale = { + "name": "y", + "type": this.metadata.types[config.y], + "range": "height", + "zero": "false", + "domain": {"data": config.title, "field": this.metadata.names[config.y]} + }; + + var scales = [xScale, yScale]; + + var axes = [ + {"type": "x", "scale": "x","grid": config.grid, "title": config.xTitle}, + {"type": "y", "scale": "y", "grid": config.grid, "title": config.yTitle} + ]; + + marks.push(getAreaMark(config, this.metadata)); + config.fillOpacity = 0; + config.markSize = 1000; + marks.push(getSymbolMark(config, this.metadata)); + + if (config.tooltip) { + marks.push(getToolTipMark(config, this.metadata)); + signals = getSignals(config,this.metadata); + this.spec.signals = signals; + } + + this.spec.width = config.width; + this.spec.height = config.height; + this.spec.axes = axes; + this.spec.data = dataTable; + this.spec.scales = scales; + this.spec.padding = config.padding; + this.spec.marks = marks; +}; + +area.prototype.draw = function(div, callbacks) { + + var viewUpdateFunction = (function(chart) { + this.view = chart({el:div}).renderer(this.config.renderer).update(); + + if (callbacks != null) { + for (var i = 0; i= this.config.maxLength){ + var allowedDataSet = []; + var startingPoint = dataset.length - maxValue; + for(var i = startingPoint; i < dataset.length;i++){ + allowedDataSet.push(dataset[i]); + } + this.spec.data[0].values = allowedDataSet; + } + } + + vg.parse.spec(this.spec, viewUpdateFunction); +}; + +area.prototype.insert = function(data) { + //Removing events when max value is enabled + if (this.config.maxLength != -1 && this.config.maxLength < (this.view.data(this.config.title).values().length + data.length)) { + var removeFunction = (function(d) { + return d[this.metadata.names[this.config.x]] == oldData; + }).bind(this); + + for (i = 0; i < data.length; i++) { + var oldData = this.view.data(this.config.title).values()[i][this.metadata.names[this.config.x]]; + this.view.data(this.config.title).remove(removeFunction); + } + } + + this.view.data(this.config.title).insert(data); + this.view.update(); +}; + +area.prototype.getSpec = function() { + return this.spec; +}; + + +function getAreaMark(config, metadata){ + var mark = { + "type": "area", + "from": {"data": config.title}, + "properties": { + "update": { + + "x": {"scale": "x", "field": metadata.names[config.x]}, + "y": {"scale": "y", "field": metadata.names[config.y]}, + "y2": {"scale": "y", "value": 0}, + "fill": { "value": config.markColor}, + "strokeWidth": {"value": 2}, + "fillOpacity": {"value": 1} + }, + "hover": { + "fillOpacity": {"value": 0.5} + } + } + }; + + return mark; +} + +; +var bar = function(dataTable, config) { + this.metadata = dataTable[0].metadata; + var marks =[]; + var scales =[]; + this.spec = {}; + var yColumn; + var yDomain; + + config = checkConfig(config, this.metadata); + this.config = config; + dataTable[0].name= config.title; + + if (config.color != -1) { + var aggregateData = { + "name": "stack", + "source": config.title, + "transform": [ + { + "type": "aggregate", + "groupby": [this.metadata.names[config.x]], + "summarize": [{"field": this.metadata.names[config.y], "ops": ["sum"]}] + } + ] + }; + + var legendTitle = "Legend"; + + if (config.title != "table") { + legendTitle = config.title; + } + + + + dataTable.push(aggregateData); + + if (config.colorDomain == null) { + config.colorDomain = {"data": config.title, "field": this.metadata.names[config.color]}; + } + + var colorScale = { + "name": "color", + "type": "ordinal", + "domain": config.colorDomain, + "range": config.colorScale + }; + + scales.push(colorScale); + + var legends = [ + { + "fill": "color", + "title": "Legend", + "offset": 0, + "properties": { + "symbols": { + "fillOpacity": {"value": 0.5}, + "stroke": {"value": "transparent"} + } + } + } + ]; + + this.spec.legends = legends; + yColumn = "sum_"+ this.metadata.names[config.y]; + yDomain = "stack"; + + } else { + yColumn = this.metadata.names[config.y]; + yDomain = config.title; + } + + var xScale = { + "name": "x", + "type": "ordinal", + "range": "width", + "domain": {"data": config.title, "field": this.metadata.names[config.x]} + }; + + var yScale = { + "name": "y", + "type": this.metadata.types[config.y], + "range": "height", + "domain": {"data": yDomain, "field": yColumn} + }; + + scales.push(xScale); + scales.push(yScale); + + + + var axes = [ + {"type": "x", "scale": "x","grid": config.grid, "title": config.xTitle}, + {"type": "y", "scale": "y", "grid": config.grid, "title": config.yTitle} + ]; + + if (config.color != -1 && config.mode == "stack") { + marks.push(getStackBarMark(config, this.metadata)); + } else { + marks.push(getBarMark(config, this.metadata)); + } + + if (config.tooltip) { + marks.push(getToolTipMark(config, this.metadata)); + config.hoverType = "rect"; + signals = getSignals(config,this.metadata); + this.spec.signals = signals; + } + + + this.spec.width = config.width; + this.spec.height = config.height; + this.spec.axes = axes; + this.spec.data = dataTable; + this.spec.scales = scales; + this.spec.padding = config.padding; + this.spec.marks = marks; + + var specc = JSON.stringify(this.spec); +}; + +bar.prototype.draw = function(div, callbacks) { + var viewUpdateFunction = (function(chart) { + this.view = chart({el:div}).renderer(this.config.renderer).update(); + + if (callbacks != null) { + for (var i = 0; i= this.config.maxLength){ + var allowedDataSet = []; + var startingPoint = dataset.length - maxValue; + for(var i = startingPoint; i < dataset.length;i++){ + allowedDataSet.push(dataset[i]); + } + this.spec.data[0].values = allowedDataSet; + } + } + + vg.parse.spec(this.spec, viewUpdateFunction); +}; + +bar.prototype.insert = function(data) { + + var xAxis = this.metadata.names[this.config.x]; + var yAxis = this.metadata.names[this.config.y]; + var size = this.metadata.names[this.config.size]; + var color = this.metadata.names[this.config.color]; + + if (this.config.maxLength != -1 && this.config.maxLength < (this.view.data(this.config.title).values().length + data.length)) { + + var allDataSet = this.view.data(this.config.title).values().concat(data); + var allowedRemovableDataSet = []; + for (i = 0; i < allDataSet.length - this.config.maxLength; i++) { + allowedRemovableDataSet.push(this.view.data(this.config.title).values()[i][xAxis]); + } + + for (i = 0; i < data.length; i++) { + var isValueMatched = false; + this.view.data(this.config.title).update(function(d) { + var match; + if (color == -1) { + match = d[xAxis] == data[i][xAxis]; + } else { + match = d[xAxis] == data[i][xAxis] && d[color] == data[i][color]; + } + }, + yAxis, + function(d) { + isValueMatched = true; + return data[i][yAxis]; + }); + + if(isValueMatched){ + var isIndexRemoved = false; + + var index = allowedRemovableDataSet.indexOf(data[i][xAxis]); + if (index > -1) { + // updated value matched in allowed removable values + isIndexRemoved = true; + allowedRemovableDataSet.splice(index, 1); + } + + if(!isIndexRemoved){ + // updated value NOT matched in allowed removable values + allowedRemovableDataSet.splice((allowedRemovableDataSet.length - 1), 1); + } + + } else { + //insert the new data + this.view.data(this.config.title).insert([data[i]]); + this.view.update(); + } + } + + var oldData; + var removeFunction = function(d) { + return d[xAxis] == oldData; + }; + + for (i = 0; i < allowedRemovableDataSet.length; i++) { + oldData = allowedRemovableDataSet[i]; + this.view.data(this.config.title).remove(removeFunction); + } + } else{ + for (i = 0; i < data.length; i++) { + var isValueMatched = false; + this.view.data(this.config.title).update(function(d) { + var match; + if (color == -1) { + match = d[xAxis] == data[i][xAxis]; + } else { + match = d[xAxis] == data[i][xAxis] && d[color] == data[i][color]; + } + }, + yAxis, + function(d) { + isValueMatched = true; + return data[i][yAxis]; + }); + + if(!isValueMatched){ + this.view.data(this.config.title).insert([data[i]]); + } + } + } + this.view.update({duration: 200}); + +}; + +bar.prototype.getSpec = function() { + return this.spec; +}; + + +function getBarMark(config, metadata){ + + var mark = { + "type": "rect", + "from": {"data": config.title}, + "properties": { + "update": { + + "x": {"scale": "x", "field": metadata.names[config.x]}, + "width": {"scale": "x", "band": true, "offset": -1}, + "y": {"scale": "y", "field": metadata.names[config.y]}, + "y2": {"scale": "y", "value": 0}, + "fill": {"value": "steelblue"}, + "fillOpacity": {"value": 1} + }, + "hover": { + "fillOpacity": {"value": 0.5} + } + } + }; + + + return mark; +} + +function getStackBarMark(config, metadata){ + + var mark = { + "type": "rect", + "from": { + "data": "table", + "transform": [ + { "type": "stack", + "groupby": [metadata.names[config.x]], + "sortby": [metadata.names[config.color]], + "field":metadata.names[config.y]} + ] + }, + "properties": { + "update": { + "x": {"scale": "x", "field": metadata.names[config.x]}, + "width": {"scale": "x", "band": true, "offset": -1}, + "y": {"scale": "y", "field": "layout_start"}, + "y2": {"scale": "y", "field": "layout_end"}, + "fill": {"scale": "color", "field": metadata.names[config.color]}, + "fillOpacity": {"value": 1} + }, + "hover": { + "fillOpacity": {"value": 0.5} + } + } + }; + + + return mark; +} + +;var vizg = function(dataTable, config) { + dataTable = buildTable(dataTable); + if (typeof config.charts !== "undefined" && config.charts.length == 1) { + //Set chart config properties for main + for (var property in config.charts[0]) { + if (config.charts[0].hasOwnProperty(property)) { + config[property] = config.charts[0][property]; + } + } + + this.chart = new window[config.type]([dataTable], config); + } +}; + +vizg.prototype.draw = function(div, callback) { + this.chart.draw(div, callback); +}; + +vizg.prototype.insert = function(data) { + this.chart.insert(buildData(data, this.chart.metadata)); +}; + +vizg.prototype.getSpec = function() { + return this.chart.getSpec(); +};; +var line = function(dataTable, config) { + this.metadata = dataTable[0].metadata; + var marks =[]; + this.spec = {}; + + config = checkConfig(config, this.metadata); + this.config = config; + dataTable[0].name= config.title; + + var xScale = { + "name": "x", + "type": this.metadata.types[config.x], + "range": "width", + "zero": config.zero, + "domain": {"data": config.title, "field": this.metadata.names[config.x]} + }; + + var yScale = { + "name": "y", + "type": this.metadata.types[config.y], + "range": "height", + "zero": config.zero, + "domain": {"data": config.title, "field": this.metadata.names[config.y]} + }; + + var scales = [xScale, yScale]; + + if (config.color != -1) { + + if (config.colorDomain == null) { + config.colorDomain = {"data": config.title, "field": this.metadata.names[config.color]}; + } + + var colorScale = { + "name": "color", + "type": "ordinal", + "domain": config.colorDomain, + "range": config.colorScale + }; + scales.push(colorScale); + } + + var axes = [ + {"type": "x", "scale": "x","grid": config.grid, "title": config.xTitle}, + {"type": "y", "scale": "y", "grid": config.grid, "title": config.yTitle} + ]; + + marks.push(getLineMark(config, this.metadata)); + config.markSize = 20; + marks.push(getSymbolMark(config, this.metadata)); + + if (config.tooltip) { + marks.push(getToolTipMark(config, this.metadata)); + signals = getSignals(config,this.metadata); + this.spec.signals = signals; + } + + if (config.color != -1) { + + var legendTitle = "Legend"; + + if (config.title != "table") { + legendTitle = config.title; + } + + var legends = [ + { + "fill": "color", + "title": "Legend", + "offset": 0, + "properties": { + "symbols": { + "fillOpacity": {"value": 0.5}, + "stroke": {"value": "transparent"} + } + } + } + ]; + + this.spec.legends = legends; + } + + this.spec.width = config.width; + this.spec.height = config.height; + this.spec.axes = axes; + this.spec.data = dataTable; + this.spec.scales = scales; + this.spec.padding = config.padding; + this.spec.marks = marks; + +}; + +line.prototype.draw = function(div, callbacks) { + + var viewUpdateFunction = (function(chart) { + this.view = chart({el:div}).renderer(this.config.renderer).update(); + + if (callbacks != null) { + for (var i = 0; i= this.config.maxLength){ + var allowedDataSet = []; + var startingPoint = dataset.length - maxValue; + for(var i = startingPoint; i < dataset.length;i++){ + allowedDataSet.push(dataset[i]); + } + this.spec.data[0].values = allowedDataSet; + } + } + + vg.parse.spec(this.spec, viewUpdateFunction); + + +}; + +line.prototype.insert = function(data) { + //Removing events when max value is enabled + if (this.config.maxLength != -1 && this.config.maxLength < (this.view.data(this.config.title).values().length + data.length)) { + var removeFunction = (function(d) { + return d[this.metadata.names[this.config.x]] == oldData; + }).bind(this); + + for (i = 0; i < data.length; i++) { + var oldData = this.view.data(this.config.title).values()[i][this.metadata.names[this.config.x]]; + this.view.data(this.config.title).remove(removeFunction); + } + } + + this.view.data(this.config.title).insert(data); + this.view.update(); +}; + +line.prototype.getSpec = function() { + return this.spec; +}; + +function getLineMark(config, metadata){ + var mark; + if (config.color != -1) { + mark = { + "type": "group", + "from": { + "data": config.title, + "transform": [{"type": "facet", "groupby": [metadata.names[config.color]]}] + }, + "marks": [ + { + "type": "line", + "properties": { + "update": { + "x": {"scale": "x", "field": metadata.names[config.x]}, + "y": {"scale": "y", "field": metadata.names[config.y]}, + "stroke": {"scale": "color", "field": metadata.names[config.color]}, + "strokeWidth": {"value": 2}, + "strokeOpacity": {"value": 1} + }, + "hover": { + "strokeOpacity": {"value": 0.5} + } + } + } + ] + }; + } else { + mark = { + "type": "line", + "from": {"data": config.title}, + "properties": { + "update": { + + "x": {"scale": "x", "field": metadata.names[config.x]}, + "y": {"scale": "y", "field": metadata.names[config.y]}, + "stroke": { "value": config.markColor}, + "strokeWidth": {"value": 2}, + "strokeOpacity": {"value": 1} + }, + "hover": { + "strokeOpacity": {"value": 0.5} + } + } + }; + } + + return mark; +} + +;var map = function(dataTable, config) { + + this.metadata = dataTable[0].metadata; + var marks ; + var signals ; + var predicates = []; + var legends = []; + this.spec = {}; + var geoInfoJson ; + + geoInfoJson = loadGeoMapCodes(config.helperUrl); + config = checkConfig(config, this.metadata); + this.config = config; + this.config.geoInfoJson = geoInfoJson; + config.toolTip.height = 20; + config.toolTip.width = 100; + + for (i = 0; i < dataTable[0].values.length; i++) { + for (var key in dataTable[0].values[i]) { + if(key == dataTable[0].metadata.names[config.x]){ + if (dataTable[0].values[i].hasOwnProperty(key)) { + dataTable[0].values[i].unitName = dataTable[0].values[i][key]; + dataTable[0].values[i][key] = getMapCode(dataTable[0].values[i][key], config.mapType,geoInfoJson); + break; + } + } + } + }; + + dataTable[0].name = config.title; + dataTable[0].transform = [ + { + "type": "formula", + "field": "v", + "expr": "datum."+this.metadata.names[config.y] + } + ]; + + if (config.tooltip) { + marks = getMapMark(config, this.metadata); + signals = getMapSignals(); + this.spec.signals = signals; + } + + dataTable.push(getTopoJson(config,this.metadata)); + predicates.push(getMapPredicates()); + legends.push(getMapLegends(config,this.metadata)); + + var cScale = { + "name": "color", + "type": "linear", + "domain": {"data": "geoData","field": "zipped.v"}, + "domainMin": 0.0, + "zero": false, + "range": ["#FFEDBC", "#f83600"] + }; + + var scales = [cScale]; + + this.spec.width = config.width; + this.spec.height = config.height; + this.spec.data = dataTable; + this.spec.scales = scales; + this.spec.padding = config.padding; + this.spec.marks = marks; + this.spec.predicates = predicates; + this.spec.legends = legends; + +}; + +map.prototype.draw = function(div, callbacks) { + var viewUpdateFunction = (function(chart) { + this.view = chart({el:div}).renderer(this.config.renderer).update(); + + if (callbacks != null) { + for (var i = 0; i
" + +"

" + +textContent+"

"; + + document.getElementById(div).innerHTML = divContent; + this.view = contentId; +}; + +number.prototype.insert = function(data) { + document.getElementById(this.view).innerHTML = data[data.length-1][this.metadata.names[this.config.x]]; +}; + + + +;var scatter = function(dataTable, config) { + + this.metadata = dataTable[0].metadata; + var marks = []; + var signals ; + this.spec = {}; + + config = checkConfig(config, this.metadata); + this.config = config; + dataTable[0].name = config.title; + + var xScale = { + "name": "x", + "type": this.metadata.types[config.x], + "range": "width", + "zero": config.zero, + "domain": {"data": config.title, "field": this.metadata.names[config.x]} + }; + + var yScale = { + "name": "y", + "type": this.metadata.types[config.y], + "range": "height", + "zero": config.zero, + "domain": {"data": config.title, "field": this.metadata.names[config.y]} + }; + + var rScale = { + "name": "size", + "type": "linear", + "range": [0,576], + "domain": {"data": config.title, "field": this.metadata.names[config.size]} + }; + + var cScale = { + "name": "color", + "type": "linear", + "range": [config.minColor,config.maxColor], + "domain": {"data": config.title, "field": this.metadata.names[config.color]} + }; + + var scales = [xScale, yScale, rScale, cScale]; + + var axes = [ + {"type": "x", "scale": "x","grid": config.grid, "title": config.xTitle}, + {"type": "y", "scale": "y", "grid": config.grid, "title": config.yTitle} + ]; + + marks.push(getScatterMark(config, this.metadata)); + + if (config.tooltip) { + marks.push(getToolTipMark(config, this.metadata)); + signals = getSignals(config,this.metadata); + this.spec.signals = signals; + } + + + this.spec.width = config.width; + this.spec.height = config.height; + this.spec.axes = axes; + this.spec.data = dataTable; + this.spec.scales = scales; + this.spec.padding = config.padding; + this.spec.marks = marks; + +}; + +scatter.prototype.draw = function(div, callbacks) { + var viewUpdateFunction = (function(chart) { + this.view = chart({el:div}).renderer(this.config.renderer).update(); + + if (callbacks != null) { + for (var i = 0; i= this.config.maxLength){ + var allowedDataSet = []; + var startingPoint = dataset.length - maxValue; + for(var i = startingPoint; i < dataset.length;i++){ + allowedDataSet.push(dataset[i]); + } + this.spec.data[0].values = allowedDataSet; + } + } + + vg.parse.spec(this.spec, viewUpdateFunction); +}; + +scatter.prototype.insert = function(data) { + + var xAxis = this.metadata.names[this.config.x]; + var yAxis = this.metadata.names[this.config.y]; + var size = this.metadata.names[this.config.size]; + var color = this.metadata.names[this.config.color]; + + if (this.config.maxLength != -1 && this.config.maxLength < (this.view.data(this.config.title).values().length + data.length)) { + + var allDataSet = this.view.data(this.config.title).values().concat(data); + var allowedRemovableDataSet = []; + for (i = 0; i < allDataSet.length - this.config.maxLength; i++) { + allowedRemovableDataSet.push(this.view.data(this.config.title).values()[i][xAxis]); + } + + for (i = 0; i < data.length; i++) { + var isValueMatched = false; + this.view.data(this.config.title).update(function(d) { + return d[xAxis] == data[i][xAxis]; }, + yAxis, + function(d) { + isValueMatched = true; + return data[i][yAxis]; + }); + + this.view.data(this.config.title).update(function(d) { + return d[xAxis] == data[i][xAxis]; }, + color, + function(d) { + isValueMatched = true; + return data[i][color]; + }); + + this.view.data(this.config.title).update(function(d) { + return d[xAxis] == data[i][xAxis]; }, + size, + function(d) { + isValueMatched = true; + return data[i][size]; + }); + + if(isValueMatched){ + var isIndexRemoved = false; + + var index = allowedRemovableDataSet.indexOf(data[i][xAxis]); + if (index > -1) { + // updated value matched in allowed removable values + isIndexRemoved = true; + allowedRemovableDataSet.splice(index, 1); + } + + if(!isIndexRemoved){ + // updated value NOT matched in allowed removable values + allowedRemovableDataSet.splice((allowedRemovableDataSet.length - 1), 1); + } + + } else { + //insert the new data + this.view.data(this.config.title).insert([data[i]]); + this.view.update(); + } + } + + var oldData; + var removeFunction = function(d) { + return d[xAxis] == oldData; + }; + + for (i = 0; i < allowedRemovableDataSet.length; i++) { + oldData = allowedRemovableDataSet[i]; + this.view.data(this.config.title).remove(removeFunction); + } + } else{ + for (i = 0; i < data.length; i++) { + var isValueMatched = false; + this.view.data(this.config.title).update(function(d) { + return d[xAxis] == data[i][xAxis]; }, + yAxis, + function(d) { + isValueMatched = true; + return data[i][yAxis]; + }); + + this.view.data(this.config.title).update(function(d) { + return d[xAxis] == data[i][xAxis]; }, + color, + function(d) { + isValueMatched = true; + return data[i][color]; + }); + + this.view.data(this.config.title).update(function(d) { + return d[xAxis] == data[i][xAxis]; }, + size, + function(d) { + isValueMatched = true; + return data[i][size]; + }); + + if(!isValueMatched){ + this.view.data(this.config.title).insert([data[i]]); + } + } + } + this.view.update({duration: 200}); +}; + +scatter.prototype.getSpec = function() { + return this.spec; +}; + + +function getScatterMark(config, metadata){ + + var mark = { + + "type": "symbol", + "from": {"data": config.title}, + "properties": { + "update": { + "x": {"scale": "x", "field": metadata.names[config.x]}, + "y": {"scale": "y", "field": metadata.names[config.y]}, + "fill": {"scale": "color", "field": metadata.names[config.color]}, + "size": {"scale":"size","field":metadata.names[config.size]}, + "fillOpacity": {"value": 1} + }, + "hover": { + "fillOpacity": {"value": 0.5} + } + } + + } + ; + + + return mark; +} + +function getScatterToolTipMark(config, metadata) { + config.toolTip.height = 50; + config.toolTip.y = -50; + + var mark = getToolTipMark(config, metadata); + var sizeText = { + "type": "text", + "properties": { + "update": { + "x": {"value": 6}, + "y": {"value": 44}, + "text": {"template": "Size \t (" + metadata.names[config.size] + ") \t {{hover." + metadata.names[config.size] + "}}"}, + "fill": {"value": "black"} + } + } + }; + mark.marks.push(sizeText); + return mark; +} +; +var table = function(dataTable, config) { + this.metadata = dataTable[0].metadata; + this.data = dataTable[0].values + var marks =[]; + this.spec = {}; + config = checkConfig(config, this.metadata); + this.config = config; + dataTable[0].name= config.title; + +}; + +table.prototype.draw = function(div) { + var table = d3.select(div).append("table") + .attr( "cellpadding", "8px") + .attr( "border", "2px") + .attr( "width", "100%") + .attr("id", this.config.title); + + // set up the table header + table.append('thead').attr("align", "center") + .append('tr') + .selectAll('th') + .data(this.config.columnTitles) + .enter() + .append('th') + .text(function (d) { return d }); + + table.append('tbody').attr("id", "tableChart-"+this.config.title); + this.setupData(this.data, this.config); + + table.selectAll("thead th") + .text(function(column) { + return column.charAt(0).toUpperCase() + column.substr(1); + }); + + + }; + +table.prototype.insert = function(data) { + this.setupData(data, this.config); +}; + + +table.prototype.setupData = function (dataset, config) { + var data = []; + var allColumns = this.metadata.names; + + //Select specified columns from dataset + for (var i = 0; i < dataset.length; i++) { + var row = {}; + + for (var x = 0; x < config.columns.length; x++) { + row[config.columns[x]] = dataset[i][config.columns[x]]; + } + data.push(row); + } + + //Select Rows by x Axis + var rows = d3.select('#tableChart-'+config.title) + .selectAll('tr') + .data(data, function(d) { return d[config.key]}) + + var entertd = rows.enter() + .append('tr') + .selectAll('td') + .data(function(row) { + return config.columns.map(function(column) { + return {column: column, value: row[column]}; + }); + }) + .enter() + .append('td') + + //Color cell background + if (config.color != -1) { + d3.select('#tableChart-'+config.title) + .selectAll('td') + .attr('bgcolor', + function(d) { + var column = d.key || d.column; + if (typeof d.value == "string") { + + } else if (config.color == "*" || column == allColumns[config.color]){ + var color; + if (typeof config.colorScale == "string") { + color = window["d3"]["scale"][config.colorScale]().range(); + } else { + color = config.colorScale; + } + + var colorIndex; + for(var i = 0; i < allColumns.length; i += 1) { + if(allColumns[i] === column) { + colorIndex = i; + } + } + var colorScale = d3.scale.linear() + .range(['#f2f2f2', color[colorIndex]]) + .domain([d3.min(d3.select('#tableChart-'+config.title) .selectAll('tr') .data(), function(d) { return d[column]; }), + d3.max(d3.select('#tableChart-'+config.title) .selectAll('tr') .data(), function(d) { return d[column]; })] + ); + + return colorScale(d.value); + } + + }); + } + + + + entertd.append('span') + var td = rows.selectAll('td') + .style({"padding": "0px 10px 0px 10px"}) + + .data(function(d) { return d3.map(d).entries() }) + .attr('class', function (d) { return d.key }) + + + + + td.select('span') + .text(function(d) { + return d.value + }) + //Remove data items when it hits maxLength + if (config.maxLength != -1 && d3.select('tbody').selectAll('tr').data().length > config.maxLength) { + var allowedDataset = d3.select('tbody').selectAll('tr').data().slice(d3.select('tbody').selectAll('tr').data().length- config.maxLength, config.maxLength); + d3.select('tbody').selectAll('tr').data(allowedDataset, + function(d) { + return(d); + }) + .remove(); + } +};function checkConfig(config, metadata){ + + if (config.title == null) { + config.title = "table"; + } + + if (config.xTitle == null) { + config.xTitle = config.x; + } + + if (config.yTitle == null) { + config.yTitle = config.y; + } + + if (config.colorScale == null) { + config.colorScale = "category10"; + } + + if (config.grid == null) { + config.grid = true; + } + + if (config.zero == null) { + config.zero = false; + } + + if (config.color == null) { + config.color = -1; + } else if (config.color != "*"){ + config.color = metadata.names.indexOf(config.color); + } + + if (config.mapType == null) { + config.mapType = -1; + } + + if (config.minColor == null) { + config.minColor = -1; + } + + if (config.maxColor == null) { + config.maxColor = -1; + } + + if (config.mode == null) { + config.mode = "stack"; + } + + if (config.size == null) { + config.size = -1; + } else { + config.size = metadata.names.indexOf(config.size); + } + + if (config.maxLength == null) { + config.maxLength = -1; + } + + if (config.markColor == null) { + config.markColor = "steelblue"; + } + + if (config.markSize == null) { + config.markSize = 2; + } + + if (config.fillOpacity == null) { + config.fillOpacity = 1; + } + + if (config.renderer == null) { + config.renderer = "canvas"; + } + + if (config.toolTip == null) { + config.toolTip = {"height" : 35, "width" : 120, "color":"#e5f2ff", "x": 0, "y":-30}; + } + + if (config.padding == null) { + config.padding = {"top": 50, "left": 60, "bottom": 40, "right": 150}; + } + + if (config.hoverType == null) { + config.hoverType = "symbol"; + } + + if (config.tooltip == null) { + config.tooltip = true; + } + + config.x = metadata.names.indexOf(config.x); + config.y = metadata.names.indexOf(config.y); + + return config; +} + +function buildTable(datatable) { + var chartDatatable = {}; + chartDatatable.metadata = datatable[0].metadata; + chartDatatable.values = buildData(datatable[0].data, datatable[0].metadata); + return chartDatatable; +} + + +function buildData(data, metadata) { + chartData = []; + for (i = 0; i < data.length; i++) { + var row = {}; + for (x = 0; x < metadata.names.length; x++) { + row[metadata.names[x]] = data[i][x]; + } + chartData.push(row); + } + return chartData; +} + +/* + General function used to draw circle symbols graphs +*/ +function getSymbolMark(config, metadata) { + + var fill; + if (config.color != -1) { + fill = {"scale": "color", "field": metadata.names[config.color]}; + } else { + fill = {"value":config.markColor}; + } + +var mark = { + "type": "symbol", + "from": {"data": config.title}, + "properties": { + "update": { + "x": {"scale": "x", "field": metadata.names[config.x]}, + "y": {"scale": "y", "field": metadata.names[config.y]}, + "fill": fill, + "size": {"value": config.markSize}, + "fillOpacity": {"value": config.fillOpacity} + } + } + } + + return mark; +} + + +function getToolTipMark(config , metadata) { + var mark = { + "type": "group", + "from": {"data": "table", + "transform": [ + { + "type": "filter", + "test": "datum." + metadata.names[config.x] + " == hover." + metadata.names[config.x] + "" + } + ]}, + "properties": { + "update": { + "x": {"scale": "x", "signal": "hover." + metadata.names[config.x], "offset": config.toolTip.x}, + "y": {"scale": "y", "signal": "hover." + metadata.names[config.y], "offset": config.toolTip.y}, + "width": {"value": config.toolTip.width}, + "height": {"value": config.toolTip.height}, + "fill": {"value": config.toolTip.color} + } + }, + + "marks": [ + { + "type": "text", + "properties": { + "update": { + "x": {"value": 6}, + "y": {"value": 14}, + "text": {"template": "X \n (" + metadata.names[config.x] + ") \t {{hover." + metadata.names[config.x] + "}}"}, + "fill": {"value": "black"} + } + } + }, + { + "type": "text", + "properties": { + "update": { + "x": {"value": 6}, + "y": {"value": 29}, + "text": {"template": "Y \t (" + metadata.names[config.y] + ") \t {{hover." + metadata.names[config.y] + "}}"}, + "fill": {"value": "black"} + } + } + } + ] + } + + return mark; +} + +function getSignals(config, metadata){ + + var signals = [{ + + "name": "hover", + "init": {}, + "streams": [ + {"type": config.hoverType+":mouseover", "expr": "datum"}, + {"type": config.hoverType+":mouseout", "expr": "{}"} + ] + }]; + + return signals; + +} + + + diff --git a/modules/jaggeryapps/loganalyzer/js/vega.js b/modules/jaggeryapps/loganalyzer/js/vega.js new file mode 100644 index 0000000..f6ea10d --- /dev/null +++ b/modules/jaggeryapps/loganalyzer/js/vega.js @@ -0,0 +1,20654 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.vg = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o> 5, + ch = 1 << 11; + +module.exports = function() { + var size = [256, 256], + text = cloudText, + font = cloudFont, + fontSize = cloudFontSize, + fontStyle = cloudFontNormal, + fontWeight = cloudFontNormal, + rotate = cloudRotate, + padding = cloudPadding, + spiral = archimedeanSpiral, + words = [], + timeInterval = Infinity, + event = dispatch("word", "end"), + timer = null, + random = Math.random, + cloud = {}, + canvas = cloudCanvas; + + cloud.canvas = function(_) { + return arguments.length ? (canvas = functor(_), cloud) : canvas; + }; + + cloud.start = function() { + var contextAndRatio = getContext(canvas()), + board = zeroArray((size[0] >> 5) * size[1]), + bounds = null, + n = words.length, + i = -1, + tags = [], + data = words.map(function(d, i) { + d.text = text.call(this, d, i); + d.font = font.call(this, d, i); + d.style = fontStyle.call(this, d, i); + d.weight = fontWeight.call(this, d, i); + d.rotate = rotate.call(this, d, i); + d.size = ~~fontSize.call(this, d, i); + d.padding = padding.call(this, d, i); + return d; + }).sort(function(a, b) { return b.size - a.size; }); + + if (timer) clearInterval(timer); + timer = setInterval(step, 0); + step(); + + return cloud; + + function step() { + var start = Date.now(); + while (Date.now() - start < timeInterval && ++i < n && timer) { + var d = data[i]; + d.x = (size[0] * (random() + .5)) >> 1; + d.y = (size[1] * (random() + .5)) >> 1; + cloudSprite(contextAndRatio, d, data, i); + if (d.hasText && place(board, d, bounds)) { + tags.push(d); + event.word(d); + if (bounds) cloudBounds(bounds, d); + else bounds = [{x: d.x + d.x0, y: d.y + d.y0}, {x: d.x + d.x1, y: d.y + d.y1}]; + // Temporary hack + d.x -= size[0] >> 1; + d.y -= size[1] >> 1; + } + } + if (i >= n) { + cloud.stop(); + event.end(tags, bounds); + } + } + } + + cloud.stop = function() { + if (timer) { + clearInterval(timer); + timer = null; + } + return cloud; + }; + + function getContext(canvas) { + canvas.width = canvas.height = 1; + var ratio = Math.sqrt(canvas.getContext("2d").getImageData(0, 0, 1, 1).data.length >> 2); + canvas.width = (cw << 5) / ratio; + canvas.height = ch / ratio; + + var context = canvas.getContext("2d"); + context.fillStyle = context.strokeStyle = "red"; + context.textAlign = "center"; + + return {context: context, ratio: ratio}; + } + + function place(board, tag, bounds) { + var perimeter = [{x: 0, y: 0}, {x: size[0], y: size[1]}], + startX = tag.x, + startY = tag.y, + maxDelta = Math.sqrt(size[0] * size[0] + size[1] * size[1]), + s = spiral(size), + dt = random() < .5 ? 1 : -1, + t = -dt, + dxdy, + dx, + dy; + + while (dxdy = s(t += dt)) { + dx = ~~dxdy[0]; + dy = ~~dxdy[1]; + + if (Math.min(Math.abs(dx), Math.abs(dy)) >= maxDelta) break; + + tag.x = startX + dx; + tag.y = startY + dy; + + if (tag.x + tag.x0 < 0 || tag.y + tag.y0 < 0 || + tag.x + tag.x1 > size[0] || tag.y + tag.y1 > size[1]) continue; + // TODO only check for collisions within current bounds. + if (!bounds || !cloudCollide(tag, board, size[0])) { + if (!bounds || collideRects(tag, bounds)) { + var sprite = tag.sprite, + w = tag.width >> 5, + sw = size[0] >> 5, + lx = tag.x - (w << 4), + sx = lx & 0x7f, + msx = 32 - sx, + h = tag.y1 - tag.y0, + x = (tag.y + tag.y0) * sw + (lx >> 5), + last; + for (var j = 0; j < h; j++) { + last = 0; + for (var i = 0; i <= w; i++) { + board[x + i] |= (last << msx) | (i < w ? (last = sprite[j * w + i]) >>> sx : 0); + } + x += sw; + } + delete tag.sprite; + return true; + } + } + } + return false; + } + + cloud.timeInterval = function(_) { + return arguments.length ? (timeInterval = _ == null ? Infinity : _, cloud) : timeInterval; + }; + + cloud.words = function(_) { + return arguments.length ? (words = _, cloud) : words; + }; + + cloud.size = function(_) { + return arguments.length ? (size = [+_[0], +_[1]], cloud) : size; + }; + + cloud.font = function(_) { + return arguments.length ? (font = functor(_), cloud) : font; + }; + + cloud.fontStyle = function(_) { + return arguments.length ? (fontStyle = functor(_), cloud) : fontStyle; + }; + + cloud.fontWeight = function(_) { + return arguments.length ? (fontWeight = functor(_), cloud) : fontWeight; + }; + + cloud.rotate = function(_) { + return arguments.length ? (rotate = functor(_), cloud) : rotate; + }; + + cloud.text = function(_) { + return arguments.length ? (text = functor(_), cloud) : text; + }; + + cloud.spiral = function(_) { + return arguments.length ? (spiral = spirals[_] || _, cloud) : spiral; + }; + + cloud.fontSize = function(_) { + return arguments.length ? (fontSize = functor(_), cloud) : fontSize; + }; + + cloud.padding = function(_) { + return arguments.length ? (padding = functor(_), cloud) : padding; + }; + + cloud.random = function(_) { + return arguments.length ? (random = _, cloud) : random; + }; + + cloud.on = function() { + var value = event.on.apply(event, arguments); + return value === event ? cloud : value; + }; + + return cloud; +}; + +function cloudText(d) { + return d.text; +} + +function cloudFont() { + return "serif"; +} + +function cloudFontNormal() { + return "normal"; +} + +function cloudFontSize(d) { + return Math.sqrt(d.value); +} + +function cloudRotate() { + return (~~(Math.random() * 6) - 3) * 30; +} + +function cloudPadding() { + return 1; +} + +// Fetches a monochrome sprite bitmap for the specified text. +// Load in batches for speed. +function cloudSprite(contextAndRatio, d, data, di) { + if (d.sprite) return; + var c = contextAndRatio.context, + ratio = contextAndRatio.ratio; + + c.clearRect(0, 0, (cw << 5) / ratio, ch / ratio); + var x = 0, + y = 0, + maxh = 0, + n = data.length; + --di; + while (++di < n) { + d = data[di]; + c.save(); + c.font = d.style + " " + d.weight + " " + ~~((d.size + 1) / ratio) + "px " + d.font; + var w = c.measureText(d.text + "m").width * ratio, + h = d.size << 1; + if (d.rotate) { + var sr = Math.sin(d.rotate * cloudRadians), + cr = Math.cos(d.rotate * cloudRadians), + wcr = w * cr, + wsr = w * sr, + hcr = h * cr, + hsr = h * sr; + w = (Math.max(Math.abs(wcr + hsr), Math.abs(wcr - hsr)) + 0x1f) >> 5 << 5; + h = ~~Math.max(Math.abs(wsr + hcr), Math.abs(wsr - hcr)); + } else { + w = (w + 0x1f) >> 5 << 5; + } + if (h > maxh) maxh = h; + if (x + w >= (cw << 5)) { + x = 0; + y += maxh; + maxh = 0; + } + if (y + h >= ch) break; + c.translate((x + (w >> 1)) / ratio, (y + (h >> 1)) / ratio); + if (d.rotate) c.rotate(d.rotate * cloudRadians); + c.fillText(d.text, 0, 0); + if (d.padding) c.lineWidth = 2 * d.padding, c.strokeText(d.text, 0, 0); + c.restore(); + d.width = w; + d.height = h; + d.xoff = x; + d.yoff = y; + d.x1 = w >> 1; + d.y1 = h >> 1; + d.x0 = -d.x1; + d.y0 = -d.y1; + d.hasText = true; + x += w; + } + var pixels = c.getImageData(0, 0, (cw << 5) / ratio, ch / ratio).data, + sprite = []; + while (--di >= 0) { + d = data[di]; + if (!d.hasText) continue; + var w = d.width, + w32 = w >> 5, + h = d.y1 - d.y0; + // Zero the buffer + for (var i = 0; i < h * w32; i++) sprite[i] = 0; + x = d.xoff; + if (x == null) return; + y = d.yoff; + var seen = 0, + seenRow = -1; + for (var j = 0; j < h; j++) { + for (var i = 0; i < w; i++) { + var k = w32 * j + (i >> 5), + m = pixels[((y + j) * (cw << 5) + (x + i)) << 2] ? 1 << (31 - (i % 32)) : 0; + sprite[k] |= m; + seen |= m; + } + if (seen) seenRow = j; + else { + d.y0++; + h--; + j--; + y++; + } + } + d.y1 = d.y0 + seenRow; + d.sprite = sprite.slice(0, (d.y1 - d.y0) * w32); + } +} + +// Use mask-based collision detection. +function cloudCollide(tag, board, sw) { + sw >>= 5; + var sprite = tag.sprite, + w = tag.width >> 5, + lx = tag.x - (w << 4), + sx = lx & 0x7f, + msx = 32 - sx, + h = tag.y1 - tag.y0, + x = (tag.y + tag.y0) * sw + (lx >> 5), + last; + for (var j = 0; j < h; j++) { + last = 0; + for (var i = 0; i <= w; i++) { + if (((last << msx) | (i < w ? (last = sprite[j * w + i]) >>> sx : 0)) + & board[x + i]) return true; + } + x += sw; + } + return false; +} + +function cloudBounds(bounds, d) { + var b0 = bounds[0], + b1 = bounds[1]; + if (d.x + d.x0 < b0.x) b0.x = d.x + d.x0; + if (d.y + d.y0 < b0.y) b0.y = d.y + d.y0; + if (d.x + d.x1 > b1.x) b1.x = d.x + d.x1; + if (d.y + d.y1 > b1.y) b1.y = d.y + d.y1; +} + +function collideRects(a, b) { + return a.x + a.x1 > b[0].x && a.x + a.x0 < b[1].x && a.y + a.y1 > b[0].y && a.y + a.y0 < b[1].y; +} + +function archimedeanSpiral(size) { + var e = size[0] / size[1]; + return function(t) { + return [e * (t *= .1) * Math.cos(t), t * Math.sin(t)]; + }; +} + +function rectangularSpiral(size) { + var dy = 4, + dx = dy * size[0] / size[1], + x = 0, + y = 0; + return function(t) { + var sign = t < 0 ? -1 : 1; + // See triangular numbers: T_n = n * (n + 1) / 2. + switch ((Math.sqrt(1 + 4 * sign * t) - sign) & 3) { + case 0: x += dx; break; + case 1: y += dy; break; + case 2: x -= dx; break; + default: y -= dy; break; + } + return [x, y]; + }; +} + +// TODO reuse arrays? +function zeroArray(n) { + var a = [], + i = -1; + while (++i < n) a[i] = 0; + return a; +} + +function cloudCanvas() { + return document.createElement("canvas"); +} + +function functor(d) { + return typeof d === "function" ? d : function() { return d; }; +} + +var spirals = { + archimedean: archimedeanSpiral, + rectangular: rectangularSpiral +}; + +},{"d3-dispatch":4}],4:[function(require,module,exports){ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define('d3-dispatch', ['exports'], factory) : + factory((global.d3_dispatch = {})); +}(this, function (exports) { 'use strict'; + + function dispatch() { + return new Dispatch(arguments); + } + + function Dispatch(types) { + var i = -1, + n = types.length, + callbacksByType = {}, + callbackByName = {}, + type, + that = this; + + that.on = function(type, callback) { + type = parseType(type); + + // Return the current callback, if any. + if (arguments.length < 2) { + return (callback = callbackByName[type.name]) && callback.value; + } + + // If a type was specified… + if (type.type) { + var callbacks = callbacksByType[type.type], + callback0 = callbackByName[type.name], + i; + + // Remove the current callback, if any, using copy-on-remove. + if (callback0) { + callback0.value = null; + i = callbacks.indexOf(callback0); + callbacksByType[type.type] = callbacks = callbacks.slice(0, i).concat(callbacks.slice(i + 1)); + delete callbackByName[type.name]; + } + + // Add the new callback, if any. + if (callback) { + callback = {value: callback}; + callbackByName[type.name] = callback; + callbacks.push(callback); + } + } + + // Otherwise, if a null callback was specified, remove all callbacks with the given name. + else if (callback == null) { + for (var otherType in callbacksByType) { + if (callback = callbackByName[otherType + type.name]) { + callback.value = null; + var callbacks = callbacksByType[otherType], i = callbacks.indexOf(callback); + callbacksByType[otherType] = callbacks.slice(0, i).concat(callbacks.slice(i + 1)); + delete callbackByName[callback.name]; + } + } + } + + return that; + }; + + while (++i < n) { + type = types[i] + ""; + if (!type || (type in that)) throw new Error("illegal or duplicate type: " + type); + callbacksByType[type] = []; + that[type] = applier(type); + } + + function parseType(type) { + var i = (type += "").indexOf("."), name = type; + if (i >= 0) type = type.slice(0, i); else name += "."; + if (type && !callbacksByType.hasOwnProperty(type)) throw new Error("unknown type: " + type); + return {type: type, name: name}; + } + + function applier(type) { + return function() { + var callbacks = callbacksByType[type], // Defensive reference; copy-on-remove. + callback, + callbackValue, + i = -1, + n = callbacks.length; + + while (++i < n) { + if (callbackValue = (callback = callbacks[i]).value) { + callbackValue.apply(this, arguments); + } + } + + return that; + }; + } + } + + dispatch.prototype = Dispatch.prototype; + + var version = "0.2.4"; + + exports.version = version; + exports.dispatch = dispatch; + +})); +},{}],5:[function(require,module,exports){ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define('d3-dsv', ['exports'], factory) : + factory((global.d3_dsv = {})); +}(this, function (exports) { 'use strict'; + + function dsv(delimiter) { + return new Dsv(delimiter); + } + + function Dsv(delimiter) { + var reFormat = new RegExp("[\"" + delimiter + "\n]"), + delimiterCode = delimiter.charCodeAt(0); + + this.parse = function(text, f) { + var o; + return this.parseRows(text, function(row, i) { + if (o) return o(row, i - 1); + var a = new Function("d", "return {" + row.map(function(name, i) { + return JSON.stringify(name) + ": d[" + i + "]"; + }).join(",") + "}"); + o = f ? function(row, i) { return f(a(row), i); } : a; + }); + }; + + this.parseRows = function(text, f) { + var EOL = {}, // sentinel value for end-of-line + EOF = {}, // sentinel value for end-of-file + rows = [], // output rows + N = text.length, + I = 0, // current character index + n = 0, // the current line number + t, // the current token + eol; // is the current token followed by EOL? + + function token() { + if (I >= N) return EOF; // special case: end of file + if (eol) return eol = false, EOL; // special case: end of line + + // special case: quotes + var j = I; + if (text.charCodeAt(j) === 34) { + var i = j; + while (i++ < N) { + if (text.charCodeAt(i) === 34) { + if (text.charCodeAt(i + 1) !== 34) break; + ++i; + } + } + I = i + 2; + var c = text.charCodeAt(i + 1); + if (c === 13) { + eol = true; + if (text.charCodeAt(i + 2) === 10) ++I; + } else if (c === 10) { + eol = true; + } + return text.slice(j + 1, i).replace(/""/g, "\""); + } + + // common case: find next delimiter or newline + while (I < N) { + var c = text.charCodeAt(I++), k = 1; + if (c === 10) eol = true; // \n + else if (c === 13) { eol = true; if (text.charCodeAt(I) === 10) ++I, ++k; } // \r|\r\n + else if (c !== delimiterCode) continue; + return text.slice(j, I - k); + } + + // special case: last token before EOF + return text.slice(j); + } + + while ((t = token()) !== EOF) { + var a = []; + while (t !== EOL && t !== EOF) { + a.push(t); + t = token(); + } + if (f && (a = f(a, n++)) == null) continue; + rows.push(a); + } + + return rows; + } + + this.format = function(rows) { + if (Array.isArray(rows[0])) return this.formatRows(rows); // deprecated; use formatRows + var fieldSet = Object.create(null), fields = []; + + // Compute unique fields in order of discovery. + rows.forEach(function(row) { + for (var field in row) { + if (!((field += "") in fieldSet)) { + fields.push(fieldSet[field] = field); + } + } + }); + + return [fields.map(formatValue).join(delimiter)].concat(rows.map(function(row) { + return fields.map(function(field) { + return formatValue(row[field]); + }).join(delimiter); + })).join("\n"); + }; + + this.formatRows = function(rows) { + return rows.map(formatRow).join("\n"); + }; + + function formatRow(row) { + return row.map(formatValue).join(delimiter); + } + + function formatValue(text) { + return reFormat.test(text) ? "\"" + text.replace(/\"/g, "\"\"") + "\"" : text; + } + }; + + dsv.prototype = Dsv.prototype; + + var csv = dsv(","); + var tsv = dsv("\t"); + + var version = "0.1.9"; + + exports.version = version; + exports.dsv = dsv; + exports.csv = csv; + exports.tsv = tsv; + +})); +},{}],6:[function(require,module,exports){ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define('d3-format', ['exports'], factory) : + factory((global.d3_format = {})); +}(this, function (exports) { 'use strict'; + + var zhCn = { + decimal: ".", + thousands: ",", + grouping: [3], + currency: ["¥", ""] + }; + + var svSe = { + decimal: ",", + thousands: "\xa0", + grouping: [3], + currency: ["", "SEK"] + }; + + var ruRu = { + decimal: ",", + thousands: "\xa0", + grouping: [3], + currency: ["", "\xa0руб."] + }; + + var ptBr = { + decimal: ",", + thousands: ".", + grouping: [3], + currency: ["R$", ""] + }; + + var plPl = { + decimal: ",", + thousands: ".", + grouping: [3], + currency: ["", "zł"] + }; + + var nlNl = { + decimal: ",", + thousands: ".", + grouping: [3], + currency: ["€\xa0", ""] + }; + + var mkMk = { + decimal: ",", + thousands: ".", + grouping: [3], + currency: ["", "\xa0ден."] + }; + + var koKr = { + decimal: ".", + thousands: ",", + grouping: [3], + currency: ["₩", ""] + }; + + var jaJp = { + decimal: ".", + thousands: ",", + grouping: [3], + currency: ["", "円"] + }; + + var itIt = { + decimal: ",", + thousands: ".", + grouping: [3], + currency: ["€", ""] + }; + + var huHu = { + decimal: ",", + thousands: "\xa0", + grouping: [3], + currency: ["", "\xa0Ft"] + }; + + var heIl = { + decimal: ".", + thousands: ",", + grouping: [3], + currency: ["₪", ""] + }; + + var frFr = { + decimal: ",", + thousands: ".", + grouping: [3], + currency: ["", "\xa0€"] + }; + + var frCa = { + decimal: ",", + thousands: "\xa0", + grouping: [3], + currency: ["", "$"] + }; + + var fiFi = { + decimal: ",", + thousands: "\xa0", + grouping: [3], + currency: ["", "\xa0€"] + }; + + var esEs = { + decimal: ",", + thousands: ".", + grouping: [3], + currency: ["", "\xa0€"] + }; + + var enUs = { + decimal: ".", + thousands: ",", + grouping: [3], + currency: ["$", ""] + }; + + var enGb = { + decimal: ".", + thousands: ",", + grouping: [3], + currency: ["£", ""] + }; + + var enCa = { + decimal: ".", + thousands: ",", + grouping: [3], + currency: ["$", ""] + }; + + var deDe = { + decimal: ",", + thousands: ".", + grouping: [3], + currency: ["", "\xa0€"] + }; + + var deCh = { + decimal: ",", + thousands: "'", + grouping: [3], + currency: ["", "\xa0CHF"] + }; + + var caEs = { + decimal: ",", + thousands: ".", + grouping: [3], + currency: ["", "\xa0€"] + }; + + // Computes the decimal coefficient and exponent of the specified number x with + // significant digits p, where x is positive and p is in [1, 21] or undefined. + // For example, formatDecimal(1.23) returns ["123", 0]. + function formatDecimal(x, p) { + if ((i = (x = p ? x.toExponential(p - 1) : x.toExponential()).indexOf("e")) < 0) return null; // NaN, ±Infinity + var i, coefficient = x.slice(0, i); + + // The string returned by toExponential either has the form \d\.\d+e[-+]\d+ + // (e.g., 1.2e+3) or the form \de[-+]\d+ (e.g., 1e+3). + return [ + coefficient.length > 1 ? coefficient[0] + coefficient.slice(2) : coefficient, + +x.slice(i + 1) + ]; + }; + + function exponent(x) { + return x = formatDecimal(Math.abs(x)), x ? x[1] : NaN; + }; + + function formatGroup(grouping, thousands) { + return function(value, width) { + var i = value.length, + t = [], + j = 0, + g = grouping[0], + length = 0; + + while (i > 0 && g > 0) { + if (length + g + 1 > width) g = Math.max(1, width - length); + t.push(value.substring(i -= g, i + g)); + if ((length += g + 1) > width) break; + g = grouping[j = (j + 1) % grouping.length]; + } + + return t.reverse().join(thousands); + }; + }; + + var prefixExponent; + + function formatPrefixAuto(x, p) { + var d = formatDecimal(x, p); + if (!d) return x + ""; + var coefficient = d[0], + exponent = d[1], + i = exponent - (prefixExponent = Math.max(-8, Math.min(8, Math.floor(exponent / 3))) * 3) + 1, + n = coefficient.length; + return i === n ? coefficient + : i > n ? coefficient + new Array(i - n + 1).join("0") + : i > 0 ? coefficient.slice(0, i) + "." + coefficient.slice(i) + : "0." + new Array(1 - i).join("0") + formatDecimal(x, Math.max(0, p + i - 1))[0]; // less than 1y! + }; + + function formatRounded(x, p) { + var d = formatDecimal(x, p); + if (!d) return x + ""; + var coefficient = d[0], + exponent = d[1]; + return exponent < 0 ? "0." + new Array(-exponent).join("0") + coefficient + : coefficient.length > exponent + 1 ? coefficient.slice(0, exponent + 1) + "." + coefficient.slice(exponent + 1) + : coefficient + new Array(exponent - coefficient.length + 2).join("0"); + }; + + function formatDefault(x, p) { + x = x.toPrecision(p); + + out: for (var n = x.length, i = 1, i0 = -1, i1; i < n; ++i) { + switch (x[i]) { + case ".": i0 = i1 = i; break; + case "0": if (i0 === 0) i0 = i; i1 = i; break; + case "e": break out; + default: if (i0 > 0) i0 = 0; break; + } + } + + return i0 > 0 ? x.slice(0, i0) + x.slice(i1 + 1) : x; + }; + + var formatTypes = { + "": formatDefault, + "%": function(x, p) { return (x * 100).toFixed(p); }, + "b": function(x) { return Math.round(x).toString(2); }, + "c": function(x) { return x + ""; }, + "d": function(x) { return Math.round(x).toString(10); }, + "e": function(x, p) { return x.toExponential(p); }, + "f": function(x, p) { return x.toFixed(p); }, + "g": function(x, p) { return x.toPrecision(p); }, + "o": function(x) { return Math.round(x).toString(8); }, + "p": function(x, p) { return formatRounded(x * 100, p); }, + "r": formatRounded, + "s": formatPrefixAuto, + "X": function(x) { return Math.round(x).toString(16).toUpperCase(); }, + "x": function(x) { return Math.round(x).toString(16); } + }; + + // [[fill]align][sign][symbol][0][width][,][.precision][type] + var re = /^(?:(.)?([<>=^]))?([+\-\( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?([a-z%])?$/i; + + function formatSpecifier(specifier) { + return new FormatSpecifier(specifier); + }; + + function FormatSpecifier(specifier) { + if (!(match = re.exec(specifier))) throw new Error("invalid format: " + specifier); + + var match, + fill = match[1] || " ", + align = match[2] || ">", + sign = match[3] || "-", + symbol = match[4] || "", + zero = !!match[5], + width = match[6] && +match[6], + comma = !!match[7], + precision = match[8] && +match[8].slice(1), + type = match[9] || ""; + + // The "n" type is an alias for ",g". + if (type === "n") comma = true, type = "g"; + + // Map invalid types to the default format. + else if (!formatTypes[type]) type = ""; + + // If zero fill is specified, padding goes after sign and before digits. + if (zero || (fill === "0" && align === "=")) zero = true, fill = "0", align = "="; + + this.fill = fill; + this.align = align; + this.sign = sign; + this.symbol = symbol; + this.zero = zero; + this.width = width; + this.comma = comma; + this.precision = precision; + this.type = type; + } + + FormatSpecifier.prototype.toString = function() { + return this.fill + + this.align + + this.sign + + this.symbol + + (this.zero ? "0" : "") + + (this.width == null ? "" : Math.max(1, this.width | 0)) + + (this.comma ? "," : "") + + (this.precision == null ? "" : "." + Math.max(0, this.precision | 0)) + + this.type; + }; + + var prefixes = ["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"]; + + function identity(x) { + return x; + } + + function locale(locale) { + var group = locale.grouping && locale.thousands ? formatGroup(locale.grouping, locale.thousands) : identity, + currency = locale.currency, + decimal = locale.decimal; + + function format(specifier) { + specifier = formatSpecifier(specifier); + + var fill = specifier.fill, + align = specifier.align, + sign = specifier.sign, + symbol = specifier.symbol, + zero = specifier.zero, + width = specifier.width, + comma = specifier.comma, + precision = specifier.precision, + type = specifier.type; + + // Compute the prefix and suffix. + // For SI-prefix, the suffix is lazily computed. + var prefix = symbol === "$" ? currency[0] : symbol === "#" && /[boxX]/.test(type) ? "0" + type.toLowerCase() : "", + suffix = symbol === "$" ? currency[1] : /[%p]/.test(type) ? "%" : ""; + + // What format function should we use? + // Is this an integer type? + // Can this type generate exponential notation? + var formatType = formatTypes[type], + maybeSuffix = !type || /[defgprs%]/.test(type); + + // Set the default precision if not specified, + // or clamp the specified precision to the supported range. + // For significant precision, it must be in [1, 21]. + // For fixed precision, it must be in [0, 20]. + precision = precision == null ? (type ? 6 : 12) + : /[gprs]/.test(type) ? Math.max(1, Math.min(21, precision)) + : Math.max(0, Math.min(20, precision)); + + return function(value) { + var valuePrefix = prefix, + valueSuffix = suffix; + + if (type === "c") { + valueSuffix = formatType(value) + valueSuffix; + value = ""; + } else { + value = +value; + + // Convert negative to positive, and compute the prefix. + // Note that -0 is not less than 0, but 1 / -0 is! + var valueNegative = (value < 0 || 1 / value < 0) && (value *= -1, true); + + // Perform the initial formatting. + value = formatType(value, precision); + + // If the original value was negative, it may be rounded to zero during + // formatting; treat this as (positive) zero. + if (valueNegative) { + var i = -1, n = value.length, c; + valueNegative = false; + while (++i < n) { + if (c = value.charCodeAt(i), (48 < c && c < 58) + || (type === "x" && 96 < c && c < 103) + || (type === "X" && 64 < c && c < 71)) { + valueNegative = true; + break; + } + } + } + + // Compute the prefix and suffix. + valuePrefix = (valueNegative ? (sign === "(" ? sign : "-") : sign === "-" || sign === "(" ? "" : sign) + valuePrefix; + valueSuffix = valueSuffix + (type === "s" ? prefixes[8 + prefixExponent / 3] : "") + (valueNegative && sign === "(" ? ")" : ""); + + // Break the formatted value into the integer “value” part that can be + // grouped, and fractional or exponential “suffix” part that is not. + if (maybeSuffix) { + var i = -1, n = value.length, c; + while (++i < n) { + if (c = value.charCodeAt(i), 48 > c || c > 57) { + valueSuffix = (c === 46 ? decimal + value.slice(i + 1) : value.slice(i)) + valueSuffix; + value = value.slice(0, i); + break; + } + } + } + } + + // If the fill character is not "0", grouping is applied before padding. + if (comma && !zero) value = group(value, Infinity); + + // Compute the padding. + var length = valuePrefix.length + value.length + valueSuffix.length, + padding = length < width ? new Array(width - length + 1).join(fill) : ""; + + // If the fill character is "0", grouping is applied after padding. + if (comma && zero) value = group(padding + value, padding.length ? width - valueSuffix.length : Infinity), padding = ""; + + // Reconstruct the final output based on the desired alignment. + switch (align) { + case "<": return valuePrefix + value + valueSuffix + padding; + case "=": return valuePrefix + padding + value + valueSuffix; + case "^": return padding.slice(0, length = padding.length >> 1) + valuePrefix + value + valueSuffix + padding.slice(length); + } + return padding + valuePrefix + value + valueSuffix; + }; + } + + function formatPrefix(specifier, value) { + var f = format((specifier = formatSpecifier(specifier), specifier.type = "f", specifier)), + e = Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3, + k = Math.pow(10, -e), + prefix = prefixes[8 + e / 3]; + return function(value) { + return f(k * value) + prefix; + }; + } + + return { + format: format, + formatPrefix: formatPrefix + }; + }; + + function precisionFixed(step) { + return Math.max(0, -exponent(Math.abs(step))); + }; + + function precisionPrefix(step, value) { + return Math.max(0, Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3 - exponent(Math.abs(step))); + }; + + function precisionRound(step, max) { + return Math.max(0, exponent(Math.abs(max)) - exponent(Math.abs(step))) + 1; + }; + + var localeDefinitions = { + "ca-ES": caEs, + "de-CH": deCh, + "de-DE": deDe, + "en-CA": enCa, + "en-GB": enGb, + "en-US": enUs, + "es-ES": esEs, + "fi-FI": fiFi, + "fr-CA": frCa, + "fr-FR": frFr, + "he-IL": heIl, + "hu-HU": huHu, + "it-IT": itIt, + "ja-JP": jaJp, + "ko-KR": koKr, + "mk-MK": mkMk, + "nl-NL": nlNl, + "pl-PL": plPl, + "pt-BR": ptBr, + "ru-RU": ruRu, + "sv-SE": svSe, + "zh-CN": zhCn + }; + + var defaultLocale = locale(enUs); + var format = defaultLocale.format; + var formatPrefix = defaultLocale.formatPrefix; + + function localeFormat(definition) { + if (typeof definition === "string") { + if (!localeDefinitions.hasOwnProperty(definition)) return null; + definition = localeDefinitions[definition]; + } + return locale(definition); + }; + + var version = "0.3.6"; + + exports.version = version; + exports.format = format; + exports.formatPrefix = formatPrefix; + exports.localeFormat = localeFormat; + exports.formatSpecifier = formatSpecifier; + exports.precisionFixed = precisionFixed; + exports.precisionPrefix = precisionPrefix; + exports.precisionRound = precisionRound; + +})); +},{}],7:[function(require,module,exports){ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-time')) : + typeof define === 'function' && define.amd ? define('d3-time-format', ['exports', 'd3-time'], factory) : + factory((global.d3_time_format = {}),global.d3_time); +}(this, function (exports,d3Time) { 'use strict'; + + var zhCn = { + dateTime: "%a %b %e %X %Y", + date: "%Y/%-m/%-d", + time: "%H:%M:%S", + periods: ["上午", "下午"], + days: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"], + shortDays: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"], + months: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"], + shortMonths: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"] + }; + + var svSe = { + dateTime: "%A den %d %B %Y %X", + date: "%Y-%m-%d", + time: "%H:%M:%S", + periods: ["fm", "em"], + days: ["Söndag", "Måndag", "Tisdag", "Onsdag", "Torsdag", "Fredag", "Lördag"], + shortDays: ["Sön", "Mån", "Tis", "Ons", "Tor", "Fre", "Lör"], + months: ["Januari", "Februari", "Mars", "April", "Maj", "Juni", "Juli", "Augusti", "September", "Oktober", "November", "December"], + shortMonths: ["Jan", "Feb", "Mar", "Apr", "Maj", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dec"] + }; + + var ruRu = { + dateTime: "%A, %e %B %Y г. %X", + date: "%d.%m.%Y", + time: "%H:%M:%S", + periods: ["AM", "PM"], + days: ["воскресенье", "понедельник", "вторник", "среда", "четверг", "пятница", "суббота"], + shortDays: ["вс", "пн", "вт", "ср", "чт", "пт", "сб"], + months: ["января", "февраля", "марта", "апреля", "мая", "июня", "июля", "августа", "сентября", "октября", "ноября", "декабря"], + shortMonths: ["янв", "фев", "мар", "апр", "май", "июн", "июл", "авг", "сен", "окт", "ноя", "дек"] + }; + + var ptBr = { + dateTime: "%A, %e de %B de %Y. %X", + date: "%d/%m/%Y", + time: "%H:%M:%S", + periods: ["AM", "PM"], + days: ["Domingo", "Segunda", "Terça", "Quarta", "Quinta", "Sexta", "Sábado"], + shortDays: ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"], + months: ["Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"], + shortMonths: ["Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"] + }; + + var plPl = { + dateTime: "%A, %e %B %Y, %X", + date: "%d/%m/%Y", + time: "%H:%M:%S", + periods: ["AM", "PM"], // unused + days: ["Niedziela", "Poniedziałek", "Wtorek", "Środa", "Czwartek", "Piątek", "Sobota"], + shortDays: ["Niedz.", "Pon.", "Wt.", "Śr.", "Czw.", "Pt.", "Sob."], + months: ["Styczeń", "Luty", "Marzec", "Kwiecień", "Maj", "Czerwiec", "Lipiec", "Sierpień", "Wrzesień", "Październik", "Listopad", "Grudzień"], + shortMonths: ["Stycz.", "Luty", "Marz.", "Kwie.", "Maj", "Czerw.", "Lipc.", "Sierp.", "Wrz.", "Paźdz.", "Listop.", "Grudz."]/* In Polish language abbraviated months are not commonly used so there is a dispute about the proper abbraviations. */ + }; + + var nlNl = { + dateTime: "%a %e %B %Y %T", + date: "%d-%m-%Y", + time: "%H:%M:%S", + periods: ["AM", "PM"], // unused + days: ["zondag", "maandag", "dinsdag", "woensdag", "donderdag", "vrijdag", "zaterdag"], + shortDays: ["zo", "ma", "di", "wo", "do", "vr", "za"], + months: ["januari", "februari", "maart", "april", "mei", "juni", "juli", "augustus", "september", "oktober", "november", "december"], + shortMonths: ["jan", "feb", "mrt", "apr", "mei", "jun", "jul", "aug", "sep", "okt", "nov", "dec"] + }; + + var mkMk = { + dateTime: "%A, %e %B %Y г. %X", + date: "%d.%m.%Y", + time: "%H:%M:%S", + periods: ["AM", "PM"], + days: ["недела", "понеделник", "вторник", "среда", "четврток", "петок", "сабота"], + shortDays: ["нед", "пон", "вто", "сре", "чет", "пет", "саб"], + months: ["јануари", "февруари", "март", "април", "мај", "јуни", "јули", "август", "септември", "октомври", "ноември", "декември"], + shortMonths: ["јан", "фев", "мар", "апр", "мај", "јун", "јул", "авг", "сеп", "окт", "ное", "дек"] + }; + + var koKr = { + dateTime: "%Y/%m/%d %a %X", + date: "%Y/%m/%d", + time: "%H:%M:%S", + periods: ["오전", "오후"], + days: ["일요일", "월요일", "화요일", "수요일", "목요일", "금요일", "토요일"], + shortDays: ["일", "월", "화", "수", "목", "금", "토"], + months: ["1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월"], + shortMonths: ["1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월"] + }; + + var jaJp = { + dateTime: "%Y %b %e %a %X", + date: "%Y/%m/%d", + time: "%H:%M:%S", + periods: ["AM", "PM"], + days: ["日曜日", "月曜日", "火曜日", "水曜日", "木曜日", "金曜日", "土曜日"], + shortDays: ["日", "月", "火", "水", "木", "金", "土"], + months: ["睦月", "如月", "弥生", "卯月", "皐月", "水無月", "文月", "葉月", "長月", "神無月", "霜月", "師走"], + shortMonths: ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"] + }; + + var itIt = { + dateTime: "%A %e %B %Y, %X", + date: "%d/%m/%Y", + time: "%H:%M:%S", + periods: ["AM", "PM"], // unused + days: ["Domenica", "Lunedì", "Martedì", "Mercoledì", "Giovedì", "Venerdì", "Sabato"], + shortDays: ["Dom", "Lun", "Mar", "Mer", "Gio", "Ven", "Sab"], + months: ["Gennaio", "Febbraio", "Marzo", "Aprile", "Maggio", "Giugno", "Luglio", "Agosto", "Settembre", "Ottobre", "Novembre", "Dicembre"], + shortMonths: ["Gen", "Feb", "Mar", "Apr", "Mag", "Giu", "Lug", "Ago", "Set", "Ott", "Nov", "Dic"] + }; + + var huHu = { + dateTime: "%Y. %B %-e., %A %X", + date: "%Y. %m. %d.", + time: "%H:%M:%S", + periods: ["de.", "du."], // unused + days: ["vasárnap", "hétfő", "kedd", "szerda", "csütörtök", "péntek", "szombat"], + shortDays: ["V", "H", "K", "Sze", "Cs", "P", "Szo"], + months: ["január", "február", "március", "április", "május", "június", "július", "augusztus", "szeptember", "október", "november", "december"], + shortMonths: ["jan.", "feb.", "már.", "ápr.", "máj.", "jún.", "júl.", "aug.", "szept.", "okt.", "nov.", "dec."] + }; + + var heIl = { + dateTime: "%A, %e ב%B %Y %X", + date: "%d.%m.%Y", + time: "%H:%M:%S", + periods: ["AM", "PM"], + days: ["ראשון", "שני", "שלישי", "רביעי", "חמישי", "שישי", "שבת"], + shortDays: ["א׳", "ב׳", "ג׳", "ד׳", "ה׳", "ו׳", "ש׳"], + months: ["ינואר", "פברואר", "מרץ", "אפריל", "מאי", "יוני", "יולי", "אוגוסט", "ספטמבר", "אוקטובר", "נובמבר", "דצמבר"], + shortMonths: ["ינו׳", "פבר׳", "מרץ", "אפר׳", "מאי", "יוני", "יולי", "אוג׳", "ספט׳", "אוק׳", "נוב׳", "דצמ׳"] + }; + + var frFr = { + dateTime: "%A, le %e %B %Y, %X", + date: "%d/%m/%Y", + time: "%H:%M:%S", + periods: ["AM", "PM"], // unused + days: ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"], + shortDays: ["dim.", "lun.", "mar.", "mer.", "jeu.", "ven.", "sam."], + months: ["janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre"], + shortMonths: ["janv.", "févr.", "mars", "avr.", "mai", "juin", "juil.", "août", "sept.", "oct.", "nov.", "déc."] + }; + + var frCa = { + dateTime: "%a %e %b %Y %X", + date: "%Y-%m-%d", + time: "%H:%M:%S", + periods: ["", ""], + days: ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"], + shortDays: ["dim", "lun", "mar", "mer", "jeu", "ven", "sam"], + months: ["janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre"], + shortMonths: ["jan", "fév", "mar", "avr", "mai", "jui", "jul", "aoû", "sep", "oct", "nov", "déc"] + }; + + var fiFi = { + dateTime: "%A, %-d. %Bta %Y klo %X", + date: "%-d.%-m.%Y", + time: "%H:%M:%S", + periods: ["a.m.", "p.m."], + days: ["sunnuntai", "maanantai", "tiistai", "keskiviikko", "torstai", "perjantai", "lauantai"], + shortDays: ["Su", "Ma", "Ti", "Ke", "To", "Pe", "La"], + months: ["tammikuu", "helmikuu", "maaliskuu", "huhtikuu", "toukokuu", "kesäkuu", "heinäkuu", "elokuu", "syyskuu", "lokakuu", "marraskuu", "joulukuu"], + shortMonths: ["Tammi", "Helmi", "Maalis", "Huhti", "Touko", "Kesä", "Heinä", "Elo", "Syys", "Loka", "Marras", "Joulu"] + }; + + var esEs = { + dateTime: "%A, %e de %B de %Y, %X", + date: "%d/%m/%Y", + time: "%H:%M:%S", + periods: ["AM", "PM"], + days: ["domingo", "lunes", "martes", "miércoles", "jueves", "viernes", "sábado"], + shortDays: ["dom", "lun", "mar", "mié", "jue", "vie", "sáb"], + months: ["enero", "febrero", "marzo", "abril", "mayo", "junio", "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre"], + shortMonths: ["ene", "feb", "mar", "abr", "may", "jun", "jul", "ago", "sep", "oct", "nov", "dic"] + }; + + var locale$1 = { + dateTime: "%a %b %e %X %Y", + date: "%m/%d/%Y", + time: "%H:%M:%S", + periods: ["AM", "PM"], + days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], + shortDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], + months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], + shortMonths: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + }; + + var enGb = { + dateTime: "%a %e %b %X %Y", + date: "%d/%m/%Y", + time: "%H:%M:%S", + periods: ["AM", "PM"], + days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], + shortDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], + months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], + shortMonths: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + }; + + var enCa = { + dateTime: "%a %b %e %X %Y", + date: "%Y-%m-%d", + time: "%H:%M:%S", + periods: ["AM", "PM"], + days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], + shortDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], + months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], + shortMonths: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + }; + + var deDe = { + dateTime: "%A, der %e. %B %Y, %X", + date: "%d.%m.%Y", + time: "%H:%M:%S", + periods: ["AM", "PM"], // unused + days: ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"], + shortDays: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"], + months: ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"], + shortMonths: ["Jan", "Feb", "Mrz", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"] + }; + + var deCh = { + dateTime: "%A, der %e. %B %Y, %X", + date: "%d.%m.%Y", + time: "%H:%M:%S", + periods: ["AM", "PM"], // unused + days: ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"], + shortDays: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"], + months: ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"], + shortMonths: ["Jan", "Feb", "Mrz", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"] + }; + + var caEs = { + dateTime: "%A, %e de %B de %Y, %X", + date: "%d/%m/%Y", + time: "%H:%M:%S", + periods: ["AM", "PM"], + days: ["diumenge", "dilluns", "dimarts", "dimecres", "dijous", "divendres", "dissabte"], + shortDays: ["dg.", "dl.", "dt.", "dc.", "dj.", "dv.", "ds."], + months: ["gener", "febrer", "març", "abril", "maig", "juny", "juliol", "agost", "setembre", "octubre", "novembre", "desembre"], + shortMonths: ["gen.", "febr.", "març", "abr.", "maig", "juny", "jul.", "ag.", "set.", "oct.", "nov.", "des."] + }; + + function localDate(d) { + if (0 <= d.y && d.y < 100) { + var date = new Date(-1, d.m, d.d, d.H, d.M, d.S, d.L); + date.setFullYear(d.y); + return date; + } + return new Date(d.y, d.m, d.d, d.H, d.M, d.S, d.L); + } + + function utcDate(d) { + if (0 <= d.y && d.y < 100) { + var date = new Date(Date.UTC(-1, d.m, d.d, d.H, d.M, d.S, d.L)); + date.setUTCFullYear(d.y); + return date; + } + return new Date(Date.UTC(d.y, d.m, d.d, d.H, d.M, d.S, d.L)); + } + + function newYear(y) { + return {y: y, m: 0, d: 1, H: 0, M: 0, S: 0, L: 0}; + } + + function locale(locale) { + var locale_dateTime = locale.dateTime, + locale_date = locale.date, + locale_time = locale.time, + locale_periods = locale.periods, + locale_weekdays = locale.days, + locale_shortWeekdays = locale.shortDays, + locale_months = locale.months, + locale_shortMonths = locale.shortMonths; + + var periodLookup = formatLookup(locale_periods), + weekdayRe = formatRe(locale_weekdays), + weekdayLookup = formatLookup(locale_weekdays), + shortWeekdayRe = formatRe(locale_shortWeekdays), + shortWeekdayLookup = formatLookup(locale_shortWeekdays), + monthRe = formatRe(locale_months), + monthLookup = formatLookup(locale_months), + shortMonthRe = formatRe(locale_shortMonths), + shortMonthLookup = formatLookup(locale_shortMonths); + + var formats = { + "a": formatShortWeekday, + "A": formatWeekday, + "b": formatShortMonth, + "B": formatMonth, + "c": null, + "d": formatDayOfMonth, + "e": formatDayOfMonth, + "H": formatHour24, + "I": formatHour12, + "j": formatDayOfYear, + "L": formatMilliseconds, + "m": formatMonthNumber, + "M": formatMinutes, + "p": formatPeriod, + "S": formatSeconds, + "U": formatWeekNumberSunday, + "w": formatWeekdayNumber, + "W": formatWeekNumberMonday, + "x": null, + "X": null, + "y": formatYear, + "Y": formatFullYear, + "Z": formatZone, + "%": formatLiteralPercent + }; + + var utcFormats = { + "a": formatUTCShortWeekday, + "A": formatUTCWeekday, + "b": formatUTCShortMonth, + "B": formatUTCMonth, + "c": null, + "d": formatUTCDayOfMonth, + "e": formatUTCDayOfMonth, + "H": formatUTCHour24, + "I": formatUTCHour12, + "j": formatUTCDayOfYear, + "L": formatUTCMilliseconds, + "m": formatUTCMonthNumber, + "M": formatUTCMinutes, + "p": formatUTCPeriod, + "S": formatUTCSeconds, + "U": formatUTCWeekNumberSunday, + "w": formatUTCWeekdayNumber, + "W": formatUTCWeekNumberMonday, + "x": null, + "X": null, + "y": formatUTCYear, + "Y": formatUTCFullYear, + "Z": formatUTCZone, + "%": formatLiteralPercent + }; + + var parses = { + "a": parseShortWeekday, + "A": parseWeekday, + "b": parseShortMonth, + "B": parseMonth, + "c": parseLocaleDateTime, + "d": parseDayOfMonth, + "e": parseDayOfMonth, + "H": parseHour24, + "I": parseHour24, + "j": parseDayOfYear, + "L": parseMilliseconds, + "m": parseMonthNumber, + "M": parseMinutes, + "p": parsePeriod, + "S": parseSeconds, + "U": parseWeekNumberSunday, + "w": parseWeekdayNumber, + "W": parseWeekNumberMonday, + "x": parseLocaleDate, + "X": parseLocaleTime, + "y": parseYear, + "Y": parseFullYear, + "Z": parseZone, + "%": parseLiteralPercent + }; + + // These recursive directive definitions must be deferred. + formats.x = newFormat(locale_date, formats); + formats.X = newFormat(locale_time, formats); + formats.c = newFormat(locale_dateTime, formats); + utcFormats.x = newFormat(locale_date, utcFormats); + utcFormats.X = newFormat(locale_time, utcFormats); + utcFormats.c = newFormat(locale_dateTime, utcFormats); + + function newFormat(specifier, formats) { + return function(date) { + var string = [], + i = -1, + j = 0, + n = specifier.length, + c, + pad, + format; + + while (++i < n) { + if (specifier.charCodeAt(i) === 37) { + string.push(specifier.slice(j, i)); + if ((pad = pads[c = specifier.charAt(++i)]) != null) c = specifier.charAt(++i); + if (format = formats[c]) c = format(date, pad == null ? (c === "e" ? " " : "0") : pad); + string.push(c); + j = i + 1; + } + } + + string.push(specifier.slice(j, i)); + return string.join(""); + }; + } + + function newParse(specifier, newDate) { + return function(string) { + var d = newYear(1900), + i = parseSpecifier(d, specifier, string, 0); + if (i != string.length) return null; + + // The am-pm flag is 0 for AM, and 1 for PM. + if ("p" in d) d.H = d.H % 12 + d.p * 12; + + // If a time zone is specified, all fields are interpreted as UTC and then + // offset according to the specified time zone. + if ("Z" in d) { + if ("w" in d && ("W" in d || "U" in d)) { + var day = utcDate(newYear(d.y)).getUTCDay(); + if ("W" in d) d.U = d.W, d.w = (d.w + 6) % 7, --day; + d.m = 0; + d.d = d.w + d.U * 7 - (day + 6) % 7; + } + d.H += d.Z / 100 | 0; + d.M += d.Z % 100; + return utcDate(d); + } + + // Otherwise, all fields are in local time. + if ("w" in d && ("W" in d || "U" in d)) { + var day = newDate(newYear(d.y)).getDay(); + if ("W" in d) d.U = d.W, d.w = (d.w + 6) % 7, --day; + d.m = 0; + d.d = d.w + d.U * 7 - (day + 6) % 7; + } + return newDate(d); + }; + } + + function parseSpecifier(d, specifier, string, j) { + var i = 0, + n = specifier.length, + m = string.length, + c, + parse; + + while (i < n) { + if (j >= m) return -1; + c = specifier.charCodeAt(i++); + if (c === 37) { + c = specifier.charAt(i++); + parse = parses[c in pads ? specifier.charAt(i++) : c]; + if (!parse || ((j = parse(d, string, j)) < 0)) return -1; + } else if (c != string.charCodeAt(j++)) { + return -1; + } + } + + return j; + } + + function parseShortWeekday(d, string, i) { + var n = shortWeekdayRe.exec(string.slice(i)); + return n ? (d.w = shortWeekdayLookup[n[0].toLowerCase()], i + n[0].length) : -1; + } + + function parseWeekday(d, string, i) { + var n = weekdayRe.exec(string.slice(i)); + return n ? (d.w = weekdayLookup[n[0].toLowerCase()], i + n[0].length) : -1; + } + + function parseShortMonth(d, string, i) { + var n = shortMonthRe.exec(string.slice(i)); + return n ? (d.m = shortMonthLookup[n[0].toLowerCase()], i + n[0].length) : -1; + } + + function parseMonth(d, string, i) { + var n = monthRe.exec(string.slice(i)); + return n ? (d.m = monthLookup[n[0].toLowerCase()], i + n[0].length) : -1; + } + + function parseLocaleDateTime(d, string, i) { + return parseSpecifier(d, locale_dateTime, string, i); + } + + function parseLocaleDate(d, string, i) { + return parseSpecifier(d, locale_date, string, i); + } + + function parseLocaleTime(d, string, i) { + return parseSpecifier(d, locale_time, string, i); + } + + function parsePeriod(d, string, i) { + var n = periodLookup[string.slice(i, i += 2).toLowerCase()]; + return n == null ? -1 : (d.p = n, i); + } + + function formatShortWeekday(d) { + return locale_shortWeekdays[d.getDay()]; + } + + function formatWeekday(d) { + return locale_weekdays[d.getDay()]; + } + + function formatShortMonth(d) { + return locale_shortMonths[d.getMonth()]; + } + + function formatMonth(d) { + return locale_months[d.getMonth()]; + } + + function formatPeriod(d) { + return locale_periods[+(d.getHours() >= 12)]; + } + + function formatUTCShortWeekday(d) { + return locale_shortWeekdays[d.getUTCDay()]; + } + + function formatUTCWeekday(d) { + return locale_weekdays[d.getUTCDay()]; + } + + function formatUTCShortMonth(d) { + return locale_shortMonths[d.getUTCMonth()]; + } + + function formatUTCMonth(d) { + return locale_months[d.getUTCMonth()]; + } + + function formatUTCPeriod(d) { + return locale_periods[+(d.getUTCHours() >= 12)]; + } + + return { + format: function(specifier) { + var f = newFormat(specifier += "", formats); + f.parse = newParse(specifier, localDate); + f.toString = function() { return specifier; }; + return f; + }, + utcFormat: function(specifier) { + var f = newFormat(specifier += "", utcFormats); + f.parse = newParse(specifier, utcDate); + f.toString = function() { return specifier; }; + return f; + } + }; + }; + + var pads = {"-": "", "_": " ", "0": "0"}; + var numberRe = /^\s*\d+/; + var percentRe = /^%/; + var requoteRe = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g; + function pad(value, fill, width) { + var sign = value < 0 ? "-" : "", + string = (sign ? -value : value) + "", + length = string.length; + return sign + (length < width ? new Array(width - length + 1).join(fill) + string : string); + } + + function requote(s) { + return s.replace(requoteRe, "\\$&"); + } + + function formatRe(names) { + return new RegExp("^(?:" + names.map(requote).join("|") + ")", "i"); + } + + function formatLookup(names) { + var map = {}, i = -1, n = names.length; + while (++i < n) map[names[i].toLowerCase()] = i; + return map; + } + + function parseWeekdayNumber(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 1)); + return n ? (d.w = +n[0], i + n[0].length) : -1; + } + + function parseWeekNumberSunday(d, string, i) { + var n = numberRe.exec(string.slice(i)); + return n ? (d.U = +n[0], i + n[0].length) : -1; + } + + function parseWeekNumberMonday(d, string, i) { + var n = numberRe.exec(string.slice(i)); + return n ? (d.W = +n[0], i + n[0].length) : -1; + } + + function parseFullYear(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 4)); + return n ? (d.y = +n[0], i + n[0].length) : -1; + } + + function parseYear(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.y = +n[0] + (+n[0] > 68 ? 1900 : 2000), i + n[0].length) : -1; + } + + function parseZone(d, string, i) { + var n = /^(Z)|([+-]\d\d)(?:\:?(\d\d))?/.exec(string.slice(i, i + 6)); + if (n) { + d.Z = n[1] ? 0 // 'Z' for UTC + : n[3] ? -(n[2] + n[3]) // sign differs from getTimezoneOffset! + : -n[2] * 100; + return i + n[0].length; + } + return -1; + } + + function parseMonthNumber(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.m = n[0] - 1, i + n[0].length) : -1; + } + + function parseDayOfMonth(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.d = +n[0], i + n[0].length) : -1; + } + + function parseDayOfYear(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 3)); + return n ? (d.m = 0, d.d = +n[0], i + n[0].length) : -1; + } + + function parseHour24(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.H = +n[0], i + n[0].length) : -1; + } + + function parseMinutes(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.M = +n[0], i + n[0].length) : -1; + } + + function parseSeconds(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.S = +n[0], i + n[0].length) : -1; + } + + function parseMilliseconds(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 3)); + return n ? (d.L = +n[0], i + n[0].length) : -1; + } + + function parseLiteralPercent(d, string, i) { + var n = percentRe.exec(string.slice(i, i + 1)); + return n ? i + n[0].length : -1; + } + + function formatDayOfMonth(d, p) { + return pad(d.getDate(), p, 2); + } + + function formatHour24(d, p) { + return pad(d.getHours(), p, 2); + } + + function formatHour12(d, p) { + return pad(d.getHours() % 12 || 12, p, 2); + } + + function formatDayOfYear(d, p) { + return pad(1 + d3Time.day.count(d3Time.year(d), d), p, 3); + } + + function formatMilliseconds(d, p) { + return pad(d.getMilliseconds(), p, 3); + } + + function formatMonthNumber(d, p) { + return pad(d.getMonth() + 1, p, 2); + } + + function formatMinutes(d, p) { + return pad(d.getMinutes(), p, 2); + } + + function formatSeconds(d, p) { + return pad(d.getSeconds(), p, 2); + } + + function formatWeekNumberSunday(d, p) { + return pad(d3Time.sunday.count(d3Time.year(d), d), p, 2); + } + + function formatWeekdayNumber(d) { + return d.getDay(); + } + + function formatWeekNumberMonday(d, p) { + return pad(d3Time.monday.count(d3Time.year(d), d), p, 2); + } + + function formatYear(d, p) { + return pad(d.getFullYear() % 100, p, 2); + } + + function formatFullYear(d, p) { + return pad(d.getFullYear() % 10000, p, 4); + } + + function formatZone(d) { + var z = d.getTimezoneOffset(); + return (z > 0 ? "-" : (z *= -1, "+")) + + pad(z / 60 | 0, "0", 2) + + pad(z % 60, "0", 2); + } + + function formatUTCDayOfMonth(d, p) { + return pad(d.getUTCDate(), p, 2); + } + + function formatUTCHour24(d, p) { + return pad(d.getUTCHours(), p, 2); + } + + function formatUTCHour12(d, p) { + return pad(d.getUTCHours() % 12 || 12, p, 2); + } + + function formatUTCDayOfYear(d, p) { + return pad(1 + d3Time.utcDay.count(d3Time.utcYear(d), d), p, 3); + } + + function formatUTCMilliseconds(d, p) { + return pad(d.getUTCMilliseconds(), p, 3); + } + + function formatUTCMonthNumber(d, p) { + return pad(d.getUTCMonth() + 1, p, 2); + } + + function formatUTCMinutes(d, p) { + return pad(d.getUTCMinutes(), p, 2); + } + + function formatUTCSeconds(d, p) { + return pad(d.getUTCSeconds(), p, 2); + } + + function formatUTCWeekNumberSunday(d, p) { + return pad(d3Time.utcSunday.count(d3Time.utcYear(d), d), p, 2); + } + + function formatUTCWeekdayNumber(d) { + return d.getUTCDay(); + } + + function formatUTCWeekNumberMonday(d, p) { + return pad(d3Time.utcMonday.count(d3Time.utcYear(d), d), p, 2); + } + + function formatUTCYear(d, p) { + return pad(d.getUTCFullYear() % 100, p, 2); + } + + function formatUTCFullYear(d, p) { + return pad(d.getUTCFullYear() % 10000, p, 4); + } + + function formatUTCZone() { + return "+0000"; + } + + function formatLiteralPercent() { + return "%"; + } + + var isoSpecifier = "%Y-%m-%dT%H:%M:%S.%LZ"; + + function formatIsoNative(date) { + return date.toISOString(); + } + + formatIsoNative.parse = function(string) { + var date = new Date(string); + return isNaN(date) ? null : date; + }; + + formatIsoNative.toString = function() { + return isoSpecifier; + }; + + var formatIso = Date.prototype.toISOString && +new Date("2000-01-01T00:00:00.000Z") + ? formatIsoNative + : locale$1.utcFormat(isoSpecifier); + + var localeDefinitions = { + "ca-ES": caEs, + "de-CH": deCh, + "de-DE": deDe, + "en-CA": enCa, + "en-GB": enGb, + "en-US": locale$1, + "es-ES": esEs, + "fi-FI": fiFi, + "fr-CA": frCa, + "fr-FR": frFr, + "he-IL": heIl, + "hu-HU": huHu, + "it-IT": itIt, + "ja-JP": jaJp, + "ko-KR": koKr, + "mk-MK": mkMk, + "nl-NL": nlNl, + "pl-PL": plPl, + "pt-BR": ptBr, + "ru-RU": ruRu, + "sv-SE": svSe, + "zh-CN": zhCn + }; + + var defaultLocale = locale(locale$1); + var format = defaultLocale.format; + var utcFormat = defaultLocale.utcFormat; + + function localeFormat(definition) { + if (typeof definition === "string") { + if (!localeDefinitions.hasOwnProperty(definition)) return null; + definition = localeDefinitions[definition]; + } + return locale(definition); + }; + + var version = "0.1.5"; + + exports.version = version; + exports.format = format; + exports.utcFormat = utcFormat; + exports.localeFormat = localeFormat; + exports.isoFormat = formatIso; + +})); +},{"d3-time":8}],8:[function(require,module,exports){ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define('d3-time', ['exports'], factory) : + factory((global.d3_time = {})); +}(this, function (exports) { 'use strict'; + + var t0 = new Date; + var t1 = new Date; + function newInterval(floori, offseti, count) { + + function interval(date) { + return floori(date = new Date(+date)), date; + } + + interval.floor = interval; + + interval.round = function(date) { + var d0 = new Date(+date), + d1 = new Date(date - 1); + floori(d0), floori(d1), offseti(d1, 1); + return date - d0 < d1 - date ? d0 : d1; + }; + + interval.ceil = function(date) { + return floori(date = new Date(date - 1)), offseti(date, 1), date; + }; + + interval.offset = function(date, step) { + return offseti(date = new Date(+date), step == null ? 1 : Math.floor(step)), date; + }; + + interval.range = function(start, stop, step) { + var range = []; + start = new Date(start - 1); + stop = new Date(+stop); + step = step == null ? 1 : Math.floor(step); + if (!(start < stop) || !(step > 0)) return range; // also handles Invalid Date + offseti(start, 1), floori(start); + if (start < stop) range.push(new Date(+start)); + while (offseti(start, step), floori(start), start < stop) range.push(new Date(+start)); + return range; + }; + + interval.filter = function(test) { + return newInterval(function(date) { + while (floori(date), !test(date)) date.setTime(date - 1); + }, function(date, step) { + while (--step >= 0) while (offseti(date, 1), !test(date)); + }); + }; + + if (count) interval.count = function(start, end) { + t0.setTime(+start), t1.setTime(+end); + floori(t0), floori(t1); + return Math.floor(count(t0, t1)); + }; + + return interval; + }; + + var millisecond = newInterval(function() { + // noop + }, function(date, step) { + date.setTime(+date + step); + }, function(start, end) { + return end - start; + }); + + var second = newInterval(function(date) { + date.setMilliseconds(0); + }, function(date, step) { + date.setTime(+date + step * 1e3); + }, function(start, end) { + return (end - start) / 1e3; + }); + + var minute = newInterval(function(date) { + date.setSeconds(0, 0); + }, function(date, step) { + date.setTime(+date + step * 6e4); + }, function(start, end) { + return (end - start) / 6e4; + }); + + var hour = newInterval(function(date) { + date.setMinutes(0, 0, 0); + }, function(date, step) { + date.setTime(+date + step * 36e5); + }, function(start, end) { + return (end - start) / 36e5; + }); + + var day = newInterval(function(date) { + date.setHours(0, 0, 0, 0); + }, function(date, step) { + date.setDate(date.getDate() + step); + }, function(start, end) { + return (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * 6e4) / 864e5; + }); + + function weekday(i) { + return newInterval(function(date) { + date.setHours(0, 0, 0, 0); + date.setDate(date.getDate() - (date.getDay() + 7 - i) % 7); + }, function(date, step) { + date.setDate(date.getDate() + step * 7); + }, function(start, end) { + return (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * 6e4) / 6048e5; + }); + } + + var sunday = weekday(0); + var monday = weekday(1); + var tuesday = weekday(2); + var wednesday = weekday(3); + var thursday = weekday(4); + var friday = weekday(5); + var saturday = weekday(6); + + var month = newInterval(function(date) { + date.setHours(0, 0, 0, 0); + date.setDate(1); + }, function(date, step) { + date.setMonth(date.getMonth() + step); + }, function(start, end) { + return end.getMonth() - start.getMonth() + (end.getFullYear() - start.getFullYear()) * 12; + }); + + var year = newInterval(function(date) { + date.setHours(0, 0, 0, 0); + date.setMonth(0, 1); + }, function(date, step) { + date.setFullYear(date.getFullYear() + step); + }, function(start, end) { + return end.getFullYear() - start.getFullYear(); + }); + + var utcSecond = newInterval(function(date) { + date.setUTCMilliseconds(0); + }, function(date, step) { + date.setTime(+date + step * 1e3); + }, function(start, end) { + return (end - start) / 1e3; + }); + + var utcMinute = newInterval(function(date) { + date.setUTCSeconds(0, 0); + }, function(date, step) { + date.setTime(+date + step * 6e4); + }, function(start, end) { + return (end - start) / 6e4; + }); + + var utcHour = newInterval(function(date) { + date.setUTCMinutes(0, 0, 0); + }, function(date, step) { + date.setTime(+date + step * 36e5); + }, function(start, end) { + return (end - start) / 36e5; + }); + + var utcDay = newInterval(function(date) { + date.setUTCHours(0, 0, 0, 0); + }, function(date, step) { + date.setUTCDate(date.getUTCDate() + step); + }, function(start, end) { + return (end - start) / 864e5; + }); + + function utcWeekday(i) { + return newInterval(function(date) { + date.setUTCHours(0, 0, 0, 0); + date.setUTCDate(date.getUTCDate() - (date.getUTCDay() + 7 - i) % 7); + }, function(date, step) { + date.setUTCDate(date.getUTCDate() + step * 7); + }, function(start, end) { + return (end - start) / 6048e5; + }); + } + + var utcSunday = utcWeekday(0); + var utcMonday = utcWeekday(1); + var utcTuesday = utcWeekday(2); + var utcWednesday = utcWeekday(3); + var utcThursday = utcWeekday(4); + var utcFriday = utcWeekday(5); + var utcSaturday = utcWeekday(6); + + var utcMonth = newInterval(function(date) { + date.setUTCHours(0, 0, 0, 0); + date.setUTCDate(1); + }, function(date, step) { + date.setUTCMonth(date.getUTCMonth() + step); + }, function(start, end) { + return end.getUTCMonth() - start.getUTCMonth() + (end.getUTCFullYear() - start.getUTCFullYear()) * 12; + }); + + var utcYear = newInterval(function(date) { + date.setUTCHours(0, 0, 0, 0); + date.setUTCMonth(0, 1); + }, function(date, step) { + date.setUTCFullYear(date.getUTCFullYear() + step); + }, function(start, end) { + return end.getUTCFullYear() - start.getUTCFullYear(); + }); + + var milliseconds = millisecond.range; + var seconds = second.range; + var minutes = minute.range; + var hours = hour.range; + var days = day.range; + var sundays = sunday.range; + var mondays = monday.range; + var tuesdays = tuesday.range; + var wednesdays = wednesday.range; + var thursdays = thursday.range; + var fridays = friday.range; + var saturdays = saturday.range; + var weeks = sunday.range; + var months = month.range; + var years = year.range; + + var utcMillisecond = millisecond; + var utcMilliseconds = milliseconds; + var utcSeconds = utcSecond.range; + var utcMinutes = utcMinute.range; + var utcHours = utcHour.range; + var utcDays = utcDay.range; + var utcSundays = utcSunday.range; + var utcMondays = utcMonday.range; + var utcTuesdays = utcTuesday.range; + var utcWednesdays = utcWednesday.range; + var utcThursdays = utcThursday.range; + var utcFridays = utcFriday.range; + var utcSaturdays = utcSaturday.range; + var utcWeeks = utcSunday.range; + var utcMonths = utcMonth.range; + var utcYears = utcYear.range; + + var version = "0.0.7"; + + exports.version = version; + exports.milliseconds = milliseconds; + exports.seconds = seconds; + exports.minutes = minutes; + exports.hours = hours; + exports.days = days; + exports.sundays = sundays; + exports.mondays = mondays; + exports.tuesdays = tuesdays; + exports.wednesdays = wednesdays; + exports.thursdays = thursdays; + exports.fridays = fridays; + exports.saturdays = saturdays; + exports.weeks = weeks; + exports.months = months; + exports.years = years; + exports.utcMillisecond = utcMillisecond; + exports.utcMilliseconds = utcMilliseconds; + exports.utcSeconds = utcSeconds; + exports.utcMinutes = utcMinutes; + exports.utcHours = utcHours; + exports.utcDays = utcDays; + exports.utcSundays = utcSundays; + exports.utcMondays = utcMondays; + exports.utcTuesdays = utcTuesdays; + exports.utcWednesdays = utcWednesdays; + exports.utcThursdays = utcThursdays; + exports.utcFridays = utcFridays; + exports.utcSaturdays = utcSaturdays; + exports.utcWeeks = utcWeeks; + exports.utcMonths = utcMonths; + exports.utcYears = utcYears; + exports.millisecond = millisecond; + exports.second = second; + exports.minute = minute; + exports.hour = hour; + exports.day = day; + exports.sunday = sunday; + exports.monday = monday; + exports.tuesday = tuesday; + exports.wednesday = wednesday; + exports.thursday = thursday; + exports.friday = friday; + exports.saturday = saturday; + exports.week = sunday; + exports.month = month; + exports.year = year; + exports.utcSecond = utcSecond; + exports.utcMinute = utcMinute; + exports.utcHour = utcHour; + exports.utcDay = utcDay; + exports.utcSunday = utcSunday; + exports.utcMonday = utcMonday; + exports.utcTuesday = utcTuesday; + exports.utcWednesday = utcWednesday; + exports.utcThursday = utcThursday; + exports.utcFriday = utcFriday; + exports.utcSaturday = utcSaturday; + exports.utcWeek = utcSunday; + exports.utcMonth = utcMonth; + exports.utcYear = utcYear; + exports.interval = newInterval; + +})); +},{}],9:[function(require,module,exports){ +var util = require('../util'), + Measures = require('./measures'), + Collector = require('./collector'); + +function Aggregator() { + this._cells = {}; + this._aggr = []; + this._stream = false; +} + +var Flags = Aggregator.Flags = { + ADD_CELL: 1, + MOD_CELL: 2 +}; + +var proto = Aggregator.prototype; + +// Parameters + +proto.stream = function(v) { + if (v == null) return this._stream; + this._stream = !!v; + this._aggr = []; + return this; +}; + +// key accessor to use for streaming removes +proto.key = function(key) { + if (key == null) return this._key; + this._key = util.$(key); + return this; +}; + +// Input: array of objects of the form +// {name: string, get: function} +proto.groupby = function(dims) { + this._dims = util.array(dims).map(function(d, i) { + d = util.isString(d) ? {name: d, get: util.$(d)} + : util.isFunction(d) ? {name: util.name(d) || d.name || ('_' + i), get: d} + : (d.name && util.isFunction(d.get)) ? d : null; + if (d == null) throw 'Invalid groupby argument: ' + d; + return d; + }); + return this.clear(); +}; + +// Input: array of objects of the form +// {name: string, ops: [string, ...]} +proto.summarize = function(fields) { + fields = summarize_args(fields); + this._count = true; + var aggr = (this._aggr = []), + m, f, i, j, op, as, get; + + for (i=0; i 0) { + // consolidate collector values + if (cell.collect) { + cell.data.values(); + } + // update tuple properties + for (i=0; i 0) { + m[a[i]] -= 1; + } else { + x[j++] = a[i]; + } + } + } else if (k) { + // has unique key field, so use that + m = util.toMap(r, k); + for (i=0, j=0, n=a.length; i 1 ? this.dev / (this.valid-1) : 0', + req: ['mean'], idx: 1 + }), + 'variancep': measure({ + name: 'variancep', + set: 'this.valid > 1 ? this.dev / this.valid : 0', + req: ['variance'], idx: 2 + }), + 'stdev': measure({ + name: 'stdev', + set: 'this.valid > 1 ? Math.sqrt(this.dev / (this.valid-1)) : 0', + req: ['variance'], idx: 2 + }), + 'stdevp': measure({ + name: 'stdevp', + set: 'this.valid > 1 ? Math.sqrt(this.dev / this.valid) : 0', + req: ['variance'], idx: 2 + }), + 'median': measure({ + name: 'median', + set: 'cell.data.q2(this.get)', + req: ['values'], idx: 3 + }), + 'q1': measure({ + name: 'q1', + set: 'cell.data.q1(this.get)', + req: ['values'], idx: 3 + }), + 'q3': measure({ + name: 'q3', + set: 'cell.data.q3(this.get)', + req: ['values'], idx: 3 + }), + 'distinct': measure({ + name: 'distinct', + set: 'this.distinct(cell.data.values(), this.get)', + req: ['values'], idx: 3 + }), + 'argmin': measure({ + name: 'argmin', + add: 'if (v < this.min) this.argmin = t;', + rem: 'if (v <= this.min) this.argmin = null;', + set: 'this.argmin = this.argmin || cell.data.argmin(this.get)', + req: ['min'], str: ['values'], idx: 3 + }), + 'argmax': measure({ + name: 'argmax', + add: 'if (v > this.max) this.argmax = t;', + rem: 'if (v >= this.max) this.argmax = null;', + set: 'this.argmax = this.argmax || cell.data.argmax(this.get)', + req: ['max'], str: ['values'], idx: 3 + }), + 'min': measure({ + name: 'min', + init: 'this.min = +Infinity;', + add: 'if (v < this.min) this.min = v;', + rem: 'if (v <= this.min) this.min = NaN;', + set: 'this.min = (isNaN(this.min) ? cell.data.min(this.get) : this.min)', + str: ['values'], idx: 4 + }), + 'max': measure({ + name: 'max', + init: 'this.max = -Infinity;', + add: 'if (v > this.max) this.max = v;', + rem: 'if (v >= this.max) this.max = NaN;', + set: 'this.max = (isNaN(this.max) ? cell.data.max(this.get) : this.max)', + str: ['values'], idx: 4 + }), + 'modeskew': measure({ + name: 'modeskew', + set: 'this.dev===0 ? 0 : (this.mean - cell.data.q2(this.get)) / Math.sqrt(this.dev/(this.valid-1))', + req: ['mean', 'stdev', 'median'], idx: 5 + }) +}; + +function measure(base) { + return function(out) { + var m = util.extend({init:'', add:'', rem:'', idx:0}, base); + m.out = out || base.name; + return m; + }; +} + +function resolve(agg, stream) { + function collect(m, a) { + function helper(r) { if (!m[r]) collect(m, m[r] = types[r]()); } + if (a.req) a.req.forEach(helper); + if (stream && a.str) a.str.forEach(helper); + return m; + } + var map = agg.reduce( + collect, + agg.reduce(function(m, a) { return (m[a.name] = a, m); }, {}) + ); + return util.vals(map).sort(function(a, b) { return a.idx - b.idx; }); +} + +function create(agg, stream, accessor, mutator) { + var all = resolve(agg, stream), + ctr = 'this.cell = cell; this.tuple = t; this.valid = 0; this.missing = 0;', + add = 'if (v==null) this.missing++; if (!this.isValid(v)) return; ++this.valid;', + rem = 'if (v==null) this.missing--; if (!this.isValid(v)) return; --this.valid;', + set = 'var t = this.tuple; var cell = this.cell;'; + + all.forEach(function(a) { + if (a.idx < 0) { + ctr = a.init + ctr; + add = a.add + add; + rem = a.rem + rem; + } else { + ctr += a.init; + add += a.add; + rem += a.rem; + } + }); + agg.slice() + .sort(function(a, b) { return a.idx - b.idx; }) + .forEach(function(a) { + set += 'this.assign(t,\''+a.out+'\','+a.set+');'; + }); + set += 'return t;'; + + /* jshint evil: true */ + ctr = Function('cell', 't', ctr); + ctr.prototype.assign = mutator; + ctr.prototype.add = Function('t', 'var v = this.get(t);' + add); + ctr.prototype.rem = Function('t', 'var v = this.get(t);' + rem); + ctr.prototype.set = Function(set); + ctr.prototype.get = accessor; + ctr.prototype.distinct = require('../stats').count.distinct; + ctr.prototype.isValid = util.isValid; + ctr.fields = agg.map(util.$('out')); + return ctr; +} + +types.create = create; +module.exports = types; + +},{"../stats":28,"../util":31}],13:[function(require,module,exports){ +var util = require('../util'), + time = require('../time'), + EPSILON = 1e-15; + +function bins(opt) { + if (!opt) { throw Error("Missing binning options."); } + + // determine range + var maxb = opt.maxbins || 15, + base = opt.base || 10, + logb = Math.log(base), + div = opt.div || [5, 2], + min = opt.min, + max = opt.max, + span = max - min, + step, level, minstep, precision, v, i, eps; + + if (opt.step) { + // if step size is explicitly given, use that + step = opt.step; + } else if (opt.steps) { + // if provided, limit choice to acceptable step sizes + step = opt.steps[Math.min( + opt.steps.length - 1, + bisect(opt.steps, span/maxb, 0, opt.steps.length) + )]; + } else { + // else use span to determine step size + level = Math.ceil(Math.log(maxb) / logb); + minstep = opt.minstep || 0; + step = Math.max( + minstep, + Math.pow(base, Math.round(Math.log(span) / logb) - level) + ); + + // increase step size if too many bins + do { step *= base; } while (Math.ceil(span/step) > maxb); + + // decrease step size if allowed + for (i=0; i= minstep && span / v <= maxb) step = v; + } + } + + // update precision, min and max + v = Math.log(step); + precision = v >= 0 ? 0 : ~~(-v / logb) + 1; + eps = Math.pow(base, -precision - 1); + min = Math.min(min, Math.floor(min / step + eps) * step); + max = Math.ceil(max / step) * step; + + return { + start: min, + stop: max, + step: step, + unit: {precision: precision}, + value: value, + index: index + }; +} + +function bisect(a, x, lo, hi) { + while (lo < hi) { + var mid = lo + hi >>> 1; + if (util.cmp(a[mid], x) < 0) { lo = mid + 1; } + else { hi = mid; } + } + return lo; +} + +function value(v) { + return this.step * Math.floor(v / this.step + EPSILON); +} + +function index(v) { + return Math.floor((v - this.start) / this.step + EPSILON); +} + +function date_value(v) { + return this.unit.date(value.call(this, v)); +} + +function date_index(v) { + return index.call(this, this.unit.unit(v)); +} + +bins.date = function(opt) { + if (!opt) { throw Error("Missing date binning options."); } + + // find time step, then bin + var units = opt.utc ? time.utc : time, + dmin = opt.min, + dmax = opt.max, + maxb = opt.maxbins || 20, + minb = opt.minbins || 4, + span = (+dmax) - (+dmin), + unit = opt.unit ? units[opt.unit] : units.find(span, minb, maxb), + spec = bins({ + min: unit.min != null ? unit.min : unit.unit(dmin), + max: unit.max != null ? unit.max : unit.unit(dmax), + maxbins: maxb, + minstep: unit.minstep, + steps: unit.step + }); + + spec.unit = unit; + spec.index = date_index; + if (!opt.raw) spec.value = date_value; + return spec; +}; + +module.exports = bins; + +},{"../time":30,"../util":31}],14:[function(require,module,exports){ +var bins = require('./bins'), + gen = require('../generate'), + type = require('../import/type'), + util = require('../util'), + stats = require('../stats'); + +var qtype = { + 'integer': 1, + 'number': 1, + 'date': 1 +}; + +function $bin(values, f, opt) { + opt = options(values, f, opt); + var b = spec(opt); + return !b ? (opt.accessor || util.identity) : + util.$func('bin', b.unit.unit ? + function(x) { return b.value(b.unit.unit(x)); } : + function(x) { return b.value(x); } + )(opt.accessor); +} + +function histogram(values, f, opt) { + opt = options(values, f, opt); + var b = spec(opt); + return b ? + numerical(values, opt.accessor, b) : + categorical(values, opt.accessor, opt && opt.sort); +} + +function spec(opt) { + var t = opt.type, b = null; + if (t == null || qtype[t]) { + if (t === 'integer' && opt.minstep == null) opt.minstep = 1; + b = (t === 'date') ? bins.date(opt) : bins(opt); + } + return b; +} + +function options() { + var a = arguments, + i = 0, + values = util.isArray(a[i]) ? a[i++] : null, + f = util.isFunction(a[i]) || util.isString(a[i]) ? util.$(a[i++]) : null, + opt = util.extend({}, a[i]); + + if (values) { + opt.type = opt.type || type(values, f); + if (qtype[opt.type]) { + var ext = stats.extent(values, f); + opt = util.extend({min: ext[0], max: ext[1]}, opt); + } + } + if (f) { opt.accessor = f; } + return opt; +} + +function numerical(values, f, b) { + var h = gen.range(b.start, b.stop + b.step/2, b.step) + .map(function(v) { return {value: b.value(v), count: 0}; }); + + for (var i=0, v, j; i= h.length || !isFinite(j)) continue; + h[j].count += 1; + } + } + h.bins = b; + return h; +} + +function categorical(values, f, sort) { + var u = stats.unique(values, f), + c = stats.count.map(values, f); + return u.map(function(k) { return {value: k, count: c[k]}; }) + .sort(util.comparator(sort ? '-count' : '+value')); +} + +module.exports = { + $bin: $bin, + histogram: histogram +}; + +},{"../generate":16,"../import/type":25,"../stats":28,"../util":31,"./bins":13}],15:[function(require,module,exports){ +var d3_time = require('d3-time'), + d3_timeF = require('d3-time-format'), + d3_numberF = require('d3-format'), + numberF = d3_numberF, // defaults to EN-US + timeF = d3_timeF; // defaults to EN-US + +function numberLocale(l) { + var f = d3_numberF.localeFormat(l); + if (f == null) throw Error('Unrecognized locale: ' + l); + numberF = f; +} + +function timeLocale(l) { + var f = d3_timeF.localeFormat(l); + if (f == null) throw Error('Unrecognized locale: ' + l); + timeF = f; +} + +module.exports = { + // Update number formatter to use provided locale configuration. + // For more see https://github.com/d3/d3-format + numberLocale: numberLocale, + number: function(f) { return numberF.format(f); }, + numberPrefix: function(f, v) { return numberF.formatPrefix(f, v); }, + + // Update time formatter to use provided locale configuration. + // For more see https://github.com/d3/d3-time-format + timeLocale: timeLocale, + time: function(f) { return timeF.format(f); }, + utc: function(f) { return timeF.utcFormat(f); }, + + // Set number and time locale simultaneously. + locale: function(l) { numberLocale(l); timeLocale(l); }, + + // automatic formatting functions + auto: { + number: numberAutoFormat, + time: function() { return timeAutoFormat(); }, + utc: function() { return utcAutoFormat(); } + } +}; + +var e10 = Math.sqrt(50), + e5 = Math.sqrt(10), + e2 = Math.sqrt(2); + +function intervals(domain, count) { + if (!domain.length) domain = [0]; + if (count == null) count = 10; + + var start = domain[0], + stop = domain[domain.length - 1]; + + if (stop < start) { error = stop; stop = start; start = error; } + + var span = (stop - start) || (count = 1, start || stop || 1), + step = Math.pow(10, Math.floor(Math.log(span / count) / Math.LN10)), + error = span / count / step; + + // Filter ticks to get closer to the desired count. + if (error >= e10) step *= 10; + else if (error >= e5) step *= 5; + else if (error >= e2) step *= 2; + + // Round start and stop values to step interval. + return [ + Math.ceil(start / step) * step, + Math.floor(stop / step) * step + step / 2, // inclusive + step + ]; +} + +function significantDigits(domain) { + return domain.map(function(x) { + // determine significant digits based on exponential format + var s = x.toExponential(), + e = s.indexOf('e'), + d = s.indexOf('.'); + return d < 0 ? 1 : (e-d); + }).reduce(function(max, p) { + // return the maximum sig digit count + return Math.max(max, p); + }, 0); +} + +function numberAutoFormat(domain, count, f) { + var range = intervals(domain, count); + if (f == null) { + f = ',.' + d3_numberF.precisionFixed(range[2]) + 'f'; + } else { + switch (f = d3_numberF.formatSpecifier(f), f.type) { + case 's': { + if (f.precision == null) f.precision = significantDigits(domain); + return numberF.format(f); + } + case '': + case 'e': + case 'g': + case 'p': + case 'r': { + if (f.precision == null) f.precision = d3_numberF.precisionRound(range[2], Math.max(Math.abs(range[0]), Math.abs(range[1]))) - (f.type === 'e'); + break; + } + case 'f': + case '%': { + if (f.precision == null) f.precision = d3_numberF.precisionFixed(range[2]) - (f.type === '%') * 2; + break; + } + } + } + return numberF.format(f); +} + +function timeAutoFormat() { + var f = timeF.format, + formatMillisecond = f('.%L'), + formatSecond = f(':%S'), + formatMinute = f('%I:%M'), + formatHour = f('%I %p'), + formatDay = f('%a %d'), + formatWeek = f('%b %d'), + formatMonth = f('%B'), + formatYear = f('%Y'); + + return function(date) { + var d = +date; + return (d3_time.second(date) < d ? formatMillisecond + : d3_time.minute(date) < d ? formatSecond + : d3_time.hour(date) < d ? formatMinute + : d3_time.day(date) < d ? formatHour + : d3_time.month(date) < d ? + (d3_time.week(date) < d ? formatDay : formatWeek) + : d3_time.year(date) < d ? formatMonth + : formatYear)(date); + }; +} + +function utcAutoFormat() { + var f = timeF.utcFormat, + formatMillisecond = f('.%L'), + formatSecond = f(':%S'), + formatMinute = f('%I:%M'), + formatHour = f('%I %p'), + formatDay = f('%a %d'), + formatWeek = f('%b %d'), + formatMonth = f('%B'), + formatYear = f('%Y'); + + return function(date) { + var d = +date; + return (d3_time.utcSecond(date) < d ? formatMillisecond + : d3_time.utcMinute(date) < d ? formatSecond + : d3_time.utcHour(date) < d ? formatMinute + : d3_time.utcDay(date) < d ? formatHour + : d3_time.utcMonth(date) < d ? + (d3_time.utcWeek(date) < d ? formatDay : formatWeek) + : d3_time.utcYear(date) < d ? formatMonth + : formatYear)(date); + }; +} + +},{"d3-format":6,"d3-time":8,"d3-time-format":7}],16:[function(require,module,exports){ +var gen = module.exports = {}; + +gen.repeat = function(val, n) { + var a = Array(n), i; + for (i=0; i stop) range.push(j); + else while ((j = start + step * ++i) < stop) range.push(j); + return range; +}; + +gen.random = {}; + +gen.random.uniform = function(min, max) { + if (max === undefined) { + max = min === undefined ? 1 : min; + min = 0; + } + var d = max - min; + var f = function() { + return min + d * Math.random(); + }; + f.samples = function(n) { return gen.zeros(n).map(f); }; + return f; +}; + +gen.random.integer = function(a, b) { + if (b === undefined) { + b = a; + a = 0; + } + var d = b - a; + var f = function() { + return a + Math.floor(d * Math.random()); + }; + f.samples = function(n) { return gen.zeros(n).map(f); }; + return f; +}; + +gen.random.normal = function(mean, stdev) { + mean = mean || 0; + stdev = stdev || 1; + var next; + var f = function() { + var x = 0, y = 0, rds, c; + if (next !== undefined) { + x = next; + next = undefined; + return x; + } + do { + x = Math.random()*2-1; + y = Math.random()*2-1; + rds = x*x + y*y; + } while (rds === 0 || rds > 1); + c = Math.sqrt(-2*Math.log(rds)/rds); // Box-Muller transform + next = mean + y*c*stdev; + return mean + x*c*stdev; + }; + f.samples = function(n) { return gen.zeros(n).map(f); }; + return f; +}; + +},{}],17:[function(require,module,exports){ +var util = require('../../util'); +var d3_dsv = require('d3-dsv'); + +function dsv(data, format) { + if (data) { + var h = format.header; + data = (h ? h.join(format.delimiter) + '\n' : '') + data; + } + return d3_dsv.dsv(format.delimiter).parse(data); +} + +dsv.delimiter = function(delim) { + var fmt = {delimiter: delim}; + return function(data, format) { + return dsv(data, format ? util.extend(format, fmt) : fmt); + }; +}; + +module.exports = dsv; + +},{"../../util":31,"d3-dsv":5}],18:[function(require,module,exports){ +var dsv = require('./dsv'); + +module.exports = { + json: require('./json'), + topojson: require('./topojson'), + treejson: require('./treejson'), + dsv: dsv, + csv: dsv.delimiter(','), + tsv: dsv.delimiter('\t') +}; + +},{"./dsv":17,"./json":19,"./topojson":20,"./treejson":21}],19:[function(require,module,exports){ +var util = require('../../util'); + +module.exports = function(data, format) { + var d = util.isObject(data) && !util.isBuffer(data) ? + data : JSON.parse(data); + if (format && format.property) { + d = util.accessor(format.property)(d); + } + return d; +}; + +},{"../../util":31}],20:[function(require,module,exports){ +(function (global){ +var json = require('./json'); + +var reader = function(data, format) { + var topojson = reader.topojson; + if (topojson == null) { throw Error('TopoJSON library not loaded.'); } + + var t = json(data, format), obj; + + if (format && format.feature) { + if ((obj = t.objects[format.feature])) { + return topojson.feature(t, obj).features; + } else { + throw Error('Invalid TopoJSON object: ' + format.feature); + } + } else if (format && format.mesh) { + if ((obj = t.objects[format.mesh])) { + return [topojson.mesh(t, t.objects[format.mesh])]; + } else { + throw Error('Invalid TopoJSON object: ' + format.mesh); + } + } else { + throw Error('Missing TopoJSON feature or mesh parameter.'); + } +}; + +reader.topojson = (typeof window !== "undefined" ? window['topojson'] : typeof global !== "undefined" ? global['topojson'] : null); +module.exports = reader; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"./json":19}],21:[function(require,module,exports){ +var json = require('./json'); + +module.exports = function(tree, format) { + return toTable(json(tree, format), format); +}; + +function toTable(root, fields) { + var childrenField = fields && fields.children || 'children', + parentField = fields && fields.parent || 'parent', + table = []; + + function visit(node, parent) { + node[parentField] = parent; + table.push(node); + var children = node[childrenField]; + if (children) { + for (var i=0; i 1 && domain[idx-1] === '.' && domain.lastIndexOf(d) === idx); + }); + if (!whiteListed) { + throw 'URL is not whitelisted: ' + url; + } + } + } + return url; +} + +function load(opt, callback) { + var error = callback || function(e) { throw e; }, url; + + try { + url = load.sanitizeUrl(opt); // enable override + } catch (err) { + error(err); + return; + } + + if (!url) { + error('Invalid URL: ' + opt.url); + } else if (load.useXHR) { + // on client, use xhr + return xhr(url, callback); + } else if (startsWith(url, fileProtocol)) { + // on server, if url starts with 'file://', strip it and load from file + return file(url.slice(fileProtocol.length), callback); + } else if (url.indexOf('://') < 0) { // TODO better protocol check? + // on server, if no protocol assume file + return file(url, callback); + } else { + // for regular URLs on server + return http(url, callback); + } +} + +function xhrHasResponse(request) { + var type = request.responseType; + return type && type !== 'text' ? + request.response : // null on error + request.responseText; // '' on error +} + +function xhr(url, callback) { + var async = !!callback; + var request = new XMLHttpRequest(); + // If IE does not support CORS, use XDomainRequest (copied from d3.xhr) + if (this.XDomainRequest && + !('withCredentials' in request) && + /^(http(s)?:)?\/\//.test(url)) request = new XDomainRequest(); + + function respond() { + var status = request.status; + if (!status && xhrHasResponse(request) || status >= 200 && status < 300 || status === 304) { + callback(null, request.responseText); + } else { + callback(request, null); + } + } + + if (async) { + if ('onload' in request) { + request.onload = request.onerror = respond; + } else { + request.onreadystatechange = function() { + if (request.readyState > 3) respond(); + }; + } + } + + request.open('GET', url, async); + request.send(); + + if (!async && xhrHasResponse(request)) { + return request.responseText; + } +} + +function file(filename, callback) { + var fs = require('fs'); + if (!callback) { + return fs.readFileSync(filename, 'utf8'); + } + fs.readFile(filename, callback); +} + +function http(url, callback) { + if (!callback) { + return require('sync-request')('GET', url).getBody(); + } + + var options = {url: url, encoding: null, gzip: true}; + require('request')(options, function(error, response, body) { + if (!error && response.statusCode === 200) { + callback(null, body); + } else { + error = error || + 'Load failed with response code ' + response.statusCode + '.'; + callback(error, null); + } + }); +} + +function startsWith(string, searchString) { + return string == null ? false : string.lastIndexOf(searchString, 0) === 0; +} + +load.sanitizeUrl = sanitizeUrl; + +load.useXHR = (typeof XMLHttpRequest !== 'undefined'); + +module.exports = load; + +},{"fs":2,"request":2,"sync-request":2,"url":2}],23:[function(require,module,exports){ +var util = require('../util'); +var type = require('./type'); +var formats = require('./formats'); + +function read(data, format) { + var type = (format && format.type) || 'json'; + data = formats[type](data, format); + if (format && format.parse) parse(data, format.parse); + return data; +} + +function parse(data, types) { + var cols, parsers, d, i, j, clen, len = data.length; + + types = (types==='auto') ? type.inferAll(data) : util.duplicate(types); + cols = util.keys(types); + parsers = cols.map(function(c) { return type.parsers[types[c]]; }); + + for (i=0, clen=cols.length; i 0 ? Math.min(l, opt.maxwidth) : l; + }); + + // print header row + var head = fields.map(function(name, i) { + return util.truncate(util.pad(name, lens[i], 'center'), lens[i]); + }).join(opt.separator); + + // build template function for each row + var tmpl = template(fields.map(function(name, i) { + return '{{' + + name + + (FMT[types[name]] || '') + + ('|pad:' + lens[i] + ',' + (POS[types[name]] || 'right')) + + ('|truncate:' + lens[i]) + + '}}'; + }).join(opt.separator)); + + // print table + return head + "\n" + data.map(tmpl).join('\n'); +}; + +module.exports.summary = function(s) { + s = s ? s.__summary__ ? s : stats.summary(s) : this; + var str = [], i, n; + for (i=0, n=s.length; i b) b = v; + } + } + return [a, b]; +}; + +// Find the integer indices of the minimum and maximum values. +stats.extent.index = function(values, f) { + f = util.$(f); + var x = -1, y = -1, a, b, v, i, n = values.length; + for (i=0; i b) { b = v; y = i; } + } + } + return [x, y]; +}; + +// Compute the dot product of two arrays of numbers. +stats.dot = function(values, a, b) { + var sum = 0, i, v; + if (!b) { + if (values.length !== a.length) { + throw Error('Array lengths must match.'); + } + for (i=0; i -1 && p !== v) { + mu = 1 + (i-1 + tie) / 2; + for (; tie -1) { + mu = 1 + (n-1 + tie) / 2; + for (; tie max) max = x; + delta = x - mean; + mean = mean + delta / (++valid); + M2 = M2 + delta * (x - mean); + vals.push(x); + } + } + M2 = M2 / (valid - 1); + sd = Math.sqrt(M2); + + // sort values for median and iqr + vals.sort(util.cmp); + + return { + type: type(values, f), + unique: u, + count: values.length, + valid: valid, + missing: missing, + distinct: distinct, + min: min, + max: max, + mean: mean, + stdev: sd, + median: (v = stats.quantile(vals, 0.5)), + q1: stats.quantile(vals, 0.25), + q3: stats.quantile(vals, 0.75), + modeskew: sd === 0 ? 0 : (mean - v) / sd + }; +}; + +// Compute profiles for all variables in a data set. +stats.summary = function(data, fields) { + fields = fields || util.keys(data[0]); + var s = fields.map(function(f) { + var p = stats.profile(data, util.$(f)); + return (p.field = f, p); + }); + return (s.__summary__ = true, s); +}; + +module.exports = stats; + +},{"./generate":16,"./import/type":25,"./util":31}],29:[function(require,module,exports){ +var util = require('./util'), + format = require('./format'); + +var context = { + formats: [], + format_map: {}, + truncate: util.truncate, + pad: util.pad +}; + +function template(text) { + var src = source(text, 'd'); + src = 'var __t; return ' + src + ';'; + + /* jshint evil: true */ + return (new Function('d', src)).bind(context); +} + +template.source = source; +template.context = context; +module.exports = template; + +// Clear cache of format objects. +// This can *break* prior template functions, so invoke with care! +template.clearFormatCache = function() { + context.formats = []; + context.format_map = {}; +}; + +// Generate property access code for use within template source. +// object: the name of the object (variable) containing template data +// property: the property access string, verbatim from template tag +template.property = function(object, property) { + var src = util.field(property).map(util.str).join(']['); + return object + '[' + src + ']'; +}; + +// Generate source code for a template function. +// text: the template text +// variable: the name of the data object variable ('obj' by default) +// properties: optional hash for collecting all accessed properties +function source(text, variable, properties) { + variable = variable || 'obj'; + var index = 0; + var src = '\''; + var regex = template_re; + + // Compile the template source, escaping string literals appropriately. + text.replace(regex, function(match, interpolate, offset) { + src += text + .slice(index, offset) + .replace(template_escaper, template_escapeChar); + index = offset + match.length; + + if (interpolate) { + src += '\'\n+((__t=(' + + template_var(interpolate, variable, properties) + + '))==null?\'\':__t)+\n\''; + } + + // Adobe VMs need the match returned to produce the correct offest. + return match; + }); + return src + '\''; +} + +function template_var(text, variable, properties) { + var filters = text.match(filter_re); + var prop = filters.shift().trim(); + var stringCast = true; + + function strcall(fn) { + fn = fn || ''; + if (stringCast) { + stringCast = false; + src = 'String(' + src + ')' + fn; + } else { + src += fn; + } + return src; + } + + function date() { + return '(typeof ' + src + '==="number"?new Date('+src+'):'+src+')'; + } + + function number_format(fmt, key) { + a = template_format(args[0], key, fmt); + stringCast = false; + src = 'this.formats['+a+']('+src+')'; + } + + function time_format(fmt, key) { + a = template_format(args[0], key, fmt); + stringCast = false; + src = 'this.formats['+a+']('+date()+')'; + } + + if (properties) properties[prop] = 1; + var src = template.property(variable, prop); + + for (var i=0; i 0) { + f = f.slice(0, pidx); + args = filters[i].slice(pidx+1) + .match(args_re) + .map(function(s) { return s.trim(); }); + } + f = f.trim(); + + switch (f) { + case 'length': + strcall('.length'); + break; + case 'lower': + strcall('.toLowerCase()'); + break; + case 'upper': + strcall('.toUpperCase()'); + break; + case 'lower-locale': + strcall('.toLocaleLowerCase()'); + break; + case 'upper-locale': + strcall('.toLocaleUpperCase()'); + break; + case 'trim': + strcall('.trim()'); + break; + case 'left': + a = util.number(args[0]); + strcall('.slice(0,' + a + ')'); + break; + case 'right': + a = util.number(args[0]); + strcall('.slice(-' + a +')'); + break; + case 'mid': + a = util.number(args[0]); + b = a + util.number(args[1]); + strcall('.slice(+'+a+','+b+')'); + break; + case 'slice': + a = util.number(args[0]); + strcall('.slice('+ a + + (args.length > 1 ? ',' + util.number(args[1]) : '') + + ')'); + break; + case 'truncate': + a = util.number(args[0]); + b = args[1]; + b = (b!=='left' && b!=='middle' && b!=='center') ? 'right' : b; + src = 'this.truncate(' + strcall() + ',' + a + ',\'' + b + '\')'; + break; + case 'pad': + a = util.number(args[0]); + b = args[1]; + b = (b!=='left' && b!=='middle' && b!=='center') ? 'right' : b; + src = 'this.pad(' + strcall() + ',' + a + ',\'' + b + '\')'; + break; + case 'number': + number_format(format.number, 'number'); + break; + case 'time': + time_format(format.time, 'time'); + break; + case 'time-utc': + time_format(format.utc, 'time-utc'); + break; + default: + throw Error('Unrecognized template filter: ' + f); + } + } + + return src; +} + +var template_re = /\{\{(.+?)\}\}|$/g, + filter_re = /(?:"[^"]*"|\'[^\']*\'|[^\|"]+|[^\|\']+)+/g, + args_re = /(?:"[^"]*"|\'[^\']*\'|[^,"]+|[^,\']+)+/g; + +// Certain characters need to be escaped so that they can be put into a +// string literal. +var template_escapes = { + '\'': '\'', + '\\': '\\', + '\r': 'r', + '\n': 'n', + '\u2028': 'u2028', + '\u2029': 'u2029' +}; + +var template_escaper = /\\|'|\r|\n|\u2028|\u2029/g; + +function template_escapeChar(match) { + return '\\' + template_escapes[match]; +} + +function template_format(pattern, key, fmt) { + if ((pattern[0] === '\'' && pattern[pattern.length-1] === '\'') || + (pattern[0] === '"' && pattern[pattern.length-1] === '"')) { + pattern = pattern.slice(1, -1); + } else { + throw Error('Format pattern must be quoted: ' + pattern); + } + key = key + ':' + pattern; + if (!context.format_map[key]) { + var f = fmt(pattern); + var i = context.formats.length; + context.formats.push(f); + context.format_map[key] = i; + } + return context.format_map[key]; +} + +},{"./format":15,"./util":31}],30:[function(require,module,exports){ +var d3_time = require('d3-time'); + +var tempDate = new Date(), + baseDate = new Date(0, 0, 1).setFullYear(0), // Jan 1, 0 AD + utcBaseDate = new Date(Date.UTC(0, 0, 1)).setUTCFullYear(0); + +function date(d) { + return (tempDate.setTime(+d), tempDate); +} + +// create a time unit entry +function entry(type, date, unit, step, min, max) { + var e = { + type: type, + date: date, + unit: unit + }; + if (step) { + e.step = step; + } else { + e.minstep = 1; + } + if (min != null) e.min = min; + if (max != null) e.max = max; + return e; +} + +function create(type, unit, base, step, min, max) { + return entry(type, + function(d) { return unit.offset(base, d); }, + function(d) { return unit.count(base, d); }, + step, min, max); +} + +var locale = [ + create('second', d3_time.second, baseDate), + create('minute', d3_time.minute, baseDate), + create('hour', d3_time.hour, baseDate), + create('day', d3_time.day, baseDate, [1, 7]), + create('month', d3_time.month, baseDate, [1, 3, 6]), + create('year', d3_time.year, baseDate), + + // periodic units + entry('seconds', + function(d) { return new Date(1970, 0, 1, 0, 0, d); }, + function(d) { return date(d).getSeconds(); }, + null, 0, 59 + ), + entry('minutes', + function(d) { return new Date(1970, 0, 1, 0, d); }, + function(d) { return date(d).getMinutes(); }, + null, 0, 59 + ), + entry('hours', + function(d) { return new Date(1970, 0, 1, d); }, + function(d) { return date(d).getHours(); }, + null, 0, 23 + ), + entry('weekdays', + function(d) { return new Date(1970, 0, 4+d); }, + function(d) { return date(d).getDay(); }, + [1], 0, 6 + ), + entry('dates', + function(d) { return new Date(1970, 0, d); }, + function(d) { return date(d).getDate(); }, + [1], 1, 31 + ), + entry('months', + function(d) { return new Date(1970, d % 12, 1); }, + function(d) { return date(d).getMonth(); }, + [1], 0, 11 + ) +]; + +var utc = [ + create('second', d3_time.utcSecond, utcBaseDate), + create('minute', d3_time.utcMinute, utcBaseDate), + create('hour', d3_time.utcHour, utcBaseDate), + create('day', d3_time.utcDay, utcBaseDate, [1, 7]), + create('month', d3_time.utcMonth, utcBaseDate, [1, 3, 6]), + create('year', d3_time.utcYear, utcBaseDate), + + // periodic units + entry('seconds', + function(d) { return new Date(Date.UTC(1970, 0, 1, 0, 0, d)); }, + function(d) { return date(d).getUTCSeconds(); }, + null, 0, 59 + ), + entry('minutes', + function(d) { return new Date(Date.UTC(1970, 0, 1, 0, d)); }, + function(d) { return date(d).getUTCMinutes(); }, + null, 0, 59 + ), + entry('hours', + function(d) { return new Date(Date.UTC(1970, 0, 1, d)); }, + function(d) { return date(d).getUTCHours(); }, + null, 0, 23 + ), + entry('weekdays', + function(d) { return new Date(Date.UTC(1970, 0, 4+d)); }, + function(d) { return date(d).getUTCDay(); }, + [1], 0, 6 + ), + entry('dates', + function(d) { return new Date(Date.UTC(1970, 0, d)); }, + function(d) { return date(d).getUTCDate(); }, + [1], 1, 31 + ), + entry('months', + function(d) { return new Date(Date.UTC(1970, d % 12, 1)); }, + function(d) { return date(d).getUTCMonth(); }, + [1], 0, 11 + ) +]; + +var STEPS = [ + [31536e6, 5], // 1-year + [7776e6, 4], // 3-month + [2592e6, 4], // 1-month + [12096e5, 3], // 2-week + [6048e5, 3], // 1-week + [1728e5, 3], // 2-day + [864e5, 3], // 1-day + [432e5, 2], // 12-hour + [216e5, 2], // 6-hour + [108e5, 2], // 3-hour + [36e5, 2], // 1-hour + [18e5, 1], // 30-minute + [9e5, 1], // 15-minute + [3e5, 1], // 5-minute + [6e4, 1], // 1-minute + [3e4, 0], // 30-second + [15e3, 0], // 15-second + [5e3, 0], // 5-second + [1e3, 0] // 1-second +]; + +function find(units, span, minb, maxb) { + var step = STEPS[0], i, n, bins; + + for (i=1, n=STEPS.length; i step[0]) { + bins = span / step[0]; + if (bins > maxb) { + return units[STEPS[i-1][1]]; + } + if (bins >= minb) { + return units[step[1]]; + } + } + } + return units[STEPS[n-1][1]]; +} + +function toUnitMap(units) { + var map = {}, i, n; + for (i=0, n=units.length; i 1 ? + function(x) { return s.reduce(function(x,f) { return x[f]; }, x); } : + function(x) { return x[f]; } + ); +}; + +// short-cut for accessor +u.$ = u.accessor; + +u.mutator = function(f) { + var s; + return u.isString(f) && (s=u.field(f)).length > 1 ? + function(x, v) { + for (var i=0; i y) return sign[i]; + } + return 0; + }; +}; + +u.cmp = function(a, b) { + if (a < b) { + return -1; + } else if (a > b) { + return 1; + } else if (a >= b) { + return 0; + } else if (a === null) { + return -1; + } else if (b === null) { + return 1; + } + return NaN; +}; + +u.numcmp = function(a, b) { return a - b; }; + +u.stablesort = function(array, sortBy, keyFn) { + var indices = array.reduce(function(idx, v, i) { + return (idx[keyFn(v)] = i, idx); + }, {}); + + array.sort(function(a, b) { + var sa = sortBy(a), + sb = sortBy(b); + return sa < sb ? -1 : sa > sb ? 1 + : (indices[keyFn(a)] - indices[keyFn(b)]); + }); + + return array; +}; + + +// string functions + +u.pad = function(s, length, pos, padchar) { + padchar = padchar || " "; + var d = length - s.length; + if (d <= 0) return s; + switch (pos) { + case 'left': + return strrep(d, padchar) + s; + case 'middle': + case 'center': + return strrep(Math.floor(d/2), padchar) + + s + strrep(Math.ceil(d/2), padchar); + default: + return s + strrep(d, padchar); + } +}; + +function strrep(n, str) { + var s = "", i; + for (i=0; i 1) { + for (var i=1, n=ref.length; i 0) { + node = pq.peek(); + pulse = pulses[node._id]; + + if (node.rank() !== node.qrank()) { + // A node's rank might change during a propagation. Re-queue if so. + pq.replace(node.qrank(true)); + } else { + // Evaluate node and propagate pulse. + pq.pop(); + pulses[node._id] = null; + listeners = node._listeners; + pulse = this.evaluate(pulse, node); + + // Propagate the pulse. + if (pulse !== this.doNotPropagate) { + // Ensure reflow pulses always send reflow pulses even if skipped. + if (!pulse.reflow && node.reflows()) { + pulse = ChangeSet.create(pulse, true); + } + + for (i=0, len=listeners.length; i 0) branch[i-1].addListener(node); + } + + return branch; +}; + +prototype.disconnect = function(branch) { + var collector, node, data, signals, i, n, j, m; + + for (i=0, n=branch.length; i= pulse.stamp, + run = node.router() || pulse.add.length || pulse.rem.length; + + return run || !reflowed || node.reevaluate(pulse); +}; + +prototype.evaluate = function(pulse, node) { + if (!this.reevaluate(pulse, node)) return pulse; + pulse = node.evaluate(pulse); + node.last(pulse.stamp); + return pulse; +}; + +module.exports = Graph; + +},{"./ChangeSet":32,"./Collector":33,"./DataSource":34,"./Dependencies":35,"./Heap":37,"./Signal":39,"./Tuple":40,"datalib":26}],37:[function(require,module,exports){ +function Heap(comparator) { + this.cmp = comparator; + this.nodes = []; +} + +var prototype = Heap.prototype; + +prototype.size = function() { + return this.nodes.length; +}; + +prototype.clear = function() { + return (this.nodes = [], this); +}; + +prototype.peek = function() { + return this.nodes[0]; +}; + +prototype.push = function(x) { + var array = this.nodes; + array.push(x); + return _siftdown(array, 0, array.length-1, this.cmp); +}; + +prototype.pop = function() { + var array = this.nodes, + last = array.pop(), + item; + + if (array.length) { + item = array[0]; + array[0] = last; + _siftup(array, 0, this.cmp); + } else { + item = last; + } + return item; +}; + +prototype.replace = function(item) { + var array = this.nodes, + retval = array[0]; + array[0] = item; + _siftup(array, 0, this.cmp); + return retval; +}; + +prototype.pushpop = function(item) { + var array = this.nodes, ref = array[0]; + if (array.length && this.cmp(ref, item) < 0) { + array[0] = item; + item = ref; + _siftup(array, 0, this.cmp); + } + return item; +}; + +function _siftdown(array, start, idx, cmp) { + var item, parent, pidx; + + item = array[idx]; + while (idx > start) { + pidx = (idx - 1) >> 1; + parent = array[pidx]; + if (cmp(item, parent) < 0) { + array[idx] = parent; + idx = pidx; + continue; + } + break; + } + return (array[idx] = item); +} + +function _siftup(array, idx, cmp) { + var start = idx, + end = array.length, + item = array[idx], + cidx = 2 * idx + 1, ridx; + + while (cidx < end) { + ridx = cidx + 1; + if (ridx < end && cmp(array[cidx], array[ridx]) >= 0) { + cidx = ridx; + } + array[idx] = array[cidx]; + idx = cidx; + cidx = 2 * idx + 1; + } + array[idx] = item; + return _siftdown(array, start, idx, cmp); +} + +module.exports = Heap; + +},{}],38:[function(require,module,exports){ +var DEPS = require('./Dependencies').ALL, + nodeID = 0; + +function Node(graph) { + if (graph) this.init(graph); +} + +var Flags = Node.Flags = { + Router: 0x01, // Responsible for propagating tuples, cannot be skipped. + Collector: 0x02, // Holds a materialized dataset, pulse node to reflow. + Produces: 0x04, // Produces new tuples. + Mutates: 0x08, // Sets properties of incoming tuples. + Reflows: 0x10, // Forwards a reflow pulse. + Batch: 0x20 // Performs batch data processing, needs collector. +}; + +var prototype = Node.prototype; + +prototype.init = function(graph) { + this._id = ++nodeID; + this._graph = graph; + this._rank = graph.rank(); // Topological sort by rank + this._qrank = null; // Rank when enqueued for propagation + this._stamp = 0; // Last stamp seen + + this._listeners = []; + this._listeners._ids = {}; // To prevent duplicate listeners + + // Initialize dependencies. + this._deps = {}; + for (var i=0, n=DEPS.length; i l._rank) { + l.rerank(); + } + + return this; +}; + +prototype.removeListener = function(l) { + if (!this._listeners._ids[l._id]) return false; + + var idx = this._listeners.indexOf(l), + b = idx >= 0; + + if (b) { + this._listeners.splice(idx, 1); + this._listeners._ids[l._id] = null; + } + return b; +}; + +prototype.disconnect = function() { + this._listeners = []; + this._listeners._ids = {}; +}; + +// Evaluate this dataflow node for the current pulse. +// Subclasses should override to perform custom processing. +prototype.evaluate = function(pulse) { + return pulse; +}; + +// Should this node be re-evaluated for the current pulse? +// Searches pulse to see if any dependencies have updated. +prototype.reevaluate = function(pulse) { + var prop, dep, i, n, j, m; + + for (i=0, n=DEPS.length; i=0;) { + if (!handler || h[i].handler === handler) { + x = h.splice(i, 1)[0]; + this.removeListener(x.node); + } + } + + return this; +}; + +module.exports = Signal; + +},{"./ChangeSet":32,"./Node":38}],40:[function(require,module,exports){ +var tupleID = 0; + +function ingest(datum) { + datum = (datum === Object(datum)) ? datum : {data: datum}; + datum._id = ++tupleID; + if (datum._prev) datum._prev = null; + return datum; +} + +function idMap(a, ids) { + ids = ids || {}; + for (var i=0, n=a.length; i0;) { + idMap(arguments[i], ids); + } + return data.filter(function(x) { return !ids[x._id]; }); + } +}; + +},{}],41:[function(require,module,exports){ +module.exports = { + ChangeSet: require('./ChangeSet'), + Collector: require('./Collector'), + DataSource: require('./DataSource'), + Dependencies: require('./Dependencies'), + Graph: require('./Graph'), + Node: require('./Node'), + Signal: require('./Signal'), + Tuple: require('./Tuple'), + debug: require('vega-logging').debug +}; + +},{"./ChangeSet":32,"./Collector":33,"./DataSource":34,"./Dependencies":35,"./Graph":36,"./Node":38,"./Signal":39,"./Tuple":40,"vega-logging":47}],42:[function(require,module,exports){ +function toMap(list) { + var map = {}, i, n; + for (i=0, n=list.length; i 0) { + return id; + } + if (constants.hasOwnProperty(id)) { + return constants[id]; + } + if (idWhiteList) { + if (idWhiteList.hasOwnProperty(id)) { + return id; + } else { + globals[id] = 1; + return lookupGlobal(id); + } + } + if (idBlackList && idBlackList.hasOwnProperty(id)) { + throw new Error('Illegal identifier: ' + id); + } + return id; + }, + 'Program': function(n) { + return n.body.map(codegen).join('\n'); + }, + 'MemberExpression': function(n) { + var d = !n.computed; + var o = codegen(n.object); + if (d) memberDepth += 1; + var p = codegen(n.property); + if (o === FIELD_VAR) { fields[p] = 1; } // HACKish... + if (d) memberDepth -= 1; + return o + (d ? '.'+p : '['+p+']'); + }, + 'CallExpression': function(n) { + if (n.callee.type !== 'Identifier') { + throw new Error('Illegal callee type: ' + n.callee.type); + } + var callee = n.callee.name; + var args = n.arguments; + var fn = functions.hasOwnProperty(callee) && functions[callee]; + if (!fn) throw new Error('Unrecognized function: ' + callee); + return fn instanceof Function ? + fn(args) : + fn + '(' + args.map(codegen).join(',') + ')'; + }, + 'ArrayExpression': function(n) { + return '[' + n.elements.map(codegen).join(',') + ']'; + }, + 'BinaryExpression': function(n) { + return '(' + codegen(n.left) + n.operator + codegen(n.right) + ')'; + }, + 'UnaryExpression': function(n) { + return '(' + n.operator + codegen(n.argument) + ')'; + }, + 'ConditionalExpression': function(n) { + return '(' + codegen(n.test) + + '?' + codegen(n.consequent) + + ':' + codegen(n.alternate) + + ')'; + }, + 'LogicalExpression': function(n) { + return '(' + codegen(n.left) + n.operator + codegen(n.right) + ')'; + }, + 'ObjectExpression': function(n) { + return '{' + n.properties.map(codegen).join(',') + '}'; + }, + 'Property': function(n) { + memberDepth += 1; + var k = codegen(n.key); + memberDepth -= 1; + return k + ':' + codegen(n.value); + }, + 'ExpressionStatement': function(n) { + return codegen(n.expression); + } + }; + + codegen_wrap.functions = functions; + codegen_wrap.constants = constants; + return codegen_wrap; +}; + +},{"./constants":43,"./functions":44}],43:[function(require,module,exports){ +module.exports = { + 'NaN': 'NaN', + 'E': 'Math.E', + 'LN2': 'Math.LN2', + 'LN10': 'Math.LN10', + 'LOG2E': 'Math.LOG2E', + 'LOG10E': 'Math.LOG10E', + 'PI': 'Math.PI', + 'SQRT1_2': 'Math.SQRT1_2', + 'SQRT2': 'Math.SQRT2' +}; +},{}],44:[function(require,module,exports){ +module.exports = function(codegen) { + + function fncall(name, args, cast, type) { + var obj = codegen(args[0]); + if (cast) { + obj = cast + '(' + obj + ')'; + if (cast.lastIndexOf('new ', 0) === 0) obj = '(' + obj + ')'; + } + return obj + '.' + name + (type < 0 ? '' : type === 0 ? + '()' : + '(' + args.slice(1).map(codegen).join(',') + ')'); + } + + function fn(name, cast, type) { + return function(args) { + return fncall(name, args, cast, type); + }; + } + + var DATE = 'new Date', + STRING = 'String', + REGEXP = 'RegExp'; + + return { + // MATH functions + 'isNaN': 'isNaN', + 'isFinite': 'isFinite', + 'abs': 'Math.abs', + 'acos': 'Math.acos', + 'asin': 'Math.asin', + 'atan': 'Math.atan', + 'atan2': 'Math.atan2', + 'ceil': 'Math.ceil', + 'cos': 'Math.cos', + 'exp': 'Math.exp', + 'floor': 'Math.floor', + 'log': 'Math.log', + 'max': 'Math.max', + 'min': 'Math.min', + 'pow': 'Math.pow', + 'random': 'Math.random', + 'round': 'Math.round', + 'sin': 'Math.sin', + 'sqrt': 'Math.sqrt', + 'tan': 'Math.tan', + + 'clamp': function(args) { + if (args.length < 3) + throw new Error('Missing arguments to clamp function.'); + if (args.length > 3) + throw new Error('Too many arguments to clamp function.'); + var a = args.map(codegen); + return 'Math.max('+a[1]+', Math.min('+a[2]+','+a[0]+'))'; + }, + + // DATE functions + 'now': 'Date.now', + 'datetime': DATE, + 'date': fn('getDate', DATE, 0), + 'day': fn('getDay', DATE, 0), + 'year': fn('getFullYear', DATE, 0), + 'month': fn('getMonth', DATE, 0), + 'hours': fn('getHours', DATE, 0), + 'minutes': fn('getMinutes', DATE, 0), + 'seconds': fn('getSeconds', DATE, 0), + 'milliseconds': fn('getMilliseconds', DATE, 0), + 'time': fn('getTime', DATE, 0), + 'timezoneoffset': fn('getTimezoneOffset', DATE, 0), + 'utcdate': fn('getUTCDate', DATE, 0), + 'utcday': fn('getUTCDay', DATE, 0), + 'utcyear': fn('getUTCFullYear', DATE, 0), + 'utcmonth': fn('getUTCMonth', DATE, 0), + 'utchours': fn('getUTCHours', DATE, 0), + 'utcminutes': fn('getUTCMinutes', DATE, 0), + 'utcseconds': fn('getUTCSeconds', DATE, 0), + 'utcmilliseconds': fn('getUTCMilliseconds', DATE, 0), + + // shared sequence functions + 'length': fn('length', null, -1), + 'indexof': fn('indexOf', null), + 'lastindexof': fn('lastIndexOf', null), + + // STRING functions + 'parseFloat': 'parseFloat', + 'parseInt': 'parseInt', + 'upper': fn('toUpperCase', STRING, 0), + 'lower': fn('toLowerCase', STRING, 0), + 'slice': fn('slice', STRING), + 'substring': fn('substring', STRING), + + // REGEXP functions + 'regexp': REGEXP, + 'test': fn('test', REGEXP), + + // Control Flow functions + 'if': function(args) { + if (args.length < 3) + throw new Error('Missing arguments to if function.'); + if (args.length > 3) + throw new Error('Too many arguments to if function.'); + var a = args.map(codegen); + return a[0]+'?'+a[1]+':'+a[2]; + } + }; +}; +},{}],45:[function(require,module,exports){ +var parser = require('./parser'), + codegen = require('./codegen'); + +var expr = module.exports = { + parse: function(input, opt) { + return parser.parse('('+input+')', opt); + }, + code: function(opt) { + return codegen(opt); + }, + compiler: function(args, opt) { + args = args.slice(); + var generator = codegen(opt), + len = args.length, + compile = function(str) { + var value = generator(expr.parse(str)); + args[len] = '"use strict"; return (' + value.code + ');'; + value.fn = Function.apply(null, args); + return value; + }; + compile.codegen = generator; + return compile; + }, + functions: require('./functions'), + constants: require('./constants') +}; + +},{"./codegen":42,"./constants":43,"./functions":44,"./parser":46}],46:[function(require,module,exports){ +/* + The following expression parser is based on Esprima (http://esprima.org/). + Original header comment and license for Esprima is included here: + + Copyright (C) 2013 Ariya Hidayat + Copyright (C) 2013 Thaddee Tyl + Copyright (C) 2013 Mathias Bynens + Copyright (C) 2012 Ariya Hidayat + Copyright (C) 2012 Mathias Bynens + Copyright (C) 2012 Joost-Wim Boekesteijn + Copyright (C) 2012 Kris Kowal + Copyright (C) 2012 Yusuke Suzuki + Copyright (C) 2012 Arpad Borsos + Copyright (C) 2011 Ariya Hidayat + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/* istanbul ignore next */ +module.exports = (function() { + 'use strict'; + + var Token, + TokenName, + Syntax, + PropertyKind, + Messages, + Regex, + source, + strict, + index, + lineNumber, + lineStart, + length, + lookahead, + state, + extra; + + Token = { + BooleanLiteral: 1, + EOF: 2, + Identifier: 3, + Keyword: 4, + NullLiteral: 5, + NumericLiteral: 6, + Punctuator: 7, + StringLiteral: 8, + RegularExpression: 9 + }; + + TokenName = {}; + TokenName[Token.BooleanLiteral] = 'Boolean'; + TokenName[Token.EOF] = ''; + TokenName[Token.Identifier] = 'Identifier'; + TokenName[Token.Keyword] = 'Keyword'; + TokenName[Token.NullLiteral] = 'Null'; + TokenName[Token.NumericLiteral] = 'Numeric'; + TokenName[Token.Punctuator] = 'Punctuator'; + TokenName[Token.StringLiteral] = 'String'; + TokenName[Token.RegularExpression] = 'RegularExpression'; + + Syntax = { + AssignmentExpression: 'AssignmentExpression', + ArrayExpression: 'ArrayExpression', + BinaryExpression: 'BinaryExpression', + CallExpression: 'CallExpression', + ConditionalExpression: 'ConditionalExpression', + ExpressionStatement: 'ExpressionStatement', + Identifier: 'Identifier', + Literal: 'Literal', + LogicalExpression: 'LogicalExpression', + MemberExpression: 'MemberExpression', + ObjectExpression: 'ObjectExpression', + Program: 'Program', + Property: 'Property', + UnaryExpression: 'UnaryExpression' + }; + + PropertyKind = { + Data: 1, + Get: 2, + Set: 4 + }; + + // Error messages should be identical to V8. + Messages = { + UnexpectedToken: 'Unexpected token %0', + UnexpectedNumber: 'Unexpected number', + UnexpectedString: 'Unexpected string', + UnexpectedIdentifier: 'Unexpected identifier', + UnexpectedReserved: 'Unexpected reserved word', + UnexpectedEOS: 'Unexpected end of input', + NewlineAfterThrow: 'Illegal newline after throw', + InvalidRegExp: 'Invalid regular expression', + UnterminatedRegExp: 'Invalid regular expression: missing /', + InvalidLHSInAssignment: 'Invalid left-hand side in assignment', + InvalidLHSInForIn: 'Invalid left-hand side in for-in', + MultipleDefaultsInSwitch: 'More than one default clause in switch statement', + NoCatchOrFinally: 'Missing catch or finally after try', + UnknownLabel: 'Undefined label \'%0\'', + Redeclaration: '%0 \'%1\' has already been declared', + IllegalContinue: 'Illegal continue statement', + IllegalBreak: 'Illegal break statement', + IllegalReturn: 'Illegal return statement', + StrictModeWith: 'Strict mode code may not include a with statement', + StrictCatchVariable: 'Catch variable may not be eval or arguments in strict mode', + StrictVarName: 'Variable name may not be eval or arguments in strict mode', + StrictParamName: 'Parameter name eval or arguments is not allowed in strict mode', + StrictParamDupe: 'Strict mode function may not have duplicate parameter names', + StrictFunctionName: 'Function name may not be eval or arguments in strict mode', + StrictOctalLiteral: 'Octal literals are not allowed in strict mode.', + StrictDelete: 'Delete of an unqualified identifier in strict mode.', + StrictDuplicateProperty: 'Duplicate data property in object literal not allowed in strict mode', + AccessorDataProperty: 'Object literal may not have data and accessor property with the same name', + AccessorGetSet: 'Object literal may not have multiple get/set accessors with the same name', + StrictLHSAssignment: 'Assignment to eval or arguments is not allowed in strict mode', + StrictLHSPostfix: 'Postfix increment/decrement may not have eval or arguments operand in strict mode', + StrictLHSPrefix: 'Prefix increment/decrement may not have eval or arguments operand in strict mode', + StrictReservedWord: 'Use of future reserved word in strict mode' + }; + + // See also tools/generate-unicode-regex.py. + Regex = { + NonAsciiIdentifierStart: new RegExp('[\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B2\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA7AD\uA7B0\uA7B1\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB5F\uAB64\uAB65\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]'), + NonAsciiIdentifierPart: new RegExp('[\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0300-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u0483-\u0487\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u061A\u0620-\u0669\u066E-\u06D3\u06D5-\u06DC\u06DF-\u06E8\u06EA-\u06FC\u06FF\u0710-\u074A\u074D-\u07B1\u07C0-\u07F5\u07FA\u0800-\u082D\u0840-\u085B\u08A0-\u08B2\u08E4-\u0963\u0966-\u096F\u0971-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09F1\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AEF\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BEF\u0C00-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58\u0C59\u0C60-\u0C63\u0C66-\u0C6F\u0C81-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D01-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D-\u0D44\u0D46-\u0D48\u0D4A-\u0D4E\u0D57\u0D60-\u0D63\u0D66-\u0D6F\u0D7A-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2\u0DF3\u0E01-\u0E3A\u0E40-\u0E4E\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F47\u0F49-\u0F6C\u0F71-\u0F84\u0F86-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1049\u1050-\u109D\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u135F\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1877\u1880-\u18AA\u18B0-\u18F5\u1900-\u191E\u1920-\u192B\u1930-\u193B\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19D9\u1A00-\u1A1B\u1A20-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA7\u1AB0-\u1ABD\u1B00-\u1B4B\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1BF3\u1C00-\u1C37\u1C40-\u1C49\u1C4D-\u1C7D\u1CD0-\u1CD2\u1CD4-\u1CF6\u1CF8\u1CF9\u1D00-\u1DF5\u1DFC-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u200C\u200D\u203F\u2040\u2054\u2071\u207F\u2090-\u209C\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D7F-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u2E2F\u3005-\u3007\u3021-\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u3099\u309A\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66F\uA674-\uA67D\uA67F-\uA69D\uA69F-\uA6F1\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA7AD\uA7B0\uA7B1\uA7F7-\uA827\uA840-\uA873\uA880-\uA8C4\uA8D0-\uA8D9\uA8E0-\uA8F7\uA8FB\uA900-\uA92D\uA930-\uA953\uA960-\uA97C\uA980-\uA9C0\uA9CF-\uA9D9\uA9E0-\uA9FE\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA60-\uAA76\uAA7A-\uAAC2\uAADB-\uAADD\uAAE0-\uAAEF\uAAF2-\uAAF6\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB5F\uAB64\uAB65\uABC0-\uABEA\uABEC\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE00-\uFE0F\uFE20-\uFE2D\uFE33\uFE34\uFE4D-\uFE4F\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]') + }; + + // Ensure the condition is true, otherwise throw an error. + // This is only to have a better contract semantic, i.e. another safety net + // to catch a logic error. The condition shall be fulfilled in normal case. + // Do NOT use this to enforce a certain condition on any user input. + + function assert(condition, message) { + if (!condition) { + throw new Error('ASSERT: ' + message); + } + } + + function isDecimalDigit(ch) { + return (ch >= 0x30 && ch <= 0x39); // 0..9 + } + + function isHexDigit(ch) { + return '0123456789abcdefABCDEF'.indexOf(ch) >= 0; + } + + function isOctalDigit(ch) { + return '01234567'.indexOf(ch) >= 0; + } + + // 7.2 White Space + + function isWhiteSpace(ch) { + return (ch === 0x20) || (ch === 0x09) || (ch === 0x0B) || (ch === 0x0C) || (ch === 0xA0) || + (ch >= 0x1680 && [0x1680, 0x180E, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200A, 0x202F, 0x205F, 0x3000, 0xFEFF].indexOf(ch) >= 0); + } + + // 7.3 Line Terminators + + function isLineTerminator(ch) { + return (ch === 0x0A) || (ch === 0x0D) || (ch === 0x2028) || (ch === 0x2029); + } + + // 7.6 Identifier Names and Identifiers + + function isIdentifierStart(ch) { + return (ch === 0x24) || (ch === 0x5F) || // $ (dollar) and _ (underscore) + (ch >= 0x41 && ch <= 0x5A) || // A..Z + (ch >= 0x61 && ch <= 0x7A) || // a..z + (ch === 0x5C) || // \ (backslash) + ((ch >= 0x80) && Regex.NonAsciiIdentifierStart.test(String.fromCharCode(ch))); + } + + function isIdentifierPart(ch) { + return (ch === 0x24) || (ch === 0x5F) || // $ (dollar) and _ (underscore) + (ch >= 0x41 && ch <= 0x5A) || // A..Z + (ch >= 0x61 && ch <= 0x7A) || // a..z + (ch >= 0x30 && ch <= 0x39) || // 0..9 + (ch === 0x5C) || // \ (backslash) + ((ch >= 0x80) && Regex.NonAsciiIdentifierPart.test(String.fromCharCode(ch))); + } + + // 7.6.1.2 Future Reserved Words + + function isFutureReservedWord(id) { + switch (id) { + case 'class': + case 'enum': + case 'export': + case 'extends': + case 'import': + case 'super': + return true; + default: + return false; + } + } + + function isStrictModeReservedWord(id) { + switch (id) { + case 'implements': + case 'interface': + case 'package': + case 'private': + case 'protected': + case 'public': + case 'static': + case 'yield': + case 'let': + return true; + default: + return false; + } + } + + // 7.6.1.1 Keywords + + function isKeyword(id) { + if (strict && isStrictModeReservedWord(id)) { + return true; + } + + // 'const' is specialized as Keyword in V8. + // 'yield' and 'let' are for compatiblity with SpiderMonkey and ES.next. + // Some others are from future reserved words. + + switch (id.length) { + case 2: + return (id === 'if') || (id === 'in') || (id === 'do'); + case 3: + return (id === 'var') || (id === 'for') || (id === 'new') || + (id === 'try') || (id === 'let'); + case 4: + return (id === 'this') || (id === 'else') || (id === 'case') || + (id === 'void') || (id === 'with') || (id === 'enum'); + case 5: + return (id === 'while') || (id === 'break') || (id === 'catch') || + (id === 'throw') || (id === 'const') || (id === 'yield') || + (id === 'class') || (id === 'super'); + case 6: + return (id === 'return') || (id === 'typeof') || (id === 'delete') || + (id === 'switch') || (id === 'export') || (id === 'import'); + case 7: + return (id === 'default') || (id === 'finally') || (id === 'extends'); + case 8: + return (id === 'function') || (id === 'continue') || (id === 'debugger'); + case 10: + return (id === 'instanceof'); + default: + return false; + } + } + + function skipComment() { + var ch, start; + + start = (index === 0); + while (index < length) { + ch = source.charCodeAt(index); + + if (isWhiteSpace(ch)) { + ++index; + } else if (isLineTerminator(ch)) { + ++index; + if (ch === 0x0D && source.charCodeAt(index) === 0x0A) { + ++index; + } + ++lineNumber; + lineStart = index; + start = true; + } else { + break; + } + } + } + + function scanHexEscape(prefix) { + var i, len, ch, code = 0; + + len = (prefix === 'u') ? 4 : 2; + for (i = 0; i < len; ++i) { + if (index < length && isHexDigit(source[index])) { + ch = source[index++]; + code = code * 16 + '0123456789abcdef'.indexOf(ch.toLowerCase()); + } else { + return ''; + } + } + return String.fromCharCode(code); + } + + function scanUnicodeCodePointEscape() { + var ch, code, cu1, cu2; + + ch = source[index]; + code = 0; + + // At least, one hex digit is required. + if (ch === '}') { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + + while (index < length) { + ch = source[index++]; + if (!isHexDigit(ch)) { + break; + } + code = code * 16 + '0123456789abcdef'.indexOf(ch.toLowerCase()); + } + + if (code > 0x10FFFF || ch !== '}') { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + + // UTF-16 Encoding + if (code <= 0xFFFF) { + return String.fromCharCode(code); + } + cu1 = ((code - 0x10000) >> 10) + 0xD800; + cu2 = ((code - 0x10000) & 1023) + 0xDC00; + return String.fromCharCode(cu1, cu2); + } + + function getEscapedIdentifier() { + var ch, id; + + ch = source.charCodeAt(index++); + id = String.fromCharCode(ch); + + // '\u' (U+005C, U+0075) denotes an escaped character. + if (ch === 0x5C) { + if (source.charCodeAt(index) !== 0x75) { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + ++index; + ch = scanHexEscape('u'); + if (!ch || ch === '\\' || !isIdentifierStart(ch.charCodeAt(0))) { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + id = ch; + } + + while (index < length) { + ch = source.charCodeAt(index); + if (!isIdentifierPart(ch)) { + break; + } + ++index; + id += String.fromCharCode(ch); + + // '\u' (U+005C, U+0075) denotes an escaped character. + if (ch === 0x5C) { + id = id.substr(0, id.length - 1); + if (source.charCodeAt(index) !== 0x75) { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + ++index; + ch = scanHexEscape('u'); + if (!ch || ch === '\\' || !isIdentifierPart(ch.charCodeAt(0))) { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + id += ch; + } + } + + return id; + } + + function getIdentifier() { + var start, ch; + + start = index++; + while (index < length) { + ch = source.charCodeAt(index); + if (ch === 0x5C) { + // Blackslash (U+005C) marks Unicode escape sequence. + index = start; + return getEscapedIdentifier(); + } + if (isIdentifierPart(ch)) { + ++index; + } else { + break; + } + } + + return source.slice(start, index); + } + + function scanIdentifier() { + var start, id, type; + + start = index; + + // Backslash (U+005C) starts an escaped character. + id = (source.charCodeAt(index) === 0x5C) ? getEscapedIdentifier() : getIdentifier(); + + // There is no keyword or literal with only one character. + // Thus, it must be an identifier. + if (id.length === 1) { + type = Token.Identifier; + } else if (isKeyword(id)) { + type = Token.Keyword; + } else if (id === 'null') { + type = Token.NullLiteral; + } else if (id === 'true' || id === 'false') { + type = Token.BooleanLiteral; + } else { + type = Token.Identifier; + } + + return { + type: type, + value: id, + lineNumber: lineNumber, + lineStart: lineStart, + start: start, + end: index + }; + } + + // 7.7 Punctuators + + function scanPunctuator() { + var start = index, + code = source.charCodeAt(index), + code2, + ch1 = source[index], + ch2, + ch3, + ch4; + + switch (code) { + + // Check for most common single-character punctuators. + case 0x2E: // . dot + case 0x28: // ( open bracket + case 0x29: // ) close bracket + case 0x3B: // ; semicolon + case 0x2C: // , comma + case 0x7B: // { open curly brace + case 0x7D: // } close curly brace + case 0x5B: // [ + case 0x5D: // ] + case 0x3A: // : + case 0x3F: // ? + case 0x7E: // ~ + ++index; + if (extra.tokenize) { + if (code === 0x28) { + extra.openParenToken = extra.tokens.length; + } else if (code === 0x7B) { + extra.openCurlyToken = extra.tokens.length; + } + } + return { + type: Token.Punctuator, + value: String.fromCharCode(code), + lineNumber: lineNumber, + lineStart: lineStart, + start: start, + end: index + }; + + default: + code2 = source.charCodeAt(index + 1); + + // '=' (U+003D) marks an assignment or comparison operator. + if (code2 === 0x3D) { + switch (code) { + case 0x2B: // + + case 0x2D: // - + case 0x2F: // / + case 0x3C: // < + case 0x3E: // > + case 0x5E: // ^ + case 0x7C: // | + case 0x25: // % + case 0x26: // & + case 0x2A: // * + index += 2; + return { + type: Token.Punctuator, + value: String.fromCharCode(code) + String.fromCharCode(code2), + lineNumber: lineNumber, + lineStart: lineStart, + start: start, + end: index + }; + + case 0x21: // ! + case 0x3D: // = + index += 2; + + // !== and === + if (source.charCodeAt(index) === 0x3D) { + ++index; + } + return { + type: Token.Punctuator, + value: source.slice(start, index), + lineNumber: lineNumber, + lineStart: lineStart, + start: start, + end: index + }; + } + } + } + + // 4-character punctuator: >>>= + + ch4 = source.substr(index, 4); + + if (ch4 === '>>>=') { + index += 4; + return { + type: Token.Punctuator, + value: ch4, + lineNumber: lineNumber, + lineStart: lineStart, + start: start, + end: index + }; + } + + // 3-character punctuators: === !== >>> <<= >>= + + ch3 = ch4.substr(0, 3); + + if (ch3 === '>>>' || ch3 === '<<=' || ch3 === '>>=') { + index += 3; + return { + type: Token.Punctuator, + value: ch3, + lineNumber: lineNumber, + lineStart: lineStart, + start: start, + end: index + }; + } + + // Other 2-character punctuators: ++ -- << >> && || + ch2 = ch3.substr(0, 2); + + if ((ch1 === ch2[1] && ('+-<>&|'.indexOf(ch1) >= 0)) || ch2 === '=>') { + index += 2; + return { + type: Token.Punctuator, + value: ch2, + lineNumber: lineNumber, + lineStart: lineStart, + start: start, + end: index + }; + } + + // 1-character punctuators: < > = ! + - * % & | ^ / + + if ('<>=!+-*%&|^/'.indexOf(ch1) >= 0) { + ++index; + return { + type: Token.Punctuator, + value: ch1, + lineNumber: lineNumber, + lineStart: lineStart, + start: start, + end: index + }; + } + + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + + // 7.8.3 Numeric Literals + + function scanHexLiteral(start) { + var number = ''; + + while (index < length) { + if (!isHexDigit(source[index])) { + break; + } + number += source[index++]; + } + + if (number.length === 0) { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + + if (isIdentifierStart(source.charCodeAt(index))) { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + + return { + type: Token.NumericLiteral, + value: parseInt('0x' + number, 16), + lineNumber: lineNumber, + lineStart: lineStart, + start: start, + end: index + }; + } + + function scanOctalLiteral(start) { + var number = '0' + source[index++]; + while (index < length) { + if (!isOctalDigit(source[index])) { + break; + } + number += source[index++]; + } + + if (isIdentifierStart(source.charCodeAt(index)) || isDecimalDigit(source.charCodeAt(index))) { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + + return { + type: Token.NumericLiteral, + value: parseInt(number, 8), + octal: true, + lineNumber: lineNumber, + lineStart: lineStart, + start: start, + end: index + }; + } + + function scanNumericLiteral() { + var number, start, ch; + + ch = source[index]; + assert(isDecimalDigit(ch.charCodeAt(0)) || (ch === '.'), + 'Numeric literal must start with a decimal digit or a decimal point'); + + start = index; + number = ''; + if (ch !== '.') { + number = source[index++]; + ch = source[index]; + + // Hex number starts with '0x'. + // Octal number starts with '0'. + if (number === '0') { + if (ch === 'x' || ch === 'X') { + ++index; + return scanHexLiteral(start); + } + if (isOctalDigit(ch)) { + return scanOctalLiteral(start); + } + + // decimal number starts with '0' such as '09' is illegal. + if (ch && isDecimalDigit(ch.charCodeAt(0))) { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + } + + while (isDecimalDigit(source.charCodeAt(index))) { + number += source[index++]; + } + ch = source[index]; + } + + if (ch === '.') { + number += source[index++]; + while (isDecimalDigit(source.charCodeAt(index))) { + number += source[index++]; + } + ch = source[index]; + } + + if (ch === 'e' || ch === 'E') { + number += source[index++]; + + ch = source[index]; + if (ch === '+' || ch === '-') { + number += source[index++]; + } + if (isDecimalDigit(source.charCodeAt(index))) { + while (isDecimalDigit(source.charCodeAt(index))) { + number += source[index++]; + } + } else { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + } + + if (isIdentifierStart(source.charCodeAt(index))) { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + + return { + type: Token.NumericLiteral, + value: parseFloat(number), + lineNumber: lineNumber, + lineStart: lineStart, + start: start, + end: index + }; + } + + // 7.8.4 String Literals + + function scanStringLiteral() { + var str = '', quote, start, ch, code, unescaped, restore, octal = false, startLineNumber, startLineStart; + startLineNumber = lineNumber; + startLineStart = lineStart; + + quote = source[index]; + assert((quote === '\'' || quote === '"'), + 'String literal must starts with a quote'); + + start = index; + ++index; + + while (index < length) { + ch = source[index++]; + + if (ch === quote) { + quote = ''; + break; + } else if (ch === '\\') { + ch = source[index++]; + if (!ch || !isLineTerminator(ch.charCodeAt(0))) { + switch (ch) { + case 'u': + case 'x': + if (source[index] === '{') { + ++index; + str += scanUnicodeCodePointEscape(); + } else { + restore = index; + unescaped = scanHexEscape(ch); + if (unescaped) { + str += unescaped; + } else { + index = restore; + str += ch; + } + } + break; + case 'n': + str += '\n'; + break; + case 'r': + str += '\r'; + break; + case 't': + str += '\t'; + break; + case 'b': + str += '\b'; + break; + case 'f': + str += '\f'; + break; + case 'v': + str += '\x0B'; + break; + + default: + if (isOctalDigit(ch)) { + code = '01234567'.indexOf(ch); + + // \0 is not octal escape sequence + if (code !== 0) { + octal = true; + } + + if (index < length && isOctalDigit(source[index])) { + octal = true; + code = code * 8 + '01234567'.indexOf(source[index++]); + + // 3 digits are only allowed when string starts + // with 0, 1, 2, 3 + if ('0123'.indexOf(ch) >= 0 && + index < length && + isOctalDigit(source[index])) { + code = code * 8 + '01234567'.indexOf(source[index++]); + } + } + str += String.fromCharCode(code); + } else { + str += ch; + } + break; + } + } else { + ++lineNumber; + if (ch === '\r' && source[index] === '\n') { + ++index; + } + lineStart = index; + } + } else if (isLineTerminator(ch.charCodeAt(0))) { + break; + } else { + str += ch; + } + } + + if (quote !== '') { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + + return { + type: Token.StringLiteral, + value: str, + octal: octal, + startLineNumber: startLineNumber, + startLineStart: startLineStart, + lineNumber: lineNumber, + lineStart: lineStart, + start: start, + end: index + }; + } + + function testRegExp(pattern, flags) { + var tmp = pattern, + value; + + if (flags.indexOf('u') >= 0) { + // Replace each astral symbol and every Unicode code point + // escape sequence with a single ASCII symbol to avoid throwing on + // regular expressions that are only valid in combination with the + // `/u` flag. + // Note: replacing with the ASCII symbol `x` might cause false + // negatives in unlikely scenarios. For example, `[\u{61}-b]` is a + // perfectly valid pattern that is equivalent to `[a-b]`, but it + // would be replaced by `[x-b]` which throws an error. + tmp = tmp + .replace(/\\u\{([0-9a-fA-F]+)\}/g, function ($0, $1) { + if (parseInt($1, 16) <= 0x10FFFF) { + return 'x'; + } + throwError({}, Messages.InvalidRegExp); + }) + .replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 'x'); + } + + // First, detect invalid regular expressions. + try { + value = new RegExp(tmp); + } catch (e) { + throwError({}, Messages.InvalidRegExp); + } + + // Return a regular expression object for this pattern-flag pair, or + // `null` in case the current environment doesn't support the flags it + // uses. + try { + return new RegExp(pattern, flags); + } catch (exception) { + return null; + } + } + + function scanRegExpBody() { + var ch, str, classMarker, terminated, body; + + ch = source[index]; + assert(ch === '/', 'Regular expression literal must start with a slash'); + str = source[index++]; + + classMarker = false; + terminated = false; + while (index < length) { + ch = source[index++]; + str += ch; + if (ch === '\\') { + ch = source[index++]; + // ECMA-262 7.8.5 + if (isLineTerminator(ch.charCodeAt(0))) { + throwError({}, Messages.UnterminatedRegExp); + } + str += ch; + } else if (isLineTerminator(ch.charCodeAt(0))) { + throwError({}, Messages.UnterminatedRegExp); + } else if (classMarker) { + if (ch === ']') { + classMarker = false; + } + } else { + if (ch === '/') { + terminated = true; + break; + } else if (ch === '[') { + classMarker = true; + } + } + } + + if (!terminated) { + throwError({}, Messages.UnterminatedRegExp); + } + + // Exclude leading and trailing slash. + body = str.substr(1, str.length - 2); + return { + value: body, + literal: str + }; + } + + function scanRegExpFlags() { + var ch, str, flags, restore; + + str = ''; + flags = ''; + while (index < length) { + ch = source[index]; + if (!isIdentifierPart(ch.charCodeAt(0))) { + break; + } + + ++index; + if (ch === '\\' && index < length) { + ch = source[index]; + if (ch === 'u') { + ++index; + restore = index; + ch = scanHexEscape('u'); + if (ch) { + flags += ch; + for (str += '\\u'; restore < index; ++restore) { + str += source[restore]; + } + } else { + index = restore; + flags += 'u'; + str += '\\u'; + } + throwErrorTolerant({}, Messages.UnexpectedToken, 'ILLEGAL'); + } else { + str += '\\'; + throwErrorTolerant({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + } else { + flags += ch; + str += ch; + } + } + + return { + value: flags, + literal: str + }; + } + + function scanRegExp() { + var start, body, flags, value; + + lookahead = null; + skipComment(); + start = index; + + body = scanRegExpBody(); + flags = scanRegExpFlags(); + value = testRegExp(body.value, flags.value); + + if (extra.tokenize) { + return { + type: Token.RegularExpression, + value: value, + regex: { + pattern: body.value, + flags: flags.value + }, + lineNumber: lineNumber, + lineStart: lineStart, + start: start, + end: index + }; + } + + return { + literal: body.literal + flags.literal, + value: value, + regex: { + pattern: body.value, + flags: flags.value + }, + start: start, + end: index + }; + } + + function collectRegex() { + var pos, loc, regex, token; + + skipComment(); + + pos = index; + loc = { + start: { + line: lineNumber, + column: index - lineStart + } + }; + + regex = scanRegExp(); + + loc.end = { + line: lineNumber, + column: index - lineStart + }; + + if (!extra.tokenize) { + // Pop the previous token, which is likely '/' or '/=' + if (extra.tokens.length > 0) { + token = extra.tokens[extra.tokens.length - 1]; + if (token.range[0] === pos && token.type === 'Punctuator') { + if (token.value === '/' || token.value === '/=') { + extra.tokens.pop(); + } + } + } + + extra.tokens.push({ + type: 'RegularExpression', + value: regex.literal, + regex: regex.regex, + range: [pos, index], + loc: loc + }); + } + + return regex; + } + + function isIdentifierName(token) { + return token.type === Token.Identifier || + token.type === Token.Keyword || + token.type === Token.BooleanLiteral || + token.type === Token.NullLiteral; + } + + function advanceSlash() { + var prevToken, + checkToken; + // Using the following algorithm: + // https://github.com/mozilla/sweet.js/wiki/design + prevToken = extra.tokens[extra.tokens.length - 1]; + if (!prevToken) { + // Nothing before that: it cannot be a division. + return collectRegex(); + } + if (prevToken.type === 'Punctuator') { + if (prevToken.value === ']') { + return scanPunctuator(); + } + if (prevToken.value === ')') { + checkToken = extra.tokens[extra.openParenToken - 1]; + if (checkToken && + checkToken.type === 'Keyword' && + (checkToken.value === 'if' || + checkToken.value === 'while' || + checkToken.value === 'for' || + checkToken.value === 'with')) { + return collectRegex(); + } + return scanPunctuator(); + } + if (prevToken.value === '}') { + // Dividing a function by anything makes little sense, + // but we have to check for that. + if (extra.tokens[extra.openCurlyToken - 3] && + extra.tokens[extra.openCurlyToken - 3].type === 'Keyword') { + // Anonymous function. + checkToken = extra.tokens[extra.openCurlyToken - 4]; + if (!checkToken) { + return scanPunctuator(); + } + } else if (extra.tokens[extra.openCurlyToken - 4] && + extra.tokens[extra.openCurlyToken - 4].type === 'Keyword') { + // Named function. + checkToken = extra.tokens[extra.openCurlyToken - 5]; + if (!checkToken) { + return collectRegex(); + } + } else { + return scanPunctuator(); + } + return scanPunctuator(); + } + return collectRegex(); + } + if (prevToken.type === 'Keyword' && prevToken.value !== 'this') { + return collectRegex(); + } + return scanPunctuator(); + } + + function advance() { + var ch; + + skipComment(); + + if (index >= length) { + return { + type: Token.EOF, + lineNumber: lineNumber, + lineStart: lineStart, + start: index, + end: index + }; + } + + ch = source.charCodeAt(index); + + if (isIdentifierStart(ch)) { + return scanIdentifier(); + } + + // Very common: ( and ) and ; + if (ch === 0x28 || ch === 0x29 || ch === 0x3B) { + return scanPunctuator(); + } + + // String literal starts with single quote (U+0027) or double quote (U+0022). + if (ch === 0x27 || ch === 0x22) { + return scanStringLiteral(); + } + + + // Dot (.) U+002E can also start a floating-point number, hence the need + // to check the next character. + if (ch === 0x2E) { + if (isDecimalDigit(source.charCodeAt(index + 1))) { + return scanNumericLiteral(); + } + return scanPunctuator(); + } + + if (isDecimalDigit(ch)) { + return scanNumericLiteral(); + } + + // Slash (/) U+002F can also start a regex. + if (extra.tokenize && ch === 0x2F) { + return advanceSlash(); + } + + return scanPunctuator(); + } + + function collectToken() { + var loc, token, value, entry; + + skipComment(); + loc = { + start: { + line: lineNumber, + column: index - lineStart + } + }; + + token = advance(); + loc.end = { + line: lineNumber, + column: index - lineStart + }; + + if (token.type !== Token.EOF) { + value = source.slice(token.start, token.end); + entry = { + type: TokenName[token.type], + value: value, + range: [token.start, token.end], + loc: loc + }; + if (token.regex) { + entry.regex = { + pattern: token.regex.pattern, + flags: token.regex.flags + }; + } + extra.tokens.push(entry); + } + + return token; + } + + function lex() { + var token; + + token = lookahead; + index = token.end; + lineNumber = token.lineNumber; + lineStart = token.lineStart; + + lookahead = (typeof extra.tokens !== 'undefined') ? collectToken() : advance(); + + index = token.end; + lineNumber = token.lineNumber; + lineStart = token.lineStart; + + return token; + } + + function peek() { + var pos, line, start; + + pos = index; + line = lineNumber; + start = lineStart; + lookahead = (typeof extra.tokens !== 'undefined') ? collectToken() : advance(); + index = pos; + lineNumber = line; + lineStart = start; + } + + function Position() { + this.line = lineNumber; + this.column = index - lineStart; + } + + function SourceLocation() { + this.start = new Position(); + this.end = null; + } + + function WrappingSourceLocation(startToken) { + if (startToken.type === Token.StringLiteral) { + this.start = { + line: startToken.startLineNumber, + column: startToken.start - startToken.startLineStart + }; + } else { + this.start = { + line: startToken.lineNumber, + column: startToken.start - startToken.lineStart + }; + } + this.end = null; + } + + function Node() { + // Skip comment. + index = lookahead.start; + if (lookahead.type === Token.StringLiteral) { + lineNumber = lookahead.startLineNumber; + lineStart = lookahead.startLineStart; + } else { + lineNumber = lookahead.lineNumber; + lineStart = lookahead.lineStart; + } + if (extra.range) { + this.range = [index, 0]; + } + if (extra.loc) { + this.loc = new SourceLocation(); + } + } + + function WrappingNode(startToken) { + if (extra.range) { + this.range = [startToken.start, 0]; + } + if (extra.loc) { + this.loc = new WrappingSourceLocation(startToken); + } + } + + WrappingNode.prototype = Node.prototype = { + + finish: function () { + if (extra.range) { + this.range[1] = index; + } + if (extra.loc) { + this.loc.end = new Position(); + if (extra.source) { + this.loc.source = extra.source; + } + } + }, + + finishArrayExpression: function (elements) { + this.type = Syntax.ArrayExpression; + this.elements = elements; + this.finish(); + return this; + }, + + finishAssignmentExpression: function (operator, left, right) { + this.type = Syntax.AssignmentExpression; + this.operator = operator; + this.left = left; + this.right = right; + this.finish(); + return this; + }, + + finishBinaryExpression: function (operator, left, right) { + this.type = (operator === '||' || operator === '&&') ? Syntax.LogicalExpression : Syntax.BinaryExpression; + this.operator = operator; + this.left = left; + this.right = right; + this.finish(); + return this; + }, + + finishCallExpression: function (callee, args) { + this.type = Syntax.CallExpression; + this.callee = callee; + this.arguments = args; + this.finish(); + return this; + }, + + finishConditionalExpression: function (test, consequent, alternate) { + this.type = Syntax.ConditionalExpression; + this.test = test; + this.consequent = consequent; + this.alternate = alternate; + this.finish(); + return this; + }, + + finishExpressionStatement: function (expression) { + this.type = Syntax.ExpressionStatement; + this.expression = expression; + this.finish(); + return this; + }, + + finishIdentifier: function (name) { + this.type = Syntax.Identifier; + this.name = name; + this.finish(); + return this; + }, + + finishLiteral: function (token) { + this.type = Syntax.Literal; + this.value = token.value; + this.raw = source.slice(token.start, token.end); + if (token.regex) { + if (this.raw == '//') { + this.raw = '/(?:)/'; + } + this.regex = token.regex; + } + this.finish(); + return this; + }, + + finishMemberExpression: function (accessor, object, property) { + this.type = Syntax.MemberExpression; + this.computed = accessor === '['; + this.object = object; + this.property = property; + this.finish(); + return this; + }, + + finishObjectExpression: function (properties) { + this.type = Syntax.ObjectExpression; + this.properties = properties; + this.finish(); + return this; + }, + + finishProgram: function (body) { + this.type = Syntax.Program; + this.body = body; + this.finish(); + return this; + }, + + finishProperty: function (kind, key, value) { + this.type = Syntax.Property; + this.key = key; + this.value = value; + this.kind = kind; + this.finish(); + return this; + }, + + finishUnaryExpression: function (operator, argument) { + this.type = Syntax.UnaryExpression; + this.operator = operator; + this.argument = argument; + this.prefix = true; + this.finish(); + return this; + } + }; + + // Return true if there is a line terminator before the next token. + + function peekLineTerminator() { + var pos, line, start, found; + + pos = index; + line = lineNumber; + start = lineStart; + skipComment(); + found = lineNumber !== line; + index = pos; + lineNumber = line; + lineStart = start; + + return found; + } + + // Throw an exception + + function throwError(token, messageFormat) { + var error, + args = Array.prototype.slice.call(arguments, 2), + msg = messageFormat.replace( + /%(\d)/g, + function (whole, index) { + assert(index < args.length, 'Message reference must be in range'); + return args[index]; + } + ); + + if (typeof token.lineNumber === 'number') { + error = new Error('Line ' + token.lineNumber + ': ' + msg); + error.index = token.start; + error.lineNumber = token.lineNumber; + error.column = token.start - lineStart + 1; + } else { + error = new Error('Line ' + lineNumber + ': ' + msg); + error.index = index; + error.lineNumber = lineNumber; + error.column = index - lineStart + 1; + } + + error.description = msg; + throw error; + } + + function throwErrorTolerant() { + try { + throwError.apply(null, arguments); + } catch (e) { + if (extra.errors) { + extra.errors.push(e); + } else { + throw e; + } + } + } + + + // Throw an exception because of the token. + + function throwUnexpected(token) { + if (token.type === Token.EOF) { + throwError(token, Messages.UnexpectedEOS); + } + + if (token.type === Token.NumericLiteral) { + throwError(token, Messages.UnexpectedNumber); + } + + if (token.type === Token.StringLiteral) { + throwError(token, Messages.UnexpectedString); + } + + if (token.type === Token.Identifier) { + throwError(token, Messages.UnexpectedIdentifier); + } + + if (token.type === Token.Keyword) { + if (isFutureReservedWord(token.value)) { + throwError(token, Messages.UnexpectedReserved); + } else if (strict && isStrictModeReservedWord(token.value)) { + throwErrorTolerant(token, Messages.StrictReservedWord); + return; + } + throwError(token, Messages.UnexpectedToken, token.value); + } + + // BooleanLiteral, NullLiteral, or Punctuator. + throwError(token, Messages.UnexpectedToken, token.value); + } + + // Expect the next token to match the specified punctuator. + // If not, an exception will be thrown. + + function expect(value) { + var token = lex(); + if (token.type !== Token.Punctuator || token.value !== value) { + throwUnexpected(token); + } + } + + /** + * @name expectTolerant + * @description Quietly expect the given token value when in tolerant mode, otherwise delegates + * to expect(value) + * @param {String} value The value we are expecting the lookahead token to have + * @since 2.0 + */ + function expectTolerant(value) { + if (extra.errors) { + var token = lookahead; + if (token.type !== Token.Punctuator && token.value !== value) { + throwErrorTolerant(token, Messages.UnexpectedToken, token.value); + } else { + lex(); + } + } else { + expect(value); + } + } + + // Return true if the next token matches the specified punctuator. + + function match(value) { + return lookahead.type === Token.Punctuator && lookahead.value === value; + } + + // Return true if the next token matches the specified keyword + + function matchKeyword(keyword) { + return lookahead.type === Token.Keyword && lookahead.value === keyword; + } + + function consumeSemicolon() { + var line; + + // Catch the very common case first: immediately a semicolon (U+003B). + if (source.charCodeAt(index) === 0x3B || match(';')) { + lex(); + return; + } + + line = lineNumber; + skipComment(); + if (lineNumber !== line) { + return; + } + + if (lookahead.type !== Token.EOF && !match('}')) { + throwUnexpected(lookahead); + } + } + + // 11.1.4 Array Initialiser + + function parseArrayInitialiser() { + var elements = [], node = new Node(); + + expect('['); + + while (!match(']')) { + if (match(',')) { + lex(); + elements.push(null); + } else { + elements.push(parseAssignmentExpression()); + + if (!match(']')) { + expect(','); + } + } + } + + lex(); + + return node.finishArrayExpression(elements); + } + + // 11.1.5 Object Initialiser + + function parseObjectPropertyKey() { + var token, node = new Node(); + + token = lex(); + + // Note: This function is called only from parseObjectProperty(), where + // EOF and Punctuator tokens are already filtered out. + + if (token.type === Token.StringLiteral || token.type === Token.NumericLiteral) { + if (strict && token.octal) { + throwErrorTolerant(token, Messages.StrictOctalLiteral); + } + return node.finishLiteral(token); + } + + return node.finishIdentifier(token.value); + } + + function parseObjectProperty() { + var token, key, id, value, node = new Node(); + + token = lookahead; + + if (token.type === Token.Identifier) { + id = parseObjectPropertyKey(); + expect(':'); + value = parseAssignmentExpression(); + return node.finishProperty('init', id, value); + } + if (token.type === Token.EOF || token.type === Token.Punctuator) { + throwUnexpected(token); + } else { + key = parseObjectPropertyKey(); + expect(':'); + value = parseAssignmentExpression(); + return node.finishProperty('init', key, value); + } + } + + function parseObjectInitialiser() { + var properties = [], property, name, key, kind, map = {}, toString = String, node = new Node(); + + expect('{'); + + while (!match('}')) { + property = parseObjectProperty(); + + if (property.key.type === Syntax.Identifier) { + name = property.key.name; + } else { + name = toString(property.key.value); + } + kind = (property.kind === 'init') ? PropertyKind.Data : (property.kind === 'get') ? PropertyKind.Get : PropertyKind.Set; + + key = '$' + name; + if (Object.prototype.hasOwnProperty.call(map, key)) { + if (map[key] === PropertyKind.Data) { + if (strict && kind === PropertyKind.Data) { + throwErrorTolerant({}, Messages.StrictDuplicateProperty); + } else if (kind !== PropertyKind.Data) { + throwErrorTolerant({}, Messages.AccessorDataProperty); + } + } else { + if (kind === PropertyKind.Data) { + throwErrorTolerant({}, Messages.AccessorDataProperty); + } else if (map[key] & kind) { + throwErrorTolerant({}, Messages.AccessorGetSet); + } + } + map[key] |= kind; + } else { + map[key] = kind; + } + + properties.push(property); + + if (!match('}')) { + expectTolerant(','); + } + } + + expect('}'); + + return node.finishObjectExpression(properties); + } + + // 11.1.6 The Grouping Operator + + function parseGroupExpression() { + var expr; + + expect('('); + + ++state.parenthesisCount; + + expr = parseExpression(); + + expect(')'); + + return expr; + } + + + // 11.1 Primary Expressions + + var legalKeywords = {"if":1, "this":1}; + + function parsePrimaryExpression() { + var type, token, expr, node; + + if (match('(')) { + return parseGroupExpression(); + } + + if (match('[')) { + return parseArrayInitialiser(); + } + + if (match('{')) { + return parseObjectInitialiser(); + } + + type = lookahead.type; + node = new Node(); + + if (type === Token.Identifier || legalKeywords[lookahead.value]) { + expr = node.finishIdentifier(lex().value); + } else if (type === Token.StringLiteral || type === Token.NumericLiteral) { + if (strict && lookahead.octal) { + throwErrorTolerant(lookahead, Messages.StrictOctalLiteral); + } + expr = node.finishLiteral(lex()); + } else if (type === Token.Keyword) { + throw new Error("Disabled."); + } else if (type === Token.BooleanLiteral) { + token = lex(); + token.value = (token.value === 'true'); + expr = node.finishLiteral(token); + } else if (type === Token.NullLiteral) { + token = lex(); + token.value = null; + expr = node.finishLiteral(token); + } else if (match('/') || match('/=')) { + if (typeof extra.tokens !== 'undefined') { + expr = node.finishLiteral(collectRegex()); + } else { + expr = node.finishLiteral(scanRegExp()); + } + peek(); + } else { + throwUnexpected(lex()); + } + + return expr; + } + + // 11.2 Left-Hand-Side Expressions + + function parseArguments() { + var args = []; + + expect('('); + + if (!match(')')) { + while (index < length) { + args.push(parseAssignmentExpression()); + if (match(')')) { + break; + } + expectTolerant(','); + } + } + + expect(')'); + + return args; + } + + function parseNonComputedProperty() { + var token, node = new Node(); + + token = lex(); + + if (!isIdentifierName(token)) { + throwUnexpected(token); + } + + return node.finishIdentifier(token.value); + } + + function parseNonComputedMember() { + expect('.'); + + return parseNonComputedProperty(); + } + + function parseComputedMember() { + var expr; + + expect('['); + + expr = parseExpression(); + + expect(']'); + + return expr; + } + + function parseLeftHandSideExpressionAllowCall() { + var expr, args, property, startToken, previousAllowIn = state.allowIn; + + startToken = lookahead; + state.allowIn = true; + expr = parsePrimaryExpression(); + + for (;;) { + if (match('.')) { + property = parseNonComputedMember(); + expr = new WrappingNode(startToken).finishMemberExpression('.', expr, property); + } else if (match('(')) { + args = parseArguments(); + expr = new WrappingNode(startToken).finishCallExpression(expr, args); + } else if (match('[')) { + property = parseComputedMember(); + expr = new WrappingNode(startToken).finishMemberExpression('[', expr, property); + } else { + break; + } + } + state.allowIn = previousAllowIn; + + return expr; + } + + // 11.3 Postfix Expressions + + function parsePostfixExpression() { + var expr = parseLeftHandSideExpressionAllowCall(); + + if (lookahead.type === Token.Punctuator) { + if ((match('++') || match('--')) && !peekLineTerminator()) { + throw new Error("Disabled."); + } + } + + return expr; + } + + // 11.4 Unary Operators + + function parseUnaryExpression() { + var token, expr, startToken; + + if (lookahead.type !== Token.Punctuator && lookahead.type !== Token.Keyword) { + expr = parsePostfixExpression(); + } else if (match('++') || match('--')) { + throw new Error("Disabled."); + } else if (match('+') || match('-') || match('~') || match('!')) { + startToken = lookahead; + token = lex(); + expr = parseUnaryExpression(); + expr = new WrappingNode(startToken).finishUnaryExpression(token.value, expr); + } else if (matchKeyword('delete') || matchKeyword('void') || matchKeyword('typeof')) { + throw new Error("Disabled."); + } else { + expr = parsePostfixExpression(); + } + + return expr; + } + + function binaryPrecedence(token, allowIn) { + var prec = 0; + + if (token.type !== Token.Punctuator && token.type !== Token.Keyword) { + return 0; + } + + switch (token.value) { + case '||': + prec = 1; + break; + + case '&&': + prec = 2; + break; + + case '|': + prec = 3; + break; + + case '^': + prec = 4; + break; + + case '&': + prec = 5; + break; + + case '==': + case '!=': + case '===': + case '!==': + prec = 6; + break; + + case '<': + case '>': + case '<=': + case '>=': + case 'instanceof': + prec = 7; + break; + + case 'in': + prec = allowIn ? 7 : 0; + break; + + case '<<': + case '>>': + case '>>>': + prec = 8; + break; + + case '+': + case '-': + prec = 9; + break; + + case '*': + case '/': + case '%': + prec = 11; + break; + + default: + break; + } + + return prec; + } + + // 11.5 Multiplicative Operators + // 11.6 Additive Operators + // 11.7 Bitwise Shift Operators + // 11.8 Relational Operators + // 11.9 Equality Operators + // 11.10 Binary Bitwise Operators + // 11.11 Binary Logical Operators + + function parseBinaryExpression() { + var marker, markers, expr, token, prec, stack, right, operator, left, i; + + marker = lookahead; + left = parseUnaryExpression(); + + token = lookahead; + prec = binaryPrecedence(token, state.allowIn); + if (prec === 0) { + return left; + } + token.prec = prec; + lex(); + + markers = [marker, lookahead]; + right = parseUnaryExpression(); + + stack = [left, token, right]; + + while ((prec = binaryPrecedence(lookahead, state.allowIn)) > 0) { + + // Reduce: make a binary expression from the three topmost entries. + while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec)) { + right = stack.pop(); + operator = stack.pop().value; + left = stack.pop(); + markers.pop(); + expr = new WrappingNode(markers[markers.length - 1]).finishBinaryExpression(operator, left, right); + stack.push(expr); + } + + // Shift. + token = lex(); + token.prec = prec; + stack.push(token); + markers.push(lookahead); + expr = parseUnaryExpression(); + stack.push(expr); + } + + // Final reduce to clean-up the stack. + i = stack.length - 1; + expr = stack[i]; + markers.pop(); + while (i > 1) { + expr = new WrappingNode(markers.pop()).finishBinaryExpression(stack[i - 1].value, stack[i - 2], expr); + i -= 2; + } + + return expr; + } + + // 11.12 Conditional Operator + + function parseConditionalExpression() { + var expr, previousAllowIn, consequent, alternate, startToken; + + startToken = lookahead; + + expr = parseBinaryExpression(); + + if (match('?')) { + lex(); + previousAllowIn = state.allowIn; + state.allowIn = true; + consequent = parseAssignmentExpression(); + state.allowIn = previousAllowIn; + expect(':'); + alternate = parseAssignmentExpression(); + + expr = new WrappingNode(startToken).finishConditionalExpression(expr, consequent, alternate); + } + + return expr; + } + + // 11.13 Assignment Operators + + function parseAssignmentExpression() { + var oldParenthesisCount, token, expr, startToken; + + oldParenthesisCount = state.parenthesisCount; + + startToken = lookahead; + token = lookahead; + + expr = parseConditionalExpression(); + + return expr; + } + + // 11.14 Comma Operator + + function parseExpression() { + var expr = parseAssignmentExpression(); + + if (match(',')) { + throw new Error("Disabled."); // no sequence expressions + } + + return expr; + } + + // 12.4 Expression Statement + + function parseExpressionStatement(node) { + var expr = parseExpression(); + consumeSemicolon(); + return node.finishExpressionStatement(expr); + } + + // 12 Statements + + function parseStatement() { + var type = lookahead.type, + expr, + node; + + if (type === Token.EOF) { + throwUnexpected(lookahead); + } + + if (type === Token.Punctuator && lookahead.value === '{') { + throw new Error("Disabled."); // block statement + } + + node = new Node(); + + if (type === Token.Punctuator) { + switch (lookahead.value) { + case ';': + throw new Error("Disabled."); // empty statement + case '(': + return parseExpressionStatement(node); + default: + break; + } + } else if (type === Token.Keyword) { + throw new Error("Disabled."); // keyword + } + + expr = parseExpression(); + consumeSemicolon(); + return node.finishExpressionStatement(expr); + } + + // 14 Program + + function parseSourceElement() { + if (lookahead.type === Token.Keyword) { + switch (lookahead.value) { + case 'const': + case 'let': + throw new Error("Disabled."); + case 'function': + throw new Error("Disabled."); + default: + return parseStatement(); + } + } + + if (lookahead.type !== Token.EOF) { + return parseStatement(); + } + } + + function parseSourceElements() { + var sourceElement, sourceElements = [], token, directive, firstRestricted; + + while (index < length) { + token = lookahead; + if (token.type !== Token.StringLiteral) { + break; + } + + sourceElement = parseSourceElement(); + sourceElements.push(sourceElement); + if (sourceElement.expression.type !== Syntax.Literal) { + // this is not directive + break; + } + directive = source.slice(token.start + 1, token.end - 1); + if (directive === 'use strict') { + strict = true; + if (firstRestricted) { + throwErrorTolerant(firstRestricted, Messages.StrictOctalLiteral); + } + } else { + if (!firstRestricted && token.octal) { + firstRestricted = token; + } + } + } + + while (index < length) { + sourceElement = parseSourceElement(); + if (typeof sourceElement === 'undefined') { + break; + } + sourceElements.push(sourceElement); + } + return sourceElements; + } + + function parseProgram() { + var body, node; + + skipComment(); + peek(); + node = new Node(); + strict = true; // assume strict + + body = parseSourceElements(); + return node.finishProgram(body); + } + + function filterTokenLocation() { + var i, entry, token, tokens = []; + + for (i = 0; i < extra.tokens.length; ++i) { + entry = extra.tokens[i]; + token = { + type: entry.type, + value: entry.value + }; + if (entry.regex) { + token.regex = { + pattern: entry.regex.pattern, + flags: entry.regex.flags + }; + } + if (extra.range) { + token.range = entry.range; + } + if (extra.loc) { + token.loc = entry.loc; + } + tokens.push(token); + } + + extra.tokens = tokens; + } + + function tokenize(code, options) { + var toString, + tokens; + + toString = String; + if (typeof code !== 'string' && !(code instanceof String)) { + code = toString(code); + } + + source = code; + index = 0; + lineNumber = (source.length > 0) ? 1 : 0; + lineStart = 0; + length = source.length; + lookahead = null; + state = { + allowIn: true, + labelSet: {}, + inFunctionBody: false, + inIteration: false, + inSwitch: false, + lastCommentStart: -1 + }; + + extra = {}; + + // Options matching. + options = options || {}; + + // Of course we collect tokens here. + options.tokens = true; + extra.tokens = []; + extra.tokenize = true; + // The following two fields are necessary to compute the Regex tokens. + extra.openParenToken = -1; + extra.openCurlyToken = -1; + + extra.range = (typeof options.range === 'boolean') && options.range; + extra.loc = (typeof options.loc === 'boolean') && options.loc; + + if (typeof options.tolerant === 'boolean' && options.tolerant) { + extra.errors = []; + } + + try { + peek(); + if (lookahead.type === Token.EOF) { + return extra.tokens; + } + + lex(); + while (lookahead.type !== Token.EOF) { + try { + lex(); + } catch (lexError) { + if (extra.errors) { + extra.errors.push(lexError); + // We have to break on the first error + // to avoid infinite loops. + break; + } else { + throw lexError; + } + } + } + + filterTokenLocation(); + tokens = extra.tokens; + if (typeof extra.errors !== 'undefined') { + tokens.errors = extra.errors; + } + } catch (e) { + throw e; + } finally { + extra = {}; + } + return tokens; + } + + function parse(code, options) { + var program, toString; + + toString = String; + if (typeof code !== 'string' && !(code instanceof String)) { + code = toString(code); + } + + source = code; + index = 0; + lineNumber = (source.length > 0) ? 1 : 0; + lineStart = 0; + length = source.length; + lookahead = null; + state = { + allowIn: true, + labelSet: {}, + parenthesisCount: 0, + inFunctionBody: false, + inIteration: false, + inSwitch: false, + lastCommentStart: -1 + }; + + extra = {}; + if (typeof options !== 'undefined') { + extra.range = (typeof options.range === 'boolean') && options.range; + extra.loc = (typeof options.loc === 'boolean') && options.loc; + + if (extra.loc && options.source !== null && options.source !== undefined) { + extra.source = toString(options.source); + } + + if (typeof options.tokens === 'boolean' && options.tokens) { + extra.tokens = []; + } + if (typeof options.tolerant === 'boolean' && options.tolerant) { + extra.errors = []; + } + } + + try { + program = parseProgram(); + if (typeof extra.tokens !== 'undefined') { + filterTokenLocation(); + program.tokens = extra.tokens; + } + if (typeof extra.errors !== 'undefined') { + program.errors = extra.errors; + } + } catch (e) { + throw e; + } finally { + extra = {}; + } + + return program; + } + + return { + tokenize: tokenize, + parse: parse + }; + +})(); +},{}],47:[function(require,module,exports){ +var ts = Date.now(); + +function write(msg) { + msg = '[Vega Log] ' + msg; + console.log(msg); +} + +function error(msg) { + msg = '[Vega Err] ' + msg; + console.error(msg); +} + +function debug(input, args) { + if (!debug.enable) return; + var log = Function.prototype.bind.call(console.log, console); + var state = { + prevTime: Date.now() - ts, + stamp: input.stamp + }; + + if (input.add) { + state.add = input.add.length; + state.mod = input.mod.length; + state.rem = input.rem.length; + state.reflow = !!input.reflow; + } + + log.apply(console, (args.push(JSON.stringify(state)), args)); + ts = Date.now(); +} + +module.exports = { + log: write, + error: error, + debug: (debug.enable = false, debug) +}; + +},{}],48:[function(require,module,exports){ +module.exports = { + path: require('./path'), + render: require('./render'), + Item: require('./util/Item'), + bound: require('./util/bound'), + Bounds: require('./util/Bounds'), + canvas: require('./util/canvas'), + Gradient: require('./util/Gradient'), + toJSON: require('./util/scene').toJSON, + fromJSON: require('./util/scene').fromJSON +}; +},{"./path":50,"./render":70,"./util/Bounds":76,"./util/Gradient":78,"./util/Item":80,"./util/bound":81,"./util/canvas":82,"./util/scene":84}],49:[function(require,module,exports){ +var segmentCache = {}, + bezierCache = {}, + join = [].join; + +// Copied from Inkscape svgtopdf, thanks! +function segments(x, y, rx, ry, large, sweep, rotateX, ox, oy) { + var key = join.call(arguments); + if (segmentCache[key]) { + return segmentCache[key]; + } + + var th = rotateX * (Math.PI/180); + var sin_th = Math.sin(th); + var cos_th = Math.cos(th); + rx = Math.abs(rx); + ry = Math.abs(ry); + var px = cos_th * (ox - x) * 0.5 + sin_th * (oy - y) * 0.5; + var py = cos_th * (oy - y) * 0.5 - sin_th * (ox - x) * 0.5; + var pl = (px*px) / (rx*rx) + (py*py) / (ry*ry); + if (pl > 1) { + pl = Math.sqrt(pl); + rx *= pl; + ry *= pl; + } + + var a00 = cos_th / rx; + var a01 = sin_th / rx; + var a10 = (-sin_th) / ry; + var a11 = (cos_th) / ry; + var x0 = a00 * ox + a01 * oy; + var y0 = a10 * ox + a11 * oy; + var x1 = a00 * x + a01 * y; + var y1 = a10 * x + a11 * y; + + var d = (x1-x0) * (x1-x0) + (y1-y0) * (y1-y0); + var sfactor_sq = 1 / d - 0.25; + if (sfactor_sq < 0) sfactor_sq = 0; + var sfactor = Math.sqrt(sfactor_sq); + if (sweep == large) sfactor = -sfactor; + var xc = 0.5 * (x0 + x1) - sfactor * (y1-y0); + var yc = 0.5 * (y0 + y1) + sfactor * (x1-x0); + + var th0 = Math.atan2(y0-yc, x0-xc); + var th1 = Math.atan2(y1-yc, x1-xc); + + var th_arc = th1-th0; + if (th_arc < 0 && sweep === 1){ + th_arc += 2 * Math.PI; + } else if (th_arc > 0 && sweep === 0) { + th_arc -= 2 * Math.PI; + } + + var segs = Math.ceil(Math.abs(th_arc / (Math.PI * 0.5 + 0.001))); + var result = []; + for (var i=0; i len) { + for (j=1, m=parsed.length; j=0;) { + if (h[i].type !== type) continue; + if (!handler || h[i].handler === handler) h.splice(i, 1); + } + return this; +}; + +prototype.pickEvent = function(evt) { + var rect = this._canvas.getBoundingClientRect(), + pad = this._padding, x, y; + return this.pick(this._scene, + x = (evt.clientX - rect.left), + y = (evt.clientY - rect.top), + x - pad.left, y - pad.top); +}; + +// find the scenegraph item at the current mouse position +// x, y -- the absolute x, y mouse coordinates on the canvas element +// gx, gy -- the relative coordinates within the current group +prototype.pick = function(scene, x, y, gx, gy) { + var g = this.context(), + mark = marks[scene.marktype]; + return mark.pick.call(this, g, scene, x, y, gx, gy); +}; + +module.exports = CanvasHandler; + +},{"../../util/dom":83,"../Handler":53,"./marks":62}],56:[function(require,module,exports){ +var DOM = require('../../util/dom'), + Bounds = require('../../util/Bounds'), + ImageLoader = require('../../util/ImageLoader'), + Canvas = require('../../util/canvas'), + Renderer = require('../Renderer'), + marks = require('./marks'); + +function CanvasRenderer(loadConfig) { + Renderer.call(this); + this._loader = new ImageLoader(loadConfig); +} + +CanvasRenderer.RETINA = true; + +var base = Renderer.prototype; +var prototype = (CanvasRenderer.prototype = Object.create(base)); +prototype.constructor = CanvasRenderer; + +prototype.initialize = function(el, width, height, padding) { + this._canvas = Canvas.instance(width, height); + if (el) { + DOM.clear(el, 0).appendChild(this._canvas); + this._canvas.setAttribute('class', 'marks'); + } + return base.initialize.call(this, el, width, height, padding); +}; + +prototype.resize = function(width, height, padding) { + base.resize.call(this, width, height, padding); + Canvas.resize(this._canvas, this._width, this._height, + this._padding, CanvasRenderer.RETINA); + return this; +}; + +prototype.canvas = function() { + return this._canvas; +}; + +prototype.context = function() { + return this._canvas ? this._canvas.getContext('2d') : null; +}; + +prototype.pendingImages = function() { + return this._loader.pending(); +}; + +function clipToBounds(g, items) { + if (!items) return null; + + var b = new Bounds(), i, n, item, mark, group; + for (i=0, n=items.length; i 0) { + if (group.fill && util.fill(g, group, opac)) { + g.fillRect(gx, gy, w, h); + } + if (group.stroke && util.stroke(g, group, opac)) { + g.strokeRect(gx, gy, w, h); + } + } + } + + // setup graphics context + g.save(); + g.translate(gx, gy); + if (group.clip) { + g.beginPath(); + g.rect(0, 0, w, h); + g.clip(); + } + if (bounds) bounds.translate(-gx, -gy); + + // draw group contents + for (j=0, m=axes.length; j=0;) { + group = groups[i]; + + // first hit test against bounding box + // if a group is clipped, that should be handled by the bounds check. + b = group.bounds; + if (b && !b.contains(gx, gy)) continue; + + // passed bounds check, so test sub-groups + axes = group.axisItems || EMPTY; + items = group.items || EMPTY; + legends = group.legendItems || EMPTY; + dx = (group.x || 0); + dy = (group.y || 0); + + g.save(); + g.translate(dx, dy); + dx = gx - dx; + dy = gy - dy; + for (j=legends.length; --j>=0;) { + subscene = legends[j]; + if (subscene.interactive !== false) { + hits = this.pick(subscene, x, y, dx, dy); + if (hits) { g.restore(); return hits; } + } + } + for (j=axes.length; --j>=0;) { + subscene = axes[j]; + if (subscene.interactive !== false && subscene.layer !== 'back') { + hits = this.pick(subscene, x, y, dx, dy); + if (hits) { g.restore(); return hits; } + } + } + for (j=items.length; --j>=0;) { + subscene = items[j]; + if (subscene.interactive !== false) { + hits = this.pick(subscene, x, y, dx, dy); + if (hits) { g.restore(); return hits; } + } + } + for (j=axes.length; --j>=0;) { + subscene = axes[j]; + if (subscene.interative !== false && subscene.layer === 'back') { + hits = this.pick(subscene, x, y, dx, dy); + if (hits) { g.restore(); return hits; } + } + } + g.restore(); + + if (scene.interactive !== false && (group.fill || group.stroke) && + dx >= 0 && dx <= group.width && dy >= 0 && dy <= group.height) { + return group; + } + } + + return null; +} + +module.exports = { + draw: draw, + pick: pick +}; + +},{"./util":69}],61:[function(require,module,exports){ +var util = require('./util'); + +function draw(g, scene, bounds) { + if (!scene.items || !scene.items.length) return; + + var renderer = this, + items = scene.items, o; + + for (var i=0, len=items.length; i= 0;) { + o = scene.items[i]; b = o.bounds; + // first hit test against bounding box + if ((b && !b.contains(gx, gy)) || !b) continue; + // if in bounding box, perform more careful test + if (test(g, o, x, y, gx, gy)) return o; + } + return null; + }; +} + +function testPath(path, filled) { + return function(g, o, x, y) { + var item = Array.isArray(o) ? o[0] : o, + fill = (filled == null) ? item.fill : filled, + stroke = item.stroke && g.isPointInStroke, lw, lc; + + if (stroke) { + lw = item.strokeWidth; + lc = item.strokeCap; + g.lineWidth = lw != null ? lw : 1; + g.lineCap = lc != null ? lc : 'butt'; + } + + return path(g, o) ? false : + (fill && g.isPointInPath(x, y)) || + (stroke && g.isPointInStroke(x, y)); + }; +} + +function pickPath(path) { + return pick(testPath(path)); +} + +function fill(g, o, opacity) { + opacity *= (o.fillOpacity==null ? 1 : o.fillOpacity); + if (opacity > 0) { + g.globalAlpha = opacity; + g.fillStyle = color(g, o, o.fill); + return true; + } else { + return false; + } +} + +function stroke(g, o, opacity) { + var lw = (lw = o.strokeWidth) != null ? lw : 1, lc; + if (lw <= 0) return false; + + opacity *= (o.strokeOpacity==null ? 1 : o.strokeOpacity); + if (opacity > 0) { + g.globalAlpha = opacity; + g.strokeStyle = color(g, o, o.stroke); + g.lineWidth = lw; + g.lineCap = (lc = o.strokeCap) != null ? lc : 'butt'; + g.vgLineDash(o.strokeDash || null); + g.vgLineDashOffset(o.strokeDashOffset || 0); + return true; + } else { + return false; + } +} + +function color(g, o, value) { + return (value.id) ? + gradient(g, value, o.bounds) : + value; +} + +function gradient(g, p, b) { + var w = b.width(), + h = b.height(), + x1 = b.x1 + p.x1 * w, + y1 = b.y1 + p.y1 * h, + x2 = b.x1 + p.x2 * w, + y2 = b.y1 + p.y2 * h, + grad = g.createLinearGradient(x1, y1, x2, y2), + stop = p.stops, + i, n; + + for (i=0, n=stop.length; i=0;) { + if (h[i].type === type && !handler || h[i].handler === handler) { + svg.removeEventListener(name, h[i].listener); + h.splice(i, 1); + } + } + return this; +}; + +module.exports = SVGHandler; + +},{"../../util/dom":83,"../Handler":53}],72:[function(require,module,exports){ +var ImageLoader = require('../../util/ImageLoader'), + Renderer = require('../Renderer'), + text = require('../../util/text'), + DOM = require('../../util/dom'), + SVG = require('../../util/svg'), + ns = SVG.metadata.xmlns, + marks = require('./marks'); + +function SVGRenderer(loadConfig) { + Renderer.call(this); + this._loader = new ImageLoader(loadConfig); + this._dirtyID = 0; +} + +var base = Renderer.prototype; +var prototype = (SVGRenderer.prototype = Object.create(base)); +prototype.constructor = SVGRenderer; + +prototype.initialize = function(el, width, height, padding) { + if (el) { + this._svg = DOM.child(el, 0, 'svg', ns, 'marks'); + DOM.clear(el, 1); + // set the svg root group + this._root = DOM.child(this._svg, 0, 'g', ns); + DOM.clear(this._svg, 1); + } + + // create the svg definitions cache + this._defs = { + clip_id: 1, + gradient: {}, + clipping: {} + }; + + // set background color if defined + this.background(this._bgcolor); + + return base.initialize.call(this, el, width, height, padding); +}; + +prototype.background = function(bgcolor) { + if (arguments.length && this._svg) { + this._svg.style.setProperty('background-color', bgcolor); + } + return base.background.apply(this, arguments); +}; + +prototype.resize = function(width, height, padding) { + base.resize.call(this, width, height, padding); + + if (this._svg) { + var w = this._width, + h = this._height, + p = this._padding; + + this._svg.setAttribute('width', w + p.left + p.right); + this._svg.setAttribute('height', h + p.top + p.bottom); + + this._root.setAttribute('transform', 'translate('+p.left+','+p.top+')'); + } + + return this; +}; + +prototype.svg = function() { + if (!this._svg) return null; + + var attr = { + 'class': 'marks', + 'width': this._width + this._padding.left + this._padding.right, + 'height': this._height + this._padding.top + this._padding.bottom, + }; + for (var key in SVG.metadata) { + attr[key] = SVG.metadata[key]; + } + + return DOM.openTag('svg', attr) + this._svg.innerHTML + DOM.closeTag('svg'); +}; + +prototype.imageURL = function(url) { + return this._loader.imageURL(url); +}; + + +// -- Render entry point -- + +prototype.render = function(scene, items) { + if (this._dirtyCheck(items)) { + if (this._dirtyAll) this._resetDefs(); + this.draw(this._root, scene, -1); + DOM.clear(this._root, 1); + } + this.updateDefs(); + return this; +}; + +prototype.draw = function(el, scene, index) { + this.drawMark(el, scene, index, marks[scene.marktype]); +}; + + +// -- Manage SVG definitions ('defs') block -- + +prototype.updateDefs = function() { + var svg = this._svg, + defs = this._defs, + el = defs.el, + index = 0, id; + + for (id in defs.gradient) { + if (!el) el = (defs.el = DOM.child(svg, 0, 'defs', ns)); + updateGradient(el, defs.gradient[id], index++); + } + + for (id in defs.clipping) { + if (!el) el = (defs.el = DOM.child(svg, 0, 'defs', ns)); + updateClipping(el, defs.clipping[id], index++); + } + + // clean-up + if (el) { + if (index === 0) { + svg.removeChild(el); + defs.el = null; + } else { + DOM.clear(el, index); + } + } +}; + +function updateGradient(el, grad, index) { + var i, n, stop; + + el = DOM.child(el, index, 'linearGradient', ns); + el.setAttribute('id', grad.id); + el.setAttribute('x1', grad.x1); + el.setAttribute('x2', grad.x2); + el.setAttribute('y1', grad.y1); + el.setAttribute('y2', grad.y2); + + for (i=0, n=grad.stops.length; i 0) ? openTag('defs') + defs + closeTag('defs') : ''; +}; + +prototype.imageURL = function(url) { + return this._loader.imageURL(url); +}; + +var object; + +function emit(name, value, ns, prefixed) { + object[prefixed || name] = value; +} + +prototype.attributes = function(attr, item) { + object = {}; + attr(emit, item, this); + return object; +}; + +prototype.mark = function(scene) { + var mdef = MARKS[scene.marktype], + tag = mdef.tag, + attr = mdef.attr, + nest = mdef.nest || false, + data = nest ? + (scene.items && scene.items.length ? [scene.items[0]] : []) : + (scene.items || []), + defs = this._defs, + str = '', + style, i, item; + + if (tag !== 'g' && scene.interactive === false) { + style = 'style="pointer-events: none;"'; + } + + // render opening group tag + str += openTag('g', { + 'class': DOM.cssClass(scene) + }, style); + + // render contained elements + for (i=0; i/g, '>'); +} + +module.exports = SVGStringRenderer; + +},{"../../util/ImageLoader":79,"../../util/dom":83,"../../util/svg":85,"../../util/text":86,"../Renderer":54,"./marks":75}],74:[function(require,module,exports){ +module.exports = { + Handler: require('./SVGHandler'), + Renderer: require('./SVGRenderer'), + string: { + Renderer : require('./SVGStringRenderer') + } +}; +},{"./SVGHandler":71,"./SVGRenderer":72,"./SVGStringRenderer":73}],75:[function(require,module,exports){ +var text = require('../../util/text'), + SVG = require('../../util/svg'), + textAlign = SVG.textAlign, + path = SVG.path; + +function translateItem(o) { + return translate(o.x || 0, o.y || 0); +} + +function translate(x, y) { + return 'translate(' + x + ',' + y + ')'; +} + +module.exports = { + arc: { + tag: 'path', + type: 'arc', + attr: function(emit, o) { + emit('transform', translateItem(o)); + emit('d', path.arc(o)); + } + }, + area: { + tag: 'path', + type: 'area', + nest: true, + attr: function(emit, o) { + var items = o.mark.items; + if (items.length) emit('d', path.area(items)); + } + }, + group: { + tag: 'g', + type: 'group', + attr: function(emit, o, renderer) { + var id = null, defs, c; + emit('transform', translateItem(o)); + if (o.clip) { + defs = renderer._defs; + id = o.clip_id || (o.clip_id = 'clip' + defs.clip_id++); + c = defs.clipping[id] || (defs.clipping[id] = {id: id}); + c.width = o.width || 0; + c.height = o.height || 0; + } + emit('clip-path', id ? ('url(#' + id + ')') : null); + }, + background: function(emit, o) { + emit('class', 'background'); + emit('width', o.width || 0); + emit('height', o.height || 0); + } + }, + image: { + tag: 'image', + type: 'image', + attr: function(emit, o, renderer) { + var x = o.x || 0, + y = o.y || 0, + w = o.width || 0, + h = o.height || 0, + url = renderer.imageURL(o.url); + + x = x - (o.align === 'center' ? w/2 : o.align === 'right' ? w : 0); + y = y - (o.baseline === 'middle' ? h/2 : o.baseline === 'bottom' ? h : 0); + + emit('href', url, 'http://www.w3.org/1999/xlink', 'xlink:href'); + emit('transform', translate(x, y)); + emit('width', w); + emit('height', h); + } + }, + line: { + tag: 'path', + type: 'line', + nest: true, + attr: function(emit, o) { + var items = o.mark.items; + if (items.length) emit('d', path.line(items)); + } + }, + path: { + tag: 'path', + type: 'path', + attr: function(emit, o) { + emit('transform', translateItem(o)); + emit('d', o.path); + } + }, + rect: { + tag: 'rect', + type: 'rect', + nest: false, + attr: function(emit, o) { + emit('transform', translateItem(o)); + emit('width', o.width || 0); + emit('height', o.height || 0); + } + }, + rule: { + tag: 'line', + type: 'rule', + attr: function(emit, o) { + emit('transform', translateItem(o)); + emit('x2', o.x2 != null ? o.x2 - (o.x||0) : 0); + emit('y2', o.y2 != null ? o.y2 - (o.y||0) : 0); + } + }, + symbol: { + tag: 'path', + type: 'symbol', + attr: function(emit, o) { + emit('transform', translateItem(o)); + emit('d', path.symbol(o)); + } + }, + text: { + tag: 'text', + type: 'text', + nest: false, + attr: function(emit, o) { + var dx = (o.dx || 0), + dy = (o.dy || 0) + text.offset(o), + x = (o.x || 0), + y = (o.y || 0), + a = o.angle || 0, + r = o.radius || 0, t; + + if (r) { + t = (o.theta || 0) - Math.PI/2; + x += r * Math.cos(t); + y += r * Math.sin(t); + } + + emit('text-anchor', textAlign[o.align] || 'start'); + + if (a) { + t = translate(x, y) + ' rotate('+a+')'; + if (dx || dy) t += ' ' + translate(dx, dy); + } else { + t = translate(x+dx, y+dy); + } + emit('transform', t); + } + } +}; + +},{"../../util/svg":85,"../../util/text":86}],76:[function(require,module,exports){ +function Bounds(b) { + this.clear(); + if (b) this.union(b); +} + +var prototype = Bounds.prototype; + +prototype.clone = function() { + return new Bounds(this); +}; + +prototype.clear = function() { + this.x1 = +Number.MAX_VALUE; + this.y1 = +Number.MAX_VALUE; + this.x2 = -Number.MAX_VALUE; + this.y2 = -Number.MAX_VALUE; + return this; +}; + +prototype.set = function(x1, y1, x2, y2) { + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + return this; +}; + +prototype.add = function(x, y) { + if (x < this.x1) this.x1 = x; + if (y < this.y1) this.y1 = y; + if (x > this.x2) this.x2 = x; + if (y > this.y2) this.y2 = y; + return this; +}; + +prototype.expand = function(d) { + this.x1 -= d; + this.y1 -= d; + this.x2 += d; + this.y2 += d; + return this; +}; + +prototype.round = function() { + this.x1 = Math.floor(this.x1); + this.y1 = Math.floor(this.y1); + this.x2 = Math.ceil(this.x2); + this.y2 = Math.ceil(this.y2); + return this; +}; + +prototype.translate = function(dx, dy) { + this.x1 += dx; + this.x2 += dx; + this.y1 += dy; + this.y2 += dy; + return this; +}; + +prototype.rotate = function(angle, x, y) { + var cos = Math.cos(angle), + sin = Math.sin(angle), + cx = x - x*cos + y*sin, + cy = y - x*sin - y*cos, + x1 = this.x1, x2 = this.x2, + y1 = this.y1, y2 = this.y2; + + return this.clear() + .add(cos*x1 - sin*y1 + cx, sin*x1 + cos*y1 + cy) + .add(cos*x1 - sin*y2 + cx, sin*x1 + cos*y2 + cy) + .add(cos*x2 - sin*y1 + cx, sin*x2 + cos*y1 + cy) + .add(cos*x2 - sin*y2 + cx, sin*x2 + cos*y2 + cy); +}; + +prototype.union = function(b) { + if (b.x1 < this.x1) this.x1 = b.x1; + if (b.y1 < this.y1) this.y1 = b.y1; + if (b.x2 > this.x2) this.x2 = b.x2; + if (b.y2 > this.y2) this.y2 = b.y2; + return this; +}; + +prototype.encloses = function(b) { + return b && ( + this.x1 <= b.x1 && + this.x2 >= b.x2 && + this.y1 <= b.y1 && + this.y2 >= b.y2 + ); +}; + +prototype.intersects = function(b) { + return b && !( + this.x2 < b.x1 || + this.x1 > b.x2 || + this.y2 < b.y1 || + this.y1 > b.y2 + ); +}; + +prototype.contains = function(x, y) { + return !( + x < this.x1 || + x > this.x2 || + y < this.y1 || + y > this.y2 + ); +}; + +prototype.width = function() { + return this.x2 - this.x1; +}; + +prototype.height = function() { + return this.y2 - this.y1; +}; + +module.exports = Bounds; + +},{}],77:[function(require,module,exports){ +module.exports = function(b) { + function noop() { } + function add(x,y) { b.add(x, y); } + + return { + bounds: function(_) { + if (!arguments.length) return b; + return (b = _, this); + }, + beginPath: noop, + closePath: noop, + moveTo: add, + lineTo: add, + quadraticCurveTo: function(x1, y1, x2, y2) { + b.add(x1, y1); + b.add(x2, y2); + }, + bezierCurveTo: function(x1, y1, x2, y2, x3, y3) { + b.add(x1, y1); + b.add(x2, y2); + b.add(x3, y3); + } + }; +}; + +},{}],78:[function(require,module,exports){ +var gradient_id = 0; + +function Gradient(type) { + this.id = 'gradient_' + (gradient_id++); + this.type = type || 'linear'; + this.stops = []; + this.x1 = 0; + this.x2 = 1; + this.y1 = 0; + this.y2 = 0; +} + +var prototype = Gradient.prototype; + +prototype.stop = function(offset, color) { + this.stops.push({ + offset: offset, + color: color + }); + return this; +}; + +module.exports = Gradient; +},{}],79:[function(require,module,exports){ +(function (global){ +var load = require('datalib/src/import/load'); + +function ImageLoader(loadConfig) { + this._pending = 0; + this._config = loadConfig || ImageLoader.Config; +} + +// Overridable global default load configuration +ImageLoader.Config = null; + +var prototype = ImageLoader.prototype; + +prototype.pending = function() { + return this._pending; +}; + +prototype.params = function(uri) { + var p = {url: uri}, k; + for (k in this._config) { p[k] = this._config[k]; } + return p; +}; + +prototype.imageURL = function(uri) { + return load.sanitizeUrl(this.params(uri)); +}; + +function browser(uri, callback) { + var url = load.sanitizeUrl(this.params(uri)); + if (!url) { // error + if (callback) callback(uri, null); + return null; + } + + var loader = this, + image = new Image(); + + loader._pending += 1; + + image.onload = function() { + loader._pending -= 1; + image.loaded = true; + if (callback) callback(null, image); + }; + image.src = url; + + return image; +} + +function server(uri, callback) { + var loader = this, + image = new ((typeof window !== "undefined" ? window['canvas'] : typeof global !== "undefined" ? global['canvas'] : null).Image)(); + + loader._pending += 1; + + load(this.params(uri), function(err, data) { + loader._pending -= 1; + if (err) { + if (callback) callback(err, null); + return null; + } + image.src = data; + image.loaded = true; + if (callback) callback(null, image); + }); + + return image; +} + +prototype.loadImage = function(uri, callback) { + return load.useXHR ? + browser.call(this, uri, callback) : + server.call(this, uri, callback); +}; + +module.exports = ImageLoader; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"datalib/src/import/load":22}],80:[function(require,module,exports){ +function Item(mark) { + this.mark = mark; +} + +var prototype = Item.prototype; + +prototype.hasPropertySet = function(name) { + var props = this.mark.def.properties; + return props && props[name] != null; +}; + +prototype.cousin = function(offset, index) { + if (offset === 0) return this; + offset = offset || -1; + var mark = this.mark, + group = mark.group, + iidx = index==null ? mark.items.indexOf(this) : index, + midx = group.items.indexOf(mark) + offset; + return group.items[midx].items[iidx]; +}; + +prototype.sibling = function(offset) { + if (offset === 0) return this; + offset = offset || -1; + var mark = this.mark, + iidx = mark.items.indexOf(this) + offset; + return mark.items[iidx]; +}; + +prototype.remove = function() { + var item = this, + list = item.mark.items, + i = list.indexOf(item); + if (i >= 0) { + if (i===list.length-1) { + list.pop(); + } else { + list.splice(i, 1); + } + } + return item; +}; + +prototype.touch = function() { + if (this.pathCache) this.pathCache = null; +}; + +module.exports = Item; +},{}],81:[function(require,module,exports){ +var BoundsContext = require('./BoundsContext'), + Bounds = require('./Bounds'), + canvas = require('./canvas'), + svg = require('./svg'), + text = require('./text'), + paths = require('../path'), + parse = paths.parse, + drawPath = paths.render, + areaPath = svg.path.area, + linePath = svg.path.line, + halfpi = Math.PI / 2, + sqrt3 = Math.sqrt(3), + tan30 = Math.tan(30 * Math.PI / 180), + g2D = null, + bc = BoundsContext(); + +function context() { + return g2D || (g2D = canvas.instance(1,1).getContext('2d')); +} + +function strokeBounds(o, bounds) { + if (o.stroke && o.opacity !== 0 && o.stokeOpacity !== 0) { + bounds.expand(o.strokeWidth != null ? o.strokeWidth : 1); + } + return bounds; +} + +function pathBounds(o, path, bounds, x, y) { + if (path == null) { + bounds.set(0, 0, 0, 0); + } else { + drawPath(bc.bounds(bounds), path, x, y); + strokeBounds(o, bounds); + } + return bounds; +} + +function path(o, bounds) { + var p = o.path ? o.pathCache || (o.pathCache = parse(o.path)) : null; + return pathBounds(o, p, bounds, o.x, o.y); +} + +function area(mark, bounds) { + if (mark.items.length === 0) return bounds; + var items = mark.items, + item = items[0], + p = item.pathCache || (item.pathCache = parse(areaPath(items))); + return pathBounds(item, p, bounds); +} + +function line(mark, bounds) { + if (mark.items.length === 0) return bounds; + var items = mark.items, + item = items[0], + p = item.pathCache || (item.pathCache = parse(linePath(items))); + return pathBounds(item, p, bounds); +} + +function rect(o, bounds) { + var x, y; + return strokeBounds(o, bounds.set( + x = o.x || 0, + y = o.y || 0, + (x + o.width) || 0, + (y + o.height) || 0 + )); +} + +function image(o, bounds) { + var x = o.x || 0, + y = o.y || 0, + w = o.width || 0, + h = o.height || 0; + x = x - (o.align === 'center' ? w/2 : (o.align === 'right' ? w : 0)); + y = y - (o.baseline === 'middle' ? h/2 : (o.baseline === 'bottom' ? h : 0)); + return bounds.set(x, y, x+w, y+h); +} + +function rule(o, bounds) { + var x1, y1; + return strokeBounds(o, bounds.set( + x1 = o.x || 0, + y1 = o.y || 0, + o.x2 != null ? o.x2 : x1, + o.y2 != null ? o.y2 : y1 + )); +} + +function arc(o, bounds) { + var cx = o.x || 0, + cy = o.y || 0, + ir = o.innerRadius || 0, + or = o.outerRadius || 0, + sa = (o.startAngle || 0) - halfpi, + ea = (o.endAngle || 0) - halfpi, + xmin = Infinity, xmax = -Infinity, + ymin = Infinity, ymax = -Infinity, + a, i, n, x, y, ix, iy, ox, oy; + + var angles = [sa, ea], + s = sa - (sa % halfpi); + for (i=0; i<4 && s index) { + el.removeChild(el.childNodes[--curr]); + } + return el; + }, + remove: remove, + // generate css class name for mark + cssClass: function(mark) { + return 'mark-' + mark.marktype + (mark.name ? ' '+mark.name : ''); + }, + // generate string for an opening xml tag + // tag: the name of the xml tag + // attr: hash of attribute name-value pairs to include + // raw: additional raw string to include in tag markup + openTag: function(tag, attr, raw) { + var s = '<' + tag, key, val; + if (attr) { + for (key in attr) { + val = attr[key]; + if (val != null) { + s += ' ' + key + '="' + val + '"'; + } + } + } + if (raw) s += ' ' + raw; + return s + '>'; + }, + // generate string for closing xml tag + // tag: the name of the xml tag + closeTag: function(tag) { + return ''; + } +}; + +},{}],84:[function(require,module,exports){ +var bound = require('../util/bound'); + +var sets = [ + 'items', + 'axisItems', + 'legendItems' +]; + +var keys = [ + 'marktype', 'name', 'interactive', 'clip', + 'items', 'axisItems', 'legendItems', 'layer', + 'x', 'y', 'width', 'height', 'align', 'baseline', // layout + 'fill', 'fillOpacity', 'opacity', // fill + 'stroke', 'strokeOpacity', 'strokeWidth', 'strokeCap', // stroke + 'strokeDash', 'strokeDashOffset', // stroke dash + 'startAngle', 'endAngle', 'innerRadius', 'outerRadius', // arc + 'interpolate', 'tension', 'orient', // area, line + 'url', // image + 'path', // path + 'x2', 'y2', // rule + 'size', 'shape', // symbol + 'text', 'angle', 'theta', 'radius', 'dx', 'dy', // text + 'font', 'fontSize', 'fontWeight', 'fontStyle', 'fontVariant' // font +]; + +function toJSON(scene, indent) { + return JSON.stringify(scene, keys, indent); +} + +function fromJSON(json) { + var scene = (typeof json === 'string' ? JSON.parse(json) : json); + return initialize(scene); +} + +function initialize(scene) { + var type = scene.marktype, + i, n, s, m, items; + + for (s=0, m=sets.length; s 0) { wait(); } else { callback(this.canvas()); } +}; + +prototype.svg = function() { + return (this._type === 'svg') ? this._renderer.svg() : null; +}; + +prototype.initialize = function() { + var w = this._width, + h = this._height, + bg = this._bgcolor, + pad = this._padding, + config = this.model().config(); + + if (this._viewport) { + w = this._viewport[0] - (pad ? pad.left + pad.right : 0); + h = this._viewport[1] - (pad ? pad.top + pad.bottom : 0); + } + + this._renderer = (this._renderer || new this._io.Renderer(config.load)) + .initialize(null, w, h, pad) + .background(bg); + + return this; +}; + +module.exports = HeadlessView; +},{"./View":89,"vega-scenegraph":48}],88:[function(require,module,exports){ +var dl = require('datalib'), + df = require('vega-dataflow'), + ChangeSet = df.ChangeSet, + Base = df.Graph.prototype, + Node = df.Node, // jshint ignore:line + GroupBuilder = require('../scene/GroupBuilder'), + visit = require('../scene/visit'), + config = require('./config'); + +function Model(cfg) { + this._defs = {}; + this._predicates = {}; + this._scene = null; + + this._node = null; + this._builder = null; // Top-level scenegraph builder + + this._reset = {axes: false, legends: false}; + + this.config(cfg); + Base.init.call(this); +} + +var prototype = (Model.prototype = Object.create(Base)); +prototype.constructor = Model; + +prototype.defs = function(defs) { + if (!arguments.length) return this._defs; + this._defs = defs; + return this; +}; + +prototype.config = function(cfg) { + if (!arguments.length) return this._config; + this._config = Object.create(config); + for (var name in cfg) { + var x = cfg[name], y = this._config[name]; + if (dl.isObject(x) && dl.isObject(y)) { + dl.extend(y, x); + } else { + this._config[name] = x; + } + } + + return this; +}; + +prototype.width = function(width) { + if (this._defs) this._defs.width = width; + if (this._defs && this._defs.marks) this._defs.marks.width = width; + if (this._scene) { + this._scene.items[0].width = width; + this._scene.items[0]._dirty = true; + } + this._reset.axes = true; + return this; +}; + +prototype.height = function(height) { + if (this._defs) this._defs.height = height; + if (this._defs && this._defs.marks) this._defs.marks.height = height; + if (this._scene) { + this._scene.items[0].height = height; + this._scene.items[0]._dirty = true; + } + this._reset.axes = true; + return this; +}; + +prototype.node = function() { + return this._node || (this._node = new Node(this)); +}; + +prototype.data = function() { + var data = Base.data.apply(this, arguments); + if (arguments.length > 1) { // new Datasource + this.node().addListener(data.pipeline()[0]); + } + return data; +}; + +function predicates(name) { + var m = this, pred = {}; + if (!dl.isArray(name)) return this._predicates[name]; + name.forEach(function(n) { pred[n] = m._predicates[n]; }); + return pred; +} + +prototype.predicate = function(name, predicate) { + if (arguments.length === 1) return predicates.call(this, name); + return (this._predicates[name] = predicate); +}; + +prototype.predicates = function() { return this._predicates; }; + +prototype.scene = function(renderer) { + if (!arguments.length) return this._scene; + + if (this._builder) { + this.node().removeListener(this._builder); + this._builder._groupBuilder.disconnect(); + } + + var m = this, + b = this._builder = new Node(this); + + b.evaluate = function(input) { + if (b._groupBuilder) return input; + + var gb = b._groupBuilder = new GroupBuilder(m, m._defs.marks, m._scene={}), + p = gb.pipeline(); + + this.addListener(gb.connect()); + p[p.length-1].addListener(renderer); + return input; + }; + + this.addListener(b); + return this; +}; + +prototype.reset = function() { + if (this._scene && this._reset.axes) { + visit(this._scene, function(item) { + if (item.axes) item.axes.forEach(function(axis) { axis.reset(); }); + }); + this._reset.axes = false; + } + if (this._scene && this._reset.legends) { + visit(this._scene, function(item) { + if (item.legends) item.legends.forEach(function(l) { l.reset(); }); + }); + this._reset.legends = false; + } + return this; +}; + +prototype.addListener = function(l) { + this.node().addListener(l); +}; + +prototype.removeListener = function(l) { + this.node().removeListener(l); +}; + +prototype.fire = function(cs) { + if (!cs) cs = ChangeSet.create(); + this.propagate(cs, this.node()); +}; + +module.exports = Model; +},{"../scene/GroupBuilder":112,"../scene/visit":117,"./config":90,"datalib":26,"vega-dataflow":41}],89:[function(require,module,exports){ +(function (global){ +var d3 = (typeof window !== "undefined" ? window['d3'] : typeof global !== "undefined" ? global['d3'] : null), + dl = require('datalib'), + df = require('vega-dataflow'), + sg = require('vega-scenegraph').render, + log = require('vega-logging'), + Deps = df.Dependencies, + parseStreams = require('../parse/streams'), + Encoder = require('../scene/Encoder'), + Transition = require('../scene/Transition'); + +function View(el, width, height) { + this._el = null; + this._model = null; + this._width = this.__width = width || 500; + this._height = this.__height = height || 300; + this._bgcolor = null; + this._autopad = 1; + this._padding = {top:0, left:0, bottom:0, right:0}; + this._viewport = null; + this._renderer = null; + this._handler = null; + this._streamer = null; // Targeted update for streaming changes + this._changeset = null; + this._repaint = true; // Full re-render on every re-init + this._renderers = sg; + this._io = null; + this._api = {}; // Stash streaming data API sandboxes. +} + +var prototype = View.prototype; + +prototype.model = function(model) { + if (!arguments.length) return this._model; + if (this._model !== model) { + this._model = model; + this._streamer = new df.Node(model); + this._streamer._rank = -1; // HACK: To reduce re-ranking churn. + this._changeset = df.ChangeSet.create(); + if (this._handler) this._handler.model(model); + } + return this; +}; + +// Sandboxed streaming data API +function streaming(src) { + var view = this, + ds = this._model.data(src), + name = ds.name(), + listener = ds.pipeline()[0], + streamer = this._streamer, + api = {}; + + // If we have it stashed, don't create a new closure. + if (this._api[src]) return this._api[src]; + + api.insert = function(vals) { + ds.insert(dl.duplicate(vals)); // Don't pollute the environment + streamer.addListener(listener); + view._changeset.data[name] = 1; + return api; + }; + + api.update = function() { + streamer.addListener(listener); + view._changeset.data[name] = 1; + return (ds.update.apply(ds, arguments), api); + }; + + api.remove = function() { + streamer.addListener(listener); + view._changeset.data[name] = 1; + return (ds.remove.apply(ds, arguments), api); + }; + + api.values = function() { return ds.values(); }; + + return (this._api[src] = api); +} + +prototype.data = function(data) { + var v = this; + if (!arguments.length) return v._model.values(); + else if (dl.isString(data)) return streaming.call(v, data); + else if (dl.isObject(data)) { + dl.keys(data).forEach(function(k) { + var api = streaming.call(v, k); + data[k](api); + }); + } + return this; +}; + +var VIEW_SIGNALS = dl.toMap(['width', 'height', 'padding']); + +prototype.signal = function(name, value, propagate) { + var m = this._model, + key, values; + + // Getter. Returns the value for the specified signal, or + // returns all signal values. + if (!arguments.length) { + return m.values(Deps.SIGNALS); + } else if (arguments.length === 1 && dl.isString(name)) { + return m.values(Deps.SIGNALS, name); + } + + // Setter. Can be done in batch or individually. In either case, + // the final argument determines if set values should propagate. + if (dl.isObject(name)) { + values = name; + propagate = value; + } else { + values = {}; + values[name] = value; + } + for (key in values) { + if (VIEW_SIGNALS[key]) { + this[key](values[key]); + } else { + setSignal.call(this, key, values[key], propagate); + } + } + return this; +}; + +function setSignal(name, value, propagate) { + var cs = this._changeset; + this._streamer.addListener(this._model.signal(name).value(value)); + if (propagate !== false) cs.signals[name] = 1; + cs.reflow = true; +} + +prototype.width = function(width) { + if (!arguments.length) return this.__width; + if (this.__width !== width) { + this._width = this.__width = width; + this.model().width(width); + this.initialize(); + if (this._strict) this._autopad = 1; + setSignal.call(this, 'width', width); + } + return this; +}; + +prototype.height = function(height) { + if (!arguments.length) return this.__height; + if (this.__height !== height) { + this._height = this.__height = height; + this.model().height(height); + this.initialize(); + if (this._strict) this._autopad = 1; + setSignal.call(this, 'height', height); + } + return this; +}; + +prototype.background = function(bgcolor) { + if (!arguments.length) return this._bgcolor; + if (this._bgcolor !== bgcolor) { + this._bgcolor = bgcolor; + this.initialize(); + } + return this; +}; + +prototype.padding = function(pad) { + if (!arguments.length) return this._padding; + if (this._padding !== pad) { + if (dl.isString(pad)) { + this._autopad = 1; + this._padding = {top:0, left:0, bottom:0, right:0}; + this._strict = (pad === 'strict'); + } else { + this._autopad = 0; + this._padding = pad; + this._strict = false; + } + if (this._renderer) this._renderer.resize(this._width, this._height, this._padding); + if (this._handler) this._handler.padding(this._padding); + setSignal.call(this, 'padding', this._padding); + } + return (this._repaint = true, this); +}; + +prototype.autopad = function(opt) { + if (this._autopad < 1) return this; + else this._autopad = 0; + + var b = this.model().scene().bounds, + pad = this._padding, + config = this.model().config(), + inset = config.autopadInset, + l = b.x1 < 0 ? Math.ceil(-b.x1) + inset : 0, + t = b.y1 < 0 ? Math.ceil(-b.y1) + inset : 0, + r = b.x2 > this._width ? Math.ceil(+b.x2 - this._width) + inset : 0; + b = b.y2 > this._height ? Math.ceil(+b.y2 - this._height) + inset : 0; + pad = {left:l, top:t, right:r, bottom:b}; + + if (this._strict) { + this._autopad = 0; + this._padding = pad; + this._width = Math.max(0, this.__width - (l+r)); + this._height = Math.max(0, this.__height - (t+b)); + + this._model.width(this._width).height(this._height).reset(); + setSignal.call(this, 'width', this._width); + setSignal.call(this, 'height', this._height); + setSignal.call(this, 'padding', pad); + + this.initialize().update({props:'enter'}).update({props:'update'}); + } else { + this.padding(pad).update(opt); + } + return this; +}; + +prototype.viewport = function(size) { + if (!arguments.length) return this._viewport; + if (this._viewport !== size) { + this._viewport = size; + this.initialize(); + } + return this; +}; + +prototype.renderer = function(type) { + if (!arguments.length) return this._renderer; + if (this._renderers[type]) type = this._renderers[type]; + else if (dl.isString(type)) throw new Error('Unknown renderer: ' + type); + else if (!type) throw new Error('No renderer specified'); + + if (this._io !== type) { + this._io = type; + this._renderer = null; + this.initialize(); + if (this._build) this.render(); + } + return this; +}; + +prototype.initialize = function(el) { + var v = this, prevHandler, + w = v._width, h = v._height, pad = v._padding, bg = v._bgcolor, + config = this.model().config(); + + if (!arguments.length || el === null) { + el = this._el ? this._el.parentNode : null; + if (!el) return this; // This View cannot init w/o an + } + + // clear pre-existing container + d3.select(el).select('div.vega').remove(); + + // add div container + this._el = el = d3.select(el) + .append('div') + .attr('class', 'vega') + .style('position', 'relative') + .node(); + if (v._viewport) { + d3.select(el) + .style('width', (v._viewport[0] || w)+'px') + .style('height', (v._viewport[1] || h)+'px') + .style('overflow', 'auto'); + } + + // renderer + sg.canvas.Renderer.RETINA = config.render.retina; + v._renderer = (v._renderer || new this._io.Renderer(config.load)) + .initialize(el, w, h, pad) + .background(bg); + + // input handler + prevHandler = v._handler; + v._handler = new this._io.Handler() + .initialize(el, pad, v); + + if (prevHandler) { + prevHandler.handlers().forEach(function(h) { + v._handler.on(h.type, h.handler); + }); + } else { + // Register event listeners for signal stream definitions. + v._detach = parseStreams(this); + } + + return (this._repaint = true, this); +}; + +prototype.destroy = function() { + if (this._detach) this._detach(); +}; + +function build() { + var v = this; + v._renderNode = new df.Node(v._model) + .router(true); + + v._renderNode.evaluate = function(input) { + log.debug(input, ['rendering']); + + var s = v._model.scene(), + h = v._handler; + + if (h && h.scene) h.scene(s); + + if (input.trans) { + input.trans.start(function(items) { v._renderer.render(s, items); }); + } else if (v._repaint) { + v._renderer.render(s); + v._repaint = false; + } else if (input.dirty.length) { + v._renderer.render(s, input.dirty); + } + + if (input.dirty.length) { + input.dirty.forEach(function(i) { i._dirty = false; }); + s.items[0]._dirty = false; + } + + // For all updated datasources, clear their previous values. + for (var d in input.data) { v._model.data(d).synchronize(); } + return input; + }; + + return (v._model.scene(v._renderNode), true); +} + +prototype.update = function(opt) { + opt = opt || {}; + var v = this, + model = this._model, + streamer = this._streamer, + cs = this._changeset, + trans = opt.duration ? new Transition(opt.duration, opt.ease) : null; + + if (trans) cs.trans = trans; + if (opt.props !== undefined) { + if (dl.keys(cs.data).length > 0) { + throw Error( + 'New data values are not reflected in the visualization.' + + ' Please call view.update() before updating a specified property set.' + ); + } + + cs.reflow = true; + cs.request = opt.props; + } + + var built = v._build; + v._build = v._build || build.call(this); + + // If specific items are specified, short-circuit dataflow graph. + // Else-If there are streaming updates, perform a targeted propagation. + // Otherwise, re-evaluate the entire model (datasources + scene). + if (opt.items && built) { + Encoder.update(model, opt.trans, opt.props, opt.items, cs.dirty); + v._renderNode.evaluate(cs); + } else if (streamer.listeners().length && built) { + // Include re-evaluation entire model when repaint flag is set + if (this._repaint) streamer.addListener(model.node()); + model.propagate(cs, streamer); + streamer.disconnect(); + } else { + model.fire(cs); + } + + v._changeset = df.ChangeSet.create(); + + return v.autopad(opt); +}; + +prototype.toImageURL = function(type) { + var v = this, Renderer; + + // lookup appropriate renderer + switch (type || 'png') { + case 'canvas': + case 'png': + Renderer = sg.canvas.Renderer; break; + case 'svg': + Renderer = sg.svg.string.Renderer; break; + default: throw Error('Unrecognized renderer type: ' + type); + } + + var retina = sg.canvas.Renderer.RETINA; + sg.canvas.Renderer.RETINA = false; // ignore retina screen + + // render the scenegraph + var ren = new Renderer(v._model.config.load) + .initialize(null, v._width, v._height, v._padding) + .render(v._model.scene()); + + sg.canvas.Renderer.RETINA = retina; // restore retina settings + + // return data url + if (type === 'svg') { + var blob = new Blob([ren.svg()], {type: 'image/svg+xml'}); + return window.URL.createObjectURL(blob); + } else { + return ren.canvas().toDataURL('image/png'); + } +}; + +prototype.render = function(items) { + this._renderer.render(this._model.scene(), items); + return this; +}; + +prototype.on = function() { + this._handler.on.apply(this._handler, arguments); + return this; +}; + +prototype.onSignal = function(name, handler) { + this._model.signal(name).on(handler); + return this; +}; + +prototype.off = function() { + this._handler.off.apply(this._handler, arguments); + return this; +}; + +prototype.offSignal = function(name, handler) { + this._model.signal(name).off(handler); + return this; +}; + +View.factory = function(model) { + var HeadlessView = require('./HeadlessView'); + return function(opt) { + opt = opt || {}; + var defs = model.defs(); + var v = (opt.el ? new View() : new HeadlessView()) + .model(model) + .renderer(opt.renderer || 'canvas') + .width(defs.width) + .height(defs.height) + .background(defs.background) + .padding(defs.padding) + .viewport(defs.viewport) + .initialize(opt.el); + + if (opt.data) v.data(opt.data); + + if (opt.hover !== false && opt.el) { + v.on('mouseover', function(evt, item) { + if (item && item.hasPropertySet('hover')) { + this.update({props:'hover', items:item}); + } + }) + .on('mouseout', function(evt, item) { + if (item && item.hasPropertySet('hover')) { + this.update({props:'update', items:item}); + } + }); + } + + return v; + }; +}; + +module.exports = View; +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"../parse/streams":107,"../scene/Encoder":111,"../scene/Transition":114,"./HeadlessView":87,"datalib":26,"vega-dataflow":41,"vega-logging":47,"vega-scenegraph":48}],90:[function(require,module,exports){ +(function (global){ +var d3 = (typeof window !== "undefined" ? window['d3'] : typeof global !== "undefined" ? global['d3'] : null), + config = {}; + +config.load = { + // base url for loading external data files + // used only for server-side operation + baseURL: '', + // Allows domain restriction when using data loading via XHR. + // To enable, set it to a list of allowed domains + // e.g., ['wikipedia.org', 'eff.org'] + domainWhiteList: false +}; + +// inset padding for automatic padding calculation +config.autopadInset = 5; + +// extensible scale lookup table +// all d3.scale.* instances also supported +config.scale = { + time: d3.time.scale, + utc: d3.time.scale.utc +}; + +// default rendering settings +config.render = { + retina: true +}; + +// root scenegraph group +config.scene = { + fill: undefined, + fillOpacity: undefined, + stroke: undefined, + strokeOpacity: undefined, + strokeWidth: undefined, + strokeDash: undefined, + strokeDashOffset: undefined +}; + +// default axis properties +config.axis = { + orient: 'bottom', + ticks: 10, + padding: 3, + axisColor: '#000', + axisWidth: 1, + gridColor: '#000', + gridOpacity: 0.15, + tickColor: '#000', + tickLabelColor: '#000', + tickWidth: 1, + tickSize: 6, + tickLabelFontSize: 11, + tickLabelFont: 'sans-serif', + titleColor: '#000', + titleFont: 'sans-serif', + titleFontSize: 11, + titleFontWeight: 'bold', + titleOffset: 'auto', + titleOffsetAutoMin: 30, + titleOffsetAutoMax: Infinity, + titleOffsetAutoMargin: 4 +}; + +// default legend properties +config.legend = { + orient: 'right', + offset: 20, + padding: 3, // padding between legend items and border + margin: 2, // extra margin between two consecutive legends + gradientStrokeColor: '#888', + gradientStrokeWidth: 1, + gradientHeight: 16, + gradientWidth: 100, + labelColor: '#000', + labelFontSize: 10, + labelFont: 'sans-serif', + labelAlign: 'left', + labelBaseline: 'middle', + labelOffset: 8, + symbolShape: 'circle', + symbolSize: 50, + symbolColor: '#888', + symbolStrokeWidth: 1, + titleColor: '#000', + titleFont: 'sans-serif', + titleFontSize: 11, + titleFontWeight: 'bold' +}; + +// default color values +config.color = { + rgb: [128, 128, 128], + lab: [50, 0, 0], + hcl: [0, 0, 50], + hsl: [0, 0, 0.5] +}; + +// default scale ranges +config.range = { + category10: d3.scale.category10().range(), + category20: d3.scale.category20().range(), + category20b: d3.scale.category20b().range(), + category20c: d3.scale.category20c().range(), + shapes: [ + 'circle', + 'cross', + 'diamond', + 'square', + 'triangle-down', + 'triangle-up' + ] +}; + +module.exports = config; +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{}],91:[function(require,module,exports){ +var dl = require('datalib'), + parse = require('../parse'), + Scale = require('../scene/Scale'), + config = require('./config'); + +function compile(module, opt, schema) { + var s = module.schema; + if (!s) return; + if (s.refs) dl.extend(schema.refs, s.refs); + if (s.defs) dl.extend(schema.defs, s.defs); +} + +module.exports = function(opt) { + var schema = null; + opt = opt || {}; + + // Compile if we're not loading the schema from a URL. + // Load from a URL to extend the existing base schema. + if (opt.url) { + schema = dl.json(dl.extend({url: opt.url}, config.load)); + } else { + schema = { + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Vega Visualization Specification Language", + "defs": {}, + "refs": {}, + "$ref": "#/defs/spec" + }; + + dl.keys(parse).forEach(function(k) { compile(parse[k], opt, schema); }); + + // Scales aren't in the parser, add schema manually + compile(Scale, opt, schema); + } + + // Extend schema to support custom mark properties or property sets. + if (opt.properties) dl.keys(opt.properties).forEach(function(k) { + schema.defs.propset.properties[k] = {"$ref": "#/refs/"+opt.properties[k]+"Value"}; + }); + + if (opt.propertySets) dl.keys(opt.propertySets).forEach(function(k) { + schema.defs.mark.properties.properties.properties[k] = {"$ref": "#/defs/propset"}; + }); + + return schema; +}; +},{"../parse":97,"../scene/Scale":113,"./config":90,"datalib":26}],92:[function(require,module,exports){ +var dl = require('datalib'), + axs = require('../scene/axis'); + +var ORIENT = { + "x": "bottom", + "y": "left", + "top": "top", + "bottom": "bottom", + "left": "left", + "right": "right" +}; + +function parseAxes(model, spec, axes, group) { + var config = model.config(); + (spec || []).forEach(function(def, index) { + axes[index] = axes[index] || axs(model); + parseAxis(config, def, index, axes[index], group); + }); +} + +function parseAxis(config, def, index, axis, group) { + // axis scale + if (def.scale !== undefined) { + axis.scale(group.scale(def.scale)); + } + + // axis orientation + axis.orient(def.orient || ORIENT[def.type]); + // axis offset + axis.offset(def.offset || 0); + // axis layer + axis.layer(def.layer || "front"); + // axis grid lines + axis.grid(def.grid || false); + // axis title + axis.title(def.title || null); + // axis title offset + axis.titleOffset(def.titleOffset != null ? + def.titleOffset : config.axis.titleOffset); + // axis values + axis.tickValues(def.values || null); + // axis label formatting + axis.tickFormat(def.format || null); + axis.tickFormatType(def.formatType || null); + // axis tick subdivision + axis.tickSubdivide(def.subdivide || 0); + // axis tick padding + axis.tickPadding(def.tickPadding || config.axis.padding); + + // axis tick size(s) + var size = []; + if (def.tickSize !== undefined) { + for (var i=0; i<3; ++i) size.push(def.tickSize); + } else { + var ts = config.axis.tickSize; + size = [ts, ts, ts]; + } + if (def.tickSizeMajor != null) size[0] = def.tickSizeMajor; + if (def.tickSizeMinor != null) size[1] = def.tickSizeMinor; + if (def.tickSizeEnd != null) size[2] = def.tickSizeEnd; + if (size.length) { + axis.tickSize.apply(axis, size); + } + + // axis tick count + axis.tickCount(def.ticks || config.axis.ticks); + + // style properties + var p = def.properties; + if (p && p.ticks) { + axis.majorTickProperties(p.majorTicks ? + dl.extend({}, p.ticks, p.majorTicks) : p.ticks); + axis.minorTickProperties(p.minorTicks ? + dl.extend({}, p.ticks, p.minorTicks) : p.ticks); + } else { + axis.majorTickProperties(p && p.majorTicks || {}); + axis.minorTickProperties(p && p.minorTicks || {}); + } + axis.tickLabelProperties(p && p.labels || {}); + axis.titleProperties(p && p.title || {}); + axis.gridLineProperties(p && p.grid || {}); + axis.domainProperties(p && p.axis || {}); +} + +module.exports = parseAxes; +},{"../scene/axis":115,"datalib":26}],93:[function(require,module,exports){ +(function (global){ +var d3 = (typeof window !== "undefined" ? window['d3'] : typeof global !== "undefined" ? global['d3'] : null); + +function parseBg(bg) { + // return null if input is null or undefined + if (bg == null) return null; + // run through d3 rgb to sanity check + return d3.rgb(bg) + ''; +} + +module.exports = parseBg; +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{}],94:[function(require,module,exports){ +var dl = require('datalib'), + log = require('vega-logging'), + parseTransforms = require('./transforms'), + parseModify = require('./modify'); + +function parseData(model, spec, callback) { + var config = model.config(), + count = 0; + + function loaded(d) { + return function(error, data) { + if (error) { + log.error('LOADING FAILED: ' + d.url + ' ' + error); + } else { + model.data(d.name).values(dl.read(data, d.format)); + } + if (--count === 0) callback(); + }; + } + + // process each data set definition + (spec || []).forEach(function(d) { + if (d.url) { + count += 1; + dl.load(dl.extend({url: d.url}, config.load), loaded(d)); + } + parseData.datasource(model, d); + }); + + if (count === 0) setTimeout(callback, 1); + return spec; +} + +parseData.datasource = function(model, d) { + var transform = (d.transform || []).map(function(t) { + return parseTransforms(model, t); + }), + mod = (d.modify || []).map(function(m) { + return parseModify(model, m, d); + }), + ds = model.data(d.name, mod.concat(transform)); + + if (d.values) { + ds.values(dl.read(d.values, d.format)); + } else if (d.source) { + // Derived ds will be pulsed by its src rather than the model. + ds.source(d.source).addListener(ds); + model.removeListener(ds.pipeline()[0]); + } + + return ds; +}; + +module.exports = parseData; +},{"./modify":101,"./transforms":108,"datalib":26,"vega-logging":47}],95:[function(require,module,exports){ +module.exports = (function() { + /* + * Generated by PEG.js 0.8.0. + * + * http://pegjs.majda.cz/ + */ + + function peg$subclass(child, parent) { + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor(); + } + + function SyntaxError(message, expected, found, offset, line, column) { + this.message = message; + this.expected = expected; + this.found = found; + this.offset = offset; + this.line = line; + this.column = column; + + this.name = "SyntaxError"; + } + + peg$subclass(SyntaxError, Error); + + function parse(input) { + var options = arguments.length > 1 ? arguments[1] : {}, + + peg$FAILED = {}, + + peg$startRuleFunctions = { start: peg$parsestart }, + peg$startRuleFunction = peg$parsestart, + + peg$c0 = peg$FAILED, + peg$c1 = ",", + peg$c2 = { type: "literal", value: ",", description: "\",\"" }, + peg$c3 = function(o, m) { return [o].concat(m); }, + peg$c4 = function(o) { return [o]; }, + peg$c5 = "[", + peg$c6 = { type: "literal", value: "[", description: "\"[\"" }, + peg$c7 = "]", + peg$c8 = { type: "literal", value: "]", description: "\"]\"" }, + peg$c9 = ">", + peg$c10 = { type: "literal", value: ">", description: "\">\"" }, + peg$c11 = function(f1, f2, o) { return {start: f1, end: f2, middle: o}; }, + peg$c12 = [], + peg$c13 = function(s, f) { return (s.filters = f, s); }, + peg$c14 = function(s) { return s; }, + peg$c15 = "(", + peg$c16 = { type: "literal", value: "(", description: "\"(\"" }, + peg$c17 = ")", + peg$c18 = { type: "literal", value: ")", description: "\")\"" }, + peg$c19 = function(m) { return {stream: m}; }, + peg$c20 = "@", + peg$c21 = { type: "literal", value: "@", description: "\"@\"" }, + peg$c22 = ":", + peg$c23 = { type: "literal", value: ":", description: "\":\"" }, + peg$c24 = function(n, e) { return {event: e, name: n}; }, + peg$c25 = function(m, e) { return {event: e, mark: m}; }, + peg$c26 = function(t, e) { return {event: e, target: t}; }, + peg$c27 = function(e) { return {event: e}; }, + peg$c28 = function(s) { return {signal: s}; }, + peg$c29 = "rect", + peg$c30 = { type: "literal", value: "rect", description: "\"rect\"" }, + peg$c31 = "symbol", + peg$c32 = { type: "literal", value: "symbol", description: "\"symbol\"" }, + peg$c33 = "path", + peg$c34 = { type: "literal", value: "path", description: "\"path\"" }, + peg$c35 = "arc", + peg$c36 = { type: "literal", value: "arc", description: "\"arc\"" }, + peg$c37 = "area", + peg$c38 = { type: "literal", value: "area", description: "\"area\"" }, + peg$c39 = "line", + peg$c40 = { type: "literal", value: "line", description: "\"line\"" }, + peg$c41 = "rule", + peg$c42 = { type: "literal", value: "rule", description: "\"rule\"" }, + peg$c43 = "image", + peg$c44 = { type: "literal", value: "image", description: "\"image\"" }, + peg$c45 = "text", + peg$c46 = { type: "literal", value: "text", description: "\"text\"" }, + peg$c47 = "group", + peg$c48 = { type: "literal", value: "group", description: "\"group\"" }, + peg$c49 = "mousedown", + peg$c50 = { type: "literal", value: "mousedown", description: "\"mousedown\"" }, + peg$c51 = "mouseup", + peg$c52 = { type: "literal", value: "mouseup", description: "\"mouseup\"" }, + peg$c53 = "click", + peg$c54 = { type: "literal", value: "click", description: "\"click\"" }, + peg$c55 = "dblclick", + peg$c56 = { type: "literal", value: "dblclick", description: "\"dblclick\"" }, + peg$c57 = "wheel", + peg$c58 = { type: "literal", value: "wheel", description: "\"wheel\"" }, + peg$c59 = "keydown", + peg$c60 = { type: "literal", value: "keydown", description: "\"keydown\"" }, + peg$c61 = "keypress", + peg$c62 = { type: "literal", value: "keypress", description: "\"keypress\"" }, + peg$c63 = "keyup", + peg$c64 = { type: "literal", value: "keyup", description: "\"keyup\"" }, + peg$c65 = "mousewheel", + peg$c66 = { type: "literal", value: "mousewheel", description: "\"mousewheel\"" }, + peg$c67 = "mousemove", + peg$c68 = { type: "literal", value: "mousemove", description: "\"mousemove\"" }, + peg$c69 = "mouseout", + peg$c70 = { type: "literal", value: "mouseout", description: "\"mouseout\"" }, + peg$c71 = "mouseover", + peg$c72 = { type: "literal", value: "mouseover", description: "\"mouseover\"" }, + peg$c73 = "mouseenter", + peg$c74 = { type: "literal", value: "mouseenter", description: "\"mouseenter\"" }, + peg$c75 = "touchstart", + peg$c76 = { type: "literal", value: "touchstart", description: "\"touchstart\"" }, + peg$c77 = "touchmove", + peg$c78 = { type: "literal", value: "touchmove", description: "\"touchmove\"" }, + peg$c79 = "touchend", + peg$c80 = { type: "literal", value: "touchend", description: "\"touchend\"" }, + peg$c81 = function(e) { return e; }, + peg$c82 = /^[a-zA-Z0-9_\-]/, + peg$c83 = { type: "class", value: "[a-zA-Z0-9_\\-]", description: "[a-zA-Z0-9_\\-]" }, + peg$c84 = function(n) { return n.join(""); }, + peg$c85 = /^[a-zA-Z0-9\-_ #.>+~[\]=|\^$*]/, + peg$c86 = { type: "class", value: "[a-zA-Z0-9\\-_ #.>+~[\\]=|\\^$*]", description: "[a-zA-Z0-9\\-_ #.>+~[\\]=|\\^$*]" }, + peg$c87 = function(c) { return c.join(""); }, + peg$c88 = /^['"a-zA-Z0-9_().><=! \t-&|~]/, + peg$c89 = { type: "class", value: "['\"a-zA-Z0-9_().><=! \\t-&|~]", description: "['\"a-zA-Z0-9_().><=! \\t-&|~]" }, + peg$c90 = function(v) { return v.join(""); }, + peg$c91 = /^[ \t\r\n]/, + peg$c92 = { type: "class", value: "[ \\t\\r\\n]", description: "[ \\t\\r\\n]" }, + + peg$currPos = 0, + peg$reportedPos = 0, + peg$cachedPos = 0, + peg$cachedPosDetails = { line: 1, column: 1, seenCR: false }, + peg$maxFailPos = 0, + peg$maxFailExpected = [], + peg$silentFails = 0, + + peg$result; + + if ("startRule" in options) { + if (!(options.startRule in peg$startRuleFunctions)) { + throw new Error("Can't start parsing from rule \"" + options.startRule + "\"."); + } + + peg$startRuleFunction = peg$startRuleFunctions[options.startRule]; + } + + function text() { + return input.substring(peg$reportedPos, peg$currPos); + } + + function offset() { + return peg$reportedPos; + } + + function line() { + return peg$computePosDetails(peg$reportedPos).line; + } + + function column() { + return peg$computePosDetails(peg$reportedPos).column; + } + + function expected(description) { + throw peg$buildException( + null, + [{ type: "other", description: description }], + peg$reportedPos + ); + } + + function error(message) { + throw peg$buildException(message, null, peg$reportedPos); + } + + function peg$computePosDetails(pos) { + function advance(details, startPos, endPos) { + var p, ch; + + for (p = startPos; p < endPos; p++) { + ch = input.charAt(p); + if (ch === "\n") { + if (!details.seenCR) { details.line++; } + details.column = 1; + details.seenCR = false; + } else if (ch === "\r" || ch === "\u2028" || ch === "\u2029") { + details.line++; + details.column = 1; + details.seenCR = true; + } else { + details.column++; + details.seenCR = false; + } + } + } + + if (peg$cachedPos !== pos) { + if (peg$cachedPos > pos) { + peg$cachedPos = 0; + peg$cachedPosDetails = { line: 1, column: 1, seenCR: false }; + } + advance(peg$cachedPosDetails, peg$cachedPos, pos); + peg$cachedPos = pos; + } + + return peg$cachedPosDetails; + } + + function peg$fail(expected) { + if (peg$currPos < peg$maxFailPos) { return; } + + if (peg$currPos > peg$maxFailPos) { + peg$maxFailPos = peg$currPos; + peg$maxFailExpected = []; + } + + peg$maxFailExpected.push(expected); + } + + function peg$buildException(message, expected, pos) { + function cleanupExpected(expected) { + var i = 1; + + expected.sort(function(a, b) { + if (a.description < b.description) { + return -1; + } else if (a.description > b.description) { + return 1; + } else { + return 0; + } + }); + + while (i < expected.length) { + if (expected[i - 1] === expected[i]) { + expected.splice(i, 1); + } else { + i++; + } + } + } + + function buildMessage(expected, found) { + function stringEscape(s) { + function hex(ch) { return ch.charCodeAt(0).toString(16).toUpperCase(); } + + return s + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\x08/g, '\\b') + .replace(/\t/g, '\\t') + .replace(/\n/g, '\\n') + .replace(/\f/g, '\\f') + .replace(/\r/g, '\\r') + .replace(/[\x00-\x07\x0B\x0E\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) + .replace(/[\x10-\x1F\x80-\xFF]/g, function(ch) { return '\\x' + hex(ch); }) + .replace(/[\u0180-\u0FFF]/g, function(ch) { return '\\u0' + hex(ch); }) + .replace(/[\u1080-\uFFFF]/g, function(ch) { return '\\u' + hex(ch); }); + } + + var expectedDescs = new Array(expected.length), + expectedDesc, foundDesc, i; + + for (i = 0; i < expected.length; i++) { + expectedDescs[i] = expected[i].description; + } + + expectedDesc = expected.length > 1 + ? expectedDescs.slice(0, -1).join(", ") + + " or " + + expectedDescs[expected.length - 1] + : expectedDescs[0]; + + foundDesc = found ? "\"" + stringEscape(found) + "\"" : "end of input"; + + return "Expected " + expectedDesc + " but " + foundDesc + " found."; + } + + var posDetails = peg$computePosDetails(pos), + found = pos < input.length ? input.charAt(pos) : null; + + if (expected !== null) { + cleanupExpected(expected); + } + + return new SyntaxError( + message !== null ? message : buildMessage(expected, found), + expected, + found, + pos, + posDetails.line, + posDetails.column + ); + } + + function peg$parsestart() { + var s0; + + s0 = peg$parsemerged(); + + return s0; + } + + function peg$parsemerged() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + s1 = peg$parseordered(); + if (s1 !== peg$FAILED) { + s2 = peg$parsesep(); + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 44) { + s3 = peg$c1; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c2); } + } + if (s3 !== peg$FAILED) { + s4 = peg$parsesep(); + if (s4 !== peg$FAILED) { + s5 = peg$parsemerged(); + if (s5 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c3(s1, s5); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parseordered(); + if (s1 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c4(s1); + } + s0 = s1; + } + + return s0; + } + + function peg$parseordered() { + var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 91) { + s1 = peg$c5; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c6); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsesep(); + if (s2 !== peg$FAILED) { + s3 = peg$parsefiltered(); + if (s3 !== peg$FAILED) { + s4 = peg$parsesep(); + if (s4 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 44) { + s5 = peg$c1; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c2); } + } + if (s5 !== peg$FAILED) { + s6 = peg$parsesep(); + if (s6 !== peg$FAILED) { + s7 = peg$parsefiltered(); + if (s7 !== peg$FAILED) { + s8 = peg$parsesep(); + if (s8 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 93) { + s9 = peg$c7; + peg$currPos++; + } else { + s9 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c8); } + } + if (s9 !== peg$FAILED) { + s10 = peg$parsesep(); + if (s10 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 62) { + s11 = peg$c9; + peg$currPos++; + } else { + s11 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c10); } + } + if (s11 !== peg$FAILED) { + s12 = peg$parsesep(); + if (s12 !== peg$FAILED) { + s13 = peg$parseordered(); + if (s13 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c11(s3, s7, s13); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + if (s0 === peg$FAILED) { + s0 = peg$parsefiltered(); + } + + return s0; + } + + function peg$parsefiltered() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + s1 = peg$parsestream(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parsefilter(); + if (s3 !== peg$FAILED) { + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parsefilter(); + } + } else { + s2 = peg$c0; + } + if (s2 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c13(s1, s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parsestream(); + if (s1 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c14(s1); + } + s0 = s1; + } + + return s0; + } + + function peg$parsestream() { + var s0, s1, s2, s3, s4; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 40) { + s1 = peg$c15; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c16); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsemerged(); + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 41) { + s3 = peg$c17; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c18); } + } + if (s3 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c19(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 64) { + s1 = peg$c20; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c21); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsename(); + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 58) { + s3 = peg$c22; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c23); } + } + if (s3 !== peg$FAILED) { + s4 = peg$parseeventType(); + if (s4 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c24(s2, s4); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parsemarkType(); + if (s1 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 58) { + s2 = peg$c22; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c23); } + } + if (s2 !== peg$FAILED) { + s3 = peg$parseeventType(); + if (s3 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c25(s1, s3); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parsecss(); + if (s1 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 58) { + s2 = peg$c22; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c23); } + } + if (s2 !== peg$FAILED) { + s3 = peg$parseeventType(); + if (s3 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c26(s1, s3); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parseeventType(); + if (s1 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c27(s1); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parsename(); + if (s1 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c28(s1); + } + s0 = s1; + } + } + } + } + } + + return s0; + } + + function peg$parsemarkType() { + var s0; + + if (input.substr(peg$currPos, 4) === peg$c29) { + s0 = peg$c29; + peg$currPos += 4; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c30); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 6) === peg$c31) { + s0 = peg$c31; + peg$currPos += 6; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c32); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 4) === peg$c33) { + s0 = peg$c33; + peg$currPos += 4; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c34); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 3) === peg$c35) { + s0 = peg$c35; + peg$currPos += 3; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c36); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 4) === peg$c37) { + s0 = peg$c37; + peg$currPos += 4; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c38); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 4) === peg$c39) { + s0 = peg$c39; + peg$currPos += 4; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c40); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 4) === peg$c41) { + s0 = peg$c41; + peg$currPos += 4; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c42); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 5) === peg$c43) { + s0 = peg$c43; + peg$currPos += 5; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c44); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 4) === peg$c45) { + s0 = peg$c45; + peg$currPos += 4; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c46); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 5) === peg$c47) { + s0 = peg$c47; + peg$currPos += 5; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c48); } + } + } + } + } + } + } + } + } + } + } + + return s0; + } + + function peg$parseeventType() { + var s0; + + if (input.substr(peg$currPos, 9) === peg$c49) { + s0 = peg$c49; + peg$currPos += 9; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c50); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 7) === peg$c51) { + s0 = peg$c51; + peg$currPos += 7; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c52); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 5) === peg$c53) { + s0 = peg$c53; + peg$currPos += 5; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c54); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 8) === peg$c55) { + s0 = peg$c55; + peg$currPos += 8; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c56); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 5) === peg$c57) { + s0 = peg$c57; + peg$currPos += 5; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c58); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 7) === peg$c59) { + s0 = peg$c59; + peg$currPos += 7; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c60); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 8) === peg$c61) { + s0 = peg$c61; + peg$currPos += 8; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c62); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 5) === peg$c63) { + s0 = peg$c63; + peg$currPos += 5; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c64); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 10) === peg$c65) { + s0 = peg$c65; + peg$currPos += 10; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c66); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 9) === peg$c67) { + s0 = peg$c67; + peg$currPos += 9; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c68); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 8) === peg$c69) { + s0 = peg$c69; + peg$currPos += 8; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c70); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 9) === peg$c71) { + s0 = peg$c71; + peg$currPos += 9; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c72); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 10) === peg$c73) { + s0 = peg$c73; + peg$currPos += 10; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c74); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 10) === peg$c75) { + s0 = peg$c75; + peg$currPos += 10; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c76); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 9) === peg$c77) { + s0 = peg$c77; + peg$currPos += 9; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c78); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 8) === peg$c79) { + s0 = peg$c79; + peg$currPos += 8; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c80); } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + + return s0; + } + + function peg$parsefilter() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 91) { + s1 = peg$c5; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c6); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parseexpr(); + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 93) { + s3 = peg$c7; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c8); } + } + if (s3 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c81(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + + return s0; + } + + function peg$parsename() { + var s0, s1, s2; + + s0 = peg$currPos; + s1 = []; + if (peg$c82.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c83); } + } + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + if (peg$c82.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c83); } + } + } + } else { + s1 = peg$c0; + } + if (s1 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c84(s1); + } + s0 = s1; + + return s0; + } + + function peg$parsecss() { + var s0, s1, s2; + + s0 = peg$currPos; + s1 = []; + if (peg$c85.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c86); } + } + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + if (peg$c85.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c86); } + } + } + } else { + s1 = peg$c0; + } + if (s1 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c87(s1); + } + s0 = s1; + + return s0; + } + + function peg$parseexpr() { + var s0, s1, s2; + + s0 = peg$currPos; + s1 = []; + if (peg$c88.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c89); } + } + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + if (peg$c88.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c89); } + } + } + } else { + s1 = peg$c0; + } + if (s1 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c90(s1); + } + s0 = s1; + + return s0; + } + + function peg$parsesep() { + var s0, s1; + + s0 = []; + if (peg$c91.test(input.charAt(peg$currPos))) { + s1 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c92); } + } + while (s1 !== peg$FAILED) { + s0.push(s1); + if (peg$c91.test(input.charAt(peg$currPos))) { + s1 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c92); } + } + } + + return s0; + } + + peg$result = peg$startRuleFunction(); + + if (peg$result !== peg$FAILED && peg$currPos === input.length) { + return peg$result; + } else { + if (peg$result !== peg$FAILED && peg$currPos < input.length) { + peg$fail({ type: "end", description: "end of input" }); + } + + throw peg$buildException(null, peg$maxFailExpected, peg$maxFailPos); + } + } + + return { + SyntaxError: SyntaxError, + parse: parse + }; +})(); +},{}],96:[function(require,module,exports){ +var expr = require('vega-expression'), + args = ['datum', 'event', 'signals']; + +module.exports = expr.compiler(args, { + idWhiteList: args, + fieldVar: args[0], + globalVar: args[2], + functions: function(codegen) { + var fn = expr.functions(codegen); + fn.eventItem = function() { return 'event.vg.item'; }; + fn.eventGroup = 'event.vg.getGroup'; + fn.eventX = 'event.vg.getX'; + fn.eventY = 'event.vg.getY'; + fn.open = 'window.open'; + return fn; + } +}); +},{"vega-expression":45}],97:[function(require,module,exports){ +module.exports = { + axes: require('./axes'), + background: require('./background'), + data: require('./data'), + events: require('./events'), + expr: require('./expr'), + legends: require('./legends'), + mark: require('./mark'), + marks: require('./marks'), + modify: require('./modify'), + padding: require('./padding'), + predicates: require('./predicates'), + properties: require('./properties'), + signals: require('./signals'), + spec: require('./spec'), + streams: require('./streams'), + transforms: require('./transforms') +}; +},{"./axes":92,"./background":93,"./data":94,"./events":95,"./expr":96,"./legends":98,"./mark":99,"./marks":100,"./modify":101,"./padding":102,"./predicates":103,"./properties":104,"./signals":105,"./spec":106,"./streams":107,"./transforms":108}],98:[function(require,module,exports){ +var lgnd = require('../scene/legend'); + +function parseLegends(model, spec, legends, group) { + (spec || []).forEach(function(def, index) { + legends[index] = legends[index] || lgnd(model); + parseLegend(def, index, legends[index], group); + }); +} + +function parseLegend(def, index, legend, group) { + // legend scales + legend.size (def.size ? group.scale(def.size) : null); + legend.shape (def.shape ? group.scale(def.shape) : null); + legend.fill (def.fill ? group.scale(def.fill) : null); + legend.stroke(def.stroke ? group.scale(def.stroke) : null); + + // legend orientation + if (def.orient) legend.orient(def.orient); + + // legend offset + if (def.offset != null) legend.offset(def.offset); + + // legend title + legend.title(def.title || null); + + // legend values + legend.values(def.values || null); + + // legend label formatting + legend.format(def.format !== undefined ? def.format : null); + + // style properties + var p = def.properties; + legend.titleProperties(p && p.title || {}); + legend.labelProperties(p && p.labels || {}); + legend.legendProperties(p && p.legend || {}); + legend.symbolProperties(p && p.symbols || {}); + legend.gradientProperties(p && p.gradient || {}); +} + +module.exports = parseLegends; +},{"../scene/legend":116}],99:[function(require,module,exports){ +var dl = require('datalib'), + parseProperties = require('./properties'); + +function parseMark(model, mark) { + var props = mark.properties, + group = mark.marks; + + // parse mark property definitions + dl.keys(props).forEach(function(k) { + props[k] = parseProperties(model, mark.type, props[k]); + }); + + // parse delay function + if (mark.delay) { + mark.delay = parseProperties(model, mark.type, {delay: mark.delay}); + } + + // recurse if group type + if (group) { + mark.marks = group.map(function(g) { return parseMark(model, g); }); + } + + return mark; +} + +module.exports = parseMark; +},{"./properties":104,"datalib":26}],100:[function(require,module,exports){ +var parseMark = require('./mark'), + parseProperties = require('./properties'); + +function parseRootMark(model, spec, width, height) { + return { + type: 'group', + width: width, + height: height, + properties: defaults(spec.scene || {}, model), + scales: spec.scales || [], + axes: spec.axes || [], + legends: spec.legends || [], + marks: (spec.marks || []).map(function(m) { return parseMark(model, m); }) + }; +} + +var PROPERTIES = [ + 'fill', 'fillOpacity', 'stroke', 'strokeOpacity', + 'strokeWidth', 'strokeDash', 'strokeDashOffset' +]; + +function defaults(spec, model) { + var config = model.config().scene, + props = {}, i, n, m, p, s; + + for (i=0, n=m=PROPERTIES.length; i= 0; --i) { + if (src[i][field] == value) + dest.push.apply(dest, src.splice(i, 1)); + } +}; + +function parseModify(model, def, ds) { + var signal = def.signal ? dl.field(def.signal) : null, + signalName = signal ? signal[0] : null, + predicate = def.predicate ? model.predicate(def.predicate.name || def.predicate) : null, + reeval = (predicate === null), + isClear = def.type === Types.CLEAR, + node = new Node(model).router(isClear); + + node.evaluate = function(input) { + if (predicate !== null) { // TODO: predicate args + var db = model.values(Deps.DATA, predicate.data || EMPTY), + sg = model.values(Deps.SIGNALS, predicate.signals || EMPTY); + reeval = predicate.call(predicate, {}, db, sg, model._predicates); + } + + log.debug(input, [def.type+"ing", reeval]); + if (!reeval || (!isClear && !input.signals[signalName])) return input; + + var datum = {}, + value = signal ? model.signalRef(def.signal) : null, + d = model.data(ds.name), + t = null; + + datum[def.field] = value; + + // We have to modify ds._data so that subsequent pulses contain + // our dynamic data. W/o modifying ds._data, only the output + // collector will contain dynamic tuples. + if (def.type === Types.INSERT) { + t = Tuple.ingest(datum); + input.add.push(t); + d._data.push(t); + } else if (def.type === Types.REMOVE) { + filter(def.field, value, input.add, input.rem); + filter(def.field, value, input.mod, input.rem); + d._data = d._data.filter(function(x) { return x[def.field] !== value; }); + } else if (def.type === Types.TOGGLE) { + var add = [], rem = []; + filter(def.field, value, input.rem, add); + filter(def.field, value, input.add, rem); + filter(def.field, value, input.mod, rem); + if (!(add.length || rem.length)) add.push(Tuple.ingest(datum)); + + input.add.push.apply(input.add, add); + d._data.push.apply(d._data, add); + input.rem.push.apply(input.rem, rem); + d._data = d._data.filter(function(x) { return rem.indexOf(x) === -1; }); + } else if (def.type === Types.CLEAR) { + input.rem.push.apply(input.rem, input.add); + input.rem.push.apply(input.rem, input.mod); + input.add = []; + input.mod = []; + d._data = []; + } + + input.fields[def.field] = 1; + return input; + }; + + if (signalName) node.dependency(Deps.SIGNALS, signalName); + + if (predicate) { + node.dependency(Deps.DATA, predicate.data); + node.dependency(Deps.SIGNALS, predicate.signals); + } + + return node; +} + +module.exports = parseModify; +},{"datalib":26,"vega-dataflow":41,"vega-logging":47}],102:[function(require,module,exports){ +var dl = require('datalib'); + +function parsePadding(pad) { + return pad == null ? 'auto' : + dl.isObject(pad) ? pad : + dl.isNumber(pad) ? {top:pad, left:pad, right:pad, bottom:pad} : + pad === 'strict' ? pad : 'auto'; +} + +module.exports = parsePadding; +},{"datalib":26}],103:[function(require,module,exports){ +var dl = require('datalib'); + +var types = { + '=': parseComparator, + '==': parseComparator, + '!=': parseComparator, + '>': parseComparator, + '>=': parseComparator, + '<': parseComparator, + '<=': parseComparator, + 'and': parseLogical, + '&&': parseLogical, + 'or': parseLogical, + '||': parseLogical, + 'in': parseIn +}; + +var nullScale = function() { return 0; }; +nullScale.invert = nullScale; + +function parsePredicates(model, spec) { + (spec || []).forEach(function(s) { + var parse = types[s.type](model, s); + + /* jshint evil:true */ + var pred = Function("args", "db", "signals", "predicates", parse.code); + pred.root = function() { return model.scene().items[0]; }; // For global scales + pred.nullScale = nullScale; + pred.isFunction = dl.isFunction; + pred.signals = parse.signals; + pred.data = parse.data; + + model.predicate(s.name, pred); + }); + + return spec; +} + +function parseSignal(signal, signals) { + var s = dl.field(signal), + code = "signals["+s.map(dl.str).join("][")+"]"; + signals[s[0]] = 1; + return code; +} + +function parseOperands(model, operands) { + var decl = [], defs = [], + signals = {}, db = {}; + + function setSignal(s) { signals[s] = 1; } + function setData(d) { db[d] = 1; } + + dl.array(operands).forEach(function(o, i) { + var name = "o" + i, + def = ""; + + if (o.value !== undefined) { + def = dl.str(o.value); + } else if (o.arg) { + def = "args["+dl.str(o.arg)+"]"; + } else if (o.signal) { + def = parseSignal(o.signal, signals); + } else if (o.predicate) { + var ref = o.predicate, + predName = ref && (ref.name || ref), + pred = model.predicate(predName), + p = "predicates["+dl.str(predName)+"]"; + + pred.signals.forEach(setSignal); + pred.data.forEach(setData); + + if (dl.isObject(ref)) { + dl.keys(ref).forEach(function(k) { + if (k === "name") return; + var i = ref[k]; + def += "args["+dl.str(k)+"] = "; + if (i.signal) { + def += parseSignal(i.signal, signals); + } else if (i.arg) { + def += "args["+dl.str(i.arg)+"]"; + } + def += ", "; + }); + } + + def += p+".call("+p+", args, db, signals, predicates)"; + } + + decl.push(name); + defs.push(name+"=("+def+")"); + }); + + return { + code: "var " + decl.join(", ") + ";\n" + defs.join(";\n") + ";\n", + signals: dl.keys(signals), + data: dl.keys(db) + }; +} + +function parseComparator(model, spec) { + var ops = parseOperands(model, spec.operands); + if (spec.type === '=') spec.type = '=='; + + ops.code += "o0 = o0 instanceof Date ? o0.getTime() : o0;\n" + + "o1 = o1 instanceof Date ? o1.getTime() : o1;\n"; + + return { + code: ops.code + "return " + ["o0", "o1"].join(spec.type) + ";", + signals: ops.signals, + data: ops.data + }; +} + +function parseLogical(model, spec) { + var ops = parseOperands(model, spec.operands), + o = [], i = 0, len = spec.operands.length; + + while (o.push("o"+i++) < len); + if (spec.type === 'and') spec.type = '&&'; + else if (spec.type === 'or') spec.type = '||'; + + return { + code: ops.code + "return " + o.join(spec.type) + ";", + signals: ops.signals, + data: ops.data + }; +} + +function parseIn(model, spec) { + var o = [spec.item], code = ""; + if (spec.range) o.push.apply(o, spec.range); + if (spec.scale) { + code = parseScale(spec.scale, o); + } + + var ops = parseOperands(model, o); + code = ops.code + code + "\n var ordSet = null;\n"; + + if (spec.data) { + var field = dl.field(spec.field).map(dl.str); + code += "var where = function(d) { return d["+field.join("][")+"] == o0 };\n"; + code += "return db["+dl.str(spec.data)+"].filter(where).length > 0;"; + } else if (spec.range) { + // TODO: inclusive/exclusive range? + if (spec.scale) { + code += "if (scale.length == 2) {\n" + // inverting ordinal scales + " ordSet = scale(o1, o2);\n" + + "} else {\n" + + " o1 = scale(o1);\no2 = scale(o2);\n" + + "}"; + } + + code += "return ordSet !== null ? ordSet.indexOf(o0) !== -1 :\n" + + " o1 < o2 ? o1 <= o0 && o0 <= o2 : o2 <= o0 && o0 <= o1;"; + } + + return { + code: code, + signals: ops.signals, + data: ops.data.concat(spec.data ? [spec.data] : []) + }; +} + +// Populate ops such that ultimate scale/inversion function will be in `scale` var. +function parseScale(spec, ops) { + var code = "var scale = ", + idx = ops.length; + + if (dl.isString(spec)) { + ops.push({ value: spec }); + code += "this.root().scale(o"+idx+")"; + } else if (spec.arg) { // Scale function is being passed as an arg + ops.push(spec); + code += "o"+idx; + } else if (spec.name) { // Full scale parameter {name: ..} + ops.push(dl.isString(spec.name) ? {value: spec.name} : spec.name); + code += "(this.isFunction(o"+idx+") ? o"+idx+" : "; + if (spec.scope) { + ops.push(spec.scope); + code += "((o"+(idx+1)+".scale || this.root().scale)(o"+idx+") || this.nullScale)"; + } else { + code += "this.root().scale(o"+idx+")"; + } + code += ")"; + } + + if (spec.invert === true) { // Allow spec.invert.arg? + code += ".invert"; + } + + return code+";\n"; +} + +module.exports = parsePredicates; +},{"datalib":26}],104:[function(require,module,exports){ +(function (global){ +var d3 = (typeof window !== "undefined" ? window['d3'] : typeof global !== "undefined" ? global['d3'] : null), + dl = require('datalib'), + log = require('vega-logging'), + Tuple = require('vega-dataflow').Tuple; + +var DEPS = ["signals", "scales", "data", "fields"]; + +function properties(model, mark, spec) { + var config = model.config(), + code = "", + names = dl.keys(spec), + i, len, name, ref, vars = {}, + deps = { + signals: {}, + scales: {}, + data: {}, + fields: {}, + nested: [], + _nRefs: {}, // Temp stash to de-dupe nested refs. + reflow: false + }; + + code += "var o = trans ? {} : item, d=0, set=this.tpl.set, tmpl=signals||{}, t;\n" + + // Stash for dl.template + "tmpl.datum = item.datum;\n" + + "tmpl.group = group;\n" + + "tmpl.parent = group.datum;\n"; + + function handleDep(p) { + if (ref[p] == null) return; + var k = dl.array(ref[p]), i, n; + for (i=0, n=k.length; i 0) ? "\n " : " "; + if (ref.rule) { + ref = rule(model, name, ref.rule); + code += "\n " + ref.code; + } else if (dl.isArray(ref)) { + ref = rule(model, name, ref); + code += "\n " + ref.code; + } else { + ref = valueRef(config, name, ref); + code += "d += set(o, "+dl.str(name)+", "+ref.val+");"; + } + + vars[name] = true; + DEPS.forEach(handleDep); + deps.reflow = deps.reflow || ref.reflow; + if (ref.nested.length) ref.nested.forEach(handleNestedRefs); + } + + // If nested references are present, sort them based on their level + // to speed up determination of whether encoders should be reeval'd. + dl.keys(deps._nRefs).forEach(function(k) { deps.nested.push(deps._nRefs[k]); }); + deps.nested.sort(function(a, b) { + a = a.level; + b = b.level; + return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; + }); + + if (vars.x2) { + if (vars.x) { + code += "\n if (o.x > o.x2) { " + + "\n t = o.x;" + + "\n d += set(o, 'x', o.x2);" + + "\n d += set(o, 'x2', t); " + + "\n };"; + code += "\n d += set(o, 'width', (o.x2 - o.x));"; + } else if (vars.width) { + code += "\n d += set(o, 'x', (o.x2 - o.width));"; + } else { + code += "\n d += set(o, 'x', o.x2);"; + } + } + + if (vars.xc) { + if (vars.width) { + code += "\n d += set(o, 'x', (o.xc - o.width/2));" ; + } else { + code += "\n d += set(o, 'x', o.xc);" ; + } + } + + if (vars.y2) { + if (vars.y) { + code += "\n if (o.y > o.y2) { " + + "\n t = o.y;" + + "\n d += set(o, 'y', o.y2);" + + "\n d += set(o, 'y2', t);" + + "\n };"; + code += "\n d += set(o, 'height', (o.y2 - o.y));"; + } else if (vars.height) { + code += "\n d += set(o, 'y', (o.y2 - o.height));"; + } else { + code += "\n d += set(o, 'y', o.y2);"; + } + } + + if (vars.yc) { + if (vars.height) { + code += "\n d += set(o, 'y', (o.yc - o.height/2));" ; + } else { + code += "\n d += set(o, 'y', o.yc);" ; + } + } + + if (hasPath(mark, vars)) code += "\n d += (item.touch(), 1);"; + code += "\n if (trans) trans.interpolate(item, o);"; + code += "\n return d > 0;"; + + try { + /* jshint evil:true */ + var encoder = Function('item', 'group', 'trans', 'db', + 'signals', 'predicates', code); + encoder.tpl = Tuple; + encoder.util = dl; + encoder.d3 = d3; // For color spaces + dl.extend(encoder, dl.template.context); + return { + encode: encoder, + signals: dl.keys(deps.signals), + scales: dl.keys(deps.scales), + data: dl.keys(deps.data), + fields: dl.keys(deps.fields), + nested: deps.nested, + reflow: deps.reflow + }; + } catch (e) { + log.error(e); + log.log(code); + } +} + +function dependencies(a, b) { + if (!dl.isObject(a)) { + a = {reflow: false, nested: []}; + DEPS.forEach(function(d) { a[d] = []; }); + } + + if (dl.isObject(b)) { + a.reflow = a.reflow || b.reflow; + a.nested.push.apply(a.nested, b.nested); + DEPS.forEach(function(d) { a[d].push.apply(a[d], b[d]); }); + } + + return a; +} + +function hasPath(mark, vars) { + return vars.path || + ((mark==='area' || mark==='line') && + (vars.x || vars.x2 || vars.width || + vars.y || vars.y2 || vars.height || + vars.tension || vars.interpolate)); +} + +function rule(model, name, rules) { + var config = model.config(), + deps = dependencies(), + inputs = [], code = ''; + + (rules||[]).forEach(function(r, i) { + var def = r.predicate, + predName = def && (def.name || def), + pred = model.predicate(predName), + p = 'predicates['+dl.str(predName)+']', + input = [], args = name+'_arg'+i, + ref; + + if (dl.isObject(def)) { + dl.keys(def).forEach(function(k) { + if (k === 'name') return; + var ref = valueRef(config, i, def[k]); + input.push(dl.str(k)+': '+ref.val); + dependencies(deps, ref); + }); + } + + ref = valueRef(config, name, r); + dependencies(deps, ref); + + if (predName) { + deps.signals.push.apply(deps.signals, pred.signals); + deps.data.push.apply(deps.data, pred.data); + inputs.push(args+" = {\n "+input.join(",\n ")+"\n }"); + code += "if ("+p+".call("+p+","+args+", db, signals, predicates)) {" + + "\n d += set(o, "+dl.str(name)+", "+ref.val+");"; + code += rules[i+1] ? "\n } else " : " }"; + } else { + code += "{" + + "\n d += set(o, "+dl.str(name)+", "+ref.val+");"+ + "\n }\n"; + } + }); + + code = "var " + inputs.join(",\n ") + ";\n " + code; + return (deps.code = code, deps); +} + +function valueRef(config, name, ref) { + if (ref == null) return null; + + if (name==='fill' || name==='stroke') { + if (ref.c) { + return colorRef(config, 'hcl', ref.h, ref.c, ref.l); + } else if (ref.h || ref.s) { + return colorRef(config, 'hsl', ref.h, ref.s, ref.l); + } else if (ref.l || ref.a) { + return colorRef(config, 'lab', ref.l, ref.a, ref.b); + } else if (ref.r || ref.g || ref.b) { + return colorRef(config, 'rgb', ref.r, ref.g, ref.b); + } + } + + // initialize value + var val = null, scale = null, + deps = dependencies(), + sgRef = null, fRef = null, sRef = null, tmpl = {}; + + if (ref.template !== undefined) { + val = dl.template.source(ref.template, 'tmpl', tmpl); + dl.keys(tmpl).forEach(function(k) { + var f = dl.field(k), + a = f.shift(); + if (a === 'parent' || a === 'group') { + deps.nested.push({ + parent: a === 'parent', + group: a === 'group', + level: 1 + }); + } else if (a === 'datum') { + deps.fields.push(f[0]); + } else { + deps.signals.push(a); + } + }); + } + + if (ref.value !== undefined) { + val = dl.str(ref.value); + } + + if (ref.signal !== undefined) { + sgRef = dl.field(ref.signal); + val = 'signals['+sgRef.map(dl.str).join('][')+']'; + deps.signals.push(sgRef.shift()); + } + + if (ref.field !== undefined) { + ref.field = dl.isString(ref.field) ? {datum: ref.field} : ref.field; + fRef = fieldRef(ref.field); + val = fRef.val; + dependencies(deps, fRef); + } + + if (ref.scale !== undefined) { + sRef = scaleRef(ref.scale); + scale = sRef.val; + dependencies(deps, sRef); + deps.scales.push(ref.scale.name || ref.scale); + + // run through scale function if val specified. + // if no val, scale function is predicate arg. + if (val !== null || ref.band || ref.mult || ref.offset) { + val = scale + (ref.band ? '.rangeBand()' : + '('+(val !== null ? val : 'item.datum.data')+')'); + } else { + val = scale; + } + } + + // multiply, offset, return value + val = '(' + (ref.mult?(dl.number(ref.mult)+' * '):'') + val + ')' + + (ref.offset ? ' + ' + dl.number(ref.offset) : ''); + + // Collate dependencies + return (deps.val = val, deps); +} + +function colorRef(config, type, x, y, z) { + var xx = x ? valueRef(config, '', x) : config.color[type][0], + yy = y ? valueRef(config, '', y) : config.color[type][1], + zz = z ? valueRef(config, '', z) : config.color[type][2], + deps = dependencies(); + + [xx, yy, zz].forEach(function(v) { + if (dl.isArray) return; + dependencies(deps, v); + }); + + var val = '(this.d3.' + type + '(' + [xx.val, yy.val, zz.val].join(',') + ') + "")'; + return (deps.val = val, deps); +} + +// {field: {datum: "foo"} } -> item.datum.foo +// {field: {group: "foo"} } -> group.foo +// {field: {parent: "foo"} } -> group.datum.foo +function fieldRef(ref) { + if (dl.isString(ref)) { + return {val: dl.field(ref).map(dl.str).join('][')}; + } + + // Resolve nesting/parent lookups + var l = ref.level || 1, + nested = (ref.group || ref.parent) && l, + scope = nested ? Array(l).join('group.mark.') : '', + r = fieldRef(ref.datum || ref.group || ref.parent || ref.signal), + val = r.val, + deps = dependencies(null, r); + + if (ref.datum) { + val = 'item.datum['+val+']'; + deps.fields.push(ref.datum); + } else if (ref.group) { + val = scope+'group['+val+']'; + deps.nested.push({ level: l, group: true }); + } else if (ref.parent) { + val = scope+'group.datum['+val+']'; + deps.nested.push({ level: l, parent: true }); + } else if (ref.signal) { + val = 'signals['+val+']'; + deps.signals.push(dl.field(ref.signal)[0]); + deps.reflow = true; + } + + return (deps.val = val, deps); +} + +// {scale: "x"} +// {scale: {name: "x"}}, +// {scale: fieldRef} +function scaleRef(ref) { + var scale = null, + fr = null, + deps = dependencies(); + + if (dl.isString(ref)) { + scale = dl.str(ref); + } else if (ref.name) { + scale = dl.isString(ref.name) ? dl.str(ref.name) : (fr = fieldRef(ref.name)).val; + } else { + scale = (fr = fieldRef(ref)).val; + } + + scale = '(item.mark._scaleRefs['+scale+'] = 1, group.scale('+scale+'))'; + if (ref.invert) scale += '.invert'; + + // Mark scale refs as they're dealt with separately in mark._scaleRefs. + if (fr) fr.nested.forEach(function(g) { g.scale = true; }); + return fr ? (fr.val = scale, fr) : (deps.val = scale, deps); +} + +module.exports = properties; +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"datalib":26,"vega-dataflow":41,"vega-logging":47}],105:[function(require,module,exports){ +var dl = require('datalib'), + SIGNALS = require('vega-dataflow').Dependencies.SIGNALS, + expr = require('./expr'); + +var RESERVED = ['datum', 'event', 'signals', 'width', 'height', 'padding'] + .concat(dl.keys(expr.codegen.functions)); + +function parseSignals(model, spec) { + // process each signal definition + (spec || []).forEach(function(s) { + if (RESERVED.indexOf(s.name) !== -1) { + throw Error('Signal name "'+s.name+'" is a '+ + 'reserved keyword ('+RESERVED.join(', ')+').'); + } + + var signal = model.signal(s.name, s.init) + .verbose(s.verbose); + + if (s.init && s.init.expr) { + s.init.expr = expr(s.init.expr); + signal.value(exprVal(model, s.init)); + } + + if (s.expr) { + s.expr = expr(s.expr); + signal.evaluate = function(input) { + var val = exprVal(model, s), + sg = input.signals; + if (val !== signal.value() || signal.verbose()) { + signal.value(val); + sg[s.name] = 1; + } + return sg[s.name] ? input : model.doNotPropagate; + }; + signal.dependency(SIGNALS, s.expr.globals); + s.expr.globals.forEach(function(dep) { + model.signal(dep).addListener(signal); + }); + } + }); + + return spec; +} + +function exprVal(model, spec) { + var e = spec.expr, + val = e.fn(null, null, model.values(SIGNALS, e.globals)); + return spec.scale ? parseSignals.scale(model, spec, val) : val; +} + +parseSignals.scale = function scale(model, spec, value, datum, evt) { + var def = spec.scale, + name = def.name || def.signal || def, + scope = def.scope, e; + + if (scope) { + if (scope.signal) { + scope = model.signalRef(scope.signal); + } else if (dl.isString(scope)) { // Scope is an expression + e = def._expr = (def._expr || expr(scope)); + scope = e.fn(datum, evt, model.values(SIGNALS, e.globals)); + } + } + + if (!scope || !scope.scale) { + scope = (scope && scope.mark) ? scope.mark.group : model.scene().items[0]; + } + + var s = scope.scale(name); + return !s ? value : (def.invert ? s.invert(value) : s(value)); +}; + +module.exports = parseSignals; +},{"./expr":96,"datalib":26,"vega-dataflow":41}],106:[function(require,module,exports){ +var dl = require('datalib'), + log = require('vega-logging'), + Model = require('../core/Model'), + View = require('../core/View'); + +function parseSpec(spec, callback) { + var vf = arguments[arguments.length-1], + viewFactory = arguments.length > 2 && dl.isFunction(vf) ? vf : View.factory, + config = arguments[2] !== viewFactory ? arguments[2] : {}, + model = new Model(config); + + function parse(spec) { + // protect against subsequent spec modification + spec = dl.duplicate(spec); + + var parsers = require('./'), + create = function() { callback(viewFactory(model)); }, + width = spec.width || 500, + height = spec.height || 500, + padding = parsers.padding(spec.padding); + + // create signals for width, height and padding + model.signal('width', width); + model.signal('height', height); + model.signal('padding', padding); + + // initialize model + model.defs({ + width: width, + height: height, + padding: padding, + viewport: spec.viewport || null, + background: parsers.background(spec.background), + signals: parsers.signals(model, spec.signals), + predicates: parsers.predicates(model, spec.predicates), + marks: parsers.marks(model, spec, width, height), + data: parsers.data(model, spec.data, create) + }); + } + + if (dl.isObject(spec)) { + parse(spec); + } else if (dl.isString(spec)) { + var opts = dl.extend({url: spec}, model.config().load); + dl.load(opts, function(err, data) { + if (err) { + log.error('LOADING SPECIFICATION FAILED: ' + err.statusText); + } else { + try { + parse(JSON.parse(data)); + } catch (e) { + log.error('INVALID SPECIFICATION: Must be a valid JSON object. '+e); + } + } + }); + } else { + log.error('INVALID SPECIFICATION: Must be a valid JSON object or URL.'); + } +} + +module.exports = parseSpec; +},{"../core/Model":88,"../core/View":89,"./":97,"datalib":26,"vega-logging":47}],107:[function(require,module,exports){ +(function (global){ +var d3 = (typeof window !== "undefined" ? window['d3'] : typeof global !== "undefined" ? global['d3'] : null), + dl = require('datalib'), + df = require('vega-dataflow'), + SIGNALS = df.Dependencies.SIGNALS, + parseSignals = require('./signals'), + selector = require('./events'), + expr = require('./expr'); + +var GATEKEEPER = '_vgGATEKEEPER'; + +var vgEvent = { + getGroup: function(name) { return name ? this.name[name] : this.group; }, + getXY: function(item) { + var p = {x: this.x, y: this.y}; + if (typeof item === 'string') { + item = this.name[item]; + } + for (; item; item = item.mark && item.mark.group) { + p.x -= item.x || 0; + p.y -= item.y || 0; + } + return p; + }, + getX: function(item) { return this.getXY(item).x; }, + getY: function(item) { return this.getXY(item).y; } +}; + +function parseStreams(view) { + var model = view.model(), + spec = model.defs().signals, + registry = {handlers: {}, nodes: {}}, + internal = dl.duplicate(registry), // Internal event processing + external = dl.duplicate(registry); // External event processing + + (spec || []).forEach(function(sig) { + var signal = model.signal(sig.name); + if (sig.expr) return; // Cannot have an expr and stream definition. + + (sig.streams || []).forEach(function(stream) { + var sel = selector.parse(stream.type), + exp = expr(stream.expr); + mergedStream(signal, sel, exp, stream); + }); + }); + + // We register the event listeners all together so that if multiple + // signals are registered on the same event, they will receive the + // new value on the same pulse. + dl.keys(internal.handlers).forEach(function(type) { + view.on(type, function(evt, item) { + evt.preventDefault(); // stop text selection + extendEvent(evt, item); + fire(internal, type, (item && item.datum) || {}, evt); + }); + }); + + // add external event listeners + dl.keys(external.handlers).forEach(function(type) { + if (typeof window === 'undefined') return; // No external support + + var h = external.handlers[type], + t = type.split(':'), // --> no element pseudo-selectors + elt = (t[0] === 'window') ? [window] : + window.document.querySelectorAll(t[0]); + + function handler(evt) { + extendEvent(evt); + fire(external, type, d3.select(this).datum(), evt); + } + + for (var i=0; i 0, + i, ilen, j, jlen, group, legend; + + if (input.add.length || input.rem.length || !items.length || + input.mod.length === items.length || + type === 'area' || type === 'line') { + bound.mark(this._mark, null, isGrp && !hasLegends); + } else { + input.mod.forEach(function(item) { bound.item(item); }); + } + + if (isGrp && hasLegends) { + for (i=0, ilen=items.length; i this._stamp) { + join.call(this, fcs, output, this._ds.values(), true, fullUpdate); + } else if (fullUpdate) { + output.mod = this._mark.items.slice(); + } + } else { + data = dl.isFunction(this._def.from) ? this._def.from() : [Sentinel]; + join.call(this, input, output, data); + } + + // Stash output before Bounder for downstream reactive geometry. + this._output = output = this._graph.evaluate(output, this._encoder); + + // Add any new scale references to the dependency list, and ensure + // they're connected. + if (update.nested && update.nested.length && this._status === CONNECTED) { + dl.keys(this._mark._scaleRefs).forEach(function(s) { + var scale = self._parent.scale(s); + if (!scale) return; + + scale.addListener(self); + self.dependency(Deps.SCALES, s); + self._encoder.dependency(Deps.SCALES, s); + }); + } + + // Supernodes calculate bounds too, but only on items marked dirty. + if (this._isSuper) { + output.mod = output.mod.filter(function(x) { return x._dirty; }); + output = this._graph.evaluate(output, this._bounder); + } + + return output; +}; + +function newItem() { + var item = Tuple.ingest(new Item(this._mark)); + + // For the root node's item + if (this._def.width) Tuple.set(item, 'width', this._def.width); + if (this._def.height) Tuple.set(item, 'height', this._def.height); + return item; +} + +function join(input, output, data, ds, fullUpdate) { + var keyf = keyFunction(this._def.key || (ds ? '_id' : null)), + prev = this._mark.items || [], + rem = ds ? input.rem : prev, + mod = Tuple.idMap((!ds || fullUpdate) ? data : input.mod), + next = [], + i, key, len, item, datum, enter, diff; + + // Only mark rems as exiting. Due to keyf, there may be an add/mod + // tuple that replaces it. + for (i=0, len=rem.length; i0) s += '|'; + s += String(f[i](d)); + } + return s; + }; +} + +module.exports = Builder; +},{"../parse/data":94,"./Bounder":109,"./Encoder":111,"datalib":26,"vega-dataflow":41,"vega-logging":47,"vega-scenegraph":48}],111:[function(require,module,exports){ +var dl = require('datalib'), + log = require('vega-logging'), + df = require('vega-dataflow'), + Node = df.Node, // jshint ignore:line + Deps = df.Dependencies, + bound = require('vega-scenegraph').bound; + +var EMPTY = {}; + +function Encoder(graph, mark, builder) { + var props = mark.def.properties || {}, + enter = props.enter, + update = props.update, + exit = props.exit; + + Node.prototype.init.call(this, graph); + + this._mark = mark; + this._builder = builder; + var s = this._scales = []; + + // Only scales used in the 'update' property set are set as + // encoder depedencies to have targeted reevaluations. However, + // we still want scales in 'enter' and 'exit' to be evaluated + // before the encoder. + if (enter) s.push.apply(s, enter.scales); + + if (update) { + this.dependency(Deps.DATA, update.data); + this.dependency(Deps.SIGNALS, update.signals); + this.dependency(Deps.FIELDS, update.fields); + this.dependency(Deps.SCALES, update.scales); + s.push.apply(s, update.scales); + } + + if (exit) s.push.apply(s, exit.scales); + + return this.mutates(true); +} + +var proto = (Encoder.prototype = new Node()); + +proto.evaluate = function(input) { + log.debug(input, ['encoding', this._mark.def.type]); + var graph = this._graph, + props = this._mark.def.properties || {}, + items = this._mark.items, + enter = props.enter, + update = props.update, + exit = props.exit, + dirty = input.dirty, + preds = graph.predicates(), + req = input.request, + group = this._mark.group, + guide = group && (group.mark.axis || group.mark.legend), + db = EMPTY, sg = EMPTY, i, len, item, prop; + + if (req && !guide) { + if ((prop = props[req]) && input.mod.length) { + db = prop.data ? graph.values(Deps.DATA, prop.data) : null; + sg = prop.signals ? graph.values(Deps.SIGNALS, prop.signals) : null; + + for (i=0, len=input.mod.length; i this._stamp) return true; + } + + return false; +} + +// Short-circuit encoder if user specifies items +Encoder.update = function(graph, trans, request, items, dirty) { + items = dl.array(items); + var preds = graph.predicates(), + db = graph.values(Deps.DATA), + sg = graph.values(Deps.SIGNALS), + i, len, item, props, prop; + + for (i=0, len=items.length; i 0, + hasAxes = dl.array(this._def.axes).length > 0, + hasLegends = dl.array(this._def.legends).length > 0, + i, j, c, len, group, pipeline, def, inline = false; + + for (i=0, len=input.add.length; i=0; --i) { + group = input.add[i]; + for (j=this._children[group._id].length-1; j>=0; --j) { + c = this._children[group._id][j]; + c.builder.connect(); + pipeline = c.builder.pipeline(); + def = c.builder._def; + + // This new child needs to be built during this propagation cycle. + // We could add its builder as a listener off the _recursor node, + // but try to inline it if we can to minimize graph dispatches. + inline = (def.type !== Types.GROUP); + inline = inline && (this._graph.data(c.from) !== undefined); + inline = inline && (pipeline[pipeline.length-1].listeners().length === 1); // Reactive geom source + inline = inline && (def.from && !def.from.mark); // Reactive geom target + c.inline = inline; + + if (inline) this._graph.evaluate(input, c.builder); + else this._recursor.addListener(c.builder); + } + } + + function removeTemp(c) { + if (c.type == Types.MARK && !c.inline && + builder._graph.data(c.from) !== undefined) { + builder._recursor.removeListener(c.builder); + } + } + + function updateAxis(a) { + var scale = a.scale(); + if (!input.scales[scale.scaleName]) return; + a.reset().def(); + } + + function updateLegend(l) { + var scale = l.size() || l.shape() || l.fill() || l.stroke(); + if (!input.scales[scale.scaleName]) return; + l.reset().def(); + } + + for (i=0, len=input.mod.length; i rng[1]) { + start = rng[1] || 0; + rng = [start + (bw * len + space), start]; + } else { + start = rng[0] || 0; + rng = [start, start + (bw * len + space)]; + } + } + + str = typeof rng[0] === 'string'; + if (str || rng.length > 2 || rng.length===1 || dataDrivenRange) { + scale.range(rng); // color or shape values + spatial = false; + } else if (points && round) { + scale.rangeRoundPoints(rng, pad); + } else if (points) { + scale.rangePoints(rng, pad); + } else if (round) { + scale.rangeRoundBands(rng, pad, outer); + } else { + scale.rangeBands(rng, pad, outer); + } + + prev.range = rng; + this._updated = true; + } + + if (!scale.invert && spatial) invertOrdinal(scale); +} + +// "Polyfill" ordinal scale inversion. Currently, only ordinal scales +// with ordered numeric ranges are supported. +var bisect = d3.bisector(dl.numcmp).right, + findAsc = function(a, x) { return bisect(a,x) - 1; }, + findDsc = d3.bisector(function(a,b) { return -1 * dl.numcmp(a,b); }).left; + +function invertOrdinal(scale) { + scale.invert = function(x, y) { + var rng = scale.range(), + asc = rng[0] < rng[1], + find = asc ? findAsc : findDsc; + + if (arguments.length === 1) { + if (!dl.isNumber(x)) { + throw Error('Ordinal scale inversion is only supported for numeric input ('+x+').'); + } + return scale.domain()[find(rng, x)]; + + } else if (arguments.length === 2) { // Invert extents + if (!dl.isNumber(x) || !dl.isNumber(y)) { + throw Error('Extents to ordinal invert are not numbers ('+x+', '+y+').'); + } + + var domain = scale.domain(), + a = find(rng, x), + b = find(rng, y), + n = rng.length - 1, r; + if (b < a) { r = a; a = b; b = a; } // ensure a <= b + if (a < 0) a = 0; + if (b > n) b = n; + + return (asc ? dl.range(a, b+1) : dl.range(b, a-1, -1)) + .map(function(i) { return domain[i]; }); + } + }; +} + +function quantitative(scale, rng, group) { + var def = this._def, + prev = scale._prev, + round = signal.call(this, def.round), + exponent = signal.call(this, def.exponent), + clamp = signal.call(this, def.clamp), + nice = signal.call(this, def.nice), + domain, interval; + + // domain + domain = (def.type === Types.QUANTILE) ? + dataRef.call(this, DataRef.DOMAIN, def.domain, scale, group) : + domainMinMax.call(this, scale, group); + if (domain && !dl.equal(prev.domain, domain)) { + scale.domain(domain); + prev.domain = domain; + this._updated = true; + } + + // range + // vertical scales should flip by default, so use XOR here + if (signal.call(this, def.range) === 'height') rng = rng.reverse(); + if (dl.equal(prev.range, rng)) return; + scale[round && scale.rangeRound ? 'rangeRound' : 'range'](rng); + prev.range = rng; + this._updated = true; + + // TODO: Support signals for these properties. Until then, only eval + // them once. + if (this._stamp > 0) return; + if (exponent && def.type===Types.POWER) scale.exponent(exponent); + if (clamp) scale.clamp(true); + if (nice) { + if (def.type === Types.TIME) { + interval = d3.time[nice]; + if (!interval) log.error('Unrecognized interval: ' + interval); + scale.nice(interval); + } else { + scale.nice(); + } + } +} + +function isUniques(scale) { + return scale.type === Types.ORDINAL || scale.type === Types.QUANTILE; +} + +function getRefs(def) { + return def.fields || dl.array(def); +} + +function inherits(refs) { + return refs.some(function(r) { + if (!r.data) return true; + return r.data && dl.array(r.field).some(function(f) { + return f.parent; + }); + }); +} + +function getFields(ref, group) { + return dl.array(ref.field).map(function(f) { + return f.parent ? + dl.accessor(f.parent)(group.datum) : + f; // String or {'signal'} + }); +} + +// Scale datarefs can be computed over multiple schema types. +// This function determines the type of aggregator created, and +// what data is sent to it: values, tuples, or multi-tuples that must +// be standardized into a consistent schema. +function aggrType(def, scale) { + var refs = getRefs(def); + + // If we're operating over only a single domain, send full tuples + // through for efficiency (fewer accessor creations/calls) + if (refs.length == 1 && dl.array(refs[0].field).length == 1) { + return Aggregate.TYPES.TUPLE; + } + + // With quantitative scales, we only care about min/max. + if (!isUniques(scale)) return Aggregate.TYPES.VALUE; + + // If we don't sort, then we can send values directly to aggrs as well + if (!dl.isObject(def.sort)) return Aggregate.TYPES.VALUE; + + return Aggregate.TYPES.MULTI; +} + +function getCache(which, def, scale, group) { + var refs = getRefs(def), + inherit = inherits(refs), + atype = aggrType(def, scale), + uniques = isUniques(scale), + sort = def.sort, + ck = '_'+which, + fields = getFields(refs[0], group); + + if (scale[ck] || this[ck]) return scale[ck] || this[ck]; + + var cache = new Aggregate(this._graph).type(atype), + groupby, summarize; + + // If a scale's dataref doesn't inherit data from the group, we can + // store the dataref aggregator at the Scale (dataflow node) level. + if (inherit) { + scale[ck] = cache; + } else { + this[ck] = cache; + } + + if (uniques) { + if (atype === Aggregate.TYPES.VALUE) { + groupby = [{ name: DataRef.GROUPBY, get: dl.identity }]; + summarize = {'*': DataRef.COUNT}; + } else if (atype === Aggregate.TYPES.TUPLE) { + groupby = [{ name: DataRef.GROUPBY, get: dl.$(fields[0]) }]; + summarize = dl.isObject(sort) ? [{ + field: DataRef.VALUE, + get: dl.$(sort.field), + ops: [sort.op] + }] : {'*': DataRef.COUNT}; + } else { // atype === Aggregate.TYPES.MULTI + groupby = DataRef.GROUPBY; + summarize = [{ field: DataRef.VALUE, ops: [sort.op] }]; + } + } else { + groupby = []; + summarize = [{ + field: DataRef.VALUE, + get: (atype == Aggregate.TYPES.TUPLE) ? dl.$(fields[0]) : dl.identity, + ops: [DataRef.MIN, DataRef.MAX], + as: [DataRef.MIN, DataRef.MAX] + }]; + } + + cache.param('groupby', groupby) + .param('summarize', summarize); + + return (cache._lastUpdate = -1, cache); +} + +function dataRef(which, def, scale, group) { + if (def == null) { return []; } + if (dl.isArray(def)) return def.map(signal.bind(this)); + + var self = this, graph = this._graph, + refs = getRefs(def), + inherit = inherits(refs), + atype = aggrType(def, scale), + cache = getCache.apply(this, arguments), + sort = def.sort, + uniques = isUniques(scale), + i, rlen, j, flen, ref, fields, field, data, from, so, cmp; + + function addDep(s) { + self.dependency(Deps.SIGNALS, s); + } + + if (inherit || (!inherit && cache._lastUpdate < this._stamp)) { + for (i=0, rlen=refs.length; i 1) f = 1; + e = curr.ease(f); + + for (i=0, n=curr.length; i 0 ? (e = 1e-12, Math.ceil) : (e = -1e-12, Math.floor), + e; + function log(x) { + return (domain[0] < 0 ? + -Math.log(x > 0 ? 0 : -x) : + Math.log(x < 0 ? 0 : x)) / Math.log(base); + } + function pow(x) { + return domain[0] < 0 ? -Math.pow(base, -x) : Math.pow(base, x); + } + return function(d) { + return pow(v(log(d) + e)) / d >= k ? f(d) : ''; + }; + } + + function getFormatter(formatType, str) { + var fmt = dl.format, + log = scale.type === 'log', + domain, f; + + switch (formatType) { + case NUMBER: + domain = scale.domain(); + f = fmt.auto.number(domain, tickCount, str || (log ? '.1r' : null)); + return log ? logFilter(domain, tickCount, f) : f; + case TIME: return (str ? fmt : fmt.auto).time(str); + case UTC: return (str ? fmt : fmt.auto).utc(str); + default: return String; + } + } + + function getTicks(format) { + var major = tickValues || (scale.ticks ? scale.ticks(tickCount) : scale.domain()), + minor = axisSubdivide(scale, major, tickSubdivide).map(ingest); + major = major.map(function(d) { return (d = ingest(d), d.label = format(d.data), d); }); + return [major, minor]; + } + + axis.def = function() { + if (!axisDef.type) axis_def(scale); + + var ticks = getTicks(getTickFormat()); + var tdata = title ? [title].map(ingest) : []; + + axisDef.marks[0].from = function() { return grid ? ticks[0] : []; }; + axisDef.marks[1].from = function() { return ticks[0]; }; + axisDef.marks[2].from = function() { return ticks[1]; }; + axisDef.marks[3].from = axisDef.marks[1].from; + axisDef.marks[4].from = function() { return [1]; }; + axisDef.marks[5].from = function() { return tdata; }; + axisDef.offset = offset; + axisDef.orient = orient; + axisDef.layer = layer; + if (titleOffset === 'auto') titleAutoOffset(axisDef); + + return axisDef; + }; + + function titleAutoOffset(axisDef) { + var orient = axisDef.orient, + update = axisDef.marks[5].properties.update, + fn = update.encode, + min = config.titleOffsetAutoMin, + max = config.titleOffsetAutoMax, + pad = config.titleOffsetAutoMargin; + + // Offset axis title using bounding box of axis domain and labels + // Assumes other components are **encoded and bounded** beforehand + update.encode = function(item, group, trans, db, signals, preds) { + var dirty = fn.call(fn, item, group, trans, db, signals, preds), + field = (orient==='bottom' || orient==='top') ? 'y' : 'x'; + if (titleStyle[field] != null) return dirty; + + axisBounds.clear() + .union(group.items[3].bounds) + .union(group.items[4].bounds); + + var o = trans ? {} : item, + method = (orient==='left' || orient==='right') ? 'width' : 'height', + sign = (orient==='top' || orient==='left') ? -1 : 1, + off = ~~(axisBounds[method]() + item.fontSize/2 + pad); + + Tuple.set(o, field, sign * Math.min(Math.max(min, off), max)); + if (trans) trans.interpolate(item, o); + return true; + }; + } + + function axis_def(scale) { + // setup scale mapping + var newScale, oldScale, range; + if (scale.type === ORDINAL) { + newScale = {scale: scale.scaleName, offset: 0.5 + scale.rangeBand()/2}; + oldScale = newScale; + } else { + newScale = {scale: scale.scaleName, offset: 0.5}; + oldScale = {scale: scale.scaleName+':prev', offset: 0.5}; + } + range = axisScaleRange(scale); + + // setup axis marks + dl.extend(m.gridLines, axisTicks(config)); + dl.extend(m.majorTicks, axisTicks(config)); + dl.extend(m.minorTicks, axisTicks(config)); + dl.extend(m.tickLabels, axisTickLabels(config)); + dl.extend(m.domain, axisDomain(config)); + dl.extend(m.title, axisTitle(config)); + m.gridLines.properties.enter.stroke = {value: config.gridColor}; + m.gridLines.properties.enter.strokeOpacity = {value: config.gridOpacity}; + + // extend axis marks based on axis orientation + axisTicksExtend(orient, m.gridLines, oldScale, newScale, Infinity); + axisTicksExtend(orient, m.majorTicks, oldScale, newScale, tickMajorSize); + axisTicksExtend(orient, m.minorTicks, oldScale, newScale, tickMinorSize); + axisLabelExtend(orient, m.tickLabels, oldScale, newScale, tickMajorSize, tickPadding); + + axisDomainExtend(orient, m.domain, range, tickEndSize); + axisTitleExtend(orient, m.title, range, +titleOffset || -1); + + // add / override custom style properties + dl.extend(m.gridLines.properties.update, gridLineStyle); + dl.extend(m.majorTicks.properties.update, majorTickStyle); + dl.extend(m.minorTicks.properties.update, minorTickStyle); + dl.extend(m.tickLabels.properties.update, tickLabelStyle); + dl.extend(m.domain.properties.update, domainStyle); + dl.extend(m.title.properties.update, titleStyle); + + var marks = [m.gridLines, m.majorTicks, m.minorTicks, m.tickLabels, m.domain, m.title]; + dl.extend(axisDef, { + type: 'group', + interactive: false, + properties: { + enter: { + encode: axisUpdate, + scales: [scale.scaleName], + signals: [], data: [] + }, + update: { + encode: axisUpdate, + scales: [scale.scaleName], + signals: [], data: [] + } + } + }); + + axisDef.marks = marks.map(function(m) { return parseMark(model, m); }); + } + + axis.scale = function(x) { + if (!arguments.length) return scale; + if (scale !== x) { scale = x; reset(); } + return axis; + }; + + axis.orient = function(x) { + if (!arguments.length) return orient; + if (orient !== x) { + orient = x in axisOrients ? x + '' : config.orient; + reset(); + } + return axis; + }; + + axis.title = function(x) { + if (!arguments.length) return title; + if (title !== x) { title = x; reset(); } + return axis; + }; + + axis.tickCount = function(x) { + if (!arguments.length) return tickCount; + tickCount = x; + return axis; + }; + + axis.tickValues = function(x) { + if (!arguments.length) return tickValues; + tickValues = x; + return axis; + }; + + axis.tickFormat = function(x) { + if (!arguments.length) return tickFormatString; + if (tickFormatString !== x) { + tickFormatString = x; + reset(); + } + return axis; + }; + + axis.tickFormatType = function(x) { + if (!arguments.length) return tickFormatType; + if (tickFormatType !== x) { + tickFormatType = x; + reset(); + } + return axis; + }; + + axis.tickSize = function(x, y) { + if (!arguments.length) return tickMajorSize; + var n = arguments.length - 1, + major = +x, + minor = n > 1 ? +y : tickMajorSize, + end = n > 0 ? +arguments[n] : tickMajorSize; + + if (tickMajorSize !== major || + tickMinorSize !== minor || + tickEndSize !== end) { + reset(); + } + + tickMajorSize = major; + tickMinorSize = minor; + tickEndSize = end; + return axis; + }; + + axis.tickSubdivide = function(x) { + if (!arguments.length) return tickSubdivide; + tickSubdivide = +x; + return axis; + }; + + axis.offset = function(x) { + if (!arguments.length) return offset; + offset = dl.isObject(x) ? x : +x; + return axis; + }; + + axis.tickPadding = function(x) { + if (!arguments.length) return tickPadding; + if (tickPadding !== +x) { tickPadding = +x; reset(); } + return axis; + }; + + axis.titleOffset = function(x) { + if (!arguments.length) return titleOffset; + if (titleOffset !== x) { titleOffset = x; reset(); } + return axis; + }; + + axis.layer = function(x) { + if (!arguments.length) return layer; + if (layer !== x) { layer = x; reset(); } + return axis; + }; + + axis.grid = function(x) { + if (!arguments.length) return grid; + if (grid !== x) { grid = x; reset(); } + return axis; + }; + + axis.gridLineProperties = function(x) { + if (!arguments.length) return gridLineStyle; + if (gridLineStyle !== x) { gridLineStyle = x; } + return axis; + }; + + axis.majorTickProperties = function(x) { + if (!arguments.length) return majorTickStyle; + if (majorTickStyle !== x) { majorTickStyle = x; } + return axis; + }; + + axis.minorTickProperties = function(x) { + if (!arguments.length) return minorTickStyle; + if (minorTickStyle !== x) { minorTickStyle = x; } + return axis; + }; + + axis.tickLabelProperties = function(x) { + if (!arguments.length) return tickLabelStyle; + if (tickLabelStyle !== x) { tickLabelStyle = x; } + return axis; + }; + + axis.titleProperties = function(x) { + if (!arguments.length) return titleStyle; + if (titleStyle !== x) { titleStyle = x; } + return axis; + }; + + axis.domainProperties = function(x) { + if (!arguments.length) return domainStyle; + if (domainStyle !== x) { domainStyle = x; } + return axis; + }; + + axis.reset = function() { + reset(); + return axis; + }; + + return axis; +} + +var axisOrients = {top: 1, right: 1, bottom: 1, left: 1}; + +function axisSubdivide(scale, ticks, m) { + var subticks = []; + if (m && ticks.length > 1) { + var extent = axisScaleExtent(scale.domain()), + i = -1, + n = ticks.length, + d = (ticks[1] - ticks[0]) / ++m, + j, + v; + while (++i < n) { + for (j = m; --j > 0;) { + if ((v = +ticks[i] - j * d) >= extent[0]) { + subticks.push(v); + } + } + } + for (--i, j = 0; ++j < m && (v = +ticks[i] + j * d) < extent[1];) { + subticks.push(v); + } + } + return subticks; +} + +function axisScaleExtent(domain) { + var start = domain[0], stop = domain[domain.length - 1]; + return start < stop ? [start, stop] : [stop, start]; +} + +function axisScaleRange(scale) { + return scale.rangeExtent ? + scale.rangeExtent() : + axisScaleExtent(scale.range()); +} + +var axisAlign = { + bottom: 'center', + top: 'center', + left: 'right', + right: 'left' +}; + +var axisBaseline = { + bottom: 'top', + top: 'bottom', + left: 'middle', + right: 'middle' +}; + +function axisLabelExtend(orient, labels, oldScale, newScale, size, pad) { + size = Math.max(size, 0) + pad; + if (orient === 'left' || orient === 'top') { + size *= -1; + } + if (orient === 'top' || orient === 'bottom') { + dl.extend(labels.properties.enter, { + x: oldScale, + y: {value: size}, + }); + dl.extend(labels.properties.update, { + x: newScale, + y: {value: size}, + align: {value: 'center'}, + baseline: {value: axisBaseline[orient]} + }); + } else { + dl.extend(labels.properties.enter, { + x: {value: size}, + y: oldScale, + }); + dl.extend(labels.properties.update, { + x: {value: size}, + y: newScale, + align: {value: axisAlign[orient]}, + baseline: {value: 'middle'} + }); + } +} + +function axisTicksExtend(orient, ticks, oldScale, newScale, size) { + var sign = (orient === 'left' || orient === 'top') ? -1 : 1; + if (size === Infinity) { + size = (orient === 'top' || orient === 'bottom') ? + {field: {group: 'height', level: 2}, mult: -sign} : + {field: {group: 'width', level: 2}, mult: -sign}; + } else { + size = {value: sign * size}; + } + if (orient === 'top' || orient === 'bottom') { + dl.extend(ticks.properties.enter, { + x: oldScale, + y: {value: 0}, + y2: size + }); + dl.extend(ticks.properties.update, { + x: newScale, + y: {value: 0}, + y2: size + }); + dl.extend(ticks.properties.exit, { + x: newScale, + }); + } else { + dl.extend(ticks.properties.enter, { + x: {value: 0}, + x2: size, + y: oldScale + }); + dl.extend(ticks.properties.update, { + x: {value: 0}, + x2: size, + y: newScale + }); + dl.extend(ticks.properties.exit, { + y: newScale, + }); + } +} + +function axisTitleExtend(orient, title, range, offset) { + var update = title.properties.update, + mid = ~~((range[0] + range[1]) / 2), + sign = (orient === 'top' || orient === 'left') ? -1 : 1; + + if (orient === 'bottom' || orient === 'top') { + update.x = {value: mid}; + update.angle = {value: 0}; + if (offset >= 0) update.y = sign * offset; + } else { + update.y = {value: mid}; + update.angle = {value: orient === 'left' ? -90 : 90}; + if (offset >= 0) update.x = sign * offset; + } +} + +function axisDomainExtend(orient, domain, range, size) { + var path; + if (orient === 'top' || orient === 'left') { + size = -1 * size; + } + if (orient === 'bottom' || orient === 'top') { + path = 'M' + range[0] + ',' + size + 'V0H' + range[1] + 'V' + size; + } else { + path = 'M' + size + ',' + range[0] + 'H0V' + range[1] + 'H' + size; + } + domain.properties.update.path = {value: path}; +} + +function axisUpdate(item, group, trans) { + var o = trans ? {} : item, + offset = item.mark.def.offset, + orient = item.mark.def.orient, + width = group.width, + height = group.height; // TODO fallback to global w,h? + + if (dl.isArray(offset)) { + var ofx = offset[0], + ofy = offset[1]; + + switch (orient) { + case 'left': { Tuple.set(o, 'x', -ofx); Tuple.set(o, 'y', ofy); break; } + case 'right': { Tuple.set(o, 'x', width + ofx); Tuple.set(o, 'y', ofy); break; } + case 'bottom': { Tuple.set(o, 'x', ofx); Tuple.set(o, 'y', height + ofy); break; } + case 'top': { Tuple.set(o, 'x', ofx); Tuple.set(o, 'y', -ofy); break; } + default: { Tuple.set(o, 'x', ofx); Tuple.set(o, 'y', ofy); } + } + } else { + if (dl.isObject(offset)) { + offset = -group.scale(offset.scale)(offset.value); + } + + switch (orient) { + case 'left': { Tuple.set(o, 'x', -offset); Tuple.set(o, 'y', 0); break; } + case 'right': { Tuple.set(o, 'x', width + offset); Tuple.set(o, 'y', 0); break; } + case 'bottom': { Tuple.set(o, 'x', 0); Tuple.set(o, 'y', height + offset); break; } + case 'top': { Tuple.set(o, 'x', 0); Tuple.set(o, 'y', -offset); break; } + default: { Tuple.set(o, 'x', 0); Tuple.set(o, 'y', 0); } + } + } + + if (trans) trans.interpolate(item, o); + return true; +} + +function axisTicks(config) { + return { + type: 'rule', + interactive: false, + key: 'data', + properties: { + enter: { + stroke: {value: config.tickColor}, + strokeWidth: {value: config.tickWidth}, + opacity: {value: 1e-6} + }, + exit: { opacity: {value: 1e-6} }, + update: { opacity: {value: 1} } + } + }; +} + +function axisTickLabels(config) { + return { + type: 'text', + interactive: true, + key: 'data', + properties: { + enter: { + fill: {value: config.tickLabelColor}, + font: {value: config.tickLabelFont}, + fontSize: {value: config.tickLabelFontSize}, + opacity: {value: 1e-6}, + text: {field: 'label'} + }, + exit: { opacity: {value: 1e-6} }, + update: { opacity: {value: 1} } + } + }; +} + +function axisTitle(config) { + return { + type: 'text', + interactive: true, + properties: { + enter: { + font: {value: config.titleFont}, + fontSize: {value: config.titleFontSize}, + fontWeight: {value: config.titleFontWeight}, + fill: {value: config.titleColor}, + align: {value: 'center'}, + baseline: {value: 'middle'}, + text: {field: 'data'} + }, + update: {} + } + }; +} + +function axisDomain(config) { + return { + type: 'path', + interactive: false, + properties: { + enter: { + x: {value: 0.5}, + y: {value: 0.5}, + stroke: {value: config.axisColor}, + strokeWidth: {value: config.axisWidth} + }, + update: {} + } + }; +} + +module.exports = axs; +},{"../parse/mark":99,"datalib":26,"vega-dataflow":41,"vega-scenegraph":48}],116:[function(require,module,exports){ +(function (global){ +var d3 = (typeof window !== "undefined" ? window['d3'] : typeof global !== "undefined" ? global['d3'] : null), + dl = require('datalib'), + Gradient = require('vega-scenegraph').Gradient, + parseProperties = require('../parse/properties'), + parseMark = require('../parse/mark'); + +function lgnd(model) { + var size = null, + shape = null, + fill = null, + stroke = null, + spacing = null, + values = null, + format = null, + formatString = null, + config = model.config().legend, + title, + orient = config.orient, + offset = config.offset, + padding = config.padding, + tickArguments = [5], + legendStyle = {}, + symbolStyle = {}, + gradientStyle = {}, + titleStyle = {}, + labelStyle = {}, + m = { // Legend marks as references for updates + titles: {}, + symbols: {}, + labels: {}, + gradient: {} + }; + + var legend = {}, + legendDef = {}; + + function reset() { legendDef.type = null; } + function ingest(d, i) { return {data: d, index: i}; } + + legend.def = function() { + var scale = size || shape || fill || stroke; + + format = !formatString ? null : ((scale.type === 'time') ? + dl.format.time(formatString) : dl.format.number(formatString)); + + if (!legendDef.type) { + legendDef = (scale===fill || scale===stroke) && !discrete(scale.type) ? + quantDef(scale) : ordinalDef(scale); + } + legendDef.orient = orient; + legendDef.offset = offset; + legendDef.padding = padding; + legendDef.margin = config.margin; + return legendDef; + }; + + function discrete(type) { + return type==='ordinal' || type==='quantize' || + type==='quantile' || type==='threshold'; + } + + function ordinalDef(scale) { + var def = o_legend_def(size, shape, fill, stroke); + + // generate data + var data = (values == null ? + (scale.ticks ? scale.ticks.apply(scale, tickArguments) : scale.domain()) : + values).map(ingest); + var fmt = format==null ? (scale.tickFormat ? scale.tickFormat.apply(scale, tickArguments) : String) : format; + + // determine spacing between legend entries + var fs, range, offset, pad=5, domain = d3.range(data.length); + if (size) { + range = data.map(function(x) { return Math.sqrt(size(x.data)); }); + offset = d3.max(range); + range = range.reduce(function(a,b,i,z) { + if (i > 0) a[i] = a[i-1] + z[i-1]/2 + pad; + return (a[i] += b/2, a); }, [0]).map(Math.round); + } else { + offset = Math.round(Math.sqrt(config.symbolSize)); + range = spacing || + (fs = labelStyle.fontSize) && (fs.value + pad) || + (config.labelFontSize + pad); + range = domain.map(function(d,i) { + return Math.round(offset/2 + i*range); + }); + } + + // account for padding and title size + var sz = padding, ts; + if (title) { + ts = titleStyle.fontSize; + sz += 5 + ((ts && ts.value) || config.titleFontSize); + } + for (var i=0, n=range.length; i'}, + summarize: { + type: 'custom', + set: function(summarize) { + var signalDeps = {}, + tx = this._transform, + i, len, f, fields, name, ops; + + if (!dl.isArray(fields = summarize)) { // Object syntax from dl + fields = []; + for (name in summarize) { + ops = dl.array(summarize[name]); + fields.push({field: name, ops: ops}); + } + } + + function sg(x) { if (x.signal) signalDeps[x.signal] = 1; } + + for (i=0, len=fields.length; i', default: [5, 2]} + }); + + this._output = { + start: 'bin_start', + end: 'bin_end', + mid: 'bin_mid' + }; + return this.mutates(true); +} + +var prototype = (Bin.prototype = Object.create(BatchTransform.prototype)); +prototype.constructor = Bin; + +prototype.extent = function(data) { + // TODO only recompute extent upon data or field change? + var e = [this.param('min'), this.param('max')], d; + if (e[0] == null || e[1] == null) { + d = dl.extent(data, this.param('field').accessor); + if (e[0] == null) e[0] = d[0]; + if (e[1] == null) e[1] = d[1]; + } + return e; +}; + +prototype.batchTransform = function(input, data) { + log.debug(input, ['binning']); + + var extent = this.extent(data), + output = this._output, + step = this.param('step'), + steps = this.param('steps'), + minstep = this.param('minstep'), + get = this.param('field').accessor, + opt = { + min: extent[0], + max: extent[1], + base: this.param('base'), + maxbins: this.param('maxbins'), + div: this.param('div') + }; + + if (step) opt.step = step; + if (steps) opt.steps = steps; + if (minstep) opt.minstep = minstep; + var b = dl.bins(opt), + s = b.step; + + function update(d) { + var v = get(d); + v = v == null ? null + : b.start + s * ~~((v - b.start) / s); + Tuple.set(d, output.start, v); + Tuple.set(d, output.end, v + s); + Tuple.set(d, output.mid, v + s/2); + } + input.add.forEach(update); + input.mod.forEach(update); + input.rem.forEach(update); + + input.fields[output.start] = 1; + input.fields[output.end] = 1; + input.fields[output.mid] = 1; + return input; +}; + +module.exports = Bin; +},{"./BatchTransform":119,"./Transform":139,"datalib":26,"vega-dataflow":41,"vega-logging":47}],121:[function(require,module,exports){ +var df = require('vega-dataflow'), + Tuple = df.Tuple, + log = require('vega-logging'), + Transform = require('./Transform'); + +function CountPattern(graph) { + Transform.prototype.init.call(this, graph); + Transform.addParameters(this, { + field: {type: 'field', default: 'data'}, + pattern: {type: 'value', default: '[\\w\']+'}, + case: {type: 'value', default: 'lower'}, + stopwords: {type: 'value', default: ''} + }); + + this._output = {text: 'text', count: 'count'}; + + return this.router(true).produces(true); +} + +var prototype = (CountPattern.prototype = Object.create(Transform.prototype)); +prototype.constructor = CountPattern; + +prototype.transform = function(input, reset) { + log.debug(input, ['countpattern']); + + var get = this.param('field').accessor, + pattern = this.param('pattern'), + stop = this.param('stopwords'), + rem = false; + + // update parameters + if (this._stop !== stop) { + this._stop = stop; + this._stop_re = new RegExp('^' + stop + '$', 'i'); + reset = true; + } + + if (this._pattern !== pattern) { + this._pattern = pattern; + this._match = new RegExp(this._pattern, 'g'); + reset = true; + } + + if (reset) this._counts = {}; + + function curr(t) { return (Tuple.prev_init(t), get(t)); } + function prev(t) { return get(Tuple.prev(t)); } + + this._add(input.add, curr); + if (!reset) this._rem(input.rem, prev); + if (reset || (rem = input.fields[get.field])) { + if (rem) this._rem(input.mod, prev); + this._add(input.mod, curr); + } + + // generate output tuples + return this._changeset(input); +}; + +prototype._changeset = function(input) { + var counts = this._counts, + tuples = this._tuples || (this._tuples = {}), + change = df.ChangeSet.create(input), + out = this._output, w, t, c; + + for (w in counts) { + t = tuples[w]; + c = counts[w] || 0; + if (!t && c) { + tuples[w] = (t = Tuple.ingest({})); + t[out.text] = w; + t[out.count] = c; + change.add.push(t); + } else if (c === 0) { + if (t) change.rem.push(t); + delete counts[w]; + delete tuples[w]; + } else if (t[out.count] !== c) { + Tuple.set(t, out.count, c); + change.mod.push(t); + } + } + return change; +}; + +prototype._tokenize = function(text) { + switch (this.param('case')) { + case 'upper': text = text.toUpperCase(); break; + case 'lower': text = text.toLowerCase(); break; + } + return text.match(this._match); +}; + +prototype._add = function(tuples, get) { + var counts = this._counts, + stop = this._stop_re, + tok, i, j, t; + + for (j=0; j cross.s, + fltrd = !cross || cross.f, + omod = output.mod, + orem = output.rem, + i, t, y; + + // If we have cached values, iterate through them for lazy + // removal, and to re-run the filter. + if (tpls) { + if (lazy || test) { + for (i=tpls.length-1; i>=0; --i) { + t = tpls[i]; + y = t[other]; + + if (lazy && !cache[y._id]) { + tpls.splice(i, 1); + continue; + } + + if (!test || test(t)) { + omod.push(t); + } else { + orem.push.apply(orem, tpls.splice(i, 1)); + cross.f = true; + } + } + + cross.s = this._lastRem; + } else { + omod.push.apply(omod, tpls); + } + } + + // If we have a filter param, call add to catch any tuples that may + // have previously been filtered. + if (test && fltrd) add.call(this, output, left, data, diag, test, x); +} + +function rem(output, x) { + var cross = this._cache[x._id]; + if (!cross) return; + output.rem.push.apply(output.rem, cross.c); + this._cache[x._id] = null; + this._lastRem = this._stamp; +} + +function purge(output) { + var cache = this._cache, + keys = dl.keys(cache), + rem = output.rem, + ids = {}, + i, len, j, jlen, cross, t; + + for (i=0, len=keys.length; i this._lastWith) { + woutput.rem.forEach(r); + woutput.add.forEach(add.bind(this, output, false, data, diag, test)); + woutput.mod.forEach(mod.bind(this, output, false, data, diag, test)); + this._lastWith = woutput.stamp; + } + + // Mods need to come after all removals have been run. + input.mod.forEach(mod.bind(this, output, true, wdata, diag, test)); + } + + output.fields[as.left] = 1; + output.fields[as.right] = 1; + return output; +}; + +module.exports = Cross; +},{"./BatchTransform":119,"./Transform":139,"datalib":26,"vega-dataflow":41,"vega-logging":47}],123:[function(require,module,exports){ +var Transform = require('./Transform'), + Aggregate = require('./Aggregate'); + +function Facet(graph) { + Transform.addParameters(this, { + transform: { + type: "custom", + set: function(pipeline) { + return (this._transform._pipeline = pipeline, this._transform); + }, + get: function() { + var parse = require('../parse/transforms'), + facet = this._transform; + return facet._pipeline.map(function(t) { + return parse(facet._graph, t); + }); + } + } + }); + + this._pipeline = []; + return Aggregate.call(this, graph); +} + +var prototype = (Facet.prototype = Object.create(Aggregate.prototype)); +prototype.constructor = Facet; + +prototype.aggr = function() { + return Aggregate.prototype.aggr.call(this).facet(this); +}; + +prototype.transform = function(input, reset) { + var output = Aggregate.prototype.transform.call(this, input, reset); + + // New facet cells should trigger a re-ranking of the dataflow graph. + // This ensures facet datasources are computed before scenegraph nodes. + // We rerank the Facet's first listener, which is the next node in the + // datasource's pipeline. + if (input.add.length) { + this.listeners()[0].rerank(); + } + + return output; +}; + +module.exports = Facet; +},{"../parse/transforms":108,"./Aggregate":118,"./Transform":139}],124:[function(require,module,exports){ +var dl = require('datalib'), + Aggregator = dl.Aggregator, + Base = Aggregator.prototype, + df = require('vega-dataflow'), + Tuple = df.Tuple, + log = require('vega-logging'), + facetID = 0; + +function Facetor() { + Aggregator.call(this); + this._facet = null; + this._facetID = ++facetID; +} + +var prototype = (Facetor.prototype = Object.create(Base)); +prototype.constructor = Facetor; + +prototype.facet = function(f) { + return arguments.length ? (this._facet = f, this) : this._facet; +}; + +prototype._ingest = function(t) { + return Tuple.ingest(t, null); +}; + +prototype._assign = Tuple.set; + +function disconnect_cell(facet) { + log.debug({}, ['disconnecting cell', this.tuple._id]); + var pipeline = this.ds.pipeline(); + facet.removeListener(pipeline[0]); + facet._graph.removeListener(pipeline[0]); + facet._graph.disconnect(pipeline); +} + +prototype._newcell = function(x, key) { + var cell = Base._newcell.call(this, x, key), + facet = this._facet; + + if (facet) { + var graph = facet._graph, + tuple = cell.tuple, + pipeline = facet.param('transform'); + cell.ds = graph.data(tuple._facetID, pipeline, tuple); + cell.disconnect = disconnect_cell; + facet.addListener(pipeline[0]); + } + + return cell; +}; + +prototype._newtuple = function(x, key) { + var t = Base._newtuple.call(this, x); + if (this._facet) { + Tuple.set(t, 'key', key); + Tuple.set(t, '_facetID', this._facetID + '_' + key); + } + return t; +}; + +prototype.clear = function() { + if (this._facet) { + for (var k in this._cells) { + this._cells[k].disconnect(this._facet); + } + } + return Base.clear.call(this); +}; + +prototype._on_add = function(x, cell) { + if (this._facet) cell.ds._input.add.push(x); +}; + +prototype._on_rem = function(x, cell) { + if (this._facet) cell.ds._input.rem.push(x); +}; + +prototype._on_mod = function(x, prev, cell0, cell1) { + if (this._facet) { // Propagate tuples + if (cell0 === cell1) { + cell0.ds._input.mod.push(x); + } else { + cell0.ds._input.rem.push(x); + cell1.ds._input.add.push(x); + } + } +}; + +prototype._on_drop = function(cell) { + if (this._facet) cell.disconnect(this._facet); +}; + +prototype._on_keep = function(cell) { + // propagate sort, signals, fields, etc. + if (this._facet) df.ChangeSet.copy(this._input, cell.ds._input); +}; + +module.exports = Facetor; +},{"datalib":26,"vega-dataflow":41,"vega-logging":47}],125:[function(require,module,exports){ +var df = require('vega-dataflow'), + SIGNALS = df.Dependencies.SIGNALS, + log = require('vega-logging'), + Transform = require('./Transform'); + +function Filter(graph) { + Transform.prototype.init.call(this, graph); + Transform.addParameters(this, {test: {type: 'expr'}}); + + this._skip = {}; + return this.router(true); +} + +var prototype = (Filter.prototype = Object.create(Transform.prototype)); +prototype.constructor = Filter; + +prototype.transform = function(input) { + log.debug(input, ['filtering']); + + var output = df.ChangeSet.create(input), + graph = this._graph, + skip = this._skip, + test = this.param('test'), + signals = graph.values(SIGNALS, this.dependency(SIGNALS)); + + input.rem.forEach(function(x) { + if (skip[x._id] !== 1) output.rem.push(x); + else skip[x._id] = 0; + }); + + input.add.forEach(function(x) { + if (test(x, null, signals)) output.add.push(x); + else skip[x._id] = 1; + }); + + input.mod.forEach(function(x) { + var b = test(x, null, signals), + s = (skip[x._id] === 1); + if (b && s) { + skip[x._id] = 0; + output.add.push(x); + } else if (b && !s) { + output.mod.push(x); + } else if (!b && s) { + // do nothing, keep skip true + } else { // !b && !s + output.rem.push(x); + skip[x._id] = 1; + } + }); + + return output; +}; + +module.exports = Filter; +},{"./Transform":139,"vega-dataflow":41,"vega-logging":47}],126:[function(require,module,exports){ +var df = require('vega-dataflow'), + Tuple = df.Tuple, + log = require('vega-logging'), + Transform = require('./Transform'); + +function Fold(graph) { + Transform.prototype.init.call(this, graph); + Transform.addParameters(this, { + fields: {type: 'array'} + }); + + this._output = {key: 'key', value: 'value'}; + this._cache = {}; + + return this.router(true).produces(true); +} + +var prototype = (Fold.prototype = Object.create(Transform.prototype)); +prototype.constructor = Fold; + +prototype._reset = function(input, output) { + for (var id in this._cache) { + output.rem.push.apply(output.rem, this._cache[id]); + } + this._cache = {}; +}; + +prototype._tuple = function(x, i, len) { + var list = this._cache[x._id] || (this._cache[x._id] = Array(len)); + return list[i] ? Tuple.rederive(x, list[i]) : (list[i] = Tuple.derive(x)); +}; + +prototype._fn = function(data, on, out) { + var i, j, n, m, d, t; + for (i=0, n=data.length; i', default: require('./screen').size}, + bound: {type: 'value', default: true}, + links: {type: 'data'}, + + // TODO: for now force these to be value params only (pun-intended) + // Can update to include fields after Parameter refactoring. + linkStrength: {type: 'value', default: 1}, + linkDistance: {type: 'value', default: 20}, + charge: {type: 'value', default: -30}, + + chargeDistance: {type: 'value', default: Infinity}, + friction: {type: 'value', default: 0.9}, + theta: {type: 'value', default: 0.8}, + gravity: {type: 'value', default: 0.1}, + alpha: {type: 'value', default: 0.1}, + iterations: {type: 'value', default: 500}, + + interactive: {type: 'value', default: this._interactive}, + active: {type: 'value', default: this._prev}, + fixed: {type: 'data'} + }); + + this._output = { + 'x': 'layout_x', + 'y': 'layout_y' + }; + + return this.mutates(true); +} + +var prototype = (Force.prototype = Object.create(Transform.prototype)); +prototype.constructor = Force; + +prototype.transform = function(nodeInput, reset) { + log.debug(nodeInput, ['force']); + reset = reset - (nodeInput.signals.active ? 1 : 0); + + // get variables + var interactive = this.param('interactive'), + linkSource = this.param('links').source, + linkInput = linkSource.last(), + active = this.param('active'), + output = this._output, + layout = this._layout, + nodes = this._nodes, + links = this._links; + + // configure nodes, links and layout + if (linkInput.stamp < nodeInput.stamp) linkInput = null; + this.configure(nodeInput, linkInput, interactive, reset); + + // run batch layout + if (!interactive) { + var iterations = this.param('iterations'); + for (var i=0; i'}, + translate: {type: 'array', default: require('./screen').center}, + rotate: {type: 'array'}, + scale: {type: 'value'}, + precision: {type: 'value'}, + clipAngle: {type: 'value'}, + clipExtent: {type: 'value'} +}; + +Geo.d3Projection = function() { + var p = this.param('projection'), + param = Geo.Parameters, + proj, name, value; + + if (p !== this._mode) { + this._mode = p; + this._projection = d3.geo[p](); + } + proj = this._projection; + + for (name in param) { + if (name === 'projection' || !proj[name]) continue; + value = this.param(name); + if (value === undefined || (dl.isArray(value) && value.length === 0)) { + continue; + } + if (value !== proj[name]()) { + proj[name](value); + } + } + + return proj; +}; + +var prototype = (Geo.prototype = Object.create(Transform.prototype)); +prototype.constructor = Geo; + +prototype.transform = function(input) { + log.debug(input, ['geo']); + + var output = this._output, + lon = this.param('lon').accessor, + lat = this.param('lat').accessor, + proj = Geo.d3Projection.call(this); + + function set(t) { + var ll = [lon(t), lat(t)]; + var xy = proj(ll) || [null, null]; + Tuple.set(t, output.x, xy[0]); + Tuple.set(t, output.y, xy[1]); + } + + input.add.forEach(set); + if (this.reevaluate(input)) { + input.mod.forEach(set); + input.rem.forEach(set); + } + + input.fields[output.x] = 1; + input.fields[output.y] = 1; + return input; +}; + +module.exports = Geo; +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"./Transform":139,"./screen":145,"datalib":26,"vega-dataflow":41,"vega-logging":47}],130:[function(require,module,exports){ +(function (global){ +var d3 = (typeof window !== "undefined" ? window['d3'] : typeof global !== "undefined" ? global['d3'] : null), + dl = require('datalib'), + Tuple = require('vega-dataflow').Tuple, + log = require('vega-logging'), + Geo = require('./Geo'), + Transform = require('./Transform'); + +function GeoPath(graph) { + Transform.prototype.init.call(this, graph); + Transform.addParameters(this, Geo.Parameters); + Transform.addParameters(this, { + field: {type: 'field', default: null}, + }); + + this._output = { + 'path': 'layout_path' + }; + return this.mutates(true); +} + +var prototype = (GeoPath.prototype = Object.create(Transform.prototype)); +prototype.constructor = GeoPath; + +prototype.transform = function(input) { + log.debug(input, ['geopath']); + + var output = this._output, + geojson = this.param('field').accessor || dl.identity, + proj = Geo.d3Projection.call(this), + path = d3.geo.path().projection(proj); + + function set(t) { + Tuple.set(t, output.path, path(geojson(t))); + } + + input.add.forEach(set); + if (this.reevaluate(input)) { + input.mod.forEach(set); + input.rem.forEach(set); + } + + input.fields[output.path] = 1; + return input; +}; + +module.exports = GeoPath; +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"./Geo":129,"./Transform":139,"datalib":26,"vega-dataflow":41,"vega-logging":47}],131:[function(require,module,exports){ +(function (global){ +var d3 = (typeof window !== "undefined" ? window['d3'] : typeof global !== "undefined" ? global['d3'] : null), + dl = require('datalib'), + Tuple = require('vega-dataflow').Tuple, + log = require('vega-logging'), + Transform = require('./Transform'), + BatchTransform = require('./BatchTransform'); + +function Hierarchy(graph) { + BatchTransform.prototype.init.call(this, graph); + Transform.addParameters(this, { + // hierarchy parameters + sort: {type: 'array', default: null}, + children: {type: 'field', default: 'children'}, + parent: {type: 'field', default: 'parent'}, + field: {type: 'value', default: null}, + // layout parameters + mode: {type: 'value', default: 'tidy'}, // tidy, cluster, partition + size: {type: 'array', default: require('./screen').size}, + nodesize: {type: 'array', default: null}, + orient: {type: 'value', default: 'cartesian'} + }); + + this._mode = null; + this._output = { + 'x': 'layout_x', + 'y': 'layout_y', + 'width': 'layout_width', + 'height': 'layout_height', + 'depth': 'layout_depth' + }; + return this.mutates(true); +} + +var PARTITION = 'partition'; + +var SEPARATION = { + cartesian: function(a, b) { return (a.parent === b.parent ? 1 : 2); }, + radial: function(a, b) { return (a.parent === b.parent ? 1 : 2) / a.depth; } +}; + +var prototype = (Hierarchy.prototype = Object.create(BatchTransform.prototype)); +prototype.constructor = Hierarchy; + +prototype.batchTransform = function(input, data) { + log.debug(input, ['hierarchy layout']); + + // get variables + var layout = this._layout, + output = this._output, + mode = this.param('mode'), + sort = this.param('sort'), + nodesz = this.param('nodesize'), + parent = this.param('parent').accessor, + root = data.filter(function(d) { return parent(d) === null; })[0]; + + if (mode !== this._mode) { + this._mode = mode; + if (mode === 'tidy') mode = 'tree'; + layout = (this._layout = d3.layout[mode]()); + } + + input.fields[output.x] = 1; + input.fields[output.y] = 1; + input.fields[output.depth] = 1; + if (mode === PARTITION) { + input.fields[output.width] = 1; + input.fields[output.height] = 1; + layout.value(this.param('field').accessor); + } else { + layout.separation(SEPARATION[this.param('orient')]); + } + + if (nodesz.length && mode !== PARTITION) { + layout.nodeSize(nodesz); + } else { + layout.size(this.param('size')); + } + + layout + .sort(sort.field.length ? dl.comparator(sort.field) : null) + .children(this.param('children').accessor) + .nodes(root); + + // copy layout values to nodes + data.forEach(function(n) { + Tuple.set(n, output.x, n.x); + Tuple.set(n, output.y, n.y); + Tuple.set(n, output.depth, n.depth); + if (mode === PARTITION) { + Tuple.set(n, output.width, n.dx); + Tuple.set(n, output.height, n.dy); + } + }); + + // return changeset + return input; +}; + +module.exports = Hierarchy; +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"./BatchTransform":119,"./Transform":139,"./screen":145,"datalib":26,"vega-dataflow":41,"vega-logging":47}],132:[function(require,module,exports){ +var dl = require('datalib'), + log = require('vega-logging'), + Tuple = require('vega-dataflow').Tuple, + Transform = require('./Transform'), + BatchTransform = require('./BatchTransform'); + +function Impute(graph) { + BatchTransform.prototype.init.call(this, graph); + Transform.addParameters(this, { + groupby: {type: 'array'}, + orderby: {type: 'array'}, + field: {type: 'field'}, + method: {type: 'value', default: 'value'}, + value: {type: 'value', default: 0} + }); + + return this.router(true).produces(true); +} + +var prototype = (Impute.prototype = Object.create(BatchTransform.prototype)); +prototype.constructor = Impute; + +prototype.batchTransform = function(input, data) { + log.debug(input, ['imputing']); + + var groupby = this.param('groupby'), + orderby = this.param('orderby'), + method = this.param('method'), + value = this.param('value'), + field = this.param('field'), + get = field.accessor, + name = field.field, + prev = this._imputed || [], curr = [], + groups = partition(data, groupby.accessor, orderby.accessor), + domain = groups.domain, + group, i, j, n, m, t; + + function getval(x) { + return x == null ? null : get(x); + } + + for (j=0, m=groups.length; j Math.PI ? ta <= sa : ta > sa; + return 'M' + (sr*sc) + ',' + (sr*ss) + + 'A' + sr + ',' + sr + ' 0 0,' + (sf?1:0) + + ' ' + (sr*tc) + ',' + (sr*ts) + + 'L' + (tr*tc) + ',' + (tr*ts); +} + +function diagonalX(sx, sy, tx, ty) { + var m = (sx + tx) / 2; + return 'M' + sx + ',' + sy + + 'C' + m + ',' + sy + + ' ' + m + ',' + ty + + ' ' + tx + ',' + ty; +} + +function diagonalY(sx, sy, tx, ty) { + var m = (sy + ty) / 2; + return 'M' + sx + ',' + sy + + 'C' + sx + ',' + m + + ' ' + tx + ',' + m + + ' ' + tx + ',' + ty; +} + +function diagonalR(sa, sr, ta, tr) { + var sc = Math.cos(sa), + ss = Math.sin(sa), + tc = Math.cos(ta), + ts = Math.sin(ta), + mr = (sr + tr) / 2; + return 'M' + (sr*sc) + ',' + (sr*ss) + + 'C' + (mr*sc) + ',' + (mr*ss) + + ' ' + (mr*tc) + ',' + (mr*ts) + + ' ' + (tr*tc) + ',' + (tr*ts); +} + +var shapes = { + line: line, + curve: curve, + cornerX: cornerX, + cornerY: cornerY, + cornerR: cornerR, + diagonalX: diagonalX, + diagonalY: diagonalY, + diagonalR: diagonalR +}; + +prototype.transform = function(input) { + log.debug(input, ['linkpath']); + + var output = this._output, + shape = shapes[this.param('shape')] || shapes.line, + sourceX = this.param('sourceX').accessor, + sourceY = this.param('sourceY').accessor, + targetX = this.param('targetX').accessor, + targetY = this.param('targetY').accessor, + tension = this.param('tension'); + + function set(t) { + var path = shape(sourceX(t), sourceY(t), targetX(t), targetY(t), tension); + Tuple.set(t, output.path, path); + } + + input.add.forEach(set); + if (this.reevaluate(input)) { + input.mod.forEach(set); + input.rem.forEach(set); + } + + input.fields[output.path] = 1; + return input; +}; + +module.exports = LinkPath; +},{"./Transform":139,"vega-dataflow":41,"vega-logging":47}],134:[function(require,module,exports){ +var Tuple = require('vega-dataflow').Tuple, + log = require('vega-logging'), + Transform = require('./Transform'); + +function Lookup(graph) { + Transform.prototype.init.call(this, graph); + Transform.addParameters(this, { + on: {type: 'data'}, + onKey: {type: 'field', default: null}, + as: {type: 'array'}, + keys: {type: 'array', default: ['data']}, + default: {type: 'value'} + }); + + return this.mutates(true); +} + +var prototype = (Lookup.prototype = Object.create(Transform.prototype)); +prototype.constructor = Lookup; + +prototype.transform = function(input, reset) { + log.debug(input, ['lookup']); + + var on = this.param('on'), + onLast = on.source.last(), + onData = on.source.values(), + onKey = this.param('onKey'), + onF = onKey.field, + keys = this.param('keys'), + get = keys.accessor, + as = this.param('as'), + defaultValue = this.param('default'), + lut = this._lut, + i, v; + + // build lookup table on init, withKey modified, or tuple add/rem + if (lut == null || this._on !== onF || onF && onLast.fields[onF] || + onLast.add.length || onLast.rem.length) + { + if (onF) { // build hash from withKey field + onKey = onKey.accessor; + for (lut={}, i=0; i'} }); + this.router(true); +} + +var prototype = (Sort.prototype = Object.create(Transform.prototype)); +prototype.constructor = Sort; + +prototype.transform = function(input) { + log.debug(input, ['sorting']); + + if (input.add.length || input.mod.length || input.rem.length) { + input.sort = dl.comparator(this.param('by').field); + } + return input; +}; + +module.exports = Sort; +},{"./Transform":139,"datalib":26,"vega-logging":47}],138:[function(require,module,exports){ +var dl = require('datalib'), + Tuple = require('vega-dataflow').Tuple, + log = require('vega-logging'), + Transform = require('./Transform'), + BatchTransform = require('./BatchTransform'); + +function Stack(graph) { + BatchTransform.prototype.init.call(this, graph); + Transform.addParameters(this, { + groupby: {type: 'array'}, + sortby: {type: 'array'}, + field: {type: 'field'}, + offset: {type: 'value', default: 'zero'} + }); + + this._output = { + 'start': 'layout_start', + 'end': 'layout_end', + 'mid': 'layout_mid' + }; + return this.mutates(true); +} + +var prototype = (Stack.prototype = Object.create(BatchTransform.prototype)); +prototype.constructor = Stack; + +prototype.batchTransform = function(input, data) { + log.debug(input, ['stacking']); + + var groupby = this.param('groupby').accessor, + sortby = dl.comparator(this.param('sortby').field), + field = this.param('field').accessor, + offset = this.param('offset'), + output = this._output; + + // partition, sum, and sort the stack groups + var groups = partition(data, groupby, sortby, field); + + // compute stack layouts per group + for (var i=0, max=groups.max; i max) max = s; + if (sortby != null) g.sort(sortby); + } + groups.max = max; + + return groups; +} + +module.exports = Stack; +},{"./BatchTransform":119,"./Transform":139,"datalib":26,"vega-dataflow":41,"vega-logging":47}],139:[function(require,module,exports){ +var df = require('vega-dataflow'), + Base = df.Node.prototype, // jshint ignore:line + Deps = df.Dependencies, + Parameter = require('./Parameter'); + +function Transform(graph) { + if (graph) Base.init.call(this, graph); +} + +Transform.addParameters = function(proto, params) { + proto._parameters = proto._parameters || {}; + for (var name in params) { + var p = params[name], + param = new Parameter(name, p.type, proto); + + proto._parameters[name] = param; + + if (p.type === 'custom') { + if (p.set) param.set = p.set.bind(param); + if (p.get) param.get = p.get.bind(param); + } + + if (p.hasOwnProperty('default')) param.set(p.default); + } +}; + +var prototype = (Transform.prototype = Object.create(Base)); +prototype.constructor = Transform; + +prototype.param = function(name, value) { + var param = this._parameters[name]; + return (param === undefined) ? this : + (arguments.length === 1) ? param.get() : param.set(value); +}; + +// Perform transformation. Subclasses should override. +prototype.transform = function(input/*, reset */) { + return input; +}; + +prototype.evaluate = function(input) { + // Many transforms store caches that must be invalidated if + // a signal value has changed. + var reset = this._stamp < input.stamp && + this.dependency(Deps.SIGNALS).reduce(function(c, s) { + return c += input.signals[s] ? 1 : 0; + }, 0); + return this.transform(input, reset); +}; + +prototype.output = function(map) { + for (var key in this._output) { + if (map[key] !== undefined) { + this._output[key] = map[key]; + } + } + return this; +}; + +module.exports = Transform; +},{"./Parameter":135,"vega-dataflow":41}],140:[function(require,module,exports){ +var dl = require('datalib'), + Tuple = require('vega-dataflow').Tuple, + log = require('vega-logging'), + Transform = require('./Transform'), + BatchTransform = require('./BatchTransform'); + +function Treeify(graph) { + BatchTransform.prototype.init.call(this, graph); + Transform.addParameters(this, { + groupby: {type: 'array'} + }); + + this._output = { + 'children': 'children', + 'parent': 'parent' + }; + return this.router(true).produces(true); +} + +var prototype = (Treeify.prototype = Object.create(BatchTransform.prototype)); +prototype.constructor = Treeify; + +prototype.batchTransform = function(input, data) { + log.debug(input, ['treeifying']); + + var fields = this.param('groupby').field, + childField = this._output.children, + parentField = this._output.parent, + summary = [{name:'*', ops: ['values'], as: [childField]}], + aggrs = fields.map(function(f) { + return dl.groupby(f).summarize(summary); + }), + prev = this._internal || [], curr = [], i, n; + + function level(index, node, values) { + var vals = aggrs[index].execute(values); + + node[childField] = vals; + vals.forEach(function(n) { + n[parentField] = node; + curr.push(Tuple.ingest(n)); + if (index+1 < fields.length) level(index+1, n, n[childField]); + else n[childField].forEach(function(c) { c[parentField] = n; }); + }); + } + + var root = Tuple.ingest({}); + root[parentField] = null; + curr.push(root); + level(0, root, data); + + // update changeset with internal nodes + for (i=0, n=curr.length; i', default: ['-value']}, + children: {type: 'field', default: 'children'}, + parent: {type: 'field', default: 'parent'}, + field: {type: 'field', default: 'value'}, + // treemap parameters + size: {type: 'array', default: require('./screen').size}, + round: {type: 'value', default: true}, + sticky: {type: 'value', default: false}, + ratio: {type: 'value', default: defaultRatio}, + padding: {type: 'value', default: null}, + mode: {type: 'value', default: 'squarify'} + }); + + this._layout = d3.layout.treemap(); + + this._output = { + 'x': 'layout_x', + 'y': 'layout_y', + 'width': 'layout_width', + 'height': 'layout_height', + 'depth': 'layout_depth', + }; + return this.mutates(true); +} + +var prototype = (Treemap.prototype = Object.create(BatchTransform.prototype)); +prototype.constructor = Treemap; + +prototype.batchTransform = function(input, data) { + log.debug(input, ['treemap']); + + // get variables + var layout = this._layout, + output = this._output, + sticky = this.param('sticky'), + parent = this.param('parent').accessor, + root = data.filter(function(d) { return parent(d) === null; })[0]; + + // layout.sticky resets state _regardless_ of input value + // so, we perform out own check first + if (layout.sticky() !== sticky) { layout.sticky(sticky); } + + // configure layout + layout + .sort(dl.comparator(this.param('sort').field)) + .children(this.param('children').accessor) + .value(this.param('field').accessor) + .size(this.param('size')) + .round(this.param('round')) + .ratio(this.param('ratio')) + .padding(this.param('padding')) + .mode(this.param('mode')) + .nodes(root); + + // copy layout values to nodes + data.forEach(function(n) { + Tuple.set(n, output.x, n.x); + Tuple.set(n, output.y, n.y); + Tuple.set(n, output.width, n.dx); + Tuple.set(n, output.height, n.dy); + Tuple.set(n, output.depth, n.depth); + }); + + // return changeset + input.fields[output.x] = 1; + input.fields[output.y] = 1; + input.fields[output.width] = 1; + input.fields[output.height] = 1; + input.fields[output.depth] = 1; + return input; +}; + +module.exports = Treemap; +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"./BatchTransform":119,"./Transform":139,"./screen":145,"datalib":26,"vega-dataflow":41,"vega-logging":47}],142:[function(require,module,exports){ +(function (global){ +var d3 = (typeof window !== "undefined" ? window['d3'] : typeof global !== "undefined" ? global['d3'] : null), + Tuple = require('vega-dataflow/src/Tuple'), + log = require('vega-logging'), + Transform = require('./Transform'), + BatchTransform = require('./BatchTransform'); + +function Voronoi(graph) { + BatchTransform.prototype.init.call(this, graph); + Transform.addParameters(this, { + clipExtent: {type: 'array', default: require('./screen').extent}, + x: {type: 'field', default: 'layout_x'}, + y: {type: 'field', default: 'layout_y'} + }); + + this._layout = d3.geom.voronoi(); + this._output = {'path': 'layout_path'}; + + return this.mutates(true); +} + +var prototype = (Voronoi.prototype = Object.create(BatchTransform.prototype)); +prototype.constructor = Voronoi; + +prototype.batchTransform = function(input, data) { + log.debug(input, ['voronoi']); + + // get variables + var pathname = this._output.path; + + // configure layout + var polygons = this._layout + .clipExtent(this.param('clipExtent')) + .x(this.param('x').accessor) + .y(this.param('y').accessor) + (data); + + // build and assign path strings + for (var i=0; i', default: require('./screen').size}, + text: {type: 'field', default: 'data'}, + rotate: {type: 'field|value', default: 0}, + font: {type: 'field|value', default: {value: 'sans-serif'}}, + fontSize: {type: 'field|value', default: 14}, + fontStyle: {type: 'field|value', default: {value: 'normal'}}, + fontWeight: {type: 'field|value', default: {value: 'normal'}}, + fontScale: {type: 'array', default: [10, 50]}, + padding: {type: 'value', default: 1}, + spiral: {type: 'value', default: 'archimedean'} + }); + + this._layout = d3_cloud().canvas(canvas.instance); + + this._output = { + 'x': 'layout_x', + 'y': 'layout_y', + 'font': 'layout_font', + 'fontSize': 'layout_fontSize', + 'fontStyle': 'layout_fontStyle', + 'fontWeight': 'layout_fontWeight', + 'rotate': 'layout_rotate', + }; + + return this.mutates(true); +} + +var prototype = (Wordcloud.prototype = Object.create(BatchTransform.prototype)); +prototype.constructor = Wordcloud; + +function get(p) { + return (p && p.accessor) || p; +} + +function wrap(tuple) { + var x = Object.create(tuple); + x._tuple = tuple; + return x; +} + +prototype.batchTransform = function(input, data) { + log.debug(input, ['wordcloud']); + + // get variables + var layout = this._layout, + output = this._output, + fontSize = this.param('fontSize'), + range = fontSize.accessor && this.param('fontScale'), + size, scale; + fontSize = fontSize.accessor || d3.functor(fontSize); + + // create font size scaling function as needed + if (range.length) { + scale = d3.scale.sqrt() + .domain(dl.extent(data, size=fontSize)) + .range(range); + fontSize = function(x) { return scale(size(x)); }; + } + + // configure layout + layout + .size(this.param('size')) + .text(get(this.param('text'))) + .padding(this.param('padding')) + .spiral(this.param('spiral')) + .rotate(get(this.param('rotate'))) + .font(get(this.param('font'))) + .fontStyle(get(this.param('fontStyle'))) + .fontWeight(get(this.param('fontWeight'))) + .fontSize(fontSize) + .words(data.map(wrap)) // wrap to avoid tuple writes + .on('end', function(words) { + var size = layout.size(), + dx = size[0] >> 1, + dy = size[1] >> 1, + w, t, i, len; + + for (i=0, len=words.length; i'+tester[0]+''+tester[1]+''); + $("#data-table").append(''+reslt[i][0]+''+reslt[i][1]+''); + } + + draw(reslt,arguments[1]); +} +/* +$(document).ready(function(){ + $("form").submit(function(){ + var query = $('#ftexte').val(); + document.getElementById("json_string").innerHTML=query; + }); +});*/ + +function filter(){ + var query = $('#ftexte').val(); + if(query==""){ + selectField2(); + } + else { + //document.getElementById("json_string2").innerHTML=fieldName; + query = query + ",," + fieldName; + + // + var payload = {}; + payload.query = query; + payload.start = 0; + payload.count = 100; + payload.timeFrom = 0; + payload.tableName = "LOGANALYZER"; + payload.timeTo = 8640000000000000; + var jsonnn = JSON.stringify(payload); + //document.getElementById("json_string2").innerHTML = "You selected:!!" + query+"!!"; + + jQuery.ajax({ + url: serverUrl + "/api/dashboard/filterData", + type: "POST", + contentType: "application/json", + dataType: "json", + data: jsonnn, + success: function (res) { + var response = JSON.stringify(res); + console.log(response); + //var datas = jsonQ(res); + // var rr = datas.find("values"); + //tableResults=response.split("||%\",\""); + $("#data-table").empty(); + addRow(res, fieldName); + //document.getElementById("json_string").innerHTML=response; + }, + error: function (res) { + var response = JSON.stringify(res); + // console.log(response); + //document.getElementById("json_string").innerHTML=response; + alert(res.error); + } + }); + } +} +function draw(){ + var dataValue=[]; + var temp= []; + var json=arguments[0]; + for(var i=0;iLink 3 - + +
+
+ Filter: + +
+
@@ -88,7 +100,13 @@

- +

+
+
+


+
+
+
@@ -122,6 +140,10 @@ + + + + From a095f64c61f743c346efd1ad68235bd4301f5ffc Mon Sep 17 00:00:00 2001 From: VIthulan Date: Tue, 16 Feb 2016 20:53:43 +0530 Subject: [PATCH 4/9] Timely groupby API and UI fixes --- .../carbon/la/restapi/DashboardApiV10.java | 404 ++++++++++++------ .../org/wso2/carbon/la/restapi/util/Util.java | 201 ++++++++- .../jaggeryapps/loganalyzer/css/dashboard.css | 111 +++++ .../jaggeryapps/loganalyzer/js/visualize.js | 307 ++++++------- .../loganalyzer/site/dashboard/visualize.jag | 211 +++++---- 5 files changed, 882 insertions(+), 352 deletions(-) create mode 100644 modules/jaggeryapps/loganalyzer/css/dashboard.css diff --git a/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/DashboardApiV10.java b/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/DashboardApiV10.java index 379b2af..efd96fd 100644 --- a/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/DashboardApiV10.java +++ b/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/DashboardApiV10.java @@ -3,6 +3,7 @@ /** * Created by vithulan on 2/2/16. */ + import org.apache.commons.collections.IteratorUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.logging.Log; @@ -55,6 +56,7 @@ public class DashboardApiV10 { private static final Log log = LogFactory.getLog(DashboardApiV10.class); private static final Gson gson = new Gson(); + @GET @Path("getFields") @Produces("application/json") @@ -86,7 +88,7 @@ public StreamingOutput fieldData(final QueryBean query) { PrivilegedCarbonContext carbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext(); int tenantId = carbonContext.getTenantId(); //String username = carbonContext.getUsername(); - List column = new ArrayList(); + List column = new ArrayList(); column.add(query.getQuery()); AnalyticsDataAPI analyticsDataAPI; AnalyticsDataResponse analyticsDataResponse; @@ -95,10 +97,10 @@ public StreamingOutput fieldData(final QueryBean query) { analyticsDataAPI = LACoreServiceValueHolder.getInstance().getAnalyticsDataAPI(); try { - analyticsDataResponse = analyticsDataAPI.get(tenantId,LAConstants.LOG_ANALYZER_STREAM_NAME.toUpperCase(),1, - column,query.getTimeFrom(),query.getTimeTo(),query.getStart(),-1); - // recordGroup = analyticsDataResponse.getRecordGroups(); - final List> iterators = Util.getRecordIterators(analyticsDataResponse,analyticsDataAPI); + analyticsDataResponse = analyticsDataAPI.get(tenantId, LAConstants.LOG_ANALYZER_STREAM_NAME.toUpperCase(), 1, + column, query.getTimeFrom(), query.getTimeTo(), query.getStart(), -1); + // recordGroup = analyticsDataResponse.getRecordGroups(); + final List> iterators = Util.getRecordIterators(analyticsDataResponse, analyticsDataAPI); return new StreamingOutput() { @Override @@ -106,9 +108,9 @@ public void write(OutputStream outputStream) throws IOException, WebApplicationException { Writer recordWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); Map values; - Map counter = new HashMap(); + Map counter = new HashMap(); - int count=0; + int count = 0; String val; recordWriter.write("["); for (Iterator iterator : iterators) { @@ -116,19 +118,18 @@ public void write(OutputStream outputStream) RecordBean recordBean = Util.createRecordBean(iterator.next()); values = recordBean.getValues(); - if(values.get(query.getQuery())==null) { + if (values.get(query.getQuery()) == null) { - if (!counter.containsKey("NULLVALUE")) { - counter.put("NULLVALUE", 1); - } else { - count = counter.get("NULLVALUE"); - count++; - counter.put("NULLVALUE", count); - } + if (!counter.containsKey("NULLVALUE")) { + counter.put("NULLVALUE", 1); + } else { + count = counter.get("NULLVALUE"); + count++; + counter.put("NULLVALUE", count); + } - } - else { - val=values.get(query.getQuery()).toString(); + } else { + val = values.get(query.getQuery()).toString(); if (!counter.containsKey(val)) { counter.put(val, 1); } else { @@ -139,11 +140,11 @@ public void write(OutputStream outputStream) } //values.get(query.getQuery()); - // recordWriter.write(gson.toJson(values.get(query.getQuery()))); + // recordWriter.write(gson.toJson(values.get(query.getQuery()))); - // recordWriter.write(recordBean.toString()); - // if (iterator.hasNext()) { - //recordWriter.write(","); + // recordWriter.write(recordBean.toString()); + // if (iterator.hasNext()) { + //recordWriter.write(","); //} if (log.isDebugEnabled()) { log.debug("Retrieved -- Record Id: " + recordBean.getId() + " values :" + @@ -151,13 +152,13 @@ public void write(OutputStream outputStream) } } } - int i=1; - for(Map.Entry entry:counter.entrySet()){ + int i = 1; + for (Map.Entry entry : counter.entrySet()) { - recordWriter.write("[[\""+entry.getKey()+"\"],[\""+entry.getValue()+"\"]]"); + recordWriter.write("[[\"" + entry.getKey() + "\"],[\"" + entry.getValue() + "\"]]"); - // recordWriter.write("\""+entry.getKey()+" : "+entry.getValue()+"||%\""); - if(i ids = Util.getRecordIds(searchResults); - analyticsDataResponse = analyticsDataAPI.get(username,query.getTableName(),1,column,ids); - final List> iterators = Util.getRecordIterators(analyticsDataResponse,analyticsDataAPI); + analyticsDataResponse = analyticsDataAPI.get(username, query.getTableName(), 1, column, ids); + final List> iterators = Util.getRecordIterators(analyticsDataResponse, analyticsDataAPI); return new StreamingOutput() { @Override public void write(OutputStream outputStream) throws IOException, WebApplicationException { Writer recordWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); Map values; - Map counter = new HashMap(); + Map counter = new HashMap(); - int count=0; + int count = 0; String val; recordWriter.write("["); for (Iterator iterator : iterators) { @@ -224,7 +225,7 @@ public void write(OutputStream outputStream) RecordBean recordBean = Util.createRecordBean(iterator.next()); values = recordBean.getValues(); - if(values.get(col)==null) { + if (values.get(col) == null) { if (!counter.containsKey("NULLVALUE")) { counter.put("NULLVALUE", 1); @@ -234,9 +235,8 @@ public void write(OutputStream outputStream) counter.put("NULLVALUE", count); } - } - else { - val=values.get(col).toString(); + } else { + val = values.get(col).toString(); if (!counter.containsKey(val)) { counter.put(val, 1); } else { @@ -259,12 +259,12 @@ public void write(OutputStream outputStream) } } } - int i=1; - for(Map.Entry entry:counter.entrySet()){ + int i = 1; + for (Map.Entry entry : counter.entrySet()) { - recordWriter.write("[[\""+entry.getKey()+"\"],[\""+entry.getValue()+"\"]]"); + recordWriter.write("[[\"" + entry.getKey() + "\"],[\"" + entry.getValue() + "\"]]"); //recordWriter.write("\""+entry.getKey()+" : "+entry.getValue()+"||%\""); - if(i column = new ArrayList<>(); column.add(col); column.add(timestamp); @@ -314,8 +313,8 @@ public StreamingOutput timeData(final QueryBean query) throws AnalyticsException query.getTableName(), searchQuery, query.getStart(), query.getCount()); List ids = Util.getRecordIds(searchResults); - analyticsDataResponse = analyticsDataAPI.get(username,query.getTableName(),1,column,ids); - final List> iterators = Util.getRecordIterators(analyticsDataResponse,analyticsDataAPI); + analyticsDataResponse = analyticsDataAPI.get(username, query.getTableName(), 1, column, ids); + final List> iterators = Util.getRecordIterators(analyticsDataResponse, analyticsDataAPI); return new StreamingOutput() { @Override public void write(OutputStream outputStream) @@ -323,8 +322,8 @@ public void write(OutputStream outputStream) Writer recordWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); Map values; - Map> stamper= new HashMap<>(); - int count=0; + Map> stamper = new HashMap<>(); + int count = 0; String val; String time; recordWriter.write("["); @@ -336,47 +335,46 @@ public void write(OutputStream outputStream) time = values.get(timestamp).toString(); temp = time.split(" "); time = temp[0]; - if (values.get(col) != null){ + if (values.get(col) != null) { val = values.get(col).toString(); - if (!stamper.containsKey(time)) { - Map counter = new HashMap(); - counter.put(val, 1); - stamper.put(time, counter); - } else { - Map counter = stamper.get(time); - if(!counter.containsKey(val)){ - counter.put(val,1); - stamper.put(time,counter); - } - else { - count = counter.get(val); - count++; - counter.put(val, count); + if (!stamper.containsKey(time)) { + Map counter = new HashMap(); + counter.put(val, 1); stamper.put(time, counter); + } else { + Map counter = stamper.get(time); + if (!counter.containsKey(val)) { + counter.put(val, 1); + stamper.put(time, counter); + } else { + count = counter.get(val); + count++; + counter.put(val, count); + stamper.put(time, counter); + } } - } - } + } if (log.isDebugEnabled()) { log.debug("Retrieved -- Record Id: " + recordBean.getId() + " values :" + recordBean.toString()); } } } - int i=1; - int j=1; - for(Map.Entry> entry:stamper.entrySet()){ + int i = 1; + int j = 1; + for (Map.Entry> entry : stamper.entrySet()) { Map counter = entry.getValue(); - for(Map.Entry infom:counter.entrySet()){ - recordWriter.write("[[\""+entry.getKey()+"\"],[\""+infom.getKey()+"\"],[\""+infom.getValue()+"\"]]"); - if(i infom : counter.entrySet()) { + recordWriter.write("[[\"" + entry.getKey() + "\"],[\"" + infom.getKey() + "\"],[\"" + infom.getValue() + "\"]]"); + if (i < counter.size()) { recordWriter.write(","); i++; } } - i=1; - if(j column = new ArrayList<>(); @@ -433,8 +430,8 @@ public StreamingOutput EpochtimeData(final QueryBean query) throws AnalyticsExce query.getTableName(), searchQuery, query.getStart(), query.getCount()); List ids = Util.getRecordIds(searchResults); - analyticsDataResponse = analyticsDataAPI.get(username,query.getTableName(),1,column,ids); - final List> iterators = Util.getRecordIterators(analyticsDataResponse,analyticsDataAPI); + analyticsDataResponse = analyticsDataAPI.get(username, query.getTableName(), 1, column, ids); + final List> iterators = Util.getRecordIterators(analyticsDataResponse, analyticsDataAPI); return new StreamingOutput() { @Override public void write(OutputStream outputStream) @@ -442,8 +439,8 @@ public void write(OutputStream outputStream) Writer recordWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); Map values; - Map> stamper= new HashMap<>(); - int count=0; + Map> stamper = new HashMap<>(); + int count = 0; String val; String time; recordWriter.write("["); @@ -464,7 +461,7 @@ public void write(OutputStream outputStream) e.printStackTrace(); } long epoch = date.getTime(); - if (values.get(col) != null){ + if (values.get(col) != null) { val = values.get(col).toString(); if (!stamper.containsKey(epoch)) { @@ -473,11 +470,10 @@ public void write(OutputStream outputStream) stamper.put(epoch, counter); } else { Map counter = stamper.get(epoch); - if(!counter.containsKey(val)){ - counter.put(val,1); - stamper.put(epoch,counter); - } - else { + if (!counter.containsKey(val)) { + counter.put(val, 1); + stamper.put(epoch, counter); + } else { count = counter.get(val); count++; counter.put(val, count); @@ -493,13 +489,13 @@ public void write(OutputStream outputStream) } } - Map > sortedMap = new TreeMap>(stamper); - int i=1; - int j=1; - long lastDay =0; - long presentDay=0; - long k=1; - for(Map.Entry> entry:sortedMap.entrySet()) { + Map> sortedMap = new TreeMap>(stamper); + int i = 1; + int j = 1; + long lastDay = 0; + long presentDay = 0; + long k = 1; + for (Map.Entry> entry : sortedMap.entrySet()) { /* Map countero = entry.getValue(); @@ -516,60 +512,226 @@ public void write(OutputStream outputStream) */ - if (j > 1) { presentDay = entry.getKey(); long dif = presentDay - lastDay; k = dif / dayGap; } - if(k>1){ - int m = (int)k; + if (k > 1) { + int m = (int) k; long newDate = lastDay; - while(k>1){ - newDate = newDate+dayGap; - long temp = newDate/1000L; - Date expiry = new Date(temp*1000L); + while (k > 1) { + newDate = newDate + dayGap; + long temp = newDate / 1000L; + Date expiry = new Date(temp * 1000L); String str_date = format.format(expiry); recordWriter.write("[[\"" + str_date + "\"],[\"" + "No Entry" + "\"],[\"" + 0 + "\"]]"); k--; - if(k!=1){ + if (k != 1) { recordWriter.write(","); } } recordWriter.write(","); - k=1; - lastDay=newDate; + k = 1; + lastDay = newDate; + } + if (k == 1) { + Map counter = entry.getValue(); + for (Map.Entry infom : counter.entrySet()) { + long temp = entry.getKey() / 1000L; + Date expiry = new Date(temp * 1000L); + String str_date = format.format(expiry); + recordWriter.write("[[\"" + str_date + "\"],[\"" + infom.getKey() + "\"],[\"" + infom.getValue() + "\"]]"); + if (i < counter.size()) { + recordWriter.write(","); + i++; + } + } + i = 1; + if (j < sortedMap.size()) { + recordWriter.write(","); + j++; + } + lastDay = entry.getKey(); + //recordWriter.write("[[\""+entry.getKey()+"\"],[\""+entry.getValue()+"\"]]"); + //recordWriter.write("\""+entry.getKey()+" : "+entry.getValue()+"||%\""); + } + + } + recordWriter.write("]"); + recordWriter.flush(); + } + }; + } else { + String msg = String.format("Error occurred while retrieving field data"); + log.error(msg); + return new StreamingOutput() { + @Override + public void write(OutputStream outputStream) throws IOException, WebApplicationException { + Writer recordWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); + recordWriter.write("Error in reading records"); + recordWriter.flush(); + } + }; + } + + } + + @POST + @Path("/epochTimeDataFinal") + @Produces("application/json") + @Consumes("application/json") + public StreamingOutput EpochtimeDataFinal(final QueryBean query) throws AnalyticsException { + + PrivilegedCarbonContext carbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + int tenantId = carbonContext.getTenantId(); + String username = carbonContext.getUsername(); + AnalyticsDataAPI analyticsDataAPI; + AnalyticsDataResponse analyticsDataResponse; + //RecordGroup recordGroup [] ; + analyticsDataAPI = LACoreServiceValueHolder.getInstance().getAnalyticsDataAPI(); + if (query != null) { + String q[] = query.getQuery().split(",,"); + String searchQuery = q[0]; + final String col = q[1]; + final String groupBy = q[2]; + final String timestamp = "_timestamp2"; + final long dayGap = 86400000; + final String pattern = "yyyy-MM-dd"; + final SimpleDateFormat format = new SimpleDateFormat(pattern); + List column = new ArrayList<>(); + column.add(col); + column.add(timestamp); + + List searchResults = analyticsDataAPI.search(username, + query.getTableName(), searchQuery, + query.getStart(), query.getCount()); + List ids = Util.getRecordIds(searchResults); + analyticsDataResponse = analyticsDataAPI.get(username, query.getTableName(), 1, column, ids); + final List> iterators = Util.getRecordIterators(analyticsDataResponse, analyticsDataAPI); + return new StreamingOutput() { + @Override + public void write(OutputStream outputStream) + throws IOException, WebApplicationException { + Writer recordWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); + Map values; + + Map> stamper = new HashMap<>(); + int count = 0; + String val; + String time; + for (Iterator iterator : iterators) { + while (iterator.hasNext()) { + RecordBean recordBean = Util.createRecordBean(iterator.next()); + values = recordBean.getValues(); + String temp[]; + time = values.get(timestamp).toString(); + temp = time.split(" "); + time = temp[0]; + + Date date = null; + try { + date = format.parse(time); + //System.out.println(date); + } catch (ParseException e) { + e.printStackTrace(); + } + long epoch = date.getTime(); + if (values.get(col) != null) { + val = values.get(col).toString(); + + if (!stamper.containsKey(epoch)) { + Map counter = new HashMap(); + counter.put(val, 1); + stamper.put(epoch, counter); + } else { + Map counter = stamper.get(epoch); + if (!counter.containsKey(val)) { + counter.put(val, 1); + stamper.put(epoch, counter); + } else { + count = counter.get(val); + count++; + counter.put(val, count); + stamper.put(epoch, counter); + } + } + + } + if (log.isDebugEnabled()) { + log.debug("Retrieved -- Record Id: " + recordBean.getId() + " values :" + + recordBean.toString()); + } } - if (k == 1){ + } + + Map> sortedMap = new TreeMap>(stamper); + Map> allDayMap = new HashMap<>(); + int j = 1; + long lastDay = 0; + long presentDay = 0; + long k = 1; + + for (Map.Entry> entry : sortedMap.entrySet()) { + + if (j > 1) { + presentDay = entry.getKey(); + long dif = presentDay - lastDay; + k = dif / dayGap; + + } + if (k > 1) { + long newDate = lastDay; + while (k > 1) { + newDate = newDate + dayGap; + Map tempMap = new HashMap<>(); + tempMap.put("No Entry",0); + allDayMap.put(newDate,tempMap); + k--; + } + k = 1; + lastDay = newDate; + } + if (k == 1) { Map counter = entry.getValue(); + if (j < sortedMap.size()) { + j++; + } + lastDay = entry.getKey(); + allDayMap.put(lastDay,counter); + } + + } + allDayMap = new TreeMap>(allDayMap); + Map> grouped = Util.getGrouping(allDayMap,groupBy); + grouped = new TreeMap<>(grouped); + + int i = 1; + int l = 1; + recordWriter.write("["); + for (Map.Entry> entry : grouped.entrySet()) { + Map counter = entry.getValue(); for (Map.Entry infom : counter.entrySet()) { - long temp = entry.getKey()/1000L; - Date expiry = new Date(temp*1000L); - String str_date = format.format(expiry); - recordWriter.write("[[\"" + str_date + "\"],[\"" + infom.getKey() + "\"],[\"" + infom.getValue() + "\"]]"); + + recordWriter.write("[[\"" + entry.getKey()+ "\"],[\"" + infom.getKey() + "\"],[\"" + infom.getValue() + "\"]]"); if (i < counter.size()) { recordWriter.write(","); i++; } } i = 1; - if (j < sortedMap.size()) { + if (l < grouped.size()) { recordWriter.write(","); - j++; + l++; } - lastDay = entry.getKey(); - //recordWriter.write("[[\""+entry.getKey()+"\"],[\""+entry.getValue()+"\"]]"); - //recordWriter.write("\""+entry.getKey()+" : "+entry.getValue()+"||%\""); - } - } recordWriter.write("]"); recordWriter.flush(); + } }; - } - else{ + } else { String msg = String.format("Error occurred while retrieving field data"); log.error(msg); return new StreamingOutput() { diff --git a/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/util/Util.java b/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/util/Util.java index 2b1b807..3eada5d 100644 --- a/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/util/Util.java +++ b/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/util/Util.java @@ -9,14 +9,19 @@ import org.wso2.carbon.analytics.datasource.commons.exception.AnalyticsException; import org.wso2.carbon.la.commons.domain.RecordBean; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; +import java.text.SimpleDateFormat; +import java.util.*; /** * Created by vithulan on 2/3/16. */ public class Util { + private static final String day = "day"; + private static final String week = "week"; + private static final String month = "month"; + private static final String year = "year"; + private static final String auto = "auto"; + public static List> getRecordIterators(AnalyticsDataResponse resp, SecureAnalyticsDataService analyticsDataService) throws AnalyticsException { @@ -45,4 +50,194 @@ public static List getRecordIds(List searchResults) { } return ids; } + + public static Map> getGrouping(Map> allDayMap,String method){ + Map> grouped = new HashMap<>(); + switch (method){ + case auto: grouped=groupbyAuto(allDayMap); + break; + case day:grouped=groupbyDay(allDayMap); + break; + case week:grouped=groupbyWeek(allDayMap); + break; + case month : grouped = groupbyMonth(allDayMap); + break; + case year : grouped = groupbyYear(allDayMap); + break; + + } + + return grouped; + } + + public static Map> groupbyAuto(Map> allDayMap){ + Map> grouped = new HashMap<>(); + int days = allDayMap.size(); + if(days<=10){ + grouped = groupbyDay(allDayMap); + } + else if(days>10 && days<=70){ + grouped = groupbyWeek(allDayMap); + } + else if(days>70 && days<=365*3){ + grouped = groupbyMonth(allDayMap); + } + else{ + grouped = groupbyYear(allDayMap); + } + return grouped; + } + + public static Map> groupbyDay(Map> allDayMap){ + Map> grouped = new HashMap<>(); + String pattern = "yyyy-MM-dd"; + SimpleDateFormat format = new SimpleDateFormat(pattern); + //long temp = epoch/1000L; + + for(Map.Entry> entry:allDayMap.entrySet()){ + Date expiry = new Date(entry.getKey()); + String str_week = format.format(expiry); + grouped.put(str_week,entry.getValue()); + } + + return grouped; + } + + public static Map> groupbyWeek(Map> allDayMap){ + + String pattern = "Y/MM:W"; + SimpleDateFormat format = new SimpleDateFormat(pattern); + //long temp = epoch/1000L; + Map>> grouped = new HashMap<>(); + for(Map.Entry> entry:allDayMap.entrySet()){ + Date expiry = new Date(entry.getKey()); + String str_week = format.format(expiry); + if(!grouped.containsKey(str_week)){ + List> list = new ArrayList<>(); + list.add(entry.getValue()); + grouped.put(str_week,list); + } + else{ + List> list = grouped.get(str_week); + list.add(entry.getValue()); + grouped.put(str_week,list); + } + //grouped.put(str_week,entry.getValue()); + + } + + Map> Fgrouped = new HashMap<>(); + for(Map.Entry>> entry:grouped.entrySet()){ + List> list = entry.getValue(); + Map data = new HashMap<>(); + //Map counter = entry.getValue(); + + for(Map dataMap : list){ + for(Map.Entry entrr:dataMap.entrySet()){ + if(!data.containsKey(entrr.getKey())){ + data.put(entrr.getKey(),entrr.getValue()); + } + else{ + int k = data.get(entrr.getKey()); + data.put(entrr.getKey(),k+entrr.getValue()); + } + } + } + Fgrouped.put(entry.getKey(),data); + } + + return Fgrouped; + } + + public static Map> groupbyMonth(Map> allDayMap){ + + String pattern = "Y-MM"; + SimpleDateFormat format = new SimpleDateFormat(pattern); + //long temp = epoch/1000L; + Map>> grouped = new HashMap<>(); + for(Map.Entry> entry:allDayMap.entrySet()){ + Date expiry = new Date(entry.getKey()); + String str_week = format.format(expiry); + if(!grouped.containsKey(str_week)){ + List> list = new ArrayList<>(); + list.add(entry.getValue()); + grouped.put(str_week,list); + } + else{ + List> list = grouped.get(str_week); + list.add(entry.getValue()); + grouped.put(str_week,list); + } + //grouped.put(str_week,entry.getValue()); + + } + + Map> Fgrouped = new HashMap<>(); + for(Map.Entry>> entry:grouped.entrySet()){ + List> list = entry.getValue(); + Map data = new HashMap<>(); + //Map counter = entry.getValue(); + + for(Map dataMap : list){ + for(Map.Entry entrr:dataMap.entrySet()){ + if(!data.containsKey(entrr.getKey())){ + data.put(entrr.getKey(),entrr.getValue()); + } + else{ + int k = data.get(entrr.getKey()); + data.put(entrr.getKey(),k+entrr.getValue()); + } + } + } + Fgrouped.put(entry.getKey(),data); + } + + return Fgrouped; + } + + public static Map> groupbyYear(Map> allDayMap){ + + String pattern = "YYYY"; + SimpleDateFormat format = new SimpleDateFormat(pattern); + //long temp = epoch/1000L; + Map>> grouped = new HashMap<>(); + for(Map.Entry> entry:allDayMap.entrySet()){ + Date expiry = new Date(entry.getKey()); + String str_week = format.format(expiry); + if(!grouped.containsKey(str_week)){ + List> list = new ArrayList<>(); + list.add(entry.getValue()); + grouped.put(str_week,list); + } + else{ + List> list = grouped.get(str_week); + list.add(entry.getValue()); + grouped.put(str_week,list); + } + //grouped.put(str_week,entry.getValue()); + + } + + Map> Fgrouped = new HashMap<>(); + for(Map.Entry>> entry:grouped.entrySet()){ + List> list = entry.getValue(); + Map data = new HashMap<>(); + //Map counter = entry.getValue(); + + for(Map dataMap : list){ + for(Map.Entry entrr:dataMap.entrySet()){ + if(!data.containsKey(entrr.getKey())){ + data.put(entrr.getKey(),entrr.getValue()); + } + else{ + int k = data.get(entrr.getKey()); + data.put(entrr.getKey(),k+entrr.getValue()); + } + } + } + Fgrouped.put(entry.getKey(),data); + } + + return Fgrouped; + } } diff --git a/modules/jaggeryapps/loganalyzer/css/dashboard.css b/modules/jaggeryapps/loganalyzer/css/dashboard.css new file mode 100644 index 0000000..067c1ea --- /dev/null +++ b/modules/jaggeryapps/loganalyzer/css/dashboard.css @@ -0,0 +1,111 @@ +.dashboardNav { + line-height:30px; + background-color:#eeeeee; + + width:20%; + float:left; + padding:20px 15px; + position: fixed; + clear:both; + +} +.backgroundPut{ + background-color:#eeeeee; + + width:20%; + float:left; + padding:20px 15px; + + clear:both; +} +.dashboard{ + background-color:#eeeeee; + height: 100%; +} +.dsWelcome{ + font-family:'MS Sans Serif', sans-serif, Verdana, Arial; + padding:25px 25px; + background-color:#eeeeee; + top: 40%; + left: 40%; + border-radius: 49px 0px 53px 0px; + -moz-border-radius: 49px 0px 53px 0px; + -webkit-border-radius: 49px 0px 53px 0px; + border: 0px dotted #000000; + position: fixed; + color: gray; + +} + +.dashboardNav p{ + font-family: "Open Sans"; + font-weight: 400; + font-size: 19px; + margin: 0px 0px 20px; + color: #533565; + border-bottom: 1px solid #E4E4E4; + +} + + +.dashboardNav select{ + margin: 0px 0px 20px; + background-color: ghostwhite; + width:100%; + border: 1px solid #C0C0C0; + +} + +.dashboardNav label{ + font-family: "Open Sans"; + font-weight: 400; + font-size: 16px; + color: #533565; + +} + +.dashboardNav button{ + width: 100%; + background:#6c5c76; + border: 1px solid transparent; + border-radius: 0px; + color:#fff; + font-size:14px; + margin: 20px 0px 20px; +} + +.dashboardNav input{ + width: 100%; + font-size:14px; + margin: 0px 0px 20px; +} + +.dashboardNav form{ + margin: 0px 0px 20px; +} +.dashboardNav h2{ + font-family: "Georgia,serif"; + alignn : center; + font-weight: 400; + font-size: 22px; + + color: #533565; + border-bottom: 2px solid #E4E4E4; + +} + +.dashboardSection { + width:80%; + float:right; + padding:10px; + position: relative; +} +.dsDatePicker{ + padding: 15px 5px; + position: absolute; + right : 0; + +} +.dsDatePicker label{ + color: #ffffff; +} diff --git a/modules/jaggeryapps/loganalyzer/js/visualize.js b/modules/jaggeryapps/loganalyzer/js/visualize.js index a122b47..5dc0502 100644 --- a/modules/jaggeryapps/loganalyzer/js/visualize.js +++ b/modules/jaggeryapps/loganalyzer/js/visualize.js @@ -5,77 +5,42 @@ var serverUrl = window.location.origin; var fields; var tableResults; var fieldName; -window.onload = function(){ +var trem; +var chartType; +window.onload = function () { checkApi(); //console.log("Hello world"); } -function selectField() { - var x = document.getElementById("fieldsName").value; - - x=x.replace("\"",""); - x=x.replace("\"",""); - var payload ={}; - payload.query = x; - payload.start = 0; - payload.count =-1 ; - payload.timeFrom = 0; - payload.tableName="LOGANALYZER"; - payload.timeTo = 8640000000000000; - var jsonnn=JSON.stringify(payload); - document.getElementById("json_string2").innerHTML = "You selected: " + x; - $('#data-table').DataTable({ - "ajax":{ - "url": serverUrl + "/api/dashboard/fieldData", - "type" : "POST", - "contentType": "application/json", - "dataType": "json", - "data" : jsonnn, - "dataSrc" : function(d){ - return d; - } - }, - "columns":[ - {"mData":"values"} - ], - "paging": false, - "searching": false - - - } - - ); -} - -function openDashboard (){ +function openDashboard() { //window.location= baseUrl +'/loganalyzer/site/visualize.jag'; - // retrieveColum(); - window.open(serverUrl +'/loganalyzer/site/dashboard/visualize.jag'); + // retrieveColum(); + window.open(serverUrl + '/loganalyzer/site/dashboard/visualize.jag'); } -function retrieveColum(){ +function retrieveColum() { jQuery.ajax({ type: "GET", url: serverUrl + "/analytics/tables/loganalyzer/schema", - //async: false, + //async: false, beforeSend: function (xhr) { - xhr.setRequestHeader ("Authorization", "Basic " + btoa("admin" + ":" + "admin")); + xhr.setRequestHeader("Authorization", "Basic " + btoa("admin" + ":" + "admin")); }, success: function (res) { - // alert(res); - // CreateHtmlTable(res); + // alert(res); + // CreateHtmlTable(res); - var fields =jsonQ(res); - var colms = fields.find('columns'); - var reslt=colms.nthElm('0'); + var fields = jsonQ(res); + var colms = fields.find('columns'); + var reslt = colms.nthElm('0'); //var fild = res.columns[0].type; - var hell = JSON.parse(res) - var Jasonstr = JSON.stringify(reslt); + var hell = JSON.parse(res) + var Jasonstr = JSON.stringify(reslt); var bla = JSON.stringify(hell.length); - // document.getElementById("json_string").innerHTML=res.columns[1]; - // document.getElementById("json_string").innerHTML=Jasonstr; + // document.getElementById("json_string").innerHTML=res.columns[1]; + // document.getElementById("json_string").innerHTML=Jasonstr; }, error: function (res) { alert(res.responseText); @@ -84,17 +49,18 @@ function retrieveColum(){ } -function checkApi(){ +function checkApi() { jQuery.ajax({ type: "GET", url: serverUrl + "/api/dashboard/getFields", success: function (res) { var Jasonstr = JSON.stringify(res); - Jasonstr=Jasonstr.replace("[",""); - Jasonstr=Jasonstr.replace("]",""); + Jasonstr = Jasonstr.replace("[", ""); + Jasonstr = Jasonstr.replace("]", ""); fields = Jasonstr.split(','); - // document.getElementById("json_string").innerHTML=fields[0]; + + // document.getElementById("json_string").innerHTML=fields[0]; addFields(); }, @@ -109,54 +75,62 @@ function checkApi(){ function addFields() { var select = document.getElementById("fieldsName"); var i; - var j=fields.length; + var j = fields.length; + + for (i = 0; i < j; i++) { + fields[i] = fields[i].replace("\"", "") + fields[i] = fields[i].replace("\"", "") + fields[i] = capitalizeFirstLetter(fields[i].replace("_", "")); - for(i=0;i'); + addRow(res, fieldName); }, error: function (res) { var response = JSON.stringify(res); - // console.log(response); - //document.getElementById("json_string").innerHTML=response; alert(res.error); } }); @@ -164,35 +138,42 @@ function selectField2() { } -function addRow(){ +function addRow() { var table = document.getElementById("data-table"); - var reslt= arguments[0]; + var reslt = arguments[0]; var i; var j = arguments[0].length; //var tester; - document.getElementById("json_string").innerHTML=j; - for(i=0;i'); + document.getElementById("json_string").innerHTML = j; + for (i = 0; i < j; i++) { + $("#data-table").append(''); } - draw(reslt,arguments[1]); + draw(reslt, arguments[1]); } /* -$(document).ready(function(){ - $("form").submit(function(){ - var query = $('#ftexte').val(); - document.getElementById("json_string").innerHTML=query; - }); -});*/ - -function filter(){ + $(document).ready(function(){ + $("form").submit(function(){ + var query = $('#ftexte').val(); + document.getElementById("json_string").innerHTML=query; + }); + });*/ + +function filter() { + $("#dsWelcome").hide(); var query = $('#ftexte').val(); - if(query==""){ + var fieldN = $("#fieldsName").val(); + fieldN = simpleFirstLetter(fieldN); + if (fieldN != "logstream") { + fieldN = "_" + fieldN; + } + + if (query == "") { selectField2(); } else { - //document.getElementById("json_string2").innerHTML=fieldName; - query = query + ",," + fieldName; + //document.getElementById("json_string2").innerHTML=fieldName; + query = query + ",," + fieldN; // var payload = {}; @@ -218,6 +199,7 @@ function filter(){ // var rr = datas.find("values"); //tableResults=response.split("||%\",\""); $("#data-table").empty(); + $("#data-table").append(''); addRow(res, fieldName); //document.getElementById("json_string").innerHTML=response; }, @@ -228,71 +210,73 @@ function filter(){ alert(res.error); } }); - } + } } -function draw(){ - var dataValue=[]; - var temp= []; - var json=arguments[0]; - for(var i=0;i WSO2 Log Analyzer - + + - + <% include("../../includes/tenantAware.jag"); %> @@ -20,48 +21,63 @@ -
-
-
+
' + trem + '' + 'Hits' + '
'+reslt[i][0]+''+reslt[i][1]+'
' + reslt[i][0] + '' + reslt[i][1] + '
' + trem + '' + 'Hits' + '
- - - - - -
Fields
+

Timely Chart

+ + + + + + +
+ + +
+
+
+
+

Welcome to Dashboard!


+

Visualize

+ +

+ Select a field from your log file and chart type to visualize your logs against Count aggregation. +

+

Filter

+ +

+ Filter your results from visualize results by using lucene query. +

+

Timely charts

+ +

+ Generate charts of count againts time from filtered logs. +

+
+
+
+

-
-
-

-

-

-
-
-


-
-
-
+

+

-
+ +

-
+ +
+ + +
+ + +
+ + @@ -129,19 +183,20 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + + From 0dcc64e86c6d1cbd3f1935027cfdd30679b42c0d Mon Sep 17 00:00:00 2001 From: VIthulan Date: Fri, 19 Feb 2016 11:32:32 +0530 Subject: [PATCH 5/9] Dashboard and search changes --- .../carbon/la/restapi/DashboardApiV10.java | 63 ++++++- .../jaggeryapps/loganalyzer/css/custom.css | 18 ++ .../jaggeryapps/loganalyzer/css/dashboard.css | 2 +- .../jaggeryapps/loganalyzer/js/visualize.js | 166 ++++++++++++++++-- .../loganalyzer/site/dashboard/visualize.jag | 13 +- .../loganalyzer/site/search/search.jag | 13 +- .../loganalyzer/site/search/search.js | 144 +++++++++++++++ 7 files changed, 401 insertions(+), 18 deletions(-) diff --git a/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/DashboardApiV10.java b/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/DashboardApiV10.java index efd96fd..7ffacae 100644 --- a/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/DashboardApiV10.java +++ b/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/DashboardApiV10.java @@ -12,8 +12,8 @@ import org.apache.cxf.jaxrs.ext.multipart.Multipart; import org.apache.http.HttpHeaders; import org.wso2.carbon.analytics.api.AnalyticsDataAPI; -import org.wso2.carbon.analytics.dataservice.commons.AnalyticsDataResponse; -import org.wso2.carbon.analytics.dataservice.commons.SearchResultEntry; +import org.wso2.carbon.analytics.dataservice.commons.*; +import org.wso2.carbon.analytics.dataservice.core.AnalyticsDataServiceUtils; import org.wso2.carbon.analytics.datasource.commons.AnalyticsSchema; import org.wso2.carbon.analytics.datasource.commons.ColumnDefinition; import org.wso2.carbon.analytics.datasource.commons.Record; @@ -745,7 +745,66 @@ public void write(OutputStream outputStream) throws IOException, WebApplicationE } } + @POST + @Path("/logStreamData") + @Produces("application/json") + @Consumes("application/json") + public StreamingOutput logStreamData(final QueryBean query) { + + PrivilegedCarbonContext carbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + int tenantId = carbonContext.getTenantId(); + AnalyticsDataAPI analyticsDataAPI; + String q[] = query.getQuery().split(",,"); + String path[] =null; + if(!q[1].equals(" ")) { + String pathName = q[1]; + path = pathName.split(","); + } + String fieldName = q[0]; + + CategoryDrillDownRequest categoryDrillDownRequest = new CategoryDrillDownRequest(); + categoryDrillDownRequest.setTableName(query.getTableName()); + categoryDrillDownRequest.setFieldName(fieldName); + categoryDrillDownRequest.setPath(path); + final List list; + analyticsDataAPI = LACoreServiceValueHolder.getInstance().getAnalyticsDataAPI(); + try { + SubCategories subCategories = analyticsDataAPI.drillDownCategories(tenantId, categoryDrillDownRequest); + list = subCategories.getCategories(); + return new StreamingOutput() { + @Override + public void write(OutputStream outputStream) + throws IOException, WebApplicationException { + Writer recordWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); + recordWriter.write("["); + int i = 1; + for (CategorySearchResultEntry lister : list) { + recordWriter.write("\"" + lister.getCategoryValue() + "\""); + if (i < list.size()) { + recordWriter.write(","); + i++; + } + } + recordWriter.write("]"); + recordWriter.flush(); + } + }; + + } catch (AnalyticsException e) { + String msg = String.format("Error occurred while retrieving field data"); + log.error(msg, e); + return new StreamingOutput() { + @Override + public void write(OutputStream outputStream) throws IOException, WebApplicationException { + Writer recordWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); + recordWriter.write("Error in reading records"); + recordWriter.flush(); + } + }; + } + + } } diff --git a/modules/jaggeryapps/loganalyzer/css/custom.css b/modules/jaggeryapps/loganalyzer/css/custom.css index d2dd6c5..3c694ee 100644 --- a/modules/jaggeryapps/loganalyzer/css/custom.css +++ b/modules/jaggeryapps/loganalyzer/css/custom.css @@ -1706,6 +1706,24 @@ fieldset[disabled] .btn-primary.active { background: greenyellow; text-decoration: underline; } +.logstreamTest{ + float:left; +} +.logstreamTest select{ + margin: 0px 0px 20px; + background-color: ghostwhite; + width:200px; + border: 1px solid #C0C0C0; +} + +.logstreamTest label{ + font-family: "Open Sans"; + margin: 0px 0px 20px; + padding:25px 25px; + font-weight: 400; + font-size: 16px; + color: #533565; +} .light { diff --git a/modules/jaggeryapps/loganalyzer/css/dashboard.css b/modules/jaggeryapps/loganalyzer/css/dashboard.css index 067c1ea..75180ff 100644 --- a/modules/jaggeryapps/loganalyzer/css/dashboard.css +++ b/modules/jaggeryapps/loganalyzer/css/dashboard.css @@ -5,7 +5,7 @@ width:20%; float:left; padding:20px 15px; - position: fixed; + position: absolute; clear:both; } diff --git a/modules/jaggeryapps/loganalyzer/js/visualize.js b/modules/jaggeryapps/loganalyzer/js/visualize.js index 5dc0502..1a540bd 100644 --- a/modules/jaggeryapps/loganalyzer/js/visualize.js +++ b/modules/jaggeryapps/loganalyzer/js/visualize.js @@ -9,6 +9,8 @@ var trem; var chartType; window.onload = function () { checkApi(); + addLogstream(); + //console.log("Hello world"); } @@ -78,13 +80,15 @@ function addFields() { var j = fields.length; for (i = 0; i < j; i++) { - fields[i] = fields[i].replace("\"", "") - fields[i] = fields[i].replace("\"", "") - fields[i] = capitalizeFirstLetter(fields[i].replace("_", "")); - var option = document.createElement('option'); - option.text = option.value = fields[i]; - select.add(option, 0); + fields[i] = fields[i].replace("\"", "") + fields[i] = fields[i].replace("\"", "") + fields[i] = capitalizeFirstLetter(fields[i].replace("_", "")); + if(fields[i]!="Message") { + var option = document.createElement('option'); + option.text = option.value = fields[i]; + select.add(option, 0); + } } } @@ -179,7 +183,7 @@ function filter() { var payload = {}; payload.query = query; payload.start = 0; - payload.count = 100; + payload.count = 1000000; payload.timeFrom = 0; payload.tableName = "LOGANALYZER"; payload.timeTo = 8640000000000000; @@ -247,10 +251,11 @@ function draw() { var lineChart = new vizg(data, config); lineChart.draw("#dChart"); } -function test() { +function test(val) { // var res = JSON.parse(arguments); - var res = arguments[0]; - document.getElementById("json_string").innerHTML = arguments[0]; + //var res = arguments[0]; + var id = val; + document.getElementById("json_string3").innerHTML = id; } function drawTime() { @@ -368,3 +373,144 @@ function visualize() { selectField2(fieldName); //document.getElementById("json_string3").innerHTML=fieldN; } + +function addLogstream() { + var payload = {}; + var logstream = "logstream"; + var seperator = ",,"; + payload.query = logstream + seperator + " "; + payload.start = 0; + payload.count = 100; + payload.timeFrom = 0; + payload.tableName = "LOGANALYZER"; + payload.timeTo = 8640000000000000; + var jsonnn = JSON.stringify(payload); + + + jQuery.ajax({ + url: serverUrl + "/api/dashboard/logStreamData", + type: "POST", + contentType: "application/json", + dataType: "json", + data: jsonnn, + success: function (res) { + //console.log(response); + //document.getElementById("json_string3").innerHTML=res.length; + var select = document.getElementById("0"); + for (var i = 0; i < res.length; i++) { + var option = document.createElement('option'); + option.text = option.value = res[i]; + option.id=-1; + select.add(option, 0); + } + + }, + error: function (res) { + var response = JSON.stringify(res); + alert(res.error); + } + }); +} +var facetCount =0; + +var facetObj = {}; +var testObj ={}; + +function addChildLogStream(val,idVal){ + var idInt= parseInt(idVal); + //document.getElementById("json_string3").innerHTML = val; + + if(testObj.hasOwnProperty(idVal)){ + for(var key in testObj){ + if(key>idVal){ + // $("#"+key).remove(); + delete testObj[key]; + } + } + } + if(!facetObj.hasOwnProperty(idVal)){ + facetObj[idVal] = val ; + + + + //facetData.push(val); + + } + else{ + for(var key in facetObj){ + if(key>=idInt) { + delete facetObj[key]; + //delete facetObj; + if(key!=idInt) { + + $("#"+key).remove(); + } + } + } + + facetCount = idInt; + facetObj[idInt] = val ; + //facetData.push(val); + + } + + var facetData = [] ; + for(var key in facetObj){ + facetData.push(facetObj[key]); + } + var facetpath = facetData; + document.getElementById("json_string3").innerHTML = JSON.stringify(facetObj)+" "+idVal+" "+val+" "+facetpath ; + //document.getElementById("json_string3").innerHTML=facetpath +" "+JSON.stringify(facetObj); + + var payload = {}; + var logstream = "logstream"; + var seperator =",,"; + payload.query = logstream+seperator+facetpath; + payload.start = 0; + payload.count = 100; + payload.timeFrom = 0; + payload.tableName = "LOGANALYZER"; + payload.timeTo = 8640000000000000; + var jsonnn = JSON.stringify(payload); + + + jQuery.ajax({ + url: serverUrl + "/api/dashboard/logStreamData", + type: "POST", + contentType: "application/json", + dataType: "json", + data: jsonnn, + success: function (res) { + //console.log(response); + //document.getElementById("json_string3").innerHTML=res.length; + //var select = document.getElementById("logstreamSelect"); + var streamDiv = document.getElementById("logStreamDiv"); + var selectList = document.createElement("select"); + facetCount++; + if (!testObj.hasOwnProperty(facetCount)){ + selectList.id = facetCount; + testObj[facetCount] = "test"; + + //selectList.onchange = addChildLogStream(this.value,this.id); + selectList.setAttribute("onchange", "addChildLogStream(this.value,this.id)"); + streamDiv.appendChild(selectList); + var option1 = document.createElement('option'); + option1.text = option1.value = "None"; + selectList.add(option1); + for (var i = 0; i < res.length; i++) { + var option = document.createElement('option'); + option.text = option.value = res[i]; + selectList.add(option, 0); + } + } + }, + error: function (res) { + var response = JSON.stringify(res); + alert(res.error); + } + }); + + +} + + diff --git a/modules/jaggeryapps/loganalyzer/site/dashboard/visualize.jag b/modules/jaggeryapps/loganalyzer/site/dashboard/visualize.jag index 511c196..d1625e7 100644 --- a/modules/jaggeryapps/loganalyzer/site/dashboard/visualize.jag +++ b/modules/jaggeryapps/loganalyzer/site/dashboard/visualize.jag @@ -87,6 +87,10 @@ Link 3 +
+

Logstream

+ +

Visualize

@@ -100,8 +104,15 @@

Filter

+ + + + title="If you put custom filter it will overide the basic filters. Use lucene queries for filtering eg: _level:ERROR _level:INFO">

Timely Chart

diff --git a/modules/jaggeryapps/loganalyzer/site/search/search.jag b/modules/jaggeryapps/loganalyzer/site/search/search.jag index f0c8495..db1ba3c 100644 --- a/modules/jaggeryapps/loganalyzer/site/search/search.jag +++ b/modules/jaggeryapps/loganalyzer/site/search/search.jag @@ -185,8 +185,12 @@ - - +
+
+ + +
+

@@ -197,9 +201,10 @@
+ - + @@ -221,7 +226,7 @@ - + diff --git a/modules/jaggeryapps/loganalyzer/site/search/search.js b/modules/jaggeryapps/loganalyzer/site/search/search.js index da64628..dfbef7c 100644 --- a/modules/jaggeryapps/loganalyzer/site/search/search.js +++ b/modules/jaggeryapps/loganalyzer/site/search/search.js @@ -46,6 +46,7 @@ function capitalizeFirstLetter(string) { } $(document).ready(function () { + addLogstream1(); var showPopover = $.fn.popover.Constructor.prototype.show; $.fn.popover.Constructor.prototype.show = function () { showPopover.call(this); @@ -197,3 +198,146 @@ function tableToCSV(table, tableElm) { link.click(); } + +function addLogstream1() { + var payload = {}; + var logstream = "logstream"; + var seperator = ",,"; + payload.query = logstream + seperator + " "; + payload.start = 0; + payload.count = 100; + payload.timeFrom = 0; + payload.tableName = "LOGANALYZER"; + payload.timeTo = 8640000000000000; + var jsonnn = JSON.stringify(payload); + + + jQuery.ajax({ + url: serverUrl + "/api/dashboard/logStreamData", + type: "POST", + contentType: "application/json", + dataType: "json", + data: jsonnn, + success: function (res) { + //console.log(response); + //document.getElementById("json_string3").innerHTML=res.length; + var select = document.getElementById("0"); + for (var i = 0; i < res.length; i++) { + var option = document.createElement('option'); + option.text = option.value = res[i]; + option.id=-1; + select.add(option, 0); + } + + }, + error: function (res) { + var response = JSON.stringify(res); + alert(res.error); + } + }); +} +var facetCount =0; + +var facetObj = {}; +var testObj ={}; + +function addChildLogStream1(val,idVal) { + + var idInt = parseInt(idVal); + + //if(testObj.hasOwnProperty(idVal)){ + for (var key in testObj) { + if (key > idVal) { + $("#" + key).remove(); + delete testObj[key]; + } + } + if (val != "None") { + // } + if (!facetObj.hasOwnProperty(idVal)) { + facetObj[idVal] = val; + + + //facetData.push(val); + + } + else { + for (var key in facetObj) { + if (key >= idInt) { + delete facetObj[key]; + //delete facetObj; + if (key != idInt) { + + $("#" + key).remove(); + } + } + } + + facetCount = idInt; + facetObj[idInt] = val; + //facetData.push(val); + + } + //document.getElementById("logTest").innerHTML = "Val "+val+" idVal"+idVal+" facetObj "+JSON.stringify(facetObj)+" testObj "+JSON.stringify(testObj); + + var facetData = []; + for (var key in facetObj) { + facetData.push(facetObj[key]); + } + var facetpath = facetData; + //document.getElementById("json_string3").innerHTML=facetpath +" "+JSON.stringify(facetObj); + + var payload = {}; + var logstream = "logstream"; + var seperator = ",,"; + payload.query = logstream + seperator + facetpath; + payload.start = 0; + payload.count = 100; + payload.timeFrom = 0; + payload.tableName = "LOGANALYZER"; + payload.timeTo = 8640000000000000; + var jsonnn = JSON.stringify(payload); + + + jQuery.ajax({ + url: serverUrl + "/api/dashboard/logStreamData", + type: "POST", + contentType: "application/json", + dataType: "json", + data: jsonnn, + success: function (res) { + //console.log(response); + //document.getElementById("json_string3").innerHTML=res.length; + //var select = document.getElementById("logstreamSelect"); + + facetCount++; + if (!testObj.hasOwnProperty(facetCount)) { + var streamDiv = document.getElementById("logStreamData"); + var selectList = document.createElement("select"); + selectList.id = facetCount; + testObj[facetCount] = "test"; + //document.getElementById("json_string3").innerHTML = JSON.stringify(facetObj); + //selectList.onchange = addChildLogStream(this.value,this.id); + selectList.setAttribute("onchange", "addChildLogStream1(this.value,this.id)"); + streamDiv.appendChild(selectList); + var option1 = document.createElement('option'); + option1.value = "None"; + option1.text ="Select a category" + selectList.add(option1); + for (var i = 0; i < res.length; i++) { + var option = document.createElement('option'); + option.text = option.value = res[i]; + selectList.add(option, 0); + } + } + }, + error: function (res) { + var response = JSON.stringify(res); + alert(res.error); + } + }); + + //document.getElementById("logTest").innerHTML = facetpath; + } + +} \ No newline at end of file From 4d182a8111d360299e48d6dbc23d27354a6b3c55 Mon Sep 17 00:00:00 2001 From: VIthulan Date: Thu, 25 Feb 2016 19:53:14 +0530 Subject: [PATCH 6/9] Adding facet search functionality --- .../carbon/la/commons/domain/QueryBean.java | 6 + .../carbon/la/core/impl/SearchController.java | 30 ++- .../carbon/la/restapi/DashboardApiV10.java | 114 ++++----- .../org/wso2/carbon/la/restapi/util/Util.java | 174 +++++++------ .../jaggeryapps/loganalyzer/css/dashboard.css | 7 + .../jaggeryapps/loganalyzer/js/visualize.js | 229 ++++++++++-------- .../loganalyzer/site/dashboard/visualize.jag | 23 +- .../loganalyzer/site/search/search.jag | 5 +- .../loganalyzer/site/search/search.js | 28 ++- 9 files changed, 353 insertions(+), 263 deletions(-) diff --git a/modules/components/org.wso2.carbon.la.commons/src/main/java/org/wso2/carbon/la/commons/domain/QueryBean.java b/modules/components/org.wso2.carbon.la.commons/src/main/java/org/wso2/carbon/la/commons/domain/QueryBean.java index b5ea9b8..0282343 100644 --- a/modules/components/org.wso2.carbon.la.commons/src/main/java/org/wso2/carbon/la/commons/domain/QueryBean.java +++ b/modules/components/org.wso2.carbon.la.commons/src/main/java/org/wso2/carbon/la/commons/domain/QueryBean.java @@ -38,10 +38,16 @@ public class QueryBean { private long timeTo; + private String facetPath; + public int getLength() { return length; } + public String getFacetPath(){return facetPath;} + + public void setFacetPath(String facetPath){this.facetPath=facetPath;} + public void setLength(int length) { this.length = length; } diff --git a/modules/components/org.wso2.carbon.la.core/src/main/java/org/wso2/carbon/la/core/impl/SearchController.java b/modules/components/org.wso2.carbon.la.core/src/main/java/org/wso2/carbon/la/core/impl/SearchController.java index eb8626b..ddda2bf 100644 --- a/modules/components/org.wso2.carbon.la.core/src/main/java/org/wso2/carbon/la/core/impl/SearchController.java +++ b/modules/components/org.wso2.carbon.la.core/src/main/java/org/wso2/carbon/la/core/impl/SearchController.java @@ -4,6 +4,7 @@ import org.apache.commons.logging.LogFactory; import org.wso2.carbon.analytics.api.AnalyticsDataAPI; import org.wso2.carbon.analytics.dataservice.commons.AnalyticsDataResponse; +import org.wso2.carbon.analytics.dataservice.commons.AnalyticsDrillDownRequest; import org.wso2.carbon.analytics.dataservice.commons.SearchResultEntry; import org.wso2.carbon.analytics.dataservice.core.AnalyticsDataServiceUtils; import org.wso2.carbon.analytics.datasource.commons.Record; @@ -13,18 +14,41 @@ import org.wso2.carbon.la.core.utils.LACoreServiceValueHolder; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class SearchController { private static final Log log = LogFactory.getLog(SearchController.class); - + private final String logstream = "logstream"; public List search(QueryBean query, String username) throws AnalyticsException { + AnalyticsDrillDownRequest analyticsDrillDownRequest = new AnalyticsDrillDownRequest(); + AnalyticsDataAPI analyticsDataService = LACoreServiceValueHolder.getInstance().getAnalyticsDataAPI(); if (query != null) { + String facetPath = query.getFacetPath(); + Map> categoryPath = new HashMap<>(); + List pathList = new ArrayList<>(); + if(facetPath.equals("None")){ + categoryPath.put(logstream,pathList); + } + else { + String pathArray[] = facetPath.split(","); + for (String path : pathArray) { + pathList.add(path); + } + categoryPath.put(logstream, pathList); + } + analyticsDrillDownRequest.setTableName(query.getTableName()); query.setQuery(appendTimeRangeToSearchQuery(query.getQuery(), query.getTimeFrom(), query.getTimeTo())); - List searchResults = analyticsDataService.search(username,query.getTableName(), - query.getQuery(),query.getStart(), query.getLength()); + analyticsDrillDownRequest.setQuery(query.getQuery()); + analyticsDrillDownRequest.setRecordCount(query.getLength()); + analyticsDrillDownRequest.setRecordStartIndex(query.getStart()); + analyticsDrillDownRequest.setCategoryPaths(categoryPath); + List searchResults = analyticsDataService.drillDownSearch(username,analyticsDrillDownRequest); + //List searchResults = analyticsDataService.search(username,query.getTableName(), + // query.getQuery(),query.getStart(), query.getLength()); List ids = getRecordIds(searchResults); AnalyticsDataResponse resp = analyticsDataService.get(username, query.getTableName(), 1, null, ids); diff --git a/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/DashboardApiV10.java b/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/DashboardApiV10.java index 7ffacae..0301ce6 100644 --- a/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/DashboardApiV10.java +++ b/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/DashboardApiV10.java @@ -138,14 +138,6 @@ public void write(OutputStream outputStream) counter.put(val, count); } } - //values.get(query.getQuery()); - - // recordWriter.write(gson.toJson(values.get(query.getQuery()))); - - // recordWriter.write(recordBean.toString()); - // if (iterator.hasNext()) { - //recordWriter.write(","); - //} if (log.isDebugEnabled()) { log.debug("Retrieved -- Record Id: " + recordBean.getId() + " values :" + recordBean.toString()); @@ -157,7 +149,6 @@ public void write(OutputStream outputStream) recordWriter.write("[[\"" + entry.getKey() + "\"],[\"" + entry.getValue() + "\"]]"); - // recordWriter.write("\""+entry.getKey()+" : "+entry.getValue()+"||%\""); if (i < counter.size()) { recordWriter.write(","); i++; @@ -194,7 +185,9 @@ public StreamingOutput filterData(final QueryBean query) throws AnalyticsExcepti String username = carbonContext.getUsername(); AnalyticsDataAPI analyticsDataAPI; AnalyticsDataResponse analyticsDataResponse; + String logstream = "logstream"; //RecordGroup recordGroup [] ; + AnalyticsDrillDownRequest analyticsDrillDownRequest = new AnalyticsDrillDownRequest(); analyticsDataAPI = LACoreServiceValueHolder.getInstance().getAnalyticsDataAPI(); if (query != null) { String q[] = query.getQuery().split(",,"); @@ -203,9 +196,25 @@ public StreamingOutput filterData(final QueryBean query) throws AnalyticsExcepti List column = new ArrayList<>(); column.add(col); - List searchResults = analyticsDataAPI.search(username, - query.getTableName(), searchQuery, - query.getStart(), query.getCount()); + String facetPath = query.getFacetPath(); + Map> categoryPath = new HashMap<>(); + List pathList = new ArrayList<>(); + if (facetPath.equals("None")) { + categoryPath.put(logstream, pathList); + } else { + String pathArray[] = facetPath.split(","); + for (String path : pathArray) { + pathList.add(path); + } + categoryPath.put(logstream, pathList); + } + analyticsDrillDownRequest.setTableName(query.getTableName()); + analyticsDrillDownRequest.setQuery(searchQuery); + analyticsDrillDownRequest.setRecordCount(query.getLength()); + analyticsDrillDownRequest.setRecordStartIndex(query.getStart()); + analyticsDrillDownRequest.setCategoryPaths(categoryPath); + List searchResults = analyticsDataAPI.drillDownSearch(username, analyticsDrillDownRequest); + List ids = Util.getRecordIds(searchResults); analyticsDataResponse = analyticsDataAPI.get(username, query.getTableName(), 1, column, ids); final List> iterators = Util.getRecordIterators(analyticsDataResponse, analyticsDataAPI); @@ -245,14 +254,7 @@ public void write(OutputStream outputStream) counter.put(val, count); } } - //values.get(query.getQuery()); - // recordWriter.write(gson.toJson(values.get(query.getQuery()))); - - // recordWriter.write(recordBean.toString()); - // if (iterator.hasNext()) { - //recordWriter.write(","); - //} if (log.isDebugEnabled()) { log.debug("Retrieved -- Record Id: " + recordBean.getId() + " values :" + recordBean.toString()); @@ -263,7 +265,6 @@ public void write(OutputStream outputStream) for (Map.Entry entry : counter.entrySet()) { recordWriter.write("[[\"" + entry.getKey() + "\"],[\"" + entry.getValue() + "\"]]"); - //recordWriter.write("\""+entry.getKey()+" : "+entry.getValue()+"||%\""); if (i < counter.size()) { recordWriter.write(","); i++; @@ -286,7 +287,7 @@ public void write(OutputStream outputStream) throws IOException, WebApplicationE }; } } - +/* @POST @Path("/timeData") @Produces("application/json") @@ -311,7 +312,7 @@ public StreamingOutput timeData(final QueryBean query) throws AnalyticsException List searchResults = analyticsDataAPI.search(username, query.getTableName(), searchQuery, - query.getStart(), query.getCount()); + query.getStart(), query.getLength()); List ids = Util.getRecordIds(searchResults); analyticsDataResponse = analyticsDataAPI.get(username, query.getTableName(), 1, column, ids); final List> iterators = Util.getRecordIterators(analyticsDataResponse, analyticsDataAPI); @@ -378,8 +379,6 @@ public void write(OutputStream outputStream) recordWriter.write(","); j++; } - //recordWriter.write("[[\""+entry.getKey()+"\"],[\""+entry.getValue()+"\"]]"); - //recordWriter.write("\""+entry.getKey()+" : "+entry.getValue()+"||%\""); } recordWriter.write("]"); @@ -428,7 +427,7 @@ public StreamingOutput EpochtimeData(final QueryBean query) throws AnalyticsExce List searchResults = analyticsDataAPI.search(username, query.getTableName(), searchQuery, - query.getStart(), query.getCount()); + query.getStart(), query.getLength()); List ids = Util.getRecordIds(searchResults); analyticsDataResponse = analyticsDataAPI.get(username, query.getTableName(), 1, column, ids); final List> iterators = Util.getRecordIterators(analyticsDataResponse, analyticsDataAPI); @@ -497,21 +496,6 @@ public void write(OutputStream outputStream) long k = 1; for (Map.Entry> entry : sortedMap.entrySet()) { - /* - Map countero = entry.getValue(); - for(Map.Entry ing:countero.entrySet()){ - long temp = entry.getKey()/1000L; - Date expiry = new Date(temp*1000L); - String str_date = format.format(expiry); - recordWriter.write("[\"" + str_date + "\"],[\""+ing.getValue()+"\"]"); - } - if (j < sortedMap.size()) { - recordWriter.write(","); - j++; - } - */ - - if (j > 1) { presentDay = entry.getKey(); long dif = presentDay - lastDay; @@ -554,8 +538,6 @@ public void write(OutputStream outputStream) j++; } lastDay = entry.getKey(); - //recordWriter.write("[[\""+entry.getKey()+"\"],[\""+entry.getValue()+"\"]]"); - //recordWriter.write("\""+entry.getKey()+" : "+entry.getValue()+"||%\""); } } @@ -576,7 +558,7 @@ public void write(OutputStream outputStream) throws IOException, WebApplicationE }; } - } + } */ @POST @Path("/epochTimeDataFinal") @@ -590,6 +572,8 @@ public StreamingOutput EpochtimeDataFinal(final QueryBean query) throws Analytic AnalyticsDataAPI analyticsDataAPI; AnalyticsDataResponse analyticsDataResponse; //RecordGroup recordGroup [] ; + AnalyticsDrillDownRequest analyticsDrillDownRequest = new AnalyticsDrillDownRequest(); + String logstream = "logstream"; analyticsDataAPI = LACoreServiceValueHolder.getInstance().getAnalyticsDataAPI(); if (query != null) { String q[] = query.getQuery().split(",,"); @@ -604,9 +588,26 @@ public StreamingOutput EpochtimeDataFinal(final QueryBean query) throws Analytic column.add(col); column.add(timestamp); - List searchResults = analyticsDataAPI.search(username, - query.getTableName(), searchQuery, - query.getStart(), query.getCount()); + String facetPath = query.getFacetPath(); + Map> categoryPath = new HashMap<>(); + List pathList = new ArrayList<>(); + if (facetPath.equals("None")) { + categoryPath.put(logstream, pathList); + } else { + String pathArray[] = facetPath.split(","); + for (String path : pathArray) { + pathList.add(path); + } + categoryPath.put(logstream, pathList); + } + analyticsDrillDownRequest.setTableName(query.getTableName()); + analyticsDrillDownRequest.setQuery(searchQuery); + analyticsDrillDownRequest.setRecordCount(query.getLength()); + analyticsDrillDownRequest.setRecordStartIndex(query.getStart()); + analyticsDrillDownRequest.setCategoryPaths(categoryPath); + List searchResults = analyticsDataAPI.drillDownSearch(username, analyticsDrillDownRequest); + + List ids = Util.getRecordIds(searchResults); analyticsDataResponse = analyticsDataAPI.get(username, query.getTableName(), 1, column, ids); final List> iterators = Util.getRecordIterators(analyticsDataResponse, analyticsDataAPI); @@ -685,9 +686,9 @@ public void write(OutputStream outputStream) long newDate = lastDay; while (k > 1) { newDate = newDate + dayGap; - Map tempMap = new HashMap<>(); - tempMap.put("No Entry",0); - allDayMap.put(newDate,tempMap); + Map tempMap = new HashMap<>(); + tempMap.put("No Entry", 0); + allDayMap.put(newDate, tempMap); k--; } k = 1; @@ -699,22 +700,22 @@ public void write(OutputStream outputStream) j++; } lastDay = entry.getKey(); - allDayMap.put(lastDay,counter); + allDayMap.put(lastDay, counter); } } - allDayMap = new TreeMap>(allDayMap); - Map> grouped = Util.getGrouping(allDayMap,groupBy); + allDayMap = new TreeMap>(allDayMap); + Map> grouped = Util.getGrouping(allDayMap, groupBy); grouped = new TreeMap<>(grouped); int i = 1; int l = 1; recordWriter.write("["); - for (Map.Entry> entry : grouped.entrySet()) { + for (Map.Entry> entry : grouped.entrySet()) { Map counter = entry.getValue(); for (Map.Entry infom : counter.entrySet()) { - recordWriter.write("[[\"" + entry.getKey()+ "\"],[\"" + infom.getKey() + "\"],[\"" + infom.getValue() + "\"]]"); + recordWriter.write("[[\"" + entry.getKey() + "\"],[\"" + infom.getKey() + "\"],[\"" + infom.getValue() + "\"]]"); if (i < counter.size()) { recordWriter.write(","); i++; @@ -745,6 +746,7 @@ public void write(OutputStream outputStream) throws IOException, WebApplicationE } } + @POST @Path("/logStreamData") @Produces("application/json") @@ -755,8 +757,8 @@ public StreamingOutput logStreamData(final QueryBean query) { int tenantId = carbonContext.getTenantId(); AnalyticsDataAPI analyticsDataAPI; String q[] = query.getQuery().split(",,"); - String path[] =null; - if(!q[1].equals(" ")) { + String path[] = null; + if (!q[1].equals(" ")) { String pathName = q[1]; path = pathName.split(","); } diff --git a/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/util/Util.java b/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/util/Util.java index 3eada5d..8b8505f 100644 --- a/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/util/Util.java +++ b/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/util/Util.java @@ -29,7 +29,7 @@ public static List> getRecordIterators(AnalyticsDataResponse re for (RecordGroup recordGroup : resp.getRecordGroups()) { iterators.add(analyticsDataService.readRecords(resp.getRecordStoreName(), recordGroup)); } - // IteratorUtils iteratorUtils = new IteratorUtils(); + // IteratorUtils iteratorUtils = new IteratorUtils(); //Iterator iterator = iteratorUtils.chainedIterator(iterators); return iterators; } @@ -51,18 +51,23 @@ public static List getRecordIds(List searchResults) { return ids; } - public static Map> getGrouping(Map> allDayMap,String method){ - Map> grouped = new HashMap<>(); - switch (method){ - case auto: grouped=groupbyAuto(allDayMap); - break; - case day:grouped=groupbyDay(allDayMap); + public static Map> getGrouping(Map> allDayMap, String method) { + Map> grouped = new HashMap<>(); + switch (method) { + case auto: + grouped = groupbyAuto(allDayMap); break; - case week:grouped=groupbyWeek(allDayMap); + case day: + grouped = groupbyDay(allDayMap); break; - case month : grouped = groupbyMonth(allDayMap); + case week: + grouped = groupbyWeek(allDayMap); break; - case year : grouped = groupbyYear(allDayMap); + case month: + grouped = groupbyMonth(allDayMap); + break; + case year: + grouped = groupbyYear(allDayMap); break; } @@ -70,172 +75,163 @@ public static Map> getGrouping(Map> groupbyAuto(Map> allDayMap){ - Map> grouped = new HashMap<>(); + public static Map> groupbyAuto(Map> allDayMap) { + Map> grouped = new HashMap<>(); int days = allDayMap.size(); - if(days<=10){ + if (days <= 10) { grouped = groupbyDay(allDayMap); - } - else if(days>10 && days<=70){ + } else if (days > 10 && days <= 70) { grouped = groupbyWeek(allDayMap); - } - else if(days>70 && days<=365*3){ + } else if (days > 70 && days <= 365 * 3) { grouped = groupbyMonth(allDayMap); - } - else{ + } else { grouped = groupbyYear(allDayMap); } return grouped; } - public static Map> groupbyDay(Map> allDayMap){ - Map> grouped = new HashMap<>(); + public static Map> groupbyDay(Map> allDayMap) { + Map> grouped = new HashMap<>(); String pattern = "yyyy-MM-dd"; SimpleDateFormat format = new SimpleDateFormat(pattern); //long temp = epoch/1000L; - for(Map.Entry> entry:allDayMap.entrySet()){ + for (Map.Entry> entry : allDayMap.entrySet()) { Date expiry = new Date(entry.getKey()); String str_week = format.format(expiry); - grouped.put(str_week,entry.getValue()); + grouped.put(str_week, entry.getValue()); } return grouped; } - public static Map> groupbyWeek(Map> allDayMap){ + public static Map> groupbyWeek(Map> allDayMap) { String pattern = "Y/MM:W"; SimpleDateFormat format = new SimpleDateFormat(pattern); //long temp = epoch/1000L; - Map>> grouped = new HashMap<>(); - for(Map.Entry> entry:allDayMap.entrySet()){ + Map>> grouped = new HashMap<>(); + for (Map.Entry> entry : allDayMap.entrySet()) { Date expiry = new Date(entry.getKey()); String str_week = format.format(expiry); - if(!grouped.containsKey(str_week)){ - List> list = new ArrayList<>(); + if (!grouped.containsKey(str_week)) { + List> list = new ArrayList<>(); list.add(entry.getValue()); - grouped.put(str_week,list); - } - else{ - List> list = grouped.get(str_week); + grouped.put(str_week, list); + } else { + List> list = grouped.get(str_week); list.add(entry.getValue()); - grouped.put(str_week,list); + grouped.put(str_week, list); } //grouped.put(str_week,entry.getValue()); } - Map> Fgrouped = new HashMap<>(); - for(Map.Entry>> entry:grouped.entrySet()){ - List> list = entry.getValue(); - Map data = new HashMap<>(); + Map> Fgrouped = new HashMap<>(); + for (Map.Entry>> entry : grouped.entrySet()) { + List> list = entry.getValue(); + Map data = new HashMap<>(); //Map counter = entry.getValue(); - for(Map dataMap : list){ - for(Map.Entry entrr:dataMap.entrySet()){ - if(!data.containsKey(entrr.getKey())){ - data.put(entrr.getKey(),entrr.getValue()); - } - else{ + for (Map dataMap : list) { + for (Map.Entry entrr : dataMap.entrySet()) { + if (!data.containsKey(entrr.getKey())) { + data.put(entrr.getKey(), entrr.getValue()); + } else { int k = data.get(entrr.getKey()); - data.put(entrr.getKey(),k+entrr.getValue()); + data.put(entrr.getKey(), k + entrr.getValue()); } } } - Fgrouped.put(entry.getKey(),data); + Fgrouped.put(entry.getKey(), data); } return Fgrouped; } - public static Map> groupbyMonth(Map> allDayMap){ + public static Map> groupbyMonth(Map> allDayMap) { String pattern = "Y-MM"; SimpleDateFormat format = new SimpleDateFormat(pattern); //long temp = epoch/1000L; - Map>> grouped = new HashMap<>(); - for(Map.Entry> entry:allDayMap.entrySet()){ + Map>> grouped = new HashMap<>(); + for (Map.Entry> entry : allDayMap.entrySet()) { Date expiry = new Date(entry.getKey()); String str_week = format.format(expiry); - if(!grouped.containsKey(str_week)){ - List> list = new ArrayList<>(); + if (!grouped.containsKey(str_week)) { + List> list = new ArrayList<>(); list.add(entry.getValue()); - grouped.put(str_week,list); - } - else{ - List> list = grouped.get(str_week); + grouped.put(str_week, list); + } else { + List> list = grouped.get(str_week); list.add(entry.getValue()); - grouped.put(str_week,list); + grouped.put(str_week, list); } //grouped.put(str_week,entry.getValue()); } - Map> Fgrouped = new HashMap<>(); - for(Map.Entry>> entry:grouped.entrySet()){ - List> list = entry.getValue(); - Map data = new HashMap<>(); + Map> Fgrouped = new HashMap<>(); + for (Map.Entry>> entry : grouped.entrySet()) { + List> list = entry.getValue(); + Map data = new HashMap<>(); //Map counter = entry.getValue(); - for(Map dataMap : list){ - for(Map.Entry entrr:dataMap.entrySet()){ - if(!data.containsKey(entrr.getKey())){ - data.put(entrr.getKey(),entrr.getValue()); - } - else{ + for (Map dataMap : list) { + for (Map.Entry entrr : dataMap.entrySet()) { + if (!data.containsKey(entrr.getKey())) { + data.put(entrr.getKey(), entrr.getValue()); + } else { int k = data.get(entrr.getKey()); - data.put(entrr.getKey(),k+entrr.getValue()); + data.put(entrr.getKey(), k + entrr.getValue()); } } } - Fgrouped.put(entry.getKey(),data); + Fgrouped.put(entry.getKey(), data); } return Fgrouped; } - public static Map> groupbyYear(Map> allDayMap){ + public static Map> groupbyYear(Map> allDayMap) { String pattern = "YYYY"; SimpleDateFormat format = new SimpleDateFormat(pattern); //long temp = epoch/1000L; - Map>> grouped = new HashMap<>(); - for(Map.Entry> entry:allDayMap.entrySet()){ + Map>> grouped = new HashMap<>(); + for (Map.Entry> entry : allDayMap.entrySet()) { Date expiry = new Date(entry.getKey()); String str_week = format.format(expiry); - if(!grouped.containsKey(str_week)){ - List> list = new ArrayList<>(); + if (!grouped.containsKey(str_week)) { + List> list = new ArrayList<>(); list.add(entry.getValue()); - grouped.put(str_week,list); - } - else{ - List> list = grouped.get(str_week); + grouped.put(str_week, list); + } else { + List> list = grouped.get(str_week); list.add(entry.getValue()); - grouped.put(str_week,list); + grouped.put(str_week, list); } //grouped.put(str_week,entry.getValue()); } - Map> Fgrouped = new HashMap<>(); - for(Map.Entry>> entry:grouped.entrySet()){ - List> list = entry.getValue(); - Map data = new HashMap<>(); + Map> Fgrouped = new HashMap<>(); + for (Map.Entry>> entry : grouped.entrySet()) { + List> list = entry.getValue(); + Map data = new HashMap<>(); //Map counter = entry.getValue(); - for(Map dataMap : list){ - for(Map.Entry entrr:dataMap.entrySet()){ - if(!data.containsKey(entrr.getKey())){ - data.put(entrr.getKey(),entrr.getValue()); - } - else{ + for (Map dataMap : list) { + for (Map.Entry entrr : dataMap.entrySet()) { + if (!data.containsKey(entrr.getKey())) { + data.put(entrr.getKey(), entrr.getValue()); + } else { int k = data.get(entrr.getKey()); - data.put(entrr.getKey(),k+entrr.getValue()); + data.put(entrr.getKey(), k + entrr.getValue()); } } } - Fgrouped.put(entry.getKey(),data); + Fgrouped.put(entry.getKey(), data); } return Fgrouped; diff --git a/modules/jaggeryapps/loganalyzer/css/dashboard.css b/modules/jaggeryapps/loganalyzer/css/dashboard.css index 75180ff..69383ce 100644 --- a/modules/jaggeryapps/loganalyzer/css/dashboard.css +++ b/modules/jaggeryapps/loganalyzer/css/dashboard.css @@ -80,6 +80,13 @@ margin: 0px 0px 20px; } +.logStreamDiv select{ + margin: 0px 0px 20px; + background-color: ghostwhite; + width:20%; + border: 1px solid #C0C0C0; +} + .dashboardNav form{ margin: 0px 0px 20px; } diff --git a/modules/jaggeryapps/loganalyzer/js/visualize.js b/modules/jaggeryapps/loganalyzer/js/visualize.js index 1a540bd..b042083 100644 --- a/modules/jaggeryapps/loganalyzer/js/visualize.js +++ b/modules/jaggeryapps/loganalyzer/js/visualize.js @@ -81,10 +81,10 @@ function addFields() { for (i = 0; i < j; i++) { - fields[i] = fields[i].replace("\"", "") - fields[i] = fields[i].replace("\"", "") - fields[i] = capitalizeFirstLetter(fields[i].replace("_", "")); - if(fields[i]!="Message") { + fields[i] = fields[i].replace("\"", "") + fields[i] = fields[i].replace("\"", "") + fields[i] = capitalizeFirstLetter(fields[i].replace("_", "")); + if (fields[i] != "Message") { var option = document.createElement('option'); option.text = option.value = fields[i]; select.add(option, 0); @@ -101,7 +101,10 @@ function simpleFirstLetter(string) { } function selectField2() { - + if ($("#0").val() == "None") { + facetPath = "None"; + } + // document.getElementById("json_string2").innerHTML=facetPath; fieldName = arguments[0]; var charttype = arguments[1]; trem = fieldName; @@ -113,7 +116,7 @@ function selectField2() { var payload = {}; payload.query = fieldName; payload.start = 0; - payload.count = 100; + payload.length = 100000; payload.timeFrom = 0; payload.tableName = "LOGANALYZER"; payload.timeTo = 8640000000000000; @@ -148,7 +151,7 @@ function addRow() { var i; var j = arguments[0].length; //var tester; - document.getElementById("json_string").innerHTML = j; + // document.getElementById("json_string").innerHTML = j; for (i = 0; i < j; i++) { $("#data-table").append('' + reslt[i][0] + '' + reslt[i][1] + ''); } @@ -164,29 +167,42 @@ function addRow() { });*/ function filter() { + if ($("#0").val() == "None") { + facetPath = "None"; + } $("#dsWelcome").hide(); var query = $('#ftexte').val(); var fieldN = $("#fieldsName").val(); + var filterSelect = $("#FilterType").val(); fieldN = simpleFirstLetter(fieldN); if (fieldN != "logstream") { fieldN = "_" + fieldN; } - if (query == "") { - selectField2(); + if (query == "" && filterSelect == "None") { + selectField2($("#fieldsName").val()); } else { //document.getElementById("json_string2").innerHTML=fieldName; - query = query + ",," + fieldN; + var quer; + if (query == "") { + quer = filterSelect; + } + else { + quer = query; + } + query = quer + ",," + fieldN; // var payload = {}; + // document.getElementById("json_string2").innerHTML = facetPath.toString(); payload.query = query; payload.start = 0; - payload.count = 1000000; + payload.length = 1000000; payload.timeFrom = 0; payload.tableName = "LOGANALYZER"; payload.timeTo = 8640000000000000; + payload.facetPath = facetPath.toString(); var jsonnn = JSON.stringify(payload); //document.getElementById("json_string2").innerHTML = "You selected:!!" + query+"!!"; @@ -224,7 +240,16 @@ function draw() { //var charttype=arguments[2]; for (var i = 0; i < json.length; i++) { var temp = []; - temp.push(json[i][0], parseInt(json[i][1])); + var xVal = json[i][0]; + var xArr = []; + xArr = json[i][0].toString().split("."); + var vall = xArr[xArr.length - 1]; + if (vall.length > 11) { + vall = vall.slice(0, 11); + } + // var xArr = json[i][0].split("."); + //document.getElementById("json_string").innerHTML=xArr[xArr.length-1]+" "; + temp.push(vall, parseInt(json[i][1])); dataValue.push(temp); } //document.getElementById("json_string").innerHTML=JSON.stringify(dataValue); @@ -255,7 +280,7 @@ function test(val) { // var res = JSON.parse(arguments); //var res = arguments[0]; var id = val; - document.getElementById("json_string3").innerHTML = id; + // document.getElementById("json_string3").innerHTML = id; } function drawTime() { @@ -291,6 +316,9 @@ function drawTime() { lineChart.draw("#timeChart"); } function timeData() { + if ($("#0").val() == "None") { + facetPath = "None"; + } $("#dsWelcome").hide(); $("#timeChart").show(); var query = $('#ftexte').val(); @@ -305,16 +333,17 @@ function timeData() { } else { //document.getElementById("json_string2").innerHTML=fieldName; - query = query + ",," + fieldN+",,"+ interval; + query = query + ",," + fieldN + ",," + interval; // var payload = {}; payload.query = query; payload.start = 0; - payload.count = 100; + payload.length = 100000; payload.timeFrom = 0; payload.tableName = "LOGANALYZER"; payload.timeTo = 8640000000000000; + payload.facetPath = facetPath.toString(); var jsonnn = JSON.stringify(payload); //document.getElementById("json_string2").innerHTML = "You selected:!!" + query+"!!"; @@ -380,7 +409,7 @@ function addLogstream() { var seperator = ",,"; payload.query = logstream + seperator + " "; payload.start = 0; - payload.count = 100; + payload.length = 100000; payload.timeFrom = 0; payload.tableName = "LOGANALYZER"; payload.timeTo = 8640000000000000; @@ -400,7 +429,7 @@ function addLogstream() { for (var i = 0; i < res.length; i++) { var option = document.createElement('option'); option.text = option.value = res[i]; - option.id=-1; + option.id = -1; select.add(option, 0); } @@ -411,106 +440,108 @@ function addLogstream() { } }); } -var facetCount =0; +var facetCount = 0; var facetObj = {}; -var testObj ={}; +var testObj = {}; -function addChildLogStream(val,idVal){ - var idInt= parseInt(idVal); - //document.getElementById("json_string3").innerHTML = val; +function addChildLogStream(val, idVal) { + var idInt = parseInt(idVal); - if(testObj.hasOwnProperty(idVal)){ - for(var key in testObj){ - if(key>idVal){ - // $("#"+key).remove(); - delete testObj[key]; - } + //if(testObj.hasOwnProperty(idVal)){ + for (var key in testObj) { + if (key > idVal) { + $("#" + key).remove(); + delete testObj[key]; } } - if(!facetObj.hasOwnProperty(idVal)){ - facetObj[idVal] = val ; - + if (val != "None") { + // } + if (!facetObj.hasOwnProperty(idVal)) { + facetObj[idVal] = val; - //facetData.push(val); + //facetData.push(val); - } - else{ - for(var key in facetObj){ - if(key>=idInt) { - delete facetObj[key]; - //delete facetObj; - if(key!=idInt) { - - $("#"+key).remove(); + } + else { + for (var key in facetObj) { + if (key >= idInt) { + delete facetObj[key]; + //delete facetObj; + if (key != idInt) { + + $("#" + key).remove(); + } } } - } - - facetCount = idInt; - facetObj[idInt] = val ; - //facetData.push(val); - } + facetCount = idInt; + facetObj[idInt] = val; + //facetData.push(val); - var facetData = [] ; - for(var key in facetObj){ - facetData.push(facetObj[key]); - } - var facetpath = facetData; - document.getElementById("json_string3").innerHTML = JSON.stringify(facetObj)+" "+idVal+" "+val+" "+facetpath ; - //document.getElementById("json_string3").innerHTML=facetpath +" "+JSON.stringify(facetObj); + } + //document.getElementById("logTest").innerHTML = "Val "+val+" idVal"+idVal+" facetObj "+JSON.stringify(facetObj)+" testObj "+JSON.stringify(testObj); - var payload = {}; - var logstream = "logstream"; - var seperator =",,"; - payload.query = logstream+seperator+facetpath; - payload.start = 0; - payload.count = 100; - payload.timeFrom = 0; - payload.tableName = "LOGANALYZER"; - payload.timeTo = 8640000000000000; - var jsonnn = JSON.stringify(payload); + var facetData = []; + for (var key in facetObj) { + facetData.push(facetObj[key]); + } + facetPath = facetData; + //document.getElementById("json_string3").innerHTML=facetpath +" "+JSON.stringify(facetObj); + // $("#facetPath").val(facetPath); + var payload = {}; + var logstream = "logstream"; + var seperator = ",,"; + payload.query = logstream + seperator + facetPath; + payload.start = 0; + payload.length = 10000; + payload.timeFrom = 0; + payload.tableName = "LOGANALYZER"; + payload.timeTo = 8640000000000000; + var jsonnn = JSON.stringify(payload); - jQuery.ajax({ - url: serverUrl + "/api/dashboard/logStreamData", - type: "POST", - contentType: "application/json", - dataType: "json", - data: jsonnn, - success: function (res) { - //console.log(response); - //document.getElementById("json_string3").innerHTML=res.length; - //var select = document.getElementById("logstreamSelect"); - var streamDiv = document.getElementById("logStreamDiv"); - var selectList = document.createElement("select"); - facetCount++; - if (!testObj.hasOwnProperty(facetCount)){ - selectList.id = facetCount; - testObj[facetCount] = "test"; - - //selectList.onchange = addChildLogStream(this.value,this.id); - selectList.setAttribute("onchange", "addChildLogStream(this.value,this.id)"); - streamDiv.appendChild(selectList); - var option1 = document.createElement('option'); - option1.text = option1.value = "None"; - selectList.add(option1); - for (var i = 0; i < res.length; i++) { - var option = document.createElement('option'); - option.text = option.value = res[i]; - selectList.add(option, 0); + jQuery.ajax({ + url: serverUrl + "/api/dashboard/logStreamData", + type: "POST", + contentType: "application/json", + dataType: "json", + data: jsonnn, + success: function (res) { + //console.log(response); + //document.getElementById("json_string3").innerHTML=res.length; + //var select = document.getElementById("logstreamSelect"); + + facetCount++; + if (!testObj.hasOwnProperty(facetCount)) { + var LogstreamDiv = document.getElementById("logStreamDiv"); + var selectList = document.createElement("select"); + selectList.id = facetCount; + testObj[facetCount] = "test"; + //document.getElementById("json_string3").innerHTML = JSON.stringify(facetObj); + //selectList.onchange = addChildLogStream(this.value,this.id); + selectList.setAttribute("onchange", "addChildLogStream(this.value,this.id)"); + LogstreamDiv.appendChild(selectList); + var option1 = document.createElement('option'); + option1.value = "None"; + option1.text = "Select a category" + selectList.add(option1); + for (var i = 0; i < res.length; i++) { + var option = document.createElement('option'); + option.text = option.value = res[i]; + selectList.add(option, 0); + } + } + }, + error: function (res) { + var response = JSON.stringify(res); + alert(res.error); } - } - }, - error: function (res) { - var response = JSON.stringify(res); - alert(res.error); - } - }); - + }); + //document.getElementById("logTest").innerHTML = facetpath; + } } diff --git a/modules/jaggeryapps/loganalyzer/site/dashboard/visualize.jag b/modules/jaggeryapps/loganalyzer/site/dashboard/visualize.jag index d1625e7..5fe7dcf 100644 --- a/modules/jaggeryapps/loganalyzer/site/dashboard/visualize.jag +++ b/modules/jaggeryapps/loganalyzer/site/dashboard/visualize.jag @@ -87,10 +87,6 @@ Link 3 -
-

Logstream

- -

Visualize

@@ -102,13 +98,24 @@ + +

Logstream

+ + +
+ +
+

Filter

- + + + +
+

+ +
- +

diff --git a/modules/jaggeryapps/loganalyzer/site/search/search.js b/modules/jaggeryapps/loganalyzer/site/search/search.js index e1d0a47..ec8b393 100644 --- a/modules/jaggeryapps/loganalyzer/site/search/search.js +++ b/modules/jaggeryapps/loganalyzer/site/search/search.js @@ -1,4 +1,10 @@ var serverUrl = window.location.origin; +var facetPath ="None" ; +function openDashboard() { + //window.location= baseUrl +'/loganalyzer/site/visualize.jag'; + // retrieveColum(); + window.open(serverUrl + '/loganalyzer/site/dashboard/visualize.jag'); +} //var baseUrl = getBaseUrl(window.location.href); var resultTable = $('#results-table').DataTable( { "processing": false, @@ -10,8 +16,15 @@ var resultTable = $('#results-table').DataTable( { "contentType": "application/json; charset=utf-8", "data": function (payload) { payload.query = $("#search-field").val(); - payload.timeFrom = parseInt($("#timestamp-from").val()); - payload.timeTo = parseInt($("#timestamp-to").val()); + // payload.timeFrom = parseInt($("#timestamp-from").val()); + // payload.timeTo = parseInt($("#timestamp-to").val()); + //document.getElementById("logpath").innerHTML = facetPath; + payload.facetPath =$("#facetPath").val(); + payload.timeFrom = 0; + payload.timeTo = 8640000000000000; + //payload.tableName="LOGANALYZER"; + //payload.length = 100; + payload.start =0; return JSON.stringify(payload) } }, @@ -159,6 +172,7 @@ function getLastMonth(){ } function searchActivities(data){ + resultTable.ajax.reload(); } @@ -201,7 +215,7 @@ function addLogstream1() { var seperator = ",,"; payload.query = logstream + seperator + " "; payload.start = 0; - payload.count = 100; + payload.length = 10000; payload.timeFrom = 0; payload.tableName = "LOGANALYZER"; payload.timeTo = 8640000000000000; @@ -280,15 +294,15 @@ function addChildLogStream1(val,idVal) { for (var key in facetObj) { facetData.push(facetObj[key]); } - var facetpath = facetData; + facetPath = facetData; //document.getElementById("json_string3").innerHTML=facetpath +" "+JSON.stringify(facetObj); - + $("#facetPath").val(facetPath); var payload = {}; var logstream = "logstream"; var seperator = ",,"; - payload.query = logstream + seperator + facetpath; + payload.query = logstream + seperator + facetPath; payload.start = 0; - payload.count = 100; + payload.length = 10000; payload.timeFrom = 0; payload.tableName = "LOGANALYZER"; payload.timeTo = 8640000000000000; From 3df4ca19db5f8f1af5de4b98d30d2d32efda1060 Mon Sep 17 00:00:00 2001 From: VIthulan Date: Mon, 29 Feb 2016 15:38:08 +0530 Subject: [PATCH 7/9] Adding time range filtering feature --- .../carbon/la/commons/domain/QueryBean.java | 15 +++++++ .../carbon/la/restapi/DashboardApiV10.java | 25 +++++++++-- .../org/wso2/carbon/la/restapi/util/Util.java | 25 +++++++++++ .../jaggeryapps/loganalyzer/css/dashboard.css | 4 ++ .../jaggeryapps/loganalyzer/js/visualize.js | 45 ++++++++++++++----- .../loganalyzer/site/dashboard/visualize.jag | 6 +-- pom.xml | 2 +- 7 files changed, 103 insertions(+), 19 deletions(-) diff --git a/modules/components/org.wso2.carbon.la.commons/src/main/java/org/wso2/carbon/la/commons/domain/QueryBean.java b/modules/components/org.wso2.carbon.la.commons/src/main/java/org/wso2/carbon/la/commons/domain/QueryBean.java index 0282343..0fcd27f 100644 --- a/modules/components/org.wso2.carbon.la.commons/src/main/java/org/wso2/carbon/la/commons/domain/QueryBean.java +++ b/modules/components/org.wso2.carbon.la.commons/src/main/java/org/wso2/carbon/la/commons/domain/QueryBean.java @@ -40,6 +40,21 @@ public class QueryBean { private String facetPath; + private String str_timeFrom; + private String str_timeTo; + + public String getStr_timeFrom(){ return str_timeFrom; } + + public void setStr_timeFrom(String str_timeFrom) { + this.str_timeFrom = str_timeFrom; + } + + public void setStr_timeTo(String str_timeTo){ + this.str_timeTo = str_timeTo; + } + + public String getStr_timeTo(){ return str_timeTo; } + public int getLength() { return length; } diff --git a/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/DashboardApiV10.java b/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/DashboardApiV10.java index 0301ce6..c37899a 100644 --- a/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/DashboardApiV10.java +++ b/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/DashboardApiV10.java @@ -117,7 +117,6 @@ public void write(OutputStream outputStream) while (iterator.hasNext()) { RecordBean recordBean = Util.createRecordBean(iterator.next()); values = recordBean.getValues(); - if (values.get(query.getQuery()) == null) { if (!counter.containsKey("NULLVALUE")) { @@ -192,6 +191,7 @@ public StreamingOutput filterData(final QueryBean query) throws AnalyticsExcepti if (query != null) { String q[] = query.getQuery().split(",,"); String searchQuery = q[0]; + searchQuery = Util.appendTimeStamp(searchQuery,query.getStr_timeFrom(),query.getStr_timeTo()); final String col = q[1]; List column = new ArrayList<>(); column.add(col); @@ -578,9 +578,10 @@ public StreamingOutput EpochtimeDataFinal(final QueryBean query) throws Analytic if (query != null) { String q[] = query.getQuery().split(",,"); String searchQuery = q[0]; + searchQuery = Util.appendTimeStamp(searchQuery,query.getStr_timeFrom(),query.getStr_timeTo()); final String col = q[1]; final String groupBy = q[2]; - final String timestamp = "_timestamp2"; + final String timestamp = "_eventTimeStamp"; final long dayGap = 86400000; final String pattern = "yyyy-MM-dd"; final SimpleDateFormat format = new SimpleDateFormat(pattern); @@ -626,11 +627,11 @@ public void write(OutputStream outputStream) while (iterator.hasNext()) { RecordBean recordBean = Util.createRecordBean(iterator.next()); values = recordBean.getValues(); - String temp[]; + + /*String temp[]; time = values.get(timestamp).toString(); temp = time.split(" "); time = temp[0]; - Date date = null; try { date = format.parse(time); @@ -638,7 +639,23 @@ public void write(OutputStream outputStream) } catch (ParseException e) { e.printStackTrace(); } + long epoch = date.getTime();*/ + + long epoch1 = Long.parseLong(values.get(timestamp).toString()); + //String pattern = "yyyy-MM-dd"; + // SimpleDateFormat format = new SimpleDateFormat(pattern); + Date expiry = new Date(epoch1); + String str_date = format.format(expiry); + + Date date = null; + try { + date = format.parse(str_date); + //System.out.println(date); + } catch (ParseException e) { + e.printStackTrace(); + } long epoch = date.getTime(); + if (values.get(col) != null) { val = values.get(col).toString(); diff --git a/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/util/Util.java b/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/util/Util.java index 8b8505f..a8ff897 100644 --- a/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/util/Util.java +++ b/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/util/Util.java @@ -1,6 +1,8 @@ package org.wso2.carbon.la.restapi.util; import org.apache.commons.collections.IteratorUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.wso2.carbon.analytics.dataservice.commons.AnalyticsDataResponse; import org.wso2.carbon.analytics.dataservice.commons.SearchResultEntry; import org.wso2.carbon.analytics.dataservice.core.SecureAnalyticsDataService; @@ -9,6 +11,7 @@ import org.wso2.carbon.analytics.datasource.commons.exception.AnalyticsException; import org.wso2.carbon.la.commons.domain.RecordBean; +import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; @@ -16,6 +19,7 @@ * Created by vithulan on 2/3/16. */ public class Util { + private static final Log log = LogFactory.getLog(Util.class); private static final String day = "day"; private static final String week = "week"; private static final String month = "month"; @@ -236,4 +240,25 @@ public static Map> groupbyYear(Map").appendTo("#timeChart"); + //("#timeChart h2").html("Hello"); drawTime(res, fieldName); //addRow(res, fieldName); // document.getElementById("json_string3").innerHTML=res[0]; @@ -525,7 +548,7 @@ function addChildLogStream(val, idVal) { LogstreamDiv.appendChild(selectList); var option1 = document.createElement('option'); option1.value = "None"; - option1.text = "Select a category" + option1.text = "Select a category"; selectList.add(option1); for (var i = 0; i < res.length; i++) { var option = document.createElement('option'); diff --git a/modules/jaggeryapps/loganalyzer/site/dashboard/visualize.jag b/modules/jaggeryapps/loganalyzer/site/dashboard/visualize.jag index 5fe7dcf..71e0f40 100644 --- a/modules/jaggeryapps/loganalyzer/site/dashboard/visualize.jag +++ b/modules/jaggeryapps/loganalyzer/site/dashboard/visualize.jag @@ -164,7 +164,7 @@

-
+

@@ -173,12 +173,12 @@

diff --git a/pom.xml b/pom.xml index 61398ce..8e6631f 100644 --- a/pom.xml +++ b/pom.xml @@ -518,7 +518,7 @@ 4.4.3 2.4 1.0.6-SNAPSHOT - 5.0.10-SNAPSHOT + 5.0.11-SNAPSHOT 4.4.1 4.4.2 4.4.8 From 4ec77201f1073e0652f9c647cc18dde09b52586b Mon Sep 17 00:00:00 2001 From: VIthulan Date: Fri, 4 Mar 2016 18:28:37 +0530 Subject: [PATCH 8/9] Adding time range filtering feature --- .../la/commons/constants/LAConstants.java | 11 + .../carbon/la/core/impl/SearchController.java | 6 +- .../carbon/la/restapi/DashboardApiV10.java | 581 ++---------------- .../org/wso2/carbon/la/restapi/util/Util.java | 270 +++----- .../jaggeryapps/loganalyzer/js/visualize.js | 16 +- .../loganalyzer/site/dashboard/visualize.jag | 15 +- pom.xml | 2 +- 7 files changed, 149 insertions(+), 752 deletions(-) diff --git a/modules/components/org.wso2.carbon.la.commons/src/main/java/org/wso2/carbon/la/commons/constants/LAConstants.java b/modules/components/org.wso2.carbon.la.commons/src/main/java/org/wso2/carbon/la/commons/constants/LAConstants.java index fc26482..38e2d51 100644 --- a/modules/components/org.wso2.carbon.la.commons/src/main/java/org/wso2/carbon/la/commons/constants/LAConstants.java +++ b/modules/components/org.wso2.carbon.la.commons/src/main/java/org/wso2/carbon/la/commons/constants/LAConstants.java @@ -39,9 +39,20 @@ private LAConstants() { public static final String LOG_TIMESTAMP = "@timestamp"; public static final String LOG_TIMESTAMP_LONG = "@timestamp_long"; public static final String DAS_TIMESTAMP_FIELD = "_timestamp"; + public static final String LOGSTREAM = "logstream"; + public static final String TIMESTAMP_FIELD = "_eventTimeStamp"; + + public static final long EPOCH_DAYGAP = 86400000; public static final String EVENT_STREAM_PERSIST_ADMINSERVICE = "EventStreamPersistenceAdminService"; + //Date format constants + public static final String TIMESTAMP_PATTERN = "yyyy-MM-dd"; + public static final String DAY_PATTERN = "yyyy-MM-dd"; + public static final String WEEK_PATTERN = "Y/MM:W"; + public static final String MONTH_PATTERN = "Y-MM"; + public static final String YEAR_PATTERN = "YYYY"; + // Data-set upload configurations public static final String UPLOAD_SETTINGS = "dataUploadSettings"; public static final String UPLOAD_LOCATION = "uploadLocation"; diff --git a/modules/components/org.wso2.carbon.la.core/src/main/java/org/wso2/carbon/la/core/impl/SearchController.java b/modules/components/org.wso2.carbon.la.core/src/main/java/org/wso2/carbon/la/core/impl/SearchController.java index ddda2bf..8a0915e 100644 --- a/modules/components/org.wso2.carbon.la.core/src/main/java/org/wso2/carbon/la/core/impl/SearchController.java +++ b/modules/components/org.wso2.carbon.la.core/src/main/java/org/wso2/carbon/la/core/impl/SearchController.java @@ -9,6 +9,7 @@ import org.wso2.carbon.analytics.dataservice.core.AnalyticsDataServiceUtils; import org.wso2.carbon.analytics.datasource.commons.Record; import org.wso2.carbon.analytics.datasource.commons.exception.AnalyticsException; +import org.wso2.carbon.la.commons.constants.LAConstants; import org.wso2.carbon.la.commons.domain.QueryBean; import org.wso2.carbon.la.commons.domain.RecordBean; import org.wso2.carbon.la.core.utils.LACoreServiceValueHolder; @@ -21,7 +22,7 @@ public class SearchController { private static final Log log = LogFactory.getLog(SearchController.class); - private final String logstream = "logstream"; + private final String logstream = LAConstants.LOGSTREAM; public List search(QueryBean query, String username) throws AnalyticsException { AnalyticsDrillDownRequest analyticsDrillDownRequest = new AnalyticsDrillDownRequest(); @@ -47,8 +48,6 @@ public List search(QueryBean query, String username) throws Analytic analyticsDrillDownRequest.setRecordStartIndex(query.getStart()); analyticsDrillDownRequest.setCategoryPaths(categoryPath); List searchResults = analyticsDataService.drillDownSearch(username,analyticsDrillDownRequest); - //List searchResults = analyticsDataService.search(username,query.getTableName(), - // query.getQuery(),query.getStart(), query.getLength()); List ids = getRecordIds(searchResults); AnalyticsDataResponse resp = analyticsDataService.get(username, query.getTableName(), 1, null, ids); @@ -117,7 +116,6 @@ public static RecordBean createRecordBean(Record record) { recordBean.setValues(record.getValues()); return recordBean; } - public int getRecordCount(String userName, QueryBean query) throws AnalyticsException { AnalyticsDataAPI analyticsDataService = LACoreServiceValueHolder.getInstance().getAnalyticsDataAPI(); if(query!=null){ diff --git a/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/DashboardApiV10.java b/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/DashboardApiV10.java index c37899a..2bfe012 100644 --- a/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/DashboardApiV10.java +++ b/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/DashboardApiV10.java @@ -24,7 +24,9 @@ import org.wso2.carbon.la.commons.domain.QueryBean; import org.wso2.carbon.la.commons.domain.RecordBean; import org.wso2.carbon.la.commons.domain.config.LogFileConf; +import org.wso2.carbon.la.core.impl.DrilldownController; import org.wso2.carbon.la.core.impl.LogFileProcessor; +import org.wso2.carbon.la.core.impl.TimeAggregator; import org.wso2.carbon.la.core.utils.LACoreServiceValueHolder; import org.wso2.carbon.la.restapi.beans.LAErrorBean; import org.wso2.carbon.la.restapi.util.Util; @@ -55,8 +57,12 @@ public class DashboardApiV10 { private static final Log log = LogFactory.getLog(DashboardApiV10.class); - private static final Gson gson = new Gson(); + /** + * GET method + * @return Returns all the column names in DAS, LOGANALYZER Table + * @throws AnalyticsException + */ @GET @Path("getFields") @Produces("application/json") @@ -64,7 +70,6 @@ public class DashboardApiV10 { public Response getFields() throws AnalyticsException { PrivilegedCarbonContext carbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext(); int tenantId = carbonContext.getTenantId(); - //String username = carbonContext.getUsername(); Map columns; List fields = new ArrayList(); AnalyticsDataAPI analyticsDataAPI; @@ -79,6 +84,11 @@ public Response getFields() throws AnalyticsException { return Response.ok(fields).build(); } + /** + * POST method + * @param query QueryBean object from front end + * @return Returns all the elements in selected column with number of Hits + */ @POST @Path("/fieldData") @Produces("application/json") @@ -87,62 +97,22 @@ public StreamingOutput fieldData(final QueryBean query) { PrivilegedCarbonContext carbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext(); int tenantId = carbonContext.getTenantId(); - //String username = carbonContext.getUsername(); List column = new ArrayList(); column.add(query.getQuery()); AnalyticsDataAPI analyticsDataAPI; AnalyticsDataResponse analyticsDataResponse; - //RecordGroup recordGroup [] ; - - analyticsDataAPI = LACoreServiceValueHolder.getInstance().getAnalyticsDataAPI(); try { analyticsDataResponse = analyticsDataAPI.get(tenantId, LAConstants.LOG_ANALYZER_STREAM_NAME.toUpperCase(), 1, column, query.getTimeFrom(), query.getTimeTo(), query.getStart(), -1); - // recordGroup = analyticsDataResponse.getRecordGroups(); - final List> iterators = Util.getRecordIterators(analyticsDataResponse, analyticsDataAPI); - + List> iterators = Util.getRecordIterators(analyticsDataResponse, analyticsDataAPI); + final Map counter = Util.getCountGroup(iterators, query.getQuery()); return new StreamingOutput() { @Override public void write(OutputStream outputStream) throws IOException, WebApplicationException { Writer recordWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); - Map values; - Map counter = new HashMap(); - - int count = 0; - String val; recordWriter.write("["); - for (Iterator iterator : iterators) { - while (iterator.hasNext()) { - RecordBean recordBean = Util.createRecordBean(iterator.next()); - values = recordBean.getValues(); - if (values.get(query.getQuery()) == null) { - - if (!counter.containsKey("NULLVALUE")) { - counter.put("NULLVALUE", 1); - } else { - count = counter.get("NULLVALUE"); - count++; - counter.put("NULLVALUE", count); - } - - } else { - val = values.get(query.getQuery()).toString(); - if (!counter.containsKey(val)) { - counter.put(val, 1); - } else { - count = counter.get(val); - count++; - counter.put(val, count); - } - } - if (log.isDebugEnabled()) { - log.debug("Retrieved -- Record Id: " + recordBean.getId() + " values :" + - recordBean.toString()); - } - } - } int i = 1; for (Map.Entry entry : counter.entrySet()) { @@ -170,28 +140,25 @@ public void write(OutputStream outputStream) throws IOException, WebApplicationE } }; } - - } + /** + * POST method + * @param query QueryBean object from front end + * @return Returns filtered data from a specified column + */ @POST @Path("/filterData") @Produces("application/json") @Consumes("application/json") - public StreamingOutput filterData(final QueryBean query) throws AnalyticsException { + public StreamingOutput filterData(final QueryBean query) { PrivilegedCarbonContext carbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext(); - int tenantId = carbonContext.getTenantId(); String username = carbonContext.getUsername(); - AnalyticsDataAPI analyticsDataAPI; - AnalyticsDataResponse analyticsDataResponse; - String logstream = "logstream"; - //RecordGroup recordGroup [] ; - AnalyticsDrillDownRequest analyticsDrillDownRequest = new AnalyticsDrillDownRequest(); - analyticsDataAPI = LACoreServiceValueHolder.getInstance().getAnalyticsDataAPI(); + DrilldownController drilldownController = new DrilldownController(); if (query != null) { String q[] = query.getQuery().split(",,"); String searchQuery = q[0]; - searchQuery = Util.appendTimeStamp(searchQuery,query.getStr_timeFrom(),query.getStr_timeTo()); + searchQuery = Util.appendTimeStamp(searchQuery, query.getStr_timeFrom(), query.getStr_timeTo()); final String col = q[1]; List column = new ArrayList<>(); column.add(col); @@ -200,70 +167,25 @@ public StreamingOutput filterData(final QueryBean query) throws AnalyticsExcepti Map> categoryPath = new HashMap<>(); List pathList = new ArrayList<>(); if (facetPath.equals("None")) { - categoryPath.put(logstream, pathList); + categoryPath.put(LAConstants.LOGSTREAM, pathList); } else { String pathArray[] = facetPath.split(","); for (String path : pathArray) { pathList.add(path); } - categoryPath.put(logstream, pathList); + categoryPath.put(LAConstants.LOGSTREAM, pathList); } - analyticsDrillDownRequest.setTableName(query.getTableName()); - analyticsDrillDownRequest.setQuery(searchQuery); - analyticsDrillDownRequest.setRecordCount(query.getLength()); - analyticsDrillDownRequest.setRecordStartIndex(query.getStart()); - analyticsDrillDownRequest.setCategoryPaths(categoryPath); - List searchResults = analyticsDataAPI.drillDownSearch(username, analyticsDrillDownRequest); - - List ids = Util.getRecordIds(searchResults); - analyticsDataResponse = analyticsDataAPI.get(username, query.getTableName(), 1, column, ids); - final List> iterators = Util.getRecordIterators(analyticsDataResponse, analyticsDataAPI); + List> iterators = drilldownController.getResults(query.getTableName(), searchQuery, + query.getLength(), query.getStart(), categoryPath, username, column); + final Map counter = Util.getCountGroup(iterators, col); return new StreamingOutput() { @Override public void write(OutputStream outputStream) throws IOException, WebApplicationException { Writer recordWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); - Map values; - Map counter = new HashMap(); - - int count = 0; - String val; recordWriter.write("["); - for (Iterator iterator : iterators) { - while (iterator.hasNext()) { - RecordBean recordBean = Util.createRecordBean(iterator.next()); - values = recordBean.getValues(); - - if (values.get(col) == null) { - - if (!counter.containsKey("NULLVALUE")) { - counter.put("NULLVALUE", 1); - } else { - count = counter.get("NULLVALUE"); - count++; - counter.put("NULLVALUE", count); - } - - } else { - val = values.get(col).toString(); - if (!counter.containsKey(val)) { - counter.put(val, 1); - } else { - count = counter.get(val); - count++; - counter.put(val, count); - } - } - - if (log.isDebugEnabled()) { - log.debug("Retrieved -- Record Id: " + recordBean.getId() + " values :" + - recordBean.toString()); - } - } - } int i = 1; for (Map.Entry entry : counter.entrySet()) { - recordWriter.write("[[\"" + entry.getKey() + "\"],[\"" + entry.getValue() + "\"]]"); if (i < counter.size()) { recordWriter.write(","); @@ -275,471 +197,80 @@ public void write(OutputStream outputStream) } }; } else { - String msg = String.format("Error occurred while retrieving field data"); - log.error(msg); - return new StreamingOutput() { - @Override - public void write(OutputStream outputStream) throws IOException, WebApplicationException { - Writer recordWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); - recordWriter.write("Error in reading records"); - recordWriter.flush(); - } - }; - } - } -/* - @POST - @Path("/timeData") - @Produces("application/json") - @Consumes("application/json") - public StreamingOutput timeData(final QueryBean query) throws AnalyticsException { - - PrivilegedCarbonContext carbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext(); - int tenantId = carbonContext.getTenantId(); - String username = carbonContext.getUsername(); - AnalyticsDataAPI analyticsDataAPI; - AnalyticsDataResponse analyticsDataResponse; - //RecordGroup recordGroup [] ; - analyticsDataAPI = LACoreServiceValueHolder.getInstance().getAnalyticsDataAPI(); - if (query != null) { - String q[] = query.getQuery().split(",,"); - String searchQuery = q[0]; - final String col = q[1]; - final String timestamp = "_timestamp2"; - List column = new ArrayList<>(); - column.add(col); - column.add(timestamp); - - List searchResults = analyticsDataAPI.search(username, - query.getTableName(), searchQuery, - query.getStart(), query.getLength()); - List ids = Util.getRecordIds(searchResults); - analyticsDataResponse = analyticsDataAPI.get(username, query.getTableName(), 1, column, ids); - final List> iterators = Util.getRecordIterators(analyticsDataResponse, analyticsDataAPI); - return new StreamingOutput() { - @Override - public void write(OutputStream outputStream) - throws IOException, WebApplicationException { - Writer recordWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); - Map values; - - Map> stamper = new HashMap<>(); - int count = 0; - String val; - String time; - recordWriter.write("["); - for (Iterator iterator : iterators) { - while (iterator.hasNext()) { - RecordBean recordBean = Util.createRecordBean(iterator.next()); - values = recordBean.getValues(); - String temp[]; - time = values.get(timestamp).toString(); - temp = time.split(" "); - time = temp[0]; - if (values.get(col) != null) { - val = values.get(col).toString(); - - if (!stamper.containsKey(time)) { - Map counter = new HashMap(); - counter.put(val, 1); - stamper.put(time, counter); - } else { - Map counter = stamper.get(time); - if (!counter.containsKey(val)) { - counter.put(val, 1); - stamper.put(time, counter); - } else { - count = counter.get(val); - count++; - counter.put(val, count); - stamper.put(time, counter); - } - } - - } - if (log.isDebugEnabled()) { - log.debug("Retrieved -- Record Id: " + recordBean.getId() + " values :" + - recordBean.toString()); - } - } - } - int i = 1; - int j = 1; - for (Map.Entry> entry : stamper.entrySet()) { - Map counter = entry.getValue(); - for (Map.Entry infom : counter.entrySet()) { - recordWriter.write("[[\"" + entry.getKey() + "\"],[\"" + infom.getKey() + "\"],[\"" + infom.getValue() + "\"]]"); - if (i < counter.size()) { - recordWriter.write(","); - i++; - } - } - i = 1; - if (j < stamper.size()) { - recordWriter.write(","); - j++; - } - - } - recordWriter.write("]"); - recordWriter.flush(); - } - }; - } else { - String msg = String.format("Error occurred while retrieving field data"); + String msg = String.format("Query cannot be NULL"); log.error(msg); return new StreamingOutput() { @Override public void write(OutputStream outputStream) throws IOException, WebApplicationException { Writer recordWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); - recordWriter.write("Error in reading records"); + recordWriter.write("Query is null"); recordWriter.flush(); } }; } - } - @POST - @Path("/epochTimeData") - @Produces("application/json") - @Consumes("application/json") - public StreamingOutput EpochtimeData(final QueryBean query) throws AnalyticsException { - - PrivilegedCarbonContext carbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext(); - int tenantId = carbonContext.getTenantId(); - String username = carbonContext.getUsername(); - AnalyticsDataAPI analyticsDataAPI; - AnalyticsDataResponse analyticsDataResponse; - //RecordGroup recordGroup [] ; - analyticsDataAPI = LACoreServiceValueHolder.getInstance().getAnalyticsDataAPI(); - if (query != null) { - String q[] = query.getQuery().split(",,"); - String searchQuery = q[0]; - final String col = q[1]; - final String timestamp = "_timestamp2"; - final long dayGap = 86400000; - final String pattern = "yyyy-MM-dd"; - final SimpleDateFormat format = new SimpleDateFormat(pattern); - List column = new ArrayList<>(); - column.add(col); - column.add(timestamp); - - List searchResults = analyticsDataAPI.search(username, - query.getTableName(), searchQuery, - query.getStart(), query.getLength()); - List ids = Util.getRecordIds(searchResults); - analyticsDataResponse = analyticsDataAPI.get(username, query.getTableName(), 1, column, ids); - final List> iterators = Util.getRecordIterators(analyticsDataResponse, analyticsDataAPI); - return new StreamingOutput() { - @Override - public void write(OutputStream outputStream) - throws IOException, WebApplicationException { - Writer recordWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); - Map values; - - Map> stamper = new HashMap<>(); - int count = 0; - String val; - String time; - recordWriter.write("["); - for (Iterator iterator : iterators) { - while (iterator.hasNext()) { - RecordBean recordBean = Util.createRecordBean(iterator.next()); - values = recordBean.getValues(); - String temp[]; - time = values.get(timestamp).toString(); - temp = time.split(" "); - time = temp[0]; - - Date date = null; - try { - date = format.parse(time); - //System.out.println(date); - } catch (ParseException e) { - e.printStackTrace(); - } - long epoch = date.getTime(); - if (values.get(col) != null) { - val = values.get(col).toString(); - - if (!stamper.containsKey(epoch)) { - Map counter = new HashMap(); - counter.put(val, 1); - stamper.put(epoch, counter); - } else { - Map counter = stamper.get(epoch); - if (!counter.containsKey(val)) { - counter.put(val, 1); - stamper.put(epoch, counter); - } else { - count = counter.get(val); - count++; - counter.put(val, count); - stamper.put(epoch, counter); - } - } - - } - if (log.isDebugEnabled()) { - log.debug("Retrieved -- Record Id: " + recordBean.getId() + " values :" + - recordBean.toString()); - } - } - } - - Map> sortedMap = new TreeMap>(stamper); - int i = 1; - int j = 1; - long lastDay = 0; - long presentDay = 0; - long k = 1; - for (Map.Entry> entry : sortedMap.entrySet()) { - - if (j > 1) { - presentDay = entry.getKey(); - long dif = presentDay - lastDay; - k = dif / dayGap; - - } - if (k > 1) { - int m = (int) k; - long newDate = lastDay; - while (k > 1) { - newDate = newDate + dayGap; - long temp = newDate / 1000L; - Date expiry = new Date(temp * 1000L); - String str_date = format.format(expiry); - recordWriter.write("[[\"" + str_date + "\"],[\"" + "No Entry" + "\"],[\"" + 0 + "\"]]"); - k--; - if (k != 1) { - recordWriter.write(","); - } - } - recordWriter.write(","); - k = 1; - lastDay = newDate; - } - if (k == 1) { - Map counter = entry.getValue(); - for (Map.Entry infom : counter.entrySet()) { - long temp = entry.getKey() / 1000L; - Date expiry = new Date(temp * 1000L); - String str_date = format.format(expiry); - recordWriter.write("[[\"" + str_date + "\"],[\"" + infom.getKey() + "\"],[\"" + infom.getValue() + "\"]]"); - if (i < counter.size()) { - recordWriter.write(","); - i++; - } - } - i = 1; - if (j < sortedMap.size()) { - recordWriter.write(","); - j++; - } - lastDay = entry.getKey(); - } - - } - recordWriter.write("]"); - recordWriter.flush(); - } - }; - } else { - String msg = String.format("Error occurred while retrieving field data"); - log.error(msg); - return new StreamingOutput() { - @Override - public void write(OutputStream outputStream) throws IOException, WebApplicationException { - Writer recordWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); - recordWriter.write("Error in reading records"); - recordWriter.flush(); - } - }; - } - - } */ - + /** + * POST method + * @param query QueryBean object from front end + * @return Returns data in specific column by grouping it by time + */ @POST @Path("/epochTimeDataFinal") @Produces("application/json") @Consumes("application/json") - public StreamingOutput EpochtimeDataFinal(final QueryBean query) throws AnalyticsException { + public StreamingOutput EpochtimeDataFinal(final QueryBean query) { PrivilegedCarbonContext carbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext(); - int tenantId = carbonContext.getTenantId(); String username = carbonContext.getUsername(); - AnalyticsDataAPI analyticsDataAPI; - AnalyticsDataResponse analyticsDataResponse; - //RecordGroup recordGroup [] ; - AnalyticsDrillDownRequest analyticsDrillDownRequest = new AnalyticsDrillDownRequest(); - String logstream = "logstream"; - analyticsDataAPI = LACoreServiceValueHolder.getInstance().getAnalyticsDataAPI(); + DrilldownController drilldownController = new DrilldownController(); + TimeAggregator timeAggregator = new TimeAggregator(); if (query != null) { String q[] = query.getQuery().split(",,"); String searchQuery = q[0]; - searchQuery = Util.appendTimeStamp(searchQuery,query.getStr_timeFrom(),query.getStr_timeTo()); - final String col = q[1]; + searchQuery = Util.appendTimeStamp(searchQuery, query.getStr_timeFrom(), query.getStr_timeTo()); + final String columnName = q[1]; final String groupBy = q[2]; - final String timestamp = "_eventTimeStamp"; - final long dayGap = 86400000; - final String pattern = "yyyy-MM-dd"; - final SimpleDateFormat format = new SimpleDateFormat(pattern); List column = new ArrayList<>(); - column.add(col); - column.add(timestamp); + column.add(columnName); + column.add(LAConstants.TIMESTAMP_FIELD); String facetPath = query.getFacetPath(); Map> categoryPath = new HashMap<>(); List pathList = new ArrayList<>(); if (facetPath.equals("None")) { - categoryPath.put(logstream, pathList); + categoryPath.put(LAConstants.LOGSTREAM, pathList); } else { String pathArray[] = facetPath.split(","); for (String path : pathArray) { pathList.add(path); } - categoryPath.put(logstream, pathList); + categoryPath.put(LAConstants.LOGSTREAM, pathList); } - analyticsDrillDownRequest.setTableName(query.getTableName()); - analyticsDrillDownRequest.setQuery(searchQuery); - analyticsDrillDownRequest.setRecordCount(query.getLength()); - analyticsDrillDownRequest.setRecordStartIndex(query.getStart()); - analyticsDrillDownRequest.setCategoryPaths(categoryPath); - List searchResults = analyticsDataAPI.drillDownSearch(username, analyticsDrillDownRequest); - - List ids = Util.getRecordIds(searchResults); - analyticsDataResponse = analyticsDataAPI.get(username, query.getTableName(), 1, column, ids); - final List> iterators = Util.getRecordIterators(analyticsDataResponse, analyticsDataAPI); + final List> iterators = drilldownController.getResults(query.getTableName(), searchQuery, + query.getLength(), query.getStart(), categoryPath, username, column); + final Map> aggregatedMap = timeAggregator.getGrouped(iterators, columnName, groupBy); return new StreamingOutput() { @Override public void write(OutputStream outputStream) throws IOException, WebApplicationException { Writer recordWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); - Map values; - - Map> stamper = new HashMap<>(); - int count = 0; - String val; - String time; - for (Iterator iterator : iterators) { - while (iterator.hasNext()) { - RecordBean recordBean = Util.createRecordBean(iterator.next()); - values = recordBean.getValues(); - - /*String temp[]; - time = values.get(timestamp).toString(); - temp = time.split(" "); - time = temp[0]; - Date date = null; - try { - date = format.parse(time); - //System.out.println(date); - } catch (ParseException e) { - e.printStackTrace(); - } - long epoch = date.getTime();*/ - - long epoch1 = Long.parseLong(values.get(timestamp).toString()); - //String pattern = "yyyy-MM-dd"; - // SimpleDateFormat format = new SimpleDateFormat(pattern); - Date expiry = new Date(epoch1); - String str_date = format.format(expiry); - - Date date = null; - try { - date = format.parse(str_date); - //System.out.println(date); - } catch (ParseException e) { - e.printStackTrace(); - } - long epoch = date.getTime(); - - if (values.get(col) != null) { - val = values.get(col).toString(); - - if (!stamper.containsKey(epoch)) { - Map counter = new HashMap(); - counter.put(val, 1); - stamper.put(epoch, counter); - } else { - Map counter = stamper.get(epoch); - if (!counter.containsKey(val)) { - counter.put(val, 1); - stamper.put(epoch, counter); - } else { - count = counter.get(val); - count++; - counter.put(val, count); - stamper.put(epoch, counter); - } - } - - } - if (log.isDebugEnabled()) { - log.debug("Retrieved -- Record Id: " + recordBean.getId() + " values :" + - recordBean.toString()); - } - } - } - - Map> sortedMap = new TreeMap>(stamper); - Map> allDayMap = new HashMap<>(); - int j = 1; - long lastDay = 0; - long presentDay = 0; - long k = 1; - - for (Map.Entry> entry : sortedMap.entrySet()) { - - if (j > 1) { - presentDay = entry.getKey(); - long dif = presentDay - lastDay; - k = dif / dayGap; - - } - if (k > 1) { - long newDate = lastDay; - while (k > 1) { - newDate = newDate + dayGap; - Map tempMap = new HashMap<>(); - tempMap.put("No Entry", 0); - allDayMap.put(newDate, tempMap); - k--; - } - k = 1; - lastDay = newDate; - } - if (k == 1) { - Map counter = entry.getValue(); - if (j < sortedMap.size()) { - j++; - } - lastDay = entry.getKey(); - allDayMap.put(lastDay, counter); - } - - } - allDayMap = new TreeMap>(allDayMap); - Map> grouped = Util.getGrouping(allDayMap, groupBy); - grouped = new TreeMap<>(grouped); - int i = 1; int l = 1; recordWriter.write("["); - for (Map.Entry> entry : grouped.entrySet()) { + for (Map.Entry> entry : aggregatedMap.entrySet()) { Map counter = entry.getValue(); for (Map.Entry infom : counter.entrySet()) { - - recordWriter.write("[[\"" + entry.getKey() + "\"],[\"" + infom.getKey() + "\"],[\"" + infom.getValue() + "\"]]"); + recordWriter.write("[[\"" + entry.getKey() + "\"],[\"" + infom.getKey() + + "\"],[\"" + infom.getValue() + "\"]]"); if (i < counter.size()) { recordWriter.write(","); i++; } } i = 1; - if (l < grouped.size()) { + if (l < aggregatedMap.size()) { recordWriter.write(","); l++; } @@ -750,20 +281,24 @@ public void write(OutputStream outputStream) } }; } else { - String msg = String.format("Error occurred while retrieving field data"); + String msg = String.format("Query cannot be NULL"); log.error(msg); return new StreamingOutput() { @Override public void write(OutputStream outputStream) throws IOException, WebApplicationException { Writer recordWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); - recordWriter.write("Error in reading records"); + recordWriter.write("Query is null"); recordWriter.flush(); } }; } - } + /** + * POST method + * @param query QueryBean object from front end + * @return returns the facet child of given facet parent. + */ @POST @Path("/logStreamData") @Produces("application/json") @@ -811,7 +346,7 @@ public void write(OutputStream outputStream) }; } catch (AnalyticsException e) { - String msg = String.format("Error occurred while retrieving field data"); + String msg = String.format("Error occurred while drilldowning facet data"); log.error(msg, e); return new StreamingOutput() { @Override diff --git a/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/util/Util.java b/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/util/Util.java index a8ff897..49e65e8 100644 --- a/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/util/Util.java +++ b/modules/components/org.wso2.carbon.la.restapi/src/main/java/org/wso2/carbon/la/restapi/util/Util.java @@ -9,6 +9,7 @@ import org.wso2.carbon.analytics.datasource.commons.Record; import org.wso2.carbon.analytics.datasource.commons.RecordGroup; import org.wso2.carbon.analytics.datasource.commons.exception.AnalyticsException; +import org.wso2.carbon.la.commons.constants.LAConstants; import org.wso2.carbon.la.commons.domain.RecordBean; import java.text.ParseException; @@ -20,12 +21,14 @@ */ public class Util { private static final Log log = LogFactory.getLog(Util.class); - private static final String day = "day"; - private static final String week = "week"; - private static final String month = "month"; - private static final String year = "year"; - private static final String auto = "auto"; + /** + * Creates list of iterators of search result entries + * @param resp analytic data response from analytics data API + * @param analyticsDataService analytics data API + * @return returns list of iterators + * @throws AnalyticsException + */ public static List> getRecordIterators(AnalyticsDataResponse resp, SecureAnalyticsDataService analyticsDataService) throws AnalyticsException { @@ -33,11 +36,14 @@ public static List> getRecordIterators(AnalyticsDataResponse re for (RecordGroup recordGroup : resp.getRecordGroups()) { iterators.add(analyticsDataService.readRecords(resp.getRecordStoreName(), recordGroup)); } - // IteratorUtils iteratorUtils = new IteratorUtils(); - //Iterator iterator = iteratorUtils.chainedIterator(iterators); return iterators; } + /** + * Gets the record bean of an entry + * @param record search result entry + * @return returns RecordBean object + */ public static RecordBean createRecordBean(Record record) { RecordBean recordBean = new RecordBean(); recordBean.setId(record.getId()); @@ -47,218 +53,80 @@ public static RecordBean createRecordBean(Record record) { return recordBean; } - public static List getRecordIds(List searchResults) { - List ids = new ArrayList<>(); - for (SearchResultEntry searchResult : searchResults) { - ids.add(searchResult.getId()); - } - return ids; - } - - public static Map> getGrouping(Map> allDayMap, String method) { - Map> grouped = new HashMap<>(); - switch (method) { - case auto: - grouped = groupbyAuto(allDayMap); - break; - case day: - grouped = groupbyDay(allDayMap); - break; - case week: - grouped = groupbyWeek(allDayMap); - break; - case month: - grouped = groupbyMonth(allDayMap); - break; - case year: - grouped = groupbyYear(allDayMap); - break; - - } - - return grouped; - } - - public static Map> groupbyAuto(Map> allDayMap) { - Map> grouped = new HashMap<>(); - int days = allDayMap.size(); - if (days <= 10) { - grouped = groupbyDay(allDayMap); - } else if (days > 10 && days <= 70) { - grouped = groupbyWeek(allDayMap); - } else if (days > 70 && days <= 365 * 3) { - grouped = groupbyMonth(allDayMap); - } else { - grouped = groupbyYear(allDayMap); - } - return grouped; - } - - public static Map> groupbyDay(Map> allDayMap) { - Map> grouped = new HashMap<>(); - String pattern = "yyyy-MM-dd"; + /** + * Appends timestamp with search query + * @param query search query + * @param timeFrom time from where results are required + * @param timeTo upto the time where results are required + * @return String search query + */ + public static String appendTimeStamp(String query, String timeFrom, String timeTo){ + String pattern = "MM/dd/yyyy"; SimpleDateFormat format = new SimpleDateFormat(pattern); - //long temp = epoch/1000L; - - for (Map.Entry> entry : allDayMap.entrySet()) { - Date expiry = new Date(entry.getKey()); - String str_week = format.format(expiry); - grouped.put(str_week, entry.getValue()); + Date fromDate =null; + Date toDate = null; + try { + fromDate = format.parse(timeFrom); + toDate = format.parse(timeTo); + } catch (ParseException e) { + log.error(e); + e.printStackTrace(); } - - return grouped; + long timeFromEpoch = fromDate.getTime(); //TODO how to avoid null pointer exception? + long timeToEpoch = toDate.getTime(); + String searchQuery = ""; + if (!"".equals(query)) { + searchQuery = query + " AND "; + } + return searchQuery + LAConstants.TIMESTAMP_FIELD+" :[" + timeFromEpoch + " TO " + timeToEpoch + "]"; } - public static Map> groupbyWeek(Map> allDayMap) { + /** + * Groups all the result entries based on entry name and gets number of Hits + * @param iterators List of iterators of search result entries + * @param ColumnName Column name where results are required + * @return returns a Map + */ + public static Map getCountGroup(List> iterators, String ColumnName){ + Map values; + Map counter = new HashMap<>(); - String pattern = "Y/MM:W"; - SimpleDateFormat format = new SimpleDateFormat(pattern); - //long temp = epoch/1000L; - Map>> grouped = new HashMap<>(); - for (Map.Entry> entry : allDayMap.entrySet()) { - Date expiry = new Date(entry.getKey()); - String str_week = format.format(expiry); - if (!grouped.containsKey(str_week)) { - List> list = new ArrayList<>(); - list.add(entry.getValue()); - grouped.put(str_week, list); - } else { - List> list = grouped.get(str_week); - list.add(entry.getValue()); - grouped.put(str_week, list); - } - //grouped.put(str_week,entry.getValue()); + int count = 0; + String val; - } + for (Iterator iterator : iterators) { + while (iterator.hasNext()) { + RecordBean recordBean = Util.createRecordBean(iterator.next()); + values = recordBean.getValues(); - Map> Fgrouped = new HashMap<>(); - for (Map.Entry>> entry : grouped.entrySet()) { - List> list = entry.getValue(); - Map data = new HashMap<>(); - //Map counter = entry.getValue(); + if (values.get(ColumnName) == null) { - for (Map dataMap : list) { - for (Map.Entry entrr : dataMap.entrySet()) { - if (!data.containsKey(entrr.getKey())) { - data.put(entrr.getKey(), entrr.getValue()); + if (!counter.containsKey("NULLVALUE")) { + counter.put("NULLVALUE", 1); } else { - int k = data.get(entrr.getKey()); - data.put(entrr.getKey(), k + entrr.getValue()); + count = counter.get("NULLVALUE"); + count++; + counter.put("NULLVALUE", count); } - } - } - Fgrouped.put(entry.getKey(), data); - } - return Fgrouped; - } - - public static Map> groupbyMonth(Map> allDayMap) { - - String pattern = "Y-MM"; - SimpleDateFormat format = new SimpleDateFormat(pattern); - //long temp = epoch/1000L; - Map>> grouped = new HashMap<>(); - for (Map.Entry> entry : allDayMap.entrySet()) { - Date expiry = new Date(entry.getKey()); - String str_week = format.format(expiry); - if (!grouped.containsKey(str_week)) { - List> list = new ArrayList<>(); - list.add(entry.getValue()); - grouped.put(str_week, list); - } else { - List> list = grouped.get(str_week); - list.add(entry.getValue()); - grouped.put(str_week, list); - } - //grouped.put(str_week,entry.getValue()); - - } - - Map> Fgrouped = new HashMap<>(); - for (Map.Entry>> entry : grouped.entrySet()) { - List> list = entry.getValue(); - Map data = new HashMap<>(); - //Map counter = entry.getValue(); - - for (Map dataMap : list) { - for (Map.Entry entrr : dataMap.entrySet()) { - if (!data.containsKey(entrr.getKey())) { - data.put(entrr.getKey(), entrr.getValue()); + } else { + val = values.get(ColumnName).toString(); + if (!counter.containsKey(val)) { + counter.put(val, 1); } else { - int k = data.get(entrr.getKey()); - data.put(entrr.getKey(), k + entrr.getValue()); + count = counter.get(val); + count++; + counter.put(val, count); } } - } - Fgrouped.put(entry.getKey(), data); - } - - return Fgrouped; - } - - public static Map> groupbyYear(Map> allDayMap) { - String pattern = "YYYY"; - SimpleDateFormat format = new SimpleDateFormat(pattern); - //long temp = epoch/1000L; - Map>> grouped = new HashMap<>(); - for (Map.Entry> entry : allDayMap.entrySet()) { - Date expiry = new Date(entry.getKey()); - String str_week = format.format(expiry); - if (!grouped.containsKey(str_week)) { - List> list = new ArrayList<>(); - list.add(entry.getValue()); - grouped.put(str_week, list); - } else { - List> list = grouped.get(str_week); - list.add(entry.getValue()); - grouped.put(str_week, list); - } - //grouped.put(str_week,entry.getValue()); - - } - - Map> Fgrouped = new HashMap<>(); - for (Map.Entry>> entry : grouped.entrySet()) { - List> list = entry.getValue(); - Map data = new HashMap<>(); - //Map counter = entry.getValue(); - - for (Map dataMap : list) { - for (Map.Entry entrr : dataMap.entrySet()) { - if (!data.containsKey(entrr.getKey())) { - data.put(entrr.getKey(), entrr.getValue()); - } else { - int k = data.get(entrr.getKey()); - data.put(entrr.getKey(), k + entrr.getValue()); - } + if (log.isDebugEnabled()) { + log.debug("Retrieved -- Record Id: " + recordBean.getId() + " values :" + + recordBean.toString()); } } - Fgrouped.put(entry.getKey(), data); } - - return Fgrouped; + return counter; } - public static String appendTimeStamp(String query, String timeFrom, String timeTo){ - String pattern = "MM/dd/yyyy"; - SimpleDateFormat format = new SimpleDateFormat(pattern); - Date fromDate =null; - Date toDate = null; - try { - fromDate = format.parse(timeFrom); - toDate = format.parse(timeTo); - } catch (ParseException e) { - log.error(e); - e.printStackTrace(); - } - long timeFromEpoch = fromDate.getTime(); - long timeToEpoch = toDate.getTime(); - String searchQuery = ""; - if (!"".equals(query)) { - searchQuery = query + " AND "; - } - return searchQuery + "_eventTimeStamp:[" + timeFromEpoch + " TO " + timeToEpoch + "]"; - } } diff --git a/modules/jaggeryapps/loganalyzer/js/visualize.js b/modules/jaggeryapps/loganalyzer/js/visualize.js index f08ad77..63e6839 100644 --- a/modules/jaggeryapps/loganalyzer/js/visualize.js +++ b/modules/jaggeryapps/loganalyzer/js/visualize.js @@ -10,9 +10,7 @@ var chartType; window.onload = function () { checkApi(); addLogstream(); - - //console.log("Hello world"); -} +}; function openDashboard() { //window.location= baseUrl +'/loganalyzer/site/visualize.jag'; @@ -177,14 +175,6 @@ function addRow() { draw(reslt, arguments[1]); } -/* - $(document).ready(function(){ - $("form").submit(function(){ - var query = $('#ftexte').val(); - document.getElementById("json_string").innerHTML=query; - }); - });*/ - function filter() { if ($("#0").val() == "None") { facetPath = "None"; @@ -243,9 +233,6 @@ function filter() { //document.getElementById("json_string").innerHTML=response; }, error: function (res) { - var response = JSON.stringify(res); - // console.log(response); - //document.getElementById("json_string").innerHTML=response; alert(res.error); } }); @@ -257,7 +244,6 @@ function draw() { var dataValue = []; var temp = []; var json = arguments[0]; - //var charttype=arguments[2]; for (var i = 0; i < json.length; i++) { var temp = []; var xVal = json[i][0]; diff --git a/modules/jaggeryapps/loganalyzer/site/dashboard/visualize.jag b/modules/jaggeryapps/loganalyzer/site/dashboard/visualize.jag index 71e0f40..bac24e0 100644 --- a/modules/jaggeryapps/loganalyzer/site/dashboard/visualize.jag +++ b/modules/jaggeryapps/loganalyzer/site/dashboard/visualize.jag @@ -87,8 +87,14 @@ Link 3
+

Logstream

+ + +

Visualize

- + -

Logstream

- -
- -

Filter

diff --git a/pom.xml b/pom.xml index 8e6631f..6fe505d 100644 --- a/pom.xml +++ b/pom.xml @@ -527,7 +527,7 @@ 4.5.6 4.4.8 4.3.4 - 2.0.11 + 2.0.12 1.0.7 1.1.1 From ff1fc2b4919b90d18dab825b6ab81a4b3a55ab04 Mon Sep 17 00:00:00 2001 From: VIthulan Date: Mon, 7 Mar 2016 10:08:12 +0530 Subject: [PATCH 9/9] adding dashboard rest api core features --- .../la/core/impl/DrilldownController.java | 102 +++++ .../carbon/la/core/impl/TimeAggregator.java | 349 ++++++++++++++++++ 2 files changed, 451 insertions(+) create mode 100644 modules/components/org.wso2.carbon.la.core/src/main/java/org/wso2/carbon/la/core/impl/DrilldownController.java create mode 100644 modules/components/org.wso2.carbon.la.core/src/main/java/org/wso2/carbon/la/core/impl/TimeAggregator.java diff --git a/modules/components/org.wso2.carbon.la.core/src/main/java/org/wso2/carbon/la/core/impl/DrilldownController.java b/modules/components/org.wso2.carbon.la.core/src/main/java/org/wso2/carbon/la/core/impl/DrilldownController.java new file mode 100644 index 0000000..24a5fec --- /dev/null +++ b/modules/components/org.wso2.carbon.la.core/src/main/java/org/wso2/carbon/la/core/impl/DrilldownController.java @@ -0,0 +1,102 @@ +package org.wso2.carbon.la.core.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.analytics.api.AnalyticsDataAPI; +import org.wso2.carbon.analytics.dataservice.commons.AnalyticsDataResponse; +import org.wso2.carbon.analytics.dataservice.commons.AnalyticsDrillDownRequest; +import org.wso2.carbon.analytics.dataservice.commons.SearchResultEntry; +import org.wso2.carbon.analytics.dataservice.commons.exception.AnalyticsIndexException; +import org.wso2.carbon.analytics.datasource.commons.Record; +import org.wso2.carbon.analytics.datasource.commons.RecordGroup; +import org.wso2.carbon.analytics.datasource.commons.exception.AnalyticsException; +import org.wso2.carbon.la.core.utils.LACoreServiceValueHolder; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * Created by vithulan on 3/4/16. + */ +public class DrilldownController { + private static final Log log = LogFactory.getLog(DrilldownController.class); + private AnalyticsDrillDownRequest analyticsDrillDownRequest; + private AnalyticsDataAPI analyticsDataAPI; + private AnalyticsDataResponse analyticsDataResponse; + public DrilldownController(){ + analyticsDrillDownRequest = new AnalyticsDrillDownRequest(); + analyticsDataAPI = LACoreServiceValueHolder.getInstance().getAnalyticsDataAPI(); + } + + /** + * @param tableName Table name of LogAnalyzer + * @param searchQuery Search query appended with timestamp + * @param length number of elements to be retrieved + * @param start starting point + * @param categoryPath facet path of logstream + * @param username username of user + * @param columnList List of columns that data has to be retrieved from + * @return list of iterators of search result. + */ + public List> getResults(String tableName, String searchQuery, int length, int start, + Map> categoryPath, String username, List columnList){ + List searchResultEntries = null; + try { + searchResultEntries = drillDownSearch(tableName,searchQuery,length,start,categoryPath,username); + } catch (AnalyticsIndexException e) { + String msg = String.format("Error occurred while drilldown searching"); + log.error(msg,e); + } + List ids = getRecordIds(searchResultEntries); + try { + analyticsDataResponse = analyticsDataAPI.get(username, tableName, 1, columnList, ids); + } catch (AnalyticsException e) { + String msg = String.format("Error occurred while filtering using column list"); + log.error(msg,e); + } + List> iterators = new ArrayList<>(); + for (RecordGroup recordGroup : analyticsDataResponse.getRecordGroups()) { + try { + iterators.add(analyticsDataAPI.readRecords(analyticsDataResponse.getRecordStoreName(), recordGroup)); + } catch (AnalyticsException e) { + String msg = String.format("Error occurred while getting record lists"); + log.error(msg,e); + } + } + + return iterators; + } + + /** + * + * @param tableName Table name of LogAnalyzer + * @param searchQuery Search query with timestamp appended + * @param length number of entries to be returned + * @param start starting entry + * @param categoryPath facet path + * @param username username of user + * @return List of ids of search result entry + * @throws AnalyticsIndexException + */ + private List drillDownSearch(String tableName, String searchQuery, int length, int start, + Map> categoryPath, + String username) throws AnalyticsIndexException { + analyticsDrillDownRequest.setTableName(tableName); + analyticsDrillDownRequest.setQuery(searchQuery); + analyticsDrillDownRequest.setRecordCount(length); + analyticsDrillDownRequest.setRecordStartIndex(start); + analyticsDrillDownRequest.setCategoryPaths(categoryPath); + return analyticsDataAPI.drillDownSearch(username, analyticsDrillDownRequest); + + } + + private List getRecordIds(List searchResultEntries){ + List ids = new ArrayList<>(); + for (SearchResultEntry searchResult : searchResultEntries) { + ids.add(searchResult.getId()); + } + return ids; + } +} diff --git a/modules/components/org.wso2.carbon.la.core/src/main/java/org/wso2/carbon/la/core/impl/TimeAggregator.java b/modules/components/org.wso2.carbon.la.core/src/main/java/org/wso2/carbon/la/core/impl/TimeAggregator.java new file mode 100644 index 0000000..908b43a --- /dev/null +++ b/modules/components/org.wso2.carbon.la.core/src/main/java/org/wso2/carbon/la/core/impl/TimeAggregator.java @@ -0,0 +1,349 @@ +package org.wso2.carbon.la.core.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.analytics.datasource.commons.Record; +import org.wso2.carbon.la.commons.constants.LAConstants; +import org.wso2.carbon.la.commons.domain.RecordBean; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * Created by vithulan on 3/4/16. + */ +public class TimeAggregator { + + private static final Log log = LogFactory.getLog(TimeAggregator.class); + /** + * final variables to group entries in a time based way. + */ + private static final String day = "day"; + private static final String week = "week"; + private static final String month = "month"; + private static final String year = "year"; + private static final String auto = "auto"; + + /** + * Groups the entries as @param groupBy + * @param iterators list of iterators of search result entry + * @param columnName column name that result is wanted to be + * @param groupBy way of grouping + * @return returns a map of column entries which are grouped in groupBy method + */ + public Map> getGrouped(List> iterators,String columnName, String groupBy){ + Map> sortedDayMap = getBasicGrouped(iterators, columnName); + Map> sortedAllDayMap = getAllDayMap(sortedDayMap); + Map> groupedMap = getGrouping(sortedAllDayMap, groupBy); + return new TreeMap<>(groupedMap); + } + + /** + * Groups the entries by day + * @param iterators list of iterators of search result entry + * @param columnName column name that result is wanted to be + * @return returns a map of entries which are grouped by day + */ + private Map> getBasicGrouped(List> iterators, String columnName){ + SimpleDateFormat format = new SimpleDateFormat(LAConstants.TIMESTAMP_PATTERN); + Map values; + Map> stamper = new HashMap<>(); + int count = 0; + String val ; + for (Iterator iterator : iterators) { + while (iterator.hasNext()) { + RecordBean recordBean = createRecordBean(iterator.next()); + values = recordBean.getValues(); + long epoch1 = Long.parseLong(values.get(LAConstants.TIMESTAMP_FIELD).toString()); + Date expiry = new Date(epoch1); + String str_date = format.format(expiry); + + Date date = null; + try { + date = format.parse(str_date); + } catch (ParseException e) { + String msg = String.format("Error occurred while parsing date"); + log.error(msg,e); + } + long epoch = date.getTime(); + + if (values.get(columnName) != null) { + val = values.get(columnName).toString(); + + if (!stamper.containsKey(epoch)) { + Map counter = new HashMap(); + counter.put(val, 1); + stamper.put(epoch, counter); + } else { + Map counter = stamper.get(epoch); + if (!counter.containsKey(val)) { + counter.put(val, 1); + stamper.put(epoch, counter); + } else { + count = counter.get(val); + count++; + counter.put(val, count); + stamper.put(epoch, counter); + } + } + + } + if (log.isDebugEnabled()) { + log.debug("Retrieved -- Record Id: " + recordBean.getId() + " values :" + + recordBean.toString()); + } + } + } + return new TreeMap<>(stamper); + } + + /** + * Adds missing days to the @param sortedDayMap + * @param sortedDayMap Map of entries which are grouped sorted in day basis + * @return Map of entries which are grouped and sorted + */ + public Map> getAllDayMap(Map> sortedDayMap){ + Map> allDayMap = new HashMap<>(); + int j = 1; + long lastDay = 0; + long presentDay = 0; + long k = 1; + + for (Map.Entry> entry : sortedDayMap.entrySet()) { + + if (j > 1) { + presentDay = entry.getKey(); + long dif = presentDay - lastDay; + k = dif / LAConstants.EPOCH_DAYGAP; + + } + if (k > 1) { + long newDate = lastDay; + while (k > 1) { + newDate = newDate + LAConstants.EPOCH_DAYGAP; + Map tempMap = new HashMap<>(); + tempMap.put("No Entry", 0); + allDayMap.put(newDate, tempMap); + k--; + } + k = 1; + lastDay = newDate; + } + if (k == 1) { + Map counter = entry.getValue(); + if (j < sortedDayMap.size()) { + j++; + } + lastDay = entry.getKey(); + allDayMap.put(lastDay, counter); + } + + } + return new TreeMap<>(allDayMap); + } + + /** + * Gets the record bean of an entry + * @param record search result entry + * @return returns RecordBean object + */ + private RecordBean createRecordBean(Record record) { + RecordBean recordBean = new RecordBean(); + recordBean.setId(record.getId()); + recordBean.setTableName(record.getTableName()); + recordBean.setTimestamp(record.getTimestamp()); + recordBean.setValues(record.getValues()); + return recordBean; + } + + /** + * Invokes functions group search entries as in @param method + * @param allDayMap sorted and grouped map in day basis + * @param method way of grouping + * @return returns a map which grouped as in @param method + */ + private Map> getGrouping(Map> allDayMap, String method) { + Map> grouped = new HashMap<>(); + switch (method) { + case auto: + grouped = groupbyAuto(allDayMap); + break; + case day: + grouped = groupbyDay(allDayMap); + break; + case week: + grouped = groupbyWeek(allDayMap); + break; + case month: + grouped = groupbyMonth(allDayMap); + break; + case year: + grouped = groupbyYear(allDayMap); + break; + } + return grouped; + } + + /** + * Groups the search entries in "Auto" method + * @param allDayMap sorted and grouped map in day basis + * @return returns grouped map + */ + private Map> groupbyAuto(Map> allDayMap) { + Map> grouped = new HashMap<>(); + int days = allDayMap.size(); + if (days <= 10) { + grouped = groupbyDay(allDayMap); + } else if (days > 10 && days <= 70) { + grouped = groupbyWeek(allDayMap); + } else if (days > 70 && days <= 365 * 3) { + grouped = groupbyMonth(allDayMap); + } else { + grouped = groupbyYear(allDayMap); + } + return grouped; + } + + /** + * Groups search result entries in Day basis + * @param allDayMap sorted and grouped map in day basis + * @return returns a grouped map with string key value + */ + private Map> groupbyDay(Map> allDayMap) { + Map> grouped = new HashMap<>(); + String pattern = LAConstants.DAY_PATTERN; + SimpleDateFormat format = new SimpleDateFormat(pattern); + + for (Map.Entry> entry : allDayMap.entrySet()) { + Date expiry = new Date(entry.getKey()); + String str_day = format.format(expiry); + grouped.put(str_day, entry.getValue()); + } + return grouped; + } + + /** + * Groups search result entries in week basis + * @param allDayMap sorted and grouped map in day basis + * @return returns a grouped map with string key value + */ + private Map> groupbyWeek(Map> allDayMap) { + String pattern = LAConstants.WEEK_PATTERN; + SimpleDateFormat format = new SimpleDateFormat(pattern); + Map>> grouped = new HashMap<>(); + for (Map.Entry> entry : allDayMap.entrySet()) { + Date expiry = new Date(entry.getKey()); + String str_week = format.format(expiry); + if (!grouped.containsKey(str_week)) { + List> list = new ArrayList<>(); + list.add(entry.getValue()); + grouped.put(str_week, list); + } else { + List> list = grouped.get(str_week); + list.add(entry.getValue()); + grouped.put(str_week, list); + } + } + Map> Fgrouped = new HashMap<>(); + for (Map.Entry>> entry : grouped.entrySet()) { + List> list = entry.getValue(); + Map data = new HashMap<>(); + for (Map dataMap : list) { + for (Map.Entry entrr : dataMap.entrySet()) { + if (!data.containsKey(entrr.getKey())) { + data.put(entrr.getKey(), entrr.getValue()); + } else { + int k = data.get(entrr.getKey()); + data.put(entrr.getKey(), k + entrr.getValue()); + } + } + } + Fgrouped.put(entry.getKey(), data); + } + return Fgrouped; + } + + /** + * Groups search result entries in month basis + * @param allDayMap sorted and grouped map in day basis + * @return returns a grouped map with string key value + */ + private Map> groupbyMonth(Map> allDayMap) { + + String pattern = LAConstants.MONTH_PATTERN; + SimpleDateFormat format = new SimpleDateFormat(pattern); + Map>> grouped = new HashMap<>(); + for (Map.Entry> entry : allDayMap.entrySet()) { + Date expiry = new Date(entry.getKey()); + String str_month = format.format(expiry); + if (!grouped.containsKey(str_month)) { + List> list = new ArrayList<>(); + list.add(entry.getValue()); + grouped.put(str_month, list); + } else { + List> list = grouped.get(str_month); + list.add(entry.getValue()); + grouped.put(str_month, list); + } + } + Map> Fgrouped = new HashMap<>(); + for (Map.Entry>> entry : grouped.entrySet()) { + List> list = entry.getValue(); + Map data = new HashMap<>(); + for (Map dataMap : list) { + for (Map.Entry entrr : dataMap.entrySet()) { + if (!data.containsKey(entrr.getKey())) { + data.put(entrr.getKey(), entrr.getValue()); + } else { + int k = data.get(entrr.getKey()); + data.put(entrr.getKey(), k + entrr.getValue()); + } + } + } + Fgrouped.put(entry.getKey(), data); + } + return Fgrouped; + } + + /** + * Groups search result entries in year basis + * @param allDayMap sorted and grouped map in day basis + * @return returns a grouped map with string key value + */ + private Map> groupbyYear(Map> allDayMap) { + String pattern = LAConstants.YEAR_PATTERN; + SimpleDateFormat format = new SimpleDateFormat(pattern); + Map>> grouped = new HashMap<>(); + for (Map.Entry> entry : allDayMap.entrySet()) { + Date expiry = new Date(entry.getKey()); + String str_year = format.format(expiry); + if (!grouped.containsKey(str_year)) { + List> list = new ArrayList<>(); + list.add(entry.getValue()); + grouped.put(str_year, list); + } else { + List> list = grouped.get(str_year); + list.add(entry.getValue()); + grouped.put(str_year, list); + } + } + Map> Fgrouped = new HashMap<>(); + for (Map.Entry>> entry : grouped.entrySet()) { + List> list = entry.getValue(); + Map data = new HashMap<>(); + for (Map dataMap : list) { + for (Map.Entry entrr : dataMap.entrySet()) { + if (!data.containsKey(entrr.getKey())) { + data.put(entrr.getKey(), entrr.getValue()); + } else { + int k = data.get(entrr.getKey()); + data.put(entrr.getKey(), k + entrr.getValue()); + } + } + } + Fgrouped.put(entry.getKey(), data); + } + return Fgrouped; + } +}