diff --git a/apps/calculation/additional_outputs/complex_graph_cell.cpp b/apps/calculation/additional_outputs/complex_graph_cell.cpp index e4249e8f2..0d0bcf6fe 100644 --- a/apps/calculation/additional_outputs/complex_graph_cell.cpp +++ b/apps/calculation/additional_outputs/complex_graph_cell.cpp @@ -24,13 +24,8 @@ void ComplexGraphView::drawRect(KDContext * ctx, KDRect rect) const { float imag = m_complex->imag(); assert(!std::isnan(real) && !std::isnan(imag) && !std::isinf(real) && !std::isinf(imag)); - /* Draw the segment from the origin to the dot (real, imag) of equation - * x(t) = t*real and y(t) = t*imag with t in [0,1] */ - drawCurve(ctx, rect, 0.0f, 1.0f, 0.01f, - [](float t, void * model, void * context) { - ComplexModel * complexModel = (ComplexModel *)model; - return Poincare::Coordinate2D(complexModel->real()*t, complexModel->imag()*t); - }, m_complex, nullptr, false, Palette::GreyDark, false); + // Draw the segment from the origin to the dot (real, imag) + drawSegment(ctx, rect, 0.0f, 0.0f, m_complex->real(), m_complex->imag(), Palette::GreyDark, false); /* Draw the partial ellipse indicating the angle θ * - the ellipse parameters are a = |real|/5 and b = |imag|/5, @@ -66,8 +61,8 @@ void ComplexGraphView::drawRect(KDContext * ctx, KDRect rect) const { }, ¶meters, &th, false, Palette::GreyDark, false); // Draw dashed segment to indicate real and imaginary - drawSegment(ctx, rect, Axis::Vertical, real, 0.0f, imag, Palette::Red, 1, 3); - drawSegment(ctx, rect, Axis::Horizontal, imag, 0.0f, real, Palette::Red, 1, 3); + drawHorizontalOrVerticalSegment(ctx, rect, Axis::Vertical, real, 0.0f, imag, Palette::Red, 1, 3); + drawHorizontalOrVerticalSegment(ctx, rect, Axis::Horizontal, imag, 0.0f, real, Palette::Red, 1, 3); // Draw complex position on the plan drawDot(ctx, rect, real, imag, Palette::Red, Size::Large); diff --git a/apps/calculation/additional_outputs/trigonometry_graph_cell.cpp b/apps/calculation/additional_outputs/trigonometry_graph_cell.cpp index 41deb62bf..f28c107f4 100644 --- a/apps/calculation/additional_outputs/trigonometry_graph_cell.cpp +++ b/apps/calculation/additional_outputs/trigonometry_graph_cell.cpp @@ -22,8 +22,8 @@ void TrigonometryGraphView::drawRect(KDContext * ctx, KDRect rect) const { return Poincare::Coordinate2D(std::cos(t), std::sin(t)); }, nullptr, nullptr, true, Palette::GreyDark, false); // Draw dashed segment to indicate sine and cosine - drawSegment(ctx, rect, Axis::Vertical, c, 0.0f, s, Palette::Red, 1, 3); - drawSegment(ctx, rect, Axis::Horizontal, s, 0.0f, c, Palette::Red, 1, 3); + drawHorizontalOrVerticalSegment(ctx, rect, Axis::Vertical, c, 0.0f, s, Palette::Red, 1, 3); + drawHorizontalOrVerticalSegment(ctx, rect, Axis::Horizontal, s, 0.0f, c, Palette::Red, 1, 3); // Draw angle position on the circle drawDot(ctx, rect, c, s, Palette::Red, Size::Large); // Draw graduations diff --git a/apps/code/catalog.de.i18n b/apps/code/catalog.de.i18n index f8a651596..f53e3d884 100644 --- a/apps/code/catalog.de.i18n +++ b/apps/code/catalog.de.i18n @@ -13,12 +13,15 @@ PythonAbs = "Absolute/r Wert/Größe" PythonAcos = "Arkuskosinus" PythonAcosh = "Hyperbelkosinus" PythonAppend = "Hängt x an das Ende der Liste" +PythonArrow = "Arrow from (x,y) to (x+dx,y+dy)" PythonAsin = "Arkussinus" PythonAsinh = "Hyperbelsinus" PythonAtan = "Arkustangens" PythonAtan2 = "Gib atan(y/x)" PythonAtanh = "Hyperbeltangens" -PythonBin = "Ganzzahl nach binär" +PythonAxis = "Set axes to (xmin,xmax,ymin,ymax)" +PythonBar = "Draw a bar plot with x values" +PythonBin = "Ganzzahl nach binär konvertieren" PythonCeil = "Aufrundung" PythonChoice = "Zufallszahl aus der Liste" PythonClear = "Leere die Liste" @@ -46,12 +49,15 @@ PythonFrExp = "Rest und Exponent von x" PythonGamma = "Gammafunktion" PythonGetPixel = "Farbe von Pixel (x,y)" PythonGetrandbits = "Ganzzahl mit k zufälligen Bits" +PythonGrid = "Toggle the visibility of the grid" PythonHex = "Ganzzahl zu Hexadecimal" +PythonHist = "Draw the histogram of x" PythonImportCmath = "cmath Modul importieren" PythonImportIon = "ion Modul importieren" PythonImportKandinsky = "kandinsky Modul importieren" PythonImportRandom = "random Modul importieren" PythonImportMath = "math Modul importieren" +PythonImportMatplotlibPyplot = "Import matplotlib.pyplot module" PythonImportTime = "time Modul importieren" PythonImportTurtle = "turtle Modul importieren" PythonIndex = "Index, bei dem x zuerst vorkommt" @@ -117,12 +123,14 @@ PythonLog = "Logarithm to base a" PythonLog10 = "Logarithm to base 10" PythonLog2 = "Logarithm to base 2" PythonMathFunction = "math module function prefix" +PythonMatplotlibPyplotFunction = "matplotlib.pyplot module prefix" PythonMax = "Maximum" PythonMin = "Minimum" PythonModf = "Fractional and integer parts of x" PythonMonotonic = "Value of a monotonic clock" PythonOct = "Convert integer to octal" PythonPhase = "Phase of z" +PythonPlot = "Plot y versus x as lines" PythonPolar = "z in polar coordinates" PythonPop = "Remove and return the last item" PythonPower = "x raised to the power y" @@ -138,9 +146,11 @@ PythonRect = "z in cartesian coordinates" PythonRemove = "Remove the first occurrence of x" PythonReverse = "Reverse the elements of the list" PythonRound = "Round to n digits" +PythonScatter = "Draw a scatter plot of y versus x" PythonSeed = "Initialize random number generator" PythonSetPixel = "Color pixel (x,y)" -PythonSin = "Sinus" +PythonShow = "Display the figure" +PythonSin = "Sine" PythonSinh = "Hyperbolic sine" PythonSleep = "Suspend the execution for t seconds" PythonSort = "Sort the list" @@ -148,6 +158,7 @@ PythonSqrt = "Wurzel" PythonSum = "Sum the items of a list" PythonTan = "Tangens" PythonTanh = "Hyperbolic tangent" +PythonText = "Display a text at (x,y) coordinates" PythonTimeFunction = "time module function prefix" PythonTrunc = "x truncated to an integer" PythonTurtleBackward = "Move backward by x pixels" diff --git a/apps/code/catalog.en.i18n b/apps/code/catalog.en.i18n index 67e6b1582..31755c5d1 100644 --- a/apps/code/catalog.en.i18n +++ b/apps/code/catalog.en.i18n @@ -13,11 +13,14 @@ PythonAbs = "Absolute value/Magnitude" PythonAcos = "Arc cosine" PythonAcosh = "Arc hyperbolic cosine" PythonAppend = "Add x to the end of the list" +PythonArrow = "Arrow from (x,y) to (x+dx,y+dy)" PythonAsin = "Arc sine" PythonAsinh = "Arc hyperbolic sine" PythonAtan = "Arc tangent" PythonAtan2 = "Return atan(y/x)" PythonAtanh = "Arc hyperbolic tangent" +PythonAxis = "Set axes to (xmin,xmax,ymin,ymax)" +PythonBar = "Draw a bar plot with x values" PythonBin = "Convert integer to binary" PythonCeil = "Ceiling" PythonChoice = "Random number in the list" @@ -46,12 +49,15 @@ PythonFrExp = "Mantissa and exponent of x" PythonGamma = "Gamma function" PythonGetPixel = "Return pixel (x,y) color" PythonGetrandbits = "Integer with k random bits" +PythonGrid = "Toggle the visibility of the grid" PythonHex = "Convert integer to hexadecimal" +PythonHist = "Draw the histogram of x" PythonImportCmath = "Import cmath module" PythonImportIon = "Import ion module" PythonImportKandinsky = "Import kandinsky module" PythonImportRandom = "Import random module" PythonImportMath = "Import math module" +PythonImportMatplotlibPyplot = "Import matplotlib.pyplot module" PythonImportTime = "Import time module" PythonImportTurtle = "Import turtle module" PythonIndex = "Index of the first x occurrence" @@ -117,12 +123,14 @@ PythonLog = "Logarithm to base a" PythonLog10 = "Logarithm to base 10" PythonLog2 = "Logarithm to base 2" PythonMathFunction = "math module function prefix" +PythonMatplotlibPyplotFunction = "matplotlib.pyplot module prefix" PythonMax = "Maximum" PythonMin = "Minimum" PythonModf = "Fractional and integer parts of x" PythonMonotonic = "Value of a monotonic clock" PythonOct = "Convert integer to octal" PythonPhase = "Phase of z" +PythonPlot = "Plot y versus x as lines" PythonPolar = "z in polar coordinates" PythonPop = "Remove and return the last item" PythonPower = "x raised to the power y" @@ -138,8 +146,10 @@ PythonRect = "z in cartesian coordinates" PythonRemove = "Remove the first occurrence of x" PythonReverse = "Reverse the elements of the list" PythonRound = "Round to n digits" +PythonScatter = "Draw a scatter plot of y versus x" PythonSeed = "Initialize random number generator" PythonSetPixel = "Color pixel (x,y)" +PythonShow = "Display the figure" PythonSin = "Sine" PythonSinh = "Hyperbolic sine" PythonSleep = "Suspend the execution for t seconds" @@ -148,6 +158,7 @@ PythonSqrt = "Square root" PythonSum = "Sum the items of a list" PythonTan = "Tangent" PythonTanh = "Hyperbolic tangent" +PythonText = "Display a text at (x,y) coordinates" PythonTimeFunction = "time module function prefix" PythonTrunc = "x truncated to an integer" PythonTurtleBackward = "Move backward by x pixels" diff --git a/apps/code/catalog.es.i18n b/apps/code/catalog.es.i18n index 074e16df1..e5a7d3d47 100644 --- a/apps/code/catalog.es.i18n +++ b/apps/code/catalog.es.i18n @@ -13,11 +13,14 @@ PythonAbs = "Absolute value/Magnitude" PythonAcos = "Arc cosine" PythonAcosh = "Arc hyperbolic cosine" PythonAppend = "Add x to the end of the list" +PythonArrow = "Arrow from (x,y) to (x+dx,y+dy)" PythonAsin = "Arc sine" PythonAsinh = "Arc hyperbolic sine" PythonAtan = "Arc tangent" PythonAtan2 = "Return atan(y/x)" PythonAtanh = "Arc hyperbolic tangent" +PythonAxis = "Set axes to (xmin,xmax,ymin,ymax)" +PythonBar = "Draw a bar plot with x values" PythonBin = "Convert integer to binary" PythonCeil = "Ceiling" PythonChoice = "Random number in the list" @@ -46,12 +49,15 @@ PythonFrExp = "Mantissa and exponent of x" PythonGamma = "Gamma function" PythonGetPixel = "Return pixel (x,y) color" PythonGetrandbits = "Integer with k random bits" +PythonGrid = "Toggle the visibility of the grid" PythonHex = "Convert integer to hexadecimal" +PythonHist = "Draw the histogram of x" PythonImportCmath = "Import cmath module" PythonImportIon = "Import ion module" PythonImportKandinsky = "Import kandinsky module" PythonImportRandom = "Import random module" PythonImportMath = "Import math module" +PythonImportMatplotlibPyplot = "Import matplotlib.pyplot module" PythonImportTime = "Import time module" PythonImportTurtle = "Import turtle module" PythonIndex = "Index of the first x occurrence" @@ -117,12 +123,14 @@ PythonLog = "Logarithm to base a" PythonLog10 = "Logarithm to base 10" PythonLog2 = "Logarithm to base 2" PythonMathFunction = "math module function prefix" +PythonMatplotlibPyplotFunction = "matplotlib.pyplot module prefix" PythonMax = "Maximum" PythonMin = "Minimum" PythonModf = "Fractional and integer parts of x" PythonMonotonic = "Value of a monotonic clock" PythonOct = "Convert integer to octal" PythonPhase = "Phase of z" +PythonPlot = "Plot y versus x as lines" PythonPolar = "z in polar coordinates" PythonPop = "Remove and return the last item" PythonPower = "x raised to the power y" @@ -138,8 +146,10 @@ PythonRect = "z in cartesian coordinates" PythonRemove = "Remove the first occurrence of x" PythonReverse = "Reverse the elements of the list" PythonRound = "Round to n digits" +PythonScatter = "Draw a scatter plot of y versus x" PythonSeed = "Initialize random number generator" PythonSetPixel = "Color pixel (x,y)" +PythonShow = "Display the figure" PythonSin = "Sine" PythonSinh = "Hyperbolic sine" PythonSleep = "Suspend the execution for t seconds" @@ -148,6 +158,7 @@ PythonSqrt = "Square root" PythonSum = "Sum the items of a list" PythonTan = "Tangent" PythonTanh = "Hyperbolic tangent" +PythonText = "Display a text at (x,y) coordinates" PythonTimeFunction = "time module function prefix" PythonTrunc = "x truncated to an integer" PythonTurtleBackward = "Move backward by x pixels" diff --git a/apps/code/catalog.fr.i18n b/apps/code/catalog.fr.i18n index b326507bc..47ca4ab0a 100644 --- a/apps/code/catalog.fr.i18n +++ b/apps/code/catalog.fr.i18n @@ -13,11 +13,14 @@ PythonAbs = "Valeur absolue/Module" PythonAcos = "Arc cosinus" PythonAcosh = "Arc cosinus hyperbolique" PythonAppend = "Insère x à la fin de la liste" +PythonArrow = "Flèche de (x,y) à (x+dx,y+dy)" PythonAsin = "Arc sinus" PythonAsinh = "Arc sinus hyperbolique" PythonAtan = "Arc tangente" PythonAtan2 = "Calcul de atan(y/x)" PythonAtanh = "Arc tangente hyperbolique" +PythonAxis = "Met les axes (xmin,xmax,ymin,ymax)" +PythonBar = "Diagramme en barres de la liste x" PythonBin = "Conversion d'un entier en binaire" PythonCeil = "Plafond" PythonChoice = "Nombre aléatoire dans la liste" @@ -46,12 +49,15 @@ PythonFrExp = "Mantisse et exposant de x : (m,e)" PythonGamma = "Fonction gamma" PythonGetPixel = "Renvoie la couleur du pixel (x,y)" PythonGetrandbits = "Nombre aléatoire sur k bits" +PythonGrid = "Affiche ou masque la grille" PythonHex = "Conversion entier en hexadécimal" +PythonHist = "Histogramme de la liste x" PythonImportCmath = "Importation du module cmath" PythonImportIon = "Importation du module ion" PythonImportKandinsky = "Importation du module kandinsky" PythonImportRandom = "Importation du module random" PythonImportMath = "Importation du module math" +PythonImportMatplotlibPyplot = "Importation de matplotlib.pyplot" PythonImportTurtle = "Importation du module turtle" PythonImportTime = "Importation du module time" PythonIndex = "Indice première occurrence de x" @@ -117,12 +123,14 @@ PythonLog = "Logarithme de base a" PythonLog10 = "Logarithme décimal" PythonLog2 = "Logarithme de base 2" PythonMathFunction = "Préfixe fonction du module math" +PythonMatplotlibPyplotFunction = "Préfixe du module matplotlib.pyplot" PythonMax = "Maximum" PythonMin = "Minimum" PythonModf = "Parties fractionnaire et entière" PythonMonotonic = "Renvoie la valeur de l'horloge" PythonOct = "Conversion en octal" PythonPhase = "Argument de z" +PythonPlot = "Trace y en fonction de x" PythonPolar = "Conversion en polaire" PythonPop = "Supprime le dernier élément" PythonPower = "x à la puissance y" @@ -138,8 +146,10 @@ PythonRect = "Conversion en algébrique" PythonRemove = "Supprime le premier x de la liste" PythonReverse = "Inverse les éléments de la liste" PythonRound = "Arrondi à n décimales" +PythonScatter = "Nuage des points (x,y)" PythonSeed = "Initialiser générateur aléatoire" PythonSetPixel = "Colore le pixel (x,y)" +PythonShow = "Affiche la figure" PythonSin = "Sinus" PythonSinh = "Sinus hyperbolique" PythonSleep = "Suspend l'exécution t secondes" @@ -148,6 +158,7 @@ PythonSqrt = "Racine carrée" PythonSum = "Somme des éléments de la liste" PythonTan = "Tangente" PythonTanh = "Tangente hyperbolique" +PythonText = "Affiche un texte en (x,y)" PythonTimeFunction = "Préfixe fonction module time" PythonTrunc = "Troncature entière" PythonTurtleBackward = "Recule de x pixels" diff --git a/apps/code/catalog.pt.i18n b/apps/code/catalog.pt.i18n index ff20380ab..ea8e665f9 100644 --- a/apps/code/catalog.pt.i18n +++ b/apps/code/catalog.pt.i18n @@ -13,11 +13,14 @@ PythonAbs = "Absolute value/Magnitude" PythonAcos = "Arc cosine" PythonAcosh = "Arc hyperbolic cosine" PythonAppend = "Add x to the end of the list" +PythonArrow = "Arrow from (x,y) to (x+dx,y+dy)" PythonAsin = "Arc sine" PythonAsinh = "Arc hyperbolic sine" PythonAtan = "Arc tangent" PythonAtan2 = "Return atan(y/x)" PythonAtanh = "Arc hyperbolic tangent" +PythonAxis = "Set axes to (xmin,xmax,ymin,ymax)" +PythonBar = "Draw a bar plot with x values" PythonBin = "Convert integer to binary" PythonCeil = "Ceiling" PythonChoice = "Random number in the list" @@ -46,12 +49,15 @@ PythonFrExp = "Mantissa and exponent of x" PythonGamma = "Gamma function" PythonGetPixel = "Return pixel (x,y) color" PythonGetrandbits = "Integer with k random bits" +PythonGrid = "Toggle the visibility of the grid" PythonHex = "Convert integer to hexadecimal" +PythonHist = "Draw the histogram of x" PythonImportCmath = "Import cmath module" PythonImportIon = "Import ion module" PythonImportKandinsky = "Import kandinsky module" PythonImportRandom = "Import random module" PythonImportMath = "Import math module" +PythonImportMatplotlibPyplot = "Import matplotlib.pyplot module" PythonImportTime = "Import time module" PythonImportTurtle = "Import turtle module" PythonIndex = "Index of the first x occurrence" @@ -117,12 +123,14 @@ PythonLog = "Logarithm to base a" PythonLog10 = "Logarithm to base 10" PythonLog2 = "Logarithm to base 2" PythonMathFunction = "math module function prefix" +PythonMatplotlibPyplotFunction = "matplotlib.pyplot module prefix" PythonMax = "Maximum" PythonMin = "Minimum" PythonModf = "Fractional and integer parts of x" PythonMonotonic = "Value of a monotonic clock" PythonOct = "Convert integer to octal" PythonPhase = "Phase of z" +PythonPlot = "Plot y versus x as lines" PythonPolar = "z in polar coordinates" PythonPop = "Remove and return the last item" PythonPower = "x raised to the power y" @@ -138,8 +146,10 @@ PythonRect = "z in cartesian coordinates" PythonRemove = "Remove the first occurrence of x" PythonReverse = "Reverse the elements of the list" PythonRound = "Round to n digits" +PythonScatter = "Draw a scatter plot of y versus x" PythonSeed = "Initialize random number generator" PythonSetPixel = "Color pixel (x,y)" +PythonShow = "Display the figure" PythonSin = "Sine" PythonSinh = "Hyperbolic sine" PythonSleep = "Suspend the execution for t seconds" @@ -148,6 +158,7 @@ PythonSqrt = "Square root" PythonSum = "Sum the items of a list" PythonTan = "Tangent" PythonTanh = "Hyperbolic tangent" +PythonText = "Display a text at (x,y) coordinates" PythonTimeFunction = "time module function prefix" PythonTrunc = "x truncated to an integer" PythonTurtleBackward = "Move backward by x pixels" diff --git a/apps/code/catalog.universal.i18n b/apps/code/catalog.universal.i18n index 111261574..d83b8cb1d 100644 --- a/apps/code/catalog.universal.i18n +++ b/apps/code/catalog.universal.i18n @@ -12,11 +12,15 @@ PythonCommandAcos = "acos(x)" PythonCommandAcosh = "acosh(x)" PythonCommandAppend = "list.append(x)" PythonCommandAppendWithoutArg = ".append(\x11)" +PythonCommandArrow = "arrow(x,y,dx,dy)" PythonCommandAsin = "asin(x)" PythonCommandAsinh = "asinh(x)" PythonCommandAtan = "atan(x)" PythonCommandAtan2 = "atan2(y,x)" PythonCommandAtanh = "atanh(x)" +PythonCommandAxis = "axis((xmin,xmax,ymin,ymax))" +PythonCommandAxisWithoutArg = "axis(\x11)" +PythonCommandBar = "bar(x,height)" PythonCommandBin = "bin(x)" PythonCommandCeil = "ceil(x)" PythonCommandChoice = "choice(list)" @@ -52,13 +56,16 @@ PythonCommandFrExp = "frexp(x)" PythonCommandGamma = "gamma(x)" PythonCommandGetPixel = "get_pixel(x,y)" PythonCommandGetrandbits = "getrandbits(k)" +PythonCommandGrid = "grid()" PythonCommandHex = "hex(x)" +PythonCommandHist = "hist(x,bins)" PythonCommandImag = "z.imag" PythonCommandImagWithoutArg = ".imag" PythonCommandImportFromCmath = "from cmath import *" PythonCommandImportFromIon = "from ion import *" PythonCommandImportFromKandinsky = "from kandinsky import *" PythonCommandImportFromMath = "from math import *" +PythonCommandImportFromMatplotlibPyplot = "from matplotlib.pyplot import *" PythonCommandImportFromRandom = "from random import *" PythonCommandImportFromTime = "from time import *" PythonCommandImportFromTurtle = "from turtle import *" @@ -66,6 +73,7 @@ PythonCommandImportCmath = "import cmath" PythonCommandImportIon = "import ion" PythonCommandImportKandinsky = "import kandinsky" PythonCommandImportMath = "import math" +PythonCommandImportMatplotlibPyplot = "import matplotlib.pyplot" PythonCommandImportRandom = "import random" PythonCommandImportTime = "import time" PythonCommandImportTurtle = "import turtle" @@ -138,12 +146,15 @@ PythonCommandLog2 = "log2(x)" PythonCommandLogComplex = "log(z,a)" PythonCommandMathFunction = "math.function" PythonCommandMathFunctionWithoutArg = "math.\x11" +PythonCommandMatplotlibPyplotFunction = "matplotlib.pyplot.function" +PythonCommandMatplotlibPyplotFunctionWithoutArg = "matplotlib.pyplot.\x11" PythonCommandMax = "max(list)" PythonCommandMin = "min(list)" PythonCommandModf = "modf(x)" PythonCommandMonotonic = "monotonic()" PythonCommandOct = "oct(x)" PythonCommandPhase = "phase(z)" +PythonCommandPlot = "plot(x,y)" PythonCommandPolar = "polar(z)" PythonCommandPop = "list.pop()" PythonCommandPopWithoutArg = ".pop()" @@ -165,8 +176,10 @@ PythonCommandRemoveWithoutArg = ".remove(\x11)" PythonCommandReverse = "list.reverse()" PythonCommandReverseWithoutArg = ".reverse()" PythonCommandRound = "round(x, n)" +PythonCommandScatter = "scatter(x,y)" PythonCommandSeed = "seed(x)" PythonCommandSetPixel = "set_pixel(x,y,color)" +PythonCommandShow = "show()" PythonCommandSin = "sin(x)" PythonCommandSinComplex = "sin(z)" PythonCommandSinh = "sinh(x)" @@ -179,6 +192,7 @@ PythonCommandSqrtComplex = "sqrt(z)" PythonCommandSum = "sum(list)" PythonCommandTan = "tan(x)" PythonCommandTanh = "tanh(x)" +PythonCommandText = "text(x,y,\"text\")" PythonCommandTimeFunction = "time.function" PythonCommandTimeFunctionWithoutArg = "time.\x11" PythonCommandTrunc = "trunc(x)" diff --git a/apps/code/console_controller.cpp b/apps/code/console_controller.cpp index 6b3c9766f..1cdb14113 100644 --- a/apps/code/console_controller.cpp +++ b/apps/code/console_controller.cpp @@ -34,9 +34,8 @@ ConsoleController::ConsoleController(Responder * parentResponder, App * pythonDe m_selectableTableView(this, this, this, this), m_editCell(this, pythonDelegate, this), m_scriptStore(scriptStore), - m_sandboxController(this, this), - m_inputRunLoopActive(false), - m_preventEdition(false) + m_sandboxController(this), + m_inputRunLoopActive(false) #if EPSILON_GETOPT , m_locked(lockOnConsole) #endif @@ -82,14 +81,12 @@ void ConsoleController::runAndPrintForCommand(const char * command) { assert(m_outputAccumulationBuffer[0] == '\0'); // Draw the console before running the code - m_preventEdition = true; m_editCell.setText(""); m_editCell.setPrompt(""); refreshPrintOutput(); runCode(storedCommand); - m_preventEdition = false; m_editCell.setPrompt(sStandardPromptText); m_editCell.setEditing(true); @@ -108,9 +105,7 @@ const char * ConsoleController::inputText(const char * prompt) { m_inputRunLoopActive = true; // Hide the sandbox if it is displayed - if (sandboxIsDisplayed()) { - hideSandbox(); - } + hideAnyDisplayedViewController(); const char * promptText = prompt; char * s = const_cast(prompt); @@ -157,8 +152,7 @@ const char * ConsoleController::inputText(const char * prompt) { m_editCell.clearAndReduceSize(); // Reload the history - m_selectableTableView.reloadData(); - m_selectableTableView.selectCellAtLocation(0, m_consoleStore.numberOfLines()); + reloadData(true); appsContainer->redrawWindow(); // Launch a new input loop @@ -191,14 +185,21 @@ void ConsoleController::viewWillAppear() { m_importScriptsWhenViewAppears = false; autoImport(); } - m_selectableTableView.reloadData(); - m_selectableTableView.selectCellAtLocation(0, m_consoleStore.numberOfLines()); - m_editCell.setEditing(true); - m_editCell.setText(""); + + reloadData(true); } void ConsoleController::didBecomeFirstResponder() { - Container::activeApp()->setFirstResponder(&m_editCell); + if (!isDisplayingViewController()) { + Container::activeApp()->setFirstResponder(&m_editCell); + } else { + /* A view controller might be displayed: for example, when pushing the + * console on the stack controller, we auto-import scripts during the + * 'viewWillAppear' and then we set the console as first responder. The + * sandbox or the matplotlib controller might have been pushed in the + * auto-import. */ + Container::activeApp()->setFirstResponder(stackViewController()->topViewController()); + } } bool ConsoleController::handleEvent(Ion::Events::Event event) { @@ -347,11 +348,8 @@ bool ConsoleController::textFieldDidFinishEditing(TextField * textField, const c } telemetryReportEvent("Console", text); runAndPrintForCommand(text); - if (!sandboxIsDisplayed()) { - m_selectableTableView.reloadData(); - m_editCell.setEditing(true); - textField->setText(""); - m_selectableTableView.selectCellAtLocation(0, m_consoleStore.numberOfLines()); + if (!isDisplayingViewController()) { + reloadData(true); } return true; } @@ -378,37 +376,53 @@ bool ConsoleController::textFieldDidAbortEditing(TextField * textField) { return true; } -void ConsoleController::displaySandbox() { - if (sandboxIsDisplayed()) { - return; - } - stackViewController()->push(&m_sandboxController); -} - -void ConsoleController::hideSandbox() { - if (!sandboxIsDisplayed()) { - return; - } - m_sandboxController.hide(); -} - void ConsoleController::resetSandbox() { - if (!sandboxIsDisplayed()) { + if (stackViewController()->topViewController() != sandbox()) { return; } m_sandboxController.reset(); } -void ConsoleController::refreshPrintOutput() { - if (sandboxIsDisplayed()) { +void ConsoleController::displayViewController(ViewController * controller) { + if (stackViewController()->topViewController() == controller) { return; } + hideAnyDisplayedViewController(); + stackViewController()->push(controller); +} + +void ConsoleController::hideAnyDisplayedViewController() { + if (!isDisplayingViewController()) { + return; + } + stackViewController()->pop(); +} + +bool ConsoleController::isDisplayingViewController() { + /* The StackViewController model state is the best way to know wether the + * console is displaying a View Controller (Sandbox or Matplotlib). Indeed, + * keeping a boolean or a pointer raises the issue of when updating it - when + * 'viewWillAppear' or when 'didEnterResponderChain' - in both cases, the + * state would be wrong at some point... */ + return stackViewController()->depth() > 2; +} + +void ConsoleController::refreshPrintOutput() { + if (!isDisplayingViewController()) { + reloadData(false); + AppsContainer::sharedAppsContainer()->redrawWindow(); + } +} + +void ConsoleController::reloadData(bool isEditing) { m_selectableTableView.reloadData(); m_selectableTableView.selectCellAtLocation(0, m_consoleStore.numberOfLines()); - if (m_preventEdition) { + if (isEditing) { + m_editCell.setEditing(true); + m_editCell.setText(""); + } else { m_editCell.setEditing(false); } - AppsContainer::sharedAppsContainer()->redrawWindow(); } /* printText is called by the Python machine. @@ -457,12 +471,11 @@ void ConsoleController::printText(const char * text, size_t length) { } void ConsoleController::autoImportScript(Script script, bool force) { - if (sandboxIsDisplayed()) { - /* The sandbox might be displayed, for instance if we are auto-importing - * several scripts that draw at importation. In this case, we want to remove - * the sandbox. */ - hideSandbox(); - } + /* The sandbox might be displayed, for instance if we are auto-importing + * several scripts that draw at importation. In this case, we want to remove + * the sandbox. */ + hideAnyDisplayedViewController(); + if (script.importationStatus() || force) { // Step 1 - Create the command "from scriptName import *". @@ -488,11 +501,8 @@ void ConsoleController::autoImportScript(Script script, bool force) { // Step 2 - Run the command runAndPrintForCommand(command); } - if (!sandboxIsDisplayed() && force) { - m_selectableTableView.reloadData(); - m_selectableTableView.selectCellAtLocation(0, m_consoleStore.numberOfLines()); - m_editCell.setEditing(true); - m_editCell.setText(""); + if (!isDisplayingViewController() && force) { + reloadData(true); } } diff --git a/apps/code/console_controller.h b/apps/code/console_controller.h index 748d3855f..e50d31c34 100644 --- a/apps/code/console_controller.h +++ b/apps/code/console_controller.h @@ -61,9 +61,10 @@ public: bool textFieldDidAbortEditing(TextField * textField) override; // MicroPython::ExecutionEnvironment - void displaySandbox() override; - void hideSandbox() override; + ViewController * sandbox() override { return &m_sandboxController; } void resetSandbox() override; + void displayViewController(ViewController * controller) override; + void hideAnyDisplayedViewController() override; void refreshPrintOutput() override; void printText(const char * text, size_t length) override; const char * inputText(const char * prompt) override; @@ -82,6 +83,8 @@ private: static constexpr int k_numberOfLineCells = (Ion::Display::Height - Metric::TitleBarHeight) / 14 + 2; // 14 = KDFont::SmallFont->glyphSize().height() // k_numberOfLineCells = (240 - 18)/14 ~ 15.9. The 0.1 cell can be above and below the 15 other cells so we add +2 cells. static constexpr int k_outputAccumulationBufferSize = 100; + bool isDisplayingViewController(); + void reloadData(bool isEditing); void flushOutputAccumulationBufferToStore(); void appendTextToOutputAccumulationBuffer(const char * text, size_t length); void emptyOutputAccumulationBuffer(); @@ -103,7 +106,6 @@ private: SandboxController m_sandboxController; bool m_inputRunLoopActive; bool m_autoImportScripts; - bool m_preventEdition; #if EPSILON_GETOPT bool m_locked; #endif diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index 792abf106..5043c7f7f 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -92,30 +92,6 @@ const ToolboxMessageTree MathModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandLgamma, I18n::Message::PythonLgamma) }; -const ToolboxMessageTree KandinskyModuleChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportKandinsky, I18n::Message::PythonImportKandinsky, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromKandinsky, I18n::Message::PythonImportKandinsky, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKandinskyFunction, I18n::Message::PythonKandinskyFunction, false, I18n::Message::PythonCommandKandinskyFunctionWithoutArg), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetPixel, I18n::Message::PythonGetPixel), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSetPixel, I18n::Message::PythonSetPixel), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColor, I18n::Message::PythonColor), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandDrawString, I18n::Message::PythonDrawString), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillRect, I18n::Message::PythonFillRect) -}; - -const ToolboxMessageTree RandomModuleChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportRandom, I18n::Message::PythonImportRandom, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromRandom, I18n::Message::PythonImportRandom, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandRandomFunction, I18n::Message::PythonRandomFunction, false, I18n::Message::PythonCommandRandomFunctionWithoutArg), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetrandbits, I18n::Message::PythonGetrandbits), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSeed, I18n::Message::PythonSeed), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandRandrange, I18n::Message::PythonRandrange), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandRandint, I18n::Message::PythonRandint), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandChoice, I18n::Message::PythonChoice), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandRandom, I18n::Message::PythonRandom, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandUniform, I18n::Message::PythonUniform) -}; - const ToolboxMessageTree CMathModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportCmath, I18n::Message::PythonImportCmath, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromCmath, I18n::Message::PythonImportCmath, false), @@ -132,6 +108,21 @@ const ToolboxMessageTree CMathModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSinComplex, I18n::Message::PythonSin) }; +const ToolboxMessageTree MatplotlibPyplotModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportMatplotlibPyplot, I18n::Message::PythonImportMatplotlibPyplot, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromMatplotlibPyplot, I18n::Message::PythonImportMatplotlibPyplot, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandMatplotlibPyplotFunction, I18n::Message::PythonMatplotlibPyplotFunction, false, I18n::Message::PythonCommandMatplotlibPyplotFunctionWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandArrow, I18n::Message::PythonArrow), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandAxis, I18n::Message::PythonAxis, false, I18n::Message::PythonCommandAxisWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandBar, I18n::Message::PythonBar), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGrid, I18n::Message::PythonGrid), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandHist, I18n::Message::PythonHist), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandPlot, I18n::Message::PythonPlot), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScatter, I18n::Message::PythonScatter), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandShow, I18n::Message::PythonShow), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandText, I18n::Message::PythonText) +}; + const ToolboxMessageTree TurtleModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportTurtle, I18n::Message::PythonImportTurtle, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromTurtle, I18n::Message::PythonImportTurtle, false), @@ -167,6 +158,30 @@ const ToolboxMessageTree TurtleModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandGrey, I18n::Message::PythonTurtleGrey, false) }; +const ToolboxMessageTree RandomModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportRandom, I18n::Message::PythonImportRandom, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromRandom, I18n::Message::PythonImportRandom, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandRandomFunction, I18n::Message::PythonRandomFunction, false, I18n::Message::PythonCommandRandomFunctionWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetrandbits, I18n::Message::PythonGetrandbits), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSeed, I18n::Message::PythonSeed), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandRandrange, I18n::Message::PythonRandrange), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandRandint, I18n::Message::PythonRandint), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandChoice, I18n::Message::PythonChoice), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandRandom, I18n::Message::PythonRandom, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandUniform, I18n::Message::PythonUniform) +}; + +const ToolboxMessageTree KandinskyModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportKandinsky, I18n::Message::PythonImportKandinsky, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromKandinsky, I18n::Message::PythonImportKandinsky, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKandinskyFunction, I18n::Message::PythonKandinskyFunction, false, I18n::Message::PythonCommandKandinskyFunctionWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetPixel, I18n::Message::PythonGetPixel), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSetPixel, I18n::Message::PythonSetPixel), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColor, I18n::Message::PythonColor), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandDrawString, I18n::Message::PythonDrawString), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillRect, I18n::Message::PythonFillRect) +}; + const ToolboxMessageTree IonModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportIon, I18n::Message::PythonImportIon, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromIon, I18n::Message::PythonImportIon, false), @@ -231,8 +246,9 @@ const ToolboxMessageTree TimeModuleChildren[] = { const ToolboxMessageTree modulesChildren[] = { ToolboxMessageTree::Node(I18n::Message::MathModule, MathModuleChildren), ToolboxMessageTree::Node(I18n::Message::CmathModule, CMathModuleChildren), - ToolboxMessageTree::Node(I18n::Message::RandomModule, RandomModuleChildren), + ToolboxMessageTree::Node(I18n::Message::MatplotlibPyplotModule, MatplotlibPyplotModuleChildren), ToolboxMessageTree::Node(I18n::Message::TurtleModule, TurtleModuleChildren), + ToolboxMessageTree::Node(I18n::Message::RandomModule, RandomModuleChildren), ToolboxMessageTree::Node(I18n::Message::KandinskyModule, KandinskyModuleChildren), ToolboxMessageTree::Node(I18n::Message::IonModule, IonModuleChildren), ToolboxMessageTree::Node(I18n::Message::TimeModule, TimeModuleChildren) @@ -251,12 +267,15 @@ const ToolboxMessageTree catalogChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandAbs, I18n::Message::PythonAbs), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandAcos, I18n::Message::PythonAcos), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandAcosh, I18n::Message::PythonAcosh), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandArrow, I18n::Message::PythonArrow), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandAsin, I18n::Message::PythonAsin), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandAsinh, I18n::Message::PythonAsinh), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandAtan, I18n::Message::PythonAtan), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandAtan2, I18n::Message::PythonAtan2), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandAtanh, I18n::Message::PythonAtanh), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandAxis, I18n::Message::PythonAxis, false, I18n::Message::PythonCommandAxisWithoutArg), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandBackward, I18n::Message::PythonTurtleBackward), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandBar, I18n::Message::PythonBar), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandBin, I18n::Message::PythonBin), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandBlack, I18n::Message::PythonTurtleBlack, false), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandBlue, I18n::Message::PythonTurtleBlue, false), @@ -290,6 +309,7 @@ const ToolboxMessageTree catalogChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromIon, I18n::Message::PythonImportIon, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromKandinsky, I18n::Message::PythonImportKandinsky, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromMath, I18n::Message::PythonImportMath, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromMatplotlibPyplot, I18n::Message::PythonImportMatplotlibPyplot, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromRandom, I18n::Message::PythonImportRandom, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromTurtle, I18n::Message::PythonImportTurtle, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromTime, I18n::Message::PythonImportTime, false), @@ -299,13 +319,16 @@ const ToolboxMessageTree catalogChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandGoto, I18n::Message::PythonTurtleGoto), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandGreen, I18n::Message::PythonTurtleGreen, false), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandGrey, I18n::Message::PythonTurtleGrey, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGrid, I18n::Message::PythonGrid), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandHeading, I18n::Message::PythonTurtleHeading, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandHex, I18n::Message::PythonHex), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandHideturtle, I18n::Message::PythonTurtleHideturtle, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandHist, I18n::Message::PythonHist), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportCmath, I18n::Message::PythonImportCmath, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportIon, I18n::Message::PythonImportIon, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportKandinsky, I18n::Message::PythonImportKandinsky, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportMath, I18n::Message::PythonImportMath, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportMatplotlibPyplot, I18n::Message::PythonImportMatplotlibPyplot, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportRandom, I18n::Message::PythonImportRandom, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportTurtle, I18n::Message::PythonImportTurtle, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportTime, I18n::Message::PythonImportTime, false), @@ -335,6 +358,7 @@ const ToolboxMessageTree catalogChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandLog10, I18n::Message::PythonLog10), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandLog2, I18n::Message::PythonLog2), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandMathFunction, I18n::Message::PythonMathFunction, false, I18n::Message::PythonCommandMathFunctionWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandMatplotlibPyplotFunction, I18n::Message::PythonMatplotlibPyplotFunction, false, I18n::Message::PythonCommandMatplotlibPyplotFunctionWithoutArg), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandMax, I18n::Message::PythonMax), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandMin, I18n::Message::PythonMin), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandModf, I18n::Message::PythonModf), @@ -350,6 +374,7 @@ const ToolboxMessageTree catalogChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandPolar, I18n::Message::PythonPolar), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandPosition, I18n::Message::PythonTurtlePosition, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandPower, I18n::Message::PythonPower), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandPlot, I18n::Message::PythonPlot), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandPrint, I18n::Message::PythonPrint), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandPurple, I18n::Message::PythonTurtlePurple, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandRadians, I18n::Message::PythonRadians), @@ -364,9 +389,11 @@ const ToolboxMessageTree catalogChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandReset, I18n::Message::PythonTurtleReset, false), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandRight, I18n::Message::PythonTurtleRight), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandRound, I18n::Message::PythonRound), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScatter, I18n::Message::PythonScatter), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandSetheading, I18n::Message::PythonTurtleSetheading), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSetPixel, I18n::Message::PythonSetPixel), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSeed, I18n::Message::PythonSeed), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandShow, I18n::Message::PythonShow), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandShowturtle, I18n::Message::PythonTurtleShowturtle, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSin, I18n::Message::PythonSin), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSinh, I18n::Message::PythonSinh), @@ -377,6 +404,7 @@ const ToolboxMessageTree catalogChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSum, I18n::Message::PythonSum), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandTan, I18n::Message::PythonTan), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandTanh, I18n::Message::PythonTanh), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandText, I18n::Message::PythonText), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandTimeFunction, I18n::Message::PythonTimeFunction, false, I18n::Message::PythonCommandTimeFunctionWithoutArg), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandTrunc, I18n::Message::PythonTrunc), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandTurtleFunction, I18n::Message::PythonTurtleFunction, false, I18n::Message::PythonCommandTurtleFunctionWithoutArg), diff --git a/apps/code/sandbox_controller.cpp b/apps/code/sandbox_controller.cpp index a8a7ab932..48d29f247 100644 --- a/apps/code/sandbox_controller.cpp +++ b/apps/code/sandbox_controller.cpp @@ -1,14 +1,16 @@ #include "sandbox_controller.h" #include +extern "C" { +#include +} + namespace Code { -SandboxController::SandboxController(Responder * parentResponder, MicroPython::ExecutionEnvironment * executionEnvironment) : +SandboxController::SandboxController(Responder * parentResponder) : ViewController(parentResponder), - m_solidColorView(Palette::CodeBackground), - m_executionEnvironment(executionEnvironment) + m_solidColorView(Palette::CodeBackground) { - assert(executionEnvironment != nullptr); } StackViewController * SandboxController::stackViewController() { @@ -20,19 +22,12 @@ void SandboxController::reset() { redrawWindow(); } -void SandboxController::hide() { - stackViewController()->pop(); -} - void SandboxController::viewWillAppear() { - assert(m_executionEnvironment != nullptr); - m_executionEnvironment->setSandboxIsDisplayed(true); redrawWindow(); } void SandboxController::viewDidDisappear() { - assert(m_executionEnvironment != nullptr); - m_executionEnvironment->setSandboxIsDisplayed(false); + modturtle_view_did_disappear(); } bool SandboxController::handleEvent(Ion::Events::Event event) { diff --git a/apps/code/sandbox_controller.h b/apps/code/sandbox_controller.h index c50725789..b5f52bc0f 100644 --- a/apps/code/sandbox_controller.h +++ b/apps/code/sandbox_controller.h @@ -11,10 +11,9 @@ namespace Code { class SandboxController : public ViewController { public: - SandboxController(Responder * parentResponder, MicroPython::ExecutionEnvironment * executionEnvironment); + SandboxController(Responder * parentResponder); StackViewController * stackViewController(); void reset(); - void hide(); // ViewController View * view() override { return &m_solidColorView; } @@ -26,7 +25,6 @@ public: private: void redrawWindow(); SolidColorView m_solidColorView; - MicroPython::ExecutionEnvironment * m_executionEnvironment; }; } diff --git a/apps/code/script_store.cpp b/apps/code/script_store.cpp index 705ba3506..e21047d7c 100644 --- a/apps/code/script_store.cpp +++ b/apps/code/script_store.cpp @@ -19,6 +19,7 @@ bool ScriptStore::ScriptNameIsFree(const char * baseName) { ScriptStore::ScriptStore() { addScriptFromTemplate(ScriptTemplate::Squares()); + addScriptFromTemplate(ScriptTemplate::Parabola()); addScriptFromTemplate(ScriptTemplate::Mandelbrot()); addScriptFromTemplate(ScriptTemplate::Polynomial()); } diff --git a/apps/code/script_template.cpp b/apps/code/script_template.cpp index 9ccfc6fff..971b44496 100644 --- a/apps/code/script_template.cpp +++ b/apps/code/script_template.cpp @@ -53,6 +53,37 @@ def roots(a,b,c): else: return None)"); +constexpr ScriptTemplate parabolaScriptTemplate("parabola.py", "\x01" R"(from matplotlib.pyplot import * +from math import * + +g=9.81 + +def x(t,v_0,alpha): + return v_0*cos(alpha)*t +def y(t,v_0,alpha,h_0): + return -0.5*g*t**2+v_0*sin(alpha)*t+h_0 + +def vx(v_0,alpha): + return v_0*cos(alpha) +def vy(t,v_0,alpha): + return -g*t+v_0*sin(alpha) + +def t_max(v_0,alpha,h_0): + return (v_0*sin(alpha)+sqrt((v_0**2)*(sin(alpha)**2)+2*g*h_0))/g + +def simulation(v_0=15,alpha=pi/4,h_0=2): + tMax=t_max(v_0,alpha,h_0) + accuracy=1/10**(floor(log10(tMax))-1) + T_MAX=floor(tMax*accuracy)+1 + X=[x(t/accuracy,v_0,alpha) for t in range(T_MAX)] + Y=[y(t/accuracy,v_0,alpha,h_0) for t in range(T_MAX)] + VX=[vx(v_0,alpha) for t in range(T_MAX)] + VY=[vy(t/accuracy,v_0,alpha) for t in range(T_MAX)] + for i in range(T_MAX): + arrow(X[i],Y[i],VX[i]/accuracy,VY[i]/accuracy) + grid() + show())"); + const ScriptTemplate * ScriptTemplate::Empty() { return &emptyScriptTemplate; } @@ -69,4 +100,8 @@ const ScriptTemplate * ScriptTemplate::Polynomial() { return &polynomialScriptTemplate; } +const ScriptTemplate * ScriptTemplate::Parabola() { + return ¶bolaScriptTemplate; +} + } diff --git a/apps/code/script_template.h b/apps/code/script_template.h index 2eb2e3472..d0d048ab1 100644 --- a/apps/code/script_template.h +++ b/apps/code/script_template.h @@ -10,6 +10,7 @@ public: static const ScriptTemplate * Squares(); static const ScriptTemplate * Mandelbrot(); static const ScriptTemplate * Polynomial(); + static const ScriptTemplate * Parabola(); const char * name() const { return m_name; } const char * content() const { return m_value+1; } const char * value() const { return m_value; } diff --git a/apps/code/toolbox.universal.i18n b/apps/code/toolbox.universal.i18n index d5afd929c..5a5072475 100644 --- a/apps/code/toolbox.universal.i18n +++ b/apps/code/toolbox.universal.i18n @@ -2,6 +2,7 @@ CmathModule = "cmath" IonModule = "ion" KandinskyModule = "kandinsky" MathModule = "math" +MatplotlibPyplotModule = "matplotlib.pyplot" TimeModule = "time" TurtleModule = "turtle" ForLoopMenu = "For" diff --git a/apps/graph/graph/calculation_graph_controller.h b/apps/graph/graph/calculation_graph_controller.h index 46f861ddf..14f5cc797 100644 --- a/apps/graph/graph/calculation_graph_controller.h +++ b/apps/graph/graph/calculation_graph_controller.h @@ -30,7 +30,6 @@ protected: MessageTextView m_defaultBannerView; bool m_isActive; private: - bool handleZoom(Ion::Events::Event event) override { return false; } bool handleEnter() override; bool moveCursorHorizontally(int direction, bool fast = false) override; Shared::InteractiveCurveViewRange * interactiveCurveViewRange() override { return m_graphRange; } diff --git a/apps/graph/graph/graph_view.cpp b/apps/graph/graph/graph_view.cpp index 7c966635a..34635f8d1 100644 --- a/apps/graph/graph/graph_view.cpp +++ b/apps/graph/graph/graph_view.cpp @@ -60,13 +60,12 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { }, f.operator->(), context(), f->color(), true, record == m_selectedRecord, m_highlightedStart, m_highlightedEnd); /* Draw tangent */ if (m_tangent && record == m_selectedRecord) { - float tangentParameter[2]; - tangentParameter[0] = f->approximateDerivative(m_curveViewCursor->x(), context()); - tangentParameter[1] = -tangentParameter[0]*m_curveViewCursor->x()+f->evaluateXYAtParameter(m_curveViewCursor->x(), context()).x2(); - drawCartesianCurve(ctx, rect, -INFINITY, INFINITY, [](float t, void * model, void * context) { - float * tangent = (float *)model; - return Poincare::Coordinate2D(t, tangent[0]*t+tangent[1]); - }, tangentParameter, nullptr, Palette::GraphTangent); + float tangentParameterA = f->approximateDerivative(m_curveViewCursor->x(), context()); + float tangentParameterB = -tangentParameterA*m_curveViewCursor->x()+f->evaluateXYAtParameter(m_curveViewCursor->x(), context()).x2(); + // To represent the tangent, we draw segment from and to abscissas at the extremity of the drawn rect + float minAbscissa = pixelToFloat(Axis::Horizontal, rect.left()); + float maxAbscissa = pixelToFloat(Axis::Horizontal, rect.right()); + drawSegment(ctx, rect, minAbscissa, tangentParameterA*minAbscissa+tangentParameterB, maxAbscissa, tangentParameterA*maxAbscissa+tangentParameterB, Palette::GraphTangent, false); } continue; } diff --git a/apps/regression/calculation_controller.cpp b/apps/regression/calculation_controller.cpp index fce340550..878ea2c12 100644 --- a/apps/regression/calculation_controller.cpp +++ b/apps/regression/calculation_controller.cpp @@ -168,6 +168,7 @@ void CalculationController::willDisplayCellAtLocation(HighlightCell * cell, int myCell->setFirstText(buffer); buffer[0] = 'Y'; myCell->setSecondText(buffer); + assert(seriesNumber < Palette::numberOfDataColors()); myCell->setColor(Palette::DataColor[seriesNumber]); return; } diff --git a/apps/regression/graph_controller.cpp b/apps/regression/graph_controller.cpp index b6b54aaf1..93b5c5867 100644 --- a/apps/regression/graph_controller.cpp +++ b/apps/regression/graph_controller.cpp @@ -407,6 +407,7 @@ void GraphController::setRoundCrossCursorView() { bool round = *m_selectedDotIndex < 0; if (round) { // Set the color although the cursor view stays round + assert(*m_selectedSeriesIndex < Palette::numberOfDataColors()); m_roundCursorView.setColor(Palette::DataColor[*m_selectedSeriesIndex]); } CursorView * nextCursorView = round ? static_cast(&m_roundCursorView) : static_cast(&m_crossCursorView); diff --git a/apps/regression/graph_view.cpp b/apps/regression/graph_view.cpp index 6a651bc22..13a31112e 100644 --- a/apps/regression/graph_view.cpp +++ b/apps/regression/graph_view.cpp @@ -22,6 +22,7 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { Poincare::Context * globContext = AppsContainer::sharedAppsContainer()->globalContext(); for (int series = 0; series < Store::k_numberOfSeries; series++) { if (!m_store->seriesIsEmpty(series)) { + assert(series < Palette::numberOfDataColors()); KDColor color = Palette::DataColor[series]; Model * seriesModel = m_store->modelForSeries(series); drawCartesianCurve(ctx, rect, -INFINITY, INFINITY, [](float abscissa, void * model, void * context) { diff --git a/apps/sequence/graph/graph_view.cpp b/apps/sequence/graph/graph_view.cpp index 5a0e51fd1..3dbb7bd92 100644 --- a/apps/sequence/graph/graph_view.cpp +++ b/apps/sequence/graph/graph_view.cpp @@ -32,9 +32,9 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { if (x >= m_highlightedStart && x <= m_highlightedEnd && record == m_selectedRecord) { KDColor color = m_shouldColorHighlighted ? s->color() : Palette::PrimaryText; if (y >= 0.0f) { - drawSegment(ctx, rect, Axis::Vertical, x, 0.0f, y, color, 1); + drawHorizontalOrVerticalSegment(ctx, rect, Axis::Vertical, x, 0.0f, y, color, 1); } else { - drawSegment(ctx, rect, Axis::Vertical, x, y, 0.0f, color, 1); + drawHorizontalOrVerticalSegment(ctx, rect, Axis::Vertical, x, y, 0.0f, color, 1); } } } diff --git a/apps/sequence/sequence_store.cpp b/apps/sequence/sequence_store.cpp index d15543595..18012cfa6 100644 --- a/apps/sequence/sequence_store.cpp +++ b/apps/sequence/sequence_store.cpp @@ -31,6 +31,7 @@ Ion::Storage::Record::ErrorStatus SequenceStore::addEmptyModel() { const char * name = firstAvailableName(&nameIndex); assert(name); // Choose the corresponding color + assert(nameIndex < Palette::numberOfDataColors()); KDColor color = Palette::DataColor[nameIndex]; Sequence::RecordDataBuffer data(color); // m_sequences diff --git a/apps/shared/Makefile b/apps/shared/Makefile index 98e4a8fe6..b406d41cb 100644 --- a/apps/shared/Makefile +++ b/apps/shared/Makefile @@ -1,6 +1,8 @@ app_shared_test_src = $(addprefix apps/shared/,\ continuous_function.cpp\ curve_view_range.cpp \ + curve_view.cpp \ + dots.cpp \ double_pair_store.cpp \ expression_model.cpp \ expression_model_handle.cpp \ @@ -9,8 +11,11 @@ app_shared_test_src = $(addprefix apps/shared/,\ global_context.cpp \ interactive_curve_view_range_delegate.cpp \ interactive_curve_view_range.cpp \ + labeled_curve_view.cpp \ memoized_curve_view_range.cpp \ range_1D.cpp \ + zoom_and_pan_curve_view_controller.cpp \ + zoom_curve_view_controller.cpp \ ) app_shared_src = $(addprefix apps/shared/,\ @@ -18,9 +23,7 @@ app_shared_src = $(addprefix apps/shared/,\ buffer_function_title_cell.cpp \ buffer_text_view_with_text_field.cpp \ button_with_separator.cpp \ - dots.cpp \ cursor_view.cpp \ - curve_view.cpp \ curve_view_cursor.cpp \ editable_cell_table_view_controller.cpp \ expression_field_delegate_app.cpp \ @@ -45,7 +48,6 @@ app_shared_src = $(addprefix apps/shared/,\ interactive_curve_view_controller.cpp \ interval.cpp \ interval_parameter_controller.cpp \ - labeled_curve_view.cpp \ language_controller.cpp \ layout_field_delegate.cpp \ list_parameter_controller.cpp \ diff --git a/apps/shared/continuous_function.cpp b/apps/shared/continuous_function.cpp index 96a7cb19a..fdb4fd2cc 100644 --- a/apps/shared/continuous_function.cpp +++ b/apps/shared/continuous_function.cpp @@ -57,8 +57,7 @@ ContinuousFunction ContinuousFunction::NewModel(Ion::Storage::Record::ErrorStatu static int s_colorIndex = 0; // Create the record char nameBuffer[SymbolAbstract::k_maxNameSize]; - int numberOfColors = sizeof(Palette::DataColor)/sizeof(KDColor); - RecordDataBuffer data(Palette::DataColor[s_colorIndex++ % numberOfColors]); + RecordDataBuffer data(Palette::nextDataColor(&s_colorIndex)); if (baseName == nullptr) { DefaultName(nameBuffer, SymbolAbstract::k_maxNameSize); baseName = nameBuffer; diff --git a/apps/shared/curve_view.cpp b/apps/shared/curve_view.cpp index 6664ea596..bf3451813 100644 --- a/apps/shared/curve_view.cpp +++ b/apps/shared/curve_view.cpp @@ -386,7 +386,7 @@ DrawLabel: } } -void CurveView::drawSegment(KDContext * ctx, KDRect rect, Axis axis, float coordinate, float lowerBound, float upperBound, KDColor color, KDCoordinate thickness, KDCoordinate dashSize) const { +void CurveView::drawHorizontalOrVerticalSegment(KDContext * ctx, KDRect rect, Axis axis, float coordinate, float lowerBound, float upperBound, KDColor color, KDCoordinate thickness, KDCoordinate dashSize) const { KDCoordinate min = (axis == Axis::Horizontal) ? rect.x() : rect.y(); KDCoordinate max = (axis == Axis::Horizontal) ? rect.x() + rect.width() : rect.y() + rect.height(); KDCoordinate start = std::isinf(lowerBound) ? min : std::round(floatToPixel(axis, lowerBound)); @@ -417,6 +417,14 @@ void CurveView::drawSegment(KDContext * ctx, KDRect rect, Axis axis, float coord } } +void CurveView::drawSegment(KDContext * ctx, KDRect rect, float x, float y, float u, float v, KDColor color, bool thick) const { + float pxf = floatToPixel(Axis::Horizontal, x); + float pyf = floatToPixel(Axis::Vertical, y); + float puf = floatToPixel(Axis::Horizontal, u); + float pvf = floatToPixel(Axis::Vertical, v); + straightJoinDots(ctx, rect, pxf, pyf, puf, pvf, color, thick); +} + void CurveView::drawDot(KDContext * ctx, KDRect rect, float x, float y, KDColor color, Size size) const { KDCoordinate diameter = 0; const uint8_t * mask = nullptr; @@ -444,6 +452,47 @@ void CurveView::drawDot(KDContext * ctx, KDRect rect, float x, float y, KDColor ctx->blendRectWithMask(dotRect, color, mask, workingBuffer); } + +void CurveView::drawArrow(KDContext * ctx, KDRect rect, float x, float y, float dx, float dy, KDColor color, KDCoordinate pixelArrowLength, float angle) const { + /* Let's call the following variables L and l: + * + * / | + * / | + * / l + * / | + * / | + * <-------------------------------------------------- + * \ + * \ + * \ + * \ + * \ + * + * ----- L ----- + * + **/ + assert(angle >= 0.0f); + /* We compute the arrow segments in pixels in order to correctly size the + * arrow without depending on the displayed range. + * Warning: the computed values are relative so we need to add/subtract the + * pixel position of 0s. */ + float x0Pixel = floatToPixel(Axis::Horizontal, 0.0f); + float y0Pixel = floatToPixel(Axis::Vertical, 0.0f); + float dxPixel = floatToPixel(Axis::Horizontal, dx) - x0Pixel; + float dyPixel = y0Pixel - floatToPixel(Axis::Vertical, dy); + float dx2dy2 = std::sqrt(dxPixel*dxPixel+dyPixel*dyPixel); + float L = pixelArrowLength; + float l = angle*L; + + float arrow1dx = pixelToFloat(Axis::Horizontal, x0Pixel + L*dxPixel/dx2dy2 + l*dyPixel/dx2dy2); + float arrow1dy = pixelToFloat(Axis::Vertical, y0Pixel - (L*dyPixel/dx2dy2 - l*dxPixel/dx2dy2)); + drawSegment(ctx, rect, x, y, x - arrow1dx, y - arrow1dy, color, false); + + float arrow2dx = pixelToFloat(Axis::Horizontal, x0Pixel + L*dxPixel/dx2dy2 - l*dyPixel/dx2dy2); + float arrow2dy = pixelToFloat(Axis::Vertical, y0Pixel - (L*dyPixel/dx2dy2 + l*dxPixel/dx2dy2)); + drawSegment(ctx, rect, x, y, x - arrow2dx, y - arrow2dy, color, false); +} + void CurveView::drawGrid(KDContext * ctx, KDRect rect) const { KDColor boldColor = Palette::GridPrimaryLine; KDColor lightColor = Palette::GridSecondaryLine; @@ -537,7 +586,7 @@ void CurveView::drawCurve(KDContext * ctx, KDRect rect, float tStart, float tEnd x = xy.x1(); y = xy.x2(); if (colorUnderCurve && !std::isnan(x) && colorLowerBound < x && x < colorUpperBound && !(std::isnan(y) || std::isinf(y))) { - drawSegment(ctx, rect, Axis::Vertical, x, minFloat(0.0f, y), maxFloat(0.0f, y), color, 1); + drawHorizontalOrVerticalSegment(ctx, rect, Axis::Vertical, x, minFloat(0.0f, y), maxFloat(0.0f, y), color, 1); } joinDots(ctx, rect, xyEvaluation, model, context, drawStraightLinesEarly, previousT, previousX, previousY, t, x, y, color, thick, k_maxNumberOfIterations); } while (true); diff --git a/apps/shared/curve_view.h b/apps/shared/curve_view.h index a9ea741ab..434b2ec1e 100644 --- a/apps/shared/curve_view.h +++ b/apps/shared/curve_view.h @@ -59,18 +59,47 @@ protected: float floatToPixel(Axis axis, float f) const; void drawLine(KDContext * ctx, KDRect rect, Axis axis, float coordinate, KDColor color, KDCoordinate thickness = 1, KDCoordinate dashSize = -1) const { - return drawSegment(ctx, rect, axis, coordinate, -INFINITY, INFINITY, color, + return drawHorizontalOrVerticalSegment(ctx, rect, axis, coordinate, -INFINITY, INFINITY, color, thickness, dashSize); } - void drawSegment(KDContext * ctx, KDRect rect, Axis axis, + void drawHorizontalOrVerticalSegment(KDContext * ctx, KDRect rect, Axis axis, float coordinate, float lowerBound, float upperBound, KDColor color, KDCoordinate thickness = 1, KDCoordinate dashSize = -1) const; + void drawSegment(KDContext * ctx, KDRect rect, + float x, float y, float u, float v, + KDColor color, bool thick = true + ) const; enum class Size : uint8_t { Small, Medium, Large }; void drawDot(KDContext * ctx, KDRect rect, float x, float y, KDColor color, Size size = Size::Small) const; + /* 'drawArrow' draws the edge of an arrow pointing to (x,y) with the + * orientation (dx,dy). + * The parameters defining the shape of the arrow are the length in pixel of + * the projection of the arrow on the segment -'pixelArrowLength'- and the + * tangent of the angle between the segment and each wing of the arrow called + * 'angle'. + * + * / | + * / | + * / L + * / | + * / | + * <-------------------------------------------------- + * \ + * \ + * \ + * \ + * \ + * + * <--- pl ---> + * + * pl = pixelArrowLength + * tan(angle) = L/pl + */ + void drawArrow(KDContext * ctx, KDRect rect, float x, float y, float dx, float dy, KDColor color, KDCoordinate pixelArrowLength = 10, float angle = 0.4f) const; void drawGrid(KDContext * ctx, KDRect rect) const; void drawAxes(KDContext * ctx, KDRect rect) const; void drawAxis(KDContext * ctx, KDRect rect, Axis axis) const; diff --git a/apps/shared/double_pair_store.h b/apps/shared/double_pair_store.h index 9c8859612..f83e88812 100644 --- a/apps/shared/double_pair_store.h +++ b/apps/shared/double_pair_store.h @@ -55,10 +55,12 @@ public: // Colors static KDColor colorOfSeriesAtIndex(int i) { assert(i >= 0 && i < k_numberOfSeries); + assert(i < Palette::numberOfDataColors()); return Palette::DataColor[i]; } static KDColor colorLightOfSeriesAtIndex(int i) { assert(i >= 0 && i < k_numberOfSeries); + assert(i < Palette::numberOfLightDataColors()); return Palette::DataColorLight[i]; } protected: diff --git a/apps/shared/simple_interactive_curve_view_controller.cpp b/apps/shared/simple_interactive_curve_view_controller.cpp index 46a4673b1..374ce26a9 100644 --- a/apps/shared/simple_interactive_curve_view_controller.cpp +++ b/apps/shared/simple_interactive_curve_view_controller.cpp @@ -6,16 +6,6 @@ using namespace Poincare; namespace Shared { -SimpleInteractiveCurveViewController::SimpleInteractiveCurveViewController(Responder * parentResponder, CurveViewCursor * cursor) : - ViewController(parentResponder), - m_cursor(cursor) -{ -} - -View * SimpleInteractiveCurveViewController::view() { - return curveView(); -} - bool SimpleInteractiveCurveViewController::handleEvent(Ion::Events::Event event) { if (event == Ion::Events::Plus || event == Ion::Events::Minus) { return handleZoom(event); @@ -36,13 +26,6 @@ bool SimpleInteractiveCurveViewController::textFieldDidReceiveEvent(TextField * return TextFieldDelegate::textFieldDidReceiveEvent(textField, event); } -bool SimpleInteractiveCurveViewController::handleZoom(Ion::Events::Event event) { - float ratio = event == Ion::Events::Plus ? 2.0f/3.0f : 3.0f/2.0f; - interactiveCurveViewRange()->zoom(ratio, m_cursor->x(), m_cursor->y()); - curveView()->reload(); - return true; -} - bool SimpleInteractiveCurveViewController::handleLeftRightEvent(Ion::Events::Event event) { int direction = event == Ion::Events::Left ? -1 : 1; if (moveCursorHorizontally(direction, Ion::Events::isLongRepetition())) { diff --git a/apps/shared/simple_interactive_curve_view_controller.h b/apps/shared/simple_interactive_curve_view_controller.h index fd30d2998..7827e649b 100644 --- a/apps/shared/simple_interactive_curve_view_controller.h +++ b/apps/shared/simple_interactive_curve_view_controller.h @@ -1,21 +1,17 @@ #ifndef SHARED_SIMPLE_INTERACTIVE_CURVE_VIEW_CONTROLLER_H #define SHARED_SIMPLE_INTERACTIVE_CURVE_VIEW_CONTROLLER_H -#include #include "text_field_delegate.h" -#include "interactive_curve_view_range.h" -#include "curve_view_cursor.h" -#include "curve_view.h" +#include "zoom_curve_view_controller.h" namespace Shared { /* SimpleInteractiveCurveViewController is a View controller with a cursor that * can handles zoom in/out and left and right events. */ -class SimpleInteractiveCurveViewController : public ViewController, public TextFieldDelegate { +class SimpleInteractiveCurveViewController : public ZoomCurveViewController, public TextFieldDelegate { public: - SimpleInteractiveCurveViewController(Responder * parentResponder, CurveViewCursor * cursor); - View * view() override; + SimpleInteractiveCurveViewController(Responder * parentResponder, CurveViewCursor * cursor) : ZoomCurveViewController(parentResponder), m_cursor(cursor) {} bool handleEvent(Ion::Events::Event event) override; bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override; protected: @@ -24,15 +20,15 @@ protected: virtual float cursorTopMarginRatio() { return 0.07f; } // (cursorHeight/2)/(graphViewHeight-1) virtual float cursorBottomMarginRatio() = 0; // (cursorHeight/2+bannerHeight)/(graphViewHeight-1) constexpr static float k_numberOfCursorStepsInGradUnit = 5.0f; - virtual bool handleZoom(Ion::Events::Event event); + // ZoomCurveViewController + float xFocus() override { return m_cursor->x(); } + float yFocus() override { return m_cursor->y(); } virtual bool handleLeftRightEvent(Ion::Events::Event event); virtual void reloadBannerView() = 0; /* the result of moveCursorVertically/Horizontally means: * false -> the cursor cannot move in this direction * true -> the cursor moved */ virtual bool moveCursorHorizontally(int direction, bool fast = false) { return false; } - virtual InteractiveCurveViewRange * interactiveCurveViewRange() = 0; - virtual CurveView * curveView() = 0; virtual bool handleEnter() = 0; CurveViewCursor * m_cursor; }; diff --git a/apps/shared/zoom_and_pan_curve_view_controller.cpp b/apps/shared/zoom_and_pan_curve_view_controller.cpp new file mode 100644 index 000000000..eff23f303 --- /dev/null +++ b/apps/shared/zoom_and_pan_curve_view_controller.cpp @@ -0,0 +1,35 @@ +#include "zoom_and_pan_curve_view_controller.h" +#include +#include + +using namespace Poincare; + +namespace Shared { + +bool ZoomAndPanCurveViewController::handleEvent(Ion::Events::Event event) { + if (event == Ion::Events::Left || event == Ion::Events::Right || event == Ion::Events::Up || event == Ion::Events::Down) { + return handlePan(event); + } + return ZoomCurveViewController::handleEvent(event); +} + +bool ZoomAndPanCurveViewController::handlePan(Ion::Events::Event event) { + float xMove = 0.0f; + float yMove = 0.0f; + if (event == Ion::Events::Up) { + yMove = interactiveCurveViewRange()->yGridUnit(); + } else if (event == Ion::Events::Down) { + yMove = -interactiveCurveViewRange()->yGridUnit(); + } else if (event == Ion::Events::Left) { + xMove = -interactiveCurveViewRange()->xGridUnit(); + } else { + assert(event == Ion::Events::Right); + xMove = interactiveCurveViewRange()->xGridUnit(); + } + interactiveCurveViewRange()->panWithVector(xMove, yMove); + curveView()->reload(); + return true; +} + +} + diff --git a/apps/shared/zoom_and_pan_curve_view_controller.h b/apps/shared/zoom_and_pan_curve_view_controller.h new file mode 100644 index 000000000..e17f9ddf5 --- /dev/null +++ b/apps/shared/zoom_and_pan_curve_view_controller.h @@ -0,0 +1,23 @@ +#ifndef SHARED_ZOOM_AND_PAN_CURVE_VIEW_CONTROLLER_H +#define SHARED_ZOOM_AND_PAN_CURVE_VIEW_CONTROLLER_H + +#include "zoom_curve_view_controller.h" + +namespace Shared { + +/* ZoomAndPanCurveViewController is a View controller with a cursor that can + * handles zoom in/out and directional pan events. */ + +class ZoomAndPanCurveViewController : public ZoomCurveViewController { +public: + ZoomAndPanCurveViewController(Responder * parentResponder) : ZoomCurveViewController(parentResponder) {} + bool handleEvent(Ion::Events::Event event) override; +protected: + virtual bool handlePan(Ion::Events::Event event); + float xFocus() override { return interactiveCurveViewRange()->xCenter(); } + float yFocus() override { return interactiveCurveViewRange()->yCenter(); } +}; + +} + +#endif diff --git a/apps/shared/zoom_curve_view_controller.cpp b/apps/shared/zoom_curve_view_controller.cpp new file mode 100644 index 000000000..45c25078d --- /dev/null +++ b/apps/shared/zoom_curve_view_controller.cpp @@ -0,0 +1,23 @@ +#include "zoom_curve_view_controller.h" +#include +#include + +using namespace Poincare; + +namespace Shared { + +bool ZoomCurveViewController::handleEvent(Ion::Events::Event event) { + if (event == Ion::Events::Plus || event == Ion::Events::Minus) { + return handleZoom(event); + } + return false; +} + +bool ZoomCurveViewController::handleZoom(Ion::Events::Event event) { + float ratio = event == Ion::Events::Plus ? 2.0f/3.0f : 3.0f/2.0f; + interactiveCurveViewRange()->zoom(ratio, xFocus(), yFocus()); + curveView()->reload(); + return true; +} + +} diff --git a/apps/shared/zoom_curve_view_controller.h b/apps/shared/zoom_curve_view_controller.h new file mode 100644 index 000000000..97698ae76 --- /dev/null +++ b/apps/shared/zoom_curve_view_controller.h @@ -0,0 +1,30 @@ +#ifndef SHARED_ZOOM_CURVE_VIEW_CONTROLLER_H +#define SHARED_ZOOM_CURVE_VIEW_CONTROLLER_H + +#include +#include "interactive_curve_view_range.h" +#include "curve_view_cursor.h" +#include "curve_view.h" + +namespace Shared { + +/* ZoomCurveViewController is a View controller with a cursor that can handles + * zoom in/out events. */ + +class ZoomCurveViewController : public ViewController { +public: + ZoomCurveViewController(Responder * parentResponder) : ViewController(parentResponder) {} + View * view() override { return curveView(); } + bool handleEvent(Ion::Events::Event event) override; +protected: + virtual bool handleZoom(Ion::Events::Event event); + virtual InteractiveCurveViewRange * interactiveCurveViewRange() = 0; + virtual CurveView * curveView() = 0; + virtual float xFocus() = 0; + virtual float yFocus() = 0; + CurveViewCursor * m_cursor; +}; + +} + +#endif diff --git a/apps/shared/zoom_parameter_controller.cpp b/apps/shared/zoom_parameter_controller.cpp index 7b65485bd..c182147e5 100644 --- a/apps/shared/zoom_parameter_controller.cpp +++ b/apps/shared/zoom_parameter_controller.cpp @@ -5,7 +5,7 @@ namespace Shared { ZoomParameterController::ZoomParameterController(Responder * parentResponder, InteractiveCurveViewRange * interactiveRange, CurveView * curveView) : - ViewController(parentResponder), + ZoomAndPanCurveViewController(parentResponder), m_contentView(curveView), m_interactiveRange(interactiveRange) { @@ -15,45 +15,6 @@ const char * ZoomParameterController::title() { return I18n::translate(I18n::Message::Zoom); } -View * ZoomParameterController::view() { - return &m_contentView; -} - -bool ZoomParameterController::handleEvent(Ion::Events::Event event) { - if (event == Ion::Events::Plus) { - m_interactiveRange->zoom(2.0f/3.0f, m_interactiveRange->xCenter(), m_interactiveRange->yCenter()); - m_contentView.curveView()->reload(); - return true; - } - if (event == Ion::Events::Minus) { - m_interactiveRange->zoom(3.0f/2.0f, m_interactiveRange->xCenter(), m_interactiveRange->yCenter()); - m_contentView.curveView()->reload(); - return true; - } - if (event == Ion::Events::Up) { - m_interactiveRange->panWithVector(0.0f, m_interactiveRange->yGridUnit()); - m_contentView.curveView()->reload(); - return true; - } - if (event == Ion::Events::Down) { - m_interactiveRange->panWithVector(0.0f, -m_interactiveRange->yGridUnit()); - m_contentView.curveView()->reload(); - return true; - } - if (event == Ion::Events::Left) { - m_interactiveRange->panWithVector(-m_interactiveRange->xGridUnit(), 0.0f); - m_contentView.curveView()->reload(); - return true; - } - if (event == Ion::Events::Right) { - m_interactiveRange->panWithVector(m_interactiveRange->xGridUnit(), 0.0f); - m_contentView.curveView()->reload(); - return true; - } - - return false; -} - void ZoomParameterController::viewWillAppear() { ViewController::viewWillAppear(); m_contentView.curveView()->setOkView(nullptr); diff --git a/apps/shared/zoom_parameter_controller.h b/apps/shared/zoom_parameter_controller.h index 3f8758afa..293f166cf 100644 --- a/apps/shared/zoom_parameter_controller.h +++ b/apps/shared/zoom_parameter_controller.h @@ -1,25 +1,23 @@ #ifndef SHARED_ZOOM_PARAMETER_CONTROLLER_H #define SHARED_ZOOM_PARAMETER_CONTROLLER_H -#include -#include "interactive_curve_view_range.h" -#include "curve_view.h" +#include "zoom_and_pan_curve_view_controller.h" #include namespace Shared { -class ZoomParameterController : public ViewController { +class ZoomParameterController : public ZoomAndPanCurveViewController { public: ZoomParameterController(Responder * parentResponder, InteractiveCurveViewRange * interactiveCurveViewRange, CurveView * curveView); const char * title() override; - View * view() override; - bool handleEvent(Ion::Events::Event event) override; + View * view() override { return &m_contentView; } void viewWillAppear() override; void viewDidDisappear() override; void didBecomeFirstResponder() override; TELEMETRY_ID("Zoom"); private: constexpr static KDCoordinate k_standardViewHeight = 175; + class ContentView : public View { public: constexpr static KDCoordinate k_legendHeight = 30; @@ -46,7 +44,13 @@ private: CurveView * m_curveView; LegendView m_legendView; }; + void adaptCurveRange(bool viewWillAppear); + + // ZoomAndPanCurveViewController + InteractiveCurveViewRange * interactiveCurveViewRange() override { return m_interactiveRange; } + CurveView * curveView() override { return m_contentView.curveView(); } + ContentView m_contentView; InteractiveCurveViewRange * m_interactiveRange; }; diff --git a/apps/statistics/box_view.cpp b/apps/statistics/box_view.cpp index d56e83631..b2e2a101d 100644 --- a/apps/statistics/box_view.cpp +++ b/apps/statistics/box_view.cpp @@ -73,8 +73,8 @@ void BoxView::drawRect(KDContext * ctx, KDRect rect) const { // Draw the horizontal lines linking the box to the extreme bounds KDColor horizontalColor = isMainViewSelected() ? m_selectedHistogramColor : Palette::SecondaryText; float segmentOrd = (lowBound + upBound)/ 2.0f; - drawSegment(ctx, rect, Axis::Horizontal, segmentOrd, minVal, firstQuart, horizontalColor); - drawSegment(ctx, rect, Axis::Horizontal, segmentOrd, thirdQuart, maxVal, horizontalColor); + drawHorizontalOrVerticalSegment(ctx, rect, Axis::Horizontal, segmentOrd, minVal, firstQuart, horizontalColor); + drawHorizontalOrVerticalSegment(ctx, rect, Axis::Horizontal, segmentOrd, thirdQuart, maxVal, horizontalColor); double calculations[5] = {minVal, firstQuart, m_store->median(m_series), thirdQuart, maxVal}; /* We then draw all the vertical lines of the box and then recolor the @@ -83,10 +83,10 @@ void BoxView::drawRect(KDContext * ctx, KDRect rect) const { * lines. This solution could hide the highlighted line by coloring the next * quantile if it has the same value. */ for (int k = 0; k < 5; k++) { - drawSegment(ctx, rect, Axis::Vertical, calculations[k], lowBound, upBound, Palette::StatisticsBoxVerticalLine, k_quantileBarWidth); + drawHorizontalOrVerticalSegment(ctx, rect, Axis::Vertical, calculations[k], lowBound, upBound, Palette::StatisticsBoxVerticalLine, k_quantileBarWidth); } if (isMainViewSelected()) { - drawSegment(ctx, rect, Axis::Vertical, calculations[(int)*m_selectedQuantile], lowBound, upBound, Palette::StatisticsBox, k_quantileBarWidth); + drawHorizontalOrVerticalSegment(ctx, rect, Axis::Vertical, calculations[(int)*m_selectedQuantile], lowBound, upBound, Palette::StatisticsBox, k_quantileBarWidth); } } diff --git a/escher/include/escher/palette.h b/escher/include/escher/palette.h new file mode 100644 index 000000000..0ebc550a7 --- /dev/null +++ b/escher/include/escher/palette.h @@ -0,0 +1,44 @@ +#ifndef ESCHER_PALETTE_H +#define ESCHER_PALETTE_H + +#include +#include + +class Palette { +public: + constexpr static KDColor YellowDark = KDColor::RGB24(0xffb734); + constexpr static KDColor YellowLight = KDColor::RGB24(0xffcc7b); + constexpr static KDColor PurpleBright = KDColor::RGB24(0x656975); + constexpr static KDColor PurpleDark = KDColor::RGB24(0x414147); + constexpr static KDColor GreyWhite = KDColor::RGB24(0xf5f5f5); + constexpr static KDColor GreyBright = KDColor::RGB24(0xececec); + constexpr static KDColor GreyMiddle = KDColor::RGB24(0xd9d9d9); + constexpr static KDColor GreyDark = KDColor::RGB24(0xa7a7a7); + constexpr static KDColor GreyVeryDark = KDColor::RGB24(0x8c8c8c); + constexpr static KDColor Select = KDColor::RGB24(0xd4d7e0); + constexpr static KDColor SelectDark = KDColor::RGB24(0xb0b8d8); + constexpr static KDColor WallScreen = KDColor::RGB24(0xf7f9fa); + constexpr static KDColor WallScreenDark = KDColor::RGB24(0xe0e6ed); + constexpr static KDColor SubTab = KDColor::RGB24(0xb8bbc5); + constexpr static KDColor LowBattery = KDColor::RGB24(0xf30211); + constexpr static KDColor Red = KDColor::RGB24(0xff000c); + constexpr static KDColor RedLight = KDColor::RGB24(0xfe6363); + constexpr static KDColor Magenta = KDColor::RGB24(0xff0588); + constexpr static KDColor Turquoise = KDColor::RGB24(0x60c1ec); + constexpr static KDColor Pink = KDColor::RGB24(0xffabb6); + constexpr static KDColor Blue = KDColor::RGB24(0x5075f2); + constexpr static KDColor BlueLight = KDColor::RGB24(0x718fee); + constexpr static KDColor Orange = KDColor::RGB24(0xfe871f); + constexpr static KDColor Green = KDColor::RGB24(0x50c102); + constexpr static KDColor GreenLight = KDColor::RGB24(0x52db8f); + constexpr static KDColor Brown = KDColor::RGB24(0x8d7350); + constexpr static KDColor Purple = KDColor::RGB24(0x6e2d79); + constexpr static KDColor DataColor[] = {Red, Blue, Green, YellowDark, Magenta, Turquoise, Pink, Orange}; + constexpr static KDColor DataColorLight[] = {RedLight, BlueLight, GreenLight, YellowLight}; + + constexpr static size_t numberOfDataColors() { return sizeof(DataColor)/sizeof(KDColor); } + constexpr static size_t numberOfLightDataColors() { return sizeof(DataColorLight)/sizeof(KDColor); } + static KDColor nextDataColor(int * colorIndex); +}; + +#endif diff --git a/escher/include/escher/stack_view_controller.h b/escher/include/escher/stack_view_controller.h index 1ff89e7a6..e3868ddf1 100644 --- a/escher/include/escher/stack_view_controller.h +++ b/escher/include/escher/stack_view_controller.h @@ -18,6 +18,7 @@ public: int depth(); View * view() override; + ViewController * topViewController(); const char * title() override; bool handleEvent(Ion::Events::Event event) override; void didBecomeFirstResponder() override; diff --git a/escher/src/palette.cpp b/escher/src/palette.cpp index 843ad3705..fcf9f5a55 100644 --- a/escher/src/palette.cpp +++ b/escher/src/palette.cpp @@ -1,4 +1,5 @@ #include +#include constexpr KDColor Palette::PrimaryText; constexpr KDColor Palette::SecondaryText; // =GREYDARK @@ -142,3 +143,11 @@ constexpr KDColor Palette::AtomReactiveNonmetal; constexpr KDColor Palette::AtomNobleGas; constexpr KDColor Palette::AtomTableLines; constexpr KDColor Palette::AtomColor[]; + +KDColor Palette::nextDataColor(int * colorIndex) { + size_t nbOfColors = numberOfDataColors(); + assert(*colorIndex < nbOfColors); + KDColor c = DataColor[*colorIndex]; + *colorIndex = (*colorIndex + 1) % nbOfColors; + return c; +} diff --git a/escher/src/stack_view_controller.cpp b/escher/src/stack_view_controller.cpp index 8aa337d17..2b88c2288 100644 --- a/escher/src/stack_view_controller.cpp +++ b/escher/src/stack_view_controller.cpp @@ -92,6 +92,13 @@ const char * StackViewController::title() { return vc->title(); } +ViewController * StackViewController::topViewController() { + if (m_numberOfChildren < 1) { + return nullptr; + } + return m_childrenFrame[m_numberOfChildren-1].viewController(); +} + void StackViewController::push(ViewController * vc, KDColor textColor, KDColor backgroundColor, KDColor separatorColor) { Frame frame = Frame(vc, textColor, backgroundColor, separatorColor); /* Add the frame to the model */ @@ -112,7 +119,7 @@ void StackViewController::push(ViewController * vc, KDColor textColor, KDColor b void StackViewController::pop() { assert(m_numberOfChildren > 0); - ViewController * vc = m_childrenFrame[m_numberOfChildren-1].viewController(); + ViewController * vc = topViewController(); if (vc->title() != nullptr && vc->displayParameter() != ViewController::DisplayParameter::DoNotShowOwnTitle) { m_view.popStack(); } @@ -131,7 +138,7 @@ void StackViewController::pushModel(Frame frame) { } void StackViewController::setupActiveViewController() { - ViewController * vc = m_childrenFrame[m_numberOfChildren-1].viewController(); + ViewController * vc = topViewController(); vc->setParentResponder(this); m_view.shouldDisplayStackHearders(vc->displayParameter() != ViewController::DisplayParameter::WantsMaximumSpace); m_view.setContentView(vc->view()); @@ -141,7 +148,7 @@ void StackViewController::setupActiveViewController() { } void StackViewController::didBecomeFirstResponder() { - ViewController * vc = m_childrenFrame[m_numberOfChildren-1].viewController(); + ViewController * vc = topViewController(); Container::activeApp()->setFirstResponder(vc); } @@ -170,7 +177,7 @@ void StackViewController::viewWillAppear() { } } /* Load the visible controller view */ - ViewController * vc = m_childrenFrame[m_numberOfChildren-1].viewController(); + ViewController * vc = topViewController(); if (m_numberOfChildren > 0 && vc) { m_view.setContentView(vc->view()); m_view.shouldDisplayStackHearders(vc->displayParameter() != ViewController::DisplayParameter::WantsMaximumSpace); @@ -180,7 +187,7 @@ void StackViewController::viewWillAppear() { } void StackViewController::viewDidDisappear() { - ViewController * vc = m_childrenFrame[m_numberOfChildren-1].viewController(); + ViewController * vc = topViewController(); if (m_numberOfChildren > 0 && vc) { vc->viewDidDisappear(); } diff --git a/python/Makefile b/python/Makefile index c8d8fa8df..cb5d5a015 100644 --- a/python/Makefile +++ b/python/Makefile @@ -134,6 +134,13 @@ port_src += $(addprefix python/port/,\ mod/ion/modion_table.cpp \ mod/kandinsky/modkandinsky.cpp \ mod/kandinsky/modkandinsky_table.c \ + mod/matplotlib/modmatplotlib.cpp \ + mod/matplotlib/modmatplotlib_table.c \ + mod/matplotlib/pyplot/modpyplot.cpp \ + mod/matplotlib/pyplot/modpyplot_table.c \ + mod/matplotlib/pyplot/plot_controller.cpp \ + mod/matplotlib/pyplot/plot_store.cpp \ + mod/matplotlib/pyplot/plot_view.cpp \ mod/time/modtime.c \ mod/time/modtime_table.c \ mod/turtle/modturtle.cpp \ diff --git a/python/port/genhdr/qstrdefs.in.h b/python/port/genhdr/qstrdefs.in.h index 54738c5da..5e46e758d 100644 --- a/python/port/genhdr/qstrdefs.in.h +++ b/python/port/genhdr/qstrdefs.in.h @@ -130,6 +130,21 @@ Q(EE) Q(Ans) Q(EXE) +// Matplotlib QSTRs +Q(arrow) +Q(axis) +Q(bar) +Q(grid) +Q(grid) +Q(hist) +Q(plot) +Q(matplotlib) +Q(matplotlib.pyplot) +Q(pyplot) +Q(scatter) +Q(show) +Q(text) + // Turtle QSTRs Q(turtle) Q(forward) diff --git a/python/port/mod/matplotlib/modmatplotlib.cpp b/python/port/mod/matplotlib/modmatplotlib.cpp new file mode 100644 index 000000000..7459c28c3 --- /dev/null +++ b/python/port/mod/matplotlib/modmatplotlib.cpp @@ -0,0 +1,13 @@ +extern "C" { +#include "modmatplotlib.h" +#include "pyplot/modpyplot.h" +} +#include + + +// Internal functions + +mp_obj_t modmatplotlib___init__() { + modpyplot___init__(); + return mp_const_none; +} diff --git a/python/port/mod/matplotlib/modmatplotlib.h b/python/port/mod/matplotlib/modmatplotlib.h new file mode 100644 index 000000000..2f146e3f3 --- /dev/null +++ b/python/port/mod/matplotlib/modmatplotlib.h @@ -0,0 +1,3 @@ +#include + +mp_obj_t modmatplotlib___init__(); diff --git a/python/port/mod/matplotlib/modmatplotlib_table.c b/python/port/mod/matplotlib/modmatplotlib_table.c new file mode 100644 index 000000000..0178bd5e1 --- /dev/null +++ b/python/port/mod/matplotlib/modmatplotlib_table.c @@ -0,0 +1,18 @@ +#include "modmatplotlib.h" + +extern const mp_obj_module_t modpyplot_module; + +STATIC MP_DEFINE_CONST_FUN_OBJ_0(modmatplotlib___init___obj, modmatplotlib___init__); + +STATIC const mp_rom_map_elem_t modmatplotlib_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_matplotlib) }, + { MP_ROM_QSTR(MP_QSTR___init__), MP_ROM_PTR(&modmatplotlib___init___obj) }, + { MP_ROM_QSTR(MP_QSTR_pyplot), MP_ROM_PTR(&modpyplot_module) } +}; + +STATIC MP_DEFINE_CONST_DICT(modmatplotlib_module_globals, modmatplotlib_module_globals_table); + +const mp_obj_module_t modmatplotlib_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&modmatplotlib_module_globals, +}; diff --git a/python/port/mod/matplotlib/modpyplot_table.c b/python/port/mod/matplotlib/modpyplot_table.c new file mode 100644 index 000000000..c19aa128f --- /dev/null +++ b/python/port/mod/matplotlib/modpyplot_table.c @@ -0,0 +1,33 @@ +#include "modpyplot.h" + +STATIC MP_DEFINE_CONST_FUN_OBJ_0(modpyplot___init___obj, modpyplot___init__); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modpyplot_arrow_obj, 4, 4, modpyplot_arrow); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modpyplot_axis_obj, 0, 1, modpyplot_axis); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modpyplot_bar_obj, 2, 4, modpyplot_bar); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modpyplot_grid_obj, 0, 1, modpyplot_grid); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modpyplot_hist_obj, 1, 2, modpyplot_hist); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modpyplot_plot_obj, 1, 2, modpyplot_plot); +STATIC MP_DEFINE_CONST_FUN_OBJ_2(modpyplot_scatter_obj, modpyplot_scatter); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(modpyplot_show_obj, modpyplot_show); +STATIC MP_DEFINE_CONST_FUN_OBJ_3(modpyplot_text_obj, modpyplot_text); + +STATIC const mp_rom_map_elem_t modpyplot_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_matplotlib_dot_pyplot) }, + { MP_ROM_QSTR(MP_QSTR___init__), MP_ROM_PTR(&modpyplot___init___obj) }, + { MP_ROM_QSTR(MP_QSTR_arrow), MP_ROM_PTR(&modpyplot_arrow_obj) }, + { MP_ROM_QSTR(MP_QSTR_axis), MP_ROM_PTR(&modpyplot_axis_obj) }, + { MP_ROM_QSTR(MP_QSTR_bar), MP_ROM_PTR(&modpyplot_bar_obj) }, + { MP_ROM_QSTR(MP_QSTR_grid), MP_ROM_PTR(&modpyplot_grid_obj) }, + { MP_ROM_QSTR(MP_QSTR_hist), MP_ROM_PTR(&modpyplot_hist_obj) }, + { MP_ROM_QSTR(MP_QSTR_plot), MP_ROM_PTR(&modpyplot_plot_obj) }, + { MP_ROM_QSTR(MP_QSTR_scatter), MP_ROM_PTR(&modpyplot_scatter_obj) }, + { MP_ROM_QSTR(MP_QSTR_show), MP_ROM_PTR(&modpyplot_show_obj) }, + { MP_ROM_QSTR(MP_QSTR_text), MP_ROM_PTR(&modpyplot_text_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(modpyplot_module_globals, modpyplot_module_globals_table); + +const mp_obj_module_t modpyplot_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&modpyplot_module_globals, +}; diff --git a/python/port/mod/matplotlib/pyplot/modpyplot.cpp b/python/port/mod/matplotlib/pyplot/modpyplot.cpp new file mode 100644 index 000000000..0b1e5a808 --- /dev/null +++ b/python/port/mod/matplotlib/pyplot/modpyplot.cpp @@ -0,0 +1,362 @@ +extern "C" { +#include "modpyplot.h" +} +#include +#include +#include "port.h" +#include "plot_controller.h" + +Matplotlib::PlotStore * sPlotStore = nullptr; +Matplotlib::PlotController * sPlotController = nullptr; +static int paletteIndex = 0; + +// Private helper + +// Method to populate items with a scalar or an array argument + +static size_t extractArgument(mp_obj_t arg, mp_obj_t ** items) { + size_t itemLength; + if (mp_obj_is_type(arg, &mp_type_tuple) || mp_obj_is_type(arg, &mp_type_list)) { + mp_obj_get_array(arg, &itemLength, items); + } else { + itemLength = 1; + *items = m_new(mp_obj_t, 1); + (*items)[0] = arg; + } + return itemLength; +} + +// Extract two scalar or array arguments and check for their strickly equal dimension + +static size_t extractArgumentsAndCheckEqualSize(mp_obj_t x, mp_obj_t y, mp_obj_t ** xItems, mp_obj_t ** yItems) { + size_t xLength = extractArgument(x, xItems); + size_t yLength = extractArgument(y, yItems); + if (xLength != yLength) { + mp_raise_ValueError("x and y must be the same size"); + } + return xLength; +} + +/* Extract one scalar or array arguments and check that it is either: + * - of size 1 + * - of the required size +*/ + +size_t extractArgumentAndValidateSize(mp_obj_t arg, size_t requiredlength, mp_obj_t ** items) { + size_t itemLength = extractArgument(arg, items); + if (itemLength > 1 && requiredlength > 1 && itemLength != requiredlength) { + mp_raise_ValueError("shape mismatch"); + } + return itemLength; +} + +// Internal functions + +mp_obj_t modpyplot___init__() { + static Matplotlib::PlotStore plotStore; + static Matplotlib::PlotController plotController(&plotStore); + sPlotStore = &plotStore; + sPlotController = &plotController; + sPlotStore->flush(); + paletteIndex = 0; + return mp_const_none; +} + +void modpyplot_gc_collect() { + if (sPlotStore == nullptr) { + return; + } + MicroPython::collectRootsAtAddress( + reinterpret_cast(sPlotStore), + sizeof(Matplotlib::PlotStore) + ); +} + +void modpyplot_flush_used_heap() { + if (sPlotStore) { + // Clean the store object + sPlotStore->flush(); + } +} + +/* arrow(x,y,dx,dy) + * x, y, dx, dy scalars + * */ + +mp_obj_t modpyplot_arrow(size_t n_args, const mp_obj_t *args) { + assert(n_args == 4); + assert(sPlotStore != nullptr); + + KDColor color = Palette::nextDataColor(&paletteIndex); + sPlotStore->addSegment(args[0], args[1], mp_obj_new_float(mp_obj_get_float(args[0])+mp_obj_get_float(args[2])), mp_obj_new_float(mp_obj_get_float(args[1])+mp_obj_get_float(args[3])), color, true); // TODO: use float_binary_op + + return mp_const_none; +} + +/* axis(arg) + * - arg = "on", "off", "auto" + * - arg = True, False + * - arg = [xmin, xmax, ymin, ymax], (xmin, xmax, ymin, ymax) + * Returns : (xmin, xmax, ymin, ymax) : float */ + +mp_obj_t modpyplot_axis(size_t n_args, const mp_obj_t *args) { + assert(sPlotStore != nullptr); + + if (n_args == 1) { + mp_obj_t arg = args[0]; + if (mp_obj_is_str(arg)) { + if (mp_obj_str_equal(arg, mp_obj_new_str("on", 2))) { + sPlotStore->setAxesRequested(true); + } else if (mp_obj_str_equal(arg, mp_obj_new_str("off", 3))) { + sPlotStore->setAxesRequested(false); + } else if (mp_obj_str_equal(arg, mp_obj_new_str("auto", 4))) { + sPlotStore->setAxesRequested(true); + sPlotStore->setAxesAuto(true); + } else { + mp_raise_ValueError("Unrecognized string given to axis; try 'on', 'off' or 'auto'"); + } +#warning Use mp_obj_is_bool when upgrading uPy + } else if (mp_obj_is_type(arg, &mp_type_bool)) { + sPlotStore->setAxesRequested(mp_obj_is_true(arg)); + } else if (mp_obj_is_type(arg, &mp_type_tuple) || mp_obj_is_type(arg, &mp_type_list)) { + mp_obj_t * items; + mp_obj_get_array_fixed_n(arg, 4, &items); + sPlotStore->setXMin(mp_obj_get_float(items[0])); + sPlotStore->setXMax(mp_obj_get_float(items[1])); + sPlotStore->setYMin(mp_obj_get_float(items[2])); + sPlotStore->setYMax(mp_obj_get_float(items[3])); + sPlotStore->setAxesAuto(false); + } else { + mp_raise_TypeError("the first argument to axis() must be an iterable of the form [xmin, xmax, ymin, ymax]"); + } + } + + // Build the return value + mp_obj_t coords[4]; + coords[0] = mp_obj_new_float(sPlotStore->xMin()); + coords[1] = mp_obj_new_float(sPlotStore->xMax()); + coords[2] = mp_obj_new_float(sPlotStore->yMin()); + coords[3] = mp_obj_new_float(sPlotStore->yMax()); + return mp_obj_new_tuple(4, coords); +} + +/* bar(x, height, width, bottom) + * 'x', 'height', 'width' and 'bottom' can either be a scalar or an array/tuple of + * scalar. + * 'width' default value is 0.8 + * 'bottom' default value is None + * */ + +// TODO: accept keyword args? + +mp_obj_t modpyplot_bar(size_t n_args, const mp_obj_t *args) { + assert(sPlotStore != nullptr); + + mp_obj_t * xItems; + mp_obj_t * hItems; + mp_obj_t * wItems; + mp_obj_t * bItems; + + // x arg + size_t xLength = extractArgument(args[0], &xItems); + + // height arg + size_t hLength = extractArgumentAndValidateSize(args[1], xLength, &hItems); + + // width arg + size_t wLength = 1; + if (n_args >= 3) { + wLength = extractArgumentAndValidateSize(args[2], xLength, &wItems); + } else { + wItems = m_new(mp_obj_t, 1); + wItems[0] = mp_obj_new_float(0.8f); + } + + // bottom arg + size_t bLength = 1; + if (n_args >= 4) { + bLength = extractArgumentAndValidateSize(args[3], xLength, &bItems); + } else { + bItems = m_new(mp_obj_t, 1); + bItems[0] = mp_obj_new_float(0.0f); + } + + KDColor color = Palette::nextDataColor(&paletteIndex); + for (size_t i=0; i 1 ? i : 0]); + mp_float_t iW = mp_obj_get_float(wItems[wLength > 1 ? i : 0]); + mp_float_t iB = mp_obj_get_float(bItems[bLength > 1 ? i : 0]); + mp_float_t iX = mp_obj_get_float(xItems[i])-iW/2.0; + mp_float_t iYStart = iH < 0.0 ? iB : iB + iH; + mp_float_t iYEnd = iH < 0.0 ? iB + iH : iB; + sPlotStore->addRect(mp_obj_new_float(iX), mp_obj_new_float(iX+iW), mp_obj_new_float(iYStart), mp_obj_new_float(iYEnd), color); // TODO: use float_binary_op? + } + return mp_const_none; +} + +mp_obj_t modpyplot_grid(size_t n_args, const mp_obj_t *args) { + assert(sPlotStore != nullptr); + + if (n_args == 0) { + // Toggle the grid visibility + sPlotStore->setGridRequested(!sPlotStore->gridRequested()); + } else { + sPlotStore->setGridRequested(mp_obj_is_true(args[0])); + } + return mp_const_none; +} + +/* hist(x, bins) + * 'x' array + * 'bins': (default value 10) + * - int (number of bins) + * - sequence of bins + * */ + +mp_obj_t modpyplot_hist(size_t n_args, const mp_obj_t *args) { + assert(sPlotStore != nullptr); + + // Sort data to easily get the minimal and maximal value and count bin sizes + mp_obj_t * xItems; + size_t xLength = extractArgument(args[0], &xItems); + if (xLength == 0) { + return mp_const_none; + } + mp_obj_t xList = mp_obj_new_list(xLength, xItems); + mp_obj_list_sort(1, &xList, (mp_map_t*)&mp_const_empty_map); + mp_obj_list_get(xList, &xLength, &xItems); + assert(xLength > 0); + mp_float_t min = mp_obj_get_float(xItems[0]); + mp_float_t max = mp_obj_get_float(xItems[xLength - 1]); + + mp_obj_t * edgeItems; + size_t nBins; + // bin arg + if (n_args >= 2 && (mp_obj_is_type(args[1], &mp_type_tuple) || mp_obj_is_type(args[1], &mp_type_list))) { + size_t nEdges; + mp_obj_get_array(args[1], &nEdges, &edgeItems); + nBins = nEdges -1; + } else { + nBins = 10; + if (n_args >= 2) { + nBins = mp_obj_get_int(args[1]); + } + + mp_float_t binWidth = (max-min)/nBins; + // Create a array of bins + edgeItems = m_new(mp_obj_t, nBins + 1); + // Handle empty range case + if (max - min <= FLT_EPSILON) { + binWidth = 1.0; + nBins = 1; + } + + // Fill the bin edges list + for (int i = 0; i < nBins+1; i++) { + edgeItems[i] = mp_obj_new_float(min+i*binWidth); + } + } + + // Initialize bins list + mp_obj_t * binItems = m_new(mp_obj_t, nBins); + for (size_t i=0; i= lowerBound); + // Increment the bin count + binItems[binIndex] = MP_OBJ_NEW_SMALL_INT(MP_OBJ_SMALL_INT_VALUE(binItems[binIndex]) + 1); + xIndex++; + } + binIndex++; + } + + KDColor color = Palette::nextDataColor(&paletteIndex); + for (size_t i=0; iaddRect(edgeItems[i], edgeItems[i+1], binItems[i], mp_obj_new_float(0.0), color); + } + return mp_const_none; +} + +/* scatter(x, y) + * - x, y: list + * - x, y: scalar + * */ + +mp_obj_t modpyplot_scatter(mp_obj_t x, mp_obj_t y) { + assert(sPlotStore != nullptr); + + mp_obj_t * xItems, * yItems; + size_t length = extractArgumentsAndCheckEqualSize(x, y, &xItems, &yItems); + + KDColor color = Palette::nextDataColor(&paletteIndex); + for (size_t i=0; iaddDot(xItems[i], yItems[i], color); + } + + return mp_const_none; +} + +/* plot(x, y) plots the curve (x, y) + * plot(y) plots the curve x as index array ([0,1,2...],y) + * */ + +mp_obj_t modpyplot_plot(size_t n_args, const mp_obj_t *args) { + assert(sPlotStore != nullptr); + + mp_obj_t * xItems, * yItems; + size_t length; + if (n_args == 1) { + length = extractArgument(args[0], &yItems); + + // Create the default xItems: [0, 1, 2,...] + xItems = m_new(mp_obj_t, length); + for (int i = 0; i < length; i++) { + xItems[i] = mp_obj_new_float((float)i); + } + } else { + assert(n_args == 2); + length = extractArgumentsAndCheckEqualSize(args[0], args[1], &xItems, &yItems); + } + + KDColor color = Palette::nextDataColor(&paletteIndex); + for (int i=0; i<(int)length-1; i++) { + sPlotStore->addSegment(xItems[i], yItems[i], xItems[i+1], yItems[i+1], color, false); + } + + return mp_const_none; +} + +mp_obj_t modpyplot_text(mp_obj_t x, mp_obj_t y, mp_obj_t s) { + assert(sPlotStore != nullptr); + + // Input parameter validation + mp_obj_get_float(x); + mp_obj_get_float(y); + mp_obj_str_get_str(s); + + sPlotStore->addLabel(x, y, s); + + return mp_const_none; +} + +mp_obj_t modpyplot_show() { + if (sPlotStore->isEmpty()) { + return mp_const_none; + } + MicroPython::ExecutionEnvironment * env = MicroPython::ExecutionEnvironment::currentExecutionEnvironment(); + env->displayViewController(sPlotController); + return mp_const_none; +} diff --git a/python/port/mod/matplotlib/pyplot/modpyplot.h b/python/port/mod/matplotlib/pyplot/modpyplot.h new file mode 100644 index 000000000..8ab760f73 --- /dev/null +++ b/python/port/mod/matplotlib/pyplot/modpyplot.h @@ -0,0 +1,15 @@ +#include + +mp_obj_t modpyplot___init__(); +void modpyplot_gc_collect(); +void modpyplot_flush_used_heap(); + +mp_obj_t modpyplot_arrow(size_t n_args, const mp_obj_t *args); +mp_obj_t modpyplot_axis(size_t n_args, const mp_obj_t *args); +mp_obj_t modpyplot_bar(size_t n_args, const mp_obj_t *args); +mp_obj_t modpyplot_grid(size_t n_args, const mp_obj_t *args); +mp_obj_t modpyplot_hist(size_t n_args, const mp_obj_t *args); +mp_obj_t modpyplot_plot(size_t n_args, const mp_obj_t *args); +mp_obj_t modpyplot_scatter(mp_obj_t x, mp_obj_t y); +mp_obj_t modpyplot_text(mp_obj_t x, mp_obj_t y, mp_obj_t s); +mp_obj_t modpyplot_show(); diff --git a/python/port/mod/matplotlib/pyplot/modpyplot_table.c b/python/port/mod/matplotlib/pyplot/modpyplot_table.c new file mode 100644 index 000000000..1f4c3152b --- /dev/null +++ b/python/port/mod/matplotlib/pyplot/modpyplot_table.c @@ -0,0 +1,33 @@ +#include "modpyplot.h" + +STATIC MP_DEFINE_CONST_FUN_OBJ_0(modpyplot___init___obj, modpyplot___init__); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modpyplot_arrow_obj, 4, 4, modpyplot_arrow); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modpyplot_axis_obj, 0, 1, modpyplot_axis); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modpyplot_bar_obj, 2, 4, modpyplot_bar); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modpyplot_grid_obj, 0, 1, modpyplot_grid); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modpyplot_hist_obj, 1, 2, modpyplot_hist); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modpyplot_plot_obj, 1, 2, modpyplot_plot); +STATIC MP_DEFINE_CONST_FUN_OBJ_2(modpyplot_scatter_obj, modpyplot_scatter); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(modpyplot_show_obj, modpyplot_show); +STATIC MP_DEFINE_CONST_FUN_OBJ_3(modpyplot_text_obj, modpyplot_text); + +STATIC const mp_rom_map_elem_t modpyplot_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_pyplot) }, + { MP_ROM_QSTR(MP_QSTR___init__), MP_ROM_PTR(&modpyplot___init___obj) }, + { MP_ROM_QSTR(MP_QSTR_arrow), MP_ROM_PTR(&modpyplot_arrow_obj) }, + { MP_ROM_QSTR(MP_QSTR_axis), MP_ROM_PTR(&modpyplot_axis_obj) }, + { MP_ROM_QSTR(MP_QSTR_bar), MP_ROM_PTR(&modpyplot_bar_obj) }, + { MP_ROM_QSTR(MP_QSTR_grid), MP_ROM_PTR(&modpyplot_grid_obj) }, + { MP_ROM_QSTR(MP_QSTR_hist), MP_ROM_PTR(&modpyplot_hist_obj) }, + { MP_ROM_QSTR(MP_QSTR_plot), MP_ROM_PTR(&modpyplot_plot_obj) }, + { MP_ROM_QSTR(MP_QSTR_scatter), MP_ROM_PTR(&modpyplot_scatter_obj) }, + { MP_ROM_QSTR(MP_QSTR_show), MP_ROM_PTR(&modpyplot_show_obj) }, + { MP_ROM_QSTR(MP_QSTR_text), MP_ROM_PTR(&modpyplot_text_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(modpyplot_module_globals, modpyplot_module_globals_table); + +const mp_obj_module_t modpyplot_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&modpyplot_module_globals, +}; diff --git a/python/port/mod/matplotlib/pyplot/plot_controller.cpp b/python/port/mod/matplotlib/pyplot/plot_controller.cpp new file mode 100644 index 000000000..1beb952f5 --- /dev/null +++ b/python/port/mod/matplotlib/pyplot/plot_controller.cpp @@ -0,0 +1,14 @@ +#include "plot_controller.h" + +namespace Matplotlib { + +void PlotController::viewWillAppear() { + m_store->initRange(); + curveView()->reload(); +} + +void PlotController::viewDidDisappear() { + m_store->flush(); +} + +} diff --git a/python/port/mod/matplotlib/pyplot/plot_controller.h b/python/port/mod/matplotlib/pyplot/plot_controller.h new file mode 100644 index 000000000..ed35622dc --- /dev/null +++ b/python/port/mod/matplotlib/pyplot/plot_controller.h @@ -0,0 +1,27 @@ +#ifndef PYTHON_MATPLOTLIB_PLOT_CONTROLLER_H +#define PYTHON_MATPLOTLIB_PLOT_CONTROLLER_H + +#include +#include "plot_view.h" +#include "plot_store.h" + +namespace Matplotlib { + +class PlotController : public Shared::ZoomAndPanCurveViewController { +public: + PlotController(PlotStore * store) : Shared::ZoomAndPanCurveViewController(nullptr), m_store(store), m_view(m_store) {} + + void viewWillAppear() override; + void viewDidDisappear() override; + +protected: + Shared::CurveView * curveView() override { return &m_view; } + Shared::InteractiveCurveViewRange * interactiveCurveViewRange() override { return m_store; } +private: + PlotStore * m_store; + PlotView m_view; +}; + +} + +#endif diff --git a/python/port/mod/matplotlib/pyplot/plot_store.cpp b/python/port/mod/matplotlib/pyplot/plot_store.cpp new file mode 100644 index 000000000..ede1a3af4 --- /dev/null +++ b/python/port/mod/matplotlib/pyplot/plot_store.cpp @@ -0,0 +1,219 @@ +#include "plot_store.h" + +namespace Matplotlib { + +PlotStore::PlotStore() : Shared::InteractiveCurveViewRange(), + m_axesRequested(true), + m_axesAuto(true), + m_gridRequested(false) +{ + flush(); +} + +void PlotStore::flush() { + m_dots = mp_obj_new_list(0, nullptr); + m_segments = mp_obj_new_list(0, nullptr); + m_rects = mp_obj_new_list(0, nullptr); + m_labels = mp_obj_new_list(0, nullptr); + m_axesRequested = true; + m_axesAuto = true; + m_gridRequested = false; +} + +bool PlotStore::isEmpty() { + return MP_OBJ_SMALL_INT_VALUE(mp_obj_len(m_dots)) == 0 && MP_OBJ_SMALL_INT_VALUE(mp_obj_len(m_segments)) == 0 && MP_OBJ_SMALL_INT_VALUE(mp_obj_len(m_rects)) == 0 && MP_OBJ_SMALL_INT_VALUE(mp_obj_len(m_labels)) == 0; +} + +// Iterators + +template +PlotStore::ListIterator PlotStore::ListIterator::Begin(mp_obj_t list) { + ListIterator it; + mp_obj_list_get(list, &(it.m_numberOfTuples), &(it.m_tuples)); + return it; +} + +template +PlotStore::ListIterator PlotStore::ListIterator::End(mp_obj_t list) { + ListIterator it; + mp_obj_list_get(list, &(it.m_numberOfTuples), &(it.m_tuples)); + if (it.m_numberOfTuples > 0) { + it.m_tupleIndex = it.m_numberOfTuples; + } + return it; +} + +template +PlotStore::ListIterator & PlotStore::ListIterator::operator++() { + if (m_tupleIndex < m_numberOfTuples) { + m_tupleIndex++; + } + return *this; +} + +template +bool PlotStore::ListIterator::operator!=(const PlotStore::ListIterator & it) const { + return m_tupleIndex != it.m_tupleIndex; +}; + +template +T PlotStore::ListIterator::operator*() { + return T(m_tuples[m_tupleIndex]); +}; + +void checkFloatType(mp_obj_t * elements, size_t nbOfElements) { + for (int i = 0; i < nbOfElements; i++) { + // TODO: we don't take advantage of the fact that we extracted the value at the sametime... Maybe change the way things are done, build the c objects in addItem instead of allocating them on the python heap? Or use float array in python? + mp_float_t value; + if (!mp_obj_get_float_maybe(elements[i], &value)) { + mp_raise_TypeError("argument should be a number"); + } + } +} + +// Dot + +template class PlotStore::ListIterator; + +PlotStore::Dot::Dot(mp_obj_t tuple) { + mp_obj_t * elements; + mp_obj_get_array_fixed_n(tuple, 3, &elements); + m_x = mp_obj_get_float(elements[0]); + m_y = mp_obj_get_float(elements[1]); + m_color = KDColor::RGB16(mp_obj_get_int(elements[2])); +} + +void PlotStore::addDot(mp_obj_t x, mp_obj_t y, KDColor c) { + mp_obj_t color = mp_obj_new_int(c); + mp_obj_t items[3] = {x, y, color}; + checkFloatType(items, 2); + mp_obj_t tuple = mp_obj_new_tuple(3, items); + mp_obj_list_append(m_dots, tuple); +} + +// Segment + +template class PlotStore::ListIterator; + +PlotStore::Segment::Segment(mp_obj_t tuple) { + mp_obj_t * elements; + mp_obj_get_array_fixed_n(tuple, 6 , &elements); + m_xStart = mp_obj_get_float(elements[0]); + m_yStart = mp_obj_get_float(elements[1]); + m_xEnd = mp_obj_get_float(elements[2]); + m_yEnd = mp_obj_get_float(elements[3]); + m_color = KDColor::RGB16(mp_obj_get_int(elements[4])); + m_arrow = elements[5] == mp_const_true; +} + +void PlotStore::addSegment(mp_obj_t xStart, mp_obj_t yStart, mp_obj_t xEnd, mp_obj_t yEnd, KDColor c, bool arrowEdge) { + mp_obj_t color = mp_obj_new_int(c); + mp_obj_t items[6] = {xStart, yStart, xEnd, yEnd, color, arrowEdge ? mp_const_true : mp_const_false}; + checkFloatType(items, 4); + mp_obj_t tuple = mp_obj_new_tuple(6, items); + mp_obj_list_append(m_segments, tuple); +} + +// Rect + +template class PlotStore::ListIterator; + +PlotStore::Rect::Rect(mp_obj_t tuple) { + mp_obj_t * elements; + mp_obj_get_array_fixed_n(tuple, 5, &elements); + m_left = mp_obj_get_float(elements[0]); + m_right = mp_obj_get_float(elements[1]); + m_top = mp_obj_get_float(elements[2]); + m_bottom = mp_obj_get_float(elements[3]); + m_color = KDColor::RGB16(mp_obj_get_int(elements[4])); +} + +void PlotStore::addRect(mp_obj_t left, mp_obj_t right, mp_obj_t top, mp_obj_t bottom, KDColor c) { + mp_obj_t color = mp_obj_new_int(c); + mp_obj_t items[5] = {left, right, top, bottom, color}; + checkFloatType(items, 4); + mp_obj_t tuple = mp_obj_new_tuple(5, items); + mp_obj_list_append(m_rects, tuple); +} + +// Label + +template class PlotStore::ListIterator; + +PlotStore::Label::Label(mp_obj_t tuple) { + mp_obj_t * elements; + mp_obj_get_array_fixed_n(tuple, 3, &elements); + m_x = mp_obj_get_float(elements[0]); + m_y = mp_obj_get_float(elements[1]); + m_string = mp_obj_str_get_str(elements[2]); +} + +void PlotStore::addLabel(mp_obj_t x, mp_obj_t y, mp_obj_t string) { + mp_obj_t items[3] = {x, y, string}; + checkFloatType(items, 2); + if (!mp_obj_is_str(string)) { + mp_raise_TypeError("argument should be a string"); + } + mp_obj_t tuple = mp_obj_new_tuple(3, items); + mp_obj_list_append(m_labels, tuple); +} + +// Axes + +static inline float minFloat(float x, float y) { return x < y ? x : y; } +static inline float maxFloat(float x, float y) { return x > y ? x : y; } + +void updateRange(float * xMin, float * xMax, float * yMin, float * yMax, float x, float y) { + if (!std::isnan(x) && !std::isinf(x) && !std::isnan(y) && !std::isinf(y)) { + *xMin = minFloat(*xMin, x); + *xMax = maxFloat(*xMax, x); + *yMin = minFloat(*yMin, y); + *yMax = maxFloat(*yMax, y); + } +} + +void checkPositiveRangeAndAddMargin(float * min, float * max) { + if (*min > *max) { + *min = - Shared::Range1D::k_default; + *max = Shared::Range1D::k_default; + return; + } + // Add margins + float margin = (*max - *min)/10.0f; + if (margin < FLT_EPSILON) { + margin = 1.0f; + } + *min -= margin; + *max += margin; +} + +void PlotStore::initRange() { + if (m_axesAuto) { + float xMin = FLT_MAX; + float xMax = -FLT_MAX; + float yMin = FLT_MAX; + float yMax = -FLT_MAX; + for (PlotStore::Dot dot : dots()) { + updateRange(&xMin, &xMax, &yMin, &yMax, dot.x(), dot.y()); + } + for (PlotStore::Label label : labels()) { + updateRange(&xMin, &xMax, &yMin, &yMax, label.x(), label.y()); + } + for (PlotStore::Segment segment : segments()) { + updateRange(&xMin, &xMax, &yMin, &yMax, segment.xStart(), segment.yStart()); + updateRange(&xMin, &xMax, &yMin, &yMax, segment.xEnd(), segment.yEnd()); + } + for (PlotStore::Rect rectangle : rects()) { + updateRange(&xMin, &xMax, &yMin, &yMax, rectangle.left(), rectangle.top()); + updateRange(&xMin, &xMax, &yMin, &yMax, rectangle.right(), rectangle.bottom()); + } + checkPositiveRangeAndAddMargin(&xMin, &xMax); + checkPositiveRangeAndAddMargin(&yMin, &yMax); + setXMin(xMin); + setXMax(xMax); + setYMin(yMin); + setYMax(yMax); + } +} + +} diff --git a/python/port/mod/matplotlib/pyplot/plot_store.h b/python/port/mod/matplotlib/pyplot/plot_store.h new file mode 100644 index 000000000..d74b60cb0 --- /dev/null +++ b/python/port/mod/matplotlib/pyplot/plot_store.h @@ -0,0 +1,142 @@ +#ifndef PYTHON_MATPLOTLIB_PLOT_STORE_H +#define PYTHON_MATPLOTLIB_PLOT_STORE_H + +//#include +#include +extern "C" { +#include +} + +namespace Matplotlib { + +class PlotStore : public Shared::InteractiveCurveViewRange { +public: + PlotStore(); + void flush(); + bool isEmpty(); + + // Iterators + + template + class ListIterator { + public: + static ListIterator Begin(mp_obj_t list); + static ListIterator End(mp_obj_t list); + T operator*(); + ListIterator & operator++(); + bool operator!=(const ListIterator & it) const; + private: + ListIterator() : m_tupleIndex(0) {} + mp_obj_t * m_tuples; + size_t m_numberOfTuples; + size_t m_tupleIndex; + }; + + template + class Iterable { + public: + Iterable(mp_obj_t list) : m_list(list) {} + T begin() const { return T::Begin(m_list); } + T end() const { return T::End(m_list); } + private: + mp_obj_t m_list; + }; + + // Dot + + class Dot { + public: + Dot(mp_obj_t tuple); + float x() const { return m_x; } + float y() const { return m_y; } + KDColor color() const { return m_color; } + private: + float m_x; + float m_y; + KDColor m_color; + }; + + void addDot(mp_obj_t x, mp_obj_t y, KDColor c); + Iterable> dots() { return Iterable>(m_dots); } + + // Segment + + class Segment { + public: + Segment(mp_obj_t tuple); + float xStart() const { return m_xStart; } + float yStart() const { return m_yStart; } + float xEnd() const { return m_xEnd; } + float yEnd() const { return m_yEnd; } + bool isArrow() const { return m_arrow; } + KDColor color() const { return m_color; } + private: + float m_xStart; + float m_yStart; + float m_xEnd; + float m_yEnd; + bool m_arrow; + KDColor m_color; + }; + + void addSegment(mp_obj_t xStart, mp_obj_t yStart, mp_obj_t xEnd, mp_obj_t yEnd, KDColor c, bool arrowEdge); + Iterable> segments() { return Iterable>(m_segments); } + + // Rect + + class Rect { + public: + Rect(mp_obj_t tuple); + float left() const { return m_left; } + float right() const { return m_right; } + float top() const { return m_top; } + float bottom() const { return m_bottom; } + KDColor color() const { return m_color; } + private: + float m_left; + float m_right; + float m_top; + float m_bottom; + KDColor m_color; + }; + + void addRect(mp_obj_t x, mp_obj_t y, mp_obj_t width, mp_obj_t height, KDColor c); + Iterable> rects() { return Iterable>(m_rects); } + + // Label + + class Label { + public: + Label(mp_obj_t tuple); + float x() const { return m_x; } + float y() const { return m_y; } + const char * string() const { return m_string; } + private: + float m_x; + float m_y; + const char * m_string; + }; + + void addLabel(mp_obj_t x, mp_obj_t y, mp_obj_t string); + Iterable> labels() { return Iterable>(m_labels); } + + void setAxesRequested(bool b) { m_axesRequested = b; } + bool axesRequested() const { return m_axesRequested; } + void setAxesAuto(bool b) { m_axesAuto = b; } + void initRange(); + + void setGridRequested(bool b) { m_gridRequested = b; } + bool gridRequested() const { return m_gridRequested; } +private: + mp_obj_t m_dots; // List of (x, y, color) + mp_obj_t m_labels; // List of (x, y, string) + mp_obj_t m_segments; // List of (x, y, dx, dy, style, color) + mp_obj_t m_rects; // List of (x, y, w, h, color) + bool m_axesRequested; + bool m_axesAuto; + bool m_gridRequested; +}; + +} + +#endif diff --git a/python/port/mod/matplotlib/pyplot/plot_view.cpp b/python/port/mod/matplotlib/pyplot/plot_view.cpp new file mode 100644 index 000000000..76daa005a --- /dev/null +++ b/python/port/mod/matplotlib/pyplot/plot_view.cpp @@ -0,0 +1,80 @@ +#include "plot_view.h" + +namespace Matplotlib { + +void PlotView::drawRect(KDContext * ctx, KDRect rect) const { + ctx->fillRect(rect, KDColorWhite); + + if (m_store->gridRequested()) { + drawGrid(ctx, rect); + } + + // Draw labels below all figures because they're drawn on a white rectangle. + // TODO: we could blend them in the background by adding a parameter to drawLabelsAndGraduations. + if (m_store->axesRequested()) { + drawAxes(ctx, rect); + drawLabelsAndGraduations(ctx, rect, Axis::Vertical, true); + drawLabelsAndGraduations(ctx, rect, Axis::Horizontal, true); + } + + for (PlotStore::Dot dot : m_store->dots()) { + traceDot(ctx, rect, dot); + } + + for (PlotStore::Label label : m_store->labels()) { + traceLabel(ctx, rect, label); + } + + for (PlotStore::Segment segment : m_store->segments()) { + traceSegment(ctx, rect, segment); + } + + for (PlotStore::Rect rectangle : m_store->rects()) { + traceRect(ctx, rect, rectangle); + } +} + +void PlotView::traceDot(KDContext * ctx, KDRect r, PlotStore::Dot dot) const { + drawDot(ctx, r, dot.x(), dot.y(), dot.color()); +} + +void PlotView::traceSegment(KDContext * ctx, KDRect r, PlotStore::Segment segment) const { + drawSegment( + ctx, r, + segment.xStart(), segment.yStart(), + segment.xEnd(), segment.yEnd(), + segment.color() + ); + if (segment.isArrow()) { + float dx = segment.xEnd() - segment.xStart(); + float dy = segment.yEnd() - segment.yStart(); + drawArrow(ctx, r, segment.xEnd(), segment.yEnd(), dx, dy, segment.color()); + } +} + +static inline KDCoordinate maxKDCoordinate(KDCoordinate x, KDCoordinate y) { return x > y ? x : y; } +void PlotView::traceRect(KDContext * ctx, KDRect r, PlotStore::Rect rect) const { + KDCoordinate left = std::round(floatToPixel(Axis::Horizontal, rect.left())); + KDCoordinate right = std::round(floatToPixel(Axis::Horizontal, rect.right())); + KDCoordinate top = std::round(floatToPixel(Axis::Vertical, rect.top())); + KDCoordinate bottom = std::round(floatToPixel(Axis::Vertical, rect.bottom())); + KDRect pixelRect( + left, + top, + maxKDCoordinate(right - left, 1), // Rectangle should at least be visible + bottom - top + ); + ctx->fillRect(pixelRect, rect.color()); +} + +void PlotView::traceLabel(KDContext * ctx, KDRect r, PlotStore::Label label) const { + drawLabel(ctx, r, + label.x(), label.y(), label.string(), + KDColorBlack, + RelativePosition::After, + RelativePosition::After + ); +} + + +} diff --git a/python/port/mod/matplotlib/pyplot/plot_view.h b/python/port/mod/matplotlib/pyplot/plot_view.h new file mode 100644 index 000000000..4a804c957 --- /dev/null +++ b/python/port/mod/matplotlib/pyplot/plot_view.h @@ -0,0 +1,24 @@ +#ifndef PYTHON_MATPLOTLIB_PLOT_VIEW_H +#define PYTHON_MATPLOTLIB_PLOT_VIEW_H + +#include +#include "plot_store.h" + +namespace Matplotlib { + +class PlotView : public Shared::LabeledCurveView { +public: + PlotView(PlotStore * s) : Shared::LabeledCurveView(s), m_store(s) {} + void drawRect(KDContext * ctx, KDRect rect) const override; +private: + void traceDot(KDContext * ctx, KDRect r, PlotStore::Dot dot) const; + void traceSegment(KDContext * ctx, KDRect r, PlotStore::Segment segment) const; + void traceRect(KDContext * ctx, KDRect r, PlotStore::Rect rect) const; + void traceLabel(KDContext * ctx, KDRect r, PlotStore::Label label) const; + PlotStore * m_store; +}; + +} + + +#endif diff --git a/python/port/mpconfigport.h b/python/port/mpconfigport.h index 8ef4cad6d..bfff743d6 100644 --- a/python/port/mpconfigport.h +++ b/python/port/mpconfigport.h @@ -130,12 +130,16 @@ typedef long mp_off_t; extern const struct _mp_obj_module_t modion_module; extern const struct _mp_obj_module_t modkandinsky_module; +extern const struct _mp_obj_module_t modmatplotlib_module; +extern const struct _mp_obj_module_t modpyplot_module; extern const struct _mp_obj_module_t modtime_module; extern const struct _mp_obj_module_t modturtle_module; #define MICROPY_PORT_BUILTIN_MODULES \ { MP_ROM_QSTR(MP_QSTR_ion), MP_ROM_PTR(&modion_module) }, \ { MP_ROM_QSTR(MP_QSTR_kandinsky), MP_ROM_PTR(&modkandinsky_module) }, \ + { MP_ROM_QSTR(MP_QSTR_matplotlib), MP_ROM_PTR(&modmatplotlib_module) }, \ + { MP_ROM_QSTR(MP_QSTR_matplotlib_dot_pyplot), MP_ROM_PTR(&modpyplot_module) }, \ { MP_ROM_QSTR(MP_QSTR_time), MP_ROM_PTR(&modtime_module) }, \ { MP_ROM_QSTR(MP_QSTR_turtle), MP_ROM_PTR(&modturtle_module) }, \ diff --git a/python/port/port.cpp b/python/port/port.cpp index 5c56064cc..77c7b6c4a 100644 --- a/python/port/port.cpp +++ b/python/port/port.cpp @@ -20,6 +20,7 @@ extern "C" { #include "py/stackctrl.h" #include "mphalport.h" #include "mod/turtle/modturtle.h" +#include "mod/matplotlib/pyplot/modpyplot.h" } static MicroPython::ScriptProvider * sScriptProvider = nullptr; @@ -81,6 +82,10 @@ void MicroPython::ExecutionEnvironment::runCode(const char * str) { mp_obj_print_helper(&mp_plat_print, (mp_obj_t)nlr.ret_val, PRINT_EXC); mp_print_str(&mp_plat_print, "\n"); /* End of mp_obj_print_exception. */ + + // Flush the store if an error is encountered to avoid being stuck with a full memory + modpyplot_flush_used_heap(); + // TODO: do the same for other modules? } // Disable the user interruption @@ -94,13 +99,6 @@ void MicroPython::ExecutionEnvironment::interrupt() { mp_keyboard_interrupt(); } -void MicroPython::ExecutionEnvironment::setSandboxIsDisplayed(bool display) { - if (m_sandboxIsDisplayed && !display) { - modturtle_view_did_disappear(); - } - m_sandboxIsDisplayed = display; -} - extern "C" { extern const void * _stack_start; extern const void * _stack_end; @@ -176,6 +174,7 @@ void gc_collect(void) { gc_collect_start(); modturtle_gc_collect(); + modpyplot_gc_collect(); /* get the registers. * regs is the also the last object on the stack so the stack is bound by diff --git a/python/port/port.h b/python/port/port.h index 63b721336..67eecd3c5 100644 --- a/python/port/port.h +++ b/python/port/port.h @@ -4,6 +4,7 @@ extern "C" { #include } +#include namespace MicroPython { @@ -14,21 +15,23 @@ public: class ExecutionEnvironment { public: - ExecutionEnvironment() : m_sandboxIsDisplayed(false) {} + ExecutionEnvironment() {} static ExecutionEnvironment * currentExecutionEnvironment(); void runCode(const char * ); virtual const char * inputText(const char * prompt) { return nullptr; } - virtual void displaySandbox() {} - virtual void hideSandbox() {} + + // Sandbox + void displaySandbox() { displayViewController(sandbox()); } + virtual ViewController * sandbox() { return nullptr; } virtual void resetSandbox() {} + + // Generic View Controller + virtual void displayViewController(ViewController * controller) {} + virtual void hideAnyDisplayedViewController() {} + virtual void printText(const char * text, size_t length) {} virtual void refreshPrintOutput() {} void interrupt(); - void setSandboxIsDisplayed(bool display); -protected: - bool sandboxIsDisplayed() const { return m_sandboxIsDisplayed; } -private: - bool m_sandboxIsDisplayed; }; void init(void * heapStart, void * heapEnd); @@ -36,6 +39,6 @@ void deinit(); void registerScriptProvider(ScriptProvider * s); void collectRootsAtAddress(char * address, int len); -}; +} #endif