Blob Blame History Raw
/* -*- 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 <iomanip>
#include <iostream>
#include <map>
#include <sstream>

#include <librevenge/librevenge.h>

#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<SubDocument const *>(&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<MWAWListener> 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<MWAWSubDocument> 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<MWAWSubDocument> 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<MWAWFontConverter> 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<MWAWFontConverter> 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: