-
Notifications
You must be signed in to change notification settings - Fork 3
RS-22971: Export a native date axis when category labels are dates #92
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
b6decb5
Export a native date axis when category labels are dates
chschan ee18837
Capture category dates for all input paths (raw date variables)
chschan 99b63a3
Preserve the user's categories.tick.format on the date axis
chschan 58d8ffd
Export category axis label rotation via LabelsRotation, not RotateLabels
chschan 94135b3
Fix category axis label rotation direction
chschan a9689ed
Gate LabelsRotation on the exporting Q's file format version
chschan feb6755
Add categories.axis.number.type to suppress the date axis when "Categ…
chschan 3836edd
Gate AxisType="Date" on Q version too, and require >= 28.08
chschan f4f0db6
convertToPPTDateFormat: bail out on unmapped strftime tokens
chschan d33011a
Send LabelsRotation as a real so fractional tick angles survive
chschan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,186 @@ | ||
| context("ChartSettings date axis") | ||
|
|
||
| # A native PowerPoint date axis is requested by attaching "category.dates" (numeric date serials) and | ||
| # "category.date.format" to the data; PrepareData does this when the category labels are dates. The | ||
| # ChartSettings$PrimaryAxis then reports AxisType = "Date" so Q can export a c:dateAx. See preparedata.R | ||
| # (transformTable) and cchart.R (getPPTSettings). | ||
|
|
||
| test_that("category.dates makes PrimaryAxis a date axis for categorical charts", | ||
| { | ||
| assign("QFileFormatVersion", 28.08, envir = .GlobalEnv) | ||
| on.exit(suppressWarnings(rm("QFileFormatVersion", envir = .GlobalEnv))) | ||
|
|
||
| dat <- matrix(1:10, ncol = 2, dimnames = list(LETTERS[1:5], c("A", "B"))) | ||
| attr(dat, "category.dates") <- as.numeric(as.Date("2020-01-01") + 0:4) | ||
| attr(dat, "category.date.format") <- "mmm dd yyyy" | ||
|
|
||
| for (chart.type in c("Column", "Area", "Line", "Bar")) | ||
| { | ||
| res <- suppressWarnings(CChart(chart.type, dat, append.data = TRUE)) | ||
| settings <- attr(res, "ChartSettings") | ||
| expect_equal(settings$PrimaryAxis$AxisType, "Date", info = chart.type) | ||
| expect_equal(settings$PrimaryAxis$NumberFormat, "mmm dd yyyy", info = chart.type) | ||
| } | ||
| }) | ||
|
|
||
| test_that("Absence of category.dates leaves a normal category axis", | ||
| { | ||
| dat <- matrix(1:10, ncol = 2, dimnames = list(LETTERS[1:5], c("A", "B"))) | ||
| res <- suppressWarnings(CChart("Column", dat, append.data = TRUE)) | ||
| expect_null(attr(res, "ChartSettings")$PrimaryAxis$AxisType) | ||
| }) | ||
|
|
||
| test_that("Pie charts never get a category date axis", | ||
| { | ||
| dat <- structure(1:5, .Names = LETTERS[1:5]) | ||
| attr(dat, "category.dates") <- as.numeric(as.Date("2020-01-01") + 0:4) | ||
| res <- suppressWarnings(CChart("Pie", dat, append.data = TRUE)) | ||
| expect_null(attr(res, "ChartSettings")$PrimaryAxis$AxisType) | ||
| }) | ||
|
|
||
| test_that("Date row labels flow through PrepareData to a native date axis (end to end)", | ||
| { | ||
| assign("QFileFormatVersion", 28.08, envir = .GlobalEnv) | ||
| on.exit(suppressWarnings(rm("QFileFormatVersion", envir = .GlobalEnv))) | ||
|
|
||
| serials <- as.numeric(as.Date("2020-01-01") + 0:4) | ||
| tbl <- matrix(1:10, ncol = 2, | ||
| dimnames = list(as.character(as.Date("2020-01-01") + 0:4), c("A", "B"))) | ||
|
|
||
| pd <- suppressWarnings(PrepareData("Column", input.data.table = tbl)) | ||
| expect_equal(attr(pd$data, "category.dates"), serials) | ||
|
|
||
| res <- suppressWarnings(CChart("Column", pd$data, append.data = TRUE)) | ||
| expect_equal(attr(res, "ChartSettings")$PrimaryAxis$AxisType, "Date") | ||
| expect_equal(attr(attr(res, "ChartData"), "category.dates"), serials) | ||
| }) | ||
|
|
||
| test_that("Date variable (raw data) flows through to a native date axis (end to end)", | ||
| { | ||
| assign("QFileFormatVersion", 28.08, envir = .GlobalEnv) | ||
| on.exit(suppressWarnings(rm("QFileFormatVersion", envir = .GlobalEnv))) | ||
|
|
||
| serials <- as.numeric(as.Date("2020-01-01") + 0:4) | ||
| input <- list(X = list(Date = as.Date("2020-01-01") + 0:4, Score = 1:5)) | ||
|
|
||
| pd <- suppressWarnings(PrepareData("Column", input.data.raw = input)) | ||
| expect_equal(attr(pd$data, "category.dates"), serials) | ||
|
|
||
| res <- suppressWarnings(CChart("Column", pd$data, append.data = TRUE)) | ||
| expect_equal(attr(res, "ChartSettings")$PrimaryAxis$AxisType, "Date") | ||
| expect_equal(attr(attr(res, "ChartData"), "category.dates"), serials) | ||
| }) | ||
|
|
||
| test_that("Non-date row labels do not trigger a date axis", | ||
| { | ||
| tbl <- matrix(1:10, ncol = 2, dimnames = list(LETTERS[1:5], c("A", "B"))) | ||
| pd <- suppressWarnings(PrepareData("Column", input.data.table = tbl)) | ||
| expect_null(attr(pd$data, "category.dates")) | ||
| }) | ||
|
|
||
| test_that("convertToPPTDateFormat maps d3 date formats and rejects non-date formats", | ||
| { | ||
| expect_equal(convertToPPTDateFormat("%Y"), "yyyy") | ||
| expect_equal(convertToPPTDateFormat("%d %b %Y"), "dd mmm yyyy") | ||
| expect_equal(convertToPPTDateFormat("%m %d %y"), "mm dd yy") | ||
| expect_equal(convertToPPTDateFormat("%B %d %Y"), "mmmm dd yyyy") | ||
| expect_equal(convertToPPTDateFormat("%H:%M"), "hh:mm") | ||
| expect_null(convertToPPTDateFormat("")) # Automatic / no format | ||
| expect_null(convertToPPTDateFormat(".0%")) # percentage, not a date | ||
| expect_null(convertToPPTDateFormat(",.0f")) # number, not a date | ||
| # Unmapped strftime tokens leave a stray "%" (which Excel reads as x100), so bail out to the fallback. | ||
| expect_null(convertToPPTDateFormat("%e %b %Y")) # %e (space-padded day) not mapped | ||
| expect_null(convertToPPTDateFormat("%-d %b %Y")) # %-d (no-pad day) not mapped | ||
| expect_null(convertToPPTDateFormat("%j")) # %j (day of year) not mapped | ||
| }) | ||
|
|
||
| test_that("A user-set date categories.tick.format is preserved on the date axis, else falls back", | ||
| { | ||
| assign("QFileFormatVersion", 28.08, envir = .GlobalEnv) | ||
| on.exit(suppressWarnings(rm("QFileFormatVersion", envir = .GlobalEnv))) | ||
|
|
||
| dat <- matrix(1:10, ncol = 2, dimnames = list(LETTERS[1:5], c("A", "B"))) | ||
| attr(dat, "category.dates") <- as.numeric(as.Date("2020-01-01") + 0:4) | ||
| attr(dat, "category.date.format") <- "mmm dd yyyy" # PrepareData's fallback | ||
|
|
||
| res <- suppressWarnings(CChart("Column", dat, append.data = TRUE, categories.tick.format = "%Y")) | ||
| expect_equal(attr(res, "ChartSettings")$PrimaryAxis$NumberFormat, "yyyy") | ||
|
|
||
| res <- suppressWarnings(CChart("Column", dat, append.data = TRUE, categories.tick.format = "%d %b %Y")) | ||
| expect_equal(attr(res, "ChartSettings")$PrimaryAxis$NumberFormat, "dd mmm yyyy") | ||
|
|
||
| # No user-set format -> the fallback PrepareData chose from the labels. | ||
| res <- suppressWarnings(CChart("Column", dat, append.data = TRUE)) | ||
| expect_equal(attr(res, "ChartSettings")$PrimaryAxis$NumberFormat, "mmm dd yyyy") | ||
| }) | ||
|
|
||
| test_that("LabelsRotation is only sent to Q versions that can parse it (28.08+)", | ||
| { | ||
| dat <- matrix(1:10, ncol = 2, dimnames = list(LETTERS[1:5], c("A", "B"))) | ||
| on.exit(if (exists("QFileFormatVersion", envir = .GlobalEnv)) rm("QFileFormatVersion", envir = .GlobalEnv)) | ||
|
|
||
| # New enough Q + a non-horizontal angle -> sent as a double, so fractional angles survive. | ||
| assign("QFileFormatVersion", 28.08, envir = .GlobalEnv) | ||
| res <- suppressWarnings(CChart("Column", dat, append.data = TRUE, categories.tick.angle = 90)) | ||
| expect_equal(attr(res, "ChartSettings")$PrimaryAxis$LabelsRotation, 90) | ||
| res <- suppressWarnings(CChart("Column", dat, append.data = TRUE, categories.tick.angle = 45.5)) | ||
| expect_equal(attr(res, "ChartSettings")$PrimaryAxis$LabelsRotation, 45.5) | ||
|
|
||
| # Older Q -> not sent, so it can't error the export. | ||
| assign("QFileFormatVersion", 28.06, envir = .GlobalEnv) | ||
| res <- suppressWarnings(CChart("Column", dat, append.data = TRUE, categories.tick.angle = 90)) | ||
| expect_null(attr(res, "ChartSettings")$PrimaryAxis$LabelsRotation) | ||
|
|
||
| # New Q but horizontal/default angle -> not sent (nothing to rotate). | ||
| assign("QFileFormatVersion", 28.08, envir = .GlobalEnv) | ||
| res <- suppressWarnings(CChart("Column", dat, append.data = TRUE, categories.tick.angle = 0)) | ||
| expect_null(attr(res, "ChartSettings")$PrimaryAxis$LabelsRotation) | ||
|
|
||
| # No version info at all -> not sent. | ||
| rm("QFileFormatVersion", envir = .GlobalEnv) | ||
| res <- suppressWarnings(CChart("Column", dat, append.data = TRUE, categories.tick.angle = 90)) | ||
| expect_null(attr(res, "ChartSettings")$PrimaryAxis$LabelsRotation) | ||
| }) | ||
|
|
||
| test_that("categories.axis.number.type = 'Category' exports date labels as plain categories", | ||
| { | ||
| assign("QFileFormatVersion", 28.08, envir = .GlobalEnv) | ||
| on.exit(suppressWarnings(rm("QFileFormatVersion", envir = .GlobalEnv))) | ||
|
|
||
| dat <- matrix(1:10, ncol = 2, dimnames = list(LETTERS[1:5], c("A", "B"))) | ||
| attr(dat, "category.dates") <- as.numeric(as.Date("2020-01-01") + 0:4) | ||
| attr(dat, "category.date.format") <- "mmm dd yyyy" | ||
|
|
||
| # Default -> date axis. | ||
| res <- suppressWarnings(CChart("Column", dat, append.data = TRUE)) | ||
| expect_equal(attr(res, "ChartSettings")$PrimaryAxis$AxisType, "Date") | ||
|
|
||
| # Explicit "Automatic" -> date axis. | ||
| res <- suppressWarnings(CChart("Column", dat, append.data = TRUE, categories.axis.number.type = "Automatic")) | ||
| expect_equal(attr(res, "ChartSettings")$PrimaryAxis$AxisType, "Date") | ||
|
|
||
| # "Category" -> no date axis (plain string categories). | ||
| res <- suppressWarnings(CChart("Column", dat, append.data = TRUE, categories.axis.number.type = "Category")) | ||
| expect_null(attr(res, "ChartSettings")$PrimaryAxis$AxisType) | ||
| }) | ||
|
|
||
| test_that("Date-axis ChartSettings are withheld from Q versions that cannot parse them", | ||
| { | ||
| dat <- matrix(1:10, ncol = 2, dimnames = list(LETTERS[1:5], c("A", "B"))) | ||
| attr(dat, "category.dates") <- as.numeric(as.Date("2020-01-01") + 0:4) | ||
| attr(dat, "category.date.format") <- "mmm dd yyyy" | ||
| on.exit(if (exists("QFileFormatVersion", envir = .GlobalEnv)) rm("QFileFormatVersion", envir = .GlobalEnv)) | ||
|
|
||
| # Old Q, and internal builds that predate the change and report 28.07 (28.06 + 0.01), can't parse | ||
| # AxisType = "Date" and would error the whole export - so it must not be sent. | ||
| for (v in c(28.06, 28.07)) { | ||
| assign("QFileFormatVersion", v, envir = .GlobalEnv) | ||
| res <- suppressWarnings(CChart("Column", dat, append.data = TRUE)) | ||
| expect_null(attr(res, "ChartSettings")$PrimaryAxis$AxisType, info = v) | ||
| } | ||
|
|
||
| # No version info at all -> also withheld. | ||
| rm("QFileFormatVersion", envir = .GlobalEnv) | ||
| res <- suppressWarnings(CChart("Column", dat, append.data = TRUE)) | ||
| expect_null(attr(res, "ChartSettings")$PrimaryAxis$AxisType) | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unmapped strftime tokens survive substitution and end up verbatim in the PPT format code. The predefined "Date/Time" dropdown formats are all covered by the token table, but
ChartNumberFormatreturns a user-typed custom format verbatim, and d3-time-format accepts tokens outside the mapped set (%e,%-d,%q,%j,%L,%Z, ...). Any of these passes thegrepl("%[A-Za-z]")date detection but is left untouched by the gsub loop.The consequence isn't just a literal rendering: in an Excel/PowerPoint number format, each
%multiplies the displayed value by 100, so a leftover token corrupts the axis labels (OADate serial x 100) rather than degrading gracefully.Cheap guard — treat any incomplete conversion as "not a date format" so the caller falls back to
category.date.format:Worth a test case alongside the existing
convertToPPTDateFormatones (e.g.expect_null(convertToPPTDateFormat("%e %b %Y"))).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in the latest push. After the substitution loop, any remaining
%(unmapped token or user-typed custom format) now returnsNULL, so the caller falls back tocategory.date.formatinstead of emitting a corruptx100axis. Addedexpect_nullcases for%e %b %Y,%-d %b %Y, and%j.