diff --git a/src/scripts/collections/baseprovider.js b/src/scripts/collections/baseprovider.js
index 4bf0c1c..3eb56b4 100644
--- a/src/scripts/collections/baseprovider.js
+++ b/src/scripts/collections/baseprovider.js
@@ -64,6 +64,12 @@ module.exports = Backbone.Collection.extend({
expression.type,
'(' + expression.value.map(enclose).join(', ') + ')'
].join(' ')
+ } else if(expression.type === 'is not' || expression.type == 'is'){
+ return [
+ field,
+ expression.type,
+ expression.value
+ ].join(' ')
} else {
return [
field,
diff --git a/src/scripts/templates/card.html b/src/scripts/templates/card.html
index a21d39d..1078225 100644
--- a/src/scripts/templates/card.html
+++ b/src/scripts/templates/card.html
@@ -21,6 +21,24 @@
Export (CSV)
+
+
+
+ Toggle base collection
+
+
+
+
+
+ Zoom to filtered collection
+
+
+
+
+
+ Expand this card
+
+
diff --git a/src/scripts/templates/header.html b/src/scripts/templates/header.html
index 5aea48d..10e6581 100644
--- a/src/scripts/templates/header.html
+++ b/src/scripts/templates/header.html
@@ -1,5 +1,5 @@
-
+
<%= data.title %>
@@ -8,18 +8,23 @@ <%= data.title %>
<% } %>
-
+
-
-
Feedback
<% if(data.navigation && data.navigation.length) { %>
+
Quick links
<% } %>
+
+
+
+
This page was built with
VizWit, an interactive data visualization tool, by
Tim Wisniewski, the Chief Data Officer for the City of Philadelphia. VizWit uses open source technology and Socrata Open Data APIs to do awesome things.
+
diff --git a/src/scripts/views/bar.js b/src/scripts/views/bar.js
index 89fd3c4..3d45b61 100644
--- a/src/scripts/views/bar.js
+++ b/src/scripts/views/bar.js
@@ -23,9 +23,18 @@ module.exports = BaseChart.extend({
clustered: false,
lineColor: '#97bbcd',
balloonFunction: function (item, graph) {
- return '
' + item.category +
+ var baloonHtml = '' + item.category +
'
Total: ' + (+item.dataContext.value).toLocaleString() +
'
Filtered Amount: ' + (+item.dataContext.filteredValue).toLocaleString()
+ try{
+ var percentOfTotal = (parseFloat(item.dataContext.filteredValue)/parseFloat(item.dataContext.value)*100).toFixed(2)
+ if(percentOfTotal != "NaN"){ // NaN becomes "NaN" when converted to Fixed
+ baloonHtml += ' (' + percentOfTotal + '%)'
+ }
+ } catch(err){
+ console.log(err)
+ }
+ return baloonHtml
}
}
],
@@ -107,7 +116,9 @@ module.exports = BaseChart.extend({
_.bindAll(this, 'onClickCursor', 'onClickBar', 'onClickLabel', 'onHover', 'onClickScroll', 'zoomToBeginning')
},
events: {
- 'click .scroll a': 'onClickScroll'
+ 'click .scroll a': 'onClickScroll',
+ 'click .toggle-base-collection-link': 'onClickToggleBaseCollectionLink',
+ 'click .zoom-to-filtered-collection-link': 'onClickZoomToFilteredCollectionLink'
},
render: function () {
BaseChart.prototype.render.apply(this, arguments)
@@ -134,6 +145,15 @@ module.exports = BaseChart.extend({
if (this.chart.endIndex - this.chart.startIndex < this.collection.length) {
this.$('.scroll').removeClass('hidden')
}
+
+ // If there are filters, show the toggle for base collection and zoom filter collection buttons
+ if(_.isEmpty(this.filteredCollection.getFilters())){
+ this.$('.toggle-base-collection').hide()
+ this.$('.zoom-to-filtered-collection').hide()
+ } else {
+ this.$('.toggle-base-collection').show()
+ this.$('.zoom-to-filtered-collection').show()
+ }
},
zoomToBeginning: function () {
if (this.collection.length > this.chart.maxSelectedSeries) {
@@ -189,5 +209,50 @@ module.exports = BaseChart.extend({
}
})
}
+ },
+ onClickToggleBaseCollectionLink: function (e) {
+ var target = $(e.target)
+ // If target was the text, change target to the icon
+ if(!target.hasClass('toggle-base-collection-icon')){
+ target = target.children('.toggle-base-collection-icon')
+ }
+ // If the toggle is currently on, hide the first graph (base collection)
+ if(target.hasClass("fa-toggle-on")){
+ this.chart.hideGraph(this.chart.graphs[0])
+ } else {
+ this.chart.showGraph(this.chart.graphs[0])
+ }
+ // No matter what, toggle the icons classes to show the other one
+ target.filter('span.fa').toggleClass("fa-toggle-on")
+ target.filter('span.fa').toggleClass("fa-toggle-off")
+ e.preventDefault()
+ },
+ onClickZoomToFilteredCollectionLink: function (e) {
+ var target = $(e.target)
+ // If target was the text, change target to the icon
+ if(!target.hasClass('zoom-to-filtered-collection-icon')){
+ target = target.children('.zoom-to-filtered-collection-icon')
+ }
+
+ // If the toggle is currently on, hide the first graph (base collection)
+ if(target.hasClass("fa-search-plus")){ // zoom in
+ var filteredCollectionValues = _.map(this.filteredCollection.models, function(o){return parseInt(o.attributes.value)})
+ var filteredCollectionMaxValue = _.max(filteredCollectionValues) * 1.15 // include some top padding
+ this.chart.valueAxes[0].maximum = filteredCollectionMaxValue
+ this.chart.validateNow()
+ } else { // zoom out
+ var collectionValues = _.map(this.collection.models, function(o){return parseInt(o.attributes.value)})
+ var collectionMaxValue = _.max(collectionValues) * 1.15 // include some top padding
+ this.chart.valueAxes[0].maximum = collectionMaxValue
+ this.chart.validateNow()
+ }
+
+ // No matter what, toggle the icons classes to show the other one
+ target.filter('span.fa').toggleClass("fa-search-plus")
+ target.filter('span.fa').toggleClass("fa-search-minus")
+ e.preventDefault()
}
+
+
+
})
diff --git a/src/scripts/views/card.js b/src/scripts/views/card.js
index 12f3e42..42485f8 100644
--- a/src/scripts/views/card.js
+++ b/src/scripts/views/card.js
@@ -27,7 +27,8 @@ module.exports = Backbone.View.extend({
// Delegate event here so as not to have it overriden by child classes' events properties
this.events = _.extend({
'click .remove-filter': 'onClickRemoveFilter',
- 'click .embed-link': 'onClickEmbedLink'
+ 'click .embed-link': 'onClickEmbedLink',
+ 'click .expand-card': 'onClickExpandLink'
}, this.events || {})
this.delegateEvents()
@@ -105,5 +106,40 @@ module.exports = Backbone.View.extend({
var exportView = new EmbedHelperView({model: new Backbone.Model(this.config)})
this.$el.after(exportView.render().el)
e.preventDefault()
+ },
+ onClickExpandLink: function (e) {
+ var card_el = this.$el
+ if (card_el.attr("data-expanded") == "true") {
+ // Contract card height
+ card_el.attr("style",card_el.attr("data-original-styles"))
+ card_el.css("min-height",card_el.attr("data-original-min-height"))
+ card_el.height(card_el.attr("data-original-height")-15) // not sure why I need to make this smaller than it was
+ // Contract card width
+ var originalClasses = card_el.attr("data-original-classes")
+ card_el.addClass("col-sm-12")
+ card_el.attr("data-original-classes","")
+ card_el.attr("class",originalClasses)
+ // Set state as not expanded
+ card_el.attr("data-expanded","false")
+ } else {
+ // Expand width
+ var originalClasses = card_el.attr("class")
+ card_el.attr("data-original-classes",originalClasses)
+ card_el.removeClass(originalClasses)
+ card_el.addClass("col-sm-12")
+ // Expand height
+ card_el.attr("data-original-styles",card_el.attr("style"))
+ card_el.attr("data-original-height",card_el.height())
+ card_el.attr("data-original-min-height",card_el.css("min-height"))
+ card_el.css("min-height",parseInt(card_el.css("min-height")*1.5)+"px")
+ // Set state as expanded
+ card_el.attr("data-expanded","true")
+ }
+ this.setHeight()
+ if (this.config.chartType == "choropleth") {
+ // Leaflet maps need to be refreshed upon container size change
+ this.map.invalidateSize()
+ }
+ e.preventDefault()
}
})
diff --git a/src/scripts/views/datetime.js b/src/scripts/views/datetime.js
index d628bd4..7ef20b0 100644
--- a/src/scripts/views/datetime.js
+++ b/src/scripts/views/datetime.js
@@ -1,3 +1,4 @@
+var $ = require('jquery')
var _ = require('underscore')
var BaseChart = require('./basechart')
var numberFormatter = require('../util/number-formatter')
@@ -61,7 +62,7 @@ module.exports = BaseChart.extend({
categoryAxis: {
autoWrap: true,
parseDates: true,
- minPeriod: 'MM',
+ minPeriod: 'DD',
gridAlpha: 0,
guides: [{
lineThickness: 2,
@@ -79,7 +80,7 @@ module.exports = BaseChart.extend({
dataDateFormat: 'YYYY-MM-DDT00:00:00.000', // "2015-04-07T16:21:00.000"
creditsPosition: 'top-right',
chartCursor: {
- categoryBalloonDateFormat: 'MMM YYYY',
+ categoryBalloonDateFormat: 'DD MMM YYYY',
cursorPosition: 'mouse',
selectWithoutZooming: true,
oneBalloonOnly: true,
@@ -92,11 +93,24 @@ module.exports = BaseChart.extend({
_.bindAll(this, 'onClick')
},
+ events: {
+ 'click .toggle-base-collection-link': 'onClickToggleBaseCollectionLink',
+ 'click .zoom-to-filtered-collection-link': 'onClickZoomToFilteredCollectionLink'
+ },
render: function () {
BaseChart.prototype.render.apply(this, arguments)
// Listen to when the user selects a range
this.chart.chartCursor.addListener('selected', this.onClick)
+
+ if(_.isEmpty(this.filteredCollection.getFilters())){
+ this.$('.toggle-base-collection').hide()
+ this.$('.zoom-to-filtered-collection').hide()
+ } else {
+ this.$('.toggle-base-collection').show()
+ this.$('.zoom-to-filtered-collection').show()
+ }
+
},
// When the user clicks on a bar in this chart
onClick: function (e) {
@@ -130,5 +144,47 @@ module.exports = BaseChart.extend({
]
}
})
+ },
+ onClickToggleBaseCollectionLink: function (e) {
+ var target = $(e.target)
+ // If target was the text, change target to the icon
+ if(!target.hasClass('toggle-base-collection-icon')){
+ target = target.children('.toggle-base-collection-icon')
+ }
+ // If the toggle is currently on, hide the first graph (base collection)
+ if(target.hasClass("fa-toggle-on")){
+ this.chart.hideGraph(this.chart.graphs[0])
+ } else {
+ this.chart.showGraph(this.chart.graphs[0])
+ }
+ // No matter what, toggle the icons classes to show the other one
+ target.filter('span.fa').toggleClass("fa-toggle-on")
+ target.filter('span.fa').toggleClass("fa-toggle-off")
+ e.preventDefault()
+ },
+ onClickZoomToFilteredCollectionLink: function (e) {
+ var target = $(e.target)
+ // If target was the text, change target to the icon
+ if(!target.hasClass('zoom-to-filtered-collection-icon')){
+ target = target.children('.zoom-to-filtered-collection-icon')
+ }
+
+ // If the toggle is currently on, hide the first graph (base collection)
+ if(target.hasClass("fa-search-plus")){ // zoom in
+ var filteredCollectionValues = _.map(this.filteredCollection.models, function(o){return parseInt(o.attributes.value)})
+ var filteredCollectionMaxValue = _.max(filteredCollectionValues) * 1.15 // include some top padding
+ this.chart.valueAxes[0].maximum = filteredCollectionMaxValue
+ this.chart.validateNow()
+ } else { // zoom out
+ var collectionValues = _.map(this.collection.models, function(o){return parseInt(o.attributes.value)})
+ var collectionMaxValue = _.max(collectionValues) * 1.15 // include some top padding
+ this.chart.valueAxes[0].maximum = collectionMaxValue
+ this.chart.validateNow()
+ }
+
+ // No matter what, toggle the icons classes to show the other one
+ target.filter('span.fa').toggleClass("fa-search-plus")
+ target.filter('span.fa').toggleClass("fa-search-minus")
+ e.preventDefault()
}
})