tests/testthat/test-chart_styling.R

test_that("Chart: Styling, Markers, and Labels", {
  chart <- Chart$new("lineChart")

  # Test Data Label Style (from Line.R)
  chart$set_data_label_style(
    show_val = TRUE, show_cat = FALSE, pos = "t", bold = TRUE, font_size = 9
  )

  # Test Marker Styling
  chart$add_series(
    name = "S1!$B$1", data = "S1!$B$2:$B$5",
    marker = "circle", marker_size = 7, marker_fill = "#FFFFFF"
  )

  xml <- as.character(chart$render())

  expect_match(xml, "<c:showVal val=\"1\"/>")
  expect_match(xml, "<c:dLblPos val=\"t\"/>")
  expect_match(xml, "<c:marker>")
  expect_match(xml, "<c:symbol val=\"circle\"/>")
  expect_match(xml, "<c:size val=\"7\"/>")

  wb <- openxlsx2::wb_workbook()$add_worksheet("S1")$add_data(x = mtcars)$
    add_encharter(graph = chart)
})

test_that("Chart: Legend and Title styles", {
  chart <- Chart$new()
  chart$set_chart_title("Main Title", bold = TRUE, font_size = 14)
  chart$set_legend_style(pos = "b", font_size = 10)
  expect_error(chart$render(), "The chart contains no data. You must add at least one series")

  chart$add_series(
    name = "S1!$B$1", data = "S1!$B$2:$B$5",
    marker = "circle", marker_size = 7, marker_fill = "#FFFFFF"
  )

  xml <- as.character(chart$render())
  expect_match(xml, "<c:title>")
  expect_match(xml, "Main Title")
  expect_match(xml, "<c:legendPos val=\"b\"/>")

  wb <- openxlsx2::wb_workbook()$add_worksheet("S1")$add_data(x = mtcars)$
    add_encharter(graph = chart)
})

test_that("Chart and Plot area styling works", {
  # 1. Create a styled chart
  chart <- Chart$new("lineChart")
  chart$add_series(name = "S1!$B$1", data = "S1!$B$2:$B$5")

  # Set Chart Background and Border (ChartSpace)
  chart$set_chart_style(
    fill = "EDF2F7",   # Light grey
    line = "2D3748",   # Dark border
    line_width = 2.25  # Should result in 28575 EMUs
  )

  # Set Plot Area Background (PlotArea)
  chart$set_plot_style(
    fill = "FFEE00",   # Yellow plot area
    line = "000000",   # Black plot border
    line_width = 1     # Should result in 12700 EMUs
  )

  # Render XML
  xml_str <- as.character(chart$render())
  xml <- read_xml(xml_str)

  # 2. Test ChartSpace Styling (Root level spPr)
  # Usually the last spPr child of chartSpace
  chart_sp_pr <- xml_find_first(xml, "/c:chartSpace/c:spPr")
  expect_false(is.null(chart_sp_pr))

  # Check Chart Fill
  expect_match(as.character(chart_sp_pr), 'val="EDF2F7"')

  # Check Chart Line Width (2.25 * 12700 = 28575)
  chart_ln <- xml_find_first(chart_sp_pr, "a:ln")
  expect_equal(xml_attr(chart_ln, "w"), "28575")

  # 3. Test PlotArea Styling
  plot_sp_pr <- xml_find_first(xml, "//c:plotArea/c:spPr")
  expect_false(is.null(plot_sp_pr))

  # Check Plot Fill
  expect_match(as.character(plot_sp_pr), 'val="FFEE00"')

  # Check Plot Line Width (1 * 12700 = 12700)
  plot_ln <- xml_find_first(plot_sp_pr, "a:ln")
  expect_equal(xml_attr(plot_ln, "w"), "12700")
})

test_that("ChartEx chart and plot styling works", {
  # 1. Create a styled ChartEx (treemap)
  ce <- ChartEx$new()
  ce$add_series(
    data = "Sheet1!$B$2:$B$5",
    label = "Sheet1!$A$2:$A$5",
    type = "treemap"
  )

  # Set Background (ChartSpace equivalent)
  ce$set_chart_style(
    fill = "F0F0F0",
    line = "FF0000",
    line_width = 2
  )

  # Set Plot Area Background (The area inside the chart)
  ce$set_plot_style(
    fill = "CCFFCC", # Light green
    line = "0000FF", # Blue border
    line_width = 1
  )

  # Render XML
  xml_str <- as.character(ce$render(1))
  xml <- read_xml(xml_str)

  wb <- openxlsx2::wb_workbook()$add_worksheet("Sheet1")$add_data(x = mtcars)
  wb <- openxlsx2::wb_add_encharter(wb, sheet = "Sheet1", dims = "A2:G12", graph = ce)

  # 2. Test Chart Styling (cx:chart/cx:spPr)
  # NOTE they behave different
  # with xml2::xml_find_first(xml2::read_xml(xml_str), "cx:spPr")
  # encharter::xml_find_first(encharter::read_xml(xml_str), "cx:spPr")
  chart_sp_pr <- xml_find_all(xml, "cx:spPr")[[2]]
  expect_false(is.null(chart_sp_pr))
  expect_match(as.character(chart_sp_pr), 'val="F0F0F0"')

  # 3. Test Plot Area Styling (cx:plotArea/cx:spPr)
  plot_sp_pr <- xml_find_first(xml, "//cx:chart/cx:plotArea/cx:plotAreaRegion/cx:plotSurface/cx:spPr")
  expect_false(is.null(plot_sp_pr))
  expect_match(as.character(plot_sp_pr), 'val="CCFFCC"')

  # Check Plot Line Width (1 * 12700 = 12700)
  plot_ln <- xml_find_first(plot_sp_pr, "a:ln")
  expect_equal(xml_attr(plot_ln, "w"), "12700")
})

test_that("Major and Minor grid_lines are correctly rendered and visible", {

  # 1. Create the dataset
  sales_data <- data.frame(
    Month = month.abb,
    Volume = c(1200, 1150, 1300, 1250, 1400, 1350, 1100, 1050, 1200, 1500, 1800, 2000),
    Sales = c(12000, 11500, 13000, 12500, 14000, 13500, 13200, 12600, 14400, 18000, 21600, 24000)
  )

  # 2. Setup the Chart Object
  my_chart <- Chart$new("barChart")

  my_chart$add_series(
    name = "Sheet1!$B$1",
    data   = "Sheet1!$B$2:$B$13",
    label  = "Sheet1!$A$2:$A$13",
    color  = "4472C4"
  )$set_y_axis(
    # Major: Dashed, 1.5pt, Dark Grey
    grid_lines        = "dash",
    grid_width       = 1.5,
    grid_color       = "333333",
    # Minor: Dotted, 0.5pt, Light Grey
    minor_grid_lines  = "dotted",
    minor_grid_width = 0.5,
    minor_grid_color = "D9D9D9"
  )

  # 3. XML Validation (Logic check)
  xml_str <- as.character(my_chart$render())
  xml <- read_xml(xml_str)

  # Check Major Gridlines
  major_ln <- xml_find_first(xml, "//c:valAx/c:majorGridlines/c:spPr/a:ln")
  expect_equal(xml_attr(major_ln, "w"), "19050") # 1.5 * 12700
  expect_equal(xml_attr(xml_find_first(major_ln, ".//a:srgbClr"), "val"), "333333")
  expect_equal(xml_attr(xml_find_first(major_ln, "a:prstDash"), "val"), "dash")

  # Check Minor Gridlines
  minor_ln <- xml_find_first(xml, "//c:valAx/c:minorGridlines/c:spPr/a:ln")
  expect_equal(xml_attr(minor_ln, "w"), "6350") # 0.5 * 12700
  expect_equal(xml_attr(xml_find_first(minor_ln, ".//a:srgbClr"), "val"), "D9D9D9")
  expect_equal(xml_attr(xml_find_first(minor_ln, "a:prstDash"), "val"), "dot")

  # 4. Visual Verification Workbook
  wb <- openxlsx2::wb_workbook() |>
    openxlsx2::wb_add_worksheet("Sheet1") |>
    openxlsx2::wb_add_data(x = sales_data) |>
    openxlsx2::wb_add_encharter(dims = "E2:M25", graph = my_chart)
})

test_that("ChartEx renders full styling and axis properties", {
  ce <- ChartEx$new()

  # Apply comprehensive Y-axis styling
  ce$set_y_axis(
    min = 50,
    max = 500,
    major = 100,
    minor = 50,
    grid_lines = "dash",
    grid_color = "FF0000",
    color = "000000",      # Black axis line
    font_size = 12,        # 12pt font
    bold = TRUE,
    italic = TRUE,
    format = "#,##0"
  )

  # Apply Title styling
  ce$set_y_title("USD (Millions)", font_size = 14, bold = TRUE, font_color = "000000")

  xml <- read_xml(ce$render(1))

  # 1. Verify Scaling Attributes (cx:valScaling)
  scaling_node <- xml_find_first(xml, "//cx:axis[@id='1']/cx:valScaling")
  expect_equal(xml_attr(scaling_node, "min"), "50")
  expect_equal(xml_attr(scaling_node, "max"), "500")
  expect_equal(xml_attr(scaling_node, "majorUnit"), "100")
  expect_equal(xml_attr(scaling_node, "minorUnit"), "50")

  # 2. Verify Axis Title Styling (cx:rich)
  # Titles use a:rPr for styling
  title_rpr <- xml_find_first(xml, "//cx:axis[@id='1']/cx:title//a:rPr")
  expect_equal(xml_attr(title_rpr, "sz"), "1400")
  # expect_equal(xml_attr(title_rpr, "b"), "1")
  expect_true(xml_has_attr(xml_find_first(title_rpr, ".//a:srgbClr"), "val"))
  expect_equal(xml_attr(xml_find_first(title_rpr, ".//a:srgbClr"), "val"), "000000")

  # 3. Verify Axis Label Styling (txPr)
  # Check BOTH defRPr (start) and endParaRPr (the fix for washed-out colors)
  tx_pr <- xml_find_first(xml, "//cx:axis[@id='1']/cx:txPr")

  # Start Properties
  def_rpr <- xml_find_first(tx_pr, ".//a:defRPr")
  expect_equal(xml_attr(def_rpr, "sz"), "1200")
  expect_equal(xml_attr(def_rpr, "b"), "1")
  expect_equal(xml_attr(def_rpr, "i"), "1")
  expect_equal(xml_attr(xml_find_first(def_rpr, ".//a:srgbClr"), "val"), "000000")

  # End Properties (CRITICAL FIX)
  end_rpr <- xml_find_first(tx_pr, ".//a:endParaRPr")
  expect_equal(xml_attr(end_rpr, "sz"), "1200")
  expect_equal(xml_attr(end_rpr, "b"), "1")
  expect_equal(xml_attr(end_rpr, "i"), "1")
  expect_equal(xml_attr(xml_find_first(end_rpr, ".//a:solidFill/a:srgbClr"), "val"), "000000")

  # 4. Verify Gridlines
  dash_node <- xml_find_first(xml, "//cx:axis[@id='1']/cx:majorGridlines//a:prstDash")
  expect_equal(xml_attr(dash_node, "val"), "dash")

  grid_clr <- xml_find_first(xml, "//cx:axis[@id='1']/cx:majorGridlines//a:srgbClr")
  expect_equal(xml_attr(grid_clr, "val"), "FF0000")

  # 5. Verify Axis Line Color
  axis_line_clr <- xml_find_first(xml, "//cx:axis[@id='1']/cx:spPr//a:ln/a:solidFill/a:srgbClr")
  expect_equal(xml_attr(axis_line_clr, "val"), "000000")
})

test_that("Bubble chart generates valid XML and integrates with workbook", {

  skip_if_not_installed("viridisLite")

  # 1. Setup Data
  df <- data.frame(
    Product = paste("Item", 1:10),
    Sales = runif(10, 50, 100),
    Profit = runif(10, 10, 40),
    MarketShare = runif(10, 5, 25)
  )

  # 2. Initialize Chart
  bc <- Chart$new()
  bc$add_series(
    name = "Market Performance",
    label  = "Sheet1!$B$2:$B$11",
    data   = "Sheet1!$C$2:$C$11",
    weight = "Sheet1!$D$2:$D$11",
    color  = viridisLite::viridis(10),
    type   = "bubbleChart"
  )

  bc$set_bubble_options(scale = 120, show_neg = FALSE)
  bc$set_chart_title("Market Share Analysis")

  # 3. Test XML structure
  # Render and parse back to xml2 object for easy XPath testing
  xml_str <- bc$render()
  xml_parsed <- read_xml(xml_str)

  # Check for mandatory bubbleChart node
  expect_true(xml_name(xml_find_first(xml_parsed, ".//c:bubbleChart")) == "c:bubbleChart")

  # Check for bubble-specific settings
  expect_equal(xml_attr(xml_find_first(xml_parsed, ".//c:bubbleScale"), "val"), "120")
  expect_equal(xml_attr(xml_find_first(xml_parsed, ".//c:showNegBubbles"), "val"), "0")

  # Check for the three data dimensions in the first series
  expect_true(xml_length(xml_find_first(xml_parsed, ".//c:ser/c:xVal")) > 0)
  expect_true(xml_length(xml_find_first(xml_parsed, ".//c:ser/c:yVal")) > 0)
  expect_true(xml_length(xml_find_first(xml_parsed, ".//c:ser/c:bubbleSize")) > 0)

  # Check Palette Application (dPt nodes)
  dpts <- xml_find_all(xml_parsed, ".//c:dPt")
  expect_gt(length(dpts), 0)
})

test_that("set_data_table correctly adds dTable node to XML", {
  # 1. Minimal setup
  chart <- Chart$new(type = "barChart")
  chart$add_series(name = "H1", data = "Sheet1!A1:A5", label = "Sheet1!B1:B5")

  # 2. Enable data table
  chart$set_data_table(TRUE)

  # 3. Render and parse XML
  xml_res <- read_xml(chart$render())

  # 4. Assertions
  # Check if the dTable tag exists
  dtable_node <- xml_find_first(xml_res, ".//c:dTable")
  expect_false(xml_type(dtable_node) == "element_absent")

  # Optional: check for specific data table attributes (keys, borders, etc.)
  expect_true(any(grepl("showKeys", as.character(xml_res))))
})

test_that("drop_lines, high_low_lines, and up_down_bars render correctly", {
  # 1. Setup a basic line chart with two series
  ch <- Chart$new(type = "lineChart")
  ch$add_series(name = "S1", data = "AA!A2:A5", label = "AA!B2:B5")
  ch$add_series(name = "S2", data = "AA!C2:C5")

  # 2. Enable features
  ch$drop_lines     <- TRUE
  ch$high_low_lines <- TRUE
  ch$up_down_bars   <- TRUE

  # 3. Render and parse XML
  expect_message(
    expect_message(
      xml_res <- read_xml(ch$render()),
      "drop lines require ",
    ),
    "high low lines require "
  )

  # 4. Assertions for specialized OOXML nodes
  # Drop Lines: <c:dropLines>
  expect_false(xml_type(xml_find_first(xml_res, ".//c:dropLines")) == "element_absent")

  # High-Low Lines: <c:hiLowLines>
  expect_false(xml_type(xml_find_first(xml_res, ".//c:hiLowLines")) == "element_absent")

  # Up-Down Bars: <c:upDownBars>
  expect_false(xml_type(xml_find_first(xml_res, ".//c:upDownBars")) == "element_absent")

  # Verify they are nested within the lineChart node
  expect_equal(length(xml_find_all(xml_res, ".//c:lineChart/c:dropLines")), 1)
})

test_that("barChart with theme colors and stacked grouping renders correctly", {
  # 1. Minimal Setup
  ch <- Chart$new()
  ch$add_series(
    name      = "Sheet1!$B$1",
    data      = "Sheet1!$B$2:$B$4",
    label     = "Sheet1!$A$2:$A$4",
    color     = wb_color(theme = 5),
    grouping  = "percentStacked",
    dir       = "bar",
    type      = "barChart"
  )

  # 2. Render XML
  xml_res <- ch$render()

  # 3. Assertions
  # Check Bar Direction (Horizontal 'bar' vs Vertical 'col')
  expect_true(any(grepl('<c:barDir val="bar"/>', as.character(xml_res))))

  # Check Stacked Grouping
  expect_true(any(grepl('<c:grouping val="percentStacked"/>', as.character(xml_res))))

  # Check Theme Color: Looking for theme="5" in the fill properties
  # Standard path: c:ser -> c:spPr -> a:solidFill -> a:schemeClr
  expect_true(any(grepl('val="accent2"', as.character(xml_res))))

  # Verify titles and styling
  ch$set_chart_title("Test Title", font_color = "0D6797")
  xml_titled <- ch$render()
  expect_true(any(grepl("0D6797", as.character(xml_titled))))
})

test_that("trendlines and error bars render correctly in a series", {
  # 1. Setup a basic chart
  ch <- Chart$new(type = "barChart")
  ch$add_series(
    name = "Revenue",
    data = "Sheet1!$B$2:$B$4",
    label = "Sheet1!$A$2:$A$4",
    # Add Trendline configuration
    trendline = list(
      type = "linear",
      color = "FF0000",
      show_r2 = FALSE
    ),
    # Add Error Bar configuration
    error_bars = list(
      type = "percentage",
      value = 10,
      color = "404040"
    )
  )

  # 2. Render XML
  xml_res <- read_xml(ch$render())

  # 3. Assertions for Trendlines
  # OOXML node for trendline is <c:trendline>
  trend_node <- xml_find_first(xml_res, ".//c:ser/c:trendline")
  expect_false(xml_type(trend_node) == "element_absent")
  expect_true(any(grepl('trendlineType val="linear"', as.character(xml_res))))
  expect_true(any(grepl('dispRSqr val="0"', as.character(xml_res))))

  # 4. Assertions for Error Bars
  # OOXML node for error bars is <c:errBars>
  err_node <- xml_find_first(xml_res, ".//c:ser/c:errBars")
  expect_false(xml_type(err_node) == "element_absent")
  expect_true(any(grepl('errBarType val="both"', as.character(xml_res)))) # Default behavior
  expect_true(any(grepl('errValType val="percentage"', as.character(xml_res))))

  # 5. Check colors (Solid Fill inside spPr of these nodes)
  expect_true(any(grepl("FF0000", as.character(xml_res)))) # Trendline color
  expect_true(any(grepl("404040", as.character(xml_res)))) # Error bar color
})

test_that("ChartEx boxWhisker with complex styling renders correctly", {
  # 1. Setup minimal data
  ch <- ChartEx$new()

  # 2. Apply Titles and Axis Styles
  ch$set_chart_title("MPG Distribution", font_size = 16, font_name = "Arial", bold = TRUE, italic = TRUE, fill = "#DDADDA", line = wb_color("blue"), line_width = 5) # test italic, fill and line
  ch$set_x_title("by Cylinder", font_size = 12, italic = TRUE)
  ch$set_x_axis(font_size = 10) # test this
  ch$set_y_axis(font_size = 12, font_name = "Times New Roman", italic = TRUE, color = "000000")

  # 3. Add Series with Color and Line Style
  ch$add_series(
    name = '"Super Duper MPG"',
    data   = "Data!$A$2:$A$5",
    label  = "Data!$B$2:$B$5",
    color  = wb_color("magenta"),
    line_color = wb_color("black"),
    type   = "boxWhisker"
  )

  ch$
    set_legend_style( # test this
      pos       = "r",
      font_size = 15,
      bold      = TRUE,
      color     = wb_color(theme = "4")
    )

  wb <- openxlsx2::wb_workbook()$add_worksheet("data")$add_encharter(graph = ch)

  # 4. Render XML
  xml_res <- read_xml(ch$render(1))

  # --- Assertions ---

  # Verify Font Names and Sizes (sz is 100x in OOXML)
  expect_true(any(grepl('typeface="Arial"', as.character(xml_res))))
  expect_true(any(grepl('typeface="Times New Roman"', as.character(xml_res))))
  expect_true(any(grepl('sz="1600"', as.character(xml_res)))) # Title sz 16
  expect_true(any(grepl('sz="1200"', as.character(xml_res)))) # Axis sz 12

  # Verify Boolean Styles
  expect_true(any(grepl(' b="1"', as.character(xml_res)))) # Bold
  expect_true(any(grepl(' i="1"', as.character(xml_res)))) # Italic

  # Verify BoxWhisker Type
  expect_true(any(grepl('layoutId="boxWhisker"', as.character(xml_res))))

  # Verify Fill and Line Colors
  # Magenta (FF00FF) and Black (000000)
  expect_true(any(grepl("FF00FF", as.character(xml_res), ignore.case = TRUE)))

  # Verify ChartEx Axis Node presence
  expect_false(xml_type(xml_find_first(xml_res, ".//cx:axis")) == "element_absent")
})

Try the encharter package in your browser

Any scripts or data that you put into this service are public.

encharter documentation built on Oct. 2, 2026, 1:07 a.m.