/* -*- Mode: C++; c-default-style: "k&r"; indent-tabs-mode: nil; tab-width: 2; c-basic-offset: 2 -*- */ /* libmwaw * Version: MPL 2.0 / LGPLv2+ * * The contents of this file are subject to the Mozilla Public License Version * 2.0 (the "License"); you may not use this file except in compliance with * the License or as specified alternatively below. You may obtain a copy of * the License at http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * Major Contributor(s): * Copyright (C) 2002 William Lachance (wrlach@gmail.com) * Copyright (C) 2002,2004 Marc Maurer (uwog@uwog.net) * Copyright (C) 2004-2006 Fridrich Strba (fridrich.strba@bluewin.ch) * Copyright (C) 2006, 2007 Andrew Ziem * Copyright (C) 2011, 2012 Alonso Laurent (alonso@loria.fr) * * * All Rights Reserved. * * For minor contributions see the git repository. * * Alternatively, the contents of this file may be used under the terms of * the GNU Lesser General Public License Version 2 or later (the "LGPLv2+"), * in which case the provisions of the LGPLv2+ are applicable * instead of those above. */ /* * Structure to store and construct a chart from an unstructured list * of cell * */ #include #include #include #include #include #include "MWAWFontConverter.hxx" #include "MWAWInputStream.hxx" #include "MWAWListener.hxx" #include "MWAWPosition.hxx" #include "MWAWSpreadsheetListener.hxx" #include "MWAWSubDocument.hxx" #include "MWAWChart.hxx" /** Internal: the structures of a MWAWChart */ namespace MWAWChartInternal { //////////////////////////////////////// //! Internal: the subdocument of a MWAWChart class SubDocument final : public MWAWSubDocument { public: SubDocument(MWAWChart *chart, MWAWChart::TextZone::Type textZone) : MWAWSubDocument(nullptr, MWAWInputStreamPtr(), MWAWEntry()) , m_chart(chart) , m_textZone(textZone) { } //! destructor ~SubDocument() final {} //! operator!= bool operator!=(MWAWSubDocument const &doc) const final { if (MWAWSubDocument::operator!=(doc)) return true; auto const *subDoc=dynamic_cast(&doc); if (!subDoc) return true; return m_chart!=subDoc->m_chart || m_textZone!=subDoc->m_textZone; } //! the parser function void parse(MWAWListenerPtr &listener, libmwaw::SubDocumentType type) final; protected: //! the chart MWAWChart *m_chart; //! the textzone type MWAWChart::TextZone::Type m_textZone; private: SubDocument(SubDocument const &orig) = delete; SubDocument &operator=(SubDocument const &orig) = delete; }; void SubDocument::parse(MWAWListenerPtr &listener, libmwaw::SubDocumentType /*type*/) { if (!listener.get()) { MWAW_DEBUG_MSG(("MWAWChartInternal::SubDocument::parse: no listener\n")); return; } if (!m_chart) { MWAW_DEBUG_MSG(("MWAWChartInternal::SubDocument::parse: can not find the chart\n")); return; } m_chart->sendTextZoneContent(m_textZone, listener); } } //////////////////////////////////////////////////////////// // MWAWChart //////////////////////////////////////////////////////////// MWAWChart::MWAWChart(std::string const &sheetName, MWAWFontConverterPtr const &fontConverter, MWAWVec2f const &dim) : m_sheetName(sheetName) , m_dim(dim) , m_type(MWAWChart::Series::S_Bar) , m_dataStacked(false) , m_legend() , m_seriesList() , m_textZoneMap() , m_fontConverter(fontConverter) { } MWAWChart::~MWAWChart() { } void MWAWChart::add(int coord, MWAWChart::Axis const &axis) { if (coord<0 || coord>2) { MWAW_DEBUG_MSG(("MWAWChart::add[Axis]: called with bad coord\n")); return; } m_axis[coord]=axis; } MWAWChart::Axis const &MWAWChart::getAxis(int coord) const { if (coord<0 || coord>2) { MWAW_DEBUG_MSG(("MWAWChart::getAxis: called with bad coord\n")); return m_axis[3]; } return m_axis[coord]; } void MWAWChart::add(MWAWChart::Series const &series) { m_seriesList.push_back(series); } bool MWAWChart::getTextZone(MWAWChart::TextZone::Type type, MWAWChart::TextZone &textZone) { if (m_textZoneMap.find(type)==m_textZoneMap.end()) return false; textZone=m_textZoneMap.find(type)->second; return true; } void MWAWChart::sendTextZoneContent(MWAWChart::TextZone::Type type, MWAWListenerPtr &listener) { if (m_textZoneMap.find(type)==m_textZoneMap.end()) { MWAW_DEBUG_MSG(("MWAWChart::sendTextZoneContent: called with unknown zone(%d)\n", int(type))); return; } sendContent(m_textZoneMap.find(type)->second, listener); } void MWAWChart::add(MWAWChart::TextZone const &textZone) { m_textZoneMap[textZone.m_type]=textZone; } void MWAWChart::sendChart(MWAWSpreadsheetListenerPtr &listener, librevenge::RVNGSpreadsheetInterface *interface) { if (!listener || !interface) { MWAW_DEBUG_MSG(("MWAWChart::sendChart: can not find listener or interface\n")); return; } if (m_seriesList.empty()) { MWAW_DEBUG_MSG(("MWAWChart::sendChart: can not find the series\n")); return; } std::shared_ptr genericListener=listener; int styleId=0; librevenge::RVNGPropertyList style; style.insert("librevenge:chart-id", styleId); style.insert("draw:stroke", "none"); style.insert("draw:fill", "none"); interface->defineChartStyle(style); librevenge::RVNGPropertyList chart; chart.insert("svg:width", double(m_dim[0]), librevenge::RVNG_POINT); chart.insert("svg:height", double(m_dim[1]), librevenge::RVNG_POINT); if (!m_seriesList.empty()) chart.insert("chart:class", Series::getSeriesTypeName(m_seriesList[0].m_type).c_str()); else chart.insert("chart:class", Series::getSeriesTypeName(m_type).c_str()); chart.insert("librevenge:chart-id", styleId++); interface->openChart(chart); // legend if (m_legend.m_show && m_fontConverter) { style=librevenge::RVNGPropertyList(); m_legend.addStyleTo(style, m_fontConverter); style.insert("librevenge:chart-id", styleId); interface->defineChartStyle(style); librevenge::RVNGPropertyList legend; m_legend.addContentTo(legend); legend.insert("librevenge:chart-id", styleId++); legend.insert("librevenge:zone-type", "legend"); interface->openChartTextObject(legend); interface->closeChartTextObject(); } for (auto textIt : m_textZoneMap) { TextZone const &zone= textIt.second; if (zone.m_type != TextZone::T_Title && zone.m_type != TextZone::T_SubTitle) continue; style=librevenge::RVNGPropertyList(); zone.addStyleTo(style, m_fontConverter); style.insert("librevenge:chart-id", styleId); interface->defineChartStyle(style); librevenge::RVNGPropertyList textZone; zone.addContentTo(m_sheetName, textZone); textZone.insert("librevenge:chart-id", styleId++); textZone.insert("librevenge:zone-type", zone.m_type==TextZone::T_Title ? "title":"subtitle"); interface->openChartTextObject(textZone); if (zone.m_contentType==TextZone::C_Text) { std::shared_ptr doc(new MWAWChartInternal::SubDocument(this, zone.m_type)); listener->handleSubDocument(doc, libmwaw::DOC_CHART_ZONE); } interface->closeChartTextObject(); } // plot area style=librevenge::RVNGPropertyList(); style.insert("librevenge:chart-id", styleId); style.insert("chart:include-hidden-cells","false"); style.insert("chart:auto-position","true"); style.insert("chart:auto-size","true"); style.insert("chart:treat-empty-cells","leave-gap"); style.insert("chart:right-angled-axes","true"); style.insert("chart:stacked", m_dataStacked); interface->defineChartStyle(style); librevenge::RVNGPropertyList plotArea; if (m_dim[0]>80) { plotArea.insert("svg:x", 20, librevenge::RVNG_POINT); plotArea.insert("svg:width", double(m_dim[0])-40, librevenge::RVNG_POINT); } if (m_dim[1]>80) { plotArea.insert("svg:y", 20, librevenge::RVNG_POINT); plotArea.insert("svg:height", double(m_dim[1])-40, librevenge::RVNG_POINT); } plotArea.insert("librevenge:chart-id", styleId++); librevenge::RVNGPropertyList floor, wall; librevenge::RVNGPropertyListVector vect; style=librevenge::RVNGPropertyList(); style.insert("draw:stroke","solid"); style.insert("svg:stroke-color","#b3b3b3"); style.insert("draw:fill","none"); style.insert("librevenge:chart-id", styleId); interface->defineChartStyle(style); floor.insert("librevenge:type", "floor"); floor.insert("librevenge:chart-id", styleId++); vect.append(floor); style.insert("draw:fill","solid"); style.insert("draw:fill-color","#ffffff"); style.insert("librevenge:chart-id", styleId); interface->defineChartStyle(style); wall.insert("librevenge:type", "wall"); wall.insert("librevenge:chart-id", styleId++); vect.append(wall); plotArea.insert("librevenge:childs", vect); interface->openChartPlotArea(plotArea); // axis for (int i=0; i<3; ++i) { if (m_axis[i].m_type==Axis::A_None) continue; style=librevenge::RVNGPropertyList(); m_axis[i].addStyleTo(style); style.insert("librevenge:chart-id", styleId); interface->defineChartStyle(style); librevenge::RVNGPropertyList axis; m_axis[i].addContentTo(m_sheetName, i, axis); axis.insert("librevenge:chart-id", styleId++); interface->insertChartAxis(axis); } // label for (auto textIt : m_textZoneMap) { TextZone const &zone= textIt.second; if (zone.m_type == TextZone::T_Title || zone.m_type == TextZone::T_SubTitle) continue; style=librevenge::RVNGPropertyList(); zone.addStyleTo(style, m_fontConverter); style.insert("librevenge:chart-id", styleId); interface->defineChartStyle(style); librevenge::RVNGPropertyList textZone; zone.addContentTo(m_sheetName, textZone); textZone.insert("librevenge:chart-id", styleId++); textZone.insert("librevenge:zone-type", "label"); interface->openChartTextObject(textZone); if (zone.m_contentType==TextZone::C_Text) { std::shared_ptr doc(new MWAWChartInternal::SubDocument(this, zone.m_type)); listener->handleSubDocument(doc, libmwaw::DOC_CHART_ZONE); } interface->closeChartTextObject(); } // series for (auto const &serie : m_seriesList) { style=librevenge::RVNGPropertyList(); serie.addStyleTo(style); style.insert("librevenge:chart-id", styleId); interface->defineChartStyle(style); librevenge::RVNGPropertyList series; serie.addContentTo(m_sheetName, series); series.insert("librevenge:chart-id", styleId++); interface->openChartSerie(series); interface->closeChartSerie(); } interface->closeChartPlotArea(); interface->closeChart(); } //////////////////////////////////////////////////////////// // Axis //////////////////////////////////////////////////////////// MWAWChart::Axis::Axis() : m_type(MWAWChart::Axis::A_None) , m_showGrid(true) , m_showLabel(true) , m_labelRange(MWAWVec2i(0,0), MWAWVec2i(-1,-1)) , m_style() { m_style.m_lineWidth=0; } MWAWChart::Axis::~Axis() { } void MWAWChart::Axis::addContentTo(std::string const &sheetName, int coord, librevenge::RVNGPropertyList &propList) const { std::string axis(""); axis += char('x'+coord); propList.insert("chart:dimension",axis.c_str()); axis = "primary-"+axis; propList.insert("chart:name",axis.c_str()); librevenge::RVNGPropertyListVector childs; if (m_showGrid && (m_type==A_Numeric || m_type==A_Logarithmic)) { librevenge::RVNGPropertyList grid; grid.insert("librevenge:type", "grid"); grid.insert("chart:class", "major"); childs.append(grid); } if (m_showLabel && m_labelRange.size()[0]>=0 && m_labelRange.size()[1]>=0) { librevenge::RVNGPropertyList range; range.insert("librevenge:sheet-name", sheetName.c_str()); range.insert("librevenge:start-row", m_labelRange.min()[1]); range.insert("librevenge:start-column", m_labelRange.min()[0]); range.insert("librevenge:end-row", m_labelRange.max()[1]); range.insert("librevenge:end-column", m_labelRange.max()[0]); librevenge::RVNGPropertyListVector vect; vect.append(range); librevenge::RVNGPropertyList categories; categories.insert("librevenge:type", "categories"); categories.insert("table:cell-range-address", vect); childs.append(categories); } if (!childs.empty()) propList.insert("librevenge:childs", childs); } void MWAWChart::Axis::addStyleTo(librevenge::RVNGPropertyList &propList) const { propList.insert("chart:display-label", m_showLabel); propList.insert("chart:axis-position", 0, librevenge::RVNG_GENERIC); propList.insert("chart:reverse-direction", false); propList.insert("chart:logarithmic", m_type==MWAWChart::Axis::A_Logarithmic); propList.insert("text:line-break", false); m_style.addTo(propList, true); } std::ostream &operator<<(std::ostream &o, MWAWChart::Axis const &axis) { switch (axis.m_type) { case MWAWChart::Axis::A_None: o << "none,"; break; case MWAWChart::Axis::A_Numeric: o << "numeric,"; break; case MWAWChart::Axis::A_Logarithmic: o << "logarithmic,"; break; case MWAWChart::Axis::A_Sequence: o << "sequence,"; break; case MWAWChart::Axis::A_Sequence_Skip_Empty: o << "sequence[noEmpty],"; break; #if !defined(__clang__) default: o << "###type,"; MWAW_DEBUG_MSG(("MWAWChart::Axis: unexpected type\n")); break; #endif } if (axis.m_showGrid) o << "show[grid],"; if (axis.m_showLabel) o << "show[label],"; if (axis.m_labelRange.size()[0]>=0 && axis.m_labelRange.size()[1]>=0) o << "label[range]=" << axis.m_labelRange << ","; o << axis.m_style; return o; } //////////////////////////////////////////////////////////// // Legend //////////////////////////////////////////////////////////// void MWAWChart::Legend::addContentTo(librevenge::RVNGPropertyList &propList) const { propList.insert("svg:x", double(m_position[0]), librevenge::RVNG_POINT); propList.insert("svg:y", double(m_position[1]), librevenge::RVNG_POINT); if (!m_autoPosition || !m_relativePosition) return; std::stringstream s; if (m_relativePosition&libmwaw::TopBit) s << "top"; else if (m_relativePosition&libmwaw::BottomBit) s << "bottom"; if (s.str().length() && (m_relativePosition&(libmwaw::LeftBit|libmwaw::RightBit))) s << "-"; if (m_relativePosition&libmwaw::LeftBit) s << "start"; else if (m_relativePosition&libmwaw::RightBit) s << "end"; propList.insert("chart:legend-position", s.str().c_str()); } void MWAWChart::Legend::addStyleTo(librevenge::RVNGPropertyList &propList, std::shared_ptr fontConverter) const { propList.insert("chart:auto-position", m_autoPosition); m_font.addTo(propList, fontConverter); m_style.addTo(propList); } std::ostream &operator<<(std::ostream &o, MWAWChart::Legend const &legend) { if (legend.m_show) o << "show,"; if (legend.m_autoPosition) { o << "automaticPos["; if (legend.m_relativePosition&libmwaw::TopBit) o << "t"; else if (legend.m_relativePosition&libmwaw::RightBit) o << "b"; else o << "c"; if (legend.m_relativePosition&libmwaw::LeftBit) o << "L"; else if (legend.m_relativePosition&libmwaw::BottomBit) o << "R"; else o << "C"; o << "]"; } else o << "pos=" << legend.m_position << ","; o << legend.m_style; return o; } //////////////////////////////////////////////////////////// // Serie //////////////////////////////////////////////////////////// MWAWChart::Series::Series() : m_type(MWAWChart::Series::S_Bar) , m_range() , m_style() { m_style.m_lineWidth=0; m_style.setSurfaceColor(MWAWColor(0x80,0x80,0xFF)); } MWAWChart::Series::~Series() { } std::string MWAWChart::Series::getSeriesTypeName(Type type) { switch (type) { case S_Area: return "chart:area"; case S_Column: return "chart:column"; case S_Line: return "chart:line"; case S_Pie: return "chart:pie"; case S_Scatter: return "chart:scatter"; case S_Stock: return "chart:stock"; case S_Bar: return "chart:bar"; #if !defined(__clang__) default: break; #endif } return "chart:bar"; } void MWAWChart::Series::addContentTo(std::string const &sheetName, librevenge::RVNGPropertyList &serie) const { serie.insert("chart:class",getSeriesTypeName(m_type).c_str()); librevenge::RVNGPropertyList range, datapoint; range.insert("librevenge:sheet-name", sheetName.c_str()); range.insert("librevenge:start-row", m_range.min()[1]); range.insert("librevenge:start-column", m_range.min()[0]); range.insert("librevenge:end-row", m_range.max()[1]); range.insert("librevenge:end-column", m_range.max()[0]); librevenge::RVNGPropertyListVector vect; vect.append(range); serie.insert("chart:values-cell-range-address", vect); vect.clear(); int numPt=m_range.size()[0]>m_range.size()[1] ? m_range.size()[0]+1 : m_range.size()[1]+1; datapoint.insert("librevenge:type", "data-point"); datapoint.insert("chart:repeated", numPt); vect.append(datapoint); serie.insert("librevenge:childs", vect); } void MWAWChart::Series::addStyleTo(librevenge::RVNGPropertyList &propList) const { m_style.addTo(propList); } std::ostream &operator<<(std::ostream &o, MWAWChart::Series const &series) { switch (series.m_type) { case MWAWChart::Series::S_Area: o << "area,"; break; case MWAWChart::Series::S_Bar: o << "bar,"; break; case MWAWChart::Series::S_Column: o << "column,"; break; case MWAWChart::Series::S_Line: o << "line,"; break; case MWAWChart::Series::S_Pie: o << "pie,"; break; case MWAWChart::Series::S_Scatter: o << "scatter,"; break; case MWAWChart::Series::S_Stock: o << "stock,"; break; #if !defined(__clang__) default: o << "###type,"; MWAW_DEBUG_MSG(("MWAWChart::Series: unexpected type\n")); break; #endif } o << "range=" << series.m_range << ","; o << series.m_style; return o; } //////////////////////////////////////////////////////////// // TextZone //////////////////////////////////////////////////////////// MWAWChart::TextZone::TextZone() : m_type(MWAWChart::TextZone::T_Title) , m_contentType(MWAWChart::TextZone::C_Cell) , m_position(-1,-1) , m_cell() , m_textEntry() , m_font() , m_style() { m_style.m_lineWidth=0; } MWAWChart::TextZone::~TextZone() { } void MWAWChart::TextZone::addContentTo(std::string const &sheetName, librevenge::RVNGPropertyList &propList) const { if (m_position[0]>=0 && m_position[1]>=0) { propList.insert("svg:x", double(m_position[0]), librevenge::RVNG_POINT); propList.insert("svg:y", double(m_position[1]), librevenge::RVNG_POINT); } switch (m_type) { case T_Title: propList.insert("librevenge:zone-type", "title"); break; case T_SubTitle: propList.insert("librevenge:zone-type", "subtitle"); break; case T_AxisX: case T_AxisY: case T_AxisZ: propList.insert("librevenge:zone-type", "label"); return; #if !defined(__clang__) default: MWAW_DEBUG_MSG(("MWAWChart::TextZone:addContentTo: unexpected type\n")); break; #endif } if (m_contentType==C_Cell) { librevenge::RVNGPropertyList range; librevenge::RVNGPropertyListVector vect; range.insert("librevenge:sheet-name", sheetName.c_str()); range.insert("librevenge:row", m_cell[1]); range.insert("librevenge:column", m_cell[0]); vect.append(range); propList.insert("table:cell-range", vect); } } void MWAWChart::TextZone::addStyleTo(librevenge::RVNGPropertyList &propList, std::shared_ptr fontConverter) const { m_font.addTo(propList, fontConverter); m_style.addTo(propList); } std::ostream &operator<<(std::ostream &o, MWAWChart::TextZone const &zone) { switch (zone.m_type) { case MWAWChart::TextZone::T_SubTitle: o << "sub"; MWAW_FALLTHROUGH; case MWAWChart::TextZone::T_Title: o << "title"; if (zone.m_contentType==MWAWChart::TextZone::C_Cell) o << "[" << zone.m_cell << "]"; o << ","; break; case MWAWChart::TextZone::T_AxisX: case MWAWChart::TextZone::T_AxisY: case MWAWChart::TextZone::T_AxisZ: if (zone.m_type==MWAWChart::TextZone::T_AxisX) o << "axisX"; else if (zone.m_type==MWAWChart::TextZone::T_AxisY) o << "axisY"; else o << "axisZ"; if (zone.m_contentType==MWAWChart::TextZone::C_Cell) o << "[cells]"; o << ","; break; #if !defined(__clang__) default: o << "###type,"; MWAW_DEBUG_MSG(("MWAWChart::TextZone: unexpected type\n")); break; #endif } if (zone.m_contentType==MWAWChart::TextZone::C_Text) o << "text,"; if (zone.m_position[0]>0 || zone.m_position[1]>0) o << "pos=" << zone.m_position << ","; o << zone.m_style; return o; } // vim: set filetype=cpp tabstop=2 shiftwidth=2 cindent autoindent smartindent noexpandtab: