From 9b548cafffd69d2db5a7b656100ea712f9689fd2 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Wed, 24 Apr 2013 14:08:32 -0500 Subject: [PATCH 001/112] Small fixes --- js/g3geoms.js | 4 ++-- js/g3subfigure.js | 2 +- js/htmltable.js | 3 +++ plots.R | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/js/g3geoms.js b/js/g3geoms.js index 66c19d0..0c0639f 100644 --- a/js/g3geoms.js +++ b/js/g3geoms.js @@ -89,11 +89,11 @@ // style("pointer-events",event?"auto":"none") // prevents hover a - // .transition() + .transition() .call(geom.position) a.exit() - // .transition() + .transition() .attr("r", 0) .remove() diff --git a/js/g3subfigure.js b/js/g3subfigure.js index 57ca9fd..1270fe5 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -600,7 +600,7 @@ xAxisNode .attr("transform", "translate(0," + height + ")") - .select("text") + .select("text").filter(function(d,i){return i==0}) .attr("x", width) .attr("y", -6) .style("text-anchor", "end") diff --git a/js/htmltable.js b/js/htmltable.js index 152f1e9..35b2dd1 100644 --- a/js/htmltable.js +++ b/js/htmltable.js @@ -371,6 +371,9 @@ headkeys.enter() .insert("th","th.value th.info") // keys go before values and info + .insert("th","th.value,th.info") // keys go before values and info + .insert("td","td.info td.value") // keyCells go before values + .insert("td","td.info,td.value") // keyCells go before values .attr("class","key") headkeys.order() diff --git a/plots.R b/plots.R index 0090ec1..5de0ac8 100644 --- a/plots.R +++ b/plots.R @@ -415,7 +415,7 @@ DNase_plot <- function(dataName) { geom="point", #onBrush=list(x=list(drag=list(filter=TRUE))), scales=list(x="linear",y="linear"), # want to use rangebanded linear here really. x will be misaligned - grid=list(key="Experiment", + grid=list(key=list(Experiment="Experiment"), group="Replicate", value="Response")#, # extents=list(y=0,x=with(aHist,breaks[length(breaks)])) From 56e4666b93400a99a8beac35443dd940ea36a21a Mon Sep 17 00:00:00 2001 From: Alex B Brown Date: Tue, 14 May 2013 04:55:38 +0000 Subject: [PATCH 002/112] set default date range for sunspots --- plotinputs.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plotinputs.R b/plotinputs.R index 4fd9b24..d5dcf53 100644 --- a/plotinputs.R +++ b/plotinputs.R @@ -42,8 +42,8 @@ AirPassengers_plotinput = function(...) { sunspots_plotinput = function(...) { tagList( - textInput("startDate","start date","-6974006400000"), - textInput("endDate","end date","439113600000") + textInput("startDate","start date","-4360176000000"), + textInput("endDate","end date","-4010428800000") ) } From 9fa28916c41a083a8d15bd4de564fc6efa1c12e9 Mon Sep 17 00:00:00 2001 From: Alex B Brown Date: Tue, 14 May 2013 04:55:56 +0000 Subject: [PATCH 003/112] update weblinks --- ui.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui.R b/ui.R index 6600de7..db02ad8 100644 --- a/ui.R +++ b/ui.R @@ -7,7 +7,7 @@ require(shiny); source("shiny_extend_g3.R") -static_links=list(`Github for g3plot`="https://github.com/404", +static_links=list(`Github for g3plot`="https://github.com/alexbbrown/g3plot-1", `Shiny`="http://shiny.rstudio.org", `D3`="http://d3js.org", `Google Groups`="http://groups.google.com/group/shiny-discuss") @@ -21,10 +21,10 @@ lapply(Sys.glob(c("*.R","*.js","js/*.js")), shinyUI(pageWithSidebar( # Application title - headerPanel(tags$a(href="https://github.com/404","G3plot=D3(Shiny(data())"),windowTitle="G3plot for Shiny and D3"), + headerPanel(tags$a(href="https://github.com/alexbbrown/g3plot-1","G3plot=D3(Shiny(data())"),windowTitle="G3plot for Shiny and D3"), sidebarPanel( - tags$p("This page is mainly to discover why g3plot is bad by applying it to data samples."), + tags$p("A demo of Intel's (c) g3plot API (BSD licence) using standard R data sets."), h3("Data Selection"), tags$p("some runs have a plot function. Others need your help"), uiOutput("dataSetControls"), From 144040eceeb14edf62c3c614a6878228bb705226 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Thu, 5 Sep 2013 20:20:03 -0700 Subject: [PATCH 004/112] fix typo in title --- ui.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui.R b/ui.R index db02ad8..a70aca2 100644 --- a/ui.R +++ b/ui.R @@ -21,7 +21,7 @@ lapply(Sys.glob(c("*.R","*.js","js/*.js")), shinyUI(pageWithSidebar( # Application title - headerPanel(tags$a(href="https://github.com/alexbbrown/g3plot-1","G3plot=D3(Shiny(data())"),windowTitle="G3plot for Shiny and D3"), + headerPanel(tags$a(href="https://github.com/alexbbrown/g3plot-1","G3plot=D3(Shiny(data()))"),windowTitle="G3plot for Shiny and D3"), sidebarPanel( tags$p("A demo of Intel's (c) g3plot API (BSD licence) using standard R data sets."), From e7f7f4c7a7be25d6041ed2c8833670f684a88529 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Thu, 5 Sep 2013 20:20:14 -0700 Subject: [PATCH 005/112] default to Airquality while debugging HACK --- dynamicUI.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dynamicUI.R b/dynamicUI.R index f88e151..600d62d 100644 --- a/dynamicUI.R +++ b/dynamicUI.R @@ -16,7 +16,7 @@ output$dataSetControls <- renderUI({ if (is.null(hashParts())) return(NULL) hashDataSelected <- hashParts()$dataSet - selectedDataSets <- NULL + selectedDataSets <- "airquality" if (!is.null(hashDataSelected)) { selectedDataSets <- as.numeric(strsplit(hashDataSelected,"\\|")[[1]]) selectedDataSets <- intersect(selectedDataSets,groupedDataSets) From 4bdbe00665793fb85a723ed4e1429f3c7d851474 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Fri, 6 Sep 2013 09:10:23 -0700 Subject: [PATCH 006/112] add layers to README --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3edb3aa..0ee17be 100644 --- a/README.md +++ b/README.md @@ -74,13 +74,15 @@ Common Name | Entity | Arity | Class | File | Message Part Document | HTML | 1 | | UI.R | - | the web page report | DIV* | n | g3plotMultiPlex | g3widget.js | Array of arrays | the shiny output section | DIV | n | pane | g3report.js | Array of list(name=?) | a single formatted d3 object - one of "plot" "list" or other text -figure+data | DIV | 1 | plot | g3plot.js | ditto | A combination of a drawing region with linked html table +figure+grid | DIV | 1 | plot | g3plot.js | ditto | A combination of a drawing region with linked html table figure | SVG | 1 | d3svg | g3plot.js | ditto | A single drawing region with any contents subfigure | G | n | subplot | g3subfigure.js | List(name=?) | container for a plot with distinct axes, data, legends plot | G | 1 | plot | g3plot.js | ditto | the bit inside the axes y facet | G | J | facet | g3plot.js | aesthetic(YFacet=?) | the Jth horizontal slice with a personal clone of the Y scale x facet | G | K | y facet | g3plot.js | aesthetic(XCluster=?)| the Kth vertical slice of the Jth horizontal +layer | G | L | layer | g3plot.js | Array of list(name=?) | a single formatted d3 object - one of "layer" geom | ? | many | dot/bar/... | g3geom.js | aesthetic(geom=?) | an actual drawing component +grid | TABLE | 1 | XX?table | ? | like layer but grid | An html table TODO ---- From d78960b083a441c5611b497e51575567b31a8525 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Fri, 6 Sep 2013 09:10:43 -0700 Subject: [PATCH 007/112] HACK sample layers plot airquality --- plots.R | 49 +++++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/plots.R b/plots.R index 5de0ac8..ac4f923 100644 --- a/plots.R +++ b/plots.R @@ -427,30 +427,31 @@ airquality_plot <- function(dataName) { dataSet$row = rownames(dataSet) list( list(type="plot", - table=forceTableVector(dataSet), - name=paste0(dataName,"_AQ"), - structure=list(Rownames="row",Measurements=selfList(c("Temp","Solar.R"))), - aesthetic=list(Key="Rownames",XFilterKey="Rownames",Y=list("Measurements","Temp"), X=list("Measurements","Solar.R")), - #labels=list(x=field, y="Count"), - geom=c("voronoi","point"), - onBrush=list(x=list(drag=list(filter=TRUE))), - scales=list(x="linear",y="linear"), - labels=list(y="Temp",x="Solar Radiation"), - onZoom=T - ), - list(type="plot", - table=forceTableVector(dataSet), - name=dataName, - structure=list(Rownames="row",Measurements=selfList(c("Wind","Temp"))), - aesthetic=list(Key="Rownames",XFilterKey="Rownames",X=list("Measurements","Wind"), Y=list("Measurements","Temp")), - #labels=list(x=field, y="Count"), - geom=c("point"), # wanted to voronoi here BUT Wind:Temp has some duplicates which crash the algoritm. need to perterb - onBrush=list(x=list(drag=list(filter=TRUE))), - scales=list(x="linear",y="linear"), - labels=list(x="Wind",y="Temp"), - grid=list(Wind=list("Measurements","Wind"), - Temp=list("Measurements","Temp")), - onZoom=T + labels=list(y="Temp",x="Solar Radiation / Wind"), + onZoom=T, + scales=list(x="linear",y="linear"), + name=paste0(dataName,"_AQ"), + onBrush=list(x=list(drag=list(filter=TRUE))), + layers=list( + list(type="layer", + name="Solar", + data=forceTableVector(dataSet), + structure=list(Rownames="row",Measurements=selfList(c("Temp","Solar.R"))), + aesthetic=list(Key="Rownames",XFilterKey="Rownames", + Y=list("Measurements","Temp"), + X=list("Measurements","Solar.R")), + geom=c("voronoi","point") + ), + list(type="layer", + data=forceTableVector(dataSet), + name="Wind", + structure=list(Rownames="row",Measurements=selfList(c("Wind","Temp"))), + aesthetic=list(Key="Rownames",XFilterKey="Rownames", + X=list("Measurements","Temp")), + Y=list("Measurements","Wind"), + geom=c("point") # wanted to voronoi here BUT Wind:Temp has some duplicates which crash the algoritm. need to perturb + ) + ) ) ) } From 61cd54a287a6534a0f815068ed49b0499693a4ef Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Fri, 6 Sep 2013 09:11:09 -0700 Subject: [PATCH 008/112] don't forceTable in renderG3plot - we always do it manually, and layers have it in a different place --- shiny_extend_g3.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shiny_extend_g3.R b/shiny_extend_g3.R index 9535f7f..5de9f8c 100644 --- a/shiny_extend_g3.R +++ b/shiny_extend_g3.R @@ -47,7 +47,8 @@ renderG3Plot <- function(func) canoniseMessage = function(val) { if(val$type=="plot") { - val$table=forceTableVector(val$table) + # TODO: process layers - but no need since forceTableVector is always done manually +# val$table=forceTableVector(val$table) val } else { val From fcbb68274e30c0b3d1e78e4269ac18e128971d8b Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Fri, 6 Sep 2013 09:11:24 -0700 Subject: [PATCH 009/112] rename table to data in message (may change this later) --- js/aestheticUtils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/aestheticUtils.js b/js/aestheticUtils.js index f3dfda7..07dba8e 100644 --- a/js/aestheticUtils.js +++ b/js/aestheticUtils.js @@ -15,10 +15,10 @@ return recordWalker.other( // for each string S in structure, pull out the indexed element // of the array named S in data. - recordWalker.indexedMemberOf(message.table,index))(structure); + recordWalker.indexedMemberOf(message.data,index))(structure); } // the number of records: - var recordCount = _.values(message.table)[0].length + var recordCount = _.values(message.data)[0].length // use the walker to map the structure onto the data var records = d3.range(0,recordCount).map(buildRecord) From 436045c77f4ba7b2a9d6c132bf15a7aab7503353 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Fri, 6 Sep 2013 09:11:53 -0700 Subject: [PATCH 010/112] move pluralise to d3functional. should probably be in a structure utilities. --- js/g3functional.js | 3 +++ js/g3subfigure.js | 6 ++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/js/g3functional.js b/js/g3functional.js index eba5e67..fe0927e 100644 --- a/js/g3functional.js +++ b/js/g3functional.js @@ -19,6 +19,9 @@ } return followChainAccessor } + + // other utilities + exports.pluralise = function(x){return typeof(x)==="undefined"?[]:_.isArray(x)?x:[x]} })(typeof exports === 'undefined'? this['g3functional']={}: exports); diff --git a/js/g3subfigure.js b/js/g3subfigure.js index 1270fe5..c5a27d3 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -107,10 +107,9 @@ } // calculate unzoomed domains - var pluralise = function(x){return typeof(x)==="undefined"?[]:_.isArray(x)?x:[x]} // note that this domain calculation ignores any use of x0 or dx and might not get // scales right for objects with those. - var xData = _.pluck(aesData,"X").concat(pluralise(graph.extents && graph.extents.x)) + var xData = _.pluck(aesData,"X").concat(g3functional.pluralise(graph.extents && graph.extents.x)) if (graph.aesthetic.DX) xData = xData.concat(aesData.map(function(d){return +d.X+d.DX})) // assume right extend DX var numerise = function(x){return _.map(x,function(x){return +x})} @@ -235,8 +234,7 @@ case "ordinal": { // possible options here allow domain to be adjusted per x facet // this code is a clone of code above in ordinal - var pluralise = function(x){return typeof(x)==="undefined"?[]:_.isArray(x)?x:[x]} - var xData = _.pluck(facet.values,"X").concat(pluralise(graph.extents && graph.extents.x)) + var xData = _.pluck(facet.values,"X").concat(g3functional.pluralise(graph.extents && graph.extents.x)) if (graph.aesthetic.DX) xData = xData.concat(aesData.map(function(d){return +d.X+d.DX})) // assume right extend DX x.domain(xData) From d7ce58df7ac1abcdbc95c807a3976bfd34ed5934 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Fri, 6 Sep 2013 09:12:15 -0700 Subject: [PATCH 011/112] parse layer aesthetics in g3message --- js/g3message.js | 64 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 18 deletions(-) diff --git a/js/g3message.js b/js/g3message.js index ab01b1e..664a90d 100644 --- a/js/g3message.js +++ b/js/g3message.js @@ -2,7 +2,37 @@ (function(exports){ + // fill in missing parts of the message + setPlotDefaults = function(result) { + // TODO: do defaults (such as dimensions) + return(result) + } + + // generate col major structured records - by 'layer' + processLayer = function(message) { + + var strData = aestheticUtils.decodeData(message); + + // TODO: raise errors if the aes or structure is not satisfiable + + // create records with fields x,y,group from data + var aesData = strData.map(aestheticUtils.applyAesthetic(message.aesthetic)) + // derive effective structure of aesthetic + var aesStructure = aestheticUtils.applyAesthetic(message.aesthetic)(message.structure) + + return {error: undefined, + data: {message: message, // TODO: drop the message + structured: strData, // TODO: drop the strData + aesthetic: aesData + }, + metaData: {aestheticStructure: aesStructure}, + name: message.name + } + } + exports.loadMessage = function(el,naive_message) { + // TODO: remove el argument! + // The starting state for a graph is a simple XML structure (OR the old structure inherited from the old graph!) // // see g3widget.html for full details. @@ -19,33 +49,31 @@ // ... and others var message = g3message.validate(naive_message) - - if(message.table.timestamp) - message.table.timestamp = message.table.timestamp.map(function(x){return new Date(+x)}) - - // generate col major structured records - var strData = aestheticUtils.decodeData(message); + + // TODO: don't handle dates like this - handle them by SCALES instead. + //if(message.table.timestamp) + // message.table.timestamp = message.table.timestamp.map(function(x){return new Date(+x)}) - // create records with fields x,y,group from data - var aesData = strData.map(aestheticUtils.applyAesthetic(message.aesthetic)) - // derive effective structure of aesthetic - var aesStructure = aestheticUtils.applyAesthetic(message.aesthetic)(message.structure) + var layers = _.map(message.layers,processLayer) - return {error: undefined, - data: {message: message, // TODO: drop the message - structured: strData, - aesthetic: aesData - }, - metaData: {aestheticStructure: aesStructure}, - name: message.name + var result = {error: undefined, + name: message.name, + data: {message: message}, // TODO: clean this up - message at top? + layers: layers } + + result = setPlotDefaults(result) + + return(result) } // tune up the inbound message so it doesn't explode g3figure // or explode here. This could also be done in R, but why not? exports.validate=function validate(M) { - if(M.table==undefined) throw({message:"Missing data in message from server"}) + if(M.layers==undefined) throw({message: "Missing layers in message from server"}) + + //if(M.table==undefined) throw({message:"Missing data in message from server"}) // should clone M so it's still available for debugging From 11f6804581a06c8e52fb354ed93cff499d48ffab Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Fri, 6 Sep 2013 09:12:58 -0700 Subject: [PATCH 012/112] simplify margin calculations by creating a hasAesthetic routine --- js/g3figure.js | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/js/g3figure.js b/js/g3figure.js index 553dacc..c337c93 100644 --- a/js/g3figure.js +++ b/js/g3figure.js @@ -194,20 +194,43 @@ }) subFigure.exit().remove(); + // returns length of (first?) valid aesthetic or undefined if none + var hasAesthetic = function hasAesthetic(plan,aesthetic) { + // look in global aesthetic structure first + var aesthetics = [] + if (plan.metaData && plan.aestheticStructure && plan.metaData.aestheticStructure[aesthetic]) { + aesthetics.push(plan.metaData.aestheticStructure[aesthetic]) + } + // look in local aesthetic next + _.map(plan.layers,function(l){ + if (l.metaData.aestheticStructure[aesthetic]) { + aesthetics.push(l.metaData.aestheticStructure[aesthetic]) + } + }) + // return the length of the olength of the first aesthetic (or 1 for atomics or objects) + return _.isObject(aesthetics[1]) ? + _.keys(aesthetics[1]).length : !_.isUndefined(aesthetics[1]) + } - // calculate the inner sizes for the subFigure + // adjust figure margins where aesthetics require guides subFigure .each(function(plan,i){ var xAxisHeight = 0 var xClusterAxisHeight = 0 - if (plan.metaData.aestheticStructure.X) { + var legendWidth = 60 + if (hasAesthetic(plan,"X")) { xAxisHeight += 25 } - if (plan.metaData.aestheticStructure.XCluster) { + if (hasAesthetic(plan,"XCluster")) { + // 1+length because there's always an outer cluster to select all + xClusterAxisHeight = (1+hasAesthetic(plan,"XCluster")) * 20 + } + if (hasAesthetic(plan,"Color") || hasAesthetic(plan,"Fill")) { // 1+length because there's always an outer cluster to select all - xClusterAxisHeight = (1+_.keys(plan.metaData.aestheticStructure.XCluster).length) * 20 + legendWidth += 100 } + // TODO: enable user to disable showing guides var s=d3.select(this); // calculate the inner sizes. Should go in d3subfigure since @@ -218,7 +241,7 @@ var dimensions = plan.dimensions dimensions.margin = { top: (widgets.size=="history")?0:20, - right: 160, + right: legendWidth, bottom: (widgets.size=="mini")?10: (widgets.size=="history")?25:(xAxisHeight + xClusterAxisHeight), xcluster: xClusterAxisHeight, From 2da5bcfd47ed0ca5427fbdae2061dad925b4c611 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Fri, 6 Sep 2013 09:13:12 -0700 Subject: [PATCH 013/112] add layer notes as notes.txt --- notes.txt | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 notes.txt diff --git a/notes.txt b/notes.txt new file mode 100644 index 0000000..923cac1 --- /dev/null +++ b/notes.txt @@ -0,0 +1,31 @@ +notes: + +Unusual fields: +grid can come from ONE layer and ONE plot. OR rather it's a 'separate' plot? referencing the other data? fie! + +data can be a table or a name - which borrows from another layer? + +layers are named (or numbered?) + +structure is per layer +aes is per layer + +simplification is carried out either in server or in client, or via a special simplified server call + +stats? + +labels is BOTH at the plot and layer level - x and y should be global, but color may be by layer (e.g. a red point is a distinct idea from rainbow of lines, and will have separate legends?) If color is at the global layer then ONE legend is present. + +onZoom is global + +onBrush is ... global but local if it needs to extract a property of the data point rather than just the axis (maybe) + +scales is global (for sure)! + +name is global AND local + +geom is local for sure + +Xcluster aesthetic has to be the SAME across all layers + + From 67f44dd573078f2c3d482054ccfb4e440b71a5ba Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Fri, 6 Sep 2013 09:34:49 -0700 Subject: [PATCH 014/112] only close errors for this g3plot - not all on page. --- js/g3widget.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/g3widget.js b/js/g3widget.js index ce2040e..da54c56 100644 --- a/js/g3widget.js +++ b/js/g3widget.js @@ -40,8 +40,8 @@ d3.select(el).select("div.content").transition().style("visibility","visible"); - // Close all jquery error alerts. may be overkill - $(".alert-error").alert('close') + // Close all associated errors + $(el).find(".alert-error").alert('close') } })(typeof exports === 'undefined'? this['g3widget']={}: exports); From 2f7459d4d7f61ec1dfc42f8f4929cf6a1d3c711d Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Fri, 6 Sep 2013 13:36:59 -0700 Subject: [PATCH 015/112] move hasAethetic into aestheticutils (for now) --- js/aestheticUtils.js | 20 ++++++++++++++++++++ js/g3figure.js | 26 ++++---------------------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/js/aestheticUtils.js b/js/aestheticUtils.js index 07dba8e..01830b5 100644 --- a/js/aestheticUtils.js +++ b/js/aestheticUtils.js @@ -63,5 +63,25 @@ return _.isUndefined(filterWalker.walk(aesFilterSpec,record)) } } + + // utilities - shouldn't be here really since it's about the concrete plan containing aesthetics. + + // returns length of (first?) valid aesthetic or undefined if none + exports.hasAesthetic = function hasAesthetic(plan,aesthetic) { + // look in global aesthetic structure first + var aesthetics = [] + if (plan.metaData && plan.aestheticStructure && plan.metaData.aestheticStructure[aesthetic]) { + aesthetics.push(plan.metaData.aestheticStructure[aesthetic]) + } + // look in local aesthetic next + _.map(plan.layers,function(l){ + if (l.metaData.aestheticStructure[aesthetic]) { + aesthetics.push(l.metaData.aestheticStructure[aesthetic]) + } + }) + // return the length of the olength of the first aesthetic (or 1 for atomics or objects) + return _.isObject(aesthetics[1]) ? + _.keys(aesthetics[1]).length : !_.isUndefined(aesthetics[1]) + } })(typeof exports === 'undefined'? this['aestheticUtils']={}: exports); diff --git a/js/g3figure.js b/js/g3figure.js index c337c93..a9b500e 100644 --- a/js/g3figure.js +++ b/js/g3figure.js @@ -194,38 +194,20 @@ }) subFigure.exit().remove(); - // returns length of (first?) valid aesthetic or undefined if none - var hasAesthetic = function hasAesthetic(plan,aesthetic) { - // look in global aesthetic structure first - var aesthetics = [] - if (plan.metaData && plan.aestheticStructure && plan.metaData.aestheticStructure[aesthetic]) { - aesthetics.push(plan.metaData.aestheticStructure[aesthetic]) - } - // look in local aesthetic next - _.map(plan.layers,function(l){ - if (l.metaData.aestheticStructure[aesthetic]) { - aesthetics.push(l.metaData.aestheticStructure[aesthetic]) - } - }) - // return the length of the olength of the first aesthetic (or 1 for atomics or objects) - return _.isObject(aesthetics[1]) ? - _.keys(aesthetics[1]).length : !_.isUndefined(aesthetics[1]) - } - // adjust figure margins where aesthetics require guides subFigure .each(function(plan,i){ var xAxisHeight = 0 var xClusterAxisHeight = 0 var legendWidth = 60 - if (hasAesthetic(plan,"X")) { + if (aestheticUtils.hasAesthetic(plan,"X")) { xAxisHeight += 25 } - if (hasAesthetic(plan,"XCluster")) { + if (aestheticUtils.hasAesthetic(plan,"XCluster")) { // 1+length because there's always an outer cluster to select all - xClusterAxisHeight = (1+hasAesthetic(plan,"XCluster")) * 20 + xClusterAxisHeight = (1+aestheticUtils.hasAesthetic(plan,"XCluster")) * 20 } - if (hasAesthetic(plan,"Color") || hasAesthetic(plan,"Fill")) { + if (aestheticUtils.hasAesthetic(plan,"Color") || aestheticUtils.hasAesthetic(plan,"Fill")) { // 1+length because there's always an outer cluster to select all legendWidth += 100 } From c57ebe996f7d76ec3e6cb3379475bf8022a4ef71 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Fri, 6 Sep 2013 13:37:18 -0700 Subject: [PATCH 016/112] added setupLayerData, still integrating --- js/g3subfigure.js | 188 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 177 insertions(+), 11 deletions(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index c5a27d3..e994e80 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -43,9 +43,159 @@ cellFacet, // the cell facet selector dataPointSelector; // how to select all geoms. This should be easier + // for each layer, partition the data by facets and clusters and calculate the domains + subfigure.setupLayerData=function(layer){ + + // expects scaleX, width, margin etc to be already set-up by caller + + // unbundle the plot message + var aesthetic = layer.data.message.aesthetic + var position = layer.data.message.position + var strData = layer.data.structured + var aesData = layer.data.aesthetic + var aesStructure = layer.metaData.aestheticStructure + var partitionedNodes + + // Setup axes + if (!_.isUndefined(aesthetic.XCluster)) { + // Build the data struture for the hierarchical x-scale/X-axis. + // It would be nice if this were a real d3 axis type, but that would require + // nested heterogenous axis with variable size rangebands. Not possible today + partitionedNodes = g3xcluster.hierarchX(aesData,aesthetic.XCluster, aesStructure.XCluster, width, margin.xcluster) + + } else { + // do it anyway, with no data, for uniformity + partitionedNodes = g3xcluster.hierarchX(aesData,"IGNORE THIS MESSAGE", {}, width, margin.xcluster) + } + + // Do calculation for stacked bars. Not sure how this gets called. + if(position && position.x == "stack") { // is this X? + g3stats.barStack(aesData, "Fill") + } else { + if (!_.isUndefined(aesthetic.XCluster)) { + _.each(aesData,function(d){ + d.y=d.Y // y is simply Y + d.y0=0 // so that bars can be drawn from unstacked data, + }) } else { + _.each(aesData,function(d){ + d.y=d.Y // y is simply Y + d.y0=0 // so that bars can be drawn from unstacked data, + d.x=d.X + }) + } + } + + // Calculate the subset of axes/data for each y-facet (vertical 'small multiple') + var yFacetedData = g3figureDataUtils.facetData(aesData,"YFacet","Y"); + + // also split x, in order to calculate facet scales - per column + var xFacetedData = g3figureDataUtils.facetData(aesData,"XCluster","X") + + // calculate unzoomed domains + // note that this domain calculation ignores any use of x0 or dx and might not get + // scales right for objects with those. + var xData = _.pluck(aesData,"X").concat(g3functional.pluralise(graph.extents && graph.extents.x)) + if (aesthetic.DX) + xData = xData.concat(aesData.map(function(d){return +d.X+d.DX})) // assume right extend DX + var numerise = function(x){return _.map(x,function(x){return +x})} + + var xScale, yScale, color // layer xscale. should probably make this a hash to allow arbitrary + + switch(scaleX) { + case "ordinal": + xScale = d3.scale.ordinal() + .domain(xData); // could use pluck here + break; + case "date": + xScale = d3.time.scale() + .domain(g3math.grow2(1.05,d3.extent(numerise(xData)))) + break; + case "linear": + xScale = d3.scale.linear() + .domain(g3math.grow2(1.05,d3.extent(numerise(xData)))) + break; + case "unit": + xScale = d3.scale.ordinal() + .domain(1) // need to update this later. + break; + default: + throw("Unknown scale type: \""+scaleX+"\"") + break; + } + + + // all the y's share a DOMAIN at the moment - + // so build it here, once + switch (scaleY) { + case "log": + yScale = d3.scale.log() + // extent is over ALL layer data here - is that appropriate? not always. + .domain(d3.extent(aesData, function(d) { return d.Y; })) + break; + case "linear": + yScale = d3.scale.linear() + // temporarily suppress 0 inclusion + .domain(d3.extent(aesData, function(d) { return ((d.y0+d.y)||d.y); })).nice(); + break; + default: + throw("Unknown y scale type: \""+scaleY+"\"") + break; + } + + // TODO: manage Color and Fill as separate scales so both can be used. + var colorField= + _.chain(aesStructure) + .keys() + .intersection(["Color","Fill"]) + .first().value() + + switch(colorField && graph.scales[colorField] || "ordinal") { + case "ordinal": + color = d3.scale.category20(); + // Inject alpha sorted fields into color right now, + // to prevent color jitter when 'animating' between + // two sims for the same data. Could be better. + // TODO: should probably NOT be in layer logic? + if (colorField= + _.chain(aesStructure) + .keys() + .intersection(["Color","Fill"]) + .first().value()) + { + if (plan.data.message.extents && plan.data.message.extents[colorField]) { + color.domain(plan.data.message.extents[colorField]) + } else { + color.domain(_.unique(_.pluck(aesData,colorField)).sort()) + } + } + break; + case "linear": + color = d3.scale.log() + .domain(d3.extent(aesData, function(d) { return +d[colorField] })) + .range(["red","yellow"]); + break; + default: + color = null; + } + + // return the scales and data as calculated for one + // layer. This will be combined with other layers + // later, since layer is the innermost data set (apart from + // geom) + // + return { + xScale: xScale, + yScale: yScale, + color: color, + xFacetedData: xFacetedData, + yFacetedData: yFacetedData + } + } + subfigure.setupData=function(subfigureElement, _plan, newDimensions){ + // note: these are not writing to locals! el = subfigureElement plan = _plan dimensions=newDimensions @@ -58,10 +208,30 @@ if (plan.error != undefined) throw({message:"renderPlot error:" + plan.error}) // unbundle the plot message - graph = plan.data.message, - strData = plan.data.structured, - aesData = plan.data.aesthetic, - aesStructure = plan.metaData.aestheticStructure; + graph = plan.data.message + + // work out what the scales are + // TODO: this should be well known from defaults set up way earlier + + // Global scales for x, y, color + // this scale will be copied everywhere. for now. + scaleX = + (graph.scales && graph.scales.x && graph.scales.x) ? graph.scales.x : "linear" + if (!aestheticUtils.hasAesthetic(plan,"X")) { + scaleX = "unit" + } + + scaleY = + graph.scales && graph.scales.y && graph.scales.y || "linear" + + // don't choose colorfield yet since it's layer dependent and HAS the SAME SCALE (probably) + + // These don't exist yet. + //strData = plan.data.structured, + //aesData = plan.data.aesthetic, + //aesStructure = plan.metaData.aestheticStructure; + + var layerdata = _.map(plan.layers,subfigure.setupLayerData) // Setup axes if (!_.isUndefined(graph.aesthetic.XCluster)) { @@ -98,13 +268,7 @@ // also split x, in order to calculate facet scales - per column xFacetedData = g3figureDataUtils.facetData(aesData,"XCluster","X") - // Global scales for x, color - // this scale will be copied everywhere. for now. - scaleX = - (graph.scales && graph.scales.x && graph.scales.x) ? graph.scales.x : "linear" - if (_.isUndefined(graph.aesthetic.X)) { - scaleX = "unit" - } + // calculate unzoomed domains // note that this domain calculation ignores any use of x0 or dx and might not get @@ -114,6 +278,8 @@ xData = xData.concat(aesData.map(function(d){return +d.X+d.DX})) // assume right extend DX var numerise = function(x){return _.map(x,function(x){return +x})} + // TODO: Merge scales + switch(scaleX) { case "ordinal": xScale_master = d3.scale.ordinal() From fdb5a2513fe5478e61f426a4f42e957fcfc60c0e Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Fri, 6 Sep 2013 16:04:02 -0700 Subject: [PATCH 017/112] magic array object to object array convertor --- js/g3subfigure.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index e994e80..a827ffb 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -231,7 +231,13 @@ //aesData = plan.data.aesthetic, //aesStructure = plan.metaData.aestheticStructure; - var layerdata = _.map(plan.layers,subfigure.setupLayerData) + var layerdata1 = _.map(plan.layers,subfigure.setupLayerData) + + // convert the array of layer parameters to parameter arrays [{a:1},{a:2}] => {a:[1,2]} + var twoargs=function(f){return function(x,y){return f(x,y)}} + var objectArraysToArrayObjects=function(d){return (function(k){return _.object(k,_.map(k,function(x){return _.pluck(d,x)}))})(_.chain(d).map(_.keys).reduce(twoargs(_.unique)).value())} + + var layerData = objectArraysToArrayObjects(layerdata1) // Setup axes if (!_.isUndefined(graph.aesthetic.XCluster)) { From 6895aa10d542b76d713a1055c128ca873388b0ae Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Fri, 6 Sep 2013 22:44:30 -0700 Subject: [PATCH 018/112] typo --- js/g3xcluster.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/g3xcluster.js b/js/g3xcluster.js index f97e66d..871ba01 100644 --- a/js/g3xcluster.js +++ b/js/g3xcluster.js @@ -37,7 +37,7 @@ // the height used for y partitioning needs to take into account the // values row (which we aren't using, and the All row, which isn't in - // the origianl aesthetic), hence it's height*(n+2)/(n+1) + // the original aesthetic), hence it's height*(n+2)/(n+1) var xlength = 1+_.values(XClusterAesthetic).length var rowsToDrop=1 // rowsToDrop is trying to drop the inner nests, since From d15861f8466116942e1187c26190796e17b5b62b Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Fri, 6 Sep 2013 22:46:07 -0700 Subject: [PATCH 019/112] more notes --- notes.txt | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/notes.txt b/notes.txt index 923cac1..b0a8b3a 100644 --- a/notes.txt +++ b/notes.txt @@ -1,4 +1,13 @@ -notes: +Notes on adding layers: +=========== + +Warning notes: +---------- + +Keep it functional wherever possible by adding a normalise and letting it accept old style data. + +Message notes: +---------- Unusual fields: grid can come from ONE layer and ONE plot. OR rather it's a 'separate' plot? referencing the other data? fie! @@ -26,6 +35,19 @@ name is global AND local geom is local for sure -Xcluster aesthetic has to be the SAME across all layers +Architecture notes +---------- + +Consider simply welding all the layers together once we have transformed into aesthetic (and keeping a layer reference in the object for filtering, etc.) Use nest to flip the structure in and out of shape. This would make the rework a whole lot easier. How would we handle geoms? Would we be able to handle reloading easily? + +Aesthetic notes: +----------- + +re-use of Color will be merged + +are Color and Fill separate? + +Xcluster aesthetic has to be the SAME across all layers - oh, but how do we calculate the partitions when there are multiple layers? TODO +position is local From 9dbc284aabbc17f89d3a57ffea62cca0479addb7 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Fri, 6 Sep 2013 23:18:50 -0700 Subject: [PATCH 020/112] fix plot layer spec in airquality example --- plots.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plots.R b/plots.R index ac4f923..21a674a 100644 --- a/plots.R +++ b/plots.R @@ -447,8 +447,8 @@ airquality_plot <- function(dataName) { name="Wind", structure=list(Rownames="row",Measurements=selfList(c("Wind","Temp"))), aesthetic=list(Key="Rownames",XFilterKey="Rownames", - X=list("Measurements","Temp")), - Y=list("Measurements","Wind"), + X=list("Measurements","Temp"), + Y=list("Measurements","Wind")), geom=c("point") # wanted to voronoi here BUT Wind:Temp has some duplicates which crash the algoritm. need to perturb ) ) From d0ed151562bebd83cac8d7c847e2f68a45d4ab65 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Fri, 6 Sep 2013 23:19:09 -0700 Subject: [PATCH 021/112] adjust processLayer to bind layer to each aesthetic data item --- js/g3message.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/js/g3message.js b/js/g3message.js index 664a90d..24c7bb2 100644 --- a/js/g3message.js +++ b/js/g3message.js @@ -9,24 +9,27 @@ } // generate col major structured records - by 'layer' - processLayer = function(message) { + processLayer = function(layer_message) { - var strData = aestheticUtils.decodeData(message); + var strData = aestheticUtils.decodeData(layer_message); // TODO: raise errors if the aes or structure is not satisfiable // create records with fields x,y,group from data - var aesData = strData.map(aestheticUtils.applyAesthetic(message.aesthetic)) + var aesData = strData.map(aestheticUtils.applyAesthetic(layer_message.aesthetic)) // derive effective structure of aesthetic - var aesStructure = aestheticUtils.applyAesthetic(message.aesthetic)(message.structure) + var aesStructure = aestheticUtils.applyAesthetic(layer_message.aesthetic)(layer_message.structure) + + // attach layer by reference to each node + _.forEach(aesData,function(a){a.layer=layer_message}) return {error: undefined, - data: {message: message, // TODO: drop the message + data: {message: layer_message, // TODO: drop the message structured: strData, // TODO: drop the strData aesthetic: aesData }, metaData: {aestheticStructure: aesStructure}, - name: message.name + name: layer_message.name } } From 958e0c3d27e9b83f6b58a0f11f776ccde08c5bbe Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Fri, 6 Sep 2013 23:19:51 -0700 Subject: [PATCH 022/112] simplify setupLayerData to become doLayerStats - and after that pull the aesthetics into a single array --- js/g3subfigure.js | 133 +++++----------------------------------------- 1 file changed, 12 insertions(+), 121 deletions(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index a827ffb..6f23894 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -43,30 +43,17 @@ cellFacet, // the cell facet selector dataPointSelector; // how to select all geoms. This should be easier - // for each layer, partition the data by facets and clusters and calculate the domains - subfigure.setupLayerData=function(layer){ + // for each layer, perform any stats which are required. After this, the layers + // are combined into a single array. Current stats are just barStack. + subfigure.doLayerStats=function(layer){ // expects scaleX, width, margin etc to be already set-up by caller // unbundle the plot message var aesthetic = layer.data.message.aesthetic var position = layer.data.message.position - var strData = layer.data.structured var aesData = layer.data.aesthetic var aesStructure = layer.metaData.aestheticStructure - var partitionedNodes - - // Setup axes - if (!_.isUndefined(aesthetic.XCluster)) { - // Build the data struture for the hierarchical x-scale/X-axis. - // It would be nice if this were a real d3 axis type, but that would require - // nested heterogenous axis with variable size rangebands. Not possible today - partitionedNodes = g3xcluster.hierarchX(aesData,aesthetic.XCluster, aesStructure.XCluster, width, margin.xcluster) - - } else { - // do it anyway, with no data, for uniformity - partitionedNodes = g3xcluster.hierarchX(aesData,"IGNORE THIS MESSAGE", {}, width, margin.xcluster) - } // Do calculation for stacked bars. Not sure how this gets called. if(position && position.x == "stack") { // is this X? @@ -85,112 +72,14 @@ } } - // Calculate the subset of axes/data for each y-facet (vertical 'small multiple') - var yFacetedData = g3figureDataUtils.facetData(aesData,"YFacet","Y"); + // TODO: here we can also assign static color mappings for each + // layer (doesn't have an entry in the layer yet) - // also split x, in order to calculate facet scales - per column - var xFacetedData = g3figureDataUtils.facetData(aesData,"XCluster","X") - - // calculate unzoomed domains - // note that this domain calculation ignores any use of x0 or dx and might not get - // scales right for objects with those. - var xData = _.pluck(aesData,"X").concat(g3functional.pluralise(graph.extents && graph.extents.x)) - if (aesthetic.DX) - xData = xData.concat(aesData.map(function(d){return +d.X+d.DX})) // assume right extend DX - var numerise = function(x){return _.map(x,function(x){return +x})} - - var xScale, yScale, color // layer xscale. should probably make this a hash to allow arbitrary - - switch(scaleX) { - case "ordinal": - xScale = d3.scale.ordinal() - .domain(xData); // could use pluck here - break; - case "date": - xScale = d3.time.scale() - .domain(g3math.grow2(1.05,d3.extent(numerise(xData)))) - break; - case "linear": - xScale = d3.scale.linear() - .domain(g3math.grow2(1.05,d3.extent(numerise(xData)))) - break; - case "unit": - xScale = d3.scale.ordinal() - .domain(1) // need to update this later. - break; - default: - throw("Unknown scale type: \""+scaleX+"\"") - break; - } - - - // all the y's share a DOMAIN at the moment - - // so build it here, once - switch (scaleY) { - case "log": - yScale = d3.scale.log() - // extent is over ALL layer data here - is that appropriate? not always. - .domain(d3.extent(aesData, function(d) { return d.Y; })) - break; - case "linear": - yScale = d3.scale.linear() - // temporarily suppress 0 inclusion - .domain(d3.extent(aesData, function(d) { return ((d.y0+d.y)||d.y); })).nice(); - - break; - default: - throw("Unknown y scale type: \""+scaleY+"\"") - break; - } + // TODO: here we can allow x and y transforms such as *10 or *2.5 to allow + // unlike quantities to be appropriately scales for parallel representation + // (yes I know it's a poor idea but it's often requested) - // TODO: manage Color and Fill as separate scales so both can be used. - var colorField= - _.chain(aesStructure) - .keys() - .intersection(["Color","Fill"]) - .first().value() - - switch(colorField && graph.scales[colorField] || "ordinal") { - case "ordinal": - color = d3.scale.category20(); - // Inject alpha sorted fields into color right now, - // to prevent color jitter when 'animating' between - // two sims for the same data. Could be better. - // TODO: should probably NOT be in layer logic? - if (colorField= - _.chain(aesStructure) - .keys() - .intersection(["Color","Fill"]) - .first().value()) - { - if (plan.data.message.extents && plan.data.message.extents[colorField]) { - color.domain(plan.data.message.extents[colorField]) - } else { - color.domain(_.unique(_.pluck(aesData,colorField)).sort()) - } - } - break; - case "linear": - color = d3.scale.log() - .domain(d3.extent(aesData, function(d) { return +d[colorField] })) - .range(["red","yellow"]); - break; - default: - color = null; - } - - // return the scales and data as calculated for one - // layer. This will be combined with other layers - // later, since layer is the innermost data set (apart from - // geom) - // - return { - xScale: xScale, - yScale: yScale, - color: color, - xFacetedData: xFacetedData, - yFacetedData: yFacetedData - } + return(aesData) } subfigure.setupData=function(subfigureElement, _plan, newDimensions){ @@ -231,7 +120,9 @@ //aesData = plan.data.aesthetic, //aesStructure = plan.metaData.aestheticStructure; - var layerdata1 = _.map(plan.layers,subfigure.setupLayerData) + var postTransformLayers = _.map(plan.layers,subfigure.doLayerStats) + + var aesData = d3.merge(postTransformLayers) // convert the array of layer parameters to parameter arrays [{a:1},{a:2}] => {a:[1,2]} var twoargs=function(f){return function(x,y){return f(x,y)}} From 0e134f119740d462c24ccd5b4f9829cc09709eda Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Sat, 7 Sep 2013 00:52:01 -0700 Subject: [PATCH 023/112] rename plotdatautils figuredatautils --- js/{g3plotDataUtils.js => g3figureDataUtils.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename js/{g3plotDataUtils.js => g3figureDataUtils.js} (100%) diff --git a/js/g3plotDataUtils.js b/js/g3figureDataUtils.js similarity index 100% rename from js/g3plotDataUtils.js rename to js/g3figureDataUtils.js From cbf1861b7ac36f4be12817403b6d984118ba6a9f Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Sat, 7 Sep 2013 00:52:28 -0700 Subject: [PATCH 024/112] save objectArraysToArrayObjects function in g3functional --- js/g3functional.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/js/g3functional.js b/js/g3functional.js index fe0927e..30611cf 100644 --- a/js/g3functional.js +++ b/js/g3functional.js @@ -23,5 +23,9 @@ // other utilities exports.pluralise = function(x){return typeof(x)==="undefined"?[]:_.isArray(x)?x:[x]} + // convert an array of objects to an object of arrays [{a:1},{a:2}] => {a:[1,2]} + // unused so far - just handy + var twoargs=function(f){return function(x,y){return f(x,y)}} + exports.objectArraysToArrayObjects=function(d){return (function(k){return _.object(k,_.map(k,function(x){return _.pluck(d,x)}))})(_.chain(d).map(_.keys).reduce(twoargs(_.unique)).value())} })(typeof exports === 'undefined'? this['g3functional']={}: exports); From db53bcb50fc9ecdbe739fda8dcd779c79e93530e Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Sat, 7 Sep 2013 00:52:52 -0700 Subject: [PATCH 025/112] thread layerdata through subfigure --- js/g3subfigure.js | 181 ++++++++++++++++++++++------------------------ 1 file changed, 86 insertions(+), 95 deletions(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index 6f23894..c9bf331 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -99,20 +99,6 @@ // unbundle the plot message graph = plan.data.message - // work out what the scales are - // TODO: this should be well known from defaults set up way earlier - - // Global scales for x, y, color - // this scale will be copied everywhere. for now. - scaleX = - (graph.scales && graph.scales.x && graph.scales.x) ? graph.scales.x : "linear" - if (!aestheticUtils.hasAesthetic(plan,"X")) { - scaleX = "unit" - } - - scaleY = - graph.scales && graph.scales.y && graph.scales.y || "linear" - // don't choose colorfield yet since it's layer dependent and HAS the SAME SCALE (probably) // These don't exist yet. @@ -124,40 +110,23 @@ var aesData = d3.merge(postTransformLayers) - // convert the array of layer parameters to parameter arrays [{a:1},{a:2}] => {a:[1,2]} - var twoargs=function(f){return function(x,y){return f(x,y)}} - var objectArraysToArrayObjects=function(d){return (function(k){return _.object(k,_.map(k,function(x){return _.pluck(d,x)}))})(_.chain(d).map(_.keys).reduce(twoargs(_.unique)).value())} - - var layerData = objectArraysToArrayObjects(layerdata1) + // Setup axes - // Setup axes - if (!_.isUndefined(graph.aesthetic.XCluster)) { + // xCluster Axis. this one is special because the structure MUST exist and be the same + // for all layers. This is not validated right now + // TODO: validate xCluster equivalence + // in the future we may allow one layer to apply to all clusters, but it's not clear + // right now how that might be implemented. + if (aestheticUtils.hasAesthetic(plan,"XCluster")) { // Build the data struture for the hierarchical x-scale/X-axis. // It would be nice if this were a real d3 axis type, but that would require // nested heterogenous axis with variable size rangebands. Not possible today - partitionedNodes = g3xcluster.hierarchX(aesData,graph.aesthetic.XCluster, aesStructure.XCluster, width, margin.xcluster) - + partitionedNodes = g3xcluster.hierarchX(aesData,plan.layers[0].aesthetic.XCluster, + plan.layers[0].metadata.aestheticStructure.XCluster, width, margin.xcluster) } else { // do it anyway, with no data, for uniformity partitionedNodes = g3xcluster.hierarchX(aesData,"IGNORE THIS MESSAGE", {}, width, margin.xcluster) } - - // Do calculation for stacked bars. Not sure how this gets called. - if(graph.position && graph.position.x == "stack") { // is this X? - g3stats.barStack(aesData, "Fill") - } else { - if (!_.isUndefined(graph.aesthetic.XCluster)) { - _.each(aesData,function(d){ - d.y=d.Y // y is simply Y - d.y0=0 // so that bars can be drawn from unstacked data, - }) } else { - _.each(aesData,function(d){ - d.y=d.Y // y is simply Y - d.y0=0 // so that bars can be drawn from unstacked data, - d.x=d.X - }) - } - } // Calculate the subset of axes/data for each y-facet (vertical 'small multiple') yFacetedData = g3figureDataUtils.facetData(aesData,"YFacet","Y"); @@ -165,18 +134,31 @@ // also split x, in order to calculate facet scales - per column xFacetedData = g3figureDataUtils.facetData(aesData,"XCluster","X") - - // calculate unzoomed domains // note that this domain calculation ignores any use of x0 or dx and might not get // scales right for objects with those. var xData = _.pluck(aesData,"X").concat(g3functional.pluralise(graph.extents && graph.extents.x)) - if (graph.aesthetic.DX) - xData = xData.concat(aesData.map(function(d){return +d.X+d.DX})) // assume right extend DX + if (aestheticUtils.hasAesthetic(plan,"DX")) + xData = xData.concat(aesData.map(function(d){return +d.X+d.DX?d.DX:0})) // assume right extend DX + var numerise = function(x){return _.map(x,function(x){return +x})} - // TODO: Merge scales - + // work out what the scales are + // TODO: this should be well known from defaults set up way earlier + + // Global scales for x, y, color + scaleX = + (graph.scales && graph.scales.x && graph.scales.x) ? graph.scales.x : "linear" + if (!aestheticUtils.hasAesthetic(plan,"X")) { + scaleX = "unit" + } + + scaleY = + graph.scales && graph.scales.y || "linear" + + var scaleColor = + graph.scales && graph.scales.color || "ordinal" + switch(scaleX) { case "ordinal": xScale_master = d3.scale.ordinal() @@ -195,63 +177,65 @@ .domain(1) // need to update this later. break; default: - throw("Unknown scale type: \""+"scaleX"+"\""); + throw("Unknown x scale type: \""+scaleX+"\""); break; } // all the y's share a DOMAIN at the moment - // so build it here, once - if (graph.scales && graph.scales.y && graph.scales.y == "log") { - yScale_master = d3.scale.log() + switch(scaleY) { + case "log": + yScale_master = d3.scale.log() // extent is over ALL data here - is that appropriate? not always. .domain(d3.extent(aesData, function(d) { return d.Y; })) - - } else { - yScale_master = d3.scale.linear() - - if (plan.data.message.extents && !_.isUndefined(plan.data.message.extents.y)) { - if (!_.isArray(plan.data.message.extents.y)) - plan.data.message.extents.y = [plan.data.message.extents.y] - yScale_master.domain(d3.extent(aesData.concat(_.map(plan.data.message.extents.y,function(y){return {y:y}})), - function(d) { return ((d.y0+d.y)||d.y); })).nice(); - } else { - // temporarily suppress 0 inclusion - yScale_master.domain(d3.extent(aesData, function(d) { return ((d.y0+d.y)||d.y); })).nice(); - } - } - - var colorField= - _.chain(aesStructure) - .keys() - .intersection(["Color","Fill"]) - .first().value() - switch(colorField && graph.scales[colorField] || "ordinal") { - case "ordinal": - color = d3.scale.category20(); - // Inject alpha sorted fields into color right now, - // to prevent color jitter when 'animating' between - // two sims for the same data. Could be better. - if (colorField= - _.chain(aesStructure) - .keys() - .intersection(["Color","Fill"]) - .first().value()) - { - if (plan.data.message.extents && plan.data.message.extents[colorField]) { - color.domain(plan.data.message.extents[colorField]) - } else { - color.domain(_.unique(_.pluck(aesData,colorField)).sort()) - } - } break; case "linear": - color = d3.scale.log() - .domain(d3.extent(aesData, function(d) { return +d[colorField] })) - .range(["red","yellow"]); + yScale_master = d3.scale.linear() + + if (plan.data.message.extents && !_.isUndefined(plan.data.message.extents.y)) { + if (!_.isArray(plan.data.message.extents.y)) + plan.data.message.extents.y = [plan.data.message.extents.y] + yScale_master.domain(d3.extent(aesData.concat(_.map(plan.data.message.extents.y,function(y){return {y:y}})), + function(d) { return ((d.y0+d.y)||d.y); })).nice(); + } else { + // temporarily suppress 0 inclusion + yScale_master.domain(d3.extent(aesData, function(d) { return ((d.y0+d.y)||d.y); })).nice(); + } break; default: - color = null; + throw("Unknown x scale type: \""+scaleX+"\""); + break; + } + + if(aestheticUtils.hasAesthetic(plan,"Color")) { + switch(graph.scales.color) { + case "ordinal": + color = d3.scale.category20(); + // Inject alpha sorted fields into color right now, + // to prevent color jitter when 'animating' between + // two sims for the same data. Could be better. + if (colorField= + _.chain(aesStructure) + .keys() + .intersection(["Color","Fill"]) + .first().value()) + { + if (plan.data.message.extents && plan.data.message.extents[colorField]) { + color.domain(plan.data.message.extents[colorField]) + } else { + color.domain(_.unique(_.pluck(aesData,colorField)).sort()) + } + } + break; + case "linear": + color = d3.scale.log() + .domain(d3.extent(aesData, function(d) { return +d[colorField] })) + .range(["red","yellow"]); + break; + default: + color = null; + } } xScale_current = xScale_master.copy() @@ -298,7 +282,7 @@ // possible options here allow domain to be adjusted per x facet // this code is a clone of code above in ordinal var xData = _.pluck(facet.values,"X").concat(g3functional.pluralise(graph.extents && graph.extents.x)) - if (graph.aesthetic.DX) + if (graph.aesthetic.DX) // TODO: fixme somehow xData = xData.concat(aesData.map(function(d){return +d.X+d.DX})) // assume right extend DX x.domain(xData) @@ -435,6 +419,10 @@ return d.key }) + // (re)build layers + + + var positionCellFacet = function(cellFacet) { if (cellFacetSVG) { // SVG has the advantage that it's clipped (non overflow) - good for @@ -625,6 +613,8 @@ return subfigure } + subfigure.setupLayers = function(){} + // redrawAxes draws the axes and legends. It should be called // at least once before redrawGeoms is called. (old comment) subfigure.redrawAxes = function redrawAxes(fast) @@ -634,7 +624,7 @@ // draw the XCluster axis // note deep clusters or XCluster + X axis can be visually ugly sometimes. - if (graph.aesthetic.XCluster) + if (aestheticUtils.hasAesthetic(plan,"XCluster")) { var clickEvent = graph.onClick && graph.onClick.XCluster && @@ -766,10 +756,11 @@ } ) // draw a legend if we have used any colors - if (graph.aesthetic.Color || graph.aesthetic.Fill) { + if (aestheticUtils.hasAesthetic(plan,"Color")) { legendAesthetic = graph.aesthetic.Color?"Color":"Fill" var legendPos = {x:width+5,y:0}; - var clickEvent = graph.onClick && graph.onClick[legendAesthetic] && + var clickEvent = graph.onClick && graph.onClick.Color && + // this will fail - TODO function(d){g3figure.filter.update(d?_.object([aesStructure[legendAesthetic]], [function(x){ return x==d From f3b3a993db5d3020290d744b62c7ea74daf3fc5a Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Mon, 9 Sep 2013 15:01:47 -0700 Subject: [PATCH 026/112] fix javascript filename issue --- g3widget.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/g3widget.html b/g3widget.html index 293704c..a9ac7aa 100644 --- a/g3widget.html +++ b/g3widget.html @@ -20,7 +20,7 @@ - + From ea97b56bc85193194ecf27c570d571b62509d881 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Mon, 9 Sep 2013 15:01:52 -0700 Subject: [PATCH 027/112] typo --- js/g3subfigure.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index c9bf331..853b430 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -41,7 +41,7 @@ yFacet, // the y (row) facet selector xFacetAxis, // the x (column) facet UI selector (does not contain cellfacets) cellFacet, // the cell facet selector - dataPointSelector; // how to select all geoms. This should be easier + dataPointSelector; // how to select all geoms. This should be easier, layered and multi-geom // for each layer, perform any stats which are required. After this, the layers // are combined into a single array. Current stats are just barStack. From be6679afaf4c1a3797353a0426d7b6dbbe674d40 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Mon, 9 Sep 2013 15:32:07 -0700 Subject: [PATCH 028/112] facetData only generates an extent when given an aesthetic (e.g. x for xcluster). This is to support layers, which have no twinned aesthetic in that sense. --- js/g3figureDataUtils.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/js/g3figureDataUtils.js b/js/g3figureDataUtils.js index 3df2ef2..73fe59f 100644 --- a/js/g3figureDataUtils.js +++ b/js/g3figureDataUtils.js @@ -22,6 +22,9 @@ }) .entries(dataToFacet) + if (_.isUndefined(coordAesthetic)) return dnest; + + // not convinced the code below is used. not needed for cell facets, anyway var coordAccessor = function(d){return d[coordAesthetic]} // now decorate the nested entries with yscale From 95926b13ab3daaddb67ec5e1d53eb521153c6c9b Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Mon, 9 Sep 2013 15:32:27 -0700 Subject: [PATCH 029/112] calculate cellFacets and layerFacets early --- js/g3subfigure.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index 853b430..53e275d 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -130,8 +130,20 @@ // Calculate the subset of axes/data for each y-facet (vertical 'small multiple') yFacetedData = g3figureDataUtils.facetData(aesData,"YFacet","Y"); + // Calculate the X subfacets (aka cellFacets) of each Y facet + _.forEach(yFacetedData,function(yFacet){ + yFacet.cellFacets = g3figureDataUtils.facetData(yFacet.values,"XCluster","X"); + // calculate the Layers in each cellFacet + _.forEach(yFacet.cellFacets,function(cellFacet){ + cellFacet.layerFacets = d3.nest().key(function(d) { + return d.layer.name; // TODO: check that all layers HAVE a name and are unique. + }) + .entries(cellFacet.values) + }) + }) + - // also split x, in order to calculate facet scales - per column + // also split global dataset x, in order to calculate facet scales - per column xFacetedData = g3figureDataUtils.facetData(aesData,"XCluster","X") // calculate unzoomed domains @@ -318,7 +330,7 @@ }) // configure each y facet, adding y-scales and axes - // also do x-faceting + // also do x-faceting to build cellfacets. _.map(yFacetedData,function(facet,i,allFacets){ var countFacets = allFacets.length @@ -343,7 +355,8 @@ // Calculate the data for just this xFacet. Note this is different // from xFacetedData which is for the whole 'column'. - facet.cellFacets = g3figureDataUtils.facetData(facet.values,"XCluster","X"); + // TODO: should this split happen much earlier? +// facet.cellFacets = g3figureDataUtils.facetData(facet.values,"XCluster","X"); // need to copy the master X facet data here for scales & friends var facetKeys = _.pluck(facet.cellFacets,"key") From acfe93fcf158244fa464020b507e5963c9a4c4e0 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Mon, 9 Sep 2013 16:52:10 -0700 Subject: [PATCH 030/112] nearly there - added layer elements, fixing up geoms to be compatible. --- js/g3subfigure.js | 167 ++++++++++++++++++++++++++++------------------ 1 file changed, 101 insertions(+), 66 deletions(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index 53e275d..18ee24f 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -41,6 +41,7 @@ yFacet, // the y (row) facet selector xFacetAxis, // the x (column) facet UI selector (does not contain cellfacets) cellFacet, // the cell facet selector + layerFacet, // the layer facet selector dataPointSelector; // how to select all geoms. This should be easier, layered and multi-geom // for each layer, perform any stats which are required. After this, the layers @@ -561,7 +562,7 @@ .attr("class","cell_facet") .call(positionCellFacet) // now create a rect background to the facet to catch events - + // // Zoom // @@ -569,6 +570,22 @@ cellFacet .transition() .call(positionCellFacet) + + // (re)build layers - each cellFacet has multiple drawing layers + // with indepdendent data, aesthetics and geoms. + // note: not a VAR because we save it. + layerFacet = cellFacet + .selectAll("g.layer_facet") + .data(function(d){ + return d.layerFacets + },function(d){ + return d.key + }) + + var newLayerFacet = layerFacet + .enter() + .append("g") + .attr("class","layer_facet") if (graph.onZoom) { xFacetAxis @@ -617,6 +634,11 @@ // note that cellfacets don't get axes - those are handled // only once per vertical , in the master xaxis object + // Layers + layerFacet + .exit() + .remove() // perhaps a little aggressive + cellFacet .exit() .transition() @@ -792,80 +814,93 @@ // redraw the currently displayed set of geoms. // fast indicates that the fast geom functions should be used that // do no transitions, joins, enters or exits and update only critical attributes + // Note that geoms are per layer. this means the geom key is false now - since multiple + // layers could use identical geoms subfigure.redrawGeoms = function(fast) { - var geoms = _.isArray(graph.geom)?graph.geom:[graph.geom] - _.map(geoms, function(geom) { - switch(geom) { - - case "voronoi": - cellFacet - .each(function(d,i){ - // note that for linear/linear plots voronoi points could be scaled later - - // at the point of drawing, which would save multiple calculations. However - // the same is not true of ordinal, since it's not clear that arbitrary - // ordinal values are real. - g3stats.voronoi(d.values,d.xScale,d.yScale) - }) - - - case "point": - case "point_bar": - case "bar": - case "range_bar": - case "point_range_bar": + layerFacet + .each(function(d,i){ + + var layerFacet = d3.select(this) + + if (d.values.length == 0) return; // TODO: fix this so the layer name is in the layerfacet (or a link) + // otherwise length 0 geoms cannot delete themselves (but maybe they don't exist) + + var geoms = d.values[0].layer.geom + _.map(geoms, function(geom) { + switch(geom) { - var clickEvent; - if (graph.onBrush && graph.onBrush.x && - graph.onBrush.x.drag && graph.onBrush.x.drag.input) { - - var clickEventInner = - g3events.updateShinyInputFromGeomFn(graph.onBrush.x.drag.input, - aesStructure.XFilterKey?"XFilterKey":"X") - - clickEvent = function(e) { - clickEventInner(d3.event.target.__data__) - d3.event.stopPropagation(); //? no idea what this does - }; - } - if (graph.onClick && graph.onClick.x && - graph.onClick.x.input) { - - var clickEventInner = - g3events.updateShinyInputFromGeomFn(graph.onClick.x.input, + case "voronoi": + layerFacet + .each(function(d,i){ + // note that for linear/linear plots voronoi points could be scaled later - + // at the point of drawing, which would save multiple calculations. However + // the same is not true of ordinal, since it's not clear that arbitrary + // ordinal values are real. + g3stats.voronoi(d.values,d.xScale,d.yScale) + }) + + + case "point": + case "point_bar": + case "bar": + case "range_bar": + case "point_range_bar": + + + var clickEvent; + if (graph.onBrush && graph.onBrush.x && + graph.onBrush.x.drag && graph.onBrush.x.drag.input) { + + var clickEventInner = + g3events.updateShinyInputFromGeomFn(graph.onBrush.x.drag.input, aesStructure.XFilterKey?"XFilterKey":"X") - - clickEvent = function(e) { - clickEventInner(d3.event.target.__data__) - d3.event.stopPropagation(); //? no idea what this does - }; - } - - if(!fast) - dataPointSelector = g3geoms[geom](cellFacet,function(d){return d.values},color,clickEvent) - .draw(cellFacet) - else - g3geoms[geom](cellFacet,function(d){return d.values},color,clickEvent) - .fast_redraw(cellFacet) + clickEvent = function(e) { + clickEventInner(d3.event.target.__data__) + d3.event.stopPropagation(); //? no idea what this does + }; + } + if (graph.onClick && graph.onClick.x && + graph.onClick.x.input) { + + var clickEventInner = + g3events.updateShinyInputFromGeomFn(graph.onClick.x.input, + aesStructure.XFilterKey?"XFilterKey":"X") - break; - case "line": // different way to send values - if(!fast) - dataPointSelector = g3geoms[geom](cellFacet,function(d){return d3.nest().key(function(d){return d.Color}).entries(d.values)},color,clickEvent) - .draw(cellFacet) - else - g3geoms[geom](cellFacet,function(d){return [d.values]},color,clickEvent) - .fast_redraw(cellFacet) + clickEvent = function(e) { + clickEventInner(d3.event.target.__data__) + d3.event.stopPropagation(); //? no idea what this does + }; + } - break; - - default: - throw({message:"Unknown geom=\""+geom+"\" in plot specification"}) - } - }) + + if(!fast) + dataPointSelector = g3geoms[geom](layerFacet,function(d){return d.values},color,clickEvent) + .draw(layerFacet) + else + g3geoms[geom](layerFacet,function(d){return d.values},color,clickEvent) + .fast_redraw(layerFacet) + + break; + case "line": // different way to send values + if(!fast) + dataPointSelector = g3geoms[geom](layerFacet,function(d){return d3.nest().key(function(d){return d.Color}).entries(d.values)},color,clickEvent) + .draw(layerFacet) + else + g3geoms[geom](layerFacet,function(d){return [d.values]},color,clickEvent) + .fast_redraw(layerFacet) + + break; + default: + throw({message:"Unknown geom=\""+geom+"\" in plot specification for layer XXX"}) + } + }) + + }) + return subfigure } From 490fab5645670838f1c070afe9e2e7c13882082d Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Mon, 9 Sep 2013 16:52:25 -0700 Subject: [PATCH 031/112] test case show only points (not voronoi) HACK --- plots.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plots.R b/plots.R index 21a674a..44afba5 100644 --- a/plots.R +++ b/plots.R @@ -440,7 +440,8 @@ airquality_plot <- function(dataName) { aesthetic=list(Key="Rownames",XFilterKey="Rownames", Y=list("Measurements","Temp"), X=list("Measurements","Solar.R")), - geom=c("voronoi","point") + #geom=c("voronoi","point") + geom=I(c("point")) ), list(type="layer", data=forceTableVector(dataSet), From 714dc9a20f314e341242e4501d95dc0c3dffc52a Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 10 Sep 2013 10:12:15 -0700 Subject: [PATCH 032/112] make sure geoms are preserved as arrays. This needs to be in g3widget or in JSON really. --- plots.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plots.R b/plots.R index 44afba5..6bec4a6 100644 --- a/plots.R +++ b/plots.R @@ -450,7 +450,7 @@ airquality_plot <- function(dataName) { aesthetic=list(Key="Rownames",XFilterKey="Rownames", X=list("Measurements","Temp"), Y=list("Measurements","Wind")), - geom=c("point") # wanted to voronoi here BUT Wind:Temp has some duplicates which crash the algoritm. need to perturb + geom=I(c("point")) # wanted to voronoi here BUT Wind:Temp has some duplicates which crash the algoritm. need to perturb ) ) ) From 8a60d1a8a6f01a187b9494ebbe4aec2e4f7fd521 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 10 Sep 2013 10:13:30 -0700 Subject: [PATCH 033/112] scale accessor needs to reach up an additional parent to skip the 'layer'. --- js/g3geoms.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/js/g3geoms.js b/js/g3geoms.js index 0c0639f..f9d5020 100644 --- a/js/g3geoms.js +++ b/js/g3geoms.js @@ -7,12 +7,14 @@ var parentD=function(n,old){return old && n.parentNode.__data_old__ ? n.parentNode.__data_old__ : n.parentNode.__data__} var parentD2=function(n){return n.parentNode.parentNode.__data__} + var parentD3=function(n){return n.parentNode.parentNode.parentNode.__data__} - // Get the parent's scale property - var xFacetScale=function(n,old){return parentD(n,old).xScale} - var yFacetScale=function(n,old){return parentD(n).yScale} + + // Get the parent cellfacet's scale property + var xFacetScale=function(n,old){return parentD2(n,old).xScale} + var yFacetScale=function(n,old){return parentD2(n).yScale} - var yFacetScale=function(n){return parentD2(n).yScale} + var yFacetScale=function(n){return parentD3(n).yScale} // NB: this keyfunction should return a unique key, which means x|color should be unique. // If not, then the code can fail. Current hierarchy implementation does generate a unique // X for each, but that will probably change, since the innermost X should probably be From 4692868af6f249309096dc53609b22367eb124e8 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 10 Sep 2013 10:13:46 -0700 Subject: [PATCH 034/112] Something odd happened to KEY function - not just in my new hack --- js/g3geoms.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/g3geoms.js b/js/g3geoms.js index f9d5020..887d022 100644 --- a/js/g3geoms.js +++ b/js/g3geoms.js @@ -23,7 +23,7 @@ // Key should uniquely identify a dot (part), and is used for animation. // Note that if key does NOT uniquely define a point then points may // be dropped - if (!_.isUndefined(d.Key)) return _.isDate(d.Key)?[""+(+d.Key)]:_.values(d.Key) + if (!_.isUndefined(d.Key)) return _.isDate(d.Key)?[""+(+d.Key)]:(_.isObject(d.Key)?_.values(d.Key):d.Key) return i } From 3923a004499550ffbdaf72cc29de7927777b7f92 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 10 Sep 2013 10:14:51 -0700 Subject: [PATCH 035/112] Make scaleColor work and only respond to Color aesthetic (no fills any more) --- js/g3subfigure.js | 53 +++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index 18ee24f..9338fc6 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -221,34 +221,33 @@ break; } - if(aestheticUtils.hasAesthetic(plan,"Color")) { - switch(graph.scales.color) { - case "ordinal": - color = d3.scale.category20(); - // Inject alpha sorted fields into color right now, - // to prevent color jitter when 'animating' between - // two sims for the same data. Could be better. - if (colorField= - _.chain(aesStructure) - .keys() - .intersection(["Color","Fill"]) - .first().value()) - { - if (plan.data.message.extents && plan.data.message.extents[colorField]) { - color.domain(plan.data.message.extents[colorField]) - } else { - color.domain(_.unique(_.pluck(aesData,colorField)).sort()) - } + switch(scaleColor) { + case "ordinal": + color = d3.scale.category20(); + // Inject alpha sorted fields into color right now, + // to prevent color jitter when 'animating' between + // two sims for the same data. Could be better. + // if (colorField= + // _.chain(aesStructure) + // .keys() + // .intersection(["Color","Fill"]) + // .first().value()) + if (aestheticUtils.hasAesthetic("Color")) + { + if (plan.data.message.extents && plan.data.message.extents.Color) { + color.domain(plan.data.message.extents.Color) + } else { + color.domain(_.unique(_.pluck(aesData,"Color")).sort()) } - break; - case "linear": - color = d3.scale.log() - .domain(d3.extent(aesData, function(d) { return +d[colorField] })) - .range(["red","yellow"]); - break; - default: - color = null; - } + } + break; + case "linear": + color = d3.scale.log() + .domain(d3.extent(aesData, function(d) { return +d.Color })) + .range(["red","yellow"]); + break; + default: + color = null; } xScale_current = xScale_master.copy() From 2353c81d0a1666c5063a14bb5e0b7f0f53fe8d9e Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 10 Sep 2013 10:15:34 -0700 Subject: [PATCH 036/112] reorder stat application to avoid xCluster application blowing away the good work of layer stats (ie restore the order to the same as before layer fix) --- js/g3subfigure.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index 9338fc6..de34aa1 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -106,10 +106,8 @@ //strData = plan.data.structured, //aesData = plan.data.aesthetic, //aesStructure = plan.metaData.aestheticStructure; - - var postTransformLayers = _.map(plan.layers,subfigure.doLayerStats) - - var aesData = d3.merge(postTransformLayers) + + var aesData = d3.merge(_.pluck(_.pluck(plan.layers,"data"),"aesthetic")) // Setup axes @@ -129,6 +127,8 @@ partitionedNodes = g3xcluster.hierarchX(aesData,"IGNORE THIS MESSAGE", {}, width, margin.xcluster) } + var postTransformLayers = _.map(plan.layers,subfigure.doLayerStats) + // Calculate the subset of axes/data for each y-facet (vertical 'small multiple') yFacetedData = g3figureDataUtils.facetData(aesData,"YFacet","Y"); // Calculate the X subfacets (aka cellFacets) of each Y facet From 9bb8102b068fe887339639ac07d2c78606e62746 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 10 Sep 2013 13:07:22 -0700 Subject: [PATCH 037/112] fix bug in asethetics that assumed 2 layers --- js/aestheticUtils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/aestheticUtils.js b/js/aestheticUtils.js index 01830b5..3a0d335 100644 --- a/js/aestheticUtils.js +++ b/js/aestheticUtils.js @@ -80,8 +80,8 @@ } }) // return the length of the olength of the first aesthetic (or 1 for atomics or objects) - return _.isObject(aesthetics[1]) ? - _.keys(aesthetics[1]).length : !_.isUndefined(aesthetics[1]) + return _.isObject(aesthetics[0]) ? + _.keys(aesthetics[0]).length : !_.isUndefined(aesthetics[0]) } })(typeof exports === 'undefined'? this['aestheticUtils']={}: exports); From 77b7b5d0b0f6025b9ceb8262b7f4c6155b37b557 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 10 Sep 2013 13:07:44 -0700 Subject: [PATCH 038/112] promote old plot spec to layered plot spec --- shiny_extend_g3.R | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/shiny_extend_g3.R b/shiny_extend_g3.R index 5de9f8c..974fbb6 100644 --- a/shiny_extend_g3.R +++ b/shiny_extend_g3.R @@ -42,18 +42,30 @@ renderG3Plot <- function(func) reactive({ # Rprof(NULL) # Rprof(append=F) - val <- func() - + canoniseMessage = function(val) { if(val$type=="plot") { # TODO: process layers - but no need since forceTableVector is always done manually -# val$table=forceTableVector(val$table) + # val$table=forceTableVector(val$table) + # todo: check all the aesthetics are mappable. + if (is.null(val$layers)) { + # update to layers + props_to_move <- c("layer","table","structure","aesthetic","geom") + props_to_copy <- c("name") + val$layers <- list() + val$layers[1] <- list(val[c(props_to_move,props_to_copy)]) + val[props_to_move] <- NULL + + # make sure geom is a list + val$layers[[1]]$geom=as.list(val$layers[[1]]$geom) + # data replaces the old 'table' name + if (is.null(val$layers[[1]]$data)) val$layers[[1]]$data <- val$layers[[1]]$table + } val } else { val } - # todo: check all the aesthetics are mappable. } # should strip out unused aes before sending, and check all aes are present, From ed344cfa5aae2b9ceba397f7f46d67c3f9edd254 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 10 Sep 2013 13:08:09 -0700 Subject: [PATCH 039/112] DX always included in scale calculation --- js/g3subfigure.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index de34aa1..b14b130 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -293,9 +293,8 @@ case "ordinal": { // possible options here allow domain to be adjusted per x facet // this code is a clone of code above in ordinal - var xData = _.pluck(facet.values,"X").concat(g3functional.pluralise(graph.extents && graph.extents.x)) - if (graph.aesthetic.DX) // TODO: fixme somehow - xData = xData.concat(aesData.map(function(d){return +d.X+d.DX})) // assume right extend DX + var xData = facet.values.map(function(d){return +d.X+(d.DX?(+d.X+d.DX):0)}) // assume right extend DX + xData.concat(g3functional.pluralise(graph.extents && graph.extents.x)) x.domain(xData) x.rangeBands([0,aValue.parent.dx]); From fc6ba33cb163c2c23209f23f7f70943a58e0c7d0 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 10 Sep 2013 13:16:42 -0700 Subject: [PATCH 040/112] use list instead of I(c in geom spec. needs fixing somewhere else. --- plots.R | 4 ++-- shiny_extend_g3.R | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/plots.R b/plots.R index 6bec4a6..2a2b567 100644 --- a/plots.R +++ b/plots.R @@ -441,7 +441,7 @@ airquality_plot <- function(dataName) { Y=list("Measurements","Temp"), X=list("Measurements","Solar.R")), #geom=c("voronoi","point") - geom=I(c("point")) + geom=list("point") ), list(type="layer", data=forceTableVector(dataSet), @@ -450,7 +450,7 @@ airquality_plot <- function(dataName) { aesthetic=list(Key="Rownames",XFilterKey="Rownames", X=list("Measurements","Temp"), Y=list("Measurements","Wind")), - geom=I(c("point")) # wanted to voronoi here BUT Wind:Temp has some duplicates which crash the algoritm. need to perturb + geom=list("line") # wanted to voronoi here BUT Wind:Temp has some duplicates which crash the algoritm. need to perturb ) ) ) diff --git a/shiny_extend_g3.R b/shiny_extend_g3.R index 974fbb6..5cf1c2c 100644 --- a/shiny_extend_g3.R +++ b/shiny_extend_g3.R @@ -62,6 +62,9 @@ renderG3Plot <- function(func) # data replaces the old 'table' name if (is.null(val$layers[[1]]$data)) val$layers[[1]]$data <- val$layers[[1]]$table } + + # TODO: make sure all layers geoms are lists + val } else { val From dc418d76e527ddde5592158212d35d7167745f7e Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 10 Sep 2013 13:17:06 -0700 Subject: [PATCH 041/112] fix x+dx calculation in two locations (should unify) --- js/g3subfigure.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index b14b130..9790157 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -152,7 +152,7 @@ // scales right for objects with those. var xData = _.pluck(aesData,"X").concat(g3functional.pluralise(graph.extents && graph.extents.x)) if (aestheticUtils.hasAesthetic(plan,"DX")) - xData = xData.concat(aesData.map(function(d){return +d.X+d.DX?d.DX:0})) // assume right extend DX + xData = xData.concat(aesData.map(function(d){return +d.DX?+d.X+d.DX:+d.X})) // assume right extend DX var numerise = function(x){return _.map(x,function(x){return +x})} @@ -293,8 +293,10 @@ case "ordinal": { // possible options here allow domain to be adjusted per x facet // this code is a clone of code above in ordinal - var xData = facet.values.map(function(d){return +d.X+(d.DX?(+d.X+d.DX):0)}) // assume right extend DX - xData.concat(g3functional.pluralise(graph.extents && graph.extents.x)) + var xData = _.pluck(aesData,"X").concat(g3functional.pluralise(graph.extents && graph.extents.x)) + if (aestheticUtils.hasAesthetic(plan,"DX")) + xData = xData.concat(aesData.map(function(d){return +d.DX?+d.X+d.DX:+d.X})) // assume right extend DX + x.domain(xData) x.rangeBands([0,aValue.parent.dx]); From ebce9c5cea453c68b382ad75e49aa8d3394b2e69 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 10 Sep 2013 13:58:10 -0700 Subject: [PATCH 042/112] fix color scale generation --- js/g3subfigure.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index 9790157..4a7d68e 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -232,7 +232,7 @@ // .keys() // .intersection(["Color","Fill"]) // .first().value()) - if (aestheticUtils.hasAesthetic("Color")) + if (aestheticUtils.hasAesthetic(plan,"Color")) { if (plan.data.message.extents && plan.data.message.extents.Color) { color.domain(plan.data.message.extents.Color) From 968cfca1d392f760defee154c95af05c7057dcdc Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 10 Sep 2013 13:58:36 -0700 Subject: [PATCH 043/112] TODOs in legend for color / layer / filter fixes HACK --- js/g3subfigure.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index 4a7d68e..915424a 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -792,15 +792,17 @@ // draw a legend if we have used any colors if (aestheticUtils.hasAesthetic(plan,"Color")) { - legendAesthetic = graph.aesthetic.Color?"Color":"Fill" + //var label = aesStructure.Color TODO: fixme + label = "Label FIXME" var legendPos = {x:width+5,y:0}; var clickEvent = graph.onClick && graph.onClick.Color && - // this will fail - TODO - function(d){g3figure.filter.update(d?_.object([aesStructure[legendAesthetic]], + // this will fail - TODO HACK : make it apply per layer + function(d){g3figure.filter.update(d?_.object([aesStructure.Color], [function(x){ return x==d }]):{})} - g3legends.discrete_color(root,color,legendPos,aesStructure[legendAesthetic], + clickEvent = clickEvent || function(x){return false} + g3legends.discrete_color(root,color,legendPos,label, clickEvent,height, graph.position && graph.position.x == "stack" // invert color legend if stacked ) @@ -916,6 +918,8 @@ // Return a handle to update this plot for filtering etc. plotHandle.update = function(filterSpec) { + // TODO: fix this to work with layers: HACK + return false; function negate(f) { return function(x){ return !f(x) } } var filterFn=aestheticUtils.filterFromFilterSpec(filterSpec,aesStructure) From fe6b0228af8bce2e3fac835ab9cf3f1548437799 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 10 Sep 2013 14:37:51 -0700 Subject: [PATCH 044/112] fix bug in domain discovery for "unit" data --- js/g3subfigure.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index 915424a..5c488c8 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -293,9 +293,9 @@ case "ordinal": { // possible options here allow domain to be adjusted per x facet // this code is a clone of code above in ordinal - var xData = _.pluck(aesData,"X").concat(g3functional.pluralise(graph.extents && graph.extents.x)) + var xData = _.pluck(facet.values,"X").concat(g3functional.pluralise(graph.extents && graph.extents.x)) if (aestheticUtils.hasAesthetic(plan,"DX")) - xData = xData.concat(aesData.map(function(d){return +d.DX?+d.X+d.DX:+d.X})) // assume right extend DX + xData = xData.concat(facet.values.map(function(d){return +d.DX?+d.X+d.DX:+d.X})) // assume right extend DX x.domain(xData) From e93f305e7caefda9f78ff7847a41ae1c045b0b02 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 10 Sep 2013 14:40:26 -0700 Subject: [PATCH 045/112] fix xcluster generation bugs - select from layer[0]. This is somewhat hacky but for now we assume all layers contain all hierarchy levels and don't omit any levels. TODO: get all factor levels from all layers. --- js/g3subfigure.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index 5c488c8..f23e830 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -120,8 +120,8 @@ // Build the data struture for the hierarchical x-scale/X-axis. // It would be nice if this were a real d3 axis type, but that would require // nested heterogenous axis with variable size rangebands. Not possible today - partitionedNodes = g3xcluster.hierarchX(aesData,plan.layers[0].aesthetic.XCluster, - plan.layers[0].metadata.aestheticStructure.XCluster, width, margin.xcluster) + partitionedNodes = g3xcluster.hierarchX(aesData,graph.layers[0].aesthetic.XCluster, + plan.layers[0].metaData.aestheticStructure.XCluster, width, margin.xcluster) } else { // do it anyway, with no data, for uniformity partitionedNodes = g3xcluster.hierarchX(aesData,"IGNORE THIS MESSAGE", {}, width, margin.xcluster) @@ -664,7 +664,7 @@ var clickEvent = graph.onClick && graph.onClick.XCluster && g3events.updateShinyInputFromHierFn(graph.onClick.XCluster.input, - aesStructure["XCluster"]) + plan.layers[0].metaData.aestheticStructure["XCluster"]) g3xcluster.hierAxis(root,partitionedNodes,height+margin.bottom-margin.xcluster,width,margin.xcluster,clickEvent,graph.format&&graph.format.XCluster) } From 4ccd576d5ffde4cd2f99df81021a188ce81e4d52 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 10 Sep 2013 14:40:36 -0700 Subject: [PATCH 046/112] probably pointbar bug --- js/g3geoms.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/g3geoms.js b/js/g3geoms.js index 887d022..5b6e316 100644 --- a/js/g3geoms.js +++ b/js/g3geoms.js @@ -323,7 +323,7 @@ geom.position = function(d) { d .attr("x", function(d) { - var x=xFacetScale(this)(d.X) + var x=xFacetScale(this)(d.x) return xFacetScale(this).rangeBand() > x_pad_minimum ? x + x_padding : x }) .attr("width", function(d) { From 625f9250c1be5b96031678b47e77facff41d92e2 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 10 Sep 2013 14:47:30 -0700 Subject: [PATCH 047/112] Fix position barstack by making it a layer property --- shiny_extend_g3.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shiny_extend_g3.R b/shiny_extend_g3.R index 5cf1c2c..17a3894 100644 --- a/shiny_extend_g3.R +++ b/shiny_extend_g3.R @@ -51,7 +51,7 @@ renderG3Plot <- function(func) # todo: check all the aesthetics are mappable. if (is.null(val$layers)) { # update to layers - props_to_move <- c("layer","table","structure","aesthetic","geom") + props_to_move <- c("layer","table","structure","aesthetic","geom","position") props_to_copy <- c("name") val$layers <- list() val$layers[1] <- list(val[c(props_to_move,props_to_copy)]) From 84e221ae6e19834a9123d5821d49a24aa80760ba Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 10 Sep 2013 15:02:55 -0700 Subject: [PATCH 048/112] handle and upgrade 'reports' --- shiny_extend_g3.R | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/shiny_extend_g3.R b/shiny_extend_g3.R index 17a3894..6671b3f 100644 --- a/shiny_extend_g3.R +++ b/shiny_extend_g3.R @@ -78,6 +78,12 @@ renderG3Plot <- function(func) canoniseMessage(val) }) } else { + if(val$type == "report") { + val$data=lapply(Filter(Negate(is.null),val$data),function(val) { + canoniseMessage(val) + }) + sendData <- val + } sendData <- canoniseMessage(val) } From 84a19aa11746575711a3506cfb6a18efc70419aa Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 10 Sep 2013 15:03:12 -0700 Subject: [PATCH 049/112] Fix explicit color scale (capitalise) --- js/g3subfigure.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index f23e830..4505571 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -170,7 +170,7 @@ graph.scales && graph.scales.y || "linear" var scaleColor = - graph.scales && graph.scales.color || "ordinal" + graph.scales && graph.scales.Color || "ordinal" switch(scaleX) { case "ordinal": From f17faa7885d10efcb2c95a61d2435a2b96174362 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 10 Sep 2013 15:03:20 -0700 Subject: [PATCH 050/112] filter notes --- notes.txt | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/notes.txt b/notes.txt index b0a8b3a..1b62ada 100644 --- a/notes.txt +++ b/notes.txt @@ -51,3 +51,28 @@ Xcluster aesthetic has to be the SAME across all layers - oh, but how do we calc position is local +Legend Notes: +--------- + +Color is unified. (how will I do outline?) + +If two layers each have a different aesthetic mapping for color (ie the structural name differs), there are two challenges: + +0) Are there two legends or one? two might be nice, but I have no mechanism right now +1) what's the legend label? - Combining is best since we can override it? +2) how does filtering work when they are clicked on? Currently filtering consumes the structural name, which may differ + +Filter Notes: +--------- + +Filtering happens in various ways. Let's start with + +Legend - click a color +XCluster - click a partition + +Linking Notes: +---------- + + +Popover Notes: +--------- \ No newline at end of file From 96fdbce832f19ad47db8e035af1c90cdedfbdf9f Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 10 Sep 2013 15:23:40 -0700 Subject: [PATCH 051/112] sample Color override --- plots.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plots.R b/plots.R index 2a2b567..5fb3b97 100644 --- a/plots.R +++ b/plots.R @@ -251,7 +251,7 @@ msleep_plot <- function(dataName,...) { Key=list("Clade","Name") # YFacet=list("Type") ), - labels=list(x="Clade", y="sleep_total"), + labels=list(x="Clade", y="sleep_total",Color="Eating habits"), geom="point_bar", scales=list(x="unit"), extents=list(y=0,Color=levels(msleep$vore)), From 3d57ebcedc44acb1ed28cf1d12f3242bfa19bf4a Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 10 Sep 2013 15:23:55 -0700 Subject: [PATCH 052/112] Fix label legends (but only for first layer) --- js/g3subfigure.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index 4505571..c014fb7 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -792,8 +792,8 @@ // draw a legend if we have used any colors if (aestheticUtils.hasAesthetic(plan,"Color")) { - //var label = aesStructure.Color TODO: fixme - label = "Label FIXME" + //var label = aesStructure.Color TODO: fixme - get labels from all layers, collapse compound structure ids. + label = graph.labels && graph.labels.Color || plan.layers[0].metaData.aestheticStructure.Color var legendPos = {x:width+5,y:0}; var clickEvent = graph.onClick && graph.onClick.Color && // this will fail - TODO HACK : make it apply per layer From 574e15489272c31851ce2db7772f0c2dce8f7004 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 10 Sep 2013 15:28:44 -0700 Subject: [PATCH 053/112] start to fix filters by accepting filter information frmo first layer only. --- js/g3subfigure.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index c014fb7..dd43982 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -792,11 +792,12 @@ // draw a legend if we have used any colors if (aestheticUtils.hasAesthetic(plan,"Color")) { - //var label = aesStructure.Color TODO: fixme - get labels from all layers, collapse compound structure ids. - label = graph.labels && graph.labels.Color || plan.layers[0].metaData.aestheticStructure.Color + // TODO: allow filtering to read from any layer + var aesStructure = plan.layers[0].metaData.aestheticStructure + // TODO: fixme - get labels from all layers, collapse compound structure ids. + var label = graph.labels && graph.labels.Color || aesStructure.Color var legendPos = {x:width+5,y:0}; var clickEvent = graph.onClick && graph.onClick.Color && - // this will fail - TODO HACK : make it apply per layer function(d){g3figure.filter.update(d?_.object([aesStructure.Color], [function(x){ return x==d From 139c547fa017a4815dfe74c7358dbdf1c0e46d12 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 10 Sep 2013 17:30:35 -0700 Subject: [PATCH 054/112] fix voronoi --- js/g3geoms.js | 9 ++++----- js/g3subfigure.js | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/js/g3geoms.js b/js/g3geoms.js index 5b6e316..f3cbce9 100644 --- a/js/g3geoms.js +++ b/js/g3geoms.js @@ -8,12 +8,11 @@ var parentD=function(n,old){return old && n.parentNode.__data_old__ ? n.parentNode.__data_old__ : n.parentNode.__data__} var parentD2=function(n){return n.parentNode.parentNode.__data__} var parentD3=function(n){return n.parentNode.parentNode.parentNode.__data__} + var parentD4=function(n){return n.parentNode.parentNode.parentNode.parentNode.__data__} // Get the parent cellfacet's scale property - var xFacetScale=function(n,old){return parentD2(n,old).xScale} - var yFacetScale=function(n,old){return parentD2(n).yScale} - + var xFacetScale=function(n,old){return parentD2(n,old).xScale} var yFacetScale=function(n){return parentD3(n).yScale} // NB: this keyfunction should return a unique key, which means x|color should be unique. // If not, then the code can fail. Current hierarchy implementation does generate a unique @@ -137,8 +136,8 @@ geom.positionCircle = function(d) { - var xFacetScale=function(n,old){return parentD2(n).xScale} - var yFacetScale=function(n,old){return parentD2(n).yScale} + var xFacetScale=function(n,old){return parentD3(n).xScale} + var yFacetScale=function(n,old){return parentD4(n).yScale} d .attr("cx",function(d) { return xFacetScale(this)(d.X) }) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index dd43982..c1f3be2 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -841,7 +841,7 @@ // at the point of drawing, which would save multiple calculations. However // the same is not true of ordinal, since it's not clear that arbitrary // ordinal values are real. - g3stats.voronoi(d.values,d.xScale,d.yScale) + g3stats.voronoi(d.values,this.parentNode.__data__.xScale,this.parentNode.__data__.yScale) }) From ccf81f37c69491fac6ef5dae5ea986f6f4c507af Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 10 Sep 2013 17:31:00 -0700 Subject: [PATCH 055/112] restore original airquality for voronoi test. --- plots.R | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/plots.R b/plots.R index 5fb3b97..440b18b 100644 --- a/plots.R +++ b/plots.R @@ -422,7 +422,7 @@ DNase_plot <- function(dataName) { ); } -airquality_plot <- function(dataName) { +airquality_layer_plot <- function(dataName) { dataSet <- get(dataName) dataSet$row = rownames(dataSet) list( @@ -457,6 +457,39 @@ airquality_plot <- function(dataName) { ) } +airquality_plot <- function(dataName) { + dataSet <- get(dataName) + dataSet$row = rownames(dataSet) + list( + list(type="plot", + table=forceTableVector(dataSet), + name=paste0(dataName,"_AQ"), + structure=list(Rownames="row",Measurements=selfList(c("Temp","Solar.R"))), + aesthetic=list(Key="Rownames",XFilterKey="Rownames",Y=list("Measurements","Temp"), X=list("Measurements","Solar.R")), + #labels=list(x=field, y="Count"), + geom=c("voronoi","point"), + onBrush=list(x=list(drag=list(filter=TRUE))), + scales=list(x="linear",y="linear"), + labels=list(y="Temp",x="Solar Radiation"), + onZoom=T + ), + list(type="plot", + table=forceTableVector(dataSet), + name=dataName, + structure=list(Rownames="row",Measurements=selfList(c("Wind","Temp"))), + aesthetic=list(Key="Rownames",XFilterKey="Rownames",X=list("Measurements","Wind"), Y=list("Measurements","Temp")), + #labels=list(x=field, y="Count"), + geom=c("point"), # wanted to voronoi here BUT Wind:Temp has some duplicates which crash the algoritm. need to perterb + onBrush=list(x=list(drag=list(filter=TRUE))), + scales=list(x="linear",y="linear"), + labels=list(x="Wind",y="Temp"), + grid=list(Wind=list("Measurements","Wind"), + Temp=list("Measurements","Temp")), + onZoom=T + ) + ) +} + # sunspots_plot <- function(dataName) { # # dataSet=as.data.frame(xy.coords(get(dataName))[c("x","y")]) From 75e3f7306120c75b0035443fbdbb9425c9d49aec Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Wed, 11 Sep 2013 09:58:25 -0700 Subject: [PATCH 056/112] more todos --- js/g3subfigure.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index c1f3be2..5205c88 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -923,7 +923,7 @@ return false; function negate(f) { return function(x){ return !f(x) } } var filterFn=aestheticUtils.filterFromFilterSpec(filterSpec,aesStructure) - + // TODO: dataPointSelector is not appropriate when multiple layers and geoms exist. Fix somehow el.select("g.plot").selectAll(dataPointSelector) .style("opacity",function(d) { return filterFn(d)?1.0:0.2}) } From a57786f3fc63402ba7dac72f6afa09c3648dc3c8 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Wed, 11 Sep 2013 09:59:18 -0700 Subject: [PATCH 057/112] airquality model layer --- plots.R | 77 +++++++++++++++++++++++++-------------------------------- 1 file changed, 34 insertions(+), 43 deletions(-) diff --git a/plots.R b/plots.R index 440b18b..8d8c9c7 100644 --- a/plots.R +++ b/plots.R @@ -422,56 +422,47 @@ DNase_plot <- function(dataName) { ); } -airquality_layer_plot <- function(dataName) { - dataSet <- get(dataName) - dataSet$row = rownames(dataSet) - list( - list(type="plot", - labels=list(y="Temp",x="Solar Radiation / Wind"), - onZoom=T, - scales=list(x="linear",y="linear"), - name=paste0(dataName,"_AQ"), - onBrush=list(x=list(drag=list(filter=TRUE))), - layers=list( - list(type="layer", - name="Solar", - data=forceTableVector(dataSet), - structure=list(Rownames="row",Measurements=selfList(c("Temp","Solar.R"))), - aesthetic=list(Key="Rownames",XFilterKey="Rownames", - Y=list("Measurements","Temp"), - X=list("Measurements","Solar.R")), - #geom=c("voronoi","point") - geom=list("point") - ), - list(type="layer", - data=forceTableVector(dataSet), - name="Wind", - structure=list(Rownames="row",Measurements=selfList(c("Wind","Temp"))), - aesthetic=list(Key="Rownames",XFilterKey="Rownames", - X=list("Measurements","Temp"), - Y=list("Measurements","Wind")), - geom=list("line") # wanted to voronoi here BUT Wind:Temp has some duplicates which crash the algoritm. need to perturb - ) - ) - ) - ) -} - airquality_plot <- function(dataName) { + + if(!require(mgcv)) stop("Required packages mgcv not available") + + # build a humped model of air quality + gairq <- gam(Temp~s(Solar.R),data=airquality) + nSolar.R <- pretty(airquality$Solar.R,n=50) + # note that JSON chokes on arrays, so convert predict output to a vector + model_frame <- data.frame(Solar.R=nSolar.R,Temp=as.vector(predict(gairq,data.frame(Solar.R=nSolar.R)))) + + dataSet <- get(dataName) dataSet$row = rownames(dataSet) list( list(type="plot", - table=forceTableVector(dataSet), + labels=list(y="Temp",x="Solar Radiation / Wind"), + onZoom=T, + scales=list(x="linear",y="linear"), name=paste0(dataName,"_AQ"), - structure=list(Rownames="row",Measurements=selfList(c("Temp","Solar.R"))), - aesthetic=list(Key="Rownames",XFilterKey="Rownames",Y=list("Measurements","Temp"), X=list("Measurements","Solar.R")), - #labels=list(x=field, y="Count"), - geom=c("voronoi","point"), onBrush=list(x=list(drag=list(filter=TRUE))), - scales=list(x="linear",y="linear"), - labels=list(y="Temp",x="Solar Radiation"), - onZoom=T + layers=list( + list(type="layer", + name="Solar", + data=forceTableVector(dataSet), + structure=list(Rownames="row",Measurements=selfList(c("Temp","Solar.R"))), + aesthetic=list(Key="Rownames",XFilterKey="Rownames", + Y=list("Measurements","Temp"), + X=list("Measurements","Solar.R")), + geom=c("voronoi","point") + #geom=list("point") + ), + list(type="layer", + name="Solar_Model", + data=forceTableVector(model_frame), + structure=list(Measurements=selfList(c("Temp","Solar.R"))), + aesthetic=list(Y=list("Measurements","Temp"), + X=list("Measurements","Solar.R")), + #geom=c("voronoi","point") + geom=list("line") + ) + ) ), list(type="plot", table=forceTableVector(dataSet), From 7578c66f16ba156e5aa37598df492153a4873026 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Wed, 11 Sep 2013 10:12:44 -0700 Subject: [PATCH 058/112] add layer class layer_name_. Might help with filtering later. --- js/g3subfigure.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index 5205c88..9d8e440 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -585,7 +585,9 @@ var newLayerFacet = layerFacet .enter() .append("g") - .attr("class","layer_facet") + .attr("class",function(d){ + return(["layer_facet","layer_name_"+d.key]) + }) if (graph.onZoom) { xFacetAxis From 17e192fe7f5e7b097108c6aa2691280ffc1292ac Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Wed, 11 Sep 2013 10:31:23 -0700 Subject: [PATCH 059/112] enable grid at layer level, draw only last specified grid. There is a bug in airquality that has an extra header --- js/g3figure.js | 15 +++++++++------ shiny_extend_g3.R | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/js/g3figure.js b/js/g3figure.js index a9b500e..07336c6 100644 --- a/js/g3figure.js +++ b/js/g3figure.js @@ -163,18 +163,21 @@ g3figure.filter.addWidget(d.subfigure.filterHandle()); }) - if(plans.length == 0 || plans[plans.length-1].data.message.grid == null){ + // find first layer with grid + var grid_layers = _.chain(plans).pluck("layers").flatten(true).filter(function(layer){return !_.isUndefined(layer.data.message.grid)}).value() + + + if(grid_layers.length==0){ // need to remove the table d3.select(el).select(".d3Table").select("table").remove() // and turn off the filters. um, why? //exports.filter.clear() } else { - // It should mean - draw a table for each subFigure that wants one. But I only - // know how to draw one table at the moment so only the last one that wants one + // It should mean - draw a table for each subfigure and layer that wants one. But I only + // know how to draw one table at the moment so only the last layer that wants one // right now it does 'if the last plan wants a table' do it. - if(plans[plans.length-1].data.message.grid != null) { - exports.filter.addWidget(g3figure.table(d3.select(el).select(".d3Table"),plans[plans.length-1])); - } + var grid_layer=_.last(grid_layers) + exports.filter.addWidget(g3figure.table(d3.select(el).select(".d3Table"),grid_layer)); } } diff --git a/shiny_extend_g3.R b/shiny_extend_g3.R index 6671b3f..5bed17e 100644 --- a/shiny_extend_g3.R +++ b/shiny_extend_g3.R @@ -51,7 +51,7 @@ renderG3Plot <- function(func) # todo: check all the aesthetics are mappable. if (is.null(val$layers)) { # update to layers - props_to_move <- c("layer","table","structure","aesthetic","geom","position") + props_to_move <- c("layer","table","structure","aesthetic","geom","position","grid") props_to_copy <- c("name") val$layers <- list() val$layers[1] <- list(val[c(props_to_move,props_to_copy)]) From ed9270903e12882eee00895438afb8277d7a6482 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Wed, 11 Sep 2013 10:46:07 -0700 Subject: [PATCH 060/112] remove layer_name_ from facet - it breaks transitions and leave layer shrapnel around --- js/g3subfigure.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index 9d8e440..d39bcdc 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -586,7 +586,8 @@ .enter() .append("g") .attr("class",function(d){ - return(["layer_facet","layer_name_"+d.key]) + return("layer_facet") + //return(["layer_facet","layer_name_"+d.key]) // Remove this for now, it breaks "exit" }) if (graph.onZoom) { From e7d80b90ac181662041e80ac8bef7aedc821ce80 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Thu, 12 Sep 2013 08:21:24 -0700 Subject: [PATCH 061/112] comments and todos --- js/g3figure.js | 7 +++++-- js/structureWalker.js | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/js/g3figure.js b/js/g3figure.js index 07336c6..39cc4e7 100644 --- a/js/g3figure.js +++ b/js/g3figure.js @@ -264,8 +264,11 @@ } // filter is a per-plot (possibly global) filter collection that - // sends filter messages amongst graphs. Should rebuild to use - // d3.dispatch (but what about new graphs - how do they get filters?) + // sends filter messages amongst graphs. + + // TODO: Should rebuild to use either: + // * d3.dispatch (but what about new graphs - how do they get filters?) + // * just using css selectors to find filterable things, then invoke update on them? exports.filter = function() { exports.filter.widgets = [] exports.filter.unfilter = function() { diff --git a/js/structureWalker.js b/js/structureWalker.js index 3b1b218..77795e1 100644 --- a/js/structureWalker.js +++ b/js/structureWalker.js @@ -24,6 +24,8 @@ // structureWalker().other(function(x){return {a: ["bob",0], b: "cheese"}[x]})(["a", {b: "b"}]) // -> [["bob",0],{b: "cheese"}] // +// the someFn arguments accept structure, iterator, context as arguments (iterator and context optional) +// structure is the structure component currently under consideraton (function(exports){ From 7115421e1be5c6d8129dbb2f5bfdab76bafc7a54 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Thu, 12 Sep 2013 08:57:39 -0700 Subject: [PATCH 062/112] put timestamp hack back in - to convert to dates. --- js/g3message.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/js/g3message.js b/js/g3message.js index 24c7bb2..0fb7fca 100644 --- a/js/g3message.js +++ b/js/g3message.js @@ -11,10 +11,14 @@ // generate col major structured records - by 'layer' processLayer = function(layer_message) { + // TODO: don't handle dates like this - handle them by SCALES instead. + if(layer_message.data.timestamp) + layer_message.data.timestamp = layer_message.data.timestamp.map(function(x){return new Date(+x)}) + var strData = aestheticUtils.decodeData(layer_message); // TODO: raise errors if the aes or structure is not satisfiable - + // create records with fields x,y,group from data var aesData = strData.map(aestheticUtils.applyAesthetic(layer_message.aesthetic)) // derive effective structure of aesthetic @@ -52,10 +56,6 @@ // ... and others var message = g3message.validate(naive_message) - - // TODO: don't handle dates like this - handle them by SCALES instead. - //if(message.table.timestamp) - // message.table.timestamp = message.table.timestamp.map(function(x){return new Date(+x)}) var layers = _.map(message.layers,processLayer) From 0ff88325d56db320ac5ff2f503b608f3c5003b0e Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Thu, 12 Sep 2013 09:01:00 -0700 Subject: [PATCH 063/112] note that the ozone dataset is interesting --- plots.R | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plots.R b/plots.R index 8d8c9c7..7225c93 100644 --- a/plots.R +++ b/plots.R @@ -650,4 +650,7 @@ wikipedia_nav_plot <- function(dataName) { extents=c(y=0) #extents=c(zoom=c(0,1000000)) ) -} \ No newline at end of file +} + + +# TODO: Ozone is very interesting \ No newline at end of file From 1707ccdc0d8e476bb6d7db02ff4a2b6984df8ef9 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Thu, 12 Sep 2013 09:02:48 -0700 Subject: [PATCH 064/112] add master_layer to control faceting, filtering and brushing. brushing and filtering now fixed. --- js/g3subfigure.js | 35 ++++++++++++++++------------------- notes.txt | 15 ++++++++++++++- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index d39bcdc..d57be41 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -31,9 +31,8 @@ color, // the color scale // transformed versions of the data partitionedNodes, // the partitioned node data (at what level?) - strData, - aesData, - aesStructure, // aes map applied to structure map gives aes -> data_frame names + master_layer_index, // the layer which controls faceting, brushing and filtering (only one for now) + master_layer, // the plan for the master layer // some information about the current geoms - for filtering yFacetedData, // the faceted data subsets - y (by row, not cell) xFacetedData, // facets for x (by column, not cell) @@ -102,13 +101,13 @@ // don't choose colorfield yet since it's layer dependent and HAS the SAME SCALE (probably) - // These don't exist yet. - //strData = plan.data.structured, - //aesData = plan.data.aesthetic, - //aesStructure = plan.metaData.aestheticStructure; - + // extract data from all layers var aesData = d3.merge(_.pluck(_.pluck(plan.layers,"data"),"aesthetic")) + // hang on to the master layer for filtering, clustering etc. + master_layer_index = 0 + master_layer = plan.layers[master_layer_index] + // Setup axes // xCluster Axis. this one is special because the structure MUST exist and be the same @@ -120,8 +119,8 @@ // Build the data struture for the hierarchical x-scale/X-axis. // It would be nice if this were a real d3 axis type, but that would require // nested heterogenous axis with variable size rangebands. Not possible today - partitionedNodes = g3xcluster.hierarchX(aesData,graph.layers[0].aesthetic.XCluster, - plan.layers[0].metaData.aestheticStructure.XCluster, width, margin.xcluster) + partitionedNodes = g3xcluster.hierarchX(aesData,graph.layers[master_layer_index].aesthetic.XCluster, + master_layer.metaData.aestheticStructure.XCluster, width, margin.xcluster) } else { // do it anyway, with no data, for uniformity partitionedNodes = g3xcluster.hierarchX(aesData,"IGNORE THIS MESSAGE", {}, width, margin.xcluster) @@ -617,8 +616,9 @@ // what do we do? It has lots of arguments because it was refactored // should fix. d.brush = - g3brush.brush(d3.select(this), graph, aesStructure, - aesData, // should we really supply all the data? or just the facet's? + g3brush.brush(d3.select(this), graph, + master_layer.metaData.aestheticStructure, + master_layer.data.aesthetic, // should we really supply all the data? or just the facet's? d.xScale, height, scaleX) @@ -796,7 +796,7 @@ // draw a legend if we have used any colors if (aestheticUtils.hasAesthetic(plan,"Color")) { // TODO: allow filtering to read from any layer - var aesStructure = plan.layers[0].metaData.aestheticStructure + var aesStructure = master_layer.metaData.aestheticStructure // TODO: fixme - get labels from all layers, collapse compound structure ids. var label = graph.labels && graph.labels.Color || aesStructure.Color var legendPos = {x:width+5,y:0}; @@ -921,12 +921,9 @@ })() // Return a handle to update this plot for filtering etc. - plotHandle.update = function(filterSpec) { - // TODO: fix this to work with layers: HACK - return false; - function negate(f) { return function(x){ return !f(x) } } - var filterFn=aestheticUtils.filterFromFilterSpec(filterSpec,aesStructure) - // TODO: dataPointSelector is not appropriate when multiple layers and geoms exist. Fix somehow + plotHandle.update = function(filterSpec) { + var filterFn=aestheticUtils.filterFromFilterSpec(filterSpec,master_layer.metaData.aestheticStructure) + // TODO: dataPointSelector is not appropriate when multiple layers and geoms exist. Fix somehow to use el.select("g.plot").selectAll(dataPointSelector) .style("opacity",function(d) { return filterFn(d)?1.0:0.2}) } diff --git a/notes.txt b/notes.txt index 1b62ada..c4921ea 100644 --- a/notes.txt +++ b/notes.txt @@ -70,9 +70,22 @@ Filtering happens in various ways. Let's start with Legend - click a color XCluster - click a partition +filtering works (just on master layer) + Linking Notes: ---------- +Clicking and brushing need to work before filters do - filters are largely gimmicky. + +now work - just on 'master' (first) layer +Clicking may not work Popover Notes: ---------- \ No newline at end of file +--------- + + +Other Bugs: +----- +Geom Clicking not tested yet +Brush + Pan has regressed (is this a d3 or browser issue, or my code?) +DatapointSelector is clearly wrong - should only be for 'master' layer - or for all layers separately. \ No newline at end of file From 793c15ae2f132c36a2b799bcebd4a5e5eada3219 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Thu, 12 Sep 2013 09:02:57 -0700 Subject: [PATCH 065/112] extract (seemingly unused) negate --- js/g3functional.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js/g3functional.js b/js/g3functional.js index 30611cf..8fc2f9c 100644 --- a/js/g3functional.js +++ b/js/g3functional.js @@ -9,6 +9,8 @@ return function repeatedly() { return v[i=(1+i)%l] } } + exports.negate = function negate(f) { return function(x){ return !f(x) } } + // using an arbitrary accessor as the next pointer, return the list thus formed from the input node exports.followChain=function(accessor){ var followChainAccessor = function(node) { From 5f312dd695cf62aa429d8c8f621662699c03b2ec Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Thu, 12 Sep 2013 09:18:32 -0700 Subject: [PATCH 066/112] Fix html nested table header duplication bug --- js/htmltable.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/js/htmltable.js b/js/htmltable.js index 35b2dd1..152f1e9 100644 --- a/js/htmltable.js +++ b/js/htmltable.js @@ -371,9 +371,6 @@ headkeys.enter() .insert("th","th.value th.info") // keys go before values and info - .insert("th","th.value,th.info") // keys go before values and info - .insert("td","td.info td.value") // keyCells go before values - .insert("td","td.info,td.value") // keyCells go before values .attr("class","key") headkeys.order() From a8ae0ce76aff8c9bf9065ff7ad30486edb1ba4eb Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Thu, 12 Sep 2013 09:42:22 -0700 Subject: [PATCH 067/112] Fix zoom+brush by attaching zoom behaviour to zoom_layer --- js/g3subfigure.js | 1 + notes.txt | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index d57be41..9924fd1 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -591,6 +591,7 @@ if (graph.onZoom) { xFacetAxis + .select(".zoom_layer") .each(function(d,i) { var zoom = d3.behavior .zoom() diff --git a/notes.txt b/notes.txt index c4921ea..81a11a6 100644 --- a/notes.txt +++ b/notes.txt @@ -87,5 +87,4 @@ Popover Notes: Other Bugs: ----- Geom Clicking not tested yet -Brush + Pan has regressed (is this a d3 or browser issue, or my code?) -DatapointSelector is clearly wrong - should only be for 'master' layer - or for all layers separately. \ No newline at end of file +DatapointSelector is clearly wrong - should only be for 'master' layer - or for all layers separately. From 150b7584e13733a909df506deaead6492110a24c Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Thu, 12 Sep 2013 09:42:36 -0700 Subject: [PATCH 068/112] disable touch style zooming --- js/g3subfigure.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index 9924fd1..95fd50c 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -542,7 +542,7 @@ if (d3.event.sourceEvent.type=="mousewheel") { // this case is bad for touch based zooming - it starts to shrink it while the user is still - var isTouch = true + var isTouch = false if (isTouch) d3.select(window).on("mousemove.zoomadjust",_.once(zoomAdjust.call(this,d3.event).adjust)) else From 0ea3f34dd2502609c94bdd86448c2e8aab67ebcb Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Thu, 12 Sep 2013 09:42:42 -0700 Subject: [PATCH 069/112] add notes --- js/htmltable.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/htmltable.js b/js/htmltable.js index 152f1e9..8d31158 100644 --- a/js/htmltable.js +++ b/js/htmltable.js @@ -107,6 +107,7 @@ // the data key to sort by (in data space, not str or aes space) exports.flatStructuredTable = function(tableNode, tableData, sortRowKey) { + // TODO: place this and other things below in 'functional' (or a new structure file) objectSlice=function(object,fields) { return _.map(fields,function(k){ return object[k] From b6197f89fb27940409901d7bf3ec6ddbf5e9c363 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Thu, 12 Sep 2013 09:44:15 -0700 Subject: [PATCH 070/112] add notes to readme --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0ee17be..b28ee1f 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ Includes interactive features such as click and drag. Add Shiny inputs to the app to control graph filtering and more. +Now supports layers + Licence ------- @@ -106,7 +108,7 @@ Fun improvements: Some ideas to develop the product * Sparklines - providing a very simple 3-layer structure without faceting - * Layers - multiple geoms on top of each other (another layer!) + * Layers - multiple geoms on top of each other (another layer!) (now implemented) * g3autoplot - looks at data and does something sensible, then tells you how to repeat / customise it From c1ca758f8d73e93107fe31f3456998514ef0fdf3 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Thu, 12 Sep 2013 10:58:52 -0700 Subject: [PATCH 071/112] Fix line filtering by cloning line nest key property into line nest Color. --- js/g3subfigure.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index 95fd50c..62a42c9 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -893,7 +893,12 @@ break; case "line": // different way to send values if(!fast) - dataPointSelector = g3geoms[geom](layerFacet,function(d){return d3.nest().key(function(d){return d.Color}).entries(d.values)},color,clickEvent) + dataPointSelector = g3geoms[geom](layerFacet,function(d){ + return _.map(d3.nest().key(function(d) { + return d.Color + }).entries(d.values),function(c) { + c.Color = c.key; return c; + })},color,clickEvent) .draw(layerFacet) else g3geoms[geom](layerFacet,function(d){return [d.values]},color,clickEvent) From 57206a4d188cd1429a350934cb77a126bb519666 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Thu, 12 Sep 2013 10:59:08 -0700 Subject: [PATCH 072/112] Fix onClick crash in geom loop --- js/g3subfigure.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index 62a42c9..88945d7 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -834,6 +834,7 @@ // otherwise length 0 geoms cannot delete themselves (but maybe they don't exist) var geoms = d.values[0].layer.geom + var aesthetic = d.values[0].layer.aesthetic _.map(geoms, function(geom) { switch(geom) { @@ -862,7 +863,7 @@ var clickEventInner = g3events.updateShinyInputFromGeomFn(graph.onBrush.x.drag.input, - aesStructure.XFilterKey?"XFilterKey":"X") + aesthetic.XFilterKey?"XFilterKey":"X") clickEvent = function(e) { clickEventInner(d3.event.target.__data__) @@ -874,7 +875,7 @@ var clickEventInner = g3events.updateShinyInputFromGeomFn(graph.onClick.x.input, - aesStructure.XFilterKey?"XFilterKey":"X") + aesthetic.XFilterKey?"XFilterKey":"X") clickEvent = function(e) { clickEventInner(d3.event.target.__data__) From 38a014527e5b0ed828ecb6f2d6a75aade39e674e Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Thu, 12 Sep 2013 10:59:48 -0700 Subject: [PATCH 073/112] todo bug notes --- notes.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/notes.txt b/notes.txt index 81a11a6..2a6f6b1 100644 --- a/notes.txt +++ b/notes.txt @@ -88,3 +88,4 @@ Other Bugs: ----- Geom Clicking not tested yet DatapointSelector is clearly wrong - should only be for 'master' layer - or for all layers separately. +There's a stray NA in layer From 6808840171aa3b55462185d42c04247bf01b3bbb Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 27 Aug 2013 10:13:18 -0700 Subject: [PATCH 074/112] allow date format y labels. Date (no time) only so far, will fix --- js/g3subfigure.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index 88945d7..2607b78 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -791,7 +791,15 @@ }) .text( function(d){ - return graph.labels.y + (("undefined"==d.key)?"":(" "+d.key)) + // handle yfacet type for date + + scaleYFacet = + (graph.scales && graph.scales.yfacet) ? graph.scales.yfacet : "linear" + + return graph.labels.y + (("undefined"==d.key)?"" + : (" "+( scaleYFacet=="date" + ? (new Date(+d.key)).toDateString() // what about format? + : d.key ))); } ) // draw a legend if we have used any colors From d3654915b6a31772ee7442d5b72bcd9e2cc5995e Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 27 Aug 2013 13:12:01 -0700 Subject: [PATCH 075/112] added area geom --- js/g3geoms.js | 61 ++++++++++++++++++++++++++++++++++++++++++++++- js/g3subfigure.js | 13 +++++++++- 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/js/g3geoms.js b/js/g3geoms.js index f3cbce9..48464f9 100644 --- a/js/g3geoms.js +++ b/js/g3geoms.js @@ -250,7 +250,66 @@ } - // draw points. + // draw area. y0 is 0 for the moment + // yScale input is carried in the facet + exports.area = function(plot,globalData,color,event) { + // subfigure redraws. + function geom() { + } + + geom.position = function(l) { + l + .x(function(d) { return xFacetScale(this)(d.X) }) + .y1(function(d) { return yFacetScale(this)(d.Y) }) + .y0(function(d) { return yFacetScale(this)(0) }) + .defined(function(d) { return !d.Missing }) + return (l) + } + + geom.draw = function area_draw(plot,data) { + if (!data) data = globalData + + var a = plot.selectAll("path.area") + .data(data) + + a.enter().append("path") + .datum(function(d){return d}) + .attr("class", "area") + .style("stroke","none") + .style("fill",function(d){return color(d.key)}) + .attr("d", function(d){return geom.position.call(this,d3.svg.area()).call(this,d.values)}) + .append("title") + + a.select("title") + .text( labelFn("Color") ) + + a + .on("click",event) // if null, removes event + .style("cursor",event?"pointer":null) + + a + // .transition() + .attr("d", function(d){return geom.position.call(this,d3.svg.area()).call(this,d.values)}) + + a.exit() + // .transition() + .attr("r", 0) + .remove() + + return("path.area") + } + + geom.fast_redraw = function fast_redraw_area(plot) { + var a = plot.selectAll(".area") + + a + .attr("d", function(d){return geom.position.call(this,d3.svg.area()).call(this,d.values)}) + } + + return geom + } + + // draw points. // yScale input is carried in the facet exports.line = function(plot,globalData,color,event) { // subfigure redraws. diff --git a/js/g3subfigure.js b/js/g3subfigure.js index 2607b78..bdac438 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -900,6 +900,7 @@ .fast_redraw(layerFacet) break; + case "line": // different way to send values if(!fast) dataPointSelector = g3geoms[geom](layerFacet,function(d){ @@ -914,10 +915,20 @@ .fast_redraw(layerFacet) break; + + case "area": // different way to send values + if(!fast) + dataPointSelector = g3geoms[geom](cellFacet,function(d){return d3.nest().key(function(d){return d.Color}).entries(d.values)},color,clickEvent) + .draw(cellFacet) + else + g3geoms[geom](cellFacet,function(d){return [d.values]},color,clickEvent) + .fast_redraw(cellFacet) + + break; default: throw({message:"Unknown geom=\""+geom+"\" in plot specification for layer XXX"}) - } + } }) }) From 17413bde48c4df5d7b20352b5fcee12ea491a365 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 27 Aug 2013 13:12:59 -0700 Subject: [PATCH 076/112] enable date formatted yfacet --- js/g3subfigure.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index bdac438..f5f239d 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -796,9 +796,12 @@ scaleYFacet = (graph.scales && graph.scales.yfacet) ? graph.scales.yfacet : "linear" + scaleYFacetFormat = + (graph.format && graph.format.yfacet) ? d3.time.format(graph.format.yfacet) : d3.time.format.iso + return graph.labels.y + (("undefined"==d.key)?"" : (" "+( scaleYFacet=="date" - ? (new Date(+d.key)).toDateString() // what about format? + ? scaleYFacetFormat(new Date(+d.key)) : d.key ))); } ) From 9baacb9b078118e01f8f37fed5598840f61f624b Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 27 Aug 2013 14:53:46 -0700 Subject: [PATCH 077/112] added facetMargin option to dimensions to control y facet --- js/g3subfigure.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index f5f239d..f347c38 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -278,7 +278,6 @@ var countFacets = allFacets.length - var facetMargin = 20 var x = xScale_current.copy() // assume the facets contain something at all @@ -335,7 +334,9 @@ var countFacets = allFacets.length - var facetMargin = 20 + var facetMargin = (graph.dimensions.facetMargin && !_.isUndefined(graph.dimensions.facetMargin.y)) ? + graph.dimensions.facetMargin.y : 20 + var y = yScale_master.copy() y.range([(countFacets-i)*(height+facetMargin)/countFacets-facetMargin, From 5af4f100284a6196fbf7f320dd6d8438714f29d0 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 27 Aug 2013 14:54:15 -0700 Subject: [PATCH 078/112] Set area fill to Fill instead of Color aesthetic --- js/g3subfigure.js | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index f347c38..ff327fb 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -894,7 +894,14 @@ d3.event.stopPropagation(); //? no idea what this does }; } - + + case "area": // different way to send values + if(!fast) + dataPointSelector = g3geoms[geom](layerFacet,function(d){return d3.nest().key(function(d){return d.Color}).entries(d.values)},color,clickEvent) + .draw(layerFacet) + else + g3geoms[geom](layerFacet,function(d){return [d.values]},color,clickEvent) + .fast_redraw(layerFacet) if(!fast) dataPointSelector = g3geoms[geom](layerFacet,function(d){return d.values},color,clickEvent) @@ -920,16 +927,6 @@ break; - case "area": // different way to send values - if(!fast) - dataPointSelector = g3geoms[geom](cellFacet,function(d){return d3.nest().key(function(d){return d.Color}).entries(d.values)},color,clickEvent) - .draw(cellFacet) - else - g3geoms[geom](cellFacet,function(d){return [d.values]},color,clickEvent) - .fast_redraw(cellFacet) - - break; - default: throw({message:"Unknown geom=\""+geom+"\" in plot specification for layer XXX"}) } From aaf2cbbdf47ca83f4e7eda3310a85735980224d1 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Thu, 29 Aug 2013 09:08:33 -0700 Subject: [PATCH 079/112] facetMargin fix configuration to work with 0 --- js/g3subfigure.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index ff327fb..8657ad3 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -334,7 +334,8 @@ var countFacets = allFacets.length - var facetMargin = (graph.dimensions.facetMargin && !_.isUndefined(graph.dimensions.facetMargin.y)) ? + var facetMargin = (graph.dimensions && + graph.dimensions.facetMargin && !_.isUndefined(graph.dimensions.facetMargin.y)) ? graph.dimensions.facetMargin.y : 20 var y = yScale_master.copy() From bfbedbd53f82e036352e248451c65f9e7950c06b Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Thu, 29 Aug 2013 09:08:58 -0700 Subject: [PATCH 080/112] only poistion brushes if brushes enabled --- js/g3subfigure.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index 8657ad3..74e0d19 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -499,7 +499,8 @@ xFacetAxis .each(function(d,i){ - d.brush.rebrush() // reposition and redraw the brushes + // and if no brushes? + if(d.brush) d.brush.rebrush() // reposition and redraw the brushes }) }) From 97958165aa33d1da7e255e3565b010a6827a2d3f Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Thu, 29 Aug 2013 17:22:33 -0700 Subject: [PATCH 081/112] hack to move y label inside graph area. make this configurable --- js/g3subfigure.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index 74e0d19..3e93de5 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -787,6 +787,9 @@ s .attr("y", function(d){return d.yScale.range()[1]}) .attr("x", 6) // or x for vertical + // http://www.w3.org/TR/SVG/text.html#BaselineAlignmentProperties + // TODO: make this configurable - or via stylesheet? + .attr("alignment-baseline", "baseline") .style("text-anchor", "start") break; } From f9d4a1eb04b63a0675ce9ecaf882822822a47045 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Fri, 30 Aug 2013 09:13:57 -0700 Subject: [PATCH 082/112] fix another location where brush existence is assumed wrongly --- js/g3subfigure.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index 3e93de5..cd021d2 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -497,9 +497,8 @@ .redrawAxes() .redrawGeoms() - xFacetAxis + xFacetAxis .each(function(d,i){ - // and if no brushes? if(d.brush) d.brush.rebrush() // reposition and redraw the brushes }) @@ -535,7 +534,7 @@ xFacetAxis .each(function(d,i){ - d.brush.rebrush() // reposition and redraw the brushes + if(d.brush) d.brush.rebrush() // reposition and redraw the brushes }) var endZoom = d3.event.sourceEvent.type=="mouseup" From b1d74ce90b8c011fe9e74026d443ad775637ea9e Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Thu, 12 Sep 2013 11:15:19 -0700 Subject: [PATCH 083/112] HACK use local javascript --- g3widget.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/g3widget.html b/g3widget.html index a9ac7aa..84f68c0 100644 --- a/g3widget.html +++ b/g3widget.html @@ -7,8 +7,8 @@ - - + + From dd36cc686423dda11c7efd56d0af08bbea9e114e Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Thu, 12 Sep 2013 11:27:03 -0700 Subject: [PATCH 084/112] include g3widget in javascript --- g3widget.html | 1 + 1 file changed, 1 insertion(+) diff --git a/g3widget.html b/g3widget.html index 84f68c0..5819f30 100644 --- a/g3widget.html +++ b/g3widget.html @@ -13,6 +13,7 @@ + From af8c794b05eb924ca3143a9653e1000aeb19ddd3 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Thu, 12 Sep 2013 11:35:39 -0700 Subject: [PATCH 085/112] Area fixes after merge --- js/g3subfigure.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index cd021d2..b8417c3 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -899,19 +899,21 @@ }; } - case "area": // different way to send values if(!fast) - dataPointSelector = g3geoms[geom](layerFacet,function(d){return d3.nest().key(function(d){return d.Color}).entries(d.values)},color,clickEvent) + dataPointSelector = g3geoms[geom](layerFacet,function(d){return d.values},color,clickEvent) .draw(layerFacet) else - g3geoms[geom](layerFacet,function(d){return [d.values]},color,clickEvent) + g3geoms[geom](layerFacet,function(d){return d.values},color,clickEvent) .fast_redraw(layerFacet) - + + break; + + case "area": // different way to send values if(!fast) - dataPointSelector = g3geoms[geom](layerFacet,function(d){return d.values},color,clickEvent) + dataPointSelector = g3geoms[geom](layerFacet,function(d){return d3.nest().key(function(d){return d.Color}).entries(d.values)},color,clickEvent) .draw(layerFacet) else - g3geoms[geom](layerFacet,function(d){return d.values},color,clickEvent) + g3geoms[geom](layerFacet,function(d){return [d.values]},color,clickEvent) .fast_redraw(layerFacet) break; From b51df63b074497ce3f023c400663e2f5b4610d18 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Thu, 12 Sep 2013 11:38:35 -0700 Subject: [PATCH 086/112] includes fix lost includes --- g3widget.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/g3widget.html b/g3widget.html index 5819f30..f113438 100644 --- a/g3widget.html +++ b/g3widget.html @@ -7,8 +7,8 @@ - - + + From 26fefda5bd69e59d18fd75baeae739bf1ec186ca Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Thu, 12 Sep 2013 11:39:03 -0700 Subject: [PATCH 087/112] includes fix lost includes --- g3widget.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/g3widget.html b/g3widget.html index f113438..6fa459e 100644 --- a/g3widget.html +++ b/g3widget.html @@ -1,7 +1,9 @@ - - + + + From 61624bb8873540a92a69dbbda211a105fd452315 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Thu, 12 Sep 2013 11:41:59 -0700 Subject: [PATCH 088/112] use external includes --- g3widget.html | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/g3widget.html b/g3widget.html index 6fa459e..f113438 100644 --- a/g3widget.html +++ b/g3widget.html @@ -1,9 +1,7 @@ - - - + + From fbc196d7078e0a0b2127a080c4830c4b20a38f5e Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Thu, 12 Sep 2013 11:44:26 -0700 Subject: [PATCH 089/112] Airpassengers is demo of area plot --- notes.txt | 1 + plots.R | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/notes.txt b/notes.txt index 2a6f6b1..5cb99d1 100644 --- a/notes.txt +++ b/notes.txt @@ -89,3 +89,4 @@ Other Bugs: Geom Clicking not tested yet DatapointSelector is clearly wrong - should only be for 'master' layer - or for all layers separately. There's a stray NA in layer +Area should base at zero perhaps? or call it something other than area - such as fill diff --git a/plots.R b/plots.R index 7225c93..3ba38f2 100644 --- a/plots.R +++ b/plots.R @@ -521,9 +521,10 @@ AirPassengers_plot <- function(dataName) { table=forceTableVector(dataSet2), structure=selfList(names(dataSet2)), aesthetic=list(X="date",Y="passengers"), - geom="line", + geom="area", labels=list(x="Date",y="Passenger count"), scales=list(x="date"), + extents=list(y=0), onZoom=T ), list(type="plot", @@ -653,4 +654,4 @@ wikipedia_nav_plot <- function(dataName) { } -# TODO: Ozone is very interesting \ No newline at end of file +# TODO: Ozone is very interesting From 18de184369b6c8e2f02806180abafc64e5156223 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Thu, 12 Sep 2013 17:27:24 -0700 Subject: [PATCH 090/112] Fix line 'animations' - color is a transition not a permanent state --- js/g3geoms.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/g3geoms.js b/js/g3geoms.js index 48464f9..046c9b7 100644 --- a/js/g3geoms.js +++ b/js/g3geoms.js @@ -334,7 +334,6 @@ .datum(function(d){return d}) .attr("class", "line") .style("fill","none") - .style("stroke",function(d){return color(d.key)}) .style("strokewidth","1px") .attr("d", function(d){return geom.position.call(this,d3.svg.line()).call(this,d.values)}) .append("title") @@ -348,6 +347,7 @@ a // .transition() + .style("stroke",function(d){return color(d.key)}) .attr("d", function(d){return geom.position.call(this,d3.svg.line()).call(this,d.values)}) a.exit() From fa7bdbe9896be1d80d1c6f831afbf57a9c084038 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Thu, 12 Sep 2013 17:28:05 -0700 Subject: [PATCH 091/112] comment --- notes.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/notes.txt b/notes.txt index 5cb99d1..d7ad717 100644 --- a/notes.txt +++ b/notes.txt @@ -90,3 +90,4 @@ Geom Clicking not tested yet DatapointSelector is clearly wrong - should only be for 'master' layer - or for all layers separately. There's a stray NA in layer Area should base at zero perhaps? or call it something other than area - such as fill +Layers should be KEYED for animation purposes From f2e42741531d7b879cbe8f0855c06e53d43ee536 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Thu, 12 Sep 2013 17:52:32 -0700 Subject: [PATCH 092/112] Add Range property to plotspec to allow actual colors used to be overridden --- js/g3subfigure.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index b8417c3..3e145d6 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -238,6 +238,10 @@ } else { color.domain(_.unique(_.pluck(aesData,"Color")).sort()) } + + if (plan.data.message.range && plan.data.message.range.Color) { + color.range(plan.data.message.range.Color) + } } break; case "linear": From cc658191fd77a5891878b6852d694b3b089934ff Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Fri, 13 Sep 2013 10:07:52 -0700 Subject: [PATCH 093/112] don't color lines from the group aesthetic - only the color aesthetic --- js/g3geoms.js | 2 +- js/g3subfigure.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/js/g3geoms.js b/js/g3geoms.js index 046c9b7..d874df6 100644 --- a/js/g3geoms.js +++ b/js/g3geoms.js @@ -347,7 +347,7 @@ a // .transition() - .style("stroke",function(d){return color(d.key)}) + .style("stroke",function(d){return color(d.Color)}) .attr("d", function(d){return geom.position.call(this,d3.svg.line()).call(this,d.values)}) a.exit() diff --git a/js/g3subfigure.js b/js/g3subfigure.js index 3e145d6..b20a803 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -926,9 +926,9 @@ if(!fast) dataPointSelector = g3geoms[geom](layerFacet,function(d){ return _.map(d3.nest().key(function(d) { - return d.Color + return d.Group || d.Color }).entries(d.values),function(c) { - c.Color = c.key; return c; + if(aesthetic.Color) {c.Color = c.key}; return c; })},color,clickEvent) .draw(layerFacet) else From cb08be8a24ae8e8e94abff288b4499897f768a23 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Fri, 13 Sep 2013 10:08:53 -0700 Subject: [PATCH 094/112] more todos --- notes.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/notes.txt b/notes.txt index d7ad717..9f14f8f 100644 --- a/notes.txt +++ b/notes.txt @@ -91,3 +91,4 @@ DatapointSelector is clearly wrong - should only be for 'master' layer - or for There's a stray NA in layer Area should base at zero perhaps? or call it something other than area - such as fill Layers should be KEYED for animation purposes +bar (rangeband) should be available with linear scales, too. From b2767845fdb8a3949e1cb38235be7fa133d7c8cb Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Mon, 16 Sep 2013 16:10:46 -0700 Subject: [PATCH 095/112] allow color scales even without color aes - let's us color the defaults --- g3widget.html | 6 ++++-- js/g3subfigure.js | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/g3widget.html b/g3widget.html index f113438..6fa459e 100644 --- a/g3widget.html +++ b/g3widget.html @@ -1,7 +1,9 @@ - - + + + diff --git a/js/g3subfigure.js b/js/g3subfigure.js index b20a803..3b492da 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -239,9 +239,11 @@ color.domain(_.unique(_.pluck(aesData,"Color")).sort()) } - if (plan.data.message.range && plan.data.message.range.Color) { + + } + // Load color scales anyway - to set the defaults. + if (plan.data.message.range && plan.data.message.range.Color) { color.range(plan.data.message.range.Color) - } } break; case "linear": From 656b6cca44130e1f246932deb820bf4401999831 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Mon, 16 Sep 2013 16:13:17 -0700 Subject: [PATCH 096/112] added rudimentary x grid support (need to be able to deactivate) --- g3widget.html | 3 ++- js/g3subfigure.js | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/g3widget.html b/g3widget.html index 6fa459e..e97333a 100644 --- a/g3widget.html +++ b/g3widget.html @@ -37,10 +37,11 @@ .axis path, .axis line { fill: none; - stroke: black; + stroke: lightgrey; stroke-width: 1px; shape-rendering: crispEdges; // warning: zoom out in chrome disappears 1px crisp lines. } + rect.haxis { // stroke: white; // stroke-width: 1; diff --git a/js/g3subfigure.js b/js/g3subfigure.js index 3b492da..ca7d5b9 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -732,9 +732,10 @@ } - if (fast) - s.call(d.xAxis) - else + if (fast) { + s.call(d.xAxis + .tickSize(-height)) // enables (flickery) x grid - remove to just have ticks + } else s .transition() .call(d.xAxis) From d85101ad1bd29974e80447b9a0ecb5269fc671e1 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Mon, 16 Sep 2013 16:13:38 -0700 Subject: [PATCH 097/112] don't color Group if color is missing --- js/g3subfigure.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index ca7d5b9..bd4b364 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -931,7 +931,7 @@ return _.map(d3.nest().key(function(d) { return d.Group || d.Color }).entries(d.values),function(c) { - if(aesthetic.Color) {c.Color = c.key}; return c; + if(aesthetic.Color) {c.Color = c.values[0].Color}; return c; })},color,clickEvent) .draw(layerFacet) else From b7ea7b64039dda82572c91caeca6c8b28700b0d4 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Mon, 16 Sep 2013 16:21:10 -0700 Subject: [PATCH 098/112] remove grid flicker (turn off crispedges) --- g3widget.html | 2 +- js/g3subfigure.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/g3widget.html b/g3widget.html index e97333a..bcb182d 100644 --- a/g3widget.html +++ b/g3widget.html @@ -39,7 +39,7 @@ fill: none; stroke: lightgrey; stroke-width: 1px; - shape-rendering: crispEdges; // warning: zoom out in chrome disappears 1px crisp lines. + // shape-rendering: crispEdges; // warning: zoom out in chrome disappears 1px crisp lines. } rect.haxis { diff --git a/js/g3subfigure.js b/js/g3subfigure.js index bd4b364..3ea6475 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -738,7 +738,7 @@ } else s .transition() - .call(d.xAxis) + .call(d.xAxis.tickSize(-height)) return s; }) From 0daf7d8f62ed2ab1267541655a0ad7750255e4e3 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Mon, 16 Sep 2013 16:23:53 -0700 Subject: [PATCH 099/112] y grid also enabled --- js/g3subfigure.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index 3ea6475..f756e13 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -770,6 +770,7 @@ var PixelsPerTick = 20 d.yAxis.ticks(Math.min(extent/PixelsPerTick,10)) } + d.yAxis.tickSize(-width) d.yAxis(s) return s; }) From 646581cde2c14a651e2e71187814fb9ed1dfb888 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 17 Sep 2013 15:49:03 -0700 Subject: [PATCH 100/112] adjust legend margin --- js/g3figure.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/g3figure.js b/js/g3figure.js index 39cc4e7..639b1de 100644 --- a/js/g3figure.js +++ b/js/g3figure.js @@ -202,7 +202,7 @@ .each(function(plan,i){ var xAxisHeight = 0 var xClusterAxisHeight = 0 - var legendWidth = 60 + var legendWidth = 20 if (aestheticUtils.hasAesthetic(plan,"X")) { xAxisHeight += 25 } From b906c2a8740364cf57ffeb566fe88b37d7282a21 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 8 Oct 2013 15:01:45 -0700 Subject: [PATCH 101/112] Replace remaining uses of Fill with Color --- js/g3geoms.js | 8 ++++---- js/g3subfigure.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/js/g3geoms.js b/js/g3geoms.js index d874df6..b0d1795 100644 --- a/js/g3geoms.js +++ b/js/g3geoms.js @@ -520,7 +520,7 @@ .attr("width", function(d) { return xFacetScale(this).rangeBand() - x_padding; }) .attr("y", first?function(d) { return yFacetScale(this)(0) }:function(d) { return yFacetScale(this)(d.y0 + d.y) }) .attr("height", first?0:function(d) { return yFacetScale(this)(d.y0) - yFacetScale(this)(d.y0 + d.y) }) - .style("fill", function(d) { return color(d.Fill); }) + .style("fill", function(d) { return color(d.Color); }) }} geom.draw = function bar_draw(plot,data) { @@ -543,7 +543,7 @@ .append("title"); a.select("title") - .text( labelFn("Fill") ) + .text( labelFn("Color") ) a .on("click",event) // if null, removes event @@ -583,7 +583,7 @@ //.attr("y", function(d) { return yFacetScale(this)(d.y0 + d.y) }) //.attr("height", function(d) { return yFacetScale(this)(d.y0) - yFacetScale(this)(d.y0 + d.y) }) - .style("fill", function(d) { return color(d.Fill); }) + .style("fill", function(d) { return color(d.Color); }) }} geom.draw = function(plot,data) { @@ -608,7 +608,7 @@ .append("title"); a.select("title") - .text( labelFn("Fill") ) + .text( labelFn("Color") ) a .transition() diff --git a/js/g3subfigure.js b/js/g3subfigure.js index f756e13..c31abe2 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -57,7 +57,7 @@ // Do calculation for stacked bars. Not sure how this gets called. if(position && position.x == "stack") { // is this X? - g3stats.barStack(aesData, "Fill") + g3stats.barStack(aesData, "Color") } else { if (!_.isUndefined(aesthetic.XCluster)) { _.each(aesData,function(d){ From a006d4047a2e79c5a51610753b347e417c3db854 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 8 Oct 2013 15:02:23 -0700 Subject: [PATCH 102/112] allow graph 'limits' to absolutely clip ranges. works well for Y, less for X --- js/g3subfigure.js | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index c31abe2..a30cad4 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -149,9 +149,14 @@ // calculate unzoomed domains // note that this domain calculation ignores any use of x0 or dx and might not get // scales right for objects with those. - var xData = _.pluck(aesData,"X").concat(g3functional.pluralise(graph.extents && graph.extents.x)) - if (aestheticUtils.hasAesthetic(plan,"DX")) - xData = xData.concat(aesData.map(function(d){return +d.DX?+d.X+d.DX:+d.X})) // assume right extend DX + var xData + if (graph.limits && !_.isUndefined(graph.limits.x)) { + xData=graph.limits.x + } else { + xData = _.pluck(aesData,"X").concat(g3functional.pluralise(graph.extents && graph.extents.x)) + if (aestheticUtils.hasAesthetic(plan,"DX")) + xData = xData.concat(aesData.map(function(d){return +d.DX?+d.X+d.DX:+d.X})) // assume right extend DX + } var numerise = function(x){return _.map(x,function(x){return +x})} @@ -204,15 +209,19 @@ break; case "linear": yScale_master = d3.scale.linear() - - if (plan.data.message.extents && !_.isUndefined(plan.data.message.extents.y)) { - if (!_.isArray(plan.data.message.extents.y)) - plan.data.message.extents.y = [plan.data.message.extents.y] - yScale_master.domain(d3.extent(aesData.concat(_.map(plan.data.message.extents.y,function(y){return {y:y}})), - function(d) { return ((d.y0+d.y)||d.y); })).nice(); + + if (plan.data.message.limits && !_.isUndefined(plan.data.message.limits.y)) { + yScale_master.domain(plan.data.message.limits.y) } else { - // temporarily suppress 0 inclusion - yScale_master.domain(d3.extent(aesData, function(d) { return ((d.y0+d.y)||d.y); })).nice(); + if (plan.data.message.extents && !_.isUndefined(plan.data.message.extents.y)) { + if (!_.isArray(plan.data.message.extents.y)) + plan.data.message.extents.y = [plan.data.message.extents.y] + yScale_master.domain(d3.extent(aesData.concat(_.map(plan.data.message.extents.y,function(y){return {y:y}})), + function(d) { return ((d.y0+d.y)||d.y); })).nice(); + } else { + // temporarily suppress 0 inclusion + yScale_master.domain(d3.extent(aesData, function(d) { return ((d.y0+d.y)||d.y); })).nice(); + } } break; default: @@ -297,9 +306,14 @@ case "ordinal": { // possible options here allow domain to be adjusted per x facet // this code is a clone of code above in ordinal - var xData = _.pluck(facet.values,"X").concat(g3functional.pluralise(graph.extents && graph.extents.x)) - if (aestheticUtils.hasAesthetic(plan,"DX")) - xData = xData.concat(facet.values.map(function(d){return +d.DX?+d.X+d.DX:+d.X})) // assume right extend DX + var xData + if (graph.limits && _.isUndefined(graph.limits.x)) { + xData=graph.limits.x + } else { + xData = _.pluck(facet.values,"X").concat(g3functional.pluralise(graph.extents && graph.extents.x)) + if (aestheticUtils.hasAesthetic(plan,"DX")) + xData = xData.concat(facet.values.map(function(d){return +d.DX?+d.X+d.DX:+d.X})) // assume right extend DX + } x.domain(xData) From a41e5d2ed10d14484edfe8cdb44b89e95750b751 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 8 Oct 2013 16:41:02 -0700 Subject: [PATCH 103/112] change x and y limits to use limits.x.all and limits.y.all to allow space for limits.x.free, limits.x.nice etc --- js/g3subfigure.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index a30cad4..e3d49ab 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -150,8 +150,8 @@ // note that this domain calculation ignores any use of x0 or dx and might not get // scales right for objects with those. var xData - if (graph.limits && !_.isUndefined(graph.limits.x)) { - xData=graph.limits.x + if (graph.limits && graph.limits.x && !_.isUndefined(graph.limits.x.all)) { + xData=graph.limits.x.all } else { xData = _.pluck(aesData,"X").concat(g3functional.pluralise(graph.extents && graph.extents.x)) if (aestheticUtils.hasAesthetic(plan,"DX")) @@ -210,8 +210,8 @@ case "linear": yScale_master = d3.scale.linear() - if (plan.data.message.limits && !_.isUndefined(plan.data.message.limits.y)) { - yScale_master.domain(plan.data.message.limits.y) + if (plan.data.message.limits && plan.data.message.limits.y && !_.isUndefined(plan.data.message.limits.y.all)) { + yScale_master.domain(plan.data.message.limits.y.all) } else { if (plan.data.message.extents && !_.isUndefined(plan.data.message.extents.y)) { if (!_.isArray(plan.data.message.extents.y)) @@ -307,8 +307,8 @@ // possible options here allow domain to be adjusted per x facet // this code is a clone of code above in ordinal var xData - if (graph.limits && _.isUndefined(graph.limits.x)) { - xData=graph.limits.x + if (graph.limits && graph.limits.x && _.isUndefined(graph.limits.x.all)) { + xData=graph.limits.x.all } else { xData = _.pluck(facet.values,"X").concat(g3functional.pluralise(graph.extents && graph.extents.x)) if (aestheticUtils.hasAesthetic(plan,"DX")) From 9c3ddbbf6a2ad2e1f203b0b41316c0028e43d803 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Tue, 8 Oct 2013 16:41:38 -0700 Subject: [PATCH 104/112] Add untested yfacet free limits --- js/g3subfigure.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index e3d49ab..d9c114e 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -359,7 +359,34 @@ graph.dimensions.facetMargin.y : 20 var y = yScale_master.copy() + + // if Y scales are free, recalculate them here + if (graph.limits && graph.limits.y && graph.limits.y.free) { + switch(scaleY) { + case "log": + y = d3.scale.log() + // extent is over ALL data here - is that appropriate? not always. + .domain(d3.extent(facet.values, function(d) { return d.Y; })) + break; + case "linear": + y = d3.scale.linear() + if (plan.data.message.extents && !_.isUndefined(plan.data.message.extents.y)) { + if (!_.isArray(plan.data.message.extents.y)) + plan.data.message.extents.y = [plan.data.message.extents.y] + y.domain(d3.extent(facet.values.concat(_.map(plan.data.message.extents.y,function(y){return {y:y}})), + function(d) { return ((d.y0+d.y)||d.y); })).nice(); + } else { + // temporarily suppress 0 inclusion + y.domain(d3.extent(facet.values, function(d) { return ((d.y0+d.y)||d.y); })).nice(); + } + break; + default: + throw("Unknown x scale type: \""+scaleY+"\""); + break; + } + } + y.range([(countFacets-i)*(height+facetMargin)/countFacets-facetMargin, (countFacets-1-i)*(height+facetMargin)/countFacets]) From fabcacad782b3e9fac9b13a6b9f8478734311ac0 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Thu, 7 Nov 2013 17:33:36 -0800 Subject: [PATCH 105/112] move to external javascript --- g3widget.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/g3widget.html b/g3widget.html index bcb182d..6498f1c 100644 --- a/g3widget.html +++ b/g3widget.html @@ -1,9 +1,9 @@ - - - + + + + From 22c567111cfdcbfa70ca0e26de9d397072d6749c Mon Sep 17 00:00:00 2001 From: Alex B Brown Date: Fri, 8 Nov 2013 20:14:36 +0000 Subject: [PATCH 106/112] fixes for demo app including allowing atomic keys and disabling voronoi until I fix it. --- js/g3geoms.js | 2 +- plots.R | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/js/g3geoms.js b/js/g3geoms.js index 0c0639f..42a41a3 100644 --- a/js/g3geoms.js +++ b/js/g3geoms.js @@ -21,7 +21,7 @@ // Key should uniquely identify a dot (part), and is used for animation. // Note that if key does NOT uniquely define a point then points may // be dropped - if (!_.isUndefined(d.Key)) return _.isDate(d.Key)?[""+(+d.Key)]:_.values(d.Key) + if (!_.isUndefined(d.Key)) return _.isDate(d.Key)?[""+(+d.Key)]:_.isObject(d.Key)?_.values(d.Key):[""+(+d.Key)] return i } diff --git a/plots.R b/plots.R index 5de0ac8..f5fc086 100644 --- a/plots.R +++ b/plots.R @@ -107,7 +107,7 @@ diamonds_plot <- function(...) { name="diamonds", structure=list(Key="id", Character=list(Cut="cut",Clarity="clarity",color="color"), Measurements=c(carat="carat",x="x",y="y",z="z",table="table",color="color"), Value="price"), - aesthetic=list(Key="Key", XCluster=list(Cut=list("Character","Cut")), Y="Value", + aesthetic=list(Key=list(Key="Key"), XCluster=list(Cut=list("Character","Cut")), Y="Value", Color=list("Measurements","color"), X=list("Measurements","carat") ), labels=list(x="Character of diamond", y="price"), @@ -432,7 +432,8 @@ airquality_plot <- function(dataName) { structure=list(Rownames="row",Measurements=selfList(c("Temp","Solar.R"))), aesthetic=list(Key="Rownames",XFilterKey="Rownames",Y=list("Measurements","Temp"), X=list("Measurements","Solar.R")), #labels=list(x=field, y="Count"), - geom=c("voronoi","point"), + # geom=c("voronoi","point"), + geom=c("point"), onBrush=list(x=list(drag=list(filter=TRUE))), scales=list(x="linear",y="linear"), labels=list(y="Temp",x="Solar Radiation"), From daa88bb145f0e4c946d817a85c74c363e2bd81cd Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Wed, 13 Nov 2013 13:52:47 -0800 Subject: [PATCH 107/112] Visual fix for co-incident voronoi --- js/g3geoms.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/js/g3geoms.js b/js/g3geoms.js index 9dbc249..af3f0d9 100644 --- a/js/g3geoms.js +++ b/js/g3geoms.js @@ -181,6 +181,10 @@ .append("title") voronoi + // note that the next line is not mactched by one that can + // remove voronois that failed to pass through - this is caused + // by do-incident points. TODO + .filter(function(d,i) { return !_.isUndefined(d.voronoiPath) }) .attr("clip-path", function(d,i) { return "url(#clip-"+d.voronoiClipID+")"; }) .attr("d", function(d){ //return geom.positionPath.call(this,d3.svg.line()).call(this,d.voronoiPath) // possible unscaled linear version From d65ec93c19efe8ae974bac55b2269e3b7030a238 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Wed, 13 Nov 2013 14:34:09 -0800 Subject: [PATCH 108/112] updated README with getting started instructions for new apps, and a geom description --- README.md | 93 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 78 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index b28ee1f..a664d64 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ A demo framework for Shiny + D3 including ggplot-like aesthetic mapping and geom Written by Alex B Brown at Intel Corp, 2012-2013 -Core idea: Pick a dataset, then describe how that data is mapped into a graph, using the handy-dangy ggplot2 like format. +Core idea: Pick a dataset, then describe how that data is mapped into a graph, using the handy-dandy ggplot2 like format. Includes interactive features such as click and drag. @@ -21,6 +21,10 @@ See the files LICENCE and NOTICE for licence terms. Usage as a demo app ------------------- +Recommended packages to install: + +shiny, plyr, httr, hmisc, reshape2, stringr, lubridate, ggplot2 (plus dependencies), + To run g3plot in demo mode, 1) start R in this directory. @@ -38,16 +42,63 @@ Look in plot.R and add some functions for other data sets. If that gets old, go If *that* gets old, start to create new graph types in javascript, or fix the html table logic. -Usage as your own app ---------------------- +Writing a new g3plot application +-------------------------------- + +You can start with this super simple application and extend it: -Checkout g3plot as a subdirectory of your Shiny Application and add the line +mkdir myproject +cd myproject +git clone g3plot +Create the file `server.R` with the contents: + +``` +require(shiny) +source("g3plot/shiny_extend_g3.R") +addResourcePath("js",tools:::file_path_as_absolute("./g3plot/js")) +shinyServer(function(input,output,session){ + output$testplot = renderG3Plot(function() { + dataSet = data.frame(x=1:10,y=1:10) + list(type="plot", + table=forceTableVector(dataSet), + structure=list(sX="x",sY="y"), + aesthetic=list(X="sX",Y="sY"), + geom="point")}) +}) +``` + +Create the file `ui.R` with the contents: + +``` +require(shiny) +source("g3plot/shiny_extend_g3.R") +shinyUI( + pageWithSidebar(div(),div(), + div(includeHTML("g3plot/g3widget.html"), + svgOutput("testplot"))) +) +``` + +Updating an existing shiny application to use g3plot +---------------------------------------------------- + +Checkout g3plot as a subdirectory of your Shiny Application + +``` +cd myproject +git clone g3plot +``` + +and add the line + +``` addResourcePath("js",tools:::file_path_as_absolute("./g3plot/js")) +``` -To your server.R. +To your `server.R`. -Then follow some of the examples in server.R and ui.R and friends to add +Then follow some of the examples in the demo `server.R` and `ui.R` and friends to add javascript plots to your Shiny Application. Note that you can still test the demo app by using @@ -59,12 +110,24 @@ Geoms Currently supports: -point -bar -point_bar -point_range_bar -line -voronoi (doesn't display but makes UI better) +name | required aesthetics | optional aesthetics | axis | description +----------------|---------------------|---------------------|--------|--- +point | X,Y | Color | cont** | a small round point +line | X,Y,Group | Group,Color | cont | a line for each Group (or Color) +area | X,Y,Group | Group,Color | cont | an area underneath the line for each group (stacking?) +bar | X,Y | Color | ordinal| a bar starting at 0, can be stacked +point_bar | X,Y | Color | ordinal| instead of a whole bar, just the tip +point_range_bar | X,DX,Y | Color | cont | like point bar but each can have a unique width (DX) +voronoi | X,Y,Label | | cont | Use with points to extend click/hover halo around point + +** *cont* is `linear` or `log` + +Other aesthetics supported by most geoms include: + +* Label - What appears when you hover. Default is cloned from XCluster or X aesthetic. +* XCluster - Compound X axis - see examples for more details. +* YFacet - Facet plot into rows with separate synchronised Y axes. +* Key - improve animation by giving each node a unique key. Layout Structure ---------------- @@ -102,16 +165,16 @@ Fun improvements: * Standardised way to add dynamic tooltips * Standardised way to hover highlight nodes - * Improved click dropzones (voronoi?) + * Improved click dropzones (voronoi?) (now implemented) * click drag on axes to scale (near ends) or pan (in middle) + * Mouseover cursor with tooltip coordinates of intersecting line / point and selection like brushes. -Some ideas to develop the product +Some ideas to develop the tool * Sparklines - providing a very simple 3-layer structure without faceting * Layers - multiple geoms on top of each other (another layer!) (now implemented) * g3autoplot - looks at data and does something sensible, then tells you how to repeat / customise it - Constraints ----------- From ce9ce8da2b9c1119d4b85da01a4c59da2f345889 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Wed, 13 Nov 2013 14:34:32 -0800 Subject: [PATCH 109/112] updated comments in touch zoom --- js/g3subfigure.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index d9c114e..1c975f9 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -590,11 +590,12 @@ if (d3.event.sourceEvent.type=="mousewheel") { - // this case is bad for touch based zooming - it starts to shrink it while the user is still var isTouch = false if (isTouch) d3.select(window).on("mousemove.zoomadjust",_.once(zoomAdjust.call(this,d3.event).adjust)) else + // this case is bad for touch based zooming - it starts to shrink it while the user is still + // in multitouch _.defer(zoomAdjust.call(this,d3.event).adjust) // I should make new ones eat older ones. } From ca31b831eb00d35695ce6d200204c465f7cba87e Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Wed, 13 Nov 2013 14:59:50 -0800 Subject: [PATCH 110/112] Handle null in line or area as missing point --- js/g3geoms.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/js/g3geoms.js b/js/g3geoms.js index af3f0d9..1cd5012 100644 --- a/js/g3geoms.js +++ b/js/g3geoms.js @@ -266,7 +266,7 @@ .x(function(d) { return xFacetScale(this)(d.X) }) .y1(function(d) { return yFacetScale(this)(d.Y) }) .y0(function(d) { return yFacetScale(this)(0) }) - .defined(function(d) { return !d.Missing }) + .defined(function(d) { return !d.Missing && d.y!=null }) return (l) } @@ -324,7 +324,8 @@ l .x(function(d) { return xFacetScale(this)(d.X) }) .y(function(d) { return yFacetScale(this)(d.Y) }) - .defined(function(d) { return !d.Missing }) + // http://stackoverflow.com/questions/15259444/drawing-non-continuous-lines-with-d3 + .defined(function(d) { return !d.Missing && d.y!=null }) return (l) } From 2cf9ffcd7b854629a066db5d06d613cbc8f2cbe3 Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Wed, 13 Nov 2013 15:00:35 -0800 Subject: [PATCH 111/112] Add plotspec limits:{x: y:} to restrict data range even if wider data exists --- js/g3subfigure.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index 1c975f9..122018c 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -371,14 +371,19 @@ case "linear": y = d3.scale.linear() - if (plan.data.message.extents && !_.isUndefined(plan.data.message.extents.y)) { - if (!_.isArray(plan.data.message.extents.y)) - plan.data.message.extents.y = [plan.data.message.extents.y] - y.domain(d3.extent(facet.values.concat(_.map(plan.data.message.extents.y,function(y){return {y:y}})), - function(d) { return ((d.y0+d.y)||d.y); })).nice(); + if (plan.data.message.limits && plan.data.message.limits.y && plan.data.message.limits.y.facets && + !_.isUndefined(plan.data.message.limits.y.facets[facet.key])) { + y.domain(plan.data.message.limits.y.facets[facet.key]) } else { - // temporarily suppress 0 inclusion - y.domain(d3.extent(facet.values, function(d) { return ((d.y0+d.y)||d.y); })).nice(); + if (plan.data.message.extents && !_.isUndefined(plan.data.message.extents.y)) { + if (!_.isArray(plan.data.message.extents.y)) + plan.data.message.extents.y = [plan.data.message.extents.y] + y.domain(d3.extent(facet.values.concat(_.map(plan.data.message.extents.y,function(y){return {y:y}})), + function(d) { return ((d.y0+d.y)||d.y); })).nice(); + } else { + // temporarily suppress 0 inclusion + y.domain(d3.extent(facet.values, function(d) { return ((d.y0+d.y)||d.y); })).nice(); + } } break; default: From 68770e91141c4f9550ac6852efed0da43a03327f Mon Sep 17 00:00:00 2001 From: Alex Brown Date: Wed, 13 Nov 2013 15:00:49 -0800 Subject: [PATCH 112/112] found odd code that needs attention later --- js/g3subfigure.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/g3subfigure.js b/js/g3subfigure.js index 122018c..0564ab5 100644 --- a/js/g3subfigure.js +++ b/js/g3subfigure.js @@ -505,7 +505,7 @@ .attr("width",function(d){return d.xExtent[1]-d.xExtent[0]}) .attr("x",function(d){return d.x}) .attr("y",0) - .attr("height",1000) + .attr("height",1000) // should be actual height? //.append("svg") // .append("rect").attr("class","svgbg").style("fill","#FFF") //.attr("y",function(d){return this.parentNode.__data__.yScale.range()[0]})