Beispiel #1
0
    def setPageSize(self):
        """Helper for setting document/page widget size.

        This is called by the widget after receiving updateControlItem
        """
        s = self.widget.settings

        # get margins in pixels
        width = self.posn[2] - self.posn[0]
        height = self.posn[3] - self.posn[1]

        # set up fake painter containing veusz scalings
        helper = document.PaintHelper(self.pagesize,
                                      scaling=self.scaling,
                                      dpi=self.dpi)

        # convert to physical units
        width = s.get('width').convertInverse(width, helper)
        height = s.get('height').convertInverse(height, helper)

        # modify widget margins
        operations = (
            document.OperationSettingSet(s.get('width'), width),
            document.OperationSettingSet(s.get('height'), height),
        )
        self.widget.document.applyOperation(
            document.OperationMultiple(operations,
                                       descr=_('change page size')))
Beispiel #2
0
    def updateControlItem(self, cgi):
        """If control item is moved or resized, this is called."""
        s = self.settings

        # calculate new position coordinate for item
        xpos, ypos = self._getGraphCoords(cgi.widgetposn, cgi.posn[0],
                                          cgi.posn[1])
        if xpos is None or ypos is None:
            return

        xw = abs(cgi.dims[0] / (cgi.widgetposn[2] - cgi.widgetposn[0]))
        yw = abs(cgi.dims[1] / (cgi.widgetposn[1] - cgi.widgetposn[3]))

        # actually do the adjustment on the document
        xp, yp = list(s.xPos), list(s.yPos)
        w, h, r = list(s.width), list(s.height), list(s.rotate)
        xp[cgi.index] = xpos
        yp[cgi.index] = ypos
        w[min(cgi.index, len(w) - 1)] = xw
        h[min(cgi.index, len(h) - 1)] = yw
        r[min(cgi.index, len(r) - 1)] = cgi.angle

        operations = (document.OperationSettingSet(s.get('xPos'), xp),
                      document.OperationSettingSet(s.get('yPos'), yp),
                      document.OperationSettingSet(s.get('width'), w),
                      document.OperationSettingSet(s.get('height'), h),
                      document.OperationSettingSet(s.get('rotate'), r))
        self.document.applyOperation(
            document.OperationMultiple(operations, descr=_('adjust shape')))
Beispiel #3
0
    def actionEmbed(self):
        """Embed external image into veusz document."""

        s = self.settings

        if s.filename == '{embedded}':
            print "Data already embedded"
            return

        # get data from external file
        try:
            f = open(s.filename, 'rb')
            data = f.read()
            f.close()
        except EnvironmentError:
            print "Could not find file. Not embedding."
            return

        # convert to base 64 to make it nicer in the saved file
        encoded = str(qt4.QByteArray(data).toBase64())

        # now put embedded data in hidden setting
        ops = [
            document.OperationSettingSet(s.get('filename'), '{embedded}'),
            document.OperationSettingSet(s.get('embeddedImageData'), encoded)
        ]
        self.document.applyOperation(
            document.OperationMultiple(ops, descr=_('embed image')))
Beispiel #4
0
 def _unlink_all():
     """Unlink all datasets associated with file."""
     self.doc.applyOperation(
         document.OperationMultiple([
             document.OperationDatasetUnlinkByFile(f) for f in filenames
         ],
                                    descr=_('unlink by file')))
Beispiel #5
0
 def _delete_all():
     """Delete all datasets associated with file."""
     self.doc.applyOperation(
         document.OperationMultiple([
             document.OperationDatasetDeleteByFile(f) for f in filenames
         ],
                                    descr=_('delete by file')))
Beispiel #6
0
    def setWidgetMargins(self):
        """A helpful routine for setting widget margins after
        moving or resizing.

        This is called by the widget after receiving updateControlItem
        """
        s = self.widget.settings

        # get margins in pixels
        left = self.posn[0] - self.maxposn[0]
        right = self.maxposn[2] - self.posn[2]
        top = self.posn[1] - self.maxposn[1]
        bottom = self.maxposn[3] - self.posn[3]

        # set up fake painthelper containing veusz scalings
        helper = document.PaintHelper(self.pagesize,
                                      scaling=self.scaling,
                                      dpi=self.dpi)

        # convert to physical units
        left = s.get('leftMargin').convertInverse(left, helper)
        right = s.get('rightMargin').convertInverse(right, helper)
        top = s.get('topMargin').convertInverse(top, helper)
        bottom = s.get('bottomMargin').convertInverse(bottom, helper)

        # modify widget margins
        operations = (document.OperationSettingSet(s.get('leftMargin'), left),
                      document.OperationSettingSet(s.get('rightMargin'),
                                                   right),
                      document.OperationSettingSet(s.get('topMargin'), top),
                      document.OperationSettingSet(s.get('bottomMargin'),
                                                   bottom))
        self.widget.document.applyOperation(
            document.OperationMultiple(operations, descr=_('resize margins')))
Beispiel #7
0
    def mouseReleaseEvent(self, event):
        """Update widget with position."""
        qt4.QGraphicsRectItem.mouseReleaseEvent(self, event)
        highlight = self.checkHighlight()
        if highlight:
            # in a highlight zone so use highlight zone name to set position
            hp, vp = highlight
            hm, vm = 0., 0.
        else:
            # calculate the position of the box to work out Manual fractions
            rect = self.rect()
            rect.translate(self.pos())
            pposn = self.params.parentposn

            hp, vp = 'manual', 'manual'
            hm = (rect.left() - pposn[0]) / (pposn[2] - pposn[0])
            vm = (pposn[3] - rect.bottom()) / (pposn[3] - pposn[1])

        # update widget with positions
        s = self.params.widget.settings
        operations = (
            document.OperationSettingSet(s.get('horzPosn'), hp),
            document.OperationSettingSet(s.get('vertPosn'), vp),
            document.OperationSettingSet(s.get('horzManual'), hm),
            document.OperationSettingSet(s.get('vertManual'), vm),
            )
        self.params.widget.document.applyOperation(
            document.OperationMultiple(operations, descr=_('move key')))
Beispiel #8
0
    def actionUp(self, x=None, y=None, x2=None, y2=None):
        logging.debug('INTERCEPT LINE UP')
        doc = self.document
        s = self.settings
        self.ops = []

        # Settings coerence
        aligned = self.toset(self, 'positioning', 'relative')
        aligned = aligned and self.toset(self, 'mode', 'point-to-point')
        if not aligned:
            logging.debug('Not aligned: apply ops', self.ops)
            doc.applyOperation(
                document.OperationMultiple(self.ops, descr='InterceptUp'))
            self.ops = []

        x = s.xPos[0] if x is None else x
        y = s.yPos[0] if y is None else y
        x2 = s.xPos2[0] if x2 is None else x2
        y2 = s.yPos2[0] if y2 is None else y2
        logging.debug(x, y, x2, y2)
        self.x = x
        self.y = y
        self.x2 = x2
        self.y2 = y2

        # Delete old datapoints and their labels
        for xy, fd in self.crossings.iteritems():
            for c, (name, dpx, dpy) in fd.iteritems():
                dp = self.parent.getChild(name)
                if dp.labelwidget is not None:
                    self.ops.append(
                        document.OperationWidgetDelete(dp.labelwidget))
                self.ops.append(document.OperationWidgetDelete(dp))

        self.apply_ops('interceptDelete')

        # Iterate over all curves in the same graph
        self.crossings = {}  # xy : {idx1:dp1, idx2:dp2, ...}
        for obj in self.parent.children:
            if obj.typename != 'xy':
                continue
            self.intercept_xy(obj)

        # Create everything
        self.apply_ops('interceptCreate')

        # Then update current datapoints
        for xy, fd in self.crossings.copy().iteritems():
            for c, (name, dpx, dpy) in fd.iteritems():
                dp = self.parent.getChild(name)
                # Updata datapoint position
                if not self.settings.showLabels:
                    lbl = dp.labelwidget
                    if lbl is not None:
                        self.ops.append(document.OperationWidgetDelete(lbl))
                dp.actionUp(dpx, dpy)
        # Create everything
        self.apply_ops('interceptLabels')
        return True
Beispiel #9
0
 def resetToDefault(self, name):
     """Reset settings to default."""
     ops = []
     for s in self._settingsatlevel:
         setn = s.get(name)
         ops.append(document.OperationSettingSet(setn, setn.default))
     self.document.applyOperation(
         document.OperationMultiple(ops, descr=_("reset to default")))
Beispiel #10
0
 def _unlink_relation():
     """Unlink dataset from relation."""
     self.doc.applyOperation(
         document.OperationMultiple([
             document.OperationDatasetUnlinkRelation(n)
             for d, n in zip(datasets, dsnames)
             if d.canUnlink() and not d.linked
         ],
                                    descr=_('unlink dataset(s)')))
Beispiel #11
0
 def slotWidgetHideShow(self, widgets, hideshow):
     """Hide or show selected widgets.
     hideshow is True for hiding, False for showing
     """
     ops = [ document.OperationSettingSet(w.settings.get('hide'), hideshow)
             for w in widgets ]
     descr = ('show', 'hide')[hideshow]
     self.document.applyOperation(
         document.OperationMultiple(ops, descr=descr))
Beispiel #12
0
    def actionSetStyleSheet(self):
        """Use the setting as the default in the stylesheet."""

        # get name of stylesheet setting
        sslink = self.setting.getStylesheetLink()
        # apply operation to change it
        self.document.applyOperation(
            document.OperationMultiple(
                [ document.OperationSettingSet(sslink, self.setting.get()),
                  document.OperationSettingSet(self.setting,
                                               self.setting.default) ],
                descr=_("make default style"))
            )
Beispiel #13
0
    def updateControlItem(self, cgi):
        """Update axis position from control item."""

        s = self.settings
        p = cgi.maxposn

        if cgi.zoomed():
            # zoom axis scale
            c1, c2 = self.plotterToGraphCoords(
                cgi.maxposn, N.array([cgi.minzoom, cgi.maxzoom]))
            if c1 > c2:
                c1, c2 = c2, c1
            operations = (
                document.OperationSettingSet(s.get('min'), float(c1)),
                document.OperationSettingSet(s.get('max'), float(c2)),
                )
            self.document.applyOperation(
                document.OperationMultiple(operations, descr=_('zoom axis')))
        elif cgi.moved():
            # move axis
            # convert positions to fractions
            pt1, pt2, ppt1, ppt2 = ( (3, 1, 0, 2), (0, 2, 3, 1)
                                     ) [s.direction == 'horizontal']
            minfrac = abs((cgi.minpos - p[pt1]) / (p[pt2] - p[pt1]))
            maxfrac = abs((cgi.maxpos - p[pt1]) / (p[pt2] - p[pt1]))
            axisfrac = abs((cgi.axispos - p[ppt1]) / (p[ppt2] - p[ppt1]))

            # swap if wrong way around
            if minfrac > maxfrac:
                minfrac, maxfrac = maxfrac, minfrac

            # update doc
            operations = (
                document.OperationSettingSet(s.get('lowerPosition'), minfrac),
                document.OperationSettingSet(s.get('upperPosition'), maxfrac),
                document.OperationSettingSet(s.get('otherPosition'), axisfrac),
                )
            self.document.applyOperation(
                document.OperationMultiple(operations, descr=_('adjust axis')))
Beispiel #14
0
    def initCurve(self,
                  name='Curve',
                  xData='xD',
                  yData='yD',
                  yAxis='y',
                  axisLabel='Curve',
                  graph='/page/graph'):
        """Configure a new curve (appearance, axes positions, data labels"""
        logging.debug('initCurve', name, xData, yData)

        doc = self.doc

        gobj = doc.resolveFullWidgetPath(graph)
        preop = []
        preop.append(document.OperationWidgetAdd(gobj, 'xy', name=name))
        create = True
        for obj in gobj.children:
            if obj.name == yAxis:
                create = False
                break
        if create:
            preop.append(
                document.OperationWidgetAdd(gobj,
                                            'axis-function',
                                            name=yAxis,
                                            direction='vertical'))

        # Create graph and axis (if needed)
        logging.debug('applying operations', preop)
        doc.applyOperation(
            document.OperationMultiple(preop, descr='PlotDataset:Create'))

        obj = gobj.getChild(name)
        n = len(doc.data[yData].data)
        thin = int(max(1, n / 100))
        if n > 10:
            self.toset(obj, 'marker', u'none')
        else:
            self.toset(obj, 'marker', u'circle')
        self.toset(obj, 'markerSize', u'2pt')
        self.toset(obj, 'thinfactor', thin)
        self.toset(obj, 'yData', yData)
        self.toset(obj, 'xData', xData)
        self.toset(obj, 'key', name.replace('_', '\\_'))
        self.toset(obj, 'yAxis', yAxis)

        yax = gobj.getChild(yAxis)
        self.toset(yax, 'label',
                   axisLabel.replace('_', '\\_').replace('/', '.'))
        self.apply_ops('PlotDataset:Associate')
        return True
Beispiel #15
0
 def actionEmbed(self):
     """Override Veusz ImageFile.actionEmbed in order """
     s = self.settings
     self.updateCachedImage()
     # now put embedded data in hidden setting
     ops = [
         document.OperationSettingSet(s.get('embeddedImageData'),
                                      self.cacheembeddata),
         document.OperationSettingSet(s.get('embeddedDataset'), s.dataset),
         document.OperationSettingSet(s.get('embeddedTarget'), s.target),
         document.OperationSettingSet(s.get('codec'), self.dec.comp)
     ]
     self.document.applyOperation(
         document.OperationMultiple(ops, descr='image reference'))
Beispiel #16
0
    def actionZeroMargins(self):
        """Zero margins of plots inside this grid."""

        operations = []
        for c in self.children:
            if isinstance(c, graph.Graph):
                s = c.settings
                for v in ('leftMargin', 'topMargin', 'rightMargin',
                          'bottomMargin'):
                    operations.append(
                        document.OperationSettingSet(s.get(v), '0cm'))

        self.document.applyOperation(
            document.OperationMultiple(operations, descr='zero margins'))
Beispiel #17
0
 def onSettingChanged(self, control, setting, val):
     """Change setting in document."""
     # construct list of operations to change each setting
     ops = []
     sname = setting.name
     if self._root:
         sname = self._root + '/' + sname
     for w in self.widgets:
         s = self.document.resolveFullSettingPath(w.path + '/' + sname)
         if s.val != val:
             ops.append(document.OperationSettingSet(s, val))
     # apply all operations
     if ops:
         self.document.applyOperation(
             document.OperationMultiple(ops, descr=_('change settings')))
Beispiel #18
0
    def updateControlItem(self, cgi, pt1, pt2):
        """If control items are moved, update line."""
        s = self.settings

        # calculate new position coordinate for item
        xpos, ypos = self._getGraphCoords(cgi.widgetposn,
                                          pt1[0], pt1[1])
        if xpos is None or ypos is None:
            return

        x, y = list(s.xPos), list(s.yPos)
        x[min(cgi.index, len(x)-1)] = xpos
        y[min(cgi.index, len(y)-1)] = ypos
        operations = [
            document.OperationSettingSet(s.get('xPos'), x),
            document.OperationSettingSet(s.get('yPos'), y),
            ]
        if s.mode == 'length-angle':
            # convert 2nd point to length, angle
            length = ( math.sqrt( (pt2[0]-pt1[0])**2 + (pt2[1]-pt1[1])**2 ) /
                       (cgi.widgetposn[2]-cgi.widgetposn[0]) )
            angle = ( (math.atan2( pt2[1]-pt1[1], pt2[0]-pt1[0] )
                       * 180. / math.pi) % 360. )
            # update values
            l, a = list(s.length), list(s.angle)
            l[min(cgi.index, len(l)-1)] = length
            a[min(cgi.index, len(a)-1)] = angle
            operations += [
                document.OperationSettingSet(s.get('length'), l),
                document.OperationSettingSet(s.get('angle'), a),
                ]
        else:
            xpos2, ypos2 = self._getGraphCoords(cgi.widgetposn,
                                                pt2[0], pt2[1])
            if xpos is not None and ypos is not None:
                x2, y2 = list(s.xPos2), list(s.yPos2)
                x2[min(cgi.index, len(x2)-1)] = xpos2
                y2[min(cgi.index, len(y2)-1)] = ypos2
                operations += [
                    document.OperationSettingSet(s.get('xPos2'), x2),
                    document.OperationSettingSet(s.get('yPos2'), y2)
                    ]

        self.document.applyOperation(
            document.OperationMultiple(operations, descr=_('adjust lines')) )
Beispiel #19
0
    def handleInternalMove(self, event):
        """Handle a move inside treeview."""

        # make sure qt doesn't handle this
        event.setDropAction(qt4.Qt.IgnoreAction)
        event.ignore()

        if not self.viewport().rect().contains(event.pos()):
            return

        # get widget at event position
        index = self.indexAt(event.pos())
        if not index.isValid():
            index = self.rootIndex()

        # adjust according to drop indicator position
        row = -1
        posn = self.dropIndicatorPosition()
        if posn == qt4.QAbstractItemView.AboveItem:
            row = index.row()
            index = index.parent()
        elif posn == qt4.QAbstractItemView.BelowItem:
            row = index.row() + 1
            index = index.parent()

        if index.isValid():
            parent = self.model().getWidget(index)
            data = str(event.mimeData().data(document.widgetmime))
            if document.isMimeDropable(parent, data):
                # move the widget!
                parentpath = parent.path
                widgetpaths = document.getMimeWidgetPaths(data)
                ops = []
                r = row
                for path in widgetpaths:
                    ops.append(
                        document.OperationWidgetMove(path, parentpath, r))
                    if r >= 0:
                        r += 1

                self.model().document.applyOperation(
                    document.OperationMultiple(ops, descr='move'))
                event.ignore()
Beispiel #20
0
    def updateControlItem(self, cgi):
        """Update position of point given new name and vals."""

        s = self.settings
        pointsX = list(s.xPos)  # make a copy here so original is not modifed
        pointsY = list(s.yPos)
        ind = cgi.index

        # calculate new position coordinate for item
        xpos, ypos = self._getGraphCoords(cgi.widgetposn,
                                          cgi.deltacrosspos[0] + cgi.posn[0],
                                          cgi.deltacrosspos[1] + cgi.posn[1])
        if xpos is None or ypos is None:
            return

        pointsX[ind], pointsY[ind] = xpos, ypos
        operations = (document.OperationSettingSet(s.get('xPos'), pointsX),
                      document.OperationSettingSet(s.get('yPos'), pointsY))
        self.document.applyOperation(
            document.OperationMultiple(operations, descr=_('move label')))
Beispiel #21
0
    def removeRows(self, row, count, parentindex):
        """Remove widgets from parent."""

        if not parentindex.isValid():
            return

        parent = self.getWidget(parentindex)
        self.suspendmodified = True
        self.beginRemoveRows(parentindex, row, row + count - 1)

        # construct an operation for deleting the rows
        deleteops = []
        for w in parent.children[row:row + count]:
            deleteops.append(document.OperationWidgetDelete(w))
        op = document.OperationMultiple(deleteops, descr=_("remove widget(s)"))
        self.document.applyOperation(op)

        self.endRemoveRows()
        self.suspendmodified = False
        return True
    def match_axes(self, first, second):
        """Add `first`-level axis to the list of matched axes of the `second`-level axis.
        The first-level axis will remain first-level.
        The `second` will become `second`-level and will be listed
        in every other first-level axis referenced in its match setting."""

        logging.debug('matching', first, second)
        s = self.doc.resolveFullSettingPath(first + '/linked')
        s1 = self.doc.resolveFullSettingPath(first + '/linkedaxis')
        ops = []
        if self.matching(first):
            print 'Unsetting', first, second
            ops.append(document.OperationSettingSet(s, False))
            ops.append(document.OperationSettingSet(s1, ''))
            s.set(False)
            s1.set('')
        else:
            print 'Setting', first, second, self.matching(first)
            ops.append(document.OperationSettingSet(s, True))
            ops.append(document.OperationSettingSet(s1, second.split('/')[-1]))
        self.doc.applyOperation(
            document.OperationMultiple(ops, descr=_('Axis match')))
Beispiel #23
0
    def setData(self, index, value, role):
        """Called to set the data."""

        if not index.isValid() or role != qt4.Qt.EditRole:
            return False

        row = index.row()
        column = index.column()
        ds = self.document.data[self.dsname]
        data = getattr(ds, ds.columns[index.column()])

        # add new column if necessary
        ops = document.OperationMultiple([], descr=_('add value'))
        if data is None:
            ops.addOperation(
                document.OperationDatasetAddColumn(self.dsname,
                                                   ds.columns[column]))

        # add a row if necessary
        if row == len(ds.data):
            ops.addOperation(
                document.OperationDatasetInsertRow(self.dsname, row, 1))

        # update if conversion okay
        try:
            val = ds.uiConvertToDataItem(value.toString())
        except ValueError:
            return False

        ops.addOperation(
            document.OperationDatasetSetVal(self.dsname, ds.columns[column],
                                            row, val))
        try:
            self.document.applyOperation(ops)
        except RuntimeError:
            return False
        return True
Beispiel #24
0
def add_datasets_to_doc(datasets, doc, original_dataset=False):
    """Create proper Veusz datasets and include in doc via operations"""
    unit = False
    ops = []
    # TODO: find a way to reliably detect the original_dataset for multi-test
    # docs!
    if not original_dataset:
        original_dataset = doc.data['0:t']
    for (pure_dataset_name, values) in datasets.iteritems():
        (data, variable_name, label, error, opt) = values[:5]
        op = new_dataset_operation(original_dataset,
                                   data,
                                   variable_name,
                                   label,
                                   pure_dataset_name,
                                   unit=unit,
                                   error=error,
                                   opt=opt)
        ops.append(op)

    if len(ops) > 0:
        doc.applyOperation(
            document.OperationMultiple(ops, descr='Add new datasets'))
    return len(ops)
Beispiel #25
0
    def doZoomRect(self, endpos):
        """Take the zoom rectangle drawn by the user and do the zooming.
        endpos is a QPoint end point

        This is pretty messy - first we have to work out the graph associated
        to the first point

        Then we have to iterate over each of the plotters, identify their
        axes, and change the range of the axes to match the screen region
        selected.
        """

        # safety net
        if self.grabpos is None or endpos is None:
            return

        # get points corresponding to corners of rectangle
        pt1 = self.grabpos
        pt2 = endpos

        # work out whether it's worthwhile to zoom: only zoom if there
        # are >=5 pixels movement
        if abs((pt2 - pt1).x()) < 10 or abs((pt2 - pt1).y()) < 10:
            return

        # try to work out in which widget the first point is in
        widget = self.painthelper.pointInWidgetBounds(pt1.x(), pt1.y(),
                                                      widgets.Graph)
        if widget is None:
            return

        # convert points on plotter to points on axis for each axis
        xpts = N.array([pt1.x(), pt2.x()])
        ypts = N.array([pt1.y(), pt2.y()])

        # build up operation list to do zoom
        operations = []

        axes = {}
        # iterate over children, to look for plotters
        for c in [
                i for i in widget.children
                if isinstance(i, widgets.GenericPlotter)
        ]:

            # get axes associated with plotter
            caxes = c.parent.getAxes((c.settings.xAxis, c.settings.yAxis))

            for a in caxes:
                if a:
                    axes[a] = True

        # iterate over each axis, and update the ranges
        for axis in axes.iterkeys():
            s = axis.settings
            if s.direction == 'horizontal':
                p = xpts
            else:
                p = ypts

            # convert points on plotter to axis coordinates
            # FIXME: Need To Trap Conversion Errors!
            try:
                r = axis.plotterToGraphCoords(
                    self.painthelper.widgetBounds(axis), p)
            except KeyError:
                continue

            # invert if min and max are inverted
            if r[1] < r[0]:
                r[1], r[0] = r[0], r[1]

            # build up operations to change axis
            if s.min != r[0]:
                operations.append(
                    document.OperationSettingSet(s.get('min'), float(r[0])))
            if s.max != r[1]:
                operations.append(
                    document.OperationSettingSet(s.get('max'), float(r[1])))

        # finally change the axes
        self.document.applyOperation(
            document.OperationMultiple(operations, descr=_('zoom axes')))
Beispiel #26
0
 def _delete():
     """Simply delete dataset."""
     self.doc.applyOperation(
         document.OperationMultiple(
             [document.OperationDatasetDelete(n) for n in dsnames],
             descr=_('delete dataset(s)')))
Beispiel #27
0
class Fit(FunctionPlotter):
    """A plotter to fit a function to data."""

    typename='fit'
    allowusercreation=True
    description=_('Fit a function to data')

    def __init__(self, parent, name=None):
        FunctionPlotter.__init__(self, parent, name=name)

        if type(self) == Fit:
            self.readDefaults()

        self.addAction( widget.Action('fit', self.actionFit,
                                      descr = _('Fit function'),
                                      usertext = _('Fit function')) )

    @classmethod
    def addSettings(klass, s):
        """Construct list of settings."""
        FunctionPlotter.addSettings(s)

        s.add( setting.FloatDict('values',
                                 {'a': 0.0, 'b': 1.0},
                                 descr = _('Variables and fit values'),
                                 usertext=_('Parameters')), 1 )
        s.add( setting.Dataset('xData', 'x',
                               descr = _('Variable containing x data to fit'),
                               usertext=_('X dataset')), 2 )
        s.add( setting.Dataset('yData', 'y',
                               descr = _('Variable containing y data to fit'),
                               usertext=_('Y dataset')), 3 )
        s.add( setting.Bool('fitRange', False,
                            descr = _('Fit only the data between the '
                                      'minimum and maximum of the axis for '
                                      'the function variable'),
                            usertext=_('Fit only range')),
               4 )
        s.add( setting.WidgetChoice(
                'outLabel', '',
                descr=_('Write best fit parameters to this text label '
                        'after fitting'),
                widgettypes=('label',),
                usertext=_('Output label')),
               5 )
        s.add( setting.Str('outExpr', '',
                           descr = _('Output best fitting expression'),
                           usertext=_('Output expression')),
               6, readonly=True )
        s.add( setting.Float('chi2', -1,
                             descr = 'Output chi^2 from fitting',
                             usertext=_('Fit &chi;<sup>2</sup>')),
               7, readonly=True )
        s.add( setting.Int('dof', -1,
                           descr = _('Output degrees of freedom from fitting'),
                           usertext=_('Fit d.o.f.')),
               8, readonly=True )
        s.add( setting.Float('redchi2', -1,
                             descr = _('Output reduced-chi-squared from fitting'),
                             usertext=_('Fit reduced &chi;<sup>2</sup>')),
               9, readonly=True )

        f = s.get('function')
        f.newDefault('a + b*x')
        f.descr = _('Function to fit')

        # modify description
        s.get('min').usertext=_('Min. fit range')
        s.get('max').usertext=_('Max. fit range')

    def providesAxesDependency(self):
        """This widget provides range information about these axes."""
        s = self.settings
        return ( (s.xAxis, 'sx'), (s.yAxis, 'sy') )

    def updateAxisRange(self, axis, depname, axrange):
        """Update range with range of data."""
        dataname = {'sx': 'xData', 'sy': 'yData'}[depname]
        data = self.settings.get(dataname).getData(self.document)
        if data:
            drange = data.getRange()
            if drange:
                axrange[0] = min(axrange[0], drange[0])
                axrange[1] = max(axrange[1], drange[1])

    def initEnviron(self):
        """Copy data into environment."""
        env = self.document.eval_context.copy()
        env.update( self.settings.values )
        return env

    def updateOutputLabel(self, ops, vals, chi2, dof):
        """Use best fit parameters to update text label."""
        s = self.settings
        labelwidget = s.get('outLabel').findWidget()

        if labelwidget is not None:
            # build up a set of X=Y values
            loc = self.document.locale
            txt = []
            for l, v in sorted(vals.iteritems()):
                val = utils.formatNumber(v, '%.4Vg', locale=loc)
                txt.append( '%s = %s' % (l, val) )
            # add chi2 output
            txt.append( r'\chi^{2}_{\nu} = %s/%i = %s' % (
                    utils.formatNumber(chi2, '%.4Vg', locale=loc),
                    dof,
                    utils.formatNumber(chi2/dof, '%.4Vg', locale=loc) ))

            # update label with text
            text = r'\\'.join(txt)
            ops.append( document.OperationSettingSet(
                    labelwidget.settings.get('label') , text ) )

    def actionFit(self):
        """Fit the data."""

        s = self.settings

        # update function for fitting
        try:
            self.checker.check(s.function, s.variable)
        except RuntimeError, e:
            self.logEvalError(e)
            return

        # populate the input parameters
        names = s.values.keys()
        names.sort()
        params = N.array( [s.values[i] for i in names] )

        # FIXME: loads of error handling!!
        d = self.document

        # choose dataset depending on fit variable
        if s.variable == 'x':
            xvals = d.getData(s.xData).data
            ydata = d.getData(s.yData)
            yvals = ydata.data
            yserr = ydata.serr
        else:
            xvals = d.getData(s.yData).data
            ydata = d.getData(s.xData)
            yvals = ydata.data
            yserr = ydata.serr

        # if there are no errors on data
        if yserr is None:
            if ydata.perr is not None and ydata.nerr is not None:
                print "Warning: Symmeterising positive and negative errors"
                yserr = N.sqrt( 0.5*(ydata.perr**2 + ydata.nerr**2) )
            else:
                print "Warning: No errors on y values. Assuming 5% errors."
                yserr = yvals*0.05
                yserr[yserr < 1e-8] = 1e-8
        
        # if the fitRange parameter is on, we chop out data outside the
        # range of the axis
        if s.fitRange:
            # get ranges for axes
            if s.variable == 'x':
                drange = self.parent.getAxes((s.xAxis,))[0].getPlottedRange()
                mask = N.logical_and(xvals >= drange[0], xvals <= drange[1])
            else:
                drange = self.parent.getAxes((s.yAxis,))[0].getPlottedRange()
                mask = N.logical_and(yvals >= drange[0], yvals <= drange[1])
            xvals, yvals, yserr = xvals[mask], yvals[mask], yserr[mask]
            print "Fitting %s from %g to %g" % (s.variable,
                                                drange[0], drange[1])

        # minimum set for fitting
        if s.min != 'Auto':
            if s.variable == 'x':
                mask = xvals >= s.min
            else:
                mask = yvals >= s.min
            xvals, yvals, yserr = xvals[mask], yvals[mask], yserr[mask]

        # maximum set for fitting
        if s.max != 'Auto':
            if s.variable == 'x':
                mask = xvals <= s.max
            else:
                mask = yvals <= s.max
            xvals, yvals, yserr = xvals[mask], yvals[mask], yserr[mask]

        if s.min != 'Auto' or s.max != 'Auto':
            print "Fitting %s between %s and %s" % (s.variable, s.min, s.max)

        # various error checks
        if len(xvals) == 0:
            sys.stderr.write(_('No data values. Not fitting.\n'))
            return
        if len(xvals) != len(yvals) or len(xvals) != len(yserr):
            sys.stderr.write(_('Fit data not equal in length. Not fitting.\n'))
            return
        if len(params) > len(xvals):
            sys.stderr.write(_('No degrees of freedom for fit. Not fitting\n'))
            return

        # actually do the fit, either via Minuit or our own LM fitter
        chi2 = 1
        dof = 1

        if minuit is not None:
            vals, chi2, dof = minuitFit(self.evalfunc, params, names, s.values, xvals, yvals, yserr)
        else:
            print _('Minuit not available, falling back to simple L-M fitting:')
            retn, chi2, dof = utils.fitLM(self.evalfunc, params,
                                          xvals,
                                          yvals, yserr)
            vals = {}
            for i, v in zip(names, retn):
                vals[i] = float(v)

        # list of operations do we can undo the changes
        operations = []
                                      
        # populate the return parameters
        operations.append( document.OperationSettingSet(s.get('values'), vals) )

        # populate the read-only fit quality params
        operations.append( document.OperationSettingSet(s.get('chi2'), float(chi2)) )
        operations.append( document.OperationSettingSet(s.get('dof'), int(dof)) )
        if dof <= 0:
            print _('No degrees of freedom in fit.\n')
            redchi2 = -1.
        else:
            redchi2 = float(chi2/dof)
        operations.append( document.OperationSettingSet(s.get('redchi2'), redchi2) )

        # expression for fit
        expr = self.generateOutputExpr(vals)
        operations.append( document.OperationSettingSet(s.get('outExpr'), expr) )

        self.updateOutputLabel(operations, vals, chi2, dof)

        # actually change all the settings
        d.applyOperation(
            document.OperationMultiple(operations, descr=_('fit')) )