<commit_before>"""
* Copyright 2007,2008,2009 <NAME>
* Copyright (C) 2009 <NAME> <<EMAIL>>
*
* Licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
*  http:#www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific
* language governing permissions and limitations under the
* License.
*
"""




from pyjamas import DOM
from pyjamas import Window



























from pyjamas.ui import Event
from pyjamas.ui.AbsolutePanel import AbsolutePanel
from pyjamas.ui.Composite import Composite
from pyjamas.ui.Grid import Grid
from pyjamas.ui import HasHorizontalAlignment
from pyjamas.ui import HasVerticalAlignment
from pyjamas.ui.HTML import HTML
from pyjamas.ui.Image import Image
from pyjamas.ui.SimplePanel import SimplePanel
from pyjamas.ui.UIObject import UIObject
from pyjamas.ui.Widget import Widget

from GChartConsts import N_PRE_SYSTEM_CURVES
from GChartConsts import TRANSPARENT_BORDER_COLOR

import GChart
import Double

"""
* This class' sole purpose is to work around a FF 2
* performance limitation: chart update times increase as
* O(N^2) after the number of direct ancestor child widgets in
* a single AbsolutePanel exceeds around 500-1000, AND the
* chart is updated in more than one browser-displayed stage
* (e.g, via a series of incremental updates that successively
* add more curve data, for purposes of user feedback). By
* contrast, IE7 times grow as O(N) even if 3,000 child widgets
* are added to a previously displayed chart (e.g. by adding a
* bar curve with 3,000 bars on it).
*
* <p>
*
* For solid-fill line chart support (LINE SymbolType
* introduced in 2.2), thousands of widgets are often needed
* and so these O(N^2) FF 2 times were just too slow.
*
* <p>
*
* Some kind of fixed hash table inside of FF 2 divs could
* explain this switch from O(N) to O(N^2) performance.
* <p>
*
* Approach is to split up the large AbsolutePanel into a
* series of child panels, each of which contains a number of
* elements within the range where FF 2 updates are O(N).
* <p>
*
* Tried to keep it light-weight so IE7 isn't penalized too
* much for having to workaround this FF 2 limitation.
*
"""

WIDGETS_PER_PANEL = 256

class PartitionedAbsolutePanel (Composite):
    """ Max number of widgets in each panel; chose a value as large as
    possible while remaining within the empirically
    determined FF 2 O(N) range. Here's the data (from a 3,000 element
    test based on the sin curve of the 2.1 live demo
    called GChartExample15c.java) upon which this choice was based:
    <p>

    <pre>

    Size    FF2    IE7
    (sec)  (sec)
    1     ~14     16
    2      11     13
    32      9      11
    64      9      12
    128      9      11
    256      9      11
    512      10     11
    1024     15     11
    2048     27     11
    4096*    61     12

    </pre>
    <p>

    The two largest sizes are good approximations of FF2 times we got
    informally before the switch to partitioned AbsolutePanels with
    charts with the corresponding number of elements (2000 or 3000).
    The overhead of the partitioning itself is very low, as shown by
    the modest time increase even when each element is placed into
    its own sub-panel. Tests with a < 256 element chart suggested at
    most a couple of ms of time increases due to the introduction of
    partitioning.

    <p>

    I kind of expected to see >61 second times with a sub-panel size
    of 1, since the parent panel still has >3,000 elements in this
    case. Whatever the cause of the performance logjam (presumably in
    the FF2 heap somewhere?)  simply the fact that you introduce a
    parent AbsolutePanel that holds various child AbslolutePanels
    that hold the actual image widgets appears to work around most of
    the problem. If such a useless change makes FF2 materially
    faster, that seems like a performance bug in FF2 to me.<p>

    The root FF cause is apparently NOT image cache related, though,
    since turning off the image cache via about:config didn't change
    times for the last row of the table above.

    """

    """ Strange, reorganized DOM layout in GChart v2.5 and now cannot
    * reproduce these results in FF2 (partitioning gave just a modest 1
    * second boost in a 15 second, 4,000 point test). So, I was about to
    * drop back to a simple AbsolutePanel. BUT, in FF3, now, the 4,000
    * point test crashes unless I use PartitionedAbsolutePanel! I guess
    * Firefox 3 has problems with divs that have 4,000 elements, or at
    * least 4,000 image elements, on them, which the partioning fixes.
    *
    """

    def __init__(self, **kwargs):
        self.root = AbsolutePanel()
        self.subPanel = None; # "selected" subPanel
        self.iSubPanel = -1;    # index of "selected" subPanel
        self.nWidgets = 0;     # total # over all subPanels

        Composite.__init__(self, **kwargs)
        self.initWidget(self.root)


    """ resets the partitioned panel to it's initial state """
    def clear(self):
        self.root.clear()
        self.subPanel = None
        self.iSubPanel = -1
        self.nWidgets = 0


    def getWidgetCount(self):
        return self.nWidgets


    # makes the subpanel containing the widget the selected one.
    def selectSubPanel(self, iWidget):
        if self.iSubPanel != iWidget/WIDGETS_PER_PANEL:
            self.iSubPanel = iWidget/WIDGETS_PER_PANEL
            self.subPanel = self.root.getWidget(self.iSubPanel)



    # adds a widget to end of this partioned absolute panel
    def add(self, w):
        if self.nWidgets % WIDGETS_PER_PANEL == 0:
            # last panel is full, time to add a one
            self.subPanel = AbsolutePanel()
            # Panel sits in upper left corner. Does nothing, can't
            # be seen. It's just a holder for other widgets.
            GChart.setOverflow(self.subPanel, "visible")
            self.subPanel.setPixelSize(0,0)
            self.root.add(self.subPanel, 0, 0)

        self.selectSubPanel(self.nWidgets)
        self.subPanel.add(w)
        self.nWidgets += 1


    # returns widget at given index
    def getWidget(self, iWidget):
        if iWidget < 0  or  iWidget >= self.nWidgets:
            raise IllegalArgumentException(
                    "Invalid widget index: " + iWidget +
                    ". Valid range is: 0..." + (self.nWidgets-1))

        selectSubPanel(iWidget)
        result = self.subPanel.getWidget( iWidget % WIDGETS_PER_PANEL)
        return result


    # Remove very last widget from panel.
    def remove(self, iWidget):
        if iWidget != self.nWidgets-1:
            raise IllegalArgumentException(
            "iWidgets arg = " + iWidget + " nWidgets-1 (" + (self.nWidgets-1)+") is required.")

        selectSubPanel(iWidget)
        result = self.subPanel.remove(iWidget % WIDGETS_PER_PANEL)
        if iWidget % WIDGETS_PER_PANEL == 0:
            # if deleted widget is last widget overall, and first on
            # the selected panel, selected panel will now be empty.
            self.root.remove(self.subPanel)
            self.iSubPanel = -1;    # next selectSubPanel will reset these
            self.subPanel = None

        self.nWidgets -= 1
        return result




    # To assure that w is on the selected subPanel, this method
    # must only be passed a widget that is in the currently
    # selected subPanel. This can be assured by passing in a
    # widget that was just added via add(), or just retrieved
    # via getWidget (otherwise, an exception will be thrown).

    def setWidgetPosition(self, w, left, top):
        self.subPanel.setWidgetPosition(w, left, top)


 # end of class PartitionedAbsolutePanel

class AlignedLabel(Grid):
    def __init__(self, **kwargs):
        Grid.__init__(self, 1, 1, **kwargs)
        self.getCellFormatter().setWordWrap(0,0,False)
        self.setCellPadding(0)
        self.setCellSpacing(0)
        self.setBorderWidth(0)





class Rectangle(object):
    # a (pixel graphics coords) rectangle
    pass
    #double x;  # x, y at upper left corner of rectangle
    #double y
    #double width;  # distance from x to right edge
    #double height; # distance from y to bottom edge

"""*
* Provides support for reusing certain property specifications
* that are likely to be the same, given how aligned labels in a
* GChart get reused, and given certain assumptions about which
* properties of the labels are most likely to remain unchanged
* between updates.
* <p>
*
* Also applies a hidden outter grid technique to allow proper
* alignment with labels of unknown size without occluding
* mouse events of nearby elements.
* <p>
*
* Very similar in intent to the ReusableImage, see that
* class' header comment for more info.
*
"""
class NonoccludingReusuableAlignedLabel (AlignedLabel):
    def getInnerGrid(self):
        return self.innerGrid

    def __init__(self):
        self.fontSize = GChart.NAI
        self.fontStyle = GChart.USE_CSS
        self.fontWeight = GChart.USE_CSS
        self.fontColor = GChart.USE_CSS
        self.labelText = None
        self.isHTML = False
        self.labelWidget = None
        self.innerGrid = AlignedLabel()

        AlignedLabel.__init__(self)

        self.setWidget(0, 0, self.innerGrid)
        """
        * The basic technique being used in the lines below is
        * illustrated by this excerpt from p 317 of "CSS, The
        * Definitive Guide" by <NAME>: <p>
        *
        * <pre>
        *   p.clear {visibility: hidden;}
        *   p.clear em {visibility: visible;}
        * </pre>
        * <p>
        *
        * In the above example, emphasized (italic) text is
        * positioned exactly as it would have been in the
        * paragraph had the normal text been visible, except that
        * the normal text <i>isn't</i> visible. And, unlike
        * transparent text, the invisible text also won't capture
        * mouse events (essential for our aligned labels)
        *
        * <p>
        *
        * With GChart's aligned label, we want the outter Grid (HTML
        * table) to be "not there" as far as visibility and
        * mouseovers, but still impact centering and
        * other alignment of the stuff in the visible, inner Grid.
        *
        * <p>
        *
        * Note that we cannot just make the Grid color transparent
        * (tried that first) because in that case the oversized
        * outter Grid required for alignment will still grab the
        * mouseover events inappropriately (wrong hovertext
        * problem).
        *
        * <p>
        *
        * Not certain but, apparently, IE6 requires that, if
        * you want to apply this trick when the outer element
        * is a table you must use another table as the
        * inner element. At least, the div inner element approach
        * I used at first (that basically worked in Firefox), made
        * both parent and child invisible in IE6.
        * <p>
        *
        * In summary:
        *
        * The upside: alignment without inappropriate event occlusion
        * The downside: the extra Grid element saps performance
        *
        """

        DOM.setStyleAttribute(getElement(), "visibility","hidden")
        DOM.setStyleAttribute(self.innerGrid.getElement(),
                              "visibility", "visible")


    """
    * Sets properties only if they have changed; to replace
    * expensive DOM calls with cheap inequality tests.
    *
    * TODO: Investigate moving properties that are guaranteed to be
    * the same across all elements in the image panel (backgroundColor,
    * borderColor, borderWidth, borderStyle, image url, possibly more)
    * into a single style for the image panel as a whole, and just add
    * that styleName to each image, and set the properties once, in
    * the style. If this worked, it would save both time and space
    * (style would be internal and not intended for direct developer
    * access, since in some charts canvas, not styles, would be
    * responsible for these curve properties), and the same approach
    * could be applied to the labelPanel.
    *
    """

    def setReusableProperties(self, fontSize, fontStyle, fontWeight, fontColor,
                                    hAlign, vAlign, labelText,
                                    isHTML, labelWidget):

        if self.fontSize != fontSize:
            DOM.setIntStyleAttribute(innerGrid.getElement(), "fontSize", fontSize)
            self.fontSize = fontSize

        if self.fontStyle != fontStyle:
            DOM.setStyleAttribute(innerGrid.getElement(), "fontStyle", fontStyle)
            self.fontStyle = fontStyle

        if self.fontWeight != fontWeight:
            DOM.setStyleAttribute(innerGrid.getElement(), "fontWeight", fontWeight)
            self.fontWeight = fontWeight

        if self.fontColor != fontColor:
            DOM.setStyleAttribute(innerGrid.getElement(),"color", fontColor)
            self.fontColor = fontColor

        if self.hAlign != hAlign:
            getCellFormatter().setHorizontalAlignment(0,0,hAlign)
            # without this, only IE6-quirks doesn't quite align right:
            innerGrid.getCellFormatter().setHorizontalAlignment(0,0,hAlign)
            self.hAlign = hAlign

        if self.vAlign != vAlign:
            getCellFormatter().setVerticalAlignment(0,0,vAlign)
            # without this, only IE6-quirks doesn't quite align right:
            innerGrid.getCellFormatter().setVerticalAlignment(0,0,vAlign)
            self.vAlign = vAlign


        if None != labelWidget:
            if self.labelWidget != labelWidget:
                innerGrid.setWidget(0,0,labelWidget)
                self.labelWidget = labelWidget
                self.labelText = None


        elif self.labelText != labelText  or  self.isHTML != isHTML:
            if None == labelText  or  "" == labelText:
                innerGrid.setText(0,0,"")

            elif not isHTML:
                innerGrid.setText(0,0,labelText)

            else:
                innerGrid.setHTML(0, 0, labelText)

            self.isHTML = isHTML
            self.labelText = labelText
            self.labelWidget = None


 # end of class NonoccludingReusuableAlignedLabel

"""
* AbsolutePanel that allows annotations it contains to be easily
* reused, for increased efficiency.
*
"""
class AnnotationRenderingPanel (PartitionedAbsolutePanel):
    labelIndex = 0;                # to-be-added-next label index
    lastVisibleLabel = -1; # just before 1st valid index
    """
    * Returns the inner grid of the first reusuable, non-occluding
    * aligned label in this rendering panel.
    *
    """
    def getFirstInnerAlignedLabel(self):
        result = None
        if labelIndex > 0:
            parent = self.getWidget(0)
            result = parent.getInnerGrid()

        return result


    def __init__(self):
        PartitionedAbsolutePanel.__init__(self)
        """
        * Because of event-occlusion that can occur on all browsers but IE,
        * annotation panels MUST by 0-sized/overflow:visible otherwise they
        * will short-circuit event processing needed for widget annotations
        * added by developer. Graphics rendering panels don't have this
        * constraint and thus can be clipped to the plot area.
        *
        """
        GChart.setOverflow(self, "visible")
        self.setPixelSize(0,0)


    def setLabelPosition(self, lbl, x, y):
        # workaround problem with special meaning of (-1,-1) to
        # setWidgetPosition (makes position off by one pixel).
        if x == -1  and  y == -1:
            x = 0

        setWidgetPosition(lbl, x, y)


    def beginRendering(self):
        labelIndex = 0


    def endRendering(self):
        # hide or remove labels no longer being used
        if optimizeForMemory:
            iLabel = (getWidgetCount()-1)
        else:
            iLabel = lastVisibleLabel
        while iLabel >= labelIndex:
            w = getWidget(iLabel)
            if optimizeForMemory:
                remove(iLabel)
            else:
                w.setVisible(False)
            iLabel -= 1

        lastVisibleLabel = labelIndex-1


    """
    * Creates (or reveals), and configures, an aligned label. Works
    * very similarly to addOrRevealImage.
    *
    """
    def getNextOrNewAlignedLabel(self, fontSize, fontStyle, fontWeight, fontColor, hAlign, vAlign, labelText, isHTML, labelWidget):
        if labelIndex < getWidgetCount():
            result = self.getWidget(labelIndex)
            if None != result.labelWidget  and  labelWidget == result.labelWidget:
                """
                * DOM element actually stored in the label's Grid-cell, and what
                * the label "thinks" is stored there, could be inconsistent if,
                * for example, the same label widget reference was used to
                * define two different points' annotations. In that case, we
                * need to clear the widget reference that the label stores
                * thus making it consistent with what is really in the DOM.
                * <p>
                *
                * This code was added to fix the bug reproduced by
                * TestGChart53.java. See that test for more info.
                *
                *
                """
                e = labelWidget.getElement()
                if None == e  or  (e.getParentElement() != result.innerGrid.getCellFormatter().getElement(0,0)):
                    # the widget' DOM parent isn't label's grid-cell (it was moved)
                    result.labelWidget = None



            if labelIndex > lastVisibleLabel:
                result.setVisible(True)


        else:
            result = NonoccludingReusuableAlignedLabel()
            add(result)

        result.setReusableProperties(fontSize, fontStyle, fontWeight,
                                    fontColor, hAlign, vAlign,
                                    labelText, isHTML, labelWidget)

        if lastVisibleLabel < labelIndex:
            lastVisibleLabel = labelIndex

        labelIndex += 1
        return result


    def renderAnnotation(self, annotation, loc, xCenter, yCenter, symWidth, symHeight, symbol):

        widthUpperBound = annotation.getWidthUpperBound()
        upLeftX = loc.getUpperLeftX(xCenter,
        widthUpperBound, abs(symWidth))
        heightUpperBound = annotation.getHeightUpperBound()
        upLeftY = loc.getUpperLeftY(yCenter,
        heightUpperBound, abs(symHeight))


        alignedLabel = getNextOrNewAlignedLabel(
                                    annotation.getFontSize(),
                                    annotation.getFontStyle(),
                                    annotation.getFontWeight(),
                                    annotation.getFontColor(),
                                    loc.getHorizontalAlignment(),
                                    loc.getVerticalAlignment(),
                                    annotation.getText(), annotation.isHTML(),
                                    annotation.getWidget())
        # If positioning by top or left edges, explicit sizing isn't needed
        # (makes the bounding box tighter, which, for reasons unknown, makes
        # rendering around 10% faster on some browsers and usage scenarios).
        if loc.getHorizontalAlignment() != HasHorizontalAlignment.ALIGN_LEFT:
            alignedLabel.setWidth(widthUpperBound + "px")

        else:
            alignedLabel.setWidth("")


        if loc.getVerticalAlignment() != HasVerticalAlignment.ALIGN_TOP:
            alignedLabel.setHeight(heightUpperBound + "px")

        else:
            alignedLabel.setHeight("")


        setLabelPosition(alignedLabel, upLeftX, upLeftY)



 # end of class AnnotationRenderingPanel

"""*
* Provides support for reusing certain property specifications
* that are likely to be the same, given how images in a GChart
* get reused, and given certain assumptions about which
* properties of the image are most likely to remain unchanged
* between updates.  For the most common scenarios, chart
* updates are significantly faster due to replacing (relatively
* expensive) DOM style attribute setting with (relatively
* cheap) String reference or integer equality tests.
* <p>
*
* For hovertext, the class also lets us defer actual generation
* of the hovertext until they actually mouse over the image,
* saving further time (it's surprisingly expensive just to
* format the numbers and such used in hovertexts).
* <p>
*
* TODO: Since we no longer use events on Image widgets,
* see if we can switch to just using simpler HTML elements,
* if that reduces the overhead associated with a Widget?
*
*
"""
class ReusableImage (Image):
    def __init__(self):
        self.backgroundColor = GChart.USE_CSS
        self.borderColor = GChart.USE_CSS
        self.borderStyle= GChart.USE_CSS
        # the capped border width, times two (to allow half-pixel widths)
        self.cappedBorderWidthX2 = GChart.NAI
        self.width = GChart.NAI
        self.height = GChart.NAI
        self.x = GChart.NAI
        self.y = GChart.NAI
        self.url = None

        Image.__init__(self)


    def setReusableProperties(self, backgroundColor, borderColor, borderStyle, borderWidth, dWidth, dHeight, xD, yD, url):

        # Round two edges, and define width to be their difference.
        # (rounding this way assures bars align with gridlines, etc.)
        newX = int ( round(xD) )
        newW = int ( round(xD + dWidth) - newX )
        newY = int ( round(yD) )
        newH = int ( round(yD + dHeight) - newY )
        thickness = min(newW, newH) 
        # Don't allow borders that would exceed specified width or
        # height. So, if smaller of width, height is at least twice the
        # border width, border width is used as is, otherwise,
        # it's replaced with half the smaller of width, height:
        newCappedBorderWidthX2 = min (2*borderWidth, thickness) 

        """
        * Note: on a GWT absolute panel, the x,y position of the widget is the
        * upper left corner of the widget's border, so x, y need no adjustment
        * to account for an internal (positive) border. Negative (external)
        * borders expand rectangle equally in all directions, so x,y need to
        * shift back to the upper left corner.  Transparent border
        * emulation sets border width to 0, and adjusts element size and
        * position to mimic border transparency (this rather odd feature is
        * required to workaround the IE6 "transparent border" bug)
        *
        """
        if TRANSPARENT_BORDER_COLOR == borderColor:
            #transparency emulation
            if newCappedBorderWidthX2 > 0:
                # to emulate an internal transparent border using a 0 width
                # border, we need to shift the upper left corner by the
                # amount of border, and shrink the size by twice the amount
                # of the border.
                newX += newCappedBorderWidthX2/2; # shift upper left corner
                newY += newCappedBorderWidthX2/2
                newH -= newCappedBorderWidthX2; # shrink size
                newW -= newCappedBorderWidthX2

            # else, external border is just eliminated, no adjustment needed
            newCappedBorderWidthX2 = 0
            borderColor = "transparent"; # because DOM won't accept None
            if backgroundColor == TRANSPARENT_BORDER_COLOR:
                backgroundColor = "transparent"


        elif newCappedBorderWidthX2 < 0:
            newX += newCappedBorderWidthX2/2; # shift upper left corner back
            newY += newCappedBorderWidthX2/2; # to incorporate external border.

        else:
            newH -= newCappedBorderWidthX2; # shrink size to incorporate
            newW -= newCappedBorderWidthX2; # impact of internal border.


        if self.cappedBorderWidthX2 != newCappedBorderWidthX2:
            if 1 == (newCappedBorderWidthX2 % 2):
                # odd pixel 2 x borderWidth needs asymetical borders to fill rect
                # (only positive (internal) borders can have half-pixel widths)
                floorBW = newCappedBorderWidthX2/2
                ceilBW = floorBW+1
                # (top, right, bottom, left) == (floor, floor, ceil, ceil)
                # assures symbol is odd-pixel border-filled in all cases
                DOM.setStyleAttribute(self.getElement(),
                                "borderWidth",
                                floorBW+"px "+floorBW+"px "+
                                ceilBW+"px " + ceilBW+"px ")

            else:
                DOM.setStyleAttribute(getElement(),
                "borderWidth", abs(newCappedBorderWidthX2/2)+"px")

            self.cappedBorderWidthX2 = newCappedBorderWidthX2


        if GChart.NAI == self.x:
            # At first, use AbsolutePanel's official API
            # (to insulate us from any future AbsolutePanel
            # changes)
            self.setImagePosition(self, newX, newY)
            self.x = newX
            self.y = newY

        else:
            # for speed, just set the edge positions that changed
            # (works, but bypasses AbsolutePanel's official API)
            if self.x != newX:
                DOM.setStyleAttribute(getElement(),"left", newX+"px")
                self.x = newX

            if self.y != newY:
                DOM.setStyleAttribute(getElement(),"top", newY+"px")
                self.y = newY



        if self.width != newW:
            setWidth(newW + "px")
            self.width = newW

        if self.height != newH:
            setHeight(newH + "px")
            self.height = newH


        if self.backgroundColor != backgroundColor:
            DOM.setStyleAttribute(getElement(), "backgroundColor",
            backgroundColor)
            self.backgroundColor =backgroundColor

        if self.borderColor != borderColor:
            DOM.setStyleAttribute(getElement(), "borderColor", borderColor)
            self.borderColor = borderColor

        if self.borderStyle != borderStyle:
            DOM.setStyleAttribute(getElement(), "borderStyle",
            borderStyle)
            self.borderStyle = borderStyle

<commit_msg>more gchart update<commit_after>"""
* Copyright 2007,2008,2009 <NAME>
* Copyright (C) 2009 <NAME> <<EMAIL>>
*
* Licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
*  http:#www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific
* language governing permissions and limitations under the
* License.
*
"""




from pyjamas import DOM
from pyjamas import Window



























from pyjamas.ui import Event
from pyjamas.ui.AbsolutePanel import AbsolutePanel
from pyjamas.ui.Composite import Composite
from pyjamas.ui.Grid import Grid
from pyjamas.ui import HasHorizontalAlignment
from pyjamas.ui import HasVerticalAlignment
from pyjamas.ui.HTML import HTML
from pyjamas.ui.Image import Image
from pyjamas.ui.SimplePanel import SimplePanel
from pyjamas.ui.UIObject import UIObject
from pyjamas.ui.Widget import Widget

from GChartConsts import N_PRE_SYSTEM_CURVES
from GChartConsts import TRANSPARENT_BORDER_COLOR

import GChart
import Double

"""
* This class' sole purpose is to work around a FF 2
* performance limitation: chart update times increase as
* O(N^2) after the number of direct ancestor child widgets in
* a single AbsolutePanel exceeds around 500-1000, AND the
* chart is updated in more than one browser-displayed stage
* (e.g, via a series of incremental updates that successively
* add more curve data, for purposes of user feedback). By
* contrast, IE7 times grow as O(N) even if 3,000 child widgets
* are added to a previously displayed chart (e.g. by adding a
* bar curve with 3,000 bars on it).
*
* <p>
*
* For solid-fill line chart support (LINE SymbolType
* introduced in 2.2), thousands of widgets are often needed
* and so these O(N^2) FF 2 times were just too slow.
*
* <p>
*
* Some kind of fixed hash table inside of FF 2 divs could
* explain this switch from O(N) to O(N^2) performance.
* <p>
*
* Approach is to split up the large AbsolutePanel into a
* series of child panels, each of which contains a number of
* elements within the range where FF 2 updates are O(N).
* <p>
*
* Tried to keep it light-weight so IE7 isn't penalized too
* much for having to workaround this FF 2 limitation.
*
"""

WIDGETS_PER_PANEL = 256

class PartitionedAbsolutePanel (Composite):
    """ Max number of widgets in each panel; chose a value as large as
    possible while remaining within the empirically
    determined FF 2 O(N) range. Here's the data (from a 3,000 element
    test based on the sin curve of the 2.1 live demo
    called GChartExample15c.java) upon which this choice was based:
    <p>

    <pre>

    Size    FF2    IE7
    (sec)  (sec)
    1     ~14     16
    2      11     13
    32      9      11
    64      9      12
    128      9      11
    256      9      11
    512      10     11
    1024     15     11
    2048     27     11
    4096*    61     12

    </pre>
    <p>

    The two largest sizes are good approximations of FF2 times we got
    informally before the switch to partitioned AbsolutePanels with
    charts with the corresponding number of elements (2000 or 3000).
    The overhead of the partitioning itself is very low, as shown by
    the modest time increase even when each element is placed into
    its own sub-panel. Tests with a < 256 element chart suggested at
    most a couple of ms of time increases due to the introduction of
    partitioning.

    <p>

    I kind of expected to see >61 second times with a sub-panel size
    of 1, since the parent panel still has >3,000 elements in this
    case. Whatever the cause of the performance logjam (presumably in
    the FF2 heap somewhere?)  simply the fact that you introduce a
    parent AbsolutePanel that holds various child AbslolutePanels
    that hold the actual image widgets appears to work around most of
    the problem. If such a useless change makes FF2 materially
    faster, that seems like a performance bug in FF2 to me.<p>

    The root FF cause is apparently NOT image cache related, though,
    since turning off the image cache via about:config didn't change
    times for the last row of the table above.

    """

    """ Strange, reorganized DOM layout in GChart v2.5 and now cannot
    * reproduce these results in FF2 (partitioning gave just a modest 1
    * second boost in a 15 second, 4,000 point test). So, I was about to
    * drop back to a simple AbsolutePanel. BUT, in FF3, now, the 4,000
    * point test crashes unless I use PartitionedAbsolutePanel! I guess
    * Firefox 3 has problems with divs that have 4,000 elements, or at
    * least 4,000 image elements, on them, which the partioning fixes.
    *
    """

    def __init__(self, **kwargs):
        self.root = AbsolutePanel()
        self.subPanel = None; # "selected" subPanel
        self.iSubPanel = -1;    # index of "selected" subPanel
        self.nWidgets = 0;     # total # over all subPanels

        Composite.__init__(self, **kwargs)
        self.initWidget(self.root)


    """ resets the partitioned panel to it's initial state """
    def clear(self):
        self.root.clear()
        self.subPanel = None
        self.iSubPanel = -1
        self.nWidgets = 0


    def getWidgetCount(self):
        return self.nWidgets


    # makes the subpanel containing the widget the selected one.
    def selectSubPanel(self, iWidget):
        if self.iSubPanel != iWidget/WIDGETS_PER_PANEL:
            self.iSubPanel = iWidget/WIDGETS_PER_PANEL
            self.subPanel = self.root.getWidget(self.iSubPanel)



    # adds a widget to end of this partioned absolute panel
    def add(self, w):
        if self.nWidgets % WIDGETS_PER_PANEL == 0:
            # last panel is full, time to add a one
            self.subPanel = AbsolutePanel()
            # Panel sits in upper left corner. Does nothing, can't
            # be seen. It's just a holder for other widgets.
            GChart.setOverflow(self.subPanel, "visible")
            self.subPanel.setPixelSize(0,0)
            self.root.add(self.subPanel, 0, 0)

        self.selectSubPanel(self.nWidgets)
        self.subPanel.add(w)
        self.nWidgets += 1


    # returns widget at given index
    def getWidget(self, iWidget):
        if iWidget < 0  or  iWidget >= self.nWidgets:
            raise IllegalArgumentException(
                    "Invalid widget index: " + iWidget +
                    ". Valid range is: 0..." + (self.nWidgets-1))

        selectSubPanel(iWidget)
        result = self.subPanel.getWidget( iWidget % WIDGETS_PER_PANEL)
        return result


    # Remove very last widget from panel.
    def remove(self, iWidget):
        if iWidget != self.nWidgets-1:
            raise IllegalArgumentException(
            "iWidgets arg = " + iWidget + " nWidgets-1 (" + (self.nWidgets-1)+") is required.")

        selectSubPanel(iWidget)
        result = self.subPanel.remove(iWidget % WIDGETS_PER_PANEL)
        if iWidget % WIDGETS_PER_PANEL == 0:
            # if deleted widget is last widget overall, and first on
            # the selected panel, selected panel will now be empty.
            self.root.remove(self.subPanel)
            self.iSubPanel = -1;    # next selectSubPanel will reset these
            self.subPanel = None

        self.nWidgets -= 1
        return result




    # To assure that w is on the selected subPanel, this method
    # must only be passed a widget that is in the currently
    # selected subPanel. This can be assured by passing in a
    # widget that was just added via add(), or just retrieved
    # via getWidget (otherwise, an exception will be thrown).

    def setWidgetPosition(self, w, left, top):
        self.subPanel.setWidgetPosition(w, left, top)


 # end of class PartitionedAbsolutePanel

class AlignedLabel(Grid):
    def __init__(self, **kwargs):
        Grid.__init__(self, 1, 1, **kwargs)
        self.getCellFormatter().setWordWrap(0,0,False)
        self.setCellPadding(0)
        self.setCellSpacing(0)
        self.setBorderWidth(0)





class Rectangle(object):
    # a (pixel graphics coords) rectangle
    pass
    #double x;  # x, y at upper left corner of rectangle
    #double y
    #double width;  # distance from x to right edge
    #double height; # distance from y to bottom edge

"""*
* Provides support for reusing certain property specifications
* that are likely to be the same, given how aligned labels in a
* GChart get reused, and given certain assumptions about which
* properties of the labels are most likely to remain unchanged
* between updates.
* <p>
*
* Also applies a hidden outter grid technique to allow proper
* alignment with labels of unknown size without occluding
* mouse events of nearby elements.
* <p>
*
* Very similar in intent to the ReusableImage, see that
* class' header comment for more info.
*
"""
class NonoccludingReusuableAlignedLabel (AlignedLabel):
    def getInnerGrid(self):
        return self.innerGrid

    def __init__(self):
        self.fontSize = GChart.NAI
        self.fontStyle = GChart.USE_CSS
        self.fontWeight = GChart.USE_CSS
        self.fontColor = GChart.USE_CSS
        self.labelText = None
        self.isHTML = False
        self.labelWidget = None
        self.innerGrid = AlignedLabel()

        AlignedLabel.__init__(self)

        self.setWidget(0, 0, self.innerGrid)
        """
        * The basic technique being used in the lines below is
        * illustrated by this excerpt from p 317 of "CSS, The
        * Definitive Guide" by <NAME>: <p>
        *
        * <pre>
        *   p.clear {visibility: hidden;}
        *   p.clear em {visibility: visible;}
        * </pre>
        * <p>
        *
        * In the above example, emphasized (italic) text is
        * positioned exactly as it would have been in the
        * paragraph had the normal text been visible, except that
        * the normal text <i>isn't</i> visible. And, unlike
        * transparent text, the invisible text also won't capture
        * mouse events (essential for our aligned labels)
        *
        * <p>
        *
        * With GChart's aligned label, we want the outter Grid (HTML
        * table) to be "not there" as far as visibility and
        * mouseovers, but still impact centering and
        * other alignment of the stuff in the visible, inner Grid.
        *
        * <p>
        *
        * Note that we cannot just make the Grid color transparent
        * (tried that first) because in that case the oversized
        * outter Grid required for alignment will still grab the
        * mouseover events inappropriately (wrong hovertext
        * problem).
        *
        * <p>
        *
        * Not certain but, apparently, IE6 requires that, if
        * you want to apply this trick when the outer element
        * is a table you must use another table as the
        * inner element. At least, the div inner element approach
        * I used at first (that basically worked in Firefox), made
        * both parent and child invisible in IE6.
        * <p>
        *
        * In summary:
        *
        * The upside: alignment without inappropriate event occlusion
        * The downside: the extra Grid element saps performance
        *
        """

        DOM.setStyleAttribute(getElement(), "visibility","hidden")
        DOM.setStyleAttribute(self.innerGrid.getElement(),
                              "visibility", "visible")


    """
    * Sets properties only if they have changed; to replace
    * expensive DOM calls with cheap inequality tests.
    *
    * TODO: Investigate moving properties that are guaranteed to be
    * the same across all elements in the image panel (backgroundColor,
    * borderColor, borderWidth, borderStyle, image url, possibly more)
    * into a single style for the image panel as a whole, and just add
    * that styleName to each image, and set the properties once, in
    * the style. If this worked, it would save both time and space
    * (style would be internal and not intended for direct developer
    * access, since in some charts canvas, not styles, would be
    * responsible for these curve properties), and the same approach
    * could be applied to the labelPanel.
    *
    """

    def setReusableProperties(self, fontSize, fontStyle, fontWeight, fontColor,
                                    hAlign, vAlign, labelText,
                                    isHTML, labelWidget):

        if self.fontSize != fontSize:
            DOM.setIntStyleAttribute(innerGrid.getElement(), "fontSize", fontSize)
            self.fontSize = fontSize

        if self.fontStyle != fontStyle:
            DOM.setStyleAttribute(innerGrid.getElement(), "fontStyle", fontStyle)
            self.fontStyle = fontStyle

        if self.fontWeight != fontWeight:
            DOM.setStyleAttribute(innerGrid.getElement(), "fontWeight", fontWeight)
            self.fontWeight = fontWeight

        if self.fontColor != fontColor:
            DOM.setStyleAttribute(innerGrid.getElement(),"color", fontColor)
            self.fontColor = fontColor

        if self.hAlign != hAlign:
            getCellFormatter().setHorizontalAlignment(0,0,hAlign)
            # without this, only IE6-quirks doesn't quite align right:
            innerGrid.getCellFormatter().setHorizontalAlignment(0,0,hAlign)
            self.hAlign = hAlign

        if self.vAlign != vAlign:
            getCellFormatter().setVerticalAlignment(0,0,vAlign)
            # without this, only IE6-quirks doesn't quite align right:
            innerGrid.getCellFormatter().setVerticalAlignment(0,0,vAlign)
            self.vAlign = vAlign


        if None != labelWidget:
            if self.labelWidget != labelWidget:
                innerGrid.setWidget(0,0,labelWidget)
                self.labelWidget = labelWidget
                self.labelText = None


        elif self.labelText != labelText  or  self.isHTML != isHTML:
            if None == labelText  or  "" == labelText:
                innerGrid.setText(0,0,"")

            elif not isHTML:
                innerGrid.setText(0,0,labelText)

            else:
                innerGrid.setHTML(0, 0, labelText)

            self.isHTML = isHTML
            self.labelText = labelText
            self.labelWidget = None


 # end of class NonoccludingReusuableAlignedLabel

"""
* AbsolutePanel that allows annotations it contains to be easily
* reused, for increased efficiency.
*
"""
class AnnotationRenderingPanel (PartitionedAbsolutePanel):
    labelIndex = 0;                # to-be-added-next label index
    lastVisibleLabel = -1; # just before 1st valid index
    """
    * Returns the inner grid of the first reusuable, non-occluding
    * aligned label in this rendering panel.
    *
    """
    def getFirstInnerAlignedLabel(self):
        result = None
        if labelIndex > 0:
            parent = self.getWidget(0)
            result = parent.getInnerGrid()

        return result


    def __init__(self):
        PartitionedAbsolutePanel.__init__(self)
        """
        * Because of event-occlusion that can occur on all browsers but IE,
        * annotation panels MUST by 0-sized/overflow:visible otherwise they
        * will short-circuit event processing needed for widget annotations
        * added by developer. Graphics rendering panels don't have this
        * constraint and thus can be clipped to the plot area.
        *
        """
        GChart.setOverflow(self, "visible")
        self.setPixelSize(0,0)


    def setLabelPosition(self, lbl, x, y):
        # workaround problem with special meaning of (-1,-1) to
        # setWidgetPosition (makes position off by one pixel).
        if x == -1  and  y == -1:
            x = 0

        setWidgetPosition(lbl, x, y)


    def beginRendering(self):
        labelIndex = 0


    def endRendering(self):
        # hide or remove labels no longer being used
        if optimizeForMemory:
            iLabel = (getWidgetCount()-1)
        else:
            iLabel = lastVisibleLabel
        while iLabel >= labelIndex:
            w = getWidget(iLabel)
            if optimizeForMemory:
                remove(iLabel)
            else:
                w.setVisible(False)
            iLabel -= 1

        lastVisibleLabel = labelIndex-1


    """
    * Creates (or reveals), and configures, an aligned label. Works
    * very similarly to addOrRevealImage.
    *
    """
    def getNextOrNewAlignedLabel(self, fontSize, fontStyle, fontWeight, fontColor, hAlign, vAlign, labelText, isHTML, labelWidget):
        if labelIndex < getWidgetCount():
            result = self.getWidget(labelIndex)
            if None != result.labelWidget  and  labelWidget == result.labelWidget:
                """
                * DOM element actually stored in the label's Grid-cell, and what
                * the label "thinks" is stored there, could be inconsistent if,
                * for example, the same label widget reference was used to
                * define two different points' annotations. In that case, we
                * need to clear the widget reference that the label stores
                * thus making it consistent with what is really in the DOM.
                * <p>
                *
                * This code was added to fix the bug reproduced by
                * TestGChart53.java. See that test for more info.
                *
                *
                """
                e = labelWidget.getElement()
                if None == e  or  (e.getParentElement() != result.innerGrid.getCellFormatter().getElement(0,0)):
                    # the widget' DOM parent isn't label's grid-cell (it was moved)
                    result.labelWidget = None



            if labelIndex > lastVisibleLabel:
                result.setVisible(True)


        else:
            result = NonoccludingReusuableAlignedLabel()
            add(result)

        result.setReusableProperties(fontSize, fontStyle, fontWeight,
                                    fontColor, hAlign, vAlign,
                                    labelText, isHTML, labelWidget)

        if lastVisibleLabel < labelIndex:
            lastVisibleLabel = labelIndex

        labelIndex += 1
        return result


    def renderAnnotation(self, annotation, loc, xCenter, yCenter, symWidth, symHeight, symbol):

        widthUpperBound = annotation.getWidthUpperBound()
        upLeftX = loc.getUpperLeftX(xCenter,
        widthUpperBound, abs(symWidth))
        heightUpperBound = annotation.getHeightUpperBound()
        upLeftY = loc.getUpperLeftY(yCenter,
        heightUpperBound, abs(symHeight))


        alignedLabel = getNextOrNewAlignedLabel(
                                    annotation.getFontSize(),
                                    annotation.getFontStyle(),
                                    annotation.getFontWeight(),
                                    annotation.getFontColor(),
                                    loc.getHorizontalAlignment(),
                                    loc.getVerticalAlignment(),
                                    annotation.getText(), annotation.isHTML(),
                                    annotation.getWidget())
        # If positioning by top or left edges, explicit sizing isn't needed
        # (makes the bounding box tighter, which, for reasons unknown, makes
        # rendering around 10% faster on some browsers and usage scenarios).
        if loc.getHorizontalAlignment() != HasHorizontalAlignment.ALIGN_LEFT:
            alignedLabel.setWidth(widthUpperBound + "px")

        else:
            alignedLabel.setWidth("")


        if loc.getVerticalAlignment() != HasVerticalAlignment.ALIGN_TOP:
            alignedLabel.setHeight(heightUpperBound + "px")

        else:
            alignedLabel.setHeight("")


        setLabelPosition(alignedLabel, upLeftX, upLeftY)



 # end of class AnnotationRenderingPanel

"""*
* Provides support for reusing certain property specifications
* that are likely to be the same, given how images in a GChart
* get reused, and given certain assumptions about which
* properties of the image are most likely to remain unchanged
* between updates.  For the most common scenarios, chart
* updates are significantly faster due to replacing (relatively
* expensive) DOM style attribute setting with (relatively
* cheap) String reference or integer equality tests.
* <p>
*
* For hovertext, the class also lets us defer actual generation
* of the hovertext until they actually mouse over the image,
* saving further time (it's surprisingly expensive just to
* format the numbers and such used in hovertexts).
* <p>
*
* TODO: Since we no longer use events on Image widgets,
* see if we can switch to just using simpler HTML elements,
* if that reduces the overhead associated with a Widget?
*
*
"""
class ReusableImage (Image):
    def __init__(self):
        self.backgroundColor = GChart.USE_CSS
        self.borderColor = GChart.USE_CSS
        self.borderStyle= GChart.USE_CSS
        # the capped border width, times two (to allow half-pixel widths)
        self.cappedBorderWidthX2 = GChart.NAI
        self.width = GChart.NAI
        self.height = GChart.NAI
        self.x = GChart.NAI
        self.y = GChart.NAI
        self.url = None

        Image.__init__(self)


    def setReusableProperties(self, backgroundColor, borderColor, borderStyle, borderWidth, dWidth, dHeight, xD, yD, url):

        # Round two edges, and define width to be their difference.
        # (rounding this way assures bars align with gridlines, etc.)
        newX = int ( round(xD) )
        newW = int ( round(xD + dWidth) - newX )
        newY = int ( round(yD) )
        newH = int ( round(yD + dHeight) - newY )
        thickness = min(newW, newH) 
        # Don't allow borders that would exceed specified width or
        # height. So, if smaller of width, height is at least twice the
        # border width, border width is used as is, otherwise,
        # it's replaced with half the smaller of width, height:
        newCappedBorderWidthX2 = min (2*borderWidth, thickness) 

        """
        * Note: on a GWT absolute panel, the x,y position of the widget is the
        * upper left corner of the widget's border, so x, y need no adjustment
        * to account for an internal (positive) border. Negative (external)
        * borders expand rectangle equally in all directions, so x,y need to
        * shift back to the upper left corner.  Transparent border
        * emulation sets border width to 0, and adjusts element size and
        * position to mimic border transparency (this rather odd feature is
        * required to workaround the IE6 "transparent border" bug)
        *
        """
        if TRANSPARENT_BORDER_COLOR == borderColor:
            #transparency emulation
            if newCappedBorderWidthX2 > 0:
                # to emulate an internal transparent border using a 0 width
                # border, we need to shift the upper left corner by the
                # amount of border, and shrink the size by twice the amount
                # of the border.
                newX += newCappedBorderWidthX2/2; # shift upper left corner
                newY += newCappedBorderWidthX2/2
                newH -= newCappedBorderWidthX2; # shrink size
                newW -= newCappedBorderWidthX2

            # else, external border is just eliminated, no adjustment needed
            newCappedBorderWidthX2 = 0
            borderColor = "transparent"; # because DOM won't accept None
            if backgroundColor == TRANSPARENT_BORDER_COLOR:
                backgroundColor = "transparent"


        elif newCappedBorderWidthX2 < 0:
            newX += newCappedBorderWidthX2/2; # shift upper left corner back
            newY += newCappedBorderWidthX2/2; # to incorporate external border.

        else:
            newH -= newCappedBorderWidthX2; # shrink size to incorporate
            newW -= newCappedBorderWidthX2; # impact of internal border.


        if self.cappedBorderWidthX2 != newCappedBorderWidthX2:
            if 1 == (newCappedBorderWidthX2 % 2):
                # odd pixel 2 x borderWidth needs asymetical borders to fill rect
                # (only positive (internal) borders can have half-pixel widths)
                floorBW = newCappedBorderWidthX2/2
                ceilBW = floorBW+1
                # (top, right, bottom, left) == (floor, floor, ceil, ceil)
                # assures symbol is odd-pixel border-filled in all cases
                DOM.setStyleAttribute(self.getElement(),
                                "borderWidth",
                                floorBW+"px "+floorBW+"px "+
                                ceilBW+"px " + ceilBW+"px ")

            else:
                DOM.setStyleAttribute(self.getElement(),
                "borderWidth", abs(newCappedBorderWidthX2/2)+"px")

            self.cappedBorderWidthX2 = newCappedBorderWidthX2


        if GChart.NAI == self.x:
            # At first, use AbsolutePanel's official API
            # (to insulate us from any future AbsolutePanel
            # changes)
            self.setImagePosition(self, newX, newY)
            self.x = newX
            self.y = newY

        else:
            # for speed, just set the edge positions that changed
            # (works, but bypasses AbsolutePanel's official API)
            if self.x != newX:
                DOM.setStyleAttribute(self.getElement(),"left", newX+"px")
                self.x = newX

            if self.y != newY:
                DOM.setStyleAttribute(self.getElement(),"top", newY+"px")
                self.y = newY



        if self.width != newW:
            setWidth(newW + "px")
            self.width = newW

        if self.height != newH:
            setHeight(newH + "px")
            self.height = newH


        if self.backgroundColor != backgroundColor:
            DOM.setStyleAttribute(self.getElement(), "backgroundColor",
            backgroundColor)
            self.backgroundColor =backgroundColor

        if self.borderColor != borderColor:
            DOM.setStyleAttribute(self.getElement(), "borderColor", borderColor)
            self.borderColor = borderColor

        if self.borderStyle != borderStyle:
            DOM.setStyleAttribute(self.getElement(), "borderStyle",
            borderStyle)
            self.borderStyle = borderStyle


{'fraction_non_alphanumeric': 0.07216746456397259, 'fraction_numerical': 0.010588566600957477, 'mean_word_length': 2.216740141312881, 'pattern_counts': {'":': 0, '<': 73, '<?xml version=': 0, '>': 77, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 24, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5310036', 'n_tokens_mistral': 14648, 'n_tokens_neox': 13694, 'n_words': 5264}