Skip to content

Commit edcfacf

Browse files
authored
Improve test coverage for charts.jl (#445)
1 parent ebc9cbb commit edcfacf

2 files changed

Lines changed: 119 additions & 14 deletions

File tree

src/charts.jl

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,6 @@
1111
# never re-read from the worksheet, so it reflects the values as of the last time
1212
# Excel saved the file.
1313
#
14-
# Include after images.jl (reuses `_drawing_path_for_sheet`, `_parse_cell_marker`,
15-
# `elements_with_tag`, `get_attr`, `root_element`) and after cell.jl
16-
# (`ERROR_STRING_TO_CODE`, `get_error_string`).
17-
#
18-
# Traversal is done with the XML.jl API directly - `XML.eachelement` (a lazy
19-
# filter, so scanning for one child allocates nothing) and `XML.is_simple_value`
20-
# (text of a leaf element, handling CData). Only `first_element_with_tag`,
21-
# `child_text`, `child_val` and `get_attr_localname` are defined here, each
22-
# because XML.jl has no equivalent and the package has no existing one.
23-
#
2414

2515
const REL_CHART =
2616
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart"
@@ -44,7 +34,7 @@ const ChartAnchor = NamedTuple{
4434
Tuple{String,Union{Nothing,String},Union{Nothing,String},String},
4535
}
4636

47-
const ChartRange = Union{Nothing,SheetCellRef,SheetCellRange,SheetColumnRange,NonContiguousRange}
37+
const ChartRange = Union{Nothing,SheetCellRef,SheetCellRange,SheetRowRange,SheetColumnRange,NonContiguousRange}
4838

4939
const ChartRanges = @NamedTuple{
5040
idx::Int,
@@ -658,7 +648,7 @@ getChartData(x::Union{Worksheet,XLSXFile}, name::AbstractString; kw...)::DataTab
658648
getChartData(getChart(x, name; kw...))
659649

660650
"""
661-
chart_range(r) -> Union{Nothing,SheetCellRef,SheetCellRange,SheetColumnRange,NonContiguousRange}
651+
chart_range(r) -> Union{Nothing,SheetCellRef,SheetCellRange,SheetRowRange,SheetColumnRange,NonContiguousRange}
662652
663653
The source range of a `ChartRef`, or `nothing` when it has no addressable one:
664654
literal series, external-workbook references, and defined names.
@@ -673,7 +663,6 @@ function chart_range(r::Union{Nothing,ChartRef})
673663
occursin(',', s) && return NonContiguousRange(String(s))
674664
(is_valid_fixed_sheet_cellrange(s) || is_valid_sheet_cellrange(s)) && return SheetCellRange(s)
675665
(is_valid_fixed_sheet_cellname(s) || is_valid_sheet_cellname(s)) && return SheetCellRef(s)
676-
is_valid_sheet_column_range(s) && return SheetColumnRange(s)
677666
(is_valid_fixed_sheet_column_range(s) || is_valid_sheet_column_range(s)) && return SheetColumnRange(s)
678667
(is_valid_fixed_sheet_row_range(s) || is_valid_sheet_row_range(s)) && return SheetRowRange(s)
679668
return nothing # defined name, or unrecognised
@@ -745,8 +734,9 @@ getChartRanges(c::Chart)::Vector{ChartRanges} =
745734
getChartRanges(x::Union{Worksheet,XLSXFile}, name::AbstractString)::Vector{ChartRanges} =
746735
getChartRanges(getChart(x, name; cache=false))
747736

748-
getChartRanges(x::Union{Worksheet,XLSXFile}) =
737+
getChartRanges(x::Union{Worksheet,XLSXFile}) =
749738
[(chart = c.name, ranges = getChartRanges(c)) for c in getCharts(x; cache=false)]
739+
750740
# ===========================================================================
751741
# Display
752742
# ===========================================================================

test/test_files/Charts_tests.jl

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@
1414
@testset "basic chart" begin # chart_basic.xlsx
1515
f = XLSX.readxlsx(joinpath(data_directory, "chart_basic.xlsx"))
1616

17+
c = XLSX.getChart(f["Data"], "chart1")
18+
@test occursin("chart1", repr(c))
19+
@test occursin("series", repr(MIME"text/plain"(), c))
20+
@test occursin("ChartSeries", repr(c.series[1]))
21+
@test occursin("pts", repr(MIME"text/plain"(), c.series[1].values))
22+
@test occursin("pts", sprint(show, c.series[1].values))
23+
@test occursin("categories", repr(MIME"text/plain"(), c.series[1]))
24+
1725
charts = XLSX.getCharts(f)
1826
@test length(charts) == 1
1927

@@ -363,4 +371,111 @@
363371
rm(tmp; force=true)
364372
end
365373

374+
@testset "chart_range" begin
375+
cr(ref) = XLSX.chart_range(XLSX.ChartRef(:num, ref, nothing, 0, Any[], Dict{Int,UInt64}()))
376+
377+
@test cr(nothing) === nothing
378+
@test XLSX.chart_range(nothing) === nothing
379+
@test cr("[1]Sheet1!\$A\$1:\$A\$5") === nothing # external
380+
@test cr("MyDefinedName") === nothing # defined name
381+
@test cr("Sheet1!\$A\$1:\$A\$5") isa XLSX.SheetCellRange
382+
@test cr("Sheet1!A1:A5") isa XLSX.SheetCellRange
383+
@test cr("Sheet1!\$A\$1") isa XLSX.SheetCellRef
384+
@test cr("Sheet1!A:C") isa XLSX.SheetColumnRange
385+
@test cr("(Sheet1!\$A\$1:\$A\$3,Sheet1!\$C\$1:\$C\$3)") isa XLSX.NonContiguousRange
386+
@test cr("Sheet1!\$A\$1:\$A\$3,Sheet1!\$C\$1:\$C\$3") isa XLSX.NonContiguousRange
387+
end
388+
@testset "row-range source (ChartRange union)" begin
389+
rr(ref) = XLSX.ChartRef(:num, ref, nothing, 0, Any[], Dict{Int,UInt64}())
390+
391+
@test XLSX.chart_range(rr("Sheet1!\$2:\$5")) isa XLSX.SheetRowRange
392+
@test XLSX.chart_range(rr("Sheet1!2:5")) isa XLSX.SheetRowRange
393+
@test XLSX.chart_range(rr("Sheet1!\$A:\$C")) isa XLSX.SheetColumnRange
394+
395+
# the conversion that used to throw
396+
s = XLSX.ChartSeries(0, 0, :barChart, "S", nothing,
397+
rr("Sheet1!\$2:\$2"), rr("Sheet1!\$3:\$3"), nothing)
398+
c = XLSX.Chart("xl/charts/chart1.xml", "chart1", nothing, nothing, nothing, nothing,
399+
nothing, [:barChart], [s])
400+
ranges = XLSX.getChartRanges(c)
401+
@test ranges[1].categories isa XLSX.SheetRowRange
402+
@test ranges[1].values isa XLSX.SheetRowRange
403+
end
404+
405+
@testset "ChartRange union covers chart_range" begin
406+
@test XLSX.SheetCellRef <: XLSX.ChartRange
407+
@test XLSX.SheetCellRange <: XLSX.ChartRange
408+
@test XLSX.SheetColumnRange <: XLSX.ChartRange
409+
@test XLSX.SheetRowRange <: XLSX.ChartRange
410+
@test XLSX.NonContiguousRange <: XLSX.ChartRange
411+
@test Nothing <: XLSX.ChartRange
412+
end
413+
414+
@testset "unique labels" begin
415+
labels = Symbol[]
416+
@test XLSX.unique_label!(labels, "Sales") === :Sales
417+
@test XLSX.unique_label!(labels, "Sales") === :Sales_2
418+
@test XLSX.unique_label!(labels, "Sales") === :Sales_3
419+
@test XLSX.unique_label!(labels, "") === :column
420+
end
421+
422+
@testset "getChartRanges dispatch" begin
423+
f = XLSX.readxlsx(joinpath(data_directory, "chart_bubble.xlsx"))
424+
425+
# workbook-wide form: Vector{@NamedTuple{chart::String, ranges::Vector{ChartRanges}}}
426+
all_f = XLSX.getChartRanges(f)
427+
@test all_f isa Vector
428+
@test length(all_f) == 2
429+
@test all(x -> x isa NamedTuple{(:chart, :ranges)}, all_f)
430+
@test all(x -> x.chart isa String, all_f)
431+
@test all(x -> x.ranges isa Vector{XLSX.ChartRanges}, all_f)
432+
433+
# follows getCharts, per the docstring
434+
@test [x.chart for x in all_f] == [c.name for c in XLSX.getCharts(f; cache=false)]
435+
436+
# identify the two charts by type rather than by part name
437+
charts = XLSX.getCharts(f; cache=false)
438+
bub = charts[findfirst(c -> :bubbleChart in c.charttypes, charts)]
439+
pie = charts[findfirst(c -> :pieChart in c.charttypes, charts)]
440+
441+
# --- (x, name) form -----------------------------------------------------
442+
rb = XLSX.getChartRanges(f, bub.name)
443+
rp = XLSX.getChartRanges(f, pie.name)
444+
@test rb isa Vector{XLSX.ChartRanges}
445+
@test rp isa Vector{XLSX.ChartRanges}
446+
447+
# parallel to c.series, document order
448+
@test length(rb) == length(bub.series)
449+
@test [x.idx for x in rb] == [s.idx for s in bub.series]
450+
@test [x.name for x in rb] == [s.name for s in bub.series]
451+
452+
# every field is a member of the declared union
453+
for x in vcat(rb, rp), fld in (:categories, :values, :bubble_sizes)
454+
@test getfield(x, fld) isa XLSX.ChartRange
455+
end
456+
457+
# the docstring's specific claim: bubble_sizes only on bubble charts
458+
@test any(!isnothing(x.bubble_sizes) for x in rb)
459+
@test all( isnothing(x.bubble_sizes) for x in rp)
460+
461+
# bubble uses xVal/yVal, which land in categories/values
462+
@test all(!isnothing(x.categories) for x in rb)
463+
@test all(!isnothing(x.values) for x in rb)
464+
465+
# name forms getChart accepts
466+
@test XLSX.getChartRanges(f, bub.name * ".xml") == rb
467+
468+
@test_throws XLSX.XLSXError XLSX.getChartRanges(f, "nosuchchart")
469+
470+
# --- worksheet form agrees with the workbook form ------------------------
471+
ws = f[bub.sheet]
472+
all_ws = XLSX.getChartRanges(ws)
473+
@test all(x -> x isa NamedTuple{(:chart, :ranges)}, all_ws)
474+
@test issubset(Set(x.chart for x in all_ws), Set(x.chart for x in all_f))
475+
476+
i = findfirst(x -> x.chart == bub.name, all_f)
477+
@test !isnothing(i)
478+
@test all_f[i].ranges == rb
479+
480+
end
366481
end

0 commit comments

Comments
 (0)