From 3e0f95c4d6c65c0110ae4abd35da9dde77ad9ed9 Mon Sep 17 00:00:00 2001 From: Regina Date: Fri, 27 Jan 2017 15:29:32 -0600 Subject: [PATCH 01/26] Userdocument12717 --- docs/User Documentation.md | 696 ++++++++++++++++++++++++++++++++----- 1 file changed, 603 insertions(+), 93 deletions(-) diff --git a/docs/User Documentation.md b/docs/User Documentation.md index 0179e7b..f5e165c 100644 --- a/docs/User Documentation.md +++ b/docs/User Documentation.md @@ -1,16 +1,17 @@ -
+## Getting Started +### Background and Overview +

Open Grid is a geographical information systems developed for residents to access public city data in a more intuitive manner. The application supports situational awareness, incident responses and monitoring, historical data retrieval, and real-time advanced analytics.

-![login](media/combine32.jpg) +

OpenGrid design has a three-layer service architecture: Presentation, Business and Data. The presentation layer provides the application’s user interface which is a static HTML/JavaScript based GUI that presents a more intuitive and modernized approach that support usage and accessibility on any mobile device and terminals in public safety vehicles.

+

Business layer which is the service layer implements the business aspects and functionality of the application. Such as defining business rules, design, deployment, maintenance and management of the application which is adaptable based off client specification.

-## Getting Started -

OpenGrid is an enterprise geographical information system. Developed to support situational awareness, incident monitoring and responses, historical data retrieval, and real-time advanced analytics.

+

The data layer is where information is stored from databases. OpenGrid utilizes MongoDB, a NoSQL open source document database optimized to handle spatial data. Mongo provides high performance, high availability and automatic scaling. The application interacts with different database engines or APIs which makes it easy for other cities: governments, non-profits and/or corporations to adopt the application.

-

The OpenGrid architecture consists of three primary parts: the user interface, service layer, and data layer. The user interface is design to remain unchanged. The service layer is meant to be flexible to any data source. The data layer will be implemented based on client specifications.

- -

OpenGrid utilizes MongoDB, a NoSQL database optimized to handle big spatially-enabled data. From the application layer, users may then query data by type, time and distance from a point or within a boundary, while retrieving real-time or historical data. Queries and data flow through a web service to ensure data security.

+

OpenGrid offers configurable methods where administrators can define user roles, system authenticators or remove security features, where verification is no longer a requirement for application accessibility. Administrators can apply certain functions and data-set/s to a specific security group/s and/or users, if relevant. +

-### Acknowledgements +## Acknowledgements OpenGrid was developed with the support of Bloomberg Philanthropies. ## User Documentation @@ -19,140 +20,649 @@ OpenGrid was developed with the support of Bloomberg Philanthropies. #### Supported Browsers

OpenGrid supports both mobile and desktop versions search engines. Mobile Android devices support Firefox version 40+ and Chrome 44+. Ios devices support Chrome 45+, Safari 8.1+ and Firefox 40+. Desktop versions support Firefox 42+ and Chrome 46+; Internet Explorer is supported from IE10+ no older versions will be continued. All browsers must have cookies enabled and support JavaScript/ECMAScript version 5.1.

-## Quick Search + +### Login/Logout

-The quick search help feature assist users in performing a valid search. Each search is unique based on query type. Some are queried by keyword and others are queried based off keyword followed by key phrase (descriptive text) e.g., tweets and weather query. The search results will appear on the grid as either points or custom icons. +When the application is initially opened through a browser the login screen will appear (If authentication feature is activated). Prompting a cursor within the username textbox; the login button is initially disabled until both username and password has been entered. Once the user information has been provided and the login button has been selected, the user credentials will become validated against the system.

-

Find an Address
50 W. Washington St

+
+
Figure 1. OpenGrid Login Screen
-

Display area by Lat and Long
41.8270, -87.6423

+
+

+If login failed, an error message will appear: “Login failed due to invalid username or password.” The system will allow the user to re-enter a valid username and/or password. +

-

Search by Place Name
Daley Center

+
-

Search by Tweets
Tweet Ballet

+

+


+
Figure 2. Login Error Message
+

-

Search area weather
Weather 60602

+
+
+If login was successful, the landing page will launch. -## Advanced Search -

Advanced Search allows you to combine search terms by setting specific parameters for your results.It gives the user the ability to narrow their searches by a series of different filters such as adding additional rules, groups,datasets and Geo spatial-filters.

+

+


+
Figure 3. Landing Page
+

+ +### The Landing Page +> The Landing page image shown above in Figure 3. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Element No.Element Name/Description
+
    +
  1. +
+

Quick Search box is used to perform basic searches on datasets. The question mark icon within the Quick Search box displays a cheat sheet for quick search syntax.

+
+
    +
  1. +
+

+

    +
  • Find Data Button displays the advanced search panel utilize to create more defined searches.

  • +
  • Clear Data Button clears any search options with the panel, textbox and the map grid.

  • +
  • Manage Button displays the list of groups and user’s data (this feature is only available for admin users)
  • +
+

+
+
    +
  1. +
+

User Credentials – Displays the name of the currently logged in user also provides the logout functionality, which is located within the drop-down menu to the right of the username.

+
+
    +
  1. +
+User Manual Icon – Link to accessing the user manual +
+
    +
  1. +
+Zoom In and Zoom Out Icon +
+
    +
  1. +
+Reset Map View and Area Zoom Icon +
+
    +
  1. +
+Zoom specification Icon
Full-Screen Icon
Geo-location Icon +
+
    +
  1. +
+

Measurement Tool

+
+
    +
  1. +
+

+Layers Icon
Consist of list of grid views and weather layers. +

-### Selecting data and date range -

To query by data and date range...Click on add datasets, select a dataset from the list (for ex: business license); click submit. Add a rule or group to your dataset (Adding a rule or group is optional and gives the user an option to query by date, city, name, address, etc. depending on the search criteria.). Date range can be specified with the parameters and relative dates of:

+

+Grid Views: +

      +
    • Street View (Default)
    • +
    • Aerial View
    • +
    • Black and White View
    • +
+

+

+Open Weather Layers: +

      +
    • Cloud Cover
    • +
    • Quantity of Precipitation
    • +
    • Sea Level Pressure
    • +
    • Temperature
    • +
+

+
+
    +
  1. +
+

Expandable Table view panel

+
+
    +
  1. +
+

Map attribution; this will display a link for any copyright information, terms of use, etc.

+
+
    +
  1. +
+

Main Map display

+
-- between -- greater -- less -- yesterday -- tomorrow -- today -- day (i.e. a day ago, 2 days ago, etc.) -- week (i.e. a week ago, 2 weeks ago, etc.) -- month (i.e. a month ago, 2 months ago, etc.) -- year (i.e. a year ago, 2 years ago, etc.)
+### Session Timeout +

+Once the landing page has been displayed if there's been no server activity on the application after four hours the user will be logged out automatically with a message appearing. See Figure 4 below: +

-![combine](media/combine21.jpg) +
+
Figure 4. Message when Session Times Out
-### Search around a parameter (Point) -

To search with given point or boundaries the Geo spatial-filters will have to be applied.
-There are two filters to search by “Within” and “Near”. “Within” is used to search boundaries within a query. “Near” is used to search within a given parameter or around the area of your locale.

-Within have search boundaries of:
+
+## Base Map +#### Map Layers +

+The layers icon displays multiple basemap views and open weather layers. The list is built dynamically based on what's available through the Map Service provider. In the lower right hand corner on the grid, there is information and active links provided about the map services. The initial launch page displays the default basemap, Street View. For all other basemaps see Figure 5a and 5b below. +

+
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec15.jpg)
+

Figure 5a. Aerial View

-- Map Extent
-- Drawn Extent (create polygon or rectangular perimeter)
-- Citywide (within the city limits)
-- Zip Code (within a given zip code)
-- Ward (within a given ward) +
+
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec17.jpg)
+

Figure 5b. Black and White View

-

Near have search points of:

-- Near “Me” (search around the user geo location)
-- Near “Marker” (search around a given area) +
-## Monitoring Queries -

The monitor mode in the quick search section allows the user to monitor and update activity using auto-refresh. To use auto-refresh for monitoring queries:

- +#### Map Legend +

+A dynamic legend will display a representation of what type of search was executed. If, multiple types with same dataset is being displayed for a search; it will display the two datatypes based off color representation from the setup on the grid. +

-## Table Functions -

The table functionality is used to organized information that has been displayed via query. The table displays the data that was pulled from the queried results and in return structures it in a readable format for the user to interpret. Each dropdown are intertwined with one another.The table consist of five drop down sections:

+
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec20.jpg)
+

Figure 6. Map Legend

+
+ +#### Map Navigation +Navigation tools are used to aid users in finding their way around a map. Using navigational controls, the user should be able to: -![table](media/combine33.jpg) -#### 1. Columns
-The columns section displays the list of columns from the query data-sets and places them in order by default. The columns can be interchangeable and removed to fit the user preference. +* zoom in/out +* reset the map +* pan to any direction +* switch to full screen mode +* activate geo-location +* apply layers +* apply measurements +* select hyperlinks -#### 2. Exportation
-The exportation dropdown is used to send or transmit data from the query into csv, pdf or MS- excel format. +
+ +#### Measurement Tool +

+By selecting the measurement icon, the measurement tool can be turned on to enable measurements of the following: +

-#### 3. Graphing
-The graphing functionality is used to display the query results into graph formation based on the information. The user has the decision to create a graph based key search criteria’s that displays in the dropdown list. +Linear Measurement +- Distance between points -#### 4. Heat Map
-Heat Map function is displayed where values contained in a matrix are represented as colors. There are many color schemes that can be used to illustrate a heat map. +

Area Measurement

+- Distance around a point -#### 5. Tile Map -Tile Maps are small images, usually rectangular or isometric that acts like a puzzle piece to cover an intended area. +
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec23.jpg)
+

Figure 7a. Linear Measurement

-### Manage Saved Queries -

Search criteria that’s been entered and saved within the database are called Saved Queries. The saved queries are store in the Manage Queries section. To save a query, the user enters in the preferred name for a query; then clicks the saved button. The user will also be given an option to overwrite other queries and save it using an existing name.

-

There are three icons for managing queries submit, share and delete. The submit function allows the user to run the query for the manage queries window. The share function gives the user the ability to share a query either to a group or to another user. The delete function gives the user the option to delete a query.


-![measure](media/combine31.jpg) -## Measure Distance and Area -

Measuring distances and areas is a function within OpenGrid that allows the user to perform measurements between any given point to another. Once the measurement tool is selected the user will be prompt to start creating measurements by adding points on the map.

+
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec21.jpg)
+

Figure 7b. Multiple Linear Measurement

+

+
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec26.jpg)
+

Figure 7c. Area Measurement

-## Measurement and Tools -

Click the measurement tool icon.


-![icoa](media/ombine1.jpg) +
-

A measurement tool textbox will appear with an option to create a new measurement.

+## Types of Searches +### Quick Search +A Quick Search box can be used to perform common searches that will support the following commands/inputs: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Query TypeDescriptionCommand SyntaxSampleDisplay Columns on Search Result
Address Finds the specified address using the Map/GIS Service. <number><direction><streetname> 50 W. Washington Displayed as a marker on the map
Latitude and Longitude Displays a marker to show location of latitude and longitude entered. <latitude>, <longitude> 41.8270, -87.6423 Displayed as a marker on the map
Place Name Shows location of the place specified. <name of place> Daley Center Displayed as a marker on the map
Tweets Displays recent tweets matching keyword, if provided. Keyword can be a bareword or a double-quoted set of words. tweet <keyword> tweet
  • Date
  • Screen Name
  • Text
  • City
Weather Displays a point in the middle of the map showing weather information for the zip code. weather <zip code> weather 60601
  • Temperature in (Fahrenheit)
  • Wind
  • Conditions
  • Humidity
  • Forecast

-![icob](media/ombine4.jpg) +

+The data results for the above search types and any other search form will appear as point/s, custom icons or markers on the map. Any search type information, whether its performed as a Quick Search or Advanced Search will display on the map and table grid. The table grid displays data as rows. +

+ +

+In some cases, there will only be one row of data on the grid (for example, Weather or Bus ID search). Quick search results can be cleared/reset by clicking on the Clear Data button. This action will also stop all data auto-refresh activities, if any are happening in the background. +

+ +

+Tweet dataset provides real-time data and automatically refreshes with new data every 30 seconds within set intervals. The data points have active links embedded, when selected the link will open in a new browser window displaying social media content, such as articles, photos and location associated with the tweet. +

-

Once click "create a new measurement"... display points of origin and destination on the map by creating points.


-![icoc](media/ombine3.jpg) + +
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec25.jpg)
+

Figure 8. Quick Search on Tweets


-

After the measurement has been created the finish measurement textbox will appear. click "Finish Measurement".

-![icod](media/combine10.jpg) +### Find Data Panel + +Note: Manage Queries, Load Saved Query and Save Query As seen in the image below are configurations that can be enabled and disabled within the application. These attributes does not appear in opengrid.io. + +##### Advanced Search + +
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec18.jpg)
+

Figure 9. Find Data Panel

+
-

After clicking on finish measurement the "Area Measurement" textbox will appear providing information about the perimeter:

. - -

It also gives the user an option to center on the Area or to Delete the perimeter.

+The Find Data Panel is where more defined searches are built, saved and existing searches are executed. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Section No.Description
+
    +
  1. +
+

Search Link - Displays the Advanced Search Panel

+ +
+
    +
  1. +
+

Manage Queries Link - Stores saved searches

+ +
+
    +
  1. +
+

Existing Queries +

        +
      • COMMONLY USED QUERIES
      • +
      • LOAD SAVED QUERIES
      • +
+

+ +
+
    +
  1. +
+

Select Data

+
        +
      • Add dataset
      • +
+ +
+
    +
  1. +
+Select Location +
        +
      • WITHIN (SELECT BOUNDARY)
      • +
      • NEAR (SELECT POINT)
      • +
+ +
+
    +
  1. +
+Save Query As +
+
    +
  1. +
+Auto-Refresh Every (SECONDS) +
+
    +
  1. +
+Get Data - Executes the search +
+
    +
  1. +
+

Clear Search - Resets the Find Data Panel

+
+
-![icoe](media/ombine5.jpg) +

+The Find Data Panel is also called the advanced search, it is used to narrow searches by applying a series of different filters and actions. The user has the ability to enter a combination of search criteria by applying one or more datasets, adding rule/s or group/s for building a search. The panel has map extent setup as default when performing a search; when a search is executed all data will plot within the area of the current map location boundary. +

-

+

+All datasets have a default color in case multiple datasets are applied on the map, it helps differentiate between the data. The user also has the ability to modify color, size and opacity of each data point pertaining to a dataset. +

-![icof](media/combine9.jpg) +

+Using food establishments as an example, a user can search for restaurants and food trucks in Chicago, the two criteria is listed under a single dataset, called Business Licenses. A user may want to run multiple Business Licenses criteria in a single search for comparison purposes. What should a user do since there is a one color limitation and default color per dataset setup? +

-## FREQUENTLY ASKED QUESTIONS +

+Simple, just add Business Licenses dataset twice, then set one with a filter License_description = “Retail Food Establishment” and the other License_description = “Mobile Food License”, and assign each dataset a different color by selecting the “Color Option” tool beneath each dataset setup. +

-#### Whats the difference between quick search and advanced search -

Quick search is a simple search tool that is queried with specific parameters but may return a broader result set whereas Advanced Search is used to combine search terms by setting specific parameters for result set; "Advanced Search" enables a user to search for requirements, features or use cases that match specific values in a dataset. The search results are smaller and more relevant than results from a quick search.

+

+In the example below, it shows how the search was applied and how each data type is represented on the grid... Retail Food Establishments has the default color of Blue and Mobile Food Licenses is Red. +

+
+
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec16.jpg)
+

Figure 10. Business Licenses Dataset Example

+
+

+To further filter a search, a geo-spatial filter can be applied by drawing a shape on a targeted area on a map or selecting one of the pre-defined boundaries from the Select Location section of the panel. Select Location has two geo-spatial options called WITHIN and NEAR (see images below). +

-#### Whats the difference between Near Me and Near Marker -

The Near "Me" and the Near "Marker" both use triangulation techniques. It’s located within the Advanced Search section under the geo-spatial filter. Using the Near “Me” function will retrieve the perimeter around the user location using both lat and long to triangulate the user’s location or from usage of information from cell towers to pinpoint the approximate position. Near “Marker” function will pull data around the perimeter based off a given point using markers.

+
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec09.jpg)
+

Figure 11a. WITHIN Boundary


+
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec10.jpg)
+

Figure 11b. NEAR Me

+
+
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec11.jpg)
+

Figure 11c. NEAR Marker

+

+Amongst applying boundaries to a search, auto refresh component can be setup for a search by the number of intervals in seconds (minimum of 30 seconds, maximum of 3600 seconds or 60 minutes). +

+

+When a query is submitted, the application will display a message when the query times out. It will also display a message when the search service returns no data. +

+
+### Existing Queries +##### Commonly Used Queries +

+Are popular searches that city residents are most likely to explore within the application. The drop menu has a list of predefined queries that a user can apply as a search. +

-Back to Top +

+Each query when selected displays its search parameters under the Select Data section. To run commonly used queries, select a search from the drop list and select Get Data. +

+
+
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec27.jpg)
+

Figure 12. Commonly Used Queries

+
+ +## Data Formation +### Map Grid +

+The map grid is interactive, a user can navigate the map using a mouse, keyboard and for mobile devices by swiping using index finger. The map displays the maximum of 1000 points. All data appears on the grid as points and/or markers. Places/Address search plot as markers and Datasets plot as points on the grid. The map legend appears on the grid when a search has been executed, displaying the color of the data point and the name of the data being displayed. A retractable information box appears to the bottom right of the grid when a search is performed, displaying the No. of records found or an error message pertaining to a search. The grid has an automatic refresh feature for updating and re-plotting data upon navigating around the map. +

+ +
+ +### Table Grid +

+The table grid is located at the bottom of the map. The table becomes active when a search has been performed and returns a set of results on the map. To access the table after a search, click on the black bar at the bottom of the map, there is a white carat displayed in the middle of the black bar below as an indicator that the bar is collapsible. After clicking on the bar, the table will expand upward exposing the table and its components. +

+
+
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec29.jpg)
+

Figure 13. Table Grid

+
+### Components of the Table Grid +Information Tab + +- Search type (i.e. Place/Address, Business License). + +Search Textbox + +- Used to filter by data components within the table. + +Columns Icon + +- Provides a drop list of available columns pertaining to the data within the datasets. The columns are interchangeable, user can enable and disable certain columns from the table by selecting or deselecting each column name from the column list. + +Export Icon +- Provides a drop-list of available exportation options used to send or transfer data from the table into the following formats: + +Graph +- Places the search results into a pie chart it is group by certain parameters from the search depending on the dataset. +Heat Map +- Data that’s contained in a matrix within a representation of colors to use for analysis, comparison or trending purposes. +Tile Map +- Small images, usually rectangular or isometric layers that acts as puzzle pieces to cover an intended area. +Rows Droplist +* Provides a drop list of total number of rows that can be displayed per page. + +Page Numbers + +* Interactive number links for maneuvering through pages. + +
+ +## Saving Searches +

+OpenGrid allows a user to create searches; there is also an option to save a search. To save a query, define a name for your search within the “Save Query As” section of the Find Data Panel. A successful save will return a message in the lower right corner “Query was successfully saved". +

+ +

+A search is saved and stored in the manage queries panel. Saved searches are also accessible within the Load Save Queries drop list located in the Existing Queries section of the Find Data Panel. Load Save Queries section stores the ten most recent saved searches. +

+ +

+A saved search can also be overwritten upon user discretion. To overwrite a search, simply access the save search in the list and within the “Save Query As” textbox remove the saved search name and redefine it. Select the Save button to execute the new save. A warning message within a decision textbox will appear alarming the user that the name already exist, would you like to continue with the overwrite and as a result it will overwrite the existing query with that name. +

+ +
+ +
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec14.jpg)
+

Figure 14. Query Overwrite

+
+ +## Administrator +##### Manage Users and Manage Groups +

The admin screen is accessible by selecting the Manage Button. Administrator capabilities are available for admin users only. Manage Users and Manage Groups are active links; when selected their panel is exposed. Admin users can perform the following tasks: +

+ +- Add/ Remove users +- Update Users +- Delete Users +- Add/Remove roles from users +- Add a Group +- Add/Remove Group + +

+To setup a group under the Manage Group link, select the green new group. The administrator will need to provide a Group ID, the group ID when initially created and saved will no longer be editable; name of the group and a description for the group, in which both are editable. There are two check boxes “Is Admin Group” which is an optional configuration that signifies the group is for admin use only; “Enabled” , activates the new group for usage. To save the new group, select the Submit button, this action will cause the group to be saved and stored within the manage groups panel list. Once a new group has been created the administrator can begin assigning the group to users. +

+ +

+Manage Group Panel provides information about the different types of groups created within the application and its components. +

+ +* Edit Functions (update and delete icons) +* New Group Button +* Group Details +* Functions +* Data Types + +

+The group details column provides the name and the description of the group. The Functions columns provides a drop list of available options that are applied to a group. The administrator will have access to all options and users only has access to the advanced search option. +

+ + +

+The manage users link displays a list of all available users and components. +

+ +* Edits functions (update and delete icons) +* New User Button +* User ID +* User First Name +* User Last Name +* Group options the users have access to + +

+The new user button performs two functionalities, finding a User and Adding a User. To find a user, select “Find By User ID” or “Find By Name”. “Find By Name” search provides multiple search options to find existing or future user/s by first name, last name, or combination of both. To look-up a user select the Find button, this action will search for a user profile. +

+ +

+To add a new user, search for the user by name or userid. When the appropriate user is found click on the submit button. This action will add the user to the application. +

+ +

+To verify, if user was successfully added navigate to the Manage Users panel, scroll down the list until the username is found. +

+ +Back to Top From ba0caaffa81757bccfea2dffdf46b5b511fbfa41 Mon Sep 17 00:00:00 2001 From: Regina Date: Fri, 27 Jan 2017 15:58:43 -0600 Subject: [PATCH 02/26] Updated User Documentation11717 --- docs/User Documentation.md | 52 ++++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/docs/User Documentation.md b/docs/User Documentation.md index f5e165c..5e2d67f 100644 --- a/docs/User Documentation.md +++ b/docs/User Documentation.md @@ -1,3 +1,39 @@ +

OpenGrid

+

Functional Design

+

Version 1.3.0

+

+

Department of Innovation & Technology

+ +# Table of Contents +* [Getting Started](#getting-started) + * [Background and Overview](#background-and-overview) +* [Acknowledgements](#acknowledgements) +* [User Documentation](#user-documentation) + * [Supported Browsers](#supported-browsers) + * [Login/Logout](#loginlogout) + * [The Landing Page](#the-landing-page) + * [Session Timeout](#session-timeout) +* [Base Map](#base-map) + * [Map Layers](#map-layers) + * [Map Legend](#map-legend) + * [Map Navigation](#map-navigation) + * [Measurement Tool](#measurement-tool) +* [Types of Searches](#types-of-searches) + * [Quick Search](#quick-search) + * [Find Data Panel](#find-data-panel) + * [Advanced Search](#advanced-search) +* [Existing Queries](#existing-queries) + * [Commonly Used Queries](#commonly-used-queries) +* [Data Formation](#data-formation) + * [Map Grid](#map-grid) + * [Table Grid](#table-grid) + * [Components of the Table Grid](#components-of-the-table-grid) +* [Saving Searches](#saving-searches) +* [Administrator](#administrator) + * [Manage Users and Manage Groups](#manage-users-and-manage-groups) +
+
+ ## Getting Started ### Background and Overview

Open Grid is a geographical information systems developed for residents to access public city data in a more intuitive manner. The application supports situational awareness, incident responses and monitoring, historical data retrieval, and real-time advanced analytics.

@@ -27,7 +63,7 @@ When the application is initially opened through a browser the login screen will

-
Figure 1. OpenGrid Login Screen
+

Figure 1. OpenGrid Login Screen


@@ -38,7 +74,7 @@ If login failed, an error message will appear: “Login failed due to invalid


-
Figure 2. Login Error Message
+

Figure 2. Login Error Message


@@ -47,7 +83,7 @@ If login was successful, the landing page will launch.


-
Figure 3. Landing Page
+

Figure 3. Landing Page

### The Landing Page @@ -225,7 +261,7 @@ Once the landing page has been displayed if there's been no server activity on t

-
Figure 4. Message when Session Times Out
+

Figure 4. Message when Session Times Out


## Base Map @@ -353,7 +389,7 @@ Tweet dataset provides real-time data and automatically refreshes with new data ##### Advanced Search
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec18.jpg)
-

Figure 9. Find Data Panel

+

Figure 9. Find Data Panel


The Find Data Panel is where more defined searches are built, saved and existing searches are executed. @@ -509,7 +545,7 @@ To further filter a search, a geo-spatial filter can be applied by drawing a sha

Figure 11b. NEAR Me


![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec11.jpg)
-

Figure 11c. NEAR Marker

+

Figure 11c. NEAR Marker

Amongst applying boundaries to a search, auto refresh component can be setup for a search by the number of intervals in seconds (minimum of 30 seconds, maximum of 3600 seconds or 60 minutes).

@@ -594,11 +630,11 @@ The table grid is located at the bottom of the map. The table becomes active whe ## Saving Searches

-OpenGrid allows a user to create searches; there is also an option to save a search. To save a query, define a name for your search within the “Save Query As” section of the Find Data Panel. A successful save will return a message in the lower right corner “Query was successfully saved". +OpenGrid allows a user to create searches; there is also an option to save a search. To save a query, define a name for your search within the “Save Query As” section of the Find Data Panel. A successful save will return a message in the lower right corner, “Query was successfully saved".

-A search is saved and stored in the manage queries panel. Saved searches are also accessible within the Load Save Queries drop list located in the Existing Queries section of the Find Data Panel. Load Save Queries section stores the ten most recent saved searches. +A search is saved and stored in the Manage Queries panel. Saved searches are also accessible within the Load Save Queries drop list located in the Existing Queries section of the Find Data Panel. Load Save Queries section stores the ten most recent saved searches.

From 6f08b3e20513d1fcbc35d0f1caba0532017ef106 Mon Sep 17 00:00:00 2001 From: Regina Date: Fri, 27 Jan 2017 16:03:33 -0600 Subject: [PATCH 03/26] Increase the font size of OpenGrid --- docs/User Documentation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/User Documentation.md b/docs/User Documentation.md index 5e2d67f..bb1f543 100644 --- a/docs/User Documentation.md +++ b/docs/User Documentation.md @@ -1,10 +1,10 @@ -

OpenGrid

+

OpenGrid

Functional Design

Version 1.3.0

Department of Innovation & Technology

-# Table of Contents +### Table of Contents * [Getting Started](#getting-started) * [Background and Overview](#background-and-overview) * [Acknowledgements](#acknowledgements) From 198c2de4074f327d1d21975cbacc81b314adb4d2 Mon Sep 17 00:00:00 2001 From: Regina Date: Fri, 27 Jan 2017 16:19:13 -0600 Subject: [PATCH 04/26] Removal Functional Design in Title --- docs/User Documentation.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/User Documentation.md b/docs/User Documentation.md index bb1f543..1e7143e 100644 --- a/docs/User Documentation.md +++ b/docs/User Documentation.md @@ -1,5 +1,4 @@

OpenGrid

-

Functional Design

Version 1.3.0

Department of Innovation & Technology

From 76e7b82aa573425cb7ac1c11b07729da619a4641 Mon Sep 17 00:00:00 2001 From: Regina Date: Mon, 20 Mar 2017 14:54:38 -0500 Subject: [PATCH 05/26] Update user documentation to fit GFM --- docs/User Documentation.md | 85 ++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 41 deletions(-) diff --git a/docs/User Documentation.md b/docs/User Documentation.md index 1e7143e..4e9c8ce 100644 --- a/docs/User Documentation.md +++ b/docs/User Documentation.md @@ -254,7 +254,9 @@ Layers Icon

Figure 4. Message when Session Times Out

-
-## Base Map -#### Map Layers + +

Base Map

+

Map Layers

The layers icon displays multiple basemap views and open weather layers. The list is built dynamically based on what's available through the Map Service provider. In the lower right hand corner on the grid, there is information and active links provided about the map services. The initial launch page displays the default basemap, Street View. For all other basemaps see Figure 5a and 5b below.

-
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec15.jpg)
+
+

Figure 5a. Aerial View

-
-
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec17.jpg)
-

Figure 5b. Black and White View


+
+

Figure 5b. Black and White View

-#### Map Legend +

Map Legend

A dynamic legend will display a representation of what type of search was executed. If, multiple types with same dataset is being displayed for a search; it will display the two datatypes based off color representation from the setup on the grid.

-
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec20.jpg)
+

Figure 6. Map Legend


-#### Map Navigation +

Map Navigation

Navigation tools are used to aid users in finding their way around a map. Using navigational controls, the user should be able to: * zoom in/out @@ -300,7 +302,7 @@ Navigation tools are used to aid users in finding their way around a map. Using
-#### Measurement Tool +

Measurement Tool

By selecting the measurement icon, the measurement tool can be turned on to enable measurements of the following:

@@ -311,22 +313,22 @@ Linear Measurement

Area Measurement

- Distance around a point -
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec23.jpg)
+

Figure 7a. Linear Measurement


-
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec21.jpg)
+

Figure 7b. Multiple Linear Measurement



-
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec26.jpg)
+

Figure 7c. Area Measurement



-## Types of Searches -### Quick Search +

Types of Searches

+

Quick Search

A Quick Search box can be used to perform common searches that will support the following commands/inputs: @@ -347,21 +349,22 @@ A Quick Search box can be used to perform common searches that will support the - + - + - + -
Address Finds the specified address using the Map/GIS Service. <number><direction><streetname> 50 W. Washington Displayed as a marker on the map
Latitude and Longitude Displays a marker to show location of latitude and longitude entered. <latitude>, <longitude> 41.8270, -87.6423 Displayed as a marker on the map
Place Name Shows location of the place specified. <name of place> Daley Center Displayed as a marker on the map
Tweets Displays recent tweets matching keyword, if provided. Keyword can be a bareword or a double-quoted set of words. tweet <keyword> tweet
  • Date
  • Screen Name
  • Text
  • City
Weather Displays a point in the middle of the map showing weather information for the zip code. weather <zip code> weather 60601
  • Temperature in (Fahrenheit)
  • Wind
  • Conditions
  • Humidity
  • Forecast
+ +

The data results for the above search types and any other search form will appear as point/s, custom icons or markers on the map. Any search type information, whether its performed as a Quick Search or Advanced Search will display on the map and table grid. The table grid displays data as rows. @@ -377,17 +380,17 @@ Tweet dataset provides real-time data and automatically refreshes with new data
-

![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec25.jpg)
+

Figure 8. Quick Search on Tweets


-### Find Data Panel +

Find Data Panel

Note: Manage Queries, Load Saved Query and Save Query As seen in the image below are configurations that can be enabled and disabled within the application. These attributes does not appear in opengrid.io. -##### Advanced Search +

Advanced Search

-
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec18.jpg)
+

Figure 9. Find Data Panel


@@ -530,20 +533,20 @@ Simple, just add Business Licenses dataset twice, then set one with a filter
-
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec16.jpg)
+

Figure 10. Business Licenses Dataset Example


To further filter a search, a geo-spatial filter can be applied by drawing a shape on a targeted area on a map or selecting one of the pre-defined boundaries from the Select Location section of the panel. Select Location has two geo-spatial options called WITHIN and NEAR (see images below).

-
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec09.jpg)
+

Figure 11a. WITHIN Boundary


-
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec10.jpg)
+

Figure 11b. NEAR Me


-
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec11.jpg)
+

Figure 11c. NEAR Marker

Amongst applying boundaries to a search, auto refresh component can be setup for a search by the number of intervals in seconds (minimum of 30 seconds, maximum of 3600 seconds or 60 minutes). @@ -553,8 +556,8 @@ Amongst applying boundaries to a search, auto refresh component can be setup for When a query is submitted, the application will display a message when the query times out. It will also display a message when the search service returns no data.


-### Existing Queries -##### Commonly Used Queries +

Existing Queries

+

Commonly Used Queries

Are popular searches that city residents are most likely to explore within the application. The drop menu has a list of predefined queries that a user can apply as a search.

@@ -563,27 +566,27 @@ Are popular searches that city residents are most likely to explore within the a Each query when selected displays its search parameters under the Select Data section. To run commonly used queries, select a search from the drop list and select Get Data.


-
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec27.jpg)
+

Figure 12. Commonly Used Queries


-## Data Formation -### Map Grid +

Data Formation

+

Map Grid

The map grid is interactive, a user can navigate the map using a mouse, keyboard and for mobile devices by swiping using index finger. The map displays the maximum of 1000 points. All data appears on the grid as points and/or markers. Places/Address search plot as markers and Datasets plot as points on the grid. The map legend appears on the grid when a search has been executed, displaying the color of the data point and the name of the data being displayed. A retractable information box appears to the bottom right of the grid when a search is performed, displaying the No. of records found or an error message pertaining to a search. The grid has an automatic refresh feature for updating and re-plotting data upon navigating around the map.


-### Table Grid +

Table Grid

The table grid is located at the bottom of the map. The table becomes active when a search has been performed and returns a set of results on the map. To access the table after a search, click on the black bar at the bottom of the map, there is a white carat displayed in the middle of the black bar below as an indicator that the bar is collapsible. After clicking on the bar, the table will expand upward exposing the table and its components.


-
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec29.jpg)
+

Figure 13. Table Grid


-### Components of the Table Grid +

Components of the Table Grid

Information Tab - Search type (i.e. Place/Address, Business License). @@ -627,7 +630,7 @@ The table grid is located at the bottom of the map. The table becomes active whe
-## Saving Searches +

Saving Searches

OpenGrid allows a user to create searches; there is also an option to save a search. To save a query, define a name for your search within the “Save Query As” section of the Find Data Panel. A successful save will return a message in the lower right corner, “Query was successfully saved".

@@ -642,12 +645,12 @@ A saved search can also be overwritten upon user discretion. To overwrite a sear
-
![](https://github.com/Chicago/opengrid/blob/dev/docs/media/dec14.jpg)
+

Figure 14. Query Overwrite


-## Administrator -##### Manage Users and Manage Groups +

Administrator

+

Manage Users and Manage Groups

The admin screen is accessible by selecting the Manage Button. Administrator capabilities are available for admin users only. Manage Users and Manage Groups are active links; when selected their panel is exposed. Admin users can perform the following tasks:

From 6ac9f0803e2263cbf3cae2e3c29a69cdbf25051c Mon Sep 17 00:00:00 2001 From: Regina Date: Mon, 20 Mar 2017 15:06:16 -0500 Subject: [PATCH 06/26] Update to User documentation in GFM --- docs/User Documentation.md | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/docs/User Documentation.md b/docs/User Documentation.md index 4e9c8ce..f090abe 100644 --- a/docs/User Documentation.md +++ b/docs/User Documentation.md @@ -277,6 +277,7 @@ The layers icon displays multiple basemap views and open weather layers. The lis
+

Figure 5b. Black and White View

Map Legend

@@ -285,11 +286,12 @@ A dynamic legend will display a representation of what type of search was execut

+

Figure 6. Map Legend


-

Map Navigation

-Navigation tools are used to aid users in finding their way around a map. Using navigational controls, the user should be able to: +#### Map Navigation +

Navigation tools are used to aid users in finding their way around a map. Using navigational controls, the user should be able to:

* zoom in/out * reset the map @@ -302,26 +304,29 @@ Navigation tools are used to aid users in finding their way around a map. Using
-

Measurement Tool

+#### Measurement Tool

By selecting the measurement icon, the measurement tool can be turned on to enable measurements of the following:

-Linear Measurement +

Linear Measurement

- Distance between points

Area Measurement

- Distance around a point -
+
+

Figure 7a. Linear Measurement


-
+
+

Figure 7b. Multiple Linear Measurement



-
+
+

Figure 7c. Area Measurement


@@ -381,6 +386,7 @@ Tweet dataset provides real-time data and automatically refreshes with new data
+

Figure 8. Quick Search on Tweets


@@ -391,6 +397,7 @@ Tweet dataset provides real-time data and automatically refreshes with new data

Advanced Search

+

Figure 9. Find Data Panel


@@ -534,6 +541,7 @@ In the example below, it shows how the search was applied and how each data type


+

Figure 10. Business Licenses Dataset Example


@@ -541,12 +549,15 @@ To further filter a search, a geo-spatial filter can be applied by drawing a sha

+

Figure 11a. WITHIN Boundary


+

Figure 11b. NEAR Me


+

Figure 11c. NEAR Marker

Amongst applying boundaries to a search, auto refresh component can be setup for a search by the number of intervals in seconds (minimum of 30 seconds, maximum of 3600 seconds or 60 minutes). @@ -567,6 +578,7 @@ Each query when selected displays its search parameters under the Select Data se


+

Figure 12. Commonly Used Queries


@@ -584,6 +596,7 @@ The table grid is located at the bottom of the map. The table becomes active whe


+

Figure 13. Table Grid


Components of the Table Grid

@@ -646,6 +659,7 @@ A saved search can also be overwritten upon user discretion. To overwrite a sear
+

Figure 14. Query Overwrite


From 2d6a5707c78bea961f6a5ee5d07493886b126ded Mon Sep 17 00:00:00 2001 From: Regina Date: Mon, 20 Mar 2017 15:15:26 -0500 Subject: [PATCH 07/26] Update User Documentation.md --- docs/User Documentation.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/User Documentation.md b/docs/User Documentation.md index f090abe..41f532e 100644 --- a/docs/User Documentation.md +++ b/docs/User Documentation.md @@ -310,10 +310,12 @@ By selecting the measurement icon, the measurement tool can be turned on to enab

Linear Measurement

-- Distance between points + +* Distance between points

Area Measurement

-- Distance around a point + +* Distance around a point
From 72b82b5ca38ba5ff7c0099424029b54a3ed4b732 Mon Sep 17 00:00:00 2001 From: Regina Date: Mon, 20 Mar 2017 15:19:47 -0500 Subject: [PATCH 08/26] user documentation updated --- docs/User Documentation.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/User Documentation.md b/docs/User Documentation.md index 41f532e..e5956d6 100644 --- a/docs/User Documentation.md +++ b/docs/User Documentation.md @@ -550,15 +550,15 @@ In the example below, it shows how the search was applied and how each data type To further filter a search, a geo-spatial filter can be applied by drawing a shape on a targeted area on a map or selecting one of the pre-defined boundaries from the Select Location section of the panel. Select Location has two geo-spatial options called WITHIN and NEAR (see images below).

-
+

Figure 11a. WITHIN Boundary


-
+

Figure 11b. NEAR Me


-
+

Figure 11c. NEAR Marker

From 23f167e76c4dac9b0d2d2988c69af2b64e84b30c Mon Sep 17 00:00:00 2001 From: Regina Date: Mon, 20 Mar 2017 15:22:09 -0500 Subject: [PATCH 09/26] Update due to GFM --- docs/Administration.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/Administration.md b/docs/Administration.md index 81c3e09..00e876a 100644 --- a/docs/Administration.md +++ b/docs/Administration.md @@ -1,8 +1,8 @@ -#Administration -### Overview +

Administration

+

Overview

Administration has the authority to manage users’ responsibilities; as well as provide arrangements and tasks needed to control the users operation. They also allow access to specified functionalities.

-The Administration has the authority to:
+

The Administration has the authority to:


- Add a new User - Remove a User From 3e17a01b7b18530a6f4f523d9d322e365696132d Mon Sep 17 00:00:00 2001 From: Regina Date: Mon, 20 Mar 2017 16:52:43 -0500 Subject: [PATCH 10/26] Updating the document to GFM --- docs/OpenGrid API.md | 282 ++++++++++++++++++++++++------------------- 1 file changed, 157 insertions(+), 125 deletions(-) diff --git a/docs/OpenGrid API.md b/docs/OpenGrid API.md index b2b5459..f3dc01c 100644 --- a/docs/OpenGrid API.md +++ b/docs/OpenGrid API.md @@ -72,14 +72,14 @@ calling /users/token as described in Section 1.1.1.

  • HTTP 403 is returned when current user does not have appropriate permissions to access a requested resource. This error code is also returned when the authentication database is unavailable.

  • HTTP 200 is returned for any successful request or any handled exceptions. To detect a failure, look for an error object. In case of failure, an error object is returned with the format below:
  • -
    
    -					{
    -						"error": {
    -									"code": "<error code>",
    -									"message": "<error message>"
    -										}
    -							}
    -							 
    + + ``` + { + "error": { + "code": "<error code>", "message": "<error message>" + } + } + ```
    • where HTTP <error code> is a code corresponding to the error that occurred and HTTP <error message> is a description of the error.
    • @@ -101,19 +101,23 @@ calling /users/token as described in Section 1.1.1.

      Sample Request

      Request Payload:

      + ``` { "username": "admin", "password": "xxx" } ``` +

      Sample Response

      The authentication token is returned, the authentication token consist of three sections separated by dots(.): HEADER.PAYLOAD.SIGNATURE; the key X-AUTH-TOKEN is appended to the response header below:

      + ``` X-AUTH-TOKEN: eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTQzOTMzNjQwNywianRpIjoiYWRtaW4iLCJyb2xlcyI6Im9wZW5ncmlkX2FkbWlucyIsImZuYW1lIjoiT3BlbkdyaWQiLCJsbmFtZSI6IkFkbWluIn0.nShqceUs52ykIxu0RBRp4vZ8zaQqfdZ2haZn8AWMqyq5GJHRQkddoOaaLtKABktr32C0zha1pMJJBrjuYoPHIQ ``` +

      This token can be parsed using the jwt_decode JavaScript Web Token library (See https://github.com/auth0/jwt-decode)

      @@ -902,7 +906,7 @@ The maximum number of records to return; If this parameter is not specified, no } ``` -## 1.1.9 /datasets/{dataset_id}/query +

      1.1.9 /datasets/{dataset_id}/query

      **Method**

      GET/datasets/{dataset_id}/query

      @@ -934,7 +938,6 @@ It is recommended that this value be URL encoded.

      - n Integer @@ -947,24 +950,26 @@ It is recommended that this value be URL encoded.

      Optional parameters that can be passed depending on service capabilities. This value needs to be encoded for the call to work as expected. "geoFilter" is an attribute supported as of Release 1.1.0 and should be a valid geoJson geometry value.

      -

      Sample Request

      -``` + ``` /datasets/twitter/query?q={"$and":[{"text":{"$regex":"happy"}}]}&n=1&opts={"geoFilter":{"type":"MultiPolygon","coordinates":[[[[-87.63304710388184,41.89278978584501],[-87.61206150054932,41.89278978584501],[-87.61206150054932,41.88140002416609],[-87.63304710388184,41.88140002416609],[-87.63304710388184,41.89278978584501]]]]}} ```

      Or when URL encoded:

      + ``` /datasets/twitter/query?q=%7B%22$and%22:%5B%7B%22text%22:%7B%22$regex%22:%22happy%22%7D%7D%5D%7D&n=6000&opts=%7B%22geoFilter%22:%7B%22type%22:%22MultiPolygon%22,%22coordinates%22:%5B%5B%5B%5B-87.63304710388184,41.89278978584501%5D,%5B-87.61206150054932,41.89278978584501%5D,%5B-87.61206150054932,41.88140002416609%5D,%5B-87.63304710388184,41.88140002416609%5D,%5B-87.63304710388184,41.89278978584501%5D%5D%5D%5D%7D%7D ``` +

      Sample Response

      + ``` { "type" : "FeatureCollection", @@ -1089,15 +1094,19 @@ It is recommended that this value be URL encoded. } ``` -**Method** + + +

      Method

      POST/datasets/{dataset_id}/query

      Execute a query against a specific dataset. The POST method is now supported to accomodate bigger request payloads primarily due to geo-spatial filters (for OpenGrid services that support geo-spatial filtering)

      Request Parameters

      -The parameter names are the same as the ones on the GET method above except that they should be passed as form data. See Sample request payload below. + +

      The parameter names are the same as the ones on the GET method above except that they should be passed as form data. See Sample request payload below.

      Sample Request Payload

      + ``` ------WebKitFormBoundaryOvluSdchMLVGg7rd Content-Disposition: form-data; name="q" @@ -1110,15 +1119,34 @@ Content-Disposition: form-data; name="n" ------WebKitFormBoundaryOvluSdchMLVGg7rd Content-Disposition: form-data; name="opts" -{"geoFilter":{"type":"MultiPolygon","coordinates":[[[[-87.65630722045898,41.89850786255543],[-87.60510921478273,41.89850786255543],[-87.60510921478273,41.87588812018588],[-87.65630722045898,41.87588812018588],[-87.65630722045898,41.89850786255543]]]]}} + { + "geoFilter": + {"type": + "MultiPolygon", + "coordinates":[ + [ + [ + [-87.65630722045898,41.89850786255543], + [-87.60510921478273,41.89850786255543], + [-87.60510921478273,41.87588812018588], + [-87.65630722045898,41.87588812018588], + [-87.65630722045898,41.89850786255543] + ] + ] + ] + } + } ------WebKitFormBoundaryOvluSdchMLVGg7rd-- ``` +

      Sample Response

      -See sample response for the GET method above. +

      See sample response for the GET method above.

      -## 1.1.10 /queries -**Method** +

      1.1.10 /queries

      + +

      Method

      +

      GET /queries

      Return list of all queries that user has access to. A user has access to all queries he or she has created and those shared with his groups or shared with him directly by other users.

      @@ -1165,11 +1193,14 @@ maximum number of records to return; If this parameter is not specified, no reco

      Sample Request

      + ``` /queries?n=1 ``` +

      Sample Response

      + ``` [ {"_id" : @@ -1221,42 +1252,42 @@ maximum number of records to return; If this parameter is not specified, no reco ``` { "o":{ - "name":"Tweets By Bud", - "owner":"user1", + "name":"Tweets By Bud", + "owner":"user1", "spec":[ - { - "dataSetId":"twitter", - "filters":{ - "condition":"AND", - "rules":[ - { - "id":"screenName", - "field":"screenName", - "type":"string", - "input":"text", - "operator":"contains", - "value":"bud" - } - ] - }, - "rendition":{ - "color":"#DC143C", - "opacity":"85", - "size":"6" - } + { + "dataSetId":"twitter", + "filters":{ + "condition":"AND", + "rules":[ + { + "id":"screenName", + "field":"screenName", + "type":"string", + "input":"text", + "operator":"contains", + "value":"bud" + } + ] + }, + "rendition":{ + "color":"#DC143C", + "opacity":"85", + "size":"6" + } } ], - "sharedWith":{ - "users":[], - "groups":[] - }, - "isCommon":false, - "autoRefresh":false, - "refreshInterval":"30", - "geoFilter":{ - "boundaryType":"within", - "boundary": "" - } + "sharedWith":{ + "users":[], + "groups":[] + }, + "isCommon":false, + "autoRefresh":false, + "refreshInterval":"30", + "geoFilter":{ + "boundaryType":"within", + "boundary": "" + } } } @@ -1269,44 +1300,44 @@ maximum number of records to return; If this parameter is not specified, no reco "name" : "Tweets By Bud", "owner" : "user1", "spec" : [{ - "dataSetId" : "twitter", - "filters" : { - "condition" : "AND", - "rules" : [ { - "id" : "screenName", - "field" : "screenName", - "type" : "string", - "input" : "text", - "operator" : "contains", - "value" : "bud" - }] - }, - "rendition" : { - "color" : "#DC143C", - "opacity" : "85", - "size" : "6" - } - }], - "sharedWith" : { - "users" : [ ], - "groups" : [ ] - }, + "dataSetId" : "twitter", + "filters" : { + "condition" : "AND", + "rules" : [ { + "id" : "screenName", + "field" : "screenName", + "type" : "string", + "input" : "text", + "operator" : "contains", + "value" : "bud" + } ] + }, + "rendition" : { + "color" : "#DC143C", + "opacity" : "85", + "size" : "6" + } + }], + "sharedWith" : { + "users" : [ ], + "groups" : [ ] + }, "isCommon" : false, "autoRefresh" : false, "refreshInterval" : "30", "geoFilter" : - { - "boundaryType" : "within", - "boundary" : "" - }, + { + "boundaryType" : "within", + "boundary" : "" + }, "_id" : { "$oid" : "55df5ec39900ec81a481b0f6"} } ``` -## 1.1.11 /queries/{query_id} +

      1.1.11 /queries/{query_id}

      -**Method** +

      Method

      GET /queries/{query_id}

      Return a single query given a query’s internal id.

      @@ -1327,33 +1358,33 @@ maximum number of records to return; If this parameter is not specified, no reco ``` [ {"_id": - { "$oid" : "5582f831a3db5f4190e4707a"}, + { "$oid" : "5582f831a3db5f4190e4707a"}, "name" : "Weather Records for 60601", "owner" : "jsmith", "spec" : [{ - "dataSetId" : "weather", - "filters" : { - "condition" : "AND", - "rules" : - [{ - "id" : "zipcode", - "field" : "zipcode", - "type" : "string", - "input" : "text", - "operator" : "equal", - "value" : "60601" - }] + "dataSetId" : "weather", + "filters" : { + "condition" : "AND", + "rules" : + [{ + "id" : "zipcode", + "field" : "zipcode", + "type" : "string", + "input" : "text", + "operator" : "equal", + "value" : "60601" + }] }, - "rendition": { - "color" : "#DC143C", - "opacity" : 85, - "size" : 6 - } - }], + "rendition": { + "color" : "#DC143C", + "opacity" : 85, + "size" : 6 + } + }], "sharedWith" : { - "users" : [ ], - "groups" : [ ] - }, + "users" : [ ], + "groups" : [ ] + }, "isCommon" : true} ] ``` @@ -1381,36 +1412,37 @@ maximum number of records to return; If this parameter is not specified, no reco "name" : "Tweets on coupon", "owner" : "user1", "spec" : [{ - "dataSetId" : "twitter", - "filters": { - "condition" : "AND", - "rules" : [{ - "id" : "text", - "field" : "text", - "type" : "string", - "input" : "text", - "operator" : "contains", - "value" : "coupon" - }]}, - "rendition" : { - "color" : "#DC143C", - "opacity" : "85", - "size" : "6" - } - }], + "dataSetId" : "twitter", + "filters": { + "condition" : "AND", + "rules" : [{ + "id" : "text", + "field" : "text", + "type" : "string", + "input" : "text", + "operator" : "contains", + "value" : "coupon" + }] + }, + "rendition" : { + "color" : "#DC143C", + "opacity" : "85", + "size" : "6" + } + }], "sharedWith" : { - "users" : [ ], - "groups" : [ ] - }, + "users" : [ ], + "groups" : [ ] + }, "isCommon" : false, "autoRefresh" : false, "refreshInterval" : "30", "_id" : {"$oid" : "55c52cf6c4aa31b24b04d620"}, "geoFilter" : { - "boundaryType" : "within", - "boundary" : "" - } - } + "boundaryType" : "within", + "boundary" : "" + } + } ```

      DELETE /queries/{query_id}

      From 3f6eed30d37ed447a53b1417fa072b6a7f93e154 Mon Sep 17 00:00:00 2001 From: Regina Date: Mon, 20 Mar 2017 17:07:53 -0500 Subject: [PATCH 11/26] updating documentation GFM --- docs/OpenGrid API.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/OpenGrid API.md b/docs/OpenGrid API.md index f3dc01c..0171ca1 100644 --- a/docs/OpenGrid API.md +++ b/docs/OpenGrid API.md @@ -73,13 +73,14 @@ calling /users/token as described in Section 1.1.1.


    • HTTP 200 is returned for any successful request or any handled exceptions. To detect a failure, look for an error object. In case of failure, an error object is returned with the format below:
    - ``` - { - "error": { - "code": "<error code>", "message": "<error message>" +``` + { + "error": { + "code": "<error code>", + "message": "<error message>" } - } - ``` + } +```
    • where HTTP <error code> is a code corresponding to the error that occurred and HTTP <error message> is a description of the error.
    • From 67a07a029267272ecdb248c5bf45492120e31daf Mon Sep 17 00:00:00 2001 From: Regina Date: Mon, 20 Mar 2017 17:21:50 -0500 Subject: [PATCH 12/26] Update OpenGrid API.md --- docs/OpenGrid API.md | 207 +++++++++++++++++++++---------------------- 1 file changed, 101 insertions(+), 106 deletions(-) diff --git a/docs/OpenGrid API.md b/docs/OpenGrid API.md index 0171ca1..f33dad8 100644 --- a/docs/OpenGrid API.md +++ b/docs/OpenGrid API.md @@ -956,140 +956,137 @@ It is recommended that this value be URL encoded.

      Sample Request

      - ``` +``` ``` /datasets/twitter/query?q={"$and":[{"text":{"$regex":"happy"}}]}&n=1&opts={"geoFilter":{"type":"MultiPolygon","coordinates":[[[[-87.63304710388184,41.89278978584501],[-87.61206150054932,41.89278978584501],[-87.61206150054932,41.88140002416609],[-87.63304710388184,41.88140002416609],[-87.63304710388184,41.89278978584501]]]]}} ```

      Or when URL encoded:

      - ``` /datasets/twitter/query?q=%7B%22$and%22:%5B%7B%22text%22:%7B%22$regex%22:%22happy%22%7D%7D%5D%7D&n=6000&opts=%7B%22geoFilter%22:%7B%22type%22:%22MultiPolygon%22,%22coordinates%22:%5B%5B%5B%5B-87.63304710388184,41.89278978584501%5D,%5B-87.61206150054932,41.89278978584501%5D,%5B-87.61206150054932,41.88140002416609%5D,%5B-87.63304710388184,41.88140002416609%5D,%5B-87.63304710388184,41.89278978584501%5D%5D%5D%5D%7D%7D ``` -

      Sample Response

      - ``` { "type" : "FeatureCollection", "features" : - [ - { - "type": "Feature", - "properties": - { - "_id" : {"$oid" : "556e6f18aef407e1dc98685e"}, - "date" : "05/02/2012 8:24 AM", - "screenName" : "DeeeEmmm", - "text" : "Just talked to bleep last nyt.... Felt happy, but sad in a lot of ways....", - "city" : "Chicago, IL", - "bio" : "I'm the female version of Ari Gold!", - "lat" : 41.84770456, - "long" : -87.8521837, - "hashtags" : "" - }, - "geometry": - { - "type": "Point", - "coordinates": [-87.8521837,41.84770456] - }, - "autoPopup": false + [ + { + "type": "Feature", + "properties": + { + "_id" : {"$oid" : "556e6f18aef407e1dc98685e"}, + "date" : "05/02/2012 8:24 AM", + "screenName" : "DeeeEmmm", + "text" : "Just talked to bleep last nyt.... Felt happy, but sad in a lot of ways....", + "city" : "Chicago, IL", + "bio" : "I'm the female version of Ari Gold!", + "lat" : 41.84770456, + "long" : -87.8521837, + "hashtags" : "" + }, + "geometry": + { + "type": "Point", + "coordinates": [-87.8521837,41.84770456] + }, + "autoPopup": false } ], "meta": { "view": - { - "id" : "twitter", - "displayName" : "Twitter", + { + "id" : "twitter", + "displayName" : "Twitter", "options": - {"rendition" : - { - "icon" : "default", - "color" : "#001F7A", - "fillColor" : "#00FFFF", - "opacity" : 85, - "size" : 6 - } + {"rendition" : + { + "icon" : "default", + "color" : "#001F7A", + "fillColor" : "#00FFFF", + "opacity" : 85, + "size" : 6 + } }, "columns" : - [ - { - "id" : "_id", - "displayName" : "ID", - "dataType" : "string", - "filter" : false, - "popup" : false, - "list" : false - }, - { - "id" : "date", - "displayName" : "Date", - "dataType" : "date", - "filter" : true, - "popup" : true, - "list" : true, - "sortOrder" : 1 - }, - { - "id" : "screenName", - "displayName" : "Screen Name", - "dataType" : "string", - "filter" : true, - "popup" : true, - "list" : true, - "sortOrder" : 2, - "groupBy" : true + [ + { + "id" : "_id", + "displayName" : "ID", + "dataType" : "string", + "filter" : false, + "popup" : false, + "list" : false }, - { - "id" : "text", - "displayName" : "Text", - "dataType" : "string", - "filter" : true, - "popup" : true, - "list" : true, - "sortOrder" : 3 + { + "id" : "date", + "displayName" : "Date", + "dataType" : "date", + "filter" : true, + "popup" : true, + "list" : true, + "sortOrder" : 1 }, - { - "id" : "city", - "displayName" : "City", - "dataType" : "string", - "filter" : true, - "popup" : true, - "list" : true, - "sortOrder" : 4, - "groupBy" : true + { + "id" : "screenName", + "displayName" : "Screen Name", + "dataType" : "string", + "filter" : true, + "popup" : true, + "list" : true, + "sortOrder" : 2, + "groupBy" : true }, - { - "id" : "bio", - "displayName" : "Bio", - "dataType" : "string", - "sortOrder" : 5 + { + "id" : "text", + "displayName" : "Text", + "dataType" : "string", + "filter" : true, + "popup" : true, + "list" : true, + "sortOrder" : 3 + }, + { + "id" : "city", + "displayName" : "City", + "dataType" : "string", + "filter" : true, + "popup" : true, + "list" : true, + "sortOrder" : 4, + "groupBy" : true }, - { - "id" : "hashtags", - "displayName" : "Hashtags", - "dataType" : "string", - "sortOrder" : 6 + { + "id" : "bio", + "displayName" : "Bio", + "dataType" : "string", + "sortOrder" : 5 }, - { - "id" : "lat", - "displayName" : "Latitude", - "dataType" : "float", - "list" : true, - "sortOrder" : 7 + { + "id" : "hashtags", + "displayName" : "Hashtags", + "dataType" : "string", + "sortOrder" : 6 }, - { - "id" : "long", - "displayName" : "Longitude", - "dataType" : "float", - "list" : true, - "sortOrder" : 8 - } - ] + { + "id" : "lat", + "displayName" : "Latitude", + "dataType" : "float", + "list" : true, + "sortOrder" : 7 + }, + { + "id" : "long", + "displayName" : "Longitude", + "dataType" : "float", + "list" : true, + "sortOrder" : 8 + } + ] } } } @@ -1161,7 +1158,6 @@ Content-Disposition: form-data; name="opts" Value Description - q @@ -1176,7 +1172,6 @@ It is recommended that this value be URL encoded.

      - n From 2d9e9d5218b24c98fb51259726dbde20966aa013 Mon Sep 17 00:00:00 2001 From: Regina Date: Tue, 21 Mar 2017 14:24:49 -0500 Subject: [PATCH 13/26] Update API to GFM --- docs/OpenGrid API.md | 859 +++++++++++++++++++++---------------------- 1 file changed, 429 insertions(+), 430 deletions(-) diff --git a/docs/OpenGrid API.md b/docs/OpenGrid API.md index f33dad8..3f3a585 100644 --- a/docs/OpenGrid API.md +++ b/docs/OpenGrid API.md @@ -74,12 +74,12 @@ calling /users/token as described in Section 1.1.1.

    • HTTP 200 is returned for any successful request or any handled exceptions. To detect a failure, look for an error object. In case of failure, an error object is returned with the format below:
    ``` - { - "error": { - "code": "<error code>", - "message": "<error message>" - } - } + { + "error": { + "code": "", + "message": "" + } + } ```
      @@ -207,11 +207,12 @@ This token can be parsed using the jwt_decode JavaScript Web Token ``` { "id":null, - "o":{"userId":"test3", - "password":"testxxx", - "firstName":"Test", - "lastName":"Three", - "groups":[] + "o":{ + "userId":"test3", + "password":"testxxx", + "firstName":"Test", + "lastName":"Three", + "groups":[] } } ``` @@ -254,7 +255,7 @@ This token can be parsed using the jwt_decode JavaScript Web Token ``` { "_id": - {"$oid":"55b63708a3db5f292c533c7b"}, + {"$oid":"55b63708a3db5f292c533c7b"}, "userId":"TesterOne", "password":"test123", "firstName":"ABC Test", @@ -284,21 +285,22 @@ This token can be parsed using the jwt_decode JavaScript Web Token ``` { "id":{"$oid":"55ccaca15fc6c6bf8a807cf2"}, - "o":{ - "_id": - {"$oid":"55ccaca15fc6c6bf8a807cf2"}, - "userId":"twitterUser", - "password":"testxxx", - "firstName":"Twitter", - "lastName":"User", - "groups":[ - "opengrid_users_L1", - "opengrid_users_L2"] - } + "o":{ + "_id": + {"$oid":"55ccaca15fc6c6bf8a807cf2"}, + "userId":"twitterUser", + "password":"testxxx", + "firstName":"Twitter", + "lastName":"User", + "groups":[ + "opengrid_users_L1", + "opengrid_users_L2"] + } } ```

      Sample Response

      + ``` { "userId":"test3", @@ -308,7 +310,7 @@ This token can be parsed using the jwt_decode JavaScript Web Token "groups":[ ] } ``` - +

      DELETE /user/{user_id}

      Delete a user given the user’s internal Id on the URL path.

      @@ -381,23 +383,22 @@ The maximum number of records to return; If this parameter is not specified, no /groups?q=%7B%7D&n=200 ``` -

      Sample Response

      ``` [ {"_id" : - {"$oid":"55c0c620a3db5f3058630eb3"}, + {"$oid":"55c0c620a3db5f3058630eb3"}, "groupId":"opengrid_users", "name":"OpenGrid Users", "description":"Group for all OpenGrid users", "enabled": true, "functions": [ - "Quick Search", - "Advanced Search"], + "Quick Search", + "Advanced Search"], "datasets": [ - "twitter", - "weather"] + "twitter", + "weather"] } ] ``` @@ -458,15 +459,15 @@ The maximum number of records to return; If this parameter is not specified, no "description" : "Group for all OpenGrid users", "enabled" : true, "functions" : [ - "Quick Search", - "Advanced Search"], + "Quick Search", + "Advanced Search"], "datasets" : [ - "twitter", - "weather"] + "twitter", + "weather"] } ] ``` - +

      PUT/groups/{group_id}

      Update a group (group-level attributes and members). Returns the updated group data, if successful.

      @@ -487,17 +488,18 @@ The maximum number of records to return; If this parameter is not specified, no ``` { - "id":{"$oid":"55c525c6c4aae748132f4d06"}, - "o":{"groupId":"opengrid_users_L2", - "name":"OpenGrid Users Level 2", - "description":"Users with access to weather data", - "enabled":true, - "isAdmin":false, - "functions":[ + "id":{"$oid":"55c525c6c4aae748132f4d06"}, + "o": { + "groupId":"opengrid_users_L2", + "name":"OpenGrid Users Level 2", + "description":"Users with access to weather data", + "enabled":true, + "isAdmin":false, + "functions":[ "Quick Search", "Advanced Search"], - "datasets":["weather"] - } + "datasets":["weather"] + } } ``` @@ -511,8 +513,8 @@ The maximum number of records to return; If this parameter is not specified, no "enabled" : true, "isAdmin" : false, "functions" : [ - "Quick Search", - "Advanced Search"], + "Quick Search", + "Advanced Search"], "datasets" : ["weather"] } ``` @@ -554,230 +556,229 @@ The maximum number of records to return; If this parameter is not specified, no "id" : "twitter", "displayName" : "Twitter", "options": {"rendition": - { - "icon" : "default", - "color" : "#001F7A", - "fillColor" : "#00FFFF", - "opacity" : 85, - "size" : 6 + { + "icon" : "default", + "color" : "#001F7A", + "fillColor" : "#00FFFF", + "opacity" : 85, + "size" : 6 + }, + "supportedOperators" : [ { + "dataType" : "string", + "operators" : [ + "equal", + "not_equal", + "in", + "begins_with", + "contains" + ] + } + ], + "quickSearch" : { + "enable" : true, + "triggerAlias" : "twe", + "triggerWord" : "tweet", + "hintCaption" : "Search Tweets", + "hintExample" : "tweet 'food'" + } + }, + "columns": + [ + { + "id" : "_id", + "displayName" : "ID", + "dataType" : "string", + "filter" : false, + "popup" : false, + "list" : false }, - "supportedOperators" : [ { - "dataType" : "string", - "operators" : [ - "equal", - "not_equal", - "in", - "begins_with", - "contains" - ] - } - ] - , - "quickSearch" : { - "enable" : true, - "triggerAlias" : "twe", - "triggerWord" : "tweet", - "hintCaption" : "Search Tweets", - "hintExample" : "tweet 'food'" - } - }, - "columns": - [ - { - "id" : "_id", + { + "id" : "date", + "displayName" : "Date", + "dataType" : "date", + "filter" : true, + "popup" : true, + "list" : true, + "sortOrder" : 1 + }, + { + "id": "screenName", + "displayName" : "Screen Name", + "dataType" : "string", + "filter" : true, + "popup" : true, + "list" : true, + "sortOrder" : 2, + "groupBy" : true, + "quickSearch" : true + }, + { + "id" : "text", + "displayName" : "Text", + "dataType" : "string", + "filter" : true, + "popup" : true, + "list" : true, + "sortOrder": 3, + "quickSearch" : true + }, + { + "id" : "city", + "displayName" : "City", + "dataType" : "string", + "filter" : true, + "popup" : true, + "list" : true, + "sortOrder" : 4, + "groupBy" : true, + "quickSearch" : true + }, + { + "id" :"bio", + "displayName" : "Bio", + "dataType" : "string", + "sortOrder" : 5 + }, + { + "id" : "hashtags", + "displayName" : "Hashtags", + "dataType" : "string", + "sortOrder" : 6 + }, + { + "id" : "lat", + "displayName" : "Latitude", + "dataType" : "float", + "list" : true, + "sortOrder" : 7 + }, + { + "id" : "long", + "displayName" : "Longitude", + "dataType" : "float", + "list" : true, + "sortOrder" : 8 + } + ] + }, + { + "id" : "weather", + "displayName" : "Weather", + "options" : {"rendition": + { + "icon" : "default", + "color" : "#8c2d04", + "fillColor" : "#fdae6b", + "opacity" : 85, + "size" : 6 + } + }, + "columns": + [ + { + "id": "_id", "displayName" : "ID", "dataType" : "string", "filter" : false, "popup" : false, "list" : false - }, - { - "id" : "date", - "displayName" : "Date", - "dataType" : "date", + }, + { + "id" : "temp", + "displayName" : "Temperature", + "dataType" : "float", "filter" : true, "popup" : true, "list" : true, "sortOrder" : 1 - }, - { - "id": "screenName", - "displayName" : "Screen Name", - "dataType" : "string", + }, + { + "id" : "windspeed", + "displayName" : "Wind Speed", + "dataType" : "float", "filter" : true, "popup" : true, "list" : true, - "sortOrder" : 2, - "groupBy" : true, - "quickSearch" : true - }, - { - "id" : "text", - "displayName" : "Text", + "sortOrder" : 2 + }, + { + "id" : "condition", + "displayName" : "Condition", "dataType" : "string", "filter" : true, "popup" : true, "list" : true, - "sortOrder": 3, - "quickSearch" : true - }, + "sortOrder" : 3 + }, { - "id" : "city", - "displayName" : "City", - "dataType" : "string", + "id" : "humidity", + "displayName" : "Humidity", + "dataType" : "float", "filter" : true, "popup" : true, "list" : true, - "sortOrder" : 4, - "groupBy" : true, - "quickSearch" : true - }, + "sortOrder" : 4 + }, { - "id" :"bio", - "displayName" : "Bio", - "dataType" : "string", + "id" : "precipIntensity", + "displayName" : "Precipitation Intensity", + "dataType" : "float", + "filter" : true, + "popup" : true, + "list" : true , "sortOrder" : 5 - }, + }, { - "id" : "hashtags", - "displayName" : "Hashtags", + "id" : "date", + "displayName" : "Date", + "dataType" : "date", + "filter" : true, + "popup" : true, + "list" : true, + "sortOrder" : 5 + }, + { + "id" : "zipcode", + "displayName" : "Zip Code", "dataType" : "string", - "sortOrder" : 6 - }, - { - "id" : "lat", - "displayName" : "Latitude", - "dataType" : "float", - "list" : true, - "sortOrder" : 7 - }, - { - "id" : "long", - "displayName" : "Longitude", - "dataType" : "float", + "filter" : true, + "popup" : true, "list" : true, - "sortOrder" : 8 - } - ] - }, - { - "id" : "weather", - "displayName" : "Weather", - "options" : {"rendition": - { - "icon" : "default", - "color" : "#8c2d04", - "fillColor" : "#fdae6b", - "opacity" : 85, - "size" : 6 - } - }, - "columns": - [ - { - "id": "_id", - "displayName" : "ID", - "dataType" : "string", - "filter" : false, - "popup" : false, - "list" : false - }, - { - "id" : "temp", - "displayName" : "Temperature", - "dataType" : "float", - "filter" : true, - "popup" : true, - "list" : true, - "sortOrder" : 1 - }, - { - "id" : "windspeed", - "displayName" : "Wind Speed", - "dataType" : "float", - "filter" : true, - "popup" : true, - "list" : true, - "sortOrder" : 2 - }, - { - "id" : "condition", - "displayName" : "Condition", - "dataType" : "string", - "filter" : true, - "popup" : true, - "list" : true, - "sortOrder" : 3 - }, - { - "id" : "humidity", - "displayName" : "Humidity", - "dataType" : "float", - "filter" : true, - "popup" : true, - "list" : true, - "sortOrder" : 4 + "sortOrder" : 6, + "values" : [ 60601 , 60602], + "groupBy" : true }, - { - "id" : "precipIntensity", - "displayName" : "Precipitation Intensity", - "dataType" : "float", - "filter" : true, - "popup" : true, - "list" : true , - "sortOrder" : 5 - }, - { - "id" : "date", - "displayName" : "Date", - "dataType" : "date", - "filter" : true, - "popup" : true, - "list" : true, - "sortOrder" : 5 - }, - { - "id" : "zipcode", - "displayName" : "Zip Code", - "dataType" : "string", - "filter" : true, - "popup" : true, - "list" : true, - "sortOrder" : 6, - "values" : [ 60601 , 60602], - "groupBy" : true - }, - { - "id" :"forecast", - "displayName" : "Today's Forecast", - "dataType" : "string", - "popup" : true, - "list" : true , - "sortOrder" : 7 + { + "id" :"forecast", + "displayName" : "Today's Forecast", + "dataType" : "string", + "popup" : true, + "list" : true , + "sortOrder" : 7 }, - { - "id" : "icon", - "displayName" : "Icon", - "dataType" : "graphic", - "popup" : true, - "sortOrder" : 7 + { + "id" : "icon", + "displayName" : "Icon", + "dataType" : "graphic", + "popup" : true, + "sortOrder" : 7 }, - { - "id" : "lat", - "displayName" : "Latitude", - "dataType" : "float", - "list" : true , - "sortOrder" : 8 + { + "id" : "lat", + "displayName" : "Latitude", + "dataType" : "float", + "list" : true , + "sortOrder" : 8 }, - { - "id" : "long", - "displayName" : "Longitude", - "dataType" : "float", - "list" : true, - "sortOrder" : 9 + { + "id" : "long", + "displayName" : "Longitude", + "dataType" : "float", + "list" : true, + "sortOrder" : 9 } - ] - } + ] + } ] ``` @@ -799,110 +800,110 @@ The maximum number of records to return; If this parameter is not specified, no ``` { - "id" : "twitter", - "displayName" : "Twitter", - "options": {"rendition": - { - "icon" : "default", - "color" : "#001F7A", - "fillColor" : "#00FFFF", - "opacity" : 85, - "size" : 6 - }, - "supportedOperators" : [ { + "id" : "twitter", + "displayName" : "Twitter", + "options": {"rendition": + { + "icon" : "default", + "color" : "#001F7A", + "fillColor" : "#00FFFF", + "opacity" : 85, + "size" : 6 + }, + "supportedOperators" : [ + { "dataType" : "string", - "operators" : [ - "equal", - "not_equal", - "in", - "begins_with", - "contains" - ] + "operators" : [ + "equal", + "not_equal", + "in", + "begins_with", + "contains" + ] } - ] - , + ], "quickSearch" : { - "enable" : true, - "triggerAlias" : "twe", - "triggerWord" : "tweet", - "hintCaption" : "Search Tweets", - "hintExample" : "tweet 'food'" - } - }, + "enable" : true, + "triggerAlias" : "twe", + "triggerWord" : "tweet", + "hintCaption" : "Search Tweets", + "hintExample" : "tweet 'food'" + } + }, "columns" : [ - { - "id" : "_id", - "displayName" : "ID", - "dataType" : "string", - "filter" : false, - "popup" : false, - "list" : false - }, - { - "id" : "date", - "displayName" : "Date", - "dataType" : "date", - "filter" : true, - "popup" : true, - "list" : true, - "sortOrder": 1 - }, - { - "id" : "screenName", - "displayName" : "Screen Name", - "dataType" : "string", - "filter" : true, - "popup" : true, - "list" : true, - "sortOrder" : 2, - "groupBy" : true - }, - { - "id" : "text", - "displayName" : "Text", - "dataType" : "string", - "filter" : true, - "popup" : true, - "list" : true, - "sortOrder" : 3 - }, - { - "id" : "city", - "displayName" : "City", - "dataType" : "string", - "filter" : true, - "popup" : true, - "list" : true , - "sortOrder" : 4, - "groupBy" : true - }, - { - "id" : "bio", - "displayName" : "Bio", - "dataType" : "string", - "sortOrder" : 5 - }, - { - "id" : "hashtags", - "displayName" : "Hashtags", - "dataType" : "string", - "sortOrder" : 6 - }, - { - "id" : "lat", - "displayName" : "Latitude", - "dataType" : "float", - "list" : true , - "sortOrder" : 7 - }, - { - "id" : "long", - "displayName" : "Longitude", - "dataType" : "float", - "list" : true, - "sortOrder" : 8 - } + { + "id" : "_id", + "displayName" : "ID", + "dataType" : "string", + "filter" : false, + "popup" : false, + "list" : false + }, + { + "id" : "date", + "displayName" : "Date", + "dataType" : "date", + "filter" : true, + "popup" : true, + "list" : true, + "sortOrder": 1 + }, + { + "id" : "screenName", + "displayName" : "Screen Name", + "dataType" : "string", + "filter" : true, + "popup" : true, + "list" : true, + "sortOrder" : 2, + "groupBy" : true + }, + { + "id" : "text", + "displayName" : "Text", + "dataType" : "string", + "filter" : true, + "popup" : true, + "list" : true, + "sortOrder" : 3 + }, + { + "id" : "city", + "displayName" : "City", + "dataType" : "string", + "filter" : true, + "popup" : true, + "list" : true , + "sortOrder" : 4, + "groupBy" : true + }, + { + "id" : "bio", + "displayName" : "Bio", + "dataType" : "string", + "sortOrder" : 5 + }, + { + "id" : "hashtags", + "displayName" : "Hashtags", + "dataType" : "string", + "sortOrder" : 6 + }, + { + "id" : "lat", + "displayName" : "Latitude", + "dataType" : "float", + "list" : true , + "sortOrder" : 7 + }, + { + "id" : "long", + "displayName" : "Longitude", + "dataType" : "float", + "list" : true, + "sortOrder" : 8 + } ] } ``` @@ -956,7 +957,7 @@ It is recommended that this value be URL encoded.

      Sample Request

      -``` ``` +``` /datasets/twitter/query?q={"$and":[{"text":{"$regex":"happy"}}]}&n=1&opts={"geoFilter":{"type":"MultiPolygon","coordinates":[[[[-87.63304710388184,41.89278978584501],[-87.61206150054932,41.89278978584501],[-87.61206150054932,41.88140002416609],[-87.63304710388184,41.88140002416609],[-87.63304710388184,41.89278978584501]]]]}} ``` @@ -995,102 +996,100 @@ It is recommended that this value be URL encoded. "autoPopup": false } ], - "meta": - { - "view": - { - "id" : "twitter", - "displayName" : "Twitter", - "options": - {"rendition" : - { - "icon" : "default", - "color" : "#001F7A", - "fillColor" : "#00FFFF", - "opacity" : 85, - "size" : 6 + { + "view": + { + "id" : "twitter", + "displayName" : "Twitter", + "options": + {"rendition" : + { + "icon" : "default", + "color" : "#001F7A", + "fillColor" : "#00FFFF", + "opacity" : 85, + "size" : 6 } }, - "columns" : - [ - { - "id" : "_id", - "displayName" : "ID", - "dataType" : "string", - "filter" : false, - "popup" : false, - "list" : false - }, - { - "id" : "date", - "displayName" : "Date", - "dataType" : "date", - "filter" : true, - "popup" : true, - "list" : true, - "sortOrder" : 1 - }, - { - "id" : "screenName", - "displayName" : "Screen Name", - "dataType" : "string", - "filter" : true, - "popup" : true, - "list" : true, - "sortOrder" : 2, - "groupBy" : true - }, - { - "id" : "text", - "displayName" : "Text", - "dataType" : "string", - "filter" : true, - "popup" : true, - "list" : true, - "sortOrder" : 3 - }, - { - "id" : "city", - "displayName" : "City", - "dataType" : "string", - "filter" : true, - "popup" : true, - "list" : true, - "sortOrder" : 4, - "groupBy" : true - }, - { - "id" : "bio", - "displayName" : "Bio", - "dataType" : "string", - "sortOrder" : 5 - }, - { - "id" : "hashtags", - "displayName" : "Hashtags", - "dataType" : "string", - "sortOrder" : 6 - }, - { - "id" : "lat", - "displayName" : "Latitude", - "dataType" : "float", - "list" : true, - "sortOrder" : 7 - }, - { - "id" : "long", - "displayName" : "Longitude", - "dataType" : "float", - "list" : true, - "sortOrder" : 8 - } - ] - } + "columns" : + [ + { + "id" : "_id", + "displayName" : "ID", + "dataType" : "string", + "filter" : false, + "popup" : false, + "list" : false + }, + { + "id" : "date", + "displayName" : "Date", + "dataType" : "date", + "filter" : true, + "popup" : true, + "list" : true, + "sortOrder" : 1 + }, + { + "id" : "screenName", + "displayName" : "Screen Name", + "dataType" : "string", + "filter" : true, + "popup" : true, + "list" : true, + "sortOrder" : 2, + "groupBy" : true + }, + { + "id" : "text", + "displayName" : "Text", + "dataType" : "string", + "filter" : true, + "popup" : true, + "list" : true, + "sortOrder" : 3 + }, + { + "id" : "city", + "displayName" : "City", + "dataType" : "string", + "filter" : true, + "popup" : true, + "list" : true, + "sortOrder" : 4, + "groupBy" : true + }, + { + "id" : "bio", + "displayName" : "Bio", + "dataType" : "string", + "sortOrder" : 5 + }, + { + "id" : "hashtags", + "displayName" : "Hashtags", + "dataType" : "string", + "sortOrder" : 6 + }, + { + "id" : "lat", + "displayName" : "Latitude", + "dataType" : "float", + "list" : true, + "sortOrder" : 7 + }, + { + "id" : "long", + "displayName" : "Longitude", + "dataType" : "float", + "list" : true, + "sortOrder" : 8 + } + ] + } } } - ``` From 6b3ecf657a66ce4d6ea48c6c05c4d4197b7de1d0 Mon Sep 17 00:00:00 2001 From: Regina Date: Tue, 21 Mar 2017 14:45:06 -0500 Subject: [PATCH 14/26] Update to API for GFM --- docs/OpenGrid API.md | 160 +++++++++++++++++++++---------------------- 1 file changed, 79 insertions(+), 81 deletions(-) diff --git a/docs/OpenGrid API.md b/docs/OpenGrid API.md index 3f3a585..d3b6636 100644 --- a/docs/OpenGrid API.md +++ b/docs/OpenGrid API.md @@ -1195,7 +1195,6 @@ maximum number of records to return; If this parameter is not specified, no reco

      Sample Response

      - ``` [ {"_id" : @@ -1203,36 +1202,36 @@ maximum number of records to return; If this parameter is not specified, no reco "name" : "Weather Records for 60601", "owner" : "jsmith", "spec" : - [ - { - "dataSetId" : "weather", - "filters" : { - "condition" : "AND", - "rules" : [ - { - "id" : "zipcode", - "field" : "zipcode", - "type" : "string", - "input" : "text", - "operator" : "equal", - "value" : "60601" - } - ] - }, - "rendition" : { - "color" : "#DC143C", - "opacity" : 85, - "size" : 6 - } + [ + { + "dataSetId" : "weather", + "filters" : { + "condition" : "AND", + "rules" : [ + { + "id" : "zipcode", + "field" : "zipcode", + "type" : "string", + "input" : "text", + "operator" : "equal", + "value" : "60601" + } + ] + }, + "rendition" : { + "color" : "#DC143C", + "opacity" : 85, + "size" : 6 + } } - ], - "sharedWith" : - { - "users" : [ ], - "groups" : [ ] - }, - "isCommon" : true - } + ], + "sharedWith" : + { + "users" : [ ], + "groups" : [ ] + }, + "isCommon" : true + } ] ``` @@ -1246,46 +1245,45 @@ maximum number of records to return; If this parameter is not specified, no reco ``` { - "o":{ + "o":{ "name":"Tweets By Bud", "owner":"user1", "spec":[ - { - "dataSetId":"twitter", - "filters":{ - "condition":"AND", - "rules":[ - { - "id":"screenName", - "field":"screenName", - "type":"string", - "input":"text", - "operator":"contains", - "value":"bud" - } - ] - }, - "rendition":{ - "color":"#DC143C", - "opacity":"85", - "size":"6" - } - } - ], - "sharedWith":{ - "users":[], - "groups":[] - }, - "isCommon":false, - "autoRefresh":false, - "refreshInterval":"30", - "geoFilter":{ - "boundaryType":"within", - "boundary": "" - } + { + "dataSetId":"twitter", + "filters":{ + "condition":"AND", + "rules":[ + { + "id":"screenName", + "field":"screenName", + "type":"string", + "input":"text", + "operator":"contains", + "value":"bud" + } + ] + }, + "rendition":{ + "color":"#DC143C", + "opacity":"85", + "size":"6" + } + } + ], + "sharedWith":{ + "users":[], + "groups":[] + }, + "isCommon":false, + "autoRefresh":false, + "refreshInterval":"30", + "geoFilter":{ + "boundaryType":"within", + "boundary": "" + } } } - ```

      Sample Response

      @@ -1352,12 +1350,12 @@ maximum number of records to return; If this parameter is not specified, no reco ``` [ - {"_id": - { "$oid" : "5582f831a3db5f4190e4707a"}, - "name" : "Weather Records for 60601", - "owner" : "jsmith", - "spec" : [{ - "dataSetId" : "weather", + {"_id": + { "$oid" : "5582f831a3db5f4190e4707a"}, + "name" : "Weather Records for 60601", + "owner" : "jsmith", + "spec" : [{ + "dataSetId" : "weather", "filters" : { "condition" : "AND", "rules" : @@ -1368,13 +1366,13 @@ maximum number of records to return; If this parameter is not specified, no reco "input" : "text", "operator" : "equal", "value" : "60601" - }] - }, + }] + }, "rendition": { "color" : "#DC143C", "opacity" : 85, "size" : 6 - } + } }], "sharedWith" : { "users" : [ ], @@ -1417,25 +1415,25 @@ maximum number of records to return; If this parameter is not specified, no reco "input" : "text", "operator" : "contains", "value" : "coupon" - }] - }, + }] + }, "rendition" : { "color" : "#DC143C", "opacity" : "85", "size" : "6" - } + } }], "sharedWith" : { "users" : [ ], "groups" : [ ] - }, + }, "isCommon" : false, "autoRefresh" : false, "refreshInterval" : "30", "_id" : {"$oid" : "55c52cf6c4aa31b24b04d620"}, - "geoFilter" : { - "boundaryType" : "within", - "boundary" : "" + "geoFilter" : { + "boundaryType" : "within", + "boundary" : "" } } ``` From a3624178f9981d1397d149074bba3b28fbf95433 Mon Sep 17 00:00:00 2001 From: Regina Date: Tue, 21 Mar 2017 15:14:10 -0500 Subject: [PATCH 15/26] Update notice.md for GFM --- notice.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/notice.md b/notice.md index 46d235b..efa6145 100644 --- a/notice.md +++ b/notice.md @@ -1,42 +1,71 @@

      NOTICE

      + #### APACHE -NOTICE file related to section 4d of the -[Apache License Version 2.0.](http://www.apache.org/) Providing presentation and/or application layer support. + +NOTICE file related to section 4d of the [Apache License Version 2.0.](http://www.apache.org/) Providing presentation and/or application layer support. +

      In the case of:

      -* [chroma-colors](http://colorbrewer2.org/)
      + +* [chroma-colors](http://colorbrewer2.org/) + * [esri-leaflet](https://github.com/Esri/esri-leaflet) + * [Jackson-JAX-RS-providers for JSON]( https://github.com/FasterXML/jackson-jaxrs-providers) + * [jjwt]( https://github.com/jwtk/jjwt) + * [Spring Framwork]( http://projects.spring.io/spring-framework/) + * [MongoDB-Drivers](https://www.mongodb.org/about/licensing/) -Copyright © 2004 The Apache Software Foundation
      +Copyright © 2004 The Apache Software Foundation +

      This product includes software developed under Apache as part of the OpenGrid Project.
      Copyright © 2015, City of Chicago - OpenGrid Project

      #### MIT + Notice file related to [MIT License](https://opensource.org/licenses/MIT). Providing presentation and/or application layer support. +

      In the case of:

      + * [bootbox](http://bootboxjs.com/) + * [bootstrap](http://getbootstrap.com/) + * [bootstrap-table](https://github.com/wenzhixin/bootstrap-table/) + * [chart.js](http://www.chartjs.org/) + * [CSS font-awesome](http://fontawesome.io) + * [HTML5 Shiv](https://github.com/afarkas/html5shiv) + * [moment.js](http://momentjs.com/) + * [respond.js](https://github.com/scottjehl/Respond) + * [jsPDF](https://parall.ax/products/jspdf) + * [jwt-decode](https://github.com/auth0/jwt-decode) + * [JQuery](https://jquery.com/) + * [jQuery dragtable plugin](https://github.com/akottr/dragtable) + * [jQuery-extendext](https://github.com/mistic100/jQuery.extendext) - * [jQuery-QueryBuilder](https://github.com/mistic100/jQuery-QueryBuilder) - * [jQuery tableExport plugin](https://github.com/hhurz/tableExport.jquery.plugin) + + * [jQuery-QueryBuilder](https://github.com/mistic100/jQuery-QueryBuilder) + + * [jQuery tableExport plugin](https://github.com/hhurz/tableExport.jquery.plugin) + * [jQuery-tmpl](https://github.com/KanbanSolutions/jquery-tmpl) + * [jQuery-timer](http://jquerytimer.com/) * [jQuery-UI](https://jqueryui.com/) + * [leaflet](http://leafletjs.com/) * [leaflet-EasyButton](https://github.com/CliffCloud/Leaflet.EasyButton) * [leaflet-locatecontrol](https://github.com/domoritz/leaflet-locatecontrol) @@ -47,7 +76,9 @@ Notice file related to [MIT License](https://opensource.org/licenses/MIT). Prov

      This project includes software developed under MIT Licensing as part of OpenGrid.
      Copyright © 2015, City of Chicago - OpenGrid Project

      #### BSD + Notice file corresponding to section 3 of the [BSD License](https://opensource.org/licenses/BSD-2-Clause). Providing presentation and/or application layer support. +

      In the case of:

      * [chroma.js](https://github.com/gka/chroma.js)
      * [leaflet fullscreen](https://github.com/Leaflet/Leaflet.fullscreen)
      @@ -56,41 +87,55 @@ Notice file corresponding to section 3 of the [BSD License](https://opensource.o

      This product includes software developed as part of the OpenGrid Project.
      All rights reserved.
      Copyright © 2015, City Of Chicago - OpenGrid

      #### ISC + NOTICE file corresponding to [ISC License](https://opensource.org/licenses/ISC). Providing presentation and/or application layer support. +

      In the case of:

      + * [leaflet.ZoomBox](https://github.com/consbio/Leaflet.ZoomBox)

      This product includes software developed as part of the OpenGrid Project.
      Copyright © 2015, City of Chicago - OpenGrid

      #### GNU + NOTICE FILE in relation to [General Public License](http://www.gnu.org/licenses/).Providing presentation and/or application layer support. +

      In the case of:

      * [MongoDB-Server](https://www.mongodb.org/about/licensing/) * [HTML5 Shiv](https://github.com/afarkas/html5shiv) Copyright © 2007 [Free Software Foundation](http://fsf.org/), Inc. +

      This product includes software developed as part of the OpenGrid Project.
      Copyright © 2015, City Of Chicago - OpenGrid

      #### EDL/EPL + NOTICE file corresponding to [Eclispe Distribution License](https://eclipse.org/org/documents/edl-v10.php) AND/OR [Eclipse Public License](https://eclipse.org/org/documents/epl-v10.php). Providing presentation and/or application layer support. +

      In the case of:

      * [Hibernate JPA API](https://github.com/hibernate/hibernate-jpa-api) * [JUnit](https://github.com/junit-team/junit) * [jQuery dragtable plugin](https://github.com/akottr/dragtable) - Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors.
      All rights reserved. +

      This product includes software developed as part of the OpenGrid Project.
      Copyright © 2015, City of Chicago - OpenGrid

      #### JSON + NOTICE file corresponding to [JSON License](http://www.json.org/license.html). Providing presentation and/or application layer support. +

      In the case of:

      + * [JSON-Java](https://github.com/douglascrockford/JSON-java) Copyright (c) 2002 JSON.org +

      This product includes software developed as part of the OpenGrid Project.
      Copyright © 2015, City of Chicago - OpenGrid

      + +Back to top From 34064279c0682389923d203fced25026270d0653 Mon Sep 17 00:00:00 2001 From: Regina Date: Tue, 21 Mar 2017 15:19:26 -0500 Subject: [PATCH 16/26] Update notice.md for GFM --- notice.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/notice.md b/notice.md index efa6145..3bdbc75 100644 --- a/notice.md +++ b/notice.md @@ -80,8 +80,9 @@ Notice file related to [MIT License](https://opensource.org/licenses/MIT). Prov Notice file corresponding to section 3 of the [BSD License](https://opensource.org/licenses/BSD-2-Clause). Providing presentation and/or application layer support.

      In the case of:

      -* [chroma.js](https://github.com/gka/chroma.js)
      -* [leaflet fullscreen](https://github.com/Leaflet/Leaflet.fullscreen)
      + +* [chroma.js](https://github.com/gka/chroma.js) +* [leaflet fullscreen](https://github.com/Leaflet/Leaflet.fullscreen) * [Leaflet heat](https://github.com/Leaflet/Leaflet.heat)

      This product includes software developed as part of the OpenGrid Project.
      All rights reserved.
      Copyright © 2015, City Of Chicago - OpenGrid

      @@ -102,10 +103,12 @@ NOTICE file corresponding to [ISC License](https://opensource.org/licenses/ISC). NOTICE FILE in relation to [General Public License](http://www.gnu.org/licenses/).Providing presentation and/or application layer support.

      In the case of:

      + * [MongoDB-Server](https://www.mongodb.org/about/licensing/) + * [HTML5 Shiv](https://github.com/afarkas/html5shiv) -Copyright © 2007 [Free Software Foundation](http://fsf.org/), Inc. +Copyright © 2007 [Free Software Foundation](http://fsf.org/), Inc.

      This product includes software developed as part of the OpenGrid Project.
      Copyright © 2015, City Of Chicago - OpenGrid

      @@ -115,8 +118,11 @@ Copyright © 2007 [Free Software Foundation](http://fsf.org/), Inc. NOTICE file corresponding to [Eclispe Distribution License](https://eclipse.org/org/documents/edl-v10.php) AND/OR [Eclipse Public License](https://eclipse.org/org/documents/epl-v10.php). Providing presentation and/or application layer support.

      In the case of:

      + * [Hibernate JPA API](https://github.com/hibernate/hibernate-jpa-api) + * [JUnit](https://github.com/junit-team/junit) + * [jQuery dragtable plugin](https://github.com/akottr/dragtable) Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors.
      From d287703af5b7fe77fa8d72ad8a2c05f6c785891d Mon Sep 17 00:00:00 2001 From: Regina Date: Tue, 21 Mar 2017 15:27:39 -0500 Subject: [PATCH 17/26] Update Readme for GFM --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5b5cb1a..c21a460 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![OpenGrid](img/branding/OpenGrid_Logo_Horizontal_3Color.png) -[![Build status - Linux](https://img.shields.io/travis/Chicago/opengrid/master.svg?style=flat-square&label=Linux build)](https://travis-ci.org/Chicago/opengrid)[![Build status - Windows](https://img.shields.io/appveyor/ci/tomschenkjr/opengrid/master.svg?style=flat-square&label=Windows build)](https://ci.appveyor.com/project/tomschenkjr/opengrid)[![Node.js dependencies](https://img.shields.io/coveralls/Chicago/opengrid/master.svg?style=flat-square)](https://coveralls.io/github/Chicago/opengrid)[![Node.js](https://img.shields.io/node/v/gh-badges.svg?style=flat-square)](https://david-dm.org/Chicago/opengrid)[![Node.js dependencies](https://img.shields.io/david/Chicago/opengrid.svg?style=flat-square)](https://david-dm.org/Chicago/opengrid)[![Node.js devdependencies](https://img.shields.io/david/dev/chicago/opengrid.svg?style=flat-square)](https://david-dm.org/Chicago/opengrid#info=devDependencies&view=table) +[![Build status-Linux](https://img.shields.io/travis/Chicago/opengrid/master.svg?style=flat-square&label=Linux build)](https://travis-ci.org/Chicago/opengrid)[![Build status-Windows](https://img.shields.io/appveyor/ci/tomschenkjr/opengrid/master.svg?style=flat-square&label=Windows build)](https://ci.appveyor.com/project/tomschenkjr/opengrid)[![Node.js dependencies](https://img.shields.io/coveralls/Chicago/opengrid/master.svg?style=flat-square)](https://coveralls.io/github/Chicago/opengrid)[![Node.js](https://img.shields.io/node/v/gh-badges.svg?style=flat-square)](https://david-dm.org/Chicago/opengrid)[![Node.js dependencies](https://img.shields.io/david/Chicago/opengrid.svg?style=flat-square)](https://david-dm.org/Chicago/opengrid)[![Node.js devdependencies](https://img.shields.io/david/dev/chicago/opengrid.svg?style=flat-square)](https://david-dm.org/Chicago/opengrid#info=devDependencies&view=table) OpenGrid an open-source, interactive map platform that allows users to explore multiple data sources in an easy-to-use interface. Developed to support situational awareness, incident monitoring and responses, historical data retrieval, and real-time advanced analytics. Users can perform advanced queries to filter data, search within custom boundaries, or based on the users location. Other GIS data, such as weather and Shapefiles can be overlaid on the map with other data. OpenGrid is natively compatible with desktops and mobile devices. @@ -10,8 +10,7 @@ OpenGrid uses a service layer to retrieve data from an underlying data store. * User Documentation: http://docs.opengrid.io * Repository for User Interface: https://github.com/Chicago/opengrid -* Repository for -* Planning Documentation and Meeting Notes: https://github.com/Chicago/opengrid/wiki +* Repository for Planning Documentation and Meeting Notes: https://github.com/Chicago/opengrid/wiki * Issues: https://github.com/Chicago/opengrid/issues * Mailing List & Discussion: https://groups.google.com/forum/#!forum/opengrid-chicago From 954a583aa6653c36e1db9d12ce84963b668968cb Mon Sep 17 00:00:00 2001 From: rladines Date: Mon, 24 Apr 2017 08:45:48 -0500 Subject: [PATCH 18/26] Fixed issue #294 Fixed rendering issues with dots on 'moveend' event --- src/js/ux/Map.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/js/ux/Map.js b/src/js/ux/Map.js index 5640cf0..f93667f 100644 --- a/src/js/ux/Map.js +++ b/src/js/ux/Map.js @@ -366,6 +366,16 @@ ogrid.Map = ogrid.Class.extend({ }, + //Issue #294 + //override Leaflet circlemarker _empty method, the renderer object does not seem to be updated on 'moveend' when we refresh our dots + //regen is an indicator that auto-requery is turned on; we might need this later + _getOverrideCircleMarkerIsEmpty: function (that, regen) { + return $.proxy(function() { + return !this._radius; + }, that); + }, + + _onRefreshData: function (evtData) { try { //console.log('map refresh: ' + JSON.stringify(evtData)); @@ -429,6 +439,9 @@ ogrid.Map = ogrid.Class.extend({ fillOpacity: (o.opacity/100), //pct fillColor: o.fillColor }); + + //Issue #294 Override _emepty method; missing dots look to be due to Leaflet renderer being out of sync at 'moveend' + cm._empty = me._getOverrideCircleMarkerIsEmpty(cm, evtData.message.options.passthroughData.regenerator); me._markers[rsId][ogrid.oid(feature)] = cm; return cm; } From cface06076a6c954ba97040dbaa4e40b8acadf50 Mon Sep 17 00:00:00 2001 From: Regina Date: Tue, 13 Jun 2017 11:29:46 -0500 Subject: [PATCH 19/26] Create User Documentation.md --- docs/User Documentation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/User Documentation.md b/docs/User Documentation.md index e5956d6..21c41b1 100644 --- a/docs/User Documentation.md +++ b/docs/User Documentation.md @@ -5,7 +5,7 @@ ### Table of Contents * [Getting Started](#getting-started) - * [Background and Overview](#background-and-overview) + * [Overview](#overview) * [Acknowledgements](#acknowledgements) * [User Documentation](#user-documentation) * [Supported Browsers](#supported-browsers) From 09d53fc91016e56f1bd690d9b0fd2599db50d789 Mon Sep 17 00:00:00 2001 From: Regina Date: Tue, 13 Jun 2017 11:30:18 -0500 Subject: [PATCH 20/26] Create User Documentation.md --- docs/User Documentation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/User Documentation.md b/docs/User Documentation.md index 21c41b1..522af47 100644 --- a/docs/User Documentation.md +++ b/docs/User Documentation.md @@ -34,7 +34,7 @@
      ## Getting Started -### Background and Overview +### Overview

      Open Grid is a geographical information systems developed for residents to access public city data in a more intuitive manner. The application supports situational awareness, incident responses and monitoring, historical data retrieval, and real-time advanced analytics.

      OpenGrid design has a three-layer service architecture: Presentation, Business and Data. The presentation layer provides the application’s user interface which is a static HTML/JavaScript based GUI that presents a more intuitive and modernized approach that support usage and accessibility on any mobile device and terminals in public safety vehicles.

      From 89aaaa69e6515055a0156d6a456d248de45702d2 Mon Sep 17 00:00:00 2001 From: Regina Date: Tue, 13 Jun 2017 16:45:00 -0500 Subject: [PATCH 21/26] update user doc --- docs/User Documentation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/User Documentation.md b/docs/User Documentation.md index 522af47..28be841 100644 --- a/docs/User Documentation.md +++ b/docs/User Documentation.md @@ -50,7 +50,7 @@ OpenGrid was developed with the support of Bloomberg Philanthropies. ## User Documentation -

      User documentation is designed to assist end users with the use of OpenGrid product and services. Upon usage of documentation the user will gain appropriate knowledge and capabilities to navigate the system. The user will inherit an understanding of the system and its processes and have the foundation to execute queries for navigation.

      +

      User documentation is designed to assist end users with the use of OpenGrid product and services. Upon usage of documentation the user will gain appropriate knowledge and capabilities to navigate the system. The user will inherit an understanding of the system, its processes and have the foundation to execute queries for navigation.

      #### Supported Browsers

      OpenGrid supports both mobile and desktop versions search engines. Mobile Android devices support Firefox version 40+ and Chrome 44+. Ios devices support Chrome 45+, Safari 8.1+ and Firefox 40+. Desktop versions support Firefox 42+ and Chrome 46+; Internet Explorer is supported from IE10+ no older versions will be continued. All browsers must have cookies enabled and support JavaScript/ECMAScript version 5.1.

      From abd4dd0a8f5d0921ff717a0c4653e99a3ff4c17c Mon Sep 17 00:00:00 2001 From: rladines Date: Tue, 20 Jun 2017 14:04:39 -0500 Subject: [PATCH 22/26] Applied milestone 1.4.0 changes --- gulpfile.js | 5 + lib/bootstrap-3.3.5/css/bootstrap-toggle.css | 83 + lib/bootstrap-3.3.5/js/bootstrap-toggle.js | 180 + lib/chart-1.0.2/js/Chart.js | 3477 ---- .../css/chart.css | 0 lib/chart-2.4.0/js/Chart-2.4.0.js | 15855 +++++++++++++++ lib/jquery-1.11.2/opengrid-query-builder.css | 4 +- lib/misc/js/crossroads.js | 725 + lib/misc/js/hasher.js | 441 + lib/misc/js/lodash.js | 16733 ++++++++++++++++ lib/misc/js/signals.js | 445 + src/css/Chart.css | 104 +- src/css/ogrid-theme-blue.css | 166 +- src/index.html | 51 +- src/js/core/Event.js | 3 +- src/js/core/Util.js | 37 +- src/js/custom/Config.js | 3 +- .../custom/ext/ogrid-bootstrap-table-graph.js | 27 +- src/js/data/Search.js | 21 +- src/js/ux/AdvancedSearch.js | 466 +- src/js/ux/Main.js | 195 +- src/js/ux/Map.js | 111 +- src/js/ux/QSearch.js | 1 + src/js/ux/TableView.js | 251 +- src/js/ux/chart/Chart.js | 430 +- 25 files changed, 35870 insertions(+), 3944 deletions(-) create mode 100644 lib/bootstrap-3.3.5/css/bootstrap-toggle.css create mode 100644 lib/bootstrap-3.3.5/js/bootstrap-toggle.js delete mode 100644 lib/chart-1.0.2/js/Chart.js rename lib/{chart-1.0.2 => chart-2.4.0}/css/chart.css (100%) create mode 100644 lib/chart-2.4.0/js/Chart-2.4.0.js create mode 100644 lib/misc/js/crossroads.js create mode 100644 lib/misc/js/hasher.js create mode 100644 lib/misc/js/lodash.js create mode 100644 lib/misc/js/signals.js diff --git a/gulpfile.js b/gulpfile.js index e8787df..b14c606 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -66,6 +66,11 @@ var third_party_sources = [ 'lib/bootstrap-table-1.8.1/js/bootstrap-table.js', 'lib/leaflet-1.0.1/js/leaflet-src.js', 'lib/jspdf-1.1.135/js/jspdf.js', + + //load first before crossroads.js + 'lib/misc/js/signals.js', + 'lib/misc/js/hasher.js', + 'lib/**/*.js' ]; diff --git a/lib/bootstrap-3.3.5/css/bootstrap-toggle.css b/lib/bootstrap-3.3.5/css/bootstrap-toggle.css new file mode 100644 index 0000000..057d08b --- /dev/null +++ b/lib/bootstrap-3.3.5/css/bootstrap-toggle.css @@ -0,0 +1,83 @@ +/*! ======================================================================== + * Bootstrap Toggle: bootstrap-toggle.css v2.2.0 + * http://www.bootstraptoggle.com + * ======================================================================== + * Copyright 2014 Min Hur, The New York Times Company + * Licensed under MIT + * ======================================================================== */ + + +.checkbox label .toggle, +.checkbox-inline .toggle { + margin-left: -20px; + margin-right: 5px; +} + +.toggle { + position: relative; + overflow: hidden; +} +.toggle input[type="checkbox"] { + display: none; +} +.toggle-group { + position: absolute; + width: 200%; + top: 0; + bottom: 0; + left: 0; + transition: left 0.35s; + -webkit-transition: left 0.35s; + -moz-user-select: none; + -webkit-user-select: none; +} +.toggle.off .toggle-group { + left: -100%; +} +.toggle-on { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 50%; + margin: 0; + border: 0; + border-radius: 0; +} +.toggle-off { + position: absolute; + top: 0; + bottom: 0; + left: 50%; + right: 0; + margin: 0; + border: 0; + border-radius: 0; +} +.toggle-handle { + position: relative; + margin: 0 auto; + padding-top: 0px; + padding-bottom: 0px; + height: 100%; + width: 0px; + border-width: 0 1px; +} + +.toggle.btn { min-width: 59px; min-height: 34px; } +.toggle-on.btn { padding-right: 24px; } +.toggle-off.btn { padding-left: 24px; } + +.toggle.btn-lg { min-width: 79px; min-height: 45px; } +.toggle-on.btn-lg { padding-right: 31px; } +.toggle-off.btn-lg { padding-left: 31px; } +.toggle-handle.btn-lg { width: 40px; } + +.toggle.btn-sm { min-width: 50px; min-height: 30px;} +.toggle-on.btn-sm { padding-right: 20px; } +.toggle-off.btn-sm { padding-left: 20px; } + +.toggle.btn-xs { min-width: 35px; min-height: 22px;} +.toggle-on.btn-xs { padding-right: 12px; } +.toggle-off.btn-xs { padding-left: 12px; } + diff --git a/lib/bootstrap-3.3.5/js/bootstrap-toggle.js b/lib/bootstrap-3.3.5/js/bootstrap-toggle.js new file mode 100644 index 0000000..533914e --- /dev/null +++ b/lib/bootstrap-3.3.5/js/bootstrap-toggle.js @@ -0,0 +1,180 @@ +/*! ======================================================================== + * Bootstrap Toggle: bootstrap-toggle.js v2.2.0 + * http://www.bootstraptoggle.com + * ======================================================================== + * Copyright 2014 Min Hur, The New York Times Company + * Licensed under MIT + * ======================================================================== */ + + + +function ($) { + 'use strict'; + + // TOGGLE PUBLIC CLASS DEFINITION + // ============================== + + var Toggle = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, this.defaults(), options) + this.render() + } + + Toggle.VERSION = '2.2.0' + + Toggle.DEFAULTS = { + on: 'On', + off: 'Off', + onstyle: 'primary', + offstyle: 'default', + size: 'normal', + style: '', + width: null, + height: null + } + + Toggle.prototype.defaults = function() { + return { + on: this.$element.attr('data-on') || Toggle.DEFAULTS.on, + off: this.$element.attr('data-off') || Toggle.DEFAULTS.off, + onstyle: this.$element.attr('data-onstyle') || Toggle.DEFAULTS.onstyle, + offstyle: this.$element.attr('data-offstyle') || Toggle.DEFAULTS.offstyle, + size: this.$element.attr('data-size') || Toggle.DEFAULTS.size, + style: this.$element.attr('data-style') || Toggle.DEFAULTS.style, + width: this.$element.attr('data-width') || Toggle.DEFAULTS.width, + height: this.$element.attr('data-height') || Toggle.DEFAULTS.height + } + } + + Toggle.prototype.render = function () { + this._onstyle = 'btn-' + this.options.onstyle + this._offstyle = 'btn-' + this.options.offstyle + var size = this.options.size === 'large' ? 'btn-lg' + : this.options.size === 'small' ? 'btn-sm' + : this.options.size === 'mini' ? 'btn-xs' + : '' + var $toggleOn = $('
    '); + return text.join(''); + }, + legend: { + labels: { + generateLabels: function(chart) { + var data = chart.data; + if (data.labels.length && data.datasets.length) { + return data.labels.map(function(label, i) { + var meta = chart.getDatasetMeta(0); + var ds = data.datasets[0]; + var arc = meta.data[i]; + var custom = arc.custom || {}; + var getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault; + var arcOpts = chart.options.elements.arc; + var fill = custom.backgroundColor ? custom.backgroundColor : getValueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor); + var stroke = custom.borderColor ? custom.borderColor : getValueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor); + var bw = custom.borderWidth ? custom.borderWidth : getValueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth); + + return { + text: label, + fillStyle: fill, + strokeStyle: stroke, + lineWidth: bw, + hidden: isNaN(ds.data[i]) || meta.data[i].hidden, + + // Extra data used for toggling the correct item + index: i + }; + }); + } + return []; + } + }, + + onClick: function(e, legendItem) { + var index = legendItem.index; + var chart = this.chart; + var i, ilen, meta; + + for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { + meta = chart.getDatasetMeta(i); + meta.data[index].hidden = !meta.data[index].hidden; + } + + chart.update(); + } + }, + + // Need to override these to give a nice default + tooltips: { + callbacks: { + title: function() { + return ''; + }, + label: function(tooltipItem, data) { + return data.labels[tooltipItem.index] + ': ' + tooltipItem.yLabel; + } + } + } + }; + + Chart.controllers.polarArea = Chart.DatasetController.extend({ + + dataElementType: Chart.elements.Arc, + + linkScales: helpers.noop, + + update: function(reset) { + var me = this; + var chart = me.chart; + var chartArea = chart.chartArea; + var meta = me.getMeta(); + var opts = chart.options; + var arcOpts = opts.elements.arc; + var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top); + chart.outerRadius = Math.max((minSize - arcOpts.borderWidth / 2) / 2, 0); + chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0); + chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); + + me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index); + me.innerRadius = me.outerRadius - chart.radiusLength; + + meta.count = me.countVisibleElements(); + + helpers.each(meta.data, function(arc, index) { + me.updateElement(arc, index, reset); + }); + }, + + updateElement: function(arc, index, reset) { + var me = this; + var chart = me.chart; + var dataset = me.getDataset(); + var opts = chart.options; + var animationOpts = opts.animation; + var scale = chart.scale; + var getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault; + var labels = chart.data.labels; + + var circumference = me.calculateCircumference(dataset.data[index]); + var centerX = scale.xCenter; + var centerY = scale.yCenter; + + // If there is NaN data before us, we need to calculate the starting angle correctly. + // We could be way more efficient here, but its unlikely that the polar area chart will have a lot of data + var visibleCount = 0; + var meta = me.getMeta(); + for (var i = 0; i < index; ++i) { + if (!isNaN(dataset.data[i]) && !meta.data[i].hidden) { + ++visibleCount; + } + } + + // var negHalfPI = -0.5 * Math.PI; + var datasetStartAngle = opts.startAngle; + var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); + var startAngle = datasetStartAngle + (circumference * visibleCount); + var endAngle = startAngle + (arc.hidden ? 0 : circumference); + + var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); + + helpers.extend(arc, { + // Utility + _datasetIndex: me.index, + _index: index, + _scale: scale, + + // Desired view properties + _model: { + x: centerX, + y: centerY, + innerRadius: 0, + outerRadius: reset ? resetRadius : distance, + startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle, + endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle, + label: getValueAtIndexOrDefault(labels, index, labels[index]) + } + }); + + // Apply border and fill style + me.removeHoverStyle(arc); + + arc.pivot(); + }, + + removeHoverStyle: function(arc) { + Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc); + }, + + countVisibleElements: function() { + var dataset = this.getDataset(); + var meta = this.getMeta(); + var count = 0; + + helpers.each(meta.data, function(element, index) { + if (!isNaN(dataset.data[index]) && !element.hidden) { + count++; + } + }); + + return count; + }, + + calculateCircumference: function(value) { + var count = this.getMeta().count; + if (count > 0 && !isNaN(value)) { + return (2 * Math.PI) / count; + } + return 0; + } + }); +}; + +},{}],20:[function(require,module,exports){ +'use strict'; + +module.exports = function(Chart) { + + var helpers = Chart.helpers; + + Chart.defaults.radar = { + aspectRatio: 1, + scale: { + type: 'radialLinear' + }, + elements: { + line: { + tension: 0 // no bezier in radar + } + } + }; + + Chart.controllers.radar = Chart.DatasetController.extend({ + + datasetElementType: Chart.elements.Line, + + dataElementType: Chart.elements.Point, + + linkScales: helpers.noop, + + update: function(reset) { + var me = this; + var meta = me.getMeta(); + var line = meta.dataset; + var points = meta.data; + var custom = line.custom || {}; + var dataset = me.getDataset(); + var lineElementOptions = me.chart.options.elements.line; + var scale = me.chart.scale; + + // Compatibility: If the properties are defined with only the old name, use those values + if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) { + dataset.lineTension = dataset.tension; + } + + helpers.extend(meta.dataset, { + // Utility + _datasetIndex: me.index, + // Data + _children: points, + _loop: true, + // Model + _model: { + // Appearance + tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.lineTension, lineElementOptions.tension), + backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor), + borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth), + borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor), + fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill), + borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle), + borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash), + borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset), + borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle), + + // Scale + scaleTop: scale.top, + scaleBottom: scale.bottom, + scaleZero: scale.getBasePosition() + } + }); + + meta.dataset.pivot(); + + // Update Points + helpers.each(points, function(point, index) { + me.updateElement(point, index, reset); + }, me); + + // Update bezier control points + me.updateBezierControlPoints(); + }, + updateElement: function(point, index, reset) { + var me = this; + var custom = point.custom || {}; + var dataset = me.getDataset(); + var scale = me.chart.scale; + var pointElementOptions = me.chart.options.elements.point; + var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]); + + helpers.extend(point, { + // Utility + _datasetIndex: me.index, + _index: index, + _scale: scale, + + // Desired view properties + _model: { + x: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales + y: reset ? scale.yCenter : pointPosition.y, + + // Appearance + tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.tension, me.chart.options.elements.line.tension), + radius: custom.radius ? custom.radius : helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius), + backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor), + borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor), + borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth), + pointStyle: custom.pointStyle ? custom.pointStyle : helpers.getValueAtIndexOrDefault(dataset.pointStyle, index, pointElementOptions.pointStyle), + + // Tooltip + hitRadius: custom.hitRadius ? custom.hitRadius : helpers.getValueAtIndexOrDefault(dataset.hitRadius, index, pointElementOptions.hitRadius) + } + }); + + point._model.skip = custom.skip ? custom.skip : (isNaN(point._model.x) || isNaN(point._model.y)); + }, + updateBezierControlPoints: function() { + var chartArea = this.chart.chartArea; + var meta = this.getMeta(); + + helpers.each(meta.data, function(point, index) { + var model = point._model; + var controlPoints = helpers.splineCurve( + helpers.previousItem(meta.data, index, true)._model, + model, + helpers.nextItem(meta.data, index, true)._model, + model.tension + ); + + // Prevent the bezier going outside of the bounds of the graph + model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, chartArea.right), chartArea.left); + model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, chartArea.bottom), chartArea.top); + + model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, chartArea.right), chartArea.left); + model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, chartArea.bottom), chartArea.top); + + // Now pivot the point for animation + point.pivot(); + }); + }, + + draw: function(ease) { + var meta = this.getMeta(); + var easingDecimal = ease || 1; + + // Transition Point Locations + helpers.each(meta.data, function(point) { + point.transition(easingDecimal); + }); + + // Transition and Draw the line + meta.dataset.transition(easingDecimal).draw(); + + // Draw the points + helpers.each(meta.data, function(point) { + point.draw(); + }); + }, + + setHoverStyle: function(point) { + // Point + var dataset = this.chart.data.datasets[point._datasetIndex]; + var custom = point.custom || {}; + var index = point._index; + var model = point._model; + + model.radius = custom.hoverRadius ? custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); + model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); + model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor)); + model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth); + }, + + removeHoverStyle: function(point) { + var dataset = this.chart.data.datasets[point._datasetIndex]; + var custom = point.custom || {}; + var index = point._index; + var model = point._model; + var pointElementOptions = this.chart.options.elements.point; + + model.radius = custom.radius ? custom.radius : helpers.getValueAtIndexOrDefault(dataset.radius, index, pointElementOptions.radius); + model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor); + model.borderColor = custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor); + model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth); + } + }); +}; + +},{}],21:[function(require,module,exports){ +/* global window: false */ +'use strict'; + +module.exports = function(Chart) { + + var helpers = Chart.helpers; + + Chart.defaults.global.animation = { + duration: 1000, + easing: 'easeOutQuart', + onProgress: helpers.noop, + onComplete: helpers.noop + }; + + Chart.Animation = Chart.Element.extend({ + currentStep: null, // the current animation step + numSteps: 60, // default number of steps + easing: '', // the easing to use for this animation + render: null, // render function used by the animation service + + onAnimationProgress: null, // user specified callback to fire on each step of the animation + onAnimationComplete: null // user specified callback to fire when the animation finishes + }); + + Chart.animationService = { + frameDuration: 17, + animations: [], + dropFrames: 0, + request: null, + + /** + * @function Chart.animationService.addAnimation + * @param chartInstance {ChartController} the chart to animate + * @param animationObject {IAnimation} the animation that we will animate + * @param duration {Number} length of animation in ms + * @param lazy {Boolean} if true, the chart is not marked as animating to enable more responsive interactions + */ + addAnimation: function(chartInstance, animationObject, duration, lazy) { + var me = this; + + if (!lazy) { + chartInstance.animating = true; + } + + for (var index = 0; index < me.animations.length; ++index) { + if (me.animations[index].chartInstance === chartInstance) { + // replacing an in progress animation + me.animations[index].animationObject = animationObject; + return; + } + } + + me.animations.push({ + chartInstance: chartInstance, + animationObject: animationObject + }); + + // If there are no animations queued, manually kickstart a digest, for lack of a better word + if (me.animations.length === 1) { + me.requestAnimationFrame(); + } + }, + // Cancel the animation for a given chart instance + cancelAnimation: function(chartInstance) { + var index = helpers.findIndex(this.animations, function(animationWrapper) { + return animationWrapper.chartInstance === chartInstance; + }); + + if (index !== -1) { + this.animations.splice(index, 1); + chartInstance.animating = false; + } + }, + requestAnimationFrame: function() { + var me = this; + if (me.request === null) { + // Skip animation frame requests until the active one is executed. + // This can happen when processing mouse events, e.g. 'mousemove' + // and 'mouseout' events will trigger multiple renders. + me.request = helpers.requestAnimFrame.call(window, function() { + me.request = null; + me.startDigest(); + }); + } + }, + startDigest: function() { + var me = this; + + var startTime = Date.now(); + var framesToDrop = 0; + + if (me.dropFrames > 1) { + framesToDrop = Math.floor(me.dropFrames); + me.dropFrames = me.dropFrames % 1; + } + + var i = 0; + while (i < me.animations.length) { + if (me.animations[i].animationObject.currentStep === null) { + me.animations[i].animationObject.currentStep = 0; + } + + me.animations[i].animationObject.currentStep += 1 + framesToDrop; + + if (me.animations[i].animationObject.currentStep > me.animations[i].animationObject.numSteps) { + me.animations[i].animationObject.currentStep = me.animations[i].animationObject.numSteps; + } + + me.animations[i].animationObject.render(me.animations[i].chartInstance, me.animations[i].animationObject); + if (me.animations[i].animationObject.onAnimationProgress && me.animations[i].animationObject.onAnimationProgress.call) { + me.animations[i].animationObject.onAnimationProgress.call(me.animations[i].chartInstance, me.animations[i]); + } + + if (me.animations[i].animationObject.currentStep === me.animations[i].animationObject.numSteps) { + if (me.animations[i].animationObject.onAnimationComplete && me.animations[i].animationObject.onAnimationComplete.call) { + me.animations[i].animationObject.onAnimationComplete.call(me.animations[i].chartInstance, me.animations[i]); + } + + // executed the last frame. Remove the animation. + me.animations[i].chartInstance.animating = false; + + me.animations.splice(i, 1); + } else { + ++i; + } + } + + var endTime = Date.now(); + var dropFrames = (endTime - startTime) / me.frameDuration; + + me.dropFrames += dropFrames; + + // Do we have more stuff to animate? + if (me.animations.length > 0) { + me.requestAnimationFrame(); + } + } + }; +}; + +},{}],22:[function(require,module,exports){ +'use strict'; + +module.exports = function(Chart) { + // Global Chart canvas helpers object for drawing items to canvas + var helpers = Chart.canvasHelpers = {}; + + helpers.drawPoint = function(ctx, pointStyle, radius, x, y) { + var type, edgeLength, xOffset, yOffset, height, size; + + if (typeof pointStyle === 'object') { + type = pointStyle.toString(); + if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') { + ctx.drawImage(pointStyle, x - pointStyle.width / 2, y - pointStyle.height / 2); + return; + } + } + + if (isNaN(radius) || radius <= 0) { + return; + } + + switch (pointStyle) { + // Default includes circle + default: + ctx.beginPath(); + ctx.arc(x, y, radius, 0, Math.PI * 2); + ctx.closePath(); + ctx.fill(); + break; + case 'triangle': + ctx.beginPath(); + edgeLength = 3 * radius / Math.sqrt(3); + height = edgeLength * Math.sqrt(3) / 2; + ctx.moveTo(x - edgeLength / 2, y + height / 3); + ctx.lineTo(x + edgeLength / 2, y + height / 3); + ctx.lineTo(x, y - 2 * height / 3); + ctx.closePath(); + ctx.fill(); + break; + case 'rect': + size = 1 / Math.SQRT2 * radius; + ctx.beginPath(); + ctx.fillRect(x - size, y - size, 2 * size, 2 * size); + ctx.strokeRect(x - size, y - size, 2 * size, 2 * size); + break; + case 'rectRot': + size = 1 / Math.SQRT2 * radius; + ctx.beginPath(); + ctx.moveTo(x - size, y); + ctx.lineTo(x, y + size); + ctx.lineTo(x + size, y); + ctx.lineTo(x, y - size); + ctx.closePath(); + ctx.fill(); + break; + case 'cross': + ctx.beginPath(); + ctx.moveTo(x, y + radius); + ctx.lineTo(x, y - radius); + ctx.moveTo(x - radius, y); + ctx.lineTo(x + radius, y); + ctx.closePath(); + break; + case 'crossRot': + ctx.beginPath(); + xOffset = Math.cos(Math.PI / 4) * radius; + yOffset = Math.sin(Math.PI / 4) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x - xOffset, y + yOffset); + ctx.lineTo(x + xOffset, y - yOffset); + ctx.closePath(); + break; + case 'star': + ctx.beginPath(); + ctx.moveTo(x, y + radius); + ctx.lineTo(x, y - radius); + ctx.moveTo(x - radius, y); + ctx.lineTo(x + radius, y); + xOffset = Math.cos(Math.PI / 4) * radius; + yOffset = Math.sin(Math.PI / 4) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x - xOffset, y + yOffset); + ctx.lineTo(x + xOffset, y - yOffset); + ctx.closePath(); + break; + case 'line': + ctx.beginPath(); + ctx.moveTo(x - radius, y); + ctx.lineTo(x + radius, y); + ctx.closePath(); + break; + case 'dash': + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineTo(x + radius, y); + ctx.closePath(); + break; + } + + ctx.stroke(); + }; +}; + +},{}],23:[function(require,module,exports){ +'use strict'; + +module.exports = function(Chart) { + + var helpers = Chart.helpers; + + // Create a dictionary of chart types, to allow for extension of existing types + Chart.types = {}; + + // Store a reference to each instance - allowing us to globally resize chart instances on window resize. + // Destroy method on the chart will remove the instance of the chart from this reference. + Chart.instances = {}; + + // Controllers available for dataset visualization eg. bar, line, slice, etc. + Chart.controllers = {}; + + /** + * The "used" size is the final value of a dimension property after all calculations have + * been performed. This method uses the computed style of `element` but returns undefined + * if the computed style is not expressed in pixels. That can happen in some cases where + * `element` has a size relative to its parent and this last one is not yet displayed, + * for example because of `display: none` on a parent node. + * TODO(SB) Move this method in the upcoming core.platform class. + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value + * @returns {Number} Size in pixels or undefined if unknown. + */ + function readUsedSize(element, property) { + var value = helpers.getStyle(element, property); + var matches = value && value.match(/(\d+)px/); + return matches? Number(matches[1]) : undefined; + } + + /** + * Initializes the canvas style and render size without modifying the canvas display size, + * since responsiveness is handled by the controller.resize() method. The config is used + * to determine the aspect ratio to apply in case no explicit height has been specified. + * TODO(SB) Move this method in the upcoming core.platform class. + */ + function initCanvas(canvas, config) { + var style = canvas.style; + + // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it + // returns null or '' if no explicit value has been set to the canvas attribute. + var renderHeight = canvas.getAttribute('height'); + var renderWidth = canvas.getAttribute('width'); + + // Chart.js modifies some canvas values that we want to restore on destroy + canvas._chartjs = { + initial: { + height: renderHeight, + width: renderWidth, + style: { + display: style.display, + height: style.height, + width: style.width + } + } + }; + + // Force canvas to display as block to avoid extra space caused by inline + // elements, which would interfere with the responsive resize process. + // https://github.com/chartjs/Chart.js/issues/2538 + style.display = style.display || 'block'; + + if (renderWidth === null || renderWidth === '') { + var displayWidth = readUsedSize(canvas, 'width'); + if (displayWidth !== undefined) { + canvas.width = displayWidth; + } + } + + if (renderHeight === null || renderHeight === '') { + if (canvas.style.height === '') { + // If no explicit render height and style height, let's apply the aspect ratio, + // which one can be specified by the user but also by charts as default option + // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2. + canvas.height = canvas.width / (config.options.aspectRatio || 2); + } else { + var displayHeight = readUsedSize(canvas, 'height'); + if (displayWidth !== undefined) { + canvas.height = displayHeight; + } + } + } + + return canvas; + } + + /** + * Restores the canvas initial state, such as render/display sizes and style. + * TODO(SB) Move this method in the upcoming core.platform class. + */ + function releaseCanvas(canvas) { + if (!canvas._chartjs) { + return; + } + + var initial = canvas._chartjs.initial; + ['height', 'width'].forEach(function(prop) { + var value = initial[prop]; + if (value === undefined || value === null) { + canvas.removeAttribute(prop); + } else { + canvas.setAttribute(prop, value); + } + }); + + helpers.each(initial.style || {}, function(value, key) { + canvas.style[key] = value; + }); + + // The canvas render size might have been changed (and thus the state stack discarded), + // we can't use save() and restore() to restore the initial state. So make sure that at + // least the canvas context is reset to the default state by setting the canvas width. + // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html + canvas.width = canvas.width; + + delete canvas._chartjs; + } + + /** + * TODO(SB) Move this method in the upcoming core.platform class. + */ + function acquireContext(item, config) { + if (typeof item === 'string') { + item = document.getElementById(item); + } else if (item.length) { + // Support for array based queries (such as jQuery) + item = item[0]; + } + + if (item && item.canvas) { + // Support for any object associated to a canvas (including a context2d) + item = item.canvas; + } + + if (item instanceof HTMLCanvasElement) { + // To prevent canvas fingerprinting, some add-ons undefine the getContext + // method, for example: https://github.com/kkapsner/CanvasBlocker + // https://github.com/chartjs/Chart.js/issues/2807 + var context = item.getContext && item.getContext('2d'); + if (context instanceof CanvasRenderingContext2D) { + initCanvas(item, config); + return context; + } + } + + return null; + } + + /** + * Initializes the given config with global and chart default values. + */ + function initConfig(config) { + config = config || {}; + + // Do NOT use configMerge() for the data object because this method merges arrays + // and so would change references to labels and datasets, preventing data updates. + var data = config.data = config.data || {}; + data.datasets = data.datasets || []; + data.labels = data.labels || []; + + config.options = helpers.configMerge( + Chart.defaults.global, + Chart.defaults[config.type], + config.options || {}); + + return config; + } + + /** + * @class Chart.Controller + * The main controller of a chart. + */ + Chart.Controller = function(item, config, instance) { + var me = this; + + config = initConfig(config); + + var context = acquireContext(item, config); + var canvas = context && context.canvas; + var height = canvas && canvas.height; + var width = canvas && canvas.width; + + instance.ctx = context; + instance.canvas = canvas; + instance.config = config; + instance.width = width; + instance.height = height; + instance.aspectRatio = height? width / height : null; + + me.id = helpers.uid(); + me.chart = instance; + me.config = config; + me.options = config.options; + me._bufferedRender = false; + + // Add the chart instance to the global namespace + Chart.instances[me.id] = me; + + Object.defineProperty(me, 'data', { + get: function() { + return me.config.data; + } + }); + + if (!context || !canvas) { + // The given item is not a compatible context2d element, let's return before finalizing + // the chart initialization but after setting basic chart / controller properties that + // can help to figure out that the chart is not valid (e.g chart.canvas !== null); + // https://github.com/chartjs/Chart.js/issues/2807 + console.error("Failed to create chart: can't acquire context from the given item"); + return me; + } + + helpers.retinaScale(instance); + + // Responsiveness is currently based on the use of an iframe, however this method causes + // performance issues and could be troublesome when used with ad blockers. So make sure + // that the user is still able to create a chart without iframe when responsive is false. + // See https://github.com/chartjs/Chart.js/issues/2210 + if (me.options.responsive) { + helpers.addResizeListener(canvas.parentNode, function() { + me.resize(); + }); + + // Initial resize before chart draws (must be silent to preserve initial animations). + me.resize(true); + } + + me.initialize(); + + return me; + }; + + helpers.extend(Chart.Controller.prototype, /** @lends Chart.Controller */ { + initialize: function() { + var me = this; + + // Before init plugin notification + Chart.plugins.notify('beforeInit', [me]); + + me.bindEvents(); + + // Make sure controllers are built first so that each dataset is bound to an axis before the scales + // are built + me.ensureScalesHaveIDs(); + me.buildOrUpdateControllers(); + me.buildScales(); + me.updateLayout(); + me.resetElements(); + me.initToolTip(); + me.update(); + + // After init plugin notification + Chart.plugins.notify('afterInit', [me]); + + return me; + }, + + clear: function() { + helpers.clear(this.chart); + return this; + }, + + stop: function() { + // Stops any current animation loop occurring + Chart.animationService.cancelAnimation(this); + return this; + }, + + resize: function(silent) { + var me = this; + var chart = me.chart; + var options = me.options; + var canvas = chart.canvas; + var aspectRatio = (options.maintainAspectRatio && chart.aspectRatio) || null; + + // the canvas render width and height will be casted to integers so make sure that + // the canvas display style uses the same integer values to avoid blurring effect. + var newWidth = Math.floor(helpers.getMaximumWidth(canvas)); + var newHeight = Math.floor(aspectRatio? newWidth / aspectRatio : helpers.getMaximumHeight(canvas)); + + if (chart.width === newWidth && chart.height === newHeight) { + return; + } + + canvas.width = chart.width = newWidth; + canvas.height = chart.height = newHeight; + canvas.style.width = newWidth + 'px'; + canvas.style.height = newHeight + 'px'; + + helpers.retinaScale(chart); + + // Notify any plugins about the resize + var newSize = {width: newWidth, height: newHeight}; + Chart.plugins.notify('resize', [me, newSize]); + + // Notify of resize + if (me.options.onResize) { + me.options.onResize(me, newSize); + } + + if (!silent) { + me.stop(); + me.update(me.options.responsiveAnimationDuration); + } + }, + + ensureScalesHaveIDs: function() { + var options = this.options; + var scalesOptions = options.scales || {}; + var scaleOptions = options.scale; + + helpers.each(scalesOptions.xAxes, function(xAxisOptions, index) { + xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index); + }); + + helpers.each(scalesOptions.yAxes, function(yAxisOptions, index) { + yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index); + }); + + if (scaleOptions) { + scaleOptions.id = scaleOptions.id || 'scale'; + } + }, + + /** + * Builds a map of scale ID to scale object for future lookup. + */ + buildScales: function() { + var me = this; + var options = me.options; + var scales = me.scales = {}; + var items = []; + + if (options.scales) { + items = items.concat( + (options.scales.xAxes || []).map(function(xAxisOptions) { + return {options: xAxisOptions, dtype: 'category'}; + }), + (options.scales.yAxes || []).map(function(yAxisOptions) { + return {options: yAxisOptions, dtype: 'linear'}; + }) + ); + } + + if (options.scale) { + items.push({options: options.scale, dtype: 'radialLinear', isDefault: true}); + } + + helpers.each(items, function(item) { + var scaleOptions = item.options; + var scaleType = helpers.getValueOrDefault(scaleOptions.type, item.dtype); + var scaleClass = Chart.scaleService.getScaleConstructor(scaleType); + if (!scaleClass) { + return; + } + + var scale = new scaleClass({ + id: scaleOptions.id, + options: scaleOptions, + ctx: me.chart.ctx, + chart: me + }); + + scales[scale.id] = scale; + + // TODO(SB): I think we should be able to remove this custom case (options.scale) + // and consider it as a regular scale part of the "scales"" map only! This would + // make the logic easier and remove some useless? custom code. + if (item.isDefault) { + me.scale = scale; + } + }); + + Chart.scaleService.addScalesToLayout(this); + }, + + updateLayout: function() { + Chart.layoutService.update(this, this.chart.width, this.chart.height); + }, + + buildOrUpdateControllers: function() { + var me = this; + var types = []; + var newControllers = []; + + helpers.each(me.data.datasets, function(dataset, datasetIndex) { + var meta = me.getDatasetMeta(datasetIndex); + if (!meta.type) { + meta.type = dataset.type || me.config.type; + } + + types.push(meta.type); + + if (meta.controller) { + meta.controller.updateIndex(datasetIndex); + } else { + meta.controller = new Chart.controllers[meta.type](me, datasetIndex); + newControllers.push(meta.controller); + } + }, me); + + if (types.length > 1) { + for (var i = 1; i < types.length; i++) { + if (types[i] !== types[i - 1]) { + me.isCombo = true; + break; + } + } + } + + return newControllers; + }, + + /** + * Reset the elements of all datasets + * @method resetElements + * @private + */ + resetElements: function() { + var me = this; + helpers.each(me.data.datasets, function(dataset, datasetIndex) { + me.getDatasetMeta(datasetIndex).controller.reset(); + }, me); + }, + + /** + * Resets the chart back to it's state before the initial animation + * @method reset + */ + reset: function() { + this.resetElements(); + this.tooltip.initialize(); + }, + + update: function(animationDuration, lazy) { + var me = this; + Chart.plugins.notify('beforeUpdate', [me]); + + // In case the entire data object changed + me.tooltip._data = me.data; + + // Make sure dataset controllers are updated and new controllers are reset + var newControllers = me.buildOrUpdateControllers(); + + // Make sure all dataset controllers have correct meta data counts + helpers.each(me.data.datasets, function(dataset, datasetIndex) { + me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements(); + }, me); + + Chart.layoutService.update(me, me.chart.width, me.chart.height); + + // Apply changes to the datasets that require the scales to have been calculated i.e BorderColor changes + Chart.plugins.notify('afterScaleUpdate', [me]); + + // Can only reset the new controllers after the scales have been updated + helpers.each(newControllers, function(controller) { + controller.reset(); + }); + + me.updateDatasets(); + + // Do this before render so that any plugins that need final scale updates can use it + Chart.plugins.notify('afterUpdate', [me]); + + if (me._bufferedRender) { + me._bufferedRequest = { + lazy: lazy, + duration: animationDuration + }; + } else { + me.render(animationDuration, lazy); + } + }, + + /** + * @method beforeDatasetsUpdate + * @description Called before all datasets are updated. If a plugin returns false, + * the datasets update will be cancelled until another chart update is triggered. + * @param {Object} instance the chart instance being updated. + * @returns {Boolean} false to cancel the datasets update. + * @memberof Chart.PluginBase + * @since version 2.1.5 + * @instance + */ + + /** + * @method afterDatasetsUpdate + * @description Called after all datasets have been updated. Note that this + * extension will not be called if the datasets update has been cancelled. + * @param {Object} instance the chart instance being updated. + * @memberof Chart.PluginBase + * @since version 2.1.5 + * @instance + */ + + /** + * Updates all datasets unless a plugin returns false to the beforeDatasetsUpdate + * extension, in which case no datasets will be updated and the afterDatasetsUpdate + * notification will be skipped. + * @protected + * @instance + */ + updateDatasets: function() { + var me = this; + var i, ilen; + + if (Chart.plugins.notify('beforeDatasetsUpdate', [me])) { + for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { + me.getDatasetMeta(i).controller.update(); + } + + Chart.plugins.notify('afterDatasetsUpdate', [me]); + } + }, + + render: function(duration, lazy) { + var me = this; + Chart.plugins.notify('beforeRender', [me]); + + var animationOptions = me.options.animation; + if (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) { + var animation = new Chart.Animation(); + animation.numSteps = (duration || animationOptions.duration) / 16.66; // 60 fps + animation.easing = animationOptions.easing; + + // render function + animation.render = function(chartInstance, animationObject) { + var easingFunction = helpers.easingEffects[animationObject.easing]; + var stepDecimal = animationObject.currentStep / animationObject.numSteps; + var easeDecimal = easingFunction(stepDecimal); + + chartInstance.draw(easeDecimal, stepDecimal, animationObject.currentStep); + }; + + // user events + animation.onAnimationProgress = animationOptions.onProgress; + animation.onAnimationComplete = animationOptions.onComplete; + + Chart.animationService.addAnimation(me, animation, duration, lazy); + } else { + me.draw(); + if (animationOptions && animationOptions.onComplete && animationOptions.onComplete.call) { + animationOptions.onComplete.call(me); + } + } + return me; + }, + + draw: function(ease) { + var me = this; + var easingDecimal = ease || 1; + me.clear(); + + Chart.plugins.notify('beforeDraw', [me, easingDecimal]); + + // Draw all the scales + helpers.each(me.boxes, function(box) { + box.draw(me.chartArea); + }, me); + if (me.scale) { + me.scale.draw(); + } + + Chart.plugins.notify('beforeDatasetsDraw', [me, easingDecimal]); + + // Draw each dataset via its respective controller (reversed to support proper line stacking) + helpers.each(me.data.datasets, function(dataset, datasetIndex) { + if (me.isDatasetVisible(datasetIndex)) { + me.getDatasetMeta(datasetIndex).controller.draw(ease); + } + }, me, true); + + Chart.plugins.notify('afterDatasetsDraw', [me, easingDecimal]); + + // Finally draw the tooltip + me.tooltip.transition(easingDecimal).draw(); + + Chart.plugins.notify('afterDraw', [me, easingDecimal]); + }, + + // Get the single element that was clicked on + // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw + getElementAtEvent: function(e) { + return Chart.Interaction.modes.single(this, e); + }, + + getElementsAtEvent: function(e) { + return Chart.Interaction.modes.label(this, e, {intersect: true}); + }, + + getElementsAtXAxis: function(e) { + return Chart.Interaction.modes['x-axis'](this, e, {intersect: true}); + }, + + getElementsAtEventForMode: function(e, mode, options) { + var method = Chart.Interaction.modes[mode]; + if (typeof method === 'function') { + return method(this, e, options); + } + + return []; + }, + + getDatasetAtEvent: function(e) { + return Chart.Interaction.modes.dataset(this, e); + }, + + getDatasetMeta: function(datasetIndex) { + var me = this; + var dataset = me.data.datasets[datasetIndex]; + if (!dataset._meta) { + dataset._meta = {}; + } + + var meta = dataset._meta[me.id]; + if (!meta) { + meta = dataset._meta[me.id] = { + type: null, + data: [], + dataset: null, + controller: null, + hidden: null, // See isDatasetVisible() comment + xAxisID: null, + yAxisID: null + }; + } + + return meta; + }, + + getVisibleDatasetCount: function() { + var count = 0; + for (var i = 0, ilen = this.data.datasets.length; i
     
    - -
    + + +
    +
    -   -
    - -
    - -
    - - -
    -
    +
    +
    + +
    + +
    + + +
    +
    +
    diff --git a/src/js/core/Event.js b/src/js/core/Event.js index c03ffd4..43f2eef 100644 --- a/src/js/core/Event.js +++ b/src/js/core/Event.js @@ -15,7 +15,8 @@ ogrid.Event = { LOGGED_IN: 'logged_in', LOGGED_OUT: 'logged_out', MOBILE_MODE_CHANGED: 'mobile_mode_changed', - MAP_EXTENT_CHANGED: 'map_extent_changed' + MAP_EXTENT_CHANGED: 'map_extent_changed', + ADVANCED_INIT_DONE: 'advanced_init_done' } }; diff --git a/src/js/core/Util.js b/src/js/core/Util.js index 9a83ce4..041251f 100644 --- a/src/js/core/Util.js +++ b/src/js/core/Util.js @@ -120,7 +120,7 @@ ogrid.Util = { }, error: function(jqXHR, txtStatus, errorThrown) { if (txtStatus === 'timeout') { - console.log('Search has timed out.'); + console.log('Service call has timed out.'); //ogrid.Alert.error('Search has timed out.'); } else { console.log( (jqXHR.responseText) ? jqXHR.responseText : txtStatus); @@ -166,6 +166,26 @@ ogrid.Util = { return new Date().today() + " " + new Date().timeNow();*/ return moment(); + }, + + waitForCondition: function(maxTime, fn) { + var d = $.Deferred(); + + //async loop + var _loop = function (i) { + setTimeout(function() { + if (d.state() === 'pending') { + fn(d); + } + }, i * 500); + }; + + var n = (maxTime / 500); + //max of 5 seconds, we're anticipating some wasted cycles here + for (var i = 0; i < n; i++) { + _loop(i); + } + return d; } }; @@ -182,4 +202,17 @@ ogrid.guid = ogrid.Util.guid; ogrid.ajax = ogrid.Util.ajax; ogrid.oid = ogrid.Util.getGeoJsonFeatureId; ogrid.hereDoc = ogrid.Util.hereDoc; -ogrid.now = ogrid.Util.now; \ No newline at end of file +ogrid.now = ogrid.Util.now; +ogrid.waitForCondition = ogrid.Util.waitForCondition; + +//more "native" bind implementation +if (!Function.prototype.bind) { // check if native implementation available + Function.prototype.bind = function(){ + var fn = this, args = Array.prototype.slice.call(arguments), + object = args.shift(); + return function(){ + return fn.apply(object, + args.concat(Array.prototype.slice.call(arguments))); + }; + }; +} \ No newline at end of file diff --git a/src/js/custom/Config.js b/src/js/custom/Config.js index 74d6b2c..6aa5a5c 100644 --- a/src/js/custom/Config.js +++ b/src/js/custom/Config.js @@ -63,7 +63,8 @@ ogrid.Config = { showGraph: true, resizable: true - } + }, + arrayPopoverMax: 100 }, map: { diff --git a/src/js/custom/ext/ogrid-bootstrap-table-graph.js b/src/js/custom/ext/ogrid-bootstrap-table-graph.js index 5b0f2bb..1fbf7cd 100644 --- a/src/js/custom/ext/ogrid-bootstrap-table-graph.js +++ b/src/js/custom/ext/ogrid-bootstrap-table-graph.js @@ -30,30 +30,39 @@ '', - '', '
    '].join('')).appendTo($btnGroup); var $menu = $div.find('.dropdown-menu'), groupFields = this.options.graphOptions.groupFields; - $.each(groupFields, function (i, type) { + /*$.each(groupFields, function (i, type) { $menu.append(['
  • ', '', type.label, '', '
  • '].join('')); - }); + });*/ + + var groupByFields=null; - $menu.find('li').click(function () { - var fieldDisplayName = _getGroupField(that.options.graphOptions.groupFields, $(this).data('type')).label; + if (that.options.graphOptions.groupFields && that.options.graphOptions.groupFields.length > 0) { + groupByFields = that.options.graphOptions.groupFields; + } + $div.find('button').click(function () { + //var fieldDisplayName = _getGroupField(that.options.graphOptions.groupFields, $(this).data('type')).label; var g = new ogrid.Chart( $('#table-chart'), { data: that.options.origData, - groupBy: $(this).data('type'), - title: that.options.origData.meta.view.displayName + ' Data Grouped by ' + fieldDisplayName + //groupBy: $(this).data('type'), + //title: that.options.origData.meta.view.displayName + ' Data Grouped by ' + fieldDisplayName, + + //TODO: use the creation time field as defined on the dataset + //dateField: 'when.shardtime', + xAxisField: that.options.graphOptions.xAxisField, + xAxisLabel: that.options.graphOptions.xAxisLabel, + dataName: that.options.origData.meta.view.displayName, + groupByFields: groupByFields } ); g.showModal(); diff --git a/src/js/data/Search.js b/src/js/data/Search.js index 81bd002..74bb769 100644 --- a/src/js/data/Search.js +++ b/src/js/data/Search.js @@ -8,29 +8,33 @@ ogrid.Search = {}; ogrid.Search._getFilterParam = function(filter) { - return JSON.stringify(filter).replace('&', encodeURIComponent('&')).replace('#', encodeURIComponent('#')); + var s = JSON.stringify(filter); + //GH issue #235 - fixup for array support + s.replace('&', encodeURIComponent('&')).replace('#', encodeURIComponent('#')); + s = s.replace('\\\\', ''); + return s; }; ogrid.Search._getQParam = function(filter) { //make sure we encode &, # here to prevent any syntax errors return 'q=' + encodeURI( - JSON.stringify(filter) - ).replace('&', encodeURIComponent('&')).replace('#', encodeURIComponent('#')); + JSON.stringify(filter) + ).replace('&', encodeURIComponent('&')).replace('#', encodeURIComponent('#')); }; ogrid.Search._getOpts = function(geoFilter) { - if (geoFilter) { - var f = {"geoFilter": geoFilter}; + if (geoFilter) { + var f = {"geoFilter": geoFilter}; //return '&opts=' + encodeURI(JSON.stringify(f)); - return JSON.stringify(f); + return JSON.stringify(f); } else return ''; }; ogrid.Search._preProcess = function(data, renditionOptions) { if (renditionOptions) - //apply rendition options to returning json + //apply rendition options to returning json data.meta.view.options.rendition = $.extend(data.meta.view.options.rendition, renditionOptions); //timestamp data @@ -77,6 +81,7 @@ ogrid.Search.exec = function(options, passThroughData) { //passThroughData is additional info from the caller that is passed to success and error callbacks var me = this; var q = this._getFilterParam(options.filter); + var opts = this._getOpts(options.geoFilter); var url = ogrid.Config.service.endpoint + '/datasets/' + options.dataSetId + '/query'; @@ -225,7 +230,7 @@ ogrid.Search.list = function(options) { $.ajax({ url: ogrid.Config.service.endpoint + '/queries/?' + q + - '&n=' + (!ogrid.isNull(options.maxResults) ? options.maxResults : ogrid.Config.service.maxresults), + '&n=' + (!ogrid.isNull(options.maxResults) ? options.maxResults : ogrid.Config.service.maxresults), type: 'GET', async: true, contentType: 'application/json', diff --git a/src/js/ux/AdvancedSearch.js b/src/js/ux/AdvancedSearch.js index b004b0e..c59b78f 100644 --- a/src/js/ux/AdvancedSearch.js +++ b/src/js/ux/AdvancedSearch.js @@ -23,7 +23,10 @@ ogrid.AdvancedSearch = ogrid.Class.extend({ _queryAdmin: null, + _isLoadingQuery: false, + _isLoggedIn: false, + _geoFilterInitDeferred: $.Deferred(), //cache for lookup values _lookup: { @@ -66,8 +69,8 @@ ogrid.AdvancedSearch = ogrid.Class.extend({ this._queryAdmin = new ogrid.QueryAdmin( $('#ogrid-admin-queries'), ogrid.App.getSession().getCurrentUser(), { - onOpen: $.proxy(this._onManageQueryOpen(false), this), - onPlay: $.proxy(this._onManageQueryOpen(true), this), + onOpen: $.proxy(this._onManageQueryOpenSetUrl(false, this), this), + onPlay: $.proxy(this._onManageQueryOpenSetUrl(true, this), this), postDelete: $.proxy(this._postManageQueryDelete, this) } ); @@ -80,22 +83,83 @@ ogrid.AdvancedSearch = ogrid.Class.extend({ this._onReset(); }, - _onManageQueryOpen: function(autoExec) { - return function(query) { - this._loadQuery(query); - //if shared query and we're not the owner, create a 'copy' of the query - if (this._sharedNotOwned(query)) { - this._activeQuery = null; + _postQueryOpen: function(context, query, autoexec) { + if (autoexec) { + context._executeSearch(query); + } else { + //set our active tab to Build Query + $('#ogrid-adv-tabs a[href="#build-query"]').tab('show'); + + //focus on query name for now + //avoid annoying keyboard popup when on mobile mode + if (!ogrid.App.mobileView()) $('#saveQueryAs').focus(); + } + }, + + //set map center and zoom level, then wait + //we need to wait here until the view has been set to our desired center and zoom level + _setMapViewWithWait: function(me, loc) { + //disable map change event handler + me._options.map.enableMapViewChangeHandler(false); + + //move our map first + var a = loc.split(','); + me._options.map.setMapView( + L.latLng(a[0], a[1]), //lat,long + a[2] //zoom + ); + + //wait for a max of 5 secs + return ogrid.waitForCondition(5000, function(deferred) { + var c = me._options.map.getMapCenter(); + if (c.lng == a[1] && c.lat == a[0] && me._options.map.getMapZoom() == a[2]) { + me._options.map.enableMapViewChangeHandler(true); + + //we're done waiting, signal calling thread + deferred.resolve('done'); } - if (autoExec) { - this._onSubmit(); - } else { - //set our active tab to Build Query - $('#ogrid-adv-tabs a[href="#build-query"]').tab('show'); + }); + }, - //focus on query name for now - //avoid annoying keyboard popup when on mobile mode - if (!ogrid.App.mobileView()) $('#saveQueryAs').focus(); + _openQuery: function(context, query, loc, autoexec) { + context._loadQuery(query); + //if shared query and we're not the owner, create a 'copy' of the query + if (context._sharedNotOwned(query)) { + context._activeQuery = null; + } + if (loc) { + //map extent (map center) was passed + $.when( context._setMapViewWithWait(context, loc) ).done(function ( result ) { + //now we're ready to submit our query + context._postQueryOpen(context, query, autoexec); + }); + } else { + context._postQueryOpen(context, query, autoexec); + } + }, + + _onManageQueryOpenSetUrl: function(autoexec, context) { + return function(query) { + context._updateHashWithQuery(query, autoexec); + }; + }, + + + _onManageQueryOpen: function(autoexec, context) { + //query-> query def, optional loc->map center and zoom + return function(query, loc) { + //this can be called while the page is still loading + if (!context._isLoggedIn) { + ogrid.Event.on(ogrid.Event.types.ADVANCED_INIT_DONE, function() { + context._openQuery(context, query, loc, autoexec); + + if (autoexec) { + //after 1 second get rid of focus on "Find Data" due to timing issue + setTimeout(context._hideMe, 1000); + } + }); + } else { + context._openQuery(context, query, loc, autoexec); } }; }, @@ -111,6 +175,8 @@ ogrid.AdvancedSearch = ogrid.Class.extend({ (query.owner != ogrid.App.getSession().getCurrentUser().getProfile().loginId) ); }, + + _setupWindowResizeHandler: function() { $(window).resize(this._onWindowResize); }, @@ -202,6 +268,7 @@ ogrid.AdvancedSearch = ogrid.Class.extend({ $('#advSearchSave').removeClass('disabled'); }, + _initEventHandlers: function() { $('#beginDate').datetimepicker(); $('#endDate').datetimepicker(); @@ -238,6 +305,7 @@ ogrid.AdvancedSearch = ogrid.Class.extend({ $('#autoRefreshSpinUp').click(this._getSpinEventHandler('#autoRefreshInterval', 1)); $('#autoRefreshSpinDn').click(this._getSpinEventHandler('#autoRefreshInterval', -1)); + $('#saveQueryAs').keyup($.proxy(this._onQueryNameKeyup, this)); if (ogrid.Config.map.zoomToResultsExtent) @@ -245,7 +313,6 @@ ogrid.AdvancedSearch = ogrid.Class.extend({ ogrid.Event.on(ogrid.Event.types.CLEAR, $.proxy(this._onClear, this)); ogrid.Event.on(ogrid.Event.types.LOGGED_IN, $.proxy(this._onLoggedIn, this)); - ogrid.Event.on(ogrid.Event.types.MAP_EXTENT_CHANGED, $.proxy(this._onMapExtentChanged, this)); }, @@ -297,10 +364,13 @@ ogrid.AdvancedSearch = ogrid.Class.extend({ // load auto-load query if option is set if (ogrid.Config.advancedSearch.autoLoadQuery) { this._loadQuery(ogrid.Config.advancedSearch.autoLoadQuery); - - //auto-open Select Data pane + + //auto-open Select Data pane this._expandPane($("#ogrid-select-data-pane")); - } + } + this._isLoggedIn = true; + ogrid.Event.raise(ogrid.Event.types.ADVANCED_INIT_DONE); + }, @@ -330,6 +400,7 @@ ogrid.AdvancedSearch = ogrid.Class.extend({ if (this._pendingQueries > 0 ) { this._pendingQueries--; if (this._pendingQueries === 0) { + //no more pending queries, we're ready to auto-zoom if (!this._isMapExtentSelected()) { this._options.map.zoomToResultBounds(); } @@ -344,6 +415,7 @@ ogrid.AdvancedSearch = ogrid.Class.extend({ _onReset: function() { this._clear(); + this._resetHash(); }, _onQueryLoad: function(e) { @@ -352,15 +424,6 @@ ogrid.AdvancedSearch = ogrid.Class.extend({ //selected blank, clear screen this._clearQueryElements(); } else { - /*var q = $(e.target).children(":selected").data('qspec'); - var geoFilter = $(e.target).children(":selected").data('geoFilter'); - - var autoRefresh = $(e.target).children(":selected").data('autoRefresh'); - var refreshInterval = $(e.target).children(":selected").data('refreshInterval'); - - var queryId = $(e.target).children(":selected").data('queryId'); - var queryName = $(e.target).children(":selected").text(); - */ this._loadQuery({ _id: $(e.target).children(":selected").data('queryId'), spec: $(e.target).children(":selected").data('qspec'), @@ -444,6 +507,24 @@ ogrid.AdvancedSearch = ogrid.Class.extend({ } }, + //support dot sizer option now + _getSize: function(tabId) { + if ($('#spinnerGroup_' + tabId).hasClass('hidden')) { + //dot sizer must have been selected + var s = $("#dotSizerFields_" + tabId).val(); + if (s === '') { + throw ogrid.error('Save Error', 'No available field for dot size'); + } else { + return { + columnId: $("#dotSizerFields_" + tabId).children(":selected").val(), + calculator: $("#dotSizerFields_" + tabId).children(":selected").data('calc') + }; + } + } else { + return $('#sizeSpin_' + tabId).val(); + } + }, + _getRendition: function(tabId) { var c = $('#colorPicker_' + tabId + ' option').filter(':selected').data('color'); @@ -451,11 +532,11 @@ ogrid.AdvancedSearch = ogrid.Class.extend({ {c = 'indianred';} return { - color: c, - fillColor: (chroma.scale(['white', c])(0.5)).hex(), - opacity: $('#opacitySpin_' + tabId).val(), - size: $('#sizeSpin_' + tabId).val() - }; + color: c, + fillColor: chroma.scale(['white', c])(0.5).hex(), + opacity: $('#opacitySpin_' + tabId).val(), + size: this._getSize(tabId) + }; }, _getQueryName: function() { @@ -480,62 +561,67 @@ ogrid.AdvancedSearch = ogrid.Class.extend({ } }, - _saveCurrentQuery: function(queryId, isCommon) { - try { - var me = this; - var q = { - name: this._getQueryName(), - owner: ogrid.App.getSession().getCurrentUser().getProfile().loginId, - spec: [], - sharedWith: {users:[], groups:[]}, //no sharing implemented for Sprint 2 - isCommon: isCommon ? isCommon : false, //make sure the isCommonFlag is not discarded - autoRefresh: $('#autoRefreshCheckbox').prop('checked'), - refreshInterval: $("#autoRefreshInterval").val() - }; - - if (queryId) - q._id = queryId; - - //get all query builders - $.each($('#ogrid-ds-content').find('.query-builder'), function(i,v) { - //for each data type selected, build query to save - var typeId = $(v).data('typeId'); - var tabId = $(v).data('parentId'); - var r = me._getRendition(tabId); - delete r.fillColor; //fillColor is calculated, no need to save - - $(v)[0].queryBuilder.setOptions({display_errors: !me._hasEmptyFilter(v)}); - //get mongo-specific query - var f = $(v).queryBuilder('getMongo'); + _getQuerySpec: function(queryId, isCommon) { + var me = this; + var q = { + name: this._getQueryName(), + owner: ogrid.App.getSession().getCurrentUser().getProfile().loginId, + spec: [], + sharedWith: {users:[], groups:[]}, //no sharing implemented for Sprint 2 + isCommon: isCommon ? isCommon : false, //make sure the isCommonFlag is not discarded + autoRefresh: $('#autoRefreshCheckbox').prop('checked'), + refreshInterval: $("#autoRefreshInterval").val() + }; - console.log("filter: " + JSON.stringify(f)); + if (queryId) + q._id = queryId; + + //get all query builders + $.each($('#ogrid-ds-content').find('.query-builder'), function(i,v) { + //for each data type selected, build query to save + var typeId = $(v).data('typeId'); + var tabId = $(v).data('parentId'); + var r = me._getRendition(tabId); + delete r.fillColor; //fillColor is calculated, no need to save + + $(v)[0].queryBuilder.setOptions({display_errors: !me._hasEmptyFilter(v)}); + //get mongo-specific query + var f = $(v).queryBuilder('getMongo'); + + console.log("filter: " + JSON.stringify(f)); + if (!ogrid.isNull(f) && !$.isEmptyObject(f)) { + console.log(JSON.stringify($(v).queryBuilder('getRules'))); + //alert(JSON.stringify($(v).queryBuilder('getRules'))); + q.spec.push({ + dataSetId: typeId, + filters: $(v).queryBuilder('getRules'), + rendition: r + }); + } else if (me._hasEmptyFilter(v)) { + q.spec.push({ + dataSetId: typeId, + filters: {}, + rendition: r + }); + } else { + throw ogrid.error('Search Error', 'Search criteria is invalid.'); + } + }); - if (!ogrid.isNull(f) && !$.isEmptyObject(f)) { - console.log(JSON.stringify($(v).queryBuilder('getRules'))); - //alert(JSON.stringify($(v).queryBuilder('getRules'))); - q.spec.push({ - dataSetId: typeId, - filters: $(v).queryBuilder('getRules'), - rendition: r - }); - } else if (me._hasEmptyFilter(v)) { - q.spec.push({ - dataSetId: typeId, - filters: {}, - rendition: r - }); - } else { - throw ogrid.error('Search Error', 'Search criteria is invalid.'); - } - }); + //get geo-filter settings + q.geoFilter = this._geoFilter.getSettings(); + return q; + }, - //get geo-filter settings - q.geoFilter = this._geoFilter.getSettings(); + //the parameters passed are attributes from original query that we want to keep and cannot be changed from the UI + _saveCurrentQuery: function(queryId, isCommon) { + try { + var q = this._getQuerySpec(queryId, isCommon); ogrid.Search.save({ query: q, - success: $.proxy(me._onSaveSuccess, me), - error: $.proxy(me._onSaveError, me) + success: $.proxy(this._onSaveSuccess, this), + error: $.proxy(this._onSaveError, this) }); } catch (ex) { ogrid.Alert.error(ex.message); @@ -642,9 +728,66 @@ ogrid.AdvancedSearch = ogrid.Class.extend({ //remove focus from button $('#ogrid-advanced-btn').blur(); + console.log("_hideMe"); }, - _onSubmit: function(e) { + _getMapLocation: function() { + //lat,long,zoom + var c = this._options.map.getMapCenter(); + return ( + c.lat + ',' + + c.lng + ',' + + this._options.map.getMapZoom() + ); + }, + + _updateHashWithQuery: function(query, autoexec) { + //we need to add some random value on the URL so it will always trigger a hasher change + var randomData = '&_=' + Math.floor(new Date().valueOf() * Math.random()); + //register with browser history + if (query.geoFilter && query.geoFilter.boundary === '_map-extent') { + hasher.setHash("query?q=" + JSON.stringify(query) + "&loc=" + this._getMapLocation() + "&run=" + autoexec + randomData); + } else { + hasher.setHash("query?q=" + JSON.stringify(query) + "&run=" + autoexec + randomData); + } + }, + + _resetHash: function() { + hasher.setHash(""); + }, + + _isShapeDataLoaded: function(me, query) { + //wait for a max of 5 secs + return ogrid.waitForCondition(10000, function(deferred) { + try { + //try restore again + me._geoFilter.restoreSettings(query.geoFilter); + + me._geoFilter.getGeoFilter(); + deferred.resolve('done'); + } catch (ex) { + console.log(ex.message); + } + }); + }, + + _doSearch: function(me, search, e) { + //if geoSpatial filtering is supported by service, send geoSpatial filters + if ( ogrid.App.serviceCapabilities().geoSpatialFiltering && me._geoFilter.getSettings().boundary) { + search.geoFilter = me._geoFilter.getGeoFilter(); + } + //immediate execution + ogrid.Search.exec(search, {origin: 'advancedSearch', search: search, + //set new object to handle refresh on map extent change + // only on map extent location and if callback function is passed as param + regenerator: (me._isMapExtentSelected()) ? {handler: me, id: ogrid.guid()} : null, + + //new done callback for map extent change + done: (typeof e == 'function') ? e : null + }); + }, + + _executeSearch: function(query) { try { var me = this; @@ -658,12 +801,10 @@ ogrid.AdvancedSearch = ogrid.Class.extend({ $.each($('#ogrid-ds-content').find('.query-builder'), function(i,v) { var f = $(v).queryBuilder('getMongo'); - //console.log("filter: " + JSON.stringify(f)); - //allow empty filters //Issue #118 //if (!ogrid.isNull(f) && !$.isEmptyObject(f)) { - me._pendingQueries++; + me._pendingQueries++; //} }); @@ -703,23 +844,22 @@ ogrid.AdvancedSearch = ogrid.Class.extend({ throw ogrid.error('Search Error', 'Search criteria is invalid.'); } - //if geoSpatial filtering is supported by service, send geoSpatial filters - if ( ogrid.App.serviceCapabilities().geoSpatialFiltering && me._geoFilter.getSettings().boundary) { - search.geoFilter = me._geoFilter.getGeoFilter(); - } + //var wait = 0; + //if (me._geoFilter.getSettings().boundary && !me._isShapeDataLoaded()) {wait = 5000; console.log("Shape data not loaded yet. Waiting for X seconds");} - //immediate execution - ogrid.Search.exec(search, {origin: 'advancedSearch', search: search, - //set new object to handle refresh on map extent change - // only on map extent location and if callback function is passed as param - regenerator: (me._isMapExtentSelected()) ? {handler: me, id: ogrid.guid()} : null, + //now that we are supporting invoking query via URL, we have to handle condition + // where boundaries are not quite loaded yet + if ( me._geoFilter.getSettings().boundary) { + $.when( me._isShapeDataLoaded(me, query) ).done(function () { + me._doSearch(me, search); + }); + } else { + me._doSearch(me, search); + } - //new done callback for map extent change - done: (typeof e == 'function') ? e : null - }); }); - //auto-hide Advanced Search pane after Submit (no longer done in mobile mode only) + //auto-hide Advanced Search pane after Submit (no longer done in mobile mode only) me._hideMe(); } catch (ex) { @@ -727,6 +867,21 @@ ogrid.AdvancedSearch = ogrid.Class.extend({ } }, + //now query submission is just triggering hash change + _onSubmit: function(e) { + try { + var id = null; + //if loaded from a previously saved query, maintain the query ID + if (this._activeQuery && this._activeQuery._id) { + id = this._activeQuery._id; + } + var q = this._getQuerySpec(id); + this._updateHashWithQuery(q, true); + } catch (ex) { + ogrid.Alert.error(ex.message); + } + }, + _hasEmptyFilter: function(qbuilder) { var r = true; //if operator container is empty, it must be an empty filter @@ -742,11 +897,14 @@ ogrid.AdvancedSearch = ogrid.Class.extend({ _onSubmitSuccess: function(data, passThroughData) { try { - //if geoSpatial filtering is not supported by service, implement filtering locally + //if geoSpatial filtering is not supported by service, implement filtering locally if ( !ogrid.App.serviceCapabilities().geoSpatialFiltering ) { //apply additional geo-spatial filter, if any is specified - if (this._geoFilter.getSettings().boundary) + if (this._geoFilter.getSettings().boundary) { + console.time("geoFilter.filterData:" + data.meta.view.id); data = this._geoFilter.filterData(data); + console.timeEnd("geoFilter.filterData:" + data.meta.view.id); + } } var rsId = ogrid.guid(); @@ -801,10 +959,10 @@ ogrid.AdvancedSearch = ogrid.Class.extend({ if (!this._options.allDataTypes) { //get all available data type descriptors from the service //ogrid.ajax(this, function(data) { - me._options.allDataTypes = this._options.datasets; - $('#dtTemplate').tmpl(this._options.datasets).appendTo('#ogrid-dtlist'); + me._options.allDataTypes = this._options.datasets; + $('#dtTemplate').tmpl(this._options.datasets).appendTo('#ogrid-dtlist'); - $('#ogrid-dtlist').find('li').click($.proxy(me._onDataTypeAdd, me)); + $('#ogrid-dtlist').find('li').click($.proxy(me._onDataTypeAdd, me)); //}, {url: '/datasets'}); } }, @@ -824,7 +982,7 @@ ogrid.AdvancedSearch = ogrid.Class.extend({ return t; }, -//caches look up values + //caches look up values _lookupLov: function(listId, done, me) { if (me._lookupLov[listId]) { done(me._lookupLov[listId]); @@ -844,6 +1002,18 @@ ogrid.AdvancedSearch = ogrid.Class.extend({ } }, + _getAutoComplete2: function(filter, column, me) { + me._lookupLov(column.listOfValuesId, + function(data){ + filter.autocomplete = { + lookup: data, + onSelect: function (suggestion) { + //alert('You selected: ' + suggestion.value + ', ' + suggestion.data); + } + }; + }, + me); + }, _filterSuggestions: function(query, data) { var a = $.map(data.suggestions, function(v, i) { @@ -991,8 +1161,30 @@ ogrid.AdvancedSearch = ogrid.Class.extend({ }); return a; }, - - _onColorOptionsClick: function(e) { + + _getDotSizers: function(ds) { + var a = []; + $.each(ds.columns, function(i, v) { + if (v.dotSizer) { + a.push ({ + columnId: v.id, + displayName: v.displayName, + calculator: v.dotSizer.calculator + }); + } + }); + if (a.length === 0) { + //default + a.push ({ + columnId: '', + displayName: '(No fields available)', + calculator: '' + }); + } + return a; + }, + + _onColorOptionsClick: function(e) { if ($(e.target).hasClass('panel-collapsed')) { // expand the panel $(e.target).parent().find('.panel-body').slideDown(); @@ -1007,15 +1199,25 @@ ogrid.AdvancedSearch = ogrid.Class.extend({ } }, + _getDotSizersSelect: function(ds) { + var a = this._getDotSizers(ds); + var s = ""; + $.each(a, function(i, v) { + s+='"'; + }); + return s; + }, + _loadNewTab: function(qspec) { $('#ogrid-ds-tabs').find('li').removeClass('active'); $('#ogrid-ds-content .tab-pane').removeClass('active'); var tabId = ogrid.guid(); + var ds = this._lookupDataType(qspec.dataSetId); $('#newDTTemplate').tmpl({ tabName: tabId, - label: this._lookupDataType(qspec.dataSetId).displayName + label: ds.displayName }).prependTo('#ogrid-ds-tabs'); //setup close event handler for X button on each data type tab @@ -1058,17 +1260,42 @@ ogrid.AdvancedSearch = ogrid.Class.extend({ //set values - $('#sizeSpin_' + tabId).val(qspec.rendition.size); $('#opacitySpin_' + tabId).val(qspec.rendition.opacity); - - //assign click handler for this clickable link + + //assign click handler for this clickable link $("#colorOptions_" + tabId).click(this._onColorOptionsClick); //hide color options explicitly $("#colorOptions_" + tabId).addClass("panel-collapsed"); $("#colorOptions_" + tabId).parent().find('.panel-body').css("display", "none!important"); - //$('#saveQueryAs').val(qspec.name); + $("#sizeSwitch_" + tabId).bootstrapToggle(); + + //populate dot sizer fields + //use raw Html, using nested jquery template does not seem to be working + $("#dotSizerFields_" + tabId).append(this._getDotSizersSelect(ds)); + + $("#sizeSwitch_" + tabId).change(function() { + if ($(this).prop('checked')) { + $("#spinnerGroup_" + tabId).addClass('hidden'); + $("#dotSizerFields_" + tabId).removeClass('hidden'); + } else { + $("#dotSizerFields_" + tabId).addClass('hidden'); + $("#spinnerGroup_" + tabId).removeClass('hidden'); + } + }); + + if (_.isObject(qspec.rendition.size)) { + //dot sizer was selected + $("#sizeSwitch_" + tabId).bootstrapToggle('on'); + $("#dotSizerFields_" + tabId).val(qspec.rendition.size.columnId); + //$("#spinnerGroup_" + tabId).addClass('hidden'); + //$("#dotSizerFields_" + tabId).removeClass('hidden'); + } else { + $('#sizeSpin_' + tabId).val(qspec.rendition.size); + //$("#dotSizerFields_" + tabId).addClass('hidden'); + //$("#spinnerGroup_" + tabId).removeClass('hidden'); + } }, _getSpinEventHandler: function(inputSelector, delta) { @@ -1084,11 +1311,10 @@ ogrid.AdvancedSearch = ogrid.Class.extend({ name: '', filters: {}, rendition: { - //defaults- get from config later //color: this._options.defaultPointColor, - color: $(e.target).data('color'), - opacity: $(e.target).data('opacity'), - size: $(e.target).data('size') + color: $(e.target).data('color'), + opacity: $(e.target).data('opacity'), + size: $(e.target).data('size') } }; $('#ogrid-ds-tabs').find('li').removeClass('active'); @@ -1107,5 +1333,9 @@ ogrid.AdvancedSearch = ogrid.Class.extend({ //re-invoke submit, passing along done callback this._onSubmit(done); } + }, + + getQueryOpener: function(autorun, context) { + return this._onManageQueryOpen(autorun, context); } }); \ No newline at end of file diff --git a/src/js/ux/Main.js b/src/js/ux/Main.js index dcb9a74..1f42804 100644 --- a/src/js/ux/Main.js +++ b/src/js/ux/Main.js @@ -20,6 +20,7 @@ ogrid.Main = ogrid.Class.extend({ //make this common now, so we don't have to retrieve multiple times _datasets: null, + _mapInit: $.Deferred(), //public attributes @@ -58,6 +59,61 @@ ogrid.Main = ogrid.Class.extend({ }, //private methods + _initRoutes: function() { + var me = this; + + crossroads.addRoute(new RegExp('^\/?query\?(.+)\/?$'), function(query) { + console.log('Route matched query resource'); + + //parse query params + var re = /(\?|&)([^=]+)=([^&]+)/img; + var match = re.exec(query); + var o = {loc:'', run: 'false'}; + while (match !== null) { + o[match[2]] = match[3]; + match = re.exec(query); + } + console.log(o); + + me._loadQuery( + o.q, + o.loc, + (o.run==='true')); + }); + + crossroads.bypassed.add(function() { + console.log('Route ignored - no match'); + }); + + hasher.initialized.add(function(h) { + console.log('hasher init: "' + h + '"'); + crossroads.parse(h); + }); + + hasher.changed.add(function(h) { + console.log('hasher changed: "' + h + '"'); + crossroads.parse(h); + }); + hasher.init(); + }, + + + _loadQuery: function(query, loc, autorun) { + try { + console.log("loadQuery"); + var q = JSON.parse(query); + + if (this._adv) { + var fn; + + fn = this._adv.getQueryOpener(autorun, this._adv); + fn(q, loc); + } + } catch (ex) { + ogrid.Alert.error("Invalid query parameter(s). " + ex.message); + } + }, + _setupActivityHooks: function() { //reset session timer when there is keyboard and mouse activity var me = this; @@ -93,54 +149,64 @@ ogrid.Main = ogrid.Class.extend({ ogrid.ajax(this, function(data) { if(typeof(data) === 'undefined' || data.error) { - me._datasets = data; + if (data.error) { + me.handleError("Loading datasets", data.error); + } else { + ogrid.Alert.error("List of available datasets cannot be retrieved."); + } } else { - me._datasets = data.sort(me._sortDs); - } - - //init commandbar - me._cb = new ogrid.CommandBar(me._options.commandbar, {datasets: me._datasets}); + me._datasets = data.sort(me._sortDs); + + //init commandbar + me._cb = new ogrid.CommandBar(me._options.commandbar, {datasets: me._datasets}); + + //some other map dependent Ux elements, but not moving to _initMapRelatedUx due to other dependencies + //we are going to just used the Deferred object + $.when( me._mapInit ).done(function() { + console.log("Map init done. Initializing advanced search and quick search"); + //init advanced search + me._adv = new ogrid.AdvancedSearch({map: me._map, datasets: me._datasets}); + + //init manage UI only if user has Manage access + if ( me._isAdmin(me._session.getCurrentUser().getProfile()) ) { + ogrid.adminUI( + $('#ogrid-admin-ui'), + {datasets: me._datasets} + ); + } - //init advanced search - me._adv = new ogrid.AdvancedSearch({map: me._map, datasets: me._datasets}); + //init quick search + me._qs = new ogrid.QSearch( + me._options.qsearch_div, + me._options.qsearch_input, + me._options.qsearch_button, + {datasets: me._datasets} + ); - //init manage UI only if user has Manage access - if ( me._isAdmin(me._session.getCurrentUser().getProfile()) ) { - ogrid.adminUI( - $('#ogrid-admin-ui'), - {datasets: me._datasets} - ); - } - //init quick search - me._qs = new ogrid.QSearch( - me._options.qsearch_div, - me._options.qsearch_input, - me._options.qsearch_button, - {datasets: me._datasets} - ); + //nav menu tweaks + me._setNavBarBehavior(); - if(!ogrid.Config.service.autologin) { - $( '#ogrid-menu .dropdown' ).removeClass( "hide" ); - } + me._timedOut = false; - //nav menu tweaks - me._setNavBarBehavior(); + //retrieve service capabilities + ogrid.ajax(me, function(data) { + me._serviceCaps = data; - me._timedOut = false; + //init our router + me._initRoutes(); - //broadcast that we're finished logged in, passing user profile - ogrid.Event.raise(ogrid.Event.types.LOGGED_IN, me._session.getCurrentUser()); + //broadcast that we're finished logged in, passing user profile + ogrid.Event.raise(ogrid.Event.types.LOGGED_IN, me._session.getCurrentUser()); - //retrieve service capabilities - ogrid.ajax(me, function(data) { - me._serviceCaps = data; - }, {url: '/capabilities'}); + }, {url: '/capabilities'}); + }); + } }, {url: '/datasets'}); }, - //performs an alphasort on the dataset based on the display name - _sortDs: function(a, b) { + //performs an alphasort on the dataset based on the display name + _sortDs: function(a, b) { if (!a || !b) return 0; @@ -151,29 +217,38 @@ ogrid.Main = ogrid.Class.extend({ }, _initMapRelatedUx: function(data) { - if (data) { - if (ogrid.Config.map.overlayLayers && Array.isArray(ogrid.Config.map.overlayLayers)) { - //append if there are static layers configured - //we have to concat to the dynamic data for the statics to appear first on the layer control - ogrid.Config.map.overlayLayers = data.concat(ogrid.Config.map.overlayLayers); - } else - ogrid.Config.map.overlayLayers = data; - } - - //init map - this._map = new ogrid.Map( - this._options.map, - //map options - ogrid.Config.map - ); - - //init other map dependent objects + try { + if (data) { + if (ogrid.Config.map.overlayLayers && Array.isArray(ogrid.Config.map.overlayLayers)) { + //append if there are static layers configured + //we have to concat to the dynamic data for the statics to appear first on the layer control + ogrid.Config.map.overlayLayers = data.concat(ogrid.Config.map.overlayLayers); + } else + ogrid.Config.map.overlayLayers = data; + } + //init map + this._map = new ogrid.Map( + this._options.map, + //map options + ogrid.Config.map + ); + console.log("Resolving _mapInit"); + //done with map init; let waiting blocks know + this._mapInit.resolve(); + + //init other map dependent objects + //init table view + this._tv = new ogrid.TableView($('#tableview'), $('#ogrid-nav-tabs'), $('#ogrid-tab-content'), + {map: this._map} + ); - //init table view - this._tv = new ogrid.TableView($('#tableview'), $('#ogrid-nav-tabs'), $('#ogrid-tab-content'), - {map: this._map} - ); + } catch (e) { + console.log("Resolving _mapInit in error"); + //done with map init; let waiting blocks know + this._mapInit.resolve(); + ogrid.Alert.error(e.message); + } }, _isAdmin: function(userProfile) { @@ -182,7 +257,6 @@ ogrid.Main = ogrid.Class.extend({ return ($.inArray('$admin', userProfile.resources) !== -1); }, - _postLogin: function() { //broadcast that we're finished logged in, passing user profile ogrid.Event.raise(ogrid.Event.types.LOGGED_IN, this._session.getCurrentUser()); @@ -324,7 +398,7 @@ ogrid.Main = ogrid.Class.extend({ return this._datasets; }, - serviceCapabilities: function() { + serviceCapabilities: function() { return this._serviceCaps; }, @@ -340,4 +414,5 @@ ogrid.Main = ogrid.Class.extend({ } } } -}); + +}); \ No newline at end of file diff --git a/src/js/ux/Map.js b/src/js/ux/Map.js index f93667f..83463ec 100644 --- a/src/js/ux/Map.js +++ b/src/js/ux/Map.js @@ -15,7 +15,7 @@ ogrid.Map = ogrid.Class.extend({ _locateControl: null, _legend: null, _lastZoom: 0, - _poppingMarker: false, + _handleMapViewChange: false, _options: { //lime @@ -88,18 +88,18 @@ ogrid.Map = ogrid.Class.extend({ _onPopupOpen: function() { try { //temporarily set flag to let _onMapViewChanged that it got triggered due to popup appearing - this._poppingMarker = true; + this._handleMapViewChange = false; } finally { var me = this; setTimeout(function() { - me._poppingMarker = false; + me._handleMapViewChange = true; }, 1000); } }, _onMapViewChanged: function() { //if movement is due to our popping marker, ignore - if (this._poppingMarker) return; + if (!this._handleMapViewChange) return; console.log('map view changed'); /*if (this._lastZoom !==0) { @@ -161,6 +161,20 @@ ogrid.Map = ogrid.Class.extend({ return r; }, + _onZoomEnd: function() { + /*if (this._lastZoom !==0) { + if (this._map.getZoom() > this._lastZoom) { + console.log("zoomed in") + } else { + console.log("zoomed out") + } + } else { + console.log("zoomed init") + }*/ + this._lastZoom = this._map.getZoom(); + }, + + _addContextMenu: function(options) { options.contextmenu = true; options.contextmenuWidth = 140; @@ -366,16 +380,47 @@ ogrid.Map = ogrid.Class.extend({ }, - //Issue #294 - //override Leaflet circlemarker _empty method, the renderer object does not seem to be updated on 'moveend' when we refresh our dots - //regen is an indicator that auto-requery is turned on; we might need this later + _getCalcCircleSize: function(sz, datasetId, properties) { + if (_.isObject(sz)) { + //size based on dotsizer + var n = properties[sz.columnId]; + if (n!==null) { + //replace token with actual value, and evaluate expression + try { + var v = eval(sz.calculator.replace("@v", n)); + return v; + } catch (ex) { + console.log("dot size evaluation error: "+ ex.message); + } + } else { + console.log("Warning: Minimum dot size is being assigned due to NULL value. datasetId=" + datasetId + ", columnId=" + sz.columnId); + return 2; + } + } + return sz; + }, + + + //override Leaflet circlemarker _empty method while we are investigating this more + //regen is an indicator that auto-requery is turned on _getOverrideCircleMarkerIsEmpty: function (that, regen) { return $.proxy(function() { - return !this._radius; + //if (!regen) { + // return this._radius && !this._renderer._bounds.intersects(this._pxBounds); + //} else { + //TODO ignore render bound for the time being when auto-requery is on + return !this._radius; + //} + /*//TODO Remove later - for debugging only + if (this._radius && !this._renderer._bounds.intersects(this._pxBounds)) { + console.log('!!!!- Empty circlemarker; radius=' + this._radius + ', pxBound=' + JSON.stringify(this._pxBounds) + ', renderer bounds: ' + JSON.stringify(this._renderer._bounds)); + } else { + console.log('!!!!- Non-empty circlemarker; radius=' + this._radius + ', pxBound=' + JSON.stringify(this._pxBounds) + ', renderer bounds: ' + JSON.stringify(this._renderer._bounds)); + } + return this._radius && !this._renderer._bounds.intersects(this._pxBounds);*/ }, that); }, - _onRefreshData: function (evtData) { try { //console.log('map refresh: ' + JSON.stringify(evtData)); @@ -398,7 +443,6 @@ ogrid.Map = ogrid.Class.extend({ //we need this for highlighting new data var latestDataTs = me._getLatestDataTs(data); - var resultsLayer = L.geoJson(data, { style: function (feature) { //can't use feature.properties.marker-color due to the dash on the name @@ -428,8 +472,11 @@ ogrid.Map = ogrid.Class.extend({ //return new L.Circle(latlng, data.meta.view.size, { var o = data.meta.view.options.rendition; + //TODO #239, bubbles on map prototype + var sz = me._getCalcCircleSize(o.size, data.meta.view.id, feature.properties); + var cm = new L.CircleMarker(latlng, { - radius: o.size, + radius: sz, color: me._getBorderColor( o.color, feature, @@ -437,12 +484,14 @@ ogrid.Map = ogrid.Class.extend({ me._markers[rsId].latestDataTs ), fillOpacity: (o.opacity/100), //pct - fillColor: o.fillColor + fillColor: o.fillColor, + weight: data.meta.view.options.rendition.borderWidth }); - - //Issue #294 Override _emepty method; missing dots look to be due to Leaflet renderer being out of sync at 'moveend' + //TODO Temp solution, while we're investigating missing dots due to Leaflet renderer being out of sync cm._empty = me._getOverrideCircleMarkerIsEmpty(cm, evtData.message.options.passthroughData.regenerator); + me._markers[rsId][ogrid.oid(feature)] = cm; + return cm; } }, @@ -495,13 +544,11 @@ ogrid.Map = ogrid.Class.extend({ } }, - _isMonitored: function(m) { return (m.options && m.options.passthroughData && m.options.passthroughData.monitorData && m.options.passthroughData.monitorData.monitorId); }, - _isAutoRequery: function(m) { return (m.options && m.options.passthroughData && m.options.passthroughData.done); }, @@ -567,15 +614,15 @@ ogrid.Map = ogrid.Class.extend({ } throw ogrid.error('Data Error', 'GeoJson data is not formatted properly. Unable to find meta/view attributes for column \'' + p + '\'.'); }, - -_clearLegend: function() { + + _clearLegend: function() { var legendDiv = this._legend.getContainer(); legendDiv.innerHTML = ''; //hide, since we have nothing to show $(legendDiv).addClass('hide'); }, - + _clear: function() { var me = this; this._map.eachLayer(function (layer) { @@ -596,10 +643,9 @@ _clearLegend: function() { //clear any remaining generated layers me._clearOwnLayersFromLayerControl(); - - //clear legend + + //clear legend me._clearLegend(); - }, //clear our own generated layers if any remains (usually unchecked ones that do not appear as a layer on the map) @@ -760,16 +806,20 @@ _clearLegend: function() { ); }, + //public methods getMapCenter: function () { return this._map.getCenter(); }, + getMapZoom: function () { + return this._map.getZoom(); + }, + getMap: function () { return this._map; }, - addHeatMapLayerFromExistingLayer: function(layer) { }, @@ -802,6 +852,7 @@ _clearLegend: function() { options.className = 'layer-opengrid'; var hl = L.heatLayer(data, options); + hl.addTo(this._map); //store opengrid data on the layer for later retrieval @@ -905,5 +956,19 @@ _clearLegend: function() { getGeoLocationControl: function() { return this._locateControl; + }, + + //for temporarily disabling _mapViewChange event handler + enableMapViewChangeHandler: function(flag) { + this._handleMapViewChange = flag; + }, + + + setMapView: function(center, zoom) { + console.log("map::setMapView"); + this._map.setView( + center, + zoom + ); } }); diff --git a/src/js/ux/QSearch.js b/src/js/ux/QSearch.js index a005ed5..9a0057e 100644 --- a/src/js/ux/QSearch.js +++ b/src/js/ux/QSearch.js @@ -176,6 +176,7 @@ ogrid.QSearch = ogrid.Class.extend({ }, _onExecDone: function (results) { + var autoRequery = this._isGeoFilterableData(results); //if geoSpatial filtering is not supported by service, implement filtering locally if ( !ogrid.App.serviceCapabilities().geoSpatialFiltering && this._isGeoFilterableData(results)) { if ( !this._geoFilter ) { diff --git a/src/js/ux/TableView.js b/src/js/ux/TableView.js index 4a00b8c..b6dc811 100644 --- a/src/js/ux/TableView.js +++ b/src/js/ux/TableView.js @@ -67,6 +67,9 @@ ogrid.TableView = ogrid.Class.extend({ $('#tableview').collapse('show'); } else { $('#tableview').collapse('hide'); + + //hide all popovers + $("a[data-toggle=popover][class=ogrid-arraycell]").popover('hide'); } }, @@ -170,6 +173,7 @@ ogrid.TableView = ogrid.Class.extend({ _populateTableRows: function(resultSetId, tableId, columns, data, showAutorefresh, lastRefreshed, creationTSColumn) { //$('#' + tableId).addClass('no-wrap'); + var chartOptions = this._getChartXAxisInfo(data.meta.view); this._tableOptions = $.extend(ogrid.Config.table.bootstrapTableOptions, { //our geoJson data in its original structure (data attribute is flattened post table creation) origData: data, @@ -181,7 +185,9 @@ ogrid.TableView = ogrid.Class.extend({ map: ogrid.App.map() }, graphOptions: { - groupFields: this._getGroupFields(data.meta.view.columns) + groupFields: this._getGroupFields(data.meta.view.columns), + xAxisField: chartOptions.xAxisField, + xAxisLabel: chartOptions.xAxisLabel }, columns: columns, height: this._getTableHeight(), @@ -223,6 +229,10 @@ ogrid.TableView = ogrid.Class.extend({ } }); + //events to handle re-activation of popovers (for now, use the same event handler) + $t.on('page-change.bs.table', $.proxy(this._onPageChange, this)); + $t.on('sort.bs.table', $.proxy(this._onPageChange, this)); + //highlight of clicked row $t.on('click', 'tbody tr', function(e) { $(this).addClass('highlight').siblings().removeClass('highlight'); @@ -232,18 +242,49 @@ ogrid.TableView = ogrid.Class.extend({ // by overriding existing event handler on export this._overrideExportBehavior($t); - //make sure height attribute is reflected, not doing it on init population - //fix unaligned headers and columns on data update - //$t.bootstrapTable('resetView'); + //with delay + this._activatePopovers(2000); + }, - /*$t.on('all.bs.table', function (e, name, args) { - console.log(name, args); - if (name === 'post-header.bs.table') { - setTimeout(function() { - $t.bootstrapTable('resetView'); - }, 500); - } - });*/ + _activatePopovers: function(delay) { + setTimeout(function() { + //instantiate popovers for this page + var $po = $("a[data-toggle=popover][class=ogrid-arraycell]").popover({ + container: 'body'}); + + //add our custom class to table popover so we can apply custom style + $.each( $po, function(i, v) { + var popover = $($("a[data-toggle=popover][class=ogrid-arraycell]").popover()[i]).data('bs.popover'); + var $tip = popover.tip(); + $tip.addClass('ogrid-arraycell-popover'); + + //only for debugging + $(v).on('shown.bs.popover', function (e) { + $tip.find('.close').bind('click', function () { + popover.hide(); + }); + //close other open popups when showing this popup + $("a[data-toggle=popover][class=ogrid-arraycell]").not($(v)).popover('hide'); + }); + $(v).on('hidden.bs.popover', function (e) { + //Fix issue with bootstrap popovers where instate.click is not reset on 'hide' + popover.inState.click = false; + }); + }); + }, delay); + }, + + _onCellClick: function(e, field, value, row) { + if (field === 'properties.what.violations') { + alert('properties.what.violations'); + } + console.log(field); + }, + + _onPageChange: function(e, number, size) { + console.log(e); + + this._activatePopovers(0); }, @@ -255,6 +296,21 @@ ogrid.TableView = ogrid.Class.extend({ }); }, + _getChartXAxisInfo: function(view) { + if (view.options.chart) { + return view.options.chart; + } else { + if (!view.options.creationTimestamp) { + throw ogrid.error('Configuration Error', 'No "creationTimestamp" field defined for this dataset (Table View)'); + } + return { + //by default, use creation timestamp column + "xAxisField": view.options.creationTimestamp, + "xAxisLabel": "Creation Date" + }; + } + }, + _overrideExportBehavior: function(t) { //get original event handler for export option @@ -307,16 +363,187 @@ ogrid.TableView = ogrid.Class.extend({ return 0; }, + _showDetails: function() { + console.log("show details"); + }, + + _isDateField: function(field) { + return (field.indexOf("$numberLong") > -1); + }, + + //parses column names from + _getPopoverColumnsMock: function(row, field) { + //{field: 'properties.' + data.meta.view.columns[i].id, title: data.meta.view.columns[i].displayName, sortable:true}; + return [ + {field: 'date', title: 'Date', sortable:false}, + {field: 'description', title: 'Description', sortable:false}, + {field: 'type', title: 'Type', sortable:false}, + ]; + }, + + _titleCase: function(str) { + return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();}); + }, + + _getPopoverColumns: function(row, field) { + var me = this; + var a = $.map(row, function(value, key) { + //flattened name example: properties..0. + var flatName = 'properties.' + field + '.0.'; //first element + var n = key.indexOf(flatName); + + //if found, parse rest of child object key name + if (n > -1) { + n+= flatName.length; + + //clean name + var rawName = key.substr(n, key.length-1); + return { + field: rawName, + title: me._titleCase(rawName.replace('.$numberLong','')), + sortable: false + }; + } + }); + if (a.length === 0) { + //default dummy column for simple types + a.push({ + field: '_value', + title: 'Value', + sortable: false + }); + } + return a; + }, + + _getPopoverDataMock: function(row, field) { + return [ + { date: '01/01/2016', description: 'some desc', type: 'some type'}, + { date: '01/02/2016', description: 'some des2c', type: 'some type2'} + ]; + }, + + _getPopoverData: function(row, field, subColumns) { + var i = 0; + var a = []; + var me = this; + + //max of X detailed records + for (i=0; i < ogrid.Config.table.arrayPopoverMax; i++) { + var o = {}; + $.each(subColumns, function(k, v) { + $.each(row, function(j, key) { + //flattened name example: properties..0. + //check if our 'reserved' simple array field + var flatName = 'properties.' + field + '.' + i; + if (v.field !== '_value') + flatName += '.' + v.field; + if (!row.hasOwnProperty(flatName)) + //break; + return false; + if (me._isDateField(v.field)) { + o[v.field] = moment(parseInt(row[flatName])).toString(); + } else { + o[v.field] = row[flatName]; + } + }); + }); + if (!$.isEmptyObject(o)) + a.push(o); + } + return a; + }, + + _escapeHTML: function(html) { + var htmlEscapes = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/' + }; + // Regex containing the keys listed immediately above. + var htmlEscaper = /[&<>"'\/]/g; + + return ('' + html).replace(htmlEscaper, function(match) { + return htmlEscapes[match]; + }); + }, + + //returns HTML for detail popover table + _getDetailHtml: function(row, field) { + var container = $("#ogrid-table-popover"); + var id = 'ogrid-popover-table_' + ogrid.guid(); + var close = ''; + var html = close + '
    '; + + container.html(html); + var cols = this._getPopoverColumns(row, field); + $('#' + id).bootstrapTable({ + data: this._getPopoverData(row, field, cols), + columns: cols, + height: 200, + }); + //hide the fixed table header to keep table lean + container.find('.fixed-table-header').remove(); + + //return escaped html + return this._escapeHTML(container.html()); + }, + + //context => this + _getActionFormatter: function(displayName, field, context) { + return function(value, row, index) { + return [ + 'View details' + ].join(' '); + }; + }, + + _getDateFormatter: function(format) { + return function(value, row, index) { + return moment(value).format(format); + }; + }, + _transformData: function(data) { var a = []; + var me = this; + + //event handlers for table cells + //placeholder only, currently not used + var actionEvents = { + 'mouseover .ogrid-arraycell': function (e, value, row) { + //console.log(row); + }, + 'click .ogrid-arraycell': function (e, value, row) { + //prevent click on link to trigger table row click + e.stopPropagation(); + } + }; + for (var i in data.meta.view.columns) { if (data.meta.view.columns[i].list) { //s += '' + data.meta.view.columns[i].displayName + ''; //build datatables column map at the same time var col = {field: 'properties.' + data.meta.view.columns[i].id, title: data.meta.view.columns[i].displayName, sortable:true}; + + //TODO #235 Honor new 'array' attribute + if ( data.meta.view.columns[i].array ) { + //set custom formatter for arrays + col.formatter = me._getActionFormatter(data.meta.view.columns[i].displayName, data.meta.view.columns[i].id, me); + col.events = actionEvents; + } if ( data.meta.view.columns[i].dataType === 'date' ) { //add a custom sorter for date columns col.sorter = $.proxy(this._sortDateValues, this); + + if (data.meta.view.columns[i].format) { + col.formatter = me._getDateFormatter(data.meta.view.columns[i].format); + } } a.push( col ); } diff --git a/src/js/ux/chart/Chart.js b/src/js/ux/chart/Chart.js index 2d7e7d6..14df631 100644 --- a/src/js/ux/chart/Chart.js +++ b/src/js/ux/chart/Chart.js @@ -9,10 +9,15 @@ ogrid.Chart = ogrid.Class.extend({ _options:{ title: '', data: null, - groupBy: '' + groupBy: '', + xAxisField: null, + dataName: null, + groupByFields: null, + xAxisLabel: null }, _container: null, _chart: null, + _containerClone: null, //public attributes @@ -25,6 +30,9 @@ ogrid.Chart = ogrid.Class.extend({ this._container = container; + //save original copy of the parent div + this._containerClone = $(this._container).clone(); + //update DOM from template this._container.html(this._getTemplate()); @@ -33,32 +41,87 @@ ogrid.Chart = ogrid.Class.extend({ this._container.on('shown.bs.modal', $.proxy(this._onModalShown, this)); $(window).resize($.proxy(this._onResize, this)); + + $('.ogrid-chart-body input[name="chartType"]').click($.proxy(this._onChartTypeClick, this)); + + //init group by dropdown + if (this._options.groupByFields) { + $('#jqtmpl-chart-groupby').tmpl(this._options.groupByFields).appendTo('#chart-groupby'); + + //always pick (None) by default + //$('#chart-groupby').val(this._options.groupByFields[0].name); + $('#chart-groupby').val(''); + } + $('#chart-groupby').change($.proxy(this._onGroupByChange, this)); }, //private methods - _onResize: function() { - /*if (this._chart) { + _onGroupByChange: function(e, done) { + switch ($(e.target).val()) { + case '': + //$('#optionDoughnut').addClass('disabled'); + //$("#typeDoughnut").prop('disabled', true); + if ($('.ogrid-chart-body :checked').attr('id') === 'typeDoughnut') { + //auto-pick one of the other options + $("#typeLine").prop('checked', true); + } + break; + default: + //$('#optionDoughnut').removeClass('disabled'); + //$("#typeDoughnut").prop('disabled', false); + } + //manually trigger to effect change + this._onChartTypeClick({target: $('.ogrid-chart-body :checked')}); + + }, + + _nest: function (seq, keys) { + var me = this; + if (!keys.length) + return seq; + var first = keys[0]; + var rest = keys.slice(1); + return _.mapValues(_.groupBy(seq, first), function (value) { + return me._nest(value, rest); + }); + }, + + _onChartTypeClick: function(e) { + this._options.groupBy = $("#chart-groupby").val(); + if ($(e.target).attr('value') !== 'doughnut') { + this._generateTimeSeriesChart($(e.target).attr('value'), false); this._chart.update(); - }*/ - //this._generate(false); - if ($('.ogrid-chart-legend').css('visibility') === 'hidden') { - //center the chart canvas (not happening with just css media query) - $('.ogrid-chart-canvas').css('margin-left', 'calc( 50% - 300px / 2)'); - $('.ogrid-chart-canvas').css('left', '0px'); - - //$(".ogrid-chart-canvas").attr("width", width); - //$(".ogrid-chart-canvas").attr("height", height); } else { - $('.ogrid-chart-canvas').css('margin-left', '30px'); - $('.ogrid-chart-canvas').css('left', 'auto'); + //if group by is (None), autoselect first group by field + if (this._options.groupBy ==='' && this._options.groupByFields) { + $('#chart-groupby').val(this._options.groupByFields[0].name); + this._options.groupBy = $("#chart-groupby").val(); + } + this._generateDoughnut(true); + } + if (this._options.groupBy && this._options.groupByFields) { + this._setModalTitle(this._options.dataName + ' Data Grouped By ' + _.find(this._options.groupByFields, {name:this._options.groupBy}).label); + } else { + this._setModalTitle(this._options.dataName + ' Data Grouped By Day'); } + }, + + _onResize: function() { //this._chart.resize(); //this._chart.update(); + this._centerModalY(); }, _onModalShown: function() { - this._generate(true); + //select line by default + //$('.ogrid-chart-body input[id="typeLine"]').prop('checked', true); + $("#typeLine").prop('checked', true); + if (!this._options.groupByFields) { + $("#typeDoughnut").prop('disabled', true); + } + //manually trigger to initialize display + this._onChartTypeClick({target: $('.ogrid-chart-body :checked')}); }, @@ -67,91 +130,348 @@ ogrid.Chart = ogrid.Class.extend({ // and to keep this widget more self-contained (may use an external file later if this becomes cumbersome) //we can use build-tools later to automate reading this from template to embedded string in our class return ''; + this._options.title + + '
    '; }, - _getChartData: function(rawData, groupField) { + _setModalTitle: function(title) { + $('.ogrid-chart-modal .modal-header').html(title + ''); + }, + + _getChartDataForDoughnut: function(rawData, groupField) { + var me = this; + + //group data by selected field + var groupedData = this._nest(rawData.features, ['properties["' + groupField + '"]']); + + var sortedValues = _.keys(groupedData).sort(); + var colors = _.map(sortedValues, function(v) { + return chroma.random().hex(); + }); + + return { + datasets: [{ + data: _.map(sortedValues, + function(v){ + return groupedData[v].length; + } ), + backgroundColor: colors, + hoverBackgroundColor: _.map(colors, function(c) { + return chroma(c).luminance(0.5).hex(); + }), + label: 'Dataset 1' + }], + labels: sortedValues + }; + }, + + _sortDays: function(a, b) { + if (!a || !b) + return 0; + + if (moment(a, 'M/D/YYYY').isAfter(moment(b), 'M/D/YYYY')) return 1; + if (moment(a, 'M/D/YYYY').isBefore(moment(b), 'M/D/YYYY')) return -1; + return 0; + }, + + //updates dataset object + //if grouped is null, resetVal is used as a constant value + _populateTimeSeriesData: function(dataset, values, grouped, resetVal) { + _.each( values, function(w) { + if (!dataset.hasOwnProperty(w)) { + var c = chroma.random().hex(); + + dataset[w] = { + label: w, //grouping field + backgroundColor: chroma(c).luminance(0.5).hex(), + borderColor: c, + fill: false, + data: [], + + //turn off line smoothing for line graphs + lineTension: 0 + }; + } + if (grouped) { + if (!_.isArray(grouped)) { + dataset[w].data.push(grouped[w].length); + } else { + //no group field specified + dataset[w].data.push(grouped.length); + } + } else { + dataset[w].data.push(resetVal); + } + + }); + }, + + _getTimeSeriesData: function(rawData, groupField) { + var me = this; + + //group data by day + var groupByList = [ + function(feature) { + //grouping by day + return moment(feature.properties[me._options.xAxisField]).format("M/D/YYYY"); + } + ]; + + if (groupField) groupByList.push('properties["' + groupField + '"]'); + + //group data + var groupedDataByDay = this._nest(rawData.features, groupByList); + + //array of unique days, sorted asc + var labels = _.keys(groupedDataByDay).sort(this._sortDays); + + var allGroupingValues = null; + if (groupField) { + //array of unique group values + allGroupingValues = _.uniq(_.map(rawData.features, 'properties["' + groupField + '"]')).sort(); + } + + var dataset={}; + + _.each(labels, function(v) { + var byDay = groupedDataByDay[v]; + var valuesWithData = ['All']; + + if (groupField) {valuesWithData = _.keys(byDay).sort();} + me._populateTimeSeriesData(dataset, valuesWithData, byDay); + + if (groupField) { + //we have to fill non-existent counts with 0 + var valuesWithNoData = _.difference(allGroupingValues, valuesWithData); + me._populateTimeSeriesData(dataset, valuesWithNoData, null, 0); + } + }); + + return { + labels: labels, + //transform to an array + datasets: _.map(dataset, function(v) { + return v; + }) + }; + }, + + _getChartDataForLine: function(rawData, groupField) { + var me = this; var counts = {}; //count occurrences by grouping field $.each(rawData.features, function(i, v){ var fieldVal = 'Unknown'; if (v.properties.hasOwnProperty(groupField) && v.properties[groupField] !=='' && v.properties[groupField] !== null) { - //record has a value on grouping field - fieldVal = v.properties[groupField]; + //record has a value on grouping field + creation date + fieldVal = v.properties[groupField] + ',' + moment(v.properties[me._options.xAxisField]).format("M/D/YYYY"); } if (counts.hasOwnProperty(fieldVal)) { counts[fieldVal].value++; } else { - var color = chroma.random().hex(); - //not in our map yet, initialize counts[fieldVal] = { value: 1, - color: color, - highlight: chroma(color).luminance(0.5).hex(), label: fieldVal }; } }); - return $.map(counts, function(v, i) { - return v; + + var dataset={}; + $.each(counts, function(i, v) { + var key = v.label; + var a = key.split(','); + var groupField = a[0]; + //var groupField = key; (if grouping by day only) + + if (!dataset.hasOwnProperty(groupField)) { + var c = chroma.random().hex(); + dataset[groupField] = { + label: groupField, //grouping field + backgroundColor: chroma(c).luminance(0.5).hex(), + borderColor: c, + fill: false, + data: [], + + //turn off line smoothing + lineTension: 0 + }; + } + dataset[groupField].data.push({ + x: a[1], + //x: groupField, (if grouping by day only) + y: counts[key].value + }); }); + return { + //transform to an array + datasets: $.map(dataset, function(v, i) { + return v; + }) + }; }, - _generate: function(animate) { + _generateTimeSeriesChart: function(type, groupByDayOnly) { + this._resetChart(); + var ctx = $('.ogrid-chart-canvas')[0].getContext("2d"); - var data = this._getChartData(this._options.data, this._options.groupBy); + //this._options.groupBy can be passed as null, if we want just counts by day + //var data = this._getTimeSeriesData(this._options.data, this._options.groupBy); + var data = this._getTimeSeriesData(this._options.data, (groupByDayOnly ? null : this._options.groupBy) ); + + var stepSize = 1; + var unit = 'day'; + if (data.labels.length >=30) { + if (data.labels.length >=60) { + if (data.labels.length >=120) { + unit = 'quarter'; + } else { + unit = 'month'; + } + } else { + unit = 'week'; + } + } else { + stepSize = Math.floor(data.labels.length /7); + } + //unit = 'quarter'; var options = { - //Boolean - Whether we should show a stroke on each segment - segmentShowStroke: true, + legend: { + position: 'top' + }, + responsive: true, + scales: { + xAxes: [{ + type: "time", + display: true, + scaleLabel: { + display: true, + labelString: this._options.xAxisLabel || 'Creation Date' + }, + time: { + unit: unit, //month, week, day, hour, quarter, year + displayFormats: { + quarter: '[Q]Q/YYYY', + month: 'M/YYYY', + week: '[W]W/YYYY', + day: 'M/D/YYYY', + hour: 'M/D/YYYY H:m' + }, + //1 week is max, otherwise adjust step size + unitStepSize: stepSize || 1 + }, + ticks: { callback: function( tick, index, ticks ){ + if(!(index % parseInt(ticks.length / 15))) { //15 max ticks regardless of unit + return tick; + } + return null; + } + } + }], + yAxes: [{ + display: true, + scaleLabel: { + display: true, + labelString: this._options.dataName + ' Records' + } + }] + } + }; - //String - The colour of each segment stroke - segmentStrokeColor: "#fff", + this._chart = new Chart(ctx, { + type: type, + data: data, + options: options + } + ); + //adjust modal layout + $('.ogrid-chart-modal').css("width", "60%"); + $('.ogrid-chart-modal').css("height", "auto"); + this._centerModalY(); + }, - //Number - The width of each segment stroke - datatrokeWidth: 2, + _centerModalY: function() { + setTimeout(function() { + //let the height refresh first + $('.ogrid-chart-modal').css("margin", (window.innerHeight - $('.ogrid-chart-modal .modal-content').height())/2 + "px auto auto auto"); + }, 200); + }, - //Number - The percentage of the chart that we cut out of the middle - percentageInnerCutout: 50, // This is 0 for Pie charts + _generateDoughnut: function() { + this._resetChart(); - //Number - Amount of animation steps - animationSteps: 100, + var ctx = $('.ogrid-chart-canvas')[0].getContext("2d"); - //Maximum - Number in display - MAXNUMBER: 100, + var data = this._getChartDataForDoughnut(this._options.data, this._options.groupBy); - //String - Animation easing effect - animationEasing: "easeOutBounce", + var options = { + legend: { + position: 'top' + }, + + animation: { + animateScale: true, + animateRotate: true, + animationEasing: "easeOutBounce", + animationSteps: 100 + }, + responsive: true - //Boolean - Whether we animate the rotation of the Doughnut - animateRotate: animate, + }; - //Boolean - Whether we animate scaling the Doughnut from the centre - //animateScale: animate, + //this._chart = new Chart(ctx).Pie(data, options); + this._chart = new Chart(ctx, + { + type: 'doughnut', + data: data, + options: options + } + ); - //responsive: true, + //adjust modal layout + //TODO implement in plain css later + if (window.innerWidth < 1300) { + $('.ogrid-chart-modal').css("width", "50%"); + } else { + $('.ogrid-chart-modal').css("width", "35%"); + } + this._centerModalY(); + }, - //String - A legend template - legendTemplate: "
      -legend\"><% for (var i=0; i
    • \"><%if(segments[i].label){%><%=segments[i].label%><%}%>
    • <%}%>
    " + //use this to prevent issue with painting new chart (Chart.js has a tendency to keep previous chart) + _resetChart: function(){ - }; + //copy with current styles to prevent Chart.js errors + //var $c = $('.ogrid-chart-canvas').clone(); - this._chart = new Chart(ctx).Pie(data, options); + //try { + if (this._chart) this._chart.destroy(); + //} catch (ex) { - //then you just need to generate the legend - var legend = this._chart.generateLegend(); + //} - //and append it to your page somewhere - $('.ogrid-chart-legend').html('Legend' + legend); + $('.ogrid-chart-canvas').remove(); + $('.ogrid-chart-body').append(''); + //$c.appendTo($('.ogrid-chart-body')); }, //public methods showModal: function() { + var me = this; + var p = $(this._container).parent(); this._container.modal({show: true}); + this._container.on('hidden.bs.modal', function () { + //clean destroy + $(me._container).remove(); + me._container = $(me._containerClone).clone(); + + //re-add restored copy + $(p).append(me._containerClone); + me._container = me._containerClone; + }); } }); From 4fc119a808987daf479a377e8392681e8d1c3301 Mon Sep 17 00:00:00 2001 From: Tom Schenk Jr Date: Mon, 26 Jun 2017 16:51:07 -0500 Subject: [PATCH 23/26] Fixed broken icons on README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c21a460..7ec4cfd 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![OpenGrid](img/branding/OpenGrid_Logo_Horizontal_3Color.png) -[![Build status-Linux](https://img.shields.io/travis/Chicago/opengrid/master.svg?style=flat-square&label=Linux build)](https://travis-ci.org/Chicago/opengrid)[![Build status-Windows](https://img.shields.io/appveyor/ci/tomschenkjr/opengrid/master.svg?style=flat-square&label=Windows build)](https://ci.appveyor.com/project/tomschenkjr/opengrid)[![Node.js dependencies](https://img.shields.io/coveralls/Chicago/opengrid/master.svg?style=flat-square)](https://coveralls.io/github/Chicago/opengrid)[![Node.js](https://img.shields.io/node/v/gh-badges.svg?style=flat-square)](https://david-dm.org/Chicago/opengrid)[![Node.js dependencies](https://img.shields.io/david/Chicago/opengrid.svg?style=flat-square)](https://david-dm.org/Chicago/opengrid)[![Node.js devdependencies](https://img.shields.io/david/dev/chicago/opengrid.svg?style=flat-square)](https://david-dm.org/Chicago/opengrid#info=devDependencies&view=table) +[![Build status-Linux](https://img.shields.io/travis/Chicago/opengrid/master.svg?style=flat-square&label=Linux%20build)](https://travis-ci.org/Chicago/opengrid)[![Build status-Windows](https://img.shields.io/appveyor/ci/tomschenkjr/opengrid/master.svg?style=flat-square&label=Windows%20build)](https://ci.appveyor.com/project/tomschenkjr/opengrid)[![Node.js dependencies](https://img.shields.io/coveralls/Chicago/opengrid/master.svg?style=flat-square)](https://coveralls.io/github/Chicago/opengrid)[![Node.js](https://img.shields.io/node/v/gh-badges.svg?style=flat-square)](https://david-dm.org/Chicago/opengrid)[![Node.js dependencies](https://img.shields.io/david/Chicago/opengrid.svg?style=flat-square)](https://david-dm.org/Chicago/opengrid)[![Node.js devdependencies](https://img.shields.io/david/dev/chicago/opengrid.svg?style=flat-square)](https://david-dm.org/Chicago/opengrid#info=devDependencies&view=table) OpenGrid an open-source, interactive map platform that allows users to explore multiple data sources in an easy-to-use interface. Developed to support situational awareness, incident monitoring and responses, historical data retrieval, and real-time advanced analytics. Users can perform advanced queries to filter data, search within custom boundaries, or based on the users location. Other GIS data, such as weather and Shapefiles can be overlaid on the map with other data. OpenGrid is natively compatible with desktops and mobile devices. From f723f909eee1f20d3232be46be9006b14876dc90 Mon Sep 17 00:00:00 2001 From: Rod Ladines Date: Thu, 29 Jun 2017 07:25:44 -0500 Subject: [PATCH 24/26] Fix to issue with embedding link in certain mail clients --- src/js/ux/AdvancedSearch.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/js/ux/AdvancedSearch.js b/src/js/ux/AdvancedSearch.js index c59b78f..99ef32a 100644 --- a/src/js/ux/AdvancedSearch.js +++ b/src/js/ux/AdvancedSearch.js @@ -744,11 +744,14 @@ ogrid.AdvancedSearch = ogrid.Class.extend({ _updateHashWithQuery: function(query, autoexec) { //we need to add some random value on the URL so it will always trigger a hasher change var randomData = '&_=' + Math.floor(new Date().valueOf() * Math.random()); + + //#issue 115; replace hash here since Hasher does not do it + //register with browser history if (query.geoFilter && query.geoFilter.boundary === '_map-extent') { - hasher.setHash("query?q=" + JSON.stringify(query) + "&loc=" + this._getMapLocation() + "&run=" + autoexec + randomData); + hasher.setHash("query?q=" + JSON.stringify(query).replace('#', encodeURIComponent('#')) + "&loc=" + this._getMapLocation() + "&run=" + autoexec + randomData); } else { - hasher.setHash("query?q=" + JSON.stringify(query) + "&run=" + autoexec + randomData); + hasher.setHash("query?q=" + JSON.stringify(query).replace('#', encodeURIComponent('#')) + "&run=" + autoexec + randomData); } }, @@ -1338,4 +1341,4 @@ ogrid.AdvancedSearch = ogrid.Class.extend({ getQueryOpener: function(autorun, context) { return this._onManageQueryOpen(autorun, context); } -}); \ No newline at end of file +}); From 91f66923990f7e2c8c477d8fb0473ade55743bcc Mon Sep 17 00:00:00 2001 From: Rod Ladines Date: Thu, 29 Jun 2017 07:26:47 -0500 Subject: [PATCH 25/26] Fixed issue with embedding links with certain mail clients --- src/js/ux/Main.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/js/ux/Main.js b/src/js/ux/Main.js index 1f42804..0060af1 100644 --- a/src/js/ux/Main.js +++ b/src/js/ux/Main.js @@ -74,6 +74,9 @@ ogrid.Main = ogrid.Class.extend({ match = re.exec(query); } console.log(o); + + //#issue 115; replace hash here or we'll get an error + o.q = o.q.replace(encodeURIComponent('#'), "#"); me._loadQuery( o.q, @@ -415,4 +418,4 @@ ogrid.Main = ogrid.Class.extend({ } } -}); \ No newline at end of file +}); From b975e161ce555fb23483ba35ce78107ce9c76ee5 Mon Sep 17 00:00:00 2001 From: Rod Ladines Date: Thu, 29 Jun 2017 09:03:01 -0500 Subject: [PATCH 26/26] Added 1.4.0 changes to dataset attributes --- docs/OpenGrid API.md | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/docs/OpenGrid API.md b/docs/OpenGrid API.md index d3b6636..47defb9 100644 --- a/docs/OpenGrid API.md +++ b/docs/OpenGrid API.md @@ -1,5 +1,5 @@

    OpenGrid REST Service
    Application Programming Interface (API)

    -

    Version 1.3.0

    +

    Version 1.4.0