Una parte de gráfico guardada por una build actual de Excel rara vez se queda en los elementos que el esquema de gráficos de ECMA-376 define. El chart space suele abrir con un bloque mc:AlternateContent que ofrece c14:style a los lectores nuevos y un c:style llano a los viejos, cada serie lleva un c:extLst con un c16:uniqueId, y las etiquetas de datos "Value From Cells" viven en otra extensión más. Un programa que parsea solo lo que modela y regenera la parte al guardar no devuelve nada de eso. Cambia una palabra del título, reabre el archivo en Excel, y las etiquetas que sacaban el texto de un rango de celdas se han ido
El arreglo es dejar de tratar un guardado como una reconstrucción. Conserva los bytes con que llegó el gráfico, escríbelos de vuelta sin cambios cuando nada modelado se haya movido, y cuando algo se haya movido, fusiona los elementos nuevos en el árbol original para que las partes desconocidas se queden donde Excel las puso
Reproducir el ChartML original cuando nada cambió
HotXLS hace exactamente eso cuando TXLSXWorkbook.PreserveUnsupportedParts es True, que es el valor por defecto en un libro recién creado. Al importar, cada gráfico conserva sus bytes UTF-8 originales junto con la longitud y una huella de 64 bits del contenido que el modelo tipado representa. Al guardar, HotXLS regenera la parte modelada, compara longitud y huella, y si ambas coinciden escribe los bytes guardados de vuelta tal cual: sin reordenación de elementos, sin deriva de espacios en blanco, y sin una segunda copia UTF-16 de cada gráfico sin tocar retenida en memoria
Como la comparación corre sobre contenido, asignar a una propiedad el valor que ya tiene no cambia nada. Los setters de TXLSXChart salen pronto ante un valor igual, así que un bucle que «refresca» el título de cada gráfico desde una fila de base de datos sigue reproduciendo cada gráfico cuyo título ya era el correcto. El formato de línea en barras de error y líneas de tendencia se rastrea con su propio flag de cambio, y cambiarlo cuenta siempre como una edición
TXLSXChart.HasPreservedXml te dice qué ruta lleva un gráfico antes de que edites nada. Reporta True cuando hay ChartML original atado al gráfico, que es la precondición tanto de la reproducción como de la fusión
var
Workbook: TXLSXWorkbook;
Chart: TXLSXChart;
begin
Workbook := TXLSXWorkbook.Create;
try
// True por defecto; ponlo explícitamente para que la intención sobreviva a refactorizaciones.
// Tiene que estar puesto antes de Open, porque Open construye el estado
Workbook.PreserveUnsupportedParts := True;
Workbook.Open('quarterly-review.xlsx');
Chart := Workbook.Sheets[1].Charts[0]; // Sheets[] es base 1, Charts[] base 0
if not Chart.HasPreservedXml then
Log('chart 0 has no original ChartML attached'); // Log es tu propia rutina
Chart.Title := Chart.Title; // valor igual: sigue reproduciendo
Workbook.Sheets[1].Charts[1].Title :=
'Revenue by region, Q3 2026'; // este gráfico toma la ruta de fusión
Workbook.SaveAs('quarterly-review-out.xlsx');
finally
Workbook.Free;
end;
end;
¿Qué sobrevive a una fusión cuando editas un gráfico?
La respuesta corta: todo de lo que el modelo tipado no es dueño. Título, series, cachés de datos, grupos de trazado y ejes son propiedad del modelo, así que el árbol recién generado los decide. Los atributos no modelados, las declaraciones de espacios de nombres, los estilos, las listas de extensión y los nodos de formato vienen del árbol original y se reinserzan al lado de los nodos coincidentes junto a los que estaban
Borra una serie y la fusión no debe resucitarla desde el XML preservado solo porque el original todavía contenga un c:ser en esa posición. Todo lo que es del modelo se toma del modelo incluso cuando su respuesta es "se ha ido". Lo mismo aplica a nivel de atributo: cuando limpias negrita, cursiva, subrayado, tachado o el tamaño de fuente de un estilo de texto conocido, la fusión lee el atributo ausente como una limpieza y no copia b ni sz de vuelta desde el defRPr original. Los atributos desconocidos del mismo elemento conservan su sitio
Los títulos siguen la misma regla. Al importar, HotXLS conserva los runs rich nativos del c:title, con sus fuentes, tamaños y etiquetas de idioma por run, y los reproduce mientras el texto no se toque. Asigna un Title nuevo y ese contenido rich se descarta; el título se reescribe como un único run estilado desde TitleTextStyle. Fusionar los runs viejos en el texto nuevo sería adivinar a qué run pertenecen las palabras nuevas, y una adivinanza aquí produce títulos que nadie escribió
El orden del esquema se conserva donde Office es quisquilloso. Un extLst se queda como último hijo de su padre, como exige el esquema. Los bloques de compatibilidad de marcado reciben tratamiento propio: el lector se salta mc:Choice y lee mc:Fallback, y cuando ese respaldo envuelve un grupo de trazado que el modelo conoce, la fusión escribe el nuevo contenido tipado de vuelta en el subárbol de respaldo. Una fusión ingenua emitiría el grupo de trazado editado y dejaría la copia vieja dentro de la rama de respaldo, así que el gráfico guardado llevaría el mismo grupo dos veces
Excluir un gráfico con ClearPreservedXml
TXLSXChart.ClearPreservedXml suelta el XML original y la huella de un gráfico, y el próximo guardado reconstruye ese gráfico puramente desde el modelo tipado. Es una decisión por gráfico. PreserveUnsupportedParts sigue gobernando si el estado de preservación se construye al importar, y cubre también otras partes del paquete sin soporte, no solo gráficos, razón por la que apagarla para conseguir un gráfico limpio es la palanca equivocada
Limpia un gráfico cuando descartar es el objetivo de la operación, por ejemplo un gráfico de plantilla que todavía lleva formato de un tema corporativo retirado. No lo uses como reflejo de depuración. Un gráfico que se renderiza mal tras una edición suele ser una cuestión de modelado, y limpiar el XML preservado tira la única copia de lo que Excel escribió originalmente
Un caso necesita comprobación previa. Si una barra de error o una línea de tendencia usa formato de línea que el modelo tipado no puede representar, su propiedad LineAppearanceSupported es False; una etiqueta de línea de tendencia con formato fuera del modelo reporta lo mismo vía LabelAppearanceSupported. HotXLS no reemplazará silenciosamente ninguna de las dos por una versión más plana: en cuanto el gráfico no tiene XML preservado al que recurrir, el guardado se detiene con una EArgumentException que nombra la apariencia de línea o etiqueta no representada. Comprobar ambas antes de limpiar lleva un bucle
// Tu propio helper, no forma parte de HotXLS: True cuando cada línea de barra de error,
// línea de trendline y etiqueta de trendline se pueden escribir solo desde el modelo tipado
function ChartRebuildable(Chart: TXLSXChart): Boolean;
var
I, T: Integer;
S: TXLSXChartSeries;
begin
Result := False;
for I := 0 to Chart.SeriesCount - 1 do // recorre cada grupo de trazado
begin
S := Chart.Series[I];
if (S.ErrBarX <> nil) and not S.ErrBarX.LineAppearanceSupported then Exit;
if (S.ErrBarY <> nil) and not S.ErrBarY.LineAppearanceSupported then Exit;
for T := 0 to S.TrendlineCount - 1 do
begin
if not S.Trendlines[T].LineAppearanceSupported then Exit;
if not S.Trendlines[T].LabelAppearanceSupported then Exit;
end;
end;
Result := True;
end;
// ...
Chart := Workbook.Sheets[1].Charts.FindByName('Old theme chart');
if (Chart <> nil) and ChartRebuildable(Chart) then
Chart.ClearPreservedXml // el próximo guardado reconstruye este gráfico desde el modelo
else
Log('kept original ChartML for this chart');
Gráficos combinados con AddPlotGroup y un eje secundario
TXLSXChart.AddPlotGroup añade un grupo de trazado ordenado con su propia familia de gráfico y su propia asignación a eje primario o secundario. Eso cubre el gráfico que pide todo equipo de finanzas: ingresos como columnas en el eje izquierdo, porcentaje de margen como línea en el derecho. Cada gráfico expone ValueAxis, CategoryAxis, SecondaryValueAxis, SecondaryCategoryAxis y SeriesAxis, con ajustes tipados de posición, identificadores de eje, cruce, unidades de fecha, intervalos de etiqueta, formato numérico y visibilidad
La API plana de los gráficos de un solo tipo sigue ahí y se mapea sobre los grupos. ChartType y AddSeries se dirigen al primer grupo de trazado; SeriesCount y Series[] recorren las series de todos los grupos en orden, razón por la que el helper de arriba ve también las series de línea. El orden importa en un sitio. TXLSXCharts.Add crea un gráfico sin grupo de trazado alguno, y la primera de AddSeries o AddPlotGroup que corra crea el grupo uno. Llama a AddPlotGroup(xlsxChartLine, xlsxAxisSecondary) primero en un gráfico fresco y la línea se convierte en el grupo uno, el tipo de gráfico cambia a línea, y la serie de ingresos que añadas después cae en él. Crear ambos grupos explícitamente elimina la ambigüedad, así que el ejemplo lo hace
var
Chart: TXLSXChart;
Columns, Line: TXLSXChartPlotGroup;
begin
// Anclado sobre las filas 1..15, columnas 5..12; aún no existe ningún grupo de trazado
Chart := Sheet.Charts.Add(xlsxChartColumn, 'Revenue and margin',
1, 5, 15, 12);
Columns := Chart.AddPlotGroup(xlsxChartColumn); // grupo 1, primario
Columns.AddSeries('Revenue', 'Data!$A$2:$A$13', 'Data!$B$2:$B$13');
Line := Chart.AddPlotGroup(xlsxChartLine, xlsxAxisSecondary); // grupo 2
Line.AddSeries('Margin %', 'Data!$A$2:$A$13', 'Data!$C$2:$C$13');
Line.ZOrder := 1; // dibuja la línea delante de las columnas
Chart.SecondaryValueAxis.NumberFormat := '0%';
Chart.SecondaryValueAxis.NumberFormatSourceLinked := False;
Chart.SecondaryValueAxis.MinValue := 0;
Chart.SecondaryValueAxis.MaxValue := 0.5;
// Área de trazado como fracciones del gráfico: x, y, ancho, alto en una llamada
Chart.PlotAreaLayout.SetPositionAndSize(0.08, 0.12, 0.80, 0.72);
end;
ZOrder en un grupo de trazado controla el apilado: el escritor de gráficos ordena los grupos por él antes de emitir los bloques c:barChart y c:lineChart, los valores bajos detrás. Como cada grupo ordenado está modelado, abrir y volver a guardar un gráfico combinado mantiene cada familia de gráfico junto con sus ejes secundario, de fecha y de series. Las referencias a fórmulas de gráfico y los valores de serie en caché sobreviven a copias del libro y a ciclos de abrir y guardar, y los anclajes de gráfico se emparejan con sus partes de gráfico por identificador de relación, así que un paquete cuyas entradas de relación reordenó otra herramienta sigue poniendo cada gráfico en su posición de hoja de cálculo
¿Por qué Office ignora un layout manual?
Porque las coordenadas llegaron incompletas. Office exige x, y, w y h como grupo completo siempre que cualquiera de ellas esté presente en un c:manualLayout, y si no ignora el elemento entero. El esquema marca las cuatro como opcionales, así que un archivo con solo x e y es válido y aun así no hace nada en Excel
TXLSXChartLayout.SetPositionAndSize es la llamada a usar. Fija los cuatro valores, marca los cuatro elementos de modo como presentes y habilita el layout en un paso. Las propiedades individuales X, Y, Width y Height fijan cada una un valor, y HotXLS escribe exactamente los elementos presentes, igual que conserva la presencia de cada uno de los nueve hijos opcionales cuando lee un archivo. Eso mantiene un layout sin tocar fiel a su fuente. También significa que un layout construido desde dos asignaciones de propiedades se escribe con dos coordenadas, y Office lo saltará. La misma clase TXLSXChartLayout está detrás de PlotAreaLayout, TitleLayout, LegendLayout y el TitleLayout de cada eje
Dos valores por defecto siguen lo que hace Excel en lugar de todo lo que la especificación permite. Un layout nuevo usa edge para XMode y YMode y factor para WidthMode y HeightMode; el estándar permite las otras combinaciones, pero Excel guarda posiciones como bordes y tamaños como factores y convierte cualquier otra cosa al guardar. Y layoutTarget (Target, interior o exterior) se escribe para el área de trazado, mientras que los layouts de título, título de eje y leyenda lo omiten, porque las notas de implementación de Microsoft para el esquema de gráficos no lo permiten bajo esos elementos
Encajar esto en un ida y vuelta del libro
La preservación de gráficos es un caso de una regla más amplia: una biblioteca que edita documentos creados en otro sitio debería cambiar lo que se le pidió cambiar y dejar el resto como lo encontró. HotXLS aplica el mismo razonamiento a temas, cadenas de cálculo y extensiones de hoja, cubierto en los ida y vuelta sin pérdida de tema, extLst y calcChain, y a proyectos VBA y enlaces externos en preservar VBA y enlaces externos. Para crear gráficos, colocar imágenes y dibujar formas desde cero, mira gráficos, imágenes y dibujos en HotXLS
HotXLS lee, edita y escribe gráficos XLSX desde código nativo de Delphi y C++Builder sin Excel instalado en la máquina, así que la fidelidad de los gráficos la decide la biblioteca y no un servidor de automatización. La referencia de la API de gráficos y una build de prueba están en la página del componente de hojas de cálculo para Delphi de HotXLS