def __call__(self):

        header = _("Reference analysis QC")
        subheader = _("Reference analysis quality control graphs ")

        MinimumResults = self.context.bika_setup.getMinimumResults()

        warning_icon = "<img src='" + self.portal_url + "/++resource++bika.lims.images/warning.png' height='9' width='9'/>"
        error_icon = "<img src='" + self.portal_url + "/++resource++bika.lims.images/exclamation.png' height='9' width='9'/>"

        self.parms = []
        titles = []

        sample_uid = self.request.form.get('ReferenceSampleUID', '')
        sample = self.reference_catalog.lookupObject(sample_uid)
        if not sample:
            message = _("No reference sample was selected.")
            self.context.plone_utils.addPortalMessage(message, 'error')
            return self.default_template()

        self.parms.append({
            'title': _("Reference Sample"),
            'value': sample.Title()
        })
        titles.append(sample.Title())

        service_uid = self.request.form.get('ReferenceServiceUID', '')
        service = self.reference_catalog.lookupObject(service_uid)
        if not service:
            message = _("No analysis services were selected.")
            self.context.plone_utils.addPortalMessage(message, 'error')
            return self.default_template()

        self.contentFilter = {
            'portal_type': 'ReferenceAnalysis',
            'review_state': ['verified', 'published'],
            'path': {
                "query": "/".join(sample.getPhysicalPath()),
                "level": 0
            }
        }

        self.parms.append({
            'title': _("Analysis Service"),
            'value': service.Title()
        })
        titles.append(service.Title())

        val = self.selection_macros.parse_daterange(self.request,
                                                    'getDateVerified',
                                                    'DateVerified')
        if val:
            self.contentFilter[val['contentFilter']
                               [0]] = val['contentFilter'][1]
            self.parms.append(val['parms'])
            titles.append(val['titles'])

        proxies = self.bika_analysis_catalog(self.contentFilter)
        if not proxies:
            message = _("No analyses matched your query")
            self.context.plone_utils.addPortalMessage(message, 'error')
            return self.default_template()

        # Compile a list with all relevant analysis data
        analyses = []

        out_of_range_count = 0
        results = []
        capture_dates = []

        plotdata = ""
        tabledata = []

        for analysis in proxies:
            analysis = analysis.getObject()
            service = analysis.getService()
            resultsrange = \
            [x for x in sample.getReferenceResults() if x['uid'] == service_uid][
                0]
            try:
                result = float(analysis.getResult())
                results.append(result)
            except:
                result = analysis.getResult()
            capture_dates.append(analysis.getResultCaptureDate())

            if result < float(resultsrange['min']) or result > float(
                    resultsrange['max']):
                out_of_range_count += 1

            try:
                precision = str(analysis.getPrecision())
            except:
                precision = "2"

            try:
                formatted_result = str("%." + precision + "f") % result
            except:
                formatted_result = result

            tabledata.append({
                _("Analysis"):
                analysis.getId(),
                _("Result"):
                formatted_result,
                _("Analyst"):
                analysis.getAnalyst(),
                _("Captured"):
                analysis.getResultCaptureDate().strftime(self.date_format_long)
            })

            plotdata += "%s\t%s\t%s\t%s\n" % (
                analysis.getResultCaptureDate().strftime(
                    self.date_format_long), result, resultsrange['min'],
                resultsrange['max'])
        plotdata.encode('utf-8')

        result_values = [int(r) for r in results]
        result_dates = [c for c in capture_dates]

        self.parms += [
            {
                "title": _("Total analyses"),
                "value": len(proxies)
            },
        ]

        # # This variable is output to the TAL
        self.report_data = {
            'header': header,
            'subheader': subheader,
            'parms': self.parms,
            'tables': [],
            'footnotes': [],
        }

        if MinimumResults <= len(proxies):
            plotscript = """
            set terminal png transparent truecolor enhanced size 700,350 font "Verdana, 8"
            set title "%(title)s"
            set xlabel "%(xlabel)s"
            set ylabel "%(ylabel)s"
            set key off
            #set logscale
            set timefmt "%(timefmt)s"
            set xdata time
            set format x "%(xformat)s"
            set xrange ["%(x_start)s":"%(x_end)s"]
            set auto fix
            set offsets graph 0, 0, 1, 1
            set xtics border nomirror rotate by 90 font "Verdana, 5" offset 0,-3
            set ytics nomirror

            f(x) = mean_y
            fit f(x) 'gpw_DATAFILE_gpw' u 1:3 via mean_y
            stddev_y = sqrt(FIT_WSSR / (FIT_NDF + 1))

            plot mean_y-stddev_y with filledcurves y1=mean_y lt 1 lc rgb "#efefef",\
                 mean_y+stddev_y with filledcurves y1=mean_y lt 1 lc rgb "#efefef",\
                 mean_y with lines lc rgb '#ffffff' lw 3,\
                 "gpw_DATAFILE_gpw" using 1:3 title 'data' with points pt 7 ps 1 lc rgb '#0000ee' lw 2,\
                   '' using 1:3 smooth unique lc rgb '#aaaaaa' lw 2,\
                   '' using 1:4 with lines lc rgb '#000000' lw 1,\
                   '' using 1:5 with lines lc rgb '#000000' lw 1""" % \
                         {
                             'title': "",
                             'xlabel': "",
                             'ylabel': service.getUnit(),
                             'x_start': "%s" % min(result_dates).strftime(
                                 self.date_format_short),
                             'x_end': "%s" % max(result_dates).strftime(
                                 self.date_format_short),
                             'timefmt': r'%Y-%m-%d %H:%M',
                             'xformat': '%%Y-%%m-%%d\n%%H:%%M',
                         }

            plot_png = plot(str(plotdata),
                            plotscript=str(plotscript),
                            usefifo=False)

            # Temporary PNG data file
            fh, data_fn = tempfile.mkstemp(suffix='.png')
            os.write(fh, plot_png)
            plot_url = data_fn
            self.request['to_remove'].append(data_fn)
            plot_url = data_fn
        else:
            plot_url = ""

        table = {
            'title':
            "%s: %s (%s)" %
            (t(_("Analysis Service")), service.Title(), service.getKeyword()),
            'columns':
            [_('Analysis'),
             _('Result'),
             _('Analyst'),
             _('Captured')],
            'parms': [],
            'data':
            tabledata,
            'plot_url':
            plot_url,
        }

        self.report_data['tables'].append(table)

        translate = self.context.translate

        ## footnotes
        if out_of_range_count:
            msgid = _("Analyses out of range")
            self.report_data['footnotes'].append("%s %s" %
                                                 (error_icon, t(msgid)))

        self.report_data['parms'].append({
            "title": _("Analyses out of range"),
            "value": out_of_range_count
        })

        title = t(header)
        if titles:
            title += " (%s)" % " ".join(titles)
        return {
            'report_title': title,
            'report_data': self.template(),
        }
Beispiel #2
0
def plot(title,
         xlabel,
         ylabel,
         xtics,
         data,
         width=1024,
         height=768,
         yunit='',
         bare=False):
    title, xlabel, ylabel = map(escape, (title, xlabel, ylabel))
    xtics = ', '.join(
        ['"{}" {:d}'.format(escape(c), i) for i, c in enumerate(xtics)])

    # this data must be well-formed
    keys = sorted(data.keys())
    colwise = [[data[k][i] for k in keys] for i in xrange(len(data[keys[0]]))]
    ymax = math.ceil(max([max(d) for d in data.values()]))
    ntics = 10

    # might want svg
    plotscript = '''
    set terminal pngcairo transparent enhanced size {width:d},{height:d}

    set title '{title}'
    set xlabel '{xlabel}'
    set ylabel '{ylabel}'
    set key outside
    set format y '%g{yunit}'
    set yrange [0:]
    set grid
    set tics scale 0
    set xtics nomirror rotate 90 font ", 8" ({xtics})
    '''.format(**locals())

    if bare:
        plotscript += '''
        set title '{title}' font ", 10" offset 0,-0.8
        unset xlabel
        unset ylabel
        unset xtics
        unset ytics
        unset key

        set bmargin 0.5
        set lmargin 0.5
        set rmargin 0.5
        set tmargin 1
        '''.format(**locals())

    plotscript += 'plot ' + ', '.join([
        '''"gpw_DATAFILE_gpw" using {:d} title '{}' with lines'''.format(
            i + 1, escape(title)) for i, title in enumerate(keys)
    ])

    plotdata = '\n'.join(
        [' '.join(['{:f}'.format(n) for n in row]) for row in colwise]) + '\n'

    if PY3:
        plotscript = plotscript.encode()
    plotout = gpw.plot(plotdata, plotscript=plotscript, usefifo=False)
    return plotout
Beispiel #3
0
    def __call__(self):

        MinimumResults = self.context.bika_setup.getMinimumResults()
        warning_icon = "<img " + \
                       "src='" + self.portal_url + "/++resource++bika.lims.images/warning.png' " + \
                       "height='9' width='9'/>"
        error_icon = "<img " + \
                     "src='" + self.portal_url + "/++resource++bika.lims.images/exclamation.png' " + \
                     "height='9' width='9'/>"

        header = _("Results per sample point")
        subheader = _(
            "Analysis results for per sample point and analysis service")

        self.contentFilter = {'portal_type': 'Analysis',
                              'review_state': ['verified', 'published']}

        parms = []
        titles = []

        val = self.selection_macros.parse_client(self.request)
        if val:
            self.contentFilter[val['contentFilter'][0]] = val['contentFilter'][1]
            parms.append(val['parms'])
            titles.append(val['titles'])

        val = self.selection_macros.parse_samplepoint(self.request)
        sp_uid = val
        if val:
            self.contentFilter[val['contentFilter'][0]] = val['contentFilter'][1]
            parms.append(val['parms'])
            titles.append(val['titles'])

        val = self.selection_macros.parse_sampletype(self.request)
        st_uid = val
        if val:
            self.contentFilter[val['contentFilter'][0]] = val['contentFilter'][1]
            parms.append(val['parms'])
            titles.append(val['titles'])

        val = self.selection_macros.parse_analysisservice(self.request)
        if val:
            self.contentFilter[val['contentFilter'][0]] = val['contentFilter'][1]
            parms.append(val['parms'])
        else:
            message = _("No analysis services were selected.")
            self.context.plone_utils.addPortalMessage(message, 'error')
            return self.default_template()

        val = self.selection_macros.parse_daterange(self.request,
                                                    'getDateSampled',
                                                    'DateSampled')
        if val:
            self.contentFilter[val['contentFilter'][0]] = val['contentFilter'][1]
            parms.append(val['parms'])
            titles.append(val['titles'])

        val = self.selection_macros.parse_state(self.request,
                                                'bika_worksheetanalysis_workflow',
                                                'worksheetanalysis_review_state',
                                                'Worksheet state')
        if val:
            self.contentFilter[val['contentFilter'][0]] = val['contentFilter'][1]
            parms.append(val['parms'])

        # Query the catalog and store analysis data in a dict
        analyses = {}
        out_of_range_count = 0
        in_shoulder_range_count = 0
        analysis_count = 0

        proxies = self.bika_analysis_catalog(self.contentFilter)

        if not proxies:
            message = _("No analyses matched your query")
            self.context.plone_utils.addPortalMessage(message, 'error')
            return self.default_template()

        # # Compile a list of dictionaries, with all relevant analysis data
        for analysis in proxies:
            analysis = analysis.getObject()
            result = analysis.getResult()
            client = analysis.aq_parent.aq_parent
            uid = analysis.UID()
            service = analysis.getService()
            keyword = service.getKeyword()
            service_title = "%s (%s)" % (service.Title(), keyword)
            result_in_range = self.ResultOutOfRange(analysis)

            if service_title not in analyses.keys():
                analyses[service_title] = []
            try:
                result = float(analysis.getResult())
            except:
                # XXX Unfloatable analysis results should be indicated
                continue
            analyses[service_title].append({
                'service': service,
                'obj': analysis,
                'Request ID': analysis.aq_parent.getId(),
                'Analyst': analysis.getAnalyst(),
                'Result': result,
                'Sampled': analysis.getDateSampled(),
                'Captured': analysis.getResultCaptureDate(),
                'Uncertainty': analysis.getUncertainty(),
                'result_in_range': result_in_range,
                'Unit': service.getUnit(),
                'Keyword': keyword,
                'icons': '',
            })
            analysis_count += 1

        keys = analyses.keys()
        keys.sort()

        parms += [
            {"title": _("Total analyses"), "value": analysis_count},
        ]

        ## This variable is output to the TAL
        self.report_data = {
            'header': header,
            'subheader': subheader,
            'parms': parms,
            'tables': [],
            'footnotes': [],
        }

        plotscript = """
        set terminal png transparent truecolor enhanced size 700,350 font "Verdana, 8"
        set title "%(title)s"
        set xlabel "%(xlabel)s"
        set ylabel "%(ylabel)s"
        set key off
        #set logscale
        set timefmt "%(date_format_long)s"
        set xdata time
        set format x "%(date_format_short)s\\n%(time_format)s"
        set xrange ["%(x_start)s":"%(x_end)s"]
        set auto fix
        set offsets graph 0, 0, 1, 1
        set xtics border nomirror rotate by 90 font "Verdana, 5" offset 0,-3
        set ytics nomirror

        f(x) = mean_y
        fit f(x) 'gpw_DATAFILE_gpw' u 1:3 via mean_y
        stddev_y = sqrt(FIT_WSSR / (FIT_NDF + 1))

        plot mean_y-stddev_y with filledcurves y1=mean_y lt 1 lc rgb "#efefef",\
             mean_y+stddev_y with filledcurves y1=mean_y lt 1 lc rgb "#efefef",\
             mean_y with lines lc rgb '#ffffff' lw 3,\
             "gpw_DATAFILE_gpw" using 1:3 title 'data' with points pt 7 ps 1 lc rgb '#0000ee' lw 2,\
               '' using 1:3 smooth unique lc rgb '#aaaaaa' lw 2,\
               '' using 1:4 with lines lc rgb '#000000' lw 1,\
               '' using 1:5 with lines lc rgb '#000000' lw 1"""

        ## Compile plots and format data for display
        for service_title in keys:
            # used to calculate XY axis ranges
            result_values = [int(o['Result']) for o in analyses[service_title]]
            result_dates = [o['Sampled'] for o in analyses[service_title]]

            parms = []
            plotdata = str()

            range_min = ''
            range_max = ''

            for a in analyses[service_title]:

                a['Sampled'] = a['Sampled'].strftime(self.date_format_long) if a[
                    'Sampled'] else ''
                a['Captured'] = a['Captured'].strftime(self.date_format_long) if \
                a['Captured'] else ''

                R = a['Result']
                U = a['Uncertainty']

                a['Result'] = a['obj'].getFormattedResult()

                in_range = a['result_in_range']
                # result out of range
                if str(in_range[0]) == 'False':
                    out_of_range_count += 1
                    a['Result'] = "%s %s" % (a['Result'], error_icon)
                # result almost out of range
                if str(in_range[0]) == '1':
                    in_shoulder_range_count += 1
                    a['Result'] = "%s %s" % (a['Result'], warning_icon)

                spec = {}
                if hasattr(a["obj"], 'specification') and a["obj"].specification:
                    spec = a["obj"].specification

                plotdata += "%s\t%s\t%s\t%s\t%s\n" % (
                    a['Sampled'],
                    R,
                    spec.get("min", ""),
                    spec.get("max", ""),
                    U and U or 0,
                )
                plotdata.encode('utf-8')

            unit = analyses[service_title][0]['Unit']
            if MinimumResults <= len(dict([(d, d) for d in result_dates])):
                _plotscript = str(plotscript) % {
                    'title': "",
                    'xlabel': t(_("Date Sampled")),
                    'ylabel': unit and unit or '',
                    'x_start': "%s" % min(result_dates).strftime(
                        self.date_format_long),
                    'x_end': "%s" % max(result_dates).strftime(
                        self.date_format_long),
                    'date_format_long': self.date_format_long,
                    'date_format_short': self.date_format_short,
                    'time_format': self.time_format,
                }

                plot_png = plot(str(plotdata),
                                plotscript=str(_plotscript),
                                usefifo=False)

                # Temporary PNG data file
                fh, data_fn = tempfile.mkstemp(suffix='.png')
                os.write(fh, plot_png)
                plot_url = data_fn
                self.request['to_remove'].append(data_fn)

                plot_url = data_fn
            else:
                plot_url = ""

            table = {
                'title': "%s: %s" % (
                    t(_("Analysis Service")),
                    service_title),
                'parms': parms,
                'columns': ['Request ID',
                            'Analyst',
                            'Result',
                            'Sampled',
                            'Captured'],
                'data': analyses[service_title],
                'plot_url': plot_url,
            }

            self.report_data['tables'].append(table)

        translate = self.context.translate

        ## footnotes
        if out_of_range_count:
            msgid = _("Analyses out of range")
            self.report_data['footnotes'].append(
                "%s %s" % (error_icon, t(msgid)))
        if in_shoulder_range_count:
            msgid = _("Analyses in error shoulder range")
            self.report_data['footnotes'].append(
                "%s %s" % (warning_icon, t(msgid)))

        self.report_data['parms'].append(
            {"title": _("Analyses out of range"),
             "value": out_of_range_count})
        self.report_data['parms'].append(
            {"title": _("Analyses in error shoulder range"),
             "value": in_shoulder_range_count})

        title = t(header)
        if titles:
            title += " (%s)" % " ".join(titles)
        return {
            'report_title': title,
            'report_data': self.template(),
        }
    def __call__(self):

        MinimumResults = self.context.bika_setup.getMinimumResults()
        warning_icon = "<img " + \
                       "src='" + self.portal_url + "/++resource++bika.lims.images/warning.png' " + \
                       "height='9' width='9'/>"
        error_icon = "<img " + \
                     "src='" + self.portal_url + "/++resource++bika.lims.images/exclamation.png' " + \
                     "height='9' width='9'/>"

        header = _("Results per sample point")
        subheader = _(
            "Analysis results for per sample point and analysis service")

        self.contentFilter = {'portal_type': 'Analysis',
                              'review_state': ['verified', 'published']}

        parms = []
        titles = []

        val = self.selection_macros.parse_client(self.request)
        if val:
            self.contentFilter[val['contentFilter'][0]] = val['contentFilter'][1]
            parms.append(val['parms'])
            titles.append(val['titles'])

        val = self.selection_macros.parse_samplepoint(self.request)
        sp_uid = val
        if val:
            self.contentFilter[val['contentFilter'][0]] = val['contentFilter'][1]
            parms.append(val['parms'])
            titles.append(val['titles'])

        val = self.selection_macros.parse_sampletype(self.request)
        st_uid = val
        if val:
            self.contentFilter[val['contentFilter'][0]] = val['contentFilter'][1]
            parms.append(val['parms'])
            titles.append(val['titles'])

        val = self.selection_macros.parse_analysisservice(self.request)
        if val:
            self.contentFilter[val['contentFilter'][0]] = val['contentFilter'][1]
            parms.append(val['parms'])
        else:
            message = _("No analysis services were selected.")
            self.context.plone_utils.addPortalMessage(message, 'error')
            return self.default_template()

        val = self.selection_macros.parse_daterange(self.request,
                                                    'getDateSampled',
                                                    'DateSampled')
        if val:
            self.contentFilter[val['contentFilter'][0]] = val['contentFilter'][1]
            parms.append(val['parms'])
            titles.append(val['titles'])

        val = self.selection_macros.parse_state(self.request,
                                                'bika_worksheetanalysis_workflow',
                                                'worksheetanalysis_review_state',
                                                'Worksheet state')
        if val:
            self.contentFilter[val['contentFilter'][0]] = val['contentFilter'][1]
            parms.append(val['parms'])

        # Query the catalog and store analysis data in a dict
        analyses = {}
        out_of_range_count = 0
        in_shoulder_range_count = 0
        analysis_count = 0

        proxies = self.bika_analysis_catalog(self.contentFilter)

        if not proxies:
            message = _("No analyses matched your query")
            self.context.plone_utils.addPortalMessage(message, 'error')
            return self.default_template()

        # # Compile a list of dictionaries, with all relevant analysis data
        for analysis in proxies:
            analysis = analysis.getObject()
            result = analysis.getResult()
            client = analysis.aq_parent.aq_parent
            uid = analysis.UID()
            keyword = analysis.getKeyword()
            service_title = "%s (%s)" % (analysis.Title(), keyword)
            result_in_range = self.ResultOutOfRange(analysis)

            if service_title not in analyses.keys():
                analyses[service_title] = []
            try:
                result = float(analysis.getResult())
            except:
                # XXX Unfloatable analysis results should be indicated
                continue
            analyses[service_title].append({
                # The report should not mind taking 'analysis' in place of
                # 'service' - the service field values are placed in analysis.
                'service': analysis,
                'obj': analysis,
                'Request ID': analysis.aq_parent.getId(),
                'Analyst': analysis.getAnalyst(),
                'Result': result,
                'Sampled': analysis.getDateSampled(),
                'Captured': analysis.getResultCaptureDate(),
                'Uncertainty': analysis.getUncertainty(),
                'result_in_range': result_in_range,
                'Unit': analysis.getUnit(),
                'Keyword': keyword,
                'icons': '',
            })
            analysis_count += 1

        keys = analyses.keys()
        keys.sort()

        parms += [
            {"title": _("Total analyses"), "value": analysis_count},
        ]

        ## This variable is output to the TAL
        self.report_data = {
            'header': header,
            'subheader': subheader,
            'parms': parms,
            'tables': [],
            'footnotes': [],
        }

        plotscript = """
        set terminal png transparent truecolor enhanced size 700,350 font "Verdana, 8"
        set title "%(title)s"
        set xlabel "%(xlabel)s"
        set ylabel "%(ylabel)s"
        set key off
        #set logscale
        set timefmt "%(date_format_long)s"
        set xdata time
        set format x "%(date_format_short)s\\n%(time_format)s"
        set xrange ["%(x_start)s":"%(x_end)s"]
        set auto fix
        set offsets graph 0, 0, 1, 1
        set xtics border nomirror rotate by 90 font "Verdana, 5" offset 0,-3
        set ytics nomirror

        f(x) = mean_y
        fit f(x) 'gpw_DATAFILE_gpw' u 1:3 via mean_y
        stddev_y = sqrt(FIT_WSSR / (FIT_NDF + 1))

        plot mean_y-stddev_y with filledcurves y1=mean_y lt 1 lc rgb "#efefef",\
             mean_y+stddev_y with filledcurves y1=mean_y lt 1 lc rgb "#efefef",\
             mean_y with lines lc rgb '#ffffff' lw 3,\
             "gpw_DATAFILE_gpw" using 1:3 title 'data' with points pt 7 ps 1 lc rgb '#0000ee' lw 2,\
               '' using 1:3 smooth unique lc rgb '#aaaaaa' lw 2,\
               '' using 1:4 with lines lc rgb '#000000' lw 1,\
               '' using 1:5 with lines lc rgb '#000000' lw 1"""

        ## Compile plots and format data for display
        for service_title in keys:
            # used to calculate XY axis ranges
            result_values = [int(o['Result']) for o in analyses[service_title]]
            result_dates = [o['Sampled'] for o in analyses[service_title]]

            parms = []
            plotdata = str()

            range_min = ''
            range_max = ''

            for a in analyses[service_title]:

                a['Sampled'] = a['Sampled'].strftime(self.date_format_long) if a[
                    'Sampled'] else ''
                a['Captured'] = a['Captured'].strftime(self.date_format_long) if \
                a['Captured'] else ''

                R = a['Result']
                U = a['Uncertainty']

                a['Result'] = a['obj'].getFormattedResult()

                in_range = a['result_in_range']
                # result out of range
                if str(in_range) == 'False':
                    out_of_range_count += 1
                    a['Result'] = "%s %s" % (a['Result'], error_icon)
                # result almost out of range
                if str(in_range) == '1':
                    in_shoulder_range_count += 1
                    a['Result'] = "%s %s" % (a['Result'], warning_icon)

                spec = {}
                if hasattr(a["obj"], 'specification') and a["obj"].specification:
                    spec = a["obj"].specification

                plotdata += "%s\t%s\t%s\t%s\t%s\n" % (
                    a['Sampled'],
                    R,
                    spec.get("min", ""),
                    spec.get("max", ""),
                    U and U or 0,
                )
                plotdata.encode('utf-8')

            unit = analyses[service_title][0]['Unit']
            if MinimumResults <= len(dict([(d, d) for d in result_dates])):
                _plotscript = str(plotscript) % {
                    'title': "",
                    'xlabel': t(_("Date Sampled")),
                    'ylabel': unit and unit or '',
                    'x_start': "%s" % min(result_dates).strftime(
                        self.date_format_long),
                    'x_end': "%s" % max(result_dates).strftime(
                        self.date_format_long),
                    'date_format_long': self.date_format_long,
                    'date_format_short': self.date_format_short,
                    'time_format': self.time_format,
                }

                plot_png = plot(str(plotdata),
                                plotscript=str(_plotscript),
                                usefifo=False)

                # Temporary PNG data file
                fh, data_fn = tempfile.mkstemp(suffix='.png')
                os.write(fh, plot_png)
                plot_url = data_fn
                self.request['to_remove'].append(data_fn)

                plot_url = data_fn
            else:
                plot_url = ""

            table = {
                'title': "%s: %s" % (
                    t(_("Analysis Service")),
                    service_title),
                'parms': parms,
                'columns': ['Request ID',
                            'Analyst',
                            'Result',
                            'Sampled',
                            'Captured'],
                'data': analyses[service_title],
                'plot_url': plot_url,
            }

            self.report_data['tables'].append(table)

        translate = self.context.translate

        ## footnotes
        if out_of_range_count:
            msgid = _("Analyses out of range")
            self.report_data['footnotes'].append(
                "%s %s" % (error_icon, t(msgid)))
        if in_shoulder_range_count:
            msgid = _("Analyses in error shoulder range")
            self.report_data['footnotes'].append(
                "%s %s" % (warning_icon, t(msgid)))

        self.report_data['parms'].append(
            {"title": _("Analyses out of range"),
             "value": out_of_range_count})
        self.report_data['parms'].append(
            {"title": _("Analyses in error shoulder range"),
             "value": in_shoulder_range_count})

        title = t(header)
        if titles:
            title += " (%s)" % " ".join(titles)
        return {
            'report_title': title,
            'report_data': self.template(),
        }
    def __call__(self):

        MinimumResults = self.context.bika_setup.getMinimumResults()
        warning_icon = (
            "<img "
            + "src='"
            + self.portal_url
            + "/++resource++bika.lims.images/warning.png' "
            + "height='9' width='9'/>"
        )
        error_icon = (
            "<img "
            + "src='"
            + self.portal_url
            + "/++resource++bika.lims.images/exclamation.png' "
            + "height='9' width='9'/>"
        )

        header = _("Results per sample point")
        subheader = _("Analysis results for per sample point and analysis service")

        self.contentFilter = {
            "portal_type": "Analysis",
            "review_state": ["verified", "published"],
            "sort_on": "getDateSampled",
        }

        spec = self.request.form.get("spec", "lab")
        spec_title = (spec == "lab") and _("Lab") or _("Client")

        parms = []
        titles = []

        val = self.selection_macros.parse_client(self.request)
        if val:
            self.contentFilter[val["contentFilter"][0]] = val["contentFilter"][1]
            parms.append(val["parms"])
            titles.append(val["titles"])

        val = self.selection_macros.parse_samplepoint(self.request)
        sp_uid = val
        if val:
            self.contentFilter[val["contentFilter"][0]] = val["contentFilter"][1]
            parms.append(val["parms"])
            titles.append(val["titles"])

        val = self.selection_macros.parse_sampletype(self.request)
        st_uid = val
        if val:
            self.contentFilter[val["contentFilter"][0]] = val["contentFilter"][1]
            parms.append(val["parms"])
            titles.append(val["titles"])

        val = self.selection_macros.parse_analysisservice(self.request)
        if val:
            self.contentFilter[val["contentFilter"][0]] = val["contentFilter"][1]
            parms.append(val["parms"])
        else:
            message = _("No analysis services were selected.")
            self.context.plone_utils.addPortalMessage(message, "error")
            return self.default_template()

        val = self.selection_macros.parse_daterange(self.request, "getDateSampled", "DateSampled")
        if val:
            self.contentFilter[val["contentFilter"][0]] = val["contentFilter"][1]
            parms.append(val["parms"])
            titles.append(val["titles"])

        val = self.selection_macros.parse_state(
            self.request, "bika_worksheetanalysis_workflow", "worksheetanalysis_review_state", "Worksheet state"
        )
        if val:
            self.contentFilter[val["contentFilter"][0]] = val["contentFilter"][1]
            parms.append(val["parms"])

        # Query the catalog and store analysis data in a dict
        analyses = {}
        out_of_range_count = 0
        in_shoulder_range_count = 0
        analysis_count = 0

        proxies = self.bika_analysis_catalog(self.contentFilter)

        if not proxies:
            message = _("No analyses matched your query")
            self.context.plone_utils.addPortalMessage(message, "error")
            return self.default_template()

        cached_specs = {}  # keyed by parent_folder

        def lookup_spec(analysis):
            # If an analysis is OUT OF RANGE, the failed spec values are passed
            # back from the result_in_range function. But if the analysis resuit
            # is IN RANGE, we need to look it up.
            service = analysis["service"]
            keyword = service["Keyword"]
            analysis = analysis["obj"]
            if spec == "client":
                parent = analysis.aq_parent.aq_parent
            else:
                parent = self.context.bika_setup.bika_analysisspecs
            if not parent.UID() in cached_specs:
                proxies = self.bika_setup_catalog(
                    portal_type="AnalysisSpec",
                    getSampleTypeUID=st_uid,
                    path={"query": "/".join(parent.getPhysicalPath()), "level": 0},
                )
                if proxies:
                    spec_obj = proxies[0].getObject()
                    this_spec = spec_obj.getResultsRangeDict()
                else:
                    this_spec = {"min": None, "max": None}
                cached_specs[parent.UID()] = this_spec
            else:
                this_spec = cached_specs[parent.UID()]
            return this_spec

        ## Compile a list of dictionaries, with all relevant analysis data
        for analysis in proxies:
            analysis = analysis.getObject()
            client = analysis.aq_parent.aq_parent
            uid = analysis.UID()
            service = analysis.getService()
            keyword = service.getKeyword()
            service_title = "%s (%s)" % (service.Title(), service.getKeyword())
            result_in_range = analysis.result_in_range(specification=spec)
            try:
                precision = str(service.getPrecision())
            except:
                precision = "2"

            if service_title not in analyses.keys():
                analyses[service_title] = []
            try:
                result = float(analysis.getResult())
            except:
                # XXX Unfloatable analysis results should be indicated
                continue
            analyses[service_title].append(
                {
                    "service": service,
                    "obj": analysis,
                    "Request ID": analysis.aq_parent.getId(),
                    "Analyst": analysis.getAnalyst(),
                    "Result": result,
                    "precision": precision,
                    "Sampled": analysis.getDateSampled(),
                    "Captured": analysis.getResultCaptureDate(),
                    "Uncertainty": analysis.getUncertainty(),
                    "result_in_range": result_in_range,
                    "Unit": service.getUnit(),
                    "Keyword": keyword,
                    "icons": "",
                }
            )
            analysis_count += 1

        keys = analyses.keys()
        keys.sort()

        parms += [
            {"title": _("Total analyses"), "value": analysis_count},
            {"title": _("Analysis specification"), "value": spec_title},
        ]

        ## This variable is output to the TAL
        self.report_data = {"header": header, "subheader": subheader, "parms": parms, "tables": [], "footnotes": []}

        plotscript = """
        set terminal png transparent truecolor enhanced size 700,350 font "Verdana, 8"
        set title "%(title)s"
        set xlabel "%(xlabel)s"
        set ylabel "%(ylabel)s"
        set key off
        #set logscale
        set timefmt "%(date_format_long)s"
        set xdata time
        set format x "%(date_format_short)s\\n%(time_format)s"
        set xrange ["%(x_start)s":"%(x_end)s"]
        set auto fix
        set offsets graph 0, 0, 1, 1
        set xtics border nomirror rotate by 90 font "Verdana, 5" offset 0,-3
        set ytics nomirror

        f(x) = mean_y
        fit f(x) 'gpw_DATAFILE_gpw' u 1:3 via mean_y
        stddev_y = sqrt(FIT_WSSR / (FIT_NDF + 1))

        plot mean_y-stddev_y with filledcurves y1=mean_y lt 1 lc rgb "#efefef",\
             mean_y+stddev_y with filledcurves y1=mean_y lt 1 lc rgb "#efefef",\
             mean_y with lines lc rgb '#ffffff' lw 3,\
             "gpw_DATAFILE_gpw" using 1:3 title 'data' with points pt 7 ps 1 lc rgb '#0000ee' lw 2,\
               '' using 1:3 smooth unique lc rgb '#aaaaaa' lw 2,\
               '' using 1:4 with lines lc rgb '#000000' lw 1,\
               '' using 1:5 with lines lc rgb '#000000' lw 1"""

        ## Compile plots and format data for display
        for service_title in keys:
            # used to calculate XY axis ranges
            result_values = [int(o["Result"]) for o in analyses[service_title]]
            result_dates = [o["Sampled"] for o in analyses[service_title]]

            parms = []
            plotdata = str()

            range_min = ""
            range_max = ""

            for a in analyses[service_title]:

                a["Sampled"] = a["Sampled"].strftime(self.date_format_long)
                a["Captured"] = a["Captured"].strftime(self.date_format_long)

                R = a["Result"]
                U = a["Uncertainty"]

                a["Result"] = str("%." + precision + "f") % a["Result"]

                in_range = a["result_in_range"]
                # in-range: lookup spec, if possible
                if in_range[1] == None:
                    this_spec_results = lookup_spec(a)
                    if this_spec_results and a["Keyword"] in this_spec_results:
                        this_spec = this_spec_results[a["Keyword"]]
                        in_range[1] == this_spec
                # If no specs are supplied, fake them
                # and do not print specification values or errors
                a["range_min"] = in_range[1] and in_range[1]["min"] or ""
                a["range_max"] = in_range[1] and in_range[1]["max"] or ""
                if a["range_min"] and a["range_max"]:
                    range_min = a["range_min"]
                    range_max = a["range_max"]
                    # result out of range
                    if str(in_range[0]) == "False":
                        out_of_range_count += 1
                        a["Result"] = "%s %s" % (a["Result"], error_icon)
                    # result almost out of range
                    if str(in_range[0]) == "1":
                        in_shoulder_range_count += 1
                        a["Result"] = "%s %s" % (a["Result"], warning_icon)
                else:
                    a["range_min"] = min(result_values)
                    a["range_max"] = max(result_values)

                plotdata += "%s\t%s\t%s\t%s\t%s\n" % (a["Sampled"], R, range_min, range_max, U and U or 0)
                plotdata.encode("utf-8")

            if range_min and range_max:
                spec_str = "%s: %s, %s: %s" % (
                    self.context.translate(_("Range min")),
                    range_min,
                    self.context.translate(_("Range max")),
                    range_max,
                )
                parms.append({"title": _("Specification"), "value": spec_str})

            unit = analyses[service_title][0]["Unit"]
            if MinimumResults <= len(dict([(d, d) for d in result_dates])):
                _plotscript = str(plotscript) % {
                    "title": "",
                    "xlabel": self.context.translate(_("Date Sampled")),
                    "ylabel": unit and unit or "",
                    "x_start": "%s" % min(result_dates).strftime(self.date_format_long),
                    "x_end": "%s" % max(result_dates).strftime(self.date_format_long),
                    "date_format_long": self.date_format_long,
                    "date_format_short": self.date_format_short,
                    "time_format": self.time_format,
                }

                plot_png = plot(str(plotdata), plotscript=str(_plotscript), usefifo=False)

                # Temporary PNG data file
                fh, data_fn = tempfile.mkstemp(suffix=".png")
                os.write(fh, plot_png)
                plot_url = data_fn
                self.request["to_remove"].append(data_fn)

                plot_url = data_fn
            else:
                plot_url = ""

            table = {
                "title": "%s: %s" % (self.context.translate(_("Analysis Service")), service_title),
                "parms": parms,
                "columns": ["Request ID", "Analyst", "Result", "Sampled", "Captured"],
                "data": analyses[service_title],
                "plot_url": plot_url,
            }

            self.report_data["tables"].append(table)

        ## footnotes
        if out_of_range_count:
            msgid = _("Analyses out of range")
            translate = self.context.translate
            self.report_data["footnotes"].append("%s %s" % (error_icon, translate(msgid)))
        if in_shoulder_range_count:
            msgid = _("Analyses in error shoulder range")
            self.report_data["footnotes"].append("%s %s" % (warning_icon, self.context.translate(msgid)))

        self.report_data["parms"].append({"title": _("Analyses out of range"), "value": out_of_range_count})
        self.report_data["parms"].append(
            {"title": _("Analyses in error shoulder range"), "value": in_shoulder_range_count}
        )

        title = self.context.translate(header)
        if titles:
            title += " (%s)" % " ".join(titles)
        return {"report_title": title, "report_data": self.template()}
    def __call__(self):

        header = _("Reference analysis QC")
        subheader = _("Reference analysis quality control graphs ")

        MinimumResults = self.context.bika_setup.getMinimumResults()

        warning_icon = "<img src='" + self.portal_url + "/++resource++bika.lims.images/warning.png' height='9' width='9'/>"
        error_icon = "<img src='" + self.portal_url + "/++resource++bika.lims.images/exclamation.png' height='9' width='9'/>"

        self.parms = []
        titles = []

        sample_uid = self.request.form.get('ReferenceSampleUID', '')
        sample = self.reference_catalog.lookupObject(sample_uid)
        if not sample:
            message = _("No reference sample was selected.")
            self.context.plone_utils.addPortalMessage(message, 'error')
            return self.default_template()

        self.parms.append(
            {'title': _("Reference Sample"), 'value': sample.Title()})
        titles.append(sample.Title())

        service_uid = self.request.form.get('ReferenceServiceUID', '')
        service = self.reference_catalog.lookupObject(service_uid)
        if not service:
            message = _("No analysis services were selected.")
            self.context.plone_utils.addPortalMessage(message, 'error')
            return self.default_template()

        self.contentFilter = {'portal_type': 'ReferenceAnalysis',
                              'review_state': ['verified', 'published'],
                              'path': {
                              "query": "/".join(sample.getPhysicalPath()),
                              "level": 0}}

        self.parms.append(
            {'title': _("Analysis Service"), 'value': service.Title()})
        titles.append(service.Title())

        val = self.selection_macros.parse_daterange(self.request,
                                                    'getDateVerified',
                                                    'DateVerified')
        if val:
            self.contentFilter[val['contentFilter'][0]] = val['contentFilter'][1]
            self.parms.append(val['parms'])
            titles.append(val['titles'])

        proxies = self.bika_analysis_catalog(self.contentFilter)
        if not proxies:
            message = _("No analyses matched your query")
            self.context.plone_utils.addPortalMessage(message, 'error')
            return self.default_template()

        # Compile a list with all relevant analysis data
        analyses = []

        out_of_range_count = 0
        results = []
        capture_dates = []

        plotdata = ""
        tabledata = []

        for analysis in proxies:
            analysis = analysis.getObject()
            service = analysis.getService()
            resultsrange = \
            [x for x in sample.getReferenceResults() if x['uid'] == service_uid][
                0]
            try:
                result = float(analysis.getResult())
                results.append(result)
            except:
                result = analysis.getResult()
            capture_dates.append(analysis.getResultCaptureDate())

            if result < float(resultsrange['min']) or result > float(
                    resultsrange['max']):
                out_of_range_count += 1

            try:
                precision = str(analysis.getPrecision())
            except:
                precision = "2"

            try:
                formatted_result = str("%." + precision + "f") % result
            except:
                formatted_result = result

            tabledata.append({_("Analysis"): analysis.getId(),
                              _("Result"): formatted_result,
                              _("Analyst"): analysis.getAnalyst(),
                              _(
                                  "Captured"): analysis.getResultCaptureDate().strftime(
                                  self.date_format_long)})

            plotdata += "%s\t%s\t%s\t%s\n" % (
                analysis.getResultCaptureDate().strftime(self.date_format_long),
                result,
                resultsrange['min'],
                resultsrange['max']
            )
        plotdata.encode('utf-8')

        result_values = [int(r) for r in results]
        result_dates = [c for c in capture_dates]

        self.parms += [
            {"title": _("Total analyses"), "value": len(proxies)},
        ]

        # # This variable is output to the TAL
        self.report_data = {
            'header': header,
            'subheader': subheader,
            'parms': self.parms,
            'tables': [],
            'footnotes': [],
        }

        if MinimumResults <= len(proxies):
            plotscript = """
            set terminal png transparent truecolor enhanced size 700,350 font "Verdana, 8"
            set title "%(title)s"
            set xlabel "%(xlabel)s"
            set ylabel "%(ylabel)s"
            set key off
            #set logscale
            set timefmt "%(timefmt)s"
            set xdata time
            set format x "%(xformat)s"
            set xrange ["%(x_start)s":"%(x_end)s"]
            set auto fix
            set offsets graph 0, 0, 1, 1
            set xtics border nomirror rotate by 90 font "Verdana, 5" offset 0,-3
            set ytics nomirror

            f(x) = mean_y
            fit f(x) 'gpw_DATAFILE_gpw' u 1:3 via mean_y
            stddev_y = sqrt(FIT_WSSR / (FIT_NDF + 1))

            plot mean_y-stddev_y with filledcurves y1=mean_y lt 1 lc rgb "#efefef",\
                 mean_y+stddev_y with filledcurves y1=mean_y lt 1 lc rgb "#efefef",\
                 mean_y with lines lc rgb '#ffffff' lw 3,\
                 "gpw_DATAFILE_gpw" using 1:3 title 'data' with points pt 7 ps 1 lc rgb '#0000ee' lw 2,\
                   '' using 1:3 smooth unique lc rgb '#aaaaaa' lw 2,\
                   '' using 1:4 with lines lc rgb '#000000' lw 1,\
                   '' using 1:5 with lines lc rgb '#000000' lw 1""" % \
                         {
                             'title': "",
                             'xlabel': "",
                             'ylabel': service.getUnit(),
                             'x_start': "%s" % min(result_dates).strftime(
                                 self.date_format_short),
                             'x_end': "%s" % max(result_dates).strftime(
                                 self.date_format_short),
                             'timefmt': r'%Y-%m-%d %H:%M',
                             'xformat': '%%Y-%%m-%%d\n%%H:%%M',
                         }

            plot_png = plot(str(plotdata), plotscript=str(plotscript),
                            usefifo=False)

            # Temporary PNG data file
            fh, data_fn = tempfile.mkstemp(suffix='.png')
            os.write(fh, plot_png)
            plot_url = data_fn
            self.request['to_remove'].append(data_fn)
            plot_url = data_fn
        else:
            plot_url = ""

        table = {
            'title': "%s: %s (%s)" % (
                t(_("Analysis Service")),
                service.Title(),
                service.getKeyword()
            ),
            'columns': [_('Analysis'),
                        _('Result'),
                        _('Analyst'),
                        _('Captured')],
            'parms': [],
            'data': tabledata,
            'plot_url': plot_url,
        }

        self.report_data['tables'].append(table)

        translate = self.context.translate

        ## footnotes
        if out_of_range_count:
            msgid = _("Analyses out of range")
            self.report_data['footnotes'].append(
                "%s %s" % (error_icon, t(msgid)))

        self.report_data['parms'].append(
            {"title": _("Analyses out of range"),
             "value": out_of_range_count})

        title = t(header)
        if titles:
            title += " (%s)" % " ".join(titles)
        return {
            'report_title': title,
            'report_data': self.template(),
        }
    def __call__(self):

        MinimumResults = self.context.bika_setup.getMinimumResults()
        warning_icon = "<img " +\
            "src='"+self.portal_url+"/++resource++bika.lims.images/warning.png' " +\
            "height='9' width='9'/>"
        error_icon = "<img " +\
            "src='"+self.portal_url+"/++resource++bika.lims.images/exclamation.png' " +\
            "height='9' width='9'/>"

        header = _("Results per sample point")
        subheader = _(
            "Analysis results for per sample point and analysis service")

        self.contentFilter = {
            'portal_type': 'Analysis',
            'review_state': ['verified', 'published'],
            'sort_on': "getDateSampled"
        }

        spec = self.request.form.get('spec', 'lab')
        spec_title = (spec == 'lab') and _("Lab") or _("Client")

        parms = []
        titles = []

        val = self.selection_macros.parse_client(self.request)
        if val:
            self.contentFilter[val['contentFilter']
                               [0]] = val['contentFilter'][1]
            parms.append(val['parms'])
            titles.append(val['titles'])

        val = self.selection_macros.parse_samplepoint(self.request)
        sp_uid = val
        if val:
            self.contentFilter[val['contentFilter']
                               [0]] = val['contentFilter'][1]
            parms.append(val['parms'])
            titles.append(val['titles'])

        val = self.selection_macros.parse_sampletype(self.request)
        st_uid = val
        if val:
            self.contentFilter[val['contentFilter']
                               [0]] = val['contentFilter'][1]
            parms.append(val['parms'])
            titles.append(val['titles'])

        val = self.selection_macros.parse_analysisservice(self.request)
        if val:
            self.contentFilter[val['contentFilter']
                               [0]] = val['contentFilter'][1]
            parms.append(val['parms'])
        else:
            message = _("No analysis services were selected.")
            self.context.plone_utils.addPortalMessage(message, 'error')
            return self.default_template()

        val = self.selection_macros.parse_daterange(self.request,
                                                    'getDateSampled',
                                                    'DateSampled')
        if val:
            self.contentFilter[val['contentFilter']
                               [0]] = val['contentFilter'][1]
            parms.append(val['parms'])
            titles.append(val['titles'])

        val = self.selection_macros.parse_state(
            self.request, 'bika_worksheetanalysis_workflow',
            'worksheetanalysis_review_state', 'Worksheet state')
        if val:
            self.contentFilter[val['contentFilter']
                               [0]] = val['contentFilter'][1]
            parms.append(val['parms'])

        # Query the catalog and store analysis data in a dict
        analyses = {}
        out_of_range_count = 0
        in_shoulder_range_count = 0
        analysis_count = 0

        proxies = self.bika_analysis_catalog(self.contentFilter)

        if not proxies:
            message = _("No analyses matched your query")
            self.context.plone_utils.addPortalMessage(message, 'error')
            return self.default_template()

        cached_specs = {}  # keyed by parent_folder

        def lookup_spec(analysis):
            # If an analysis is OUT OF RANGE, the failed spec values are passed
            # back from the result_in_range function. But if the analysis resuit
            # is IN RANGE, we need to look it up.
            service = analysis['service']
            keyword = service['Keyword']
            analysis = analysis['obj']
            if spec == "client":
                parent = analysis.aq_parent.aq_parent
            else:
                parent = self.context.bika_setup.bika_analysisspecs
            if not parent.UID() in cached_specs:
                proxies = self.bika_setup_catalog(
                    portal_type='AnalysisSpec',
                    getSampleTypeUID=st_uid,
                    path={
                        "query": "/".join(parent.getPhysicalPath()),
                        "level": 0
                    })
                if proxies:
                    spec_obj = proxies[0].getObject()
                    this_spec = spec_obj.getResultsRangeDict()
                else:
                    this_spec = {'min': None, 'max': None}
                cached_specs[parent.UID()] = this_spec
            else:
                this_spec = cached_specs[parent.UID()]
            return this_spec

        ## Compile a list of dictionaries, with all relevant analysis data
        for analysis in proxies:
            analysis = analysis.getObject()
            client = analysis.aq_parent.aq_parent
            uid = analysis.UID()
            service = analysis.getService()
            keyword = service.getKeyword()
            service_title = "%s (%s)" % (service.Title(), service.getKeyword())
            result_in_range = analysis.result_in_range(specification=spec)
            try:
                precision = str(service.getPrecision())
            except:
                precision = "2"

            if service_title not in analyses.keys():
                analyses[service_title] = []
            try:
                result = float(analysis.getResult())
            except:
                # XXX Unfloatable analysis results should be indicated
                continue
            analyses[service_title].append({
                'service':
                service,
                'obj':
                analysis,
                'Request ID':
                analysis.aq_parent.getId(),
                'Analyst':
                analysis.getAnalyst(),
                'Result':
                result,
                'precision':
                precision,
                'Sampled':
                analysis.getDateSampled(),
                'Captured':
                analysis.getResultCaptureDate(),
                'Uncertainty':
                analysis.getUncertainty(),
                'result_in_range':
                result_in_range,
                'Unit':
                service.getUnit(),
                'Keyword':
                keyword,
                'icons':
                '',
            })
            analysis_count += 1

        keys = analyses.keys()
        keys.sort()

        parms += [
            {
                "title": _("Total analyses"),
                "value": analysis_count
            },
            {
                "title": _("Analysis specification"),
                "value": spec_title
            },
        ]

        ## This variable is output to the TAL
        self.report_data = {
            'header': header,
            'subheader': subheader,
            'parms': parms,
            'tables': [],
            'footnotes': [],
        }

        plotscript = """
        set terminal png transparent truecolor enhanced size 700,350 font "Verdana, 8"
        set title "%(title)s"
        set xlabel "%(xlabel)s"
        set ylabel "%(ylabel)s"
        set key off
        #set logscale
        set timefmt "%(date_format_long)s"
        set xdata time
        set format x "%(date_format_short)s\\n%(time_format)s"
        set xrange ["%(x_start)s":"%(x_end)s"]
        set auto fix
        set offsets graph 0, 0, 1, 1
        set xtics border nomirror rotate by 90 font "Verdana, 5" offset 0,-3
        set ytics nomirror

        f(x) = mean_y
        fit f(x) 'gpw_DATAFILE_gpw' u 1:3 via mean_y
        stddev_y = sqrt(FIT_WSSR / (FIT_NDF + 1))

        plot mean_y-stddev_y with filledcurves y1=mean_y lt 1 lc rgb "#efefef",\
             mean_y+stddev_y with filledcurves y1=mean_y lt 1 lc rgb "#efefef",\
             mean_y with lines lc rgb '#ffffff' lw 3,\
             "gpw_DATAFILE_gpw" using 1:3 title 'data' with points pt 7 ps 1 lc rgb '#0000ee' lw 2,\
               '' using 1:3 smooth unique lc rgb '#aaaaaa' lw 2,\
               '' using 1:4 with lines lc rgb '#000000' lw 1,\
               '' using 1:5 with lines lc rgb '#000000' lw 1"""

        ## Compile plots and format data for display
        for service_title in keys:
            # used to calculate XY axis ranges
            result_values = [int(o['Result']) for o in analyses[service_title]]
            result_dates = [o['Sampled'] for o in analyses[service_title]]

            parms = []
            plotdata = str()

            range_min = ''
            range_max = ''

            for a in analyses[service_title]:

                a['Sampled'] = a['Sampled'].strftime(self.date_format_long)
                a['Captured'] = a['Captured'].strftime(self.date_format_long)

                R = a['Result']
                U = a['Uncertainty']

                a['Result'] = str("%." + precision + "f") % a['Result']

                in_range = a['result_in_range']
                # in-range: lookup spec, if possible
                if in_range[1] == None:
                    this_spec_results = lookup_spec(a)
                    if this_spec_results and a['Keyword'] in this_spec_results:
                        this_spec = this_spec_results[a['Keyword']]
                        in_range[1] == this_spec
                # If no specs are supplied, fake them
                # and do not print specification values or errors
                a['range_min'] = in_range[1] and in_range[1]['min'] or ''
                a['range_max'] = in_range[1] and in_range[1]['max'] or ''
                if a['range_min'] and a['range_max']:
                    range_min = a['range_min']
                    range_max = a['range_max']
                    # result out of range
                    if str(in_range[0]) == 'False':
                        out_of_range_count += 1
                        a['Result'] = "%s %s" % (a['Result'], error_icon)
                    # result almost out of range
                    if str(in_range[0]) == '1':
                        in_shoulder_range_count += 1
                        a['Result'] = "%s %s" % (a['Result'], warning_icon)
                else:
                    a['range_min'] = min(result_values)
                    a['range_max'] = max(result_values)

                plotdata += "%s\t%s\t%s\t%s\t%s\n" % (
                    a['Sampled'],
                    R,
                    range_min,
                    range_max,
                    U and U or 0,
                )
                plotdata.encode('utf-8')

            if range_min and range_max:
                spec_str = "%s: %s, %s: %s" % (
                    self.context.translate(_("Range min")),
                    range_min,
                    self.context.translate(_("Range max")),
                    range_max,
                )
                parms.append({
                    'title': _('Specification'),
                    'value': spec_str,
                })

            unit = analyses[service_title][0]['Unit']
            if MinimumResults <= len(dict([(d, d) for d in result_dates])):
                _plotscript = str(plotscript) % {
                    'title':
                    "",
                    'xlabel':
                    self.context.translate(_("Date Sampled")),
                    'ylabel':
                    unit and unit or '',
                    'x_start':
                    "%s" % min(result_dates).strftime(self.date_format_long),
                    'x_end':
                    "%s" % max(result_dates).strftime(self.date_format_long),
                    'date_format_long':
                    self.date_format_long,
                    'date_format_short':
                    self.date_format_short,
                    'time_format':
                    self.time_format,
                }

                plot_png = plot(str(plotdata),
                                plotscript=str(_plotscript),
                                usefifo=False)

                # Temporary PNG data file
                fh, data_fn = tempfile.mkstemp(suffix='.png')
                os.write(fh, plot_png)
                plot_url = data_fn
                self.request['to_remove'].append(data_fn)

                plot_url = data_fn
            else:
                plot_url = ""

            table = {
                'title':
                "%s: %s" %
                (self.context.translate(_("Analysis Service")), service_title),
                'parms':
                parms,
                'columns':
                ['Request ID', 'Analyst', 'Result', 'Sampled', 'Captured'],
                'data':
                analyses[service_title],
                'plot_url':
                plot_url,
            }

            self.report_data['tables'].append(table)

        ## footnotes
        if out_of_range_count:
            msgid = _("Analyses out of range")
            translate = self.context.translate
            self.report_data['footnotes'].append(
                "%s %s" % (error_icon, translate(msgid)))
        if in_shoulder_range_count:
            msgid = _("Analyses in error shoulder range")
            self.report_data['footnotes'].append(
                "%s %s" % (warning_icon, self.context.translate(msgid)))

        self.report_data['parms'].append({
            "title": _("Analyses out of range"),
            "value": out_of_range_count
        })
        self.report_data['parms'].append({
            "title":
            _("Analyses in error shoulder range"),
            "value":
            in_shoulder_range_count
        })

        title = self.context.translate(header)
        if titles:
            title += " (%s)" % " ".join(titles)
        return {
            'report_title': title,
            'report_data': self.template(),
        }
    def __call__(self):
        MinimumResults = self.context.bika_setup.getMinimumResults()
        warning_icon = "<img " +\
            "src='"+self.portal_url+"/++resource++bika.lims.images/warning.png' " +\
            "height='9' width='9'/>"
        error_icon = "<img " +\
            "src='"+self.portal_url+"/++resource++bika.lims.images/exclamation.png' " +\
            "height='9' width='9'/>"

        header = _("Reference analysis QC")
        subheader = _("Reference analysis quality control graphs ")

        self.contentFilter = {'portal_type': 'ReferenceAnalysis',
                              'review_state': ['verified', 'published'],
                              }

        self.parms = []
        titles = []

        sample_uid = self.request.form.get('ReferenceSampleUID', '')
        sample = self.reference_catalog.lookupObject(sample_uid)
        if not sample:
            message = _("No reference sample was selected.")
            self.context.plone_utils.addPortalMessage(message, 'error')
            return self.default_template()
        self.parms.append({'title':_("Reference Sample"),'value':sample.Title()})
        titles.append(sample.Title())

        service_uid = self.request.form.get('ReferenceServiceUID', '')
        service = self.reference_catalog.lookupObject(service_uid)
        if not service:
            message = _("No analysis services were selected.")
            self.context.plone_utils.addPortalMessage(message, 'error')
            return self.default_template()

        self.contentFilter['path'] = {"query": "/".join(sample.getPhysicalPath()),
                                      "level" : 0 }
        keyword = service.getKeyword()
        unit = service.getUnit()
        service_title = "%s (%s)" % (service.Title(), service.getKeyword())
        try:
            precision = str(service.getPrecision())
        except:
            precision = "2"
        self.parms.append({'title':_("Analysis Service"),'value':service.Title()})
        titles.append(service.Title())

        val = self.selection_macros.parse_daterange(self.request,
                                                    'getDateVerified',
                                                    'DateVerified')
        if val:
            self.contentFilter[val['contentFilter'][0]] = val['contentFilter'][1]
            self.parms.append(val['parms'])
            titles.append(val['titles'])

        # GET min/max for range checking

        proxies = self.bika_analysis_catalog(self.contentFilter)
        if not proxies:
            message = _("No analyses matched your query")
            self.context.plone_utils.addPortalMessage(message, 'error')
            return self.default_template()

        ## Compile a list with all relevant analysis data
        analyses = []
        out_of_range_count = 0
        in_shoulder_range_count = 0
        plot_data = ""
        formatted_results = []
        results = []
        tabledata = []

        for analysis in proxies:
            analysis = analysis.getObject()
            analyses.append(analysis)
            try:
                result = float(analysis.getResult())
            except ValueError:
                pass
            results.append(result)
            captured = self.ulocalized_time(analysis.getResultCaptureDate(), long_format=1)
            analyst = analysis.getAnalyst()
            title = analysis.getId()
            formatted_result = str("%." + precision + "f")%result
            formatted_results.append(formatted_result)
            tabledata.append({_("Analysis"): title,
                              _("Result"): formatted_result,
                              _("Analyst"): analyst,
                              _("Captured"): captured})
        plotdata = "\n".join(formatted_results)
        plotdata.encode('utf-8')

        ### CHECK RANGES

        self.parms += [
            {"title": _("Total analyses"), "value": len(analyses)},
        ]

        ## This variable is output to the TAL
        self.report_data = {
            'header': header,
            'subheader': subheader,
            'parms': self.parms,
            'tables': [],
            'footnotes': [],
        }

        plotscript = """
        set terminal png transparent truecolor enhanced size 700,350 font "Verdana, 8"
        set title "%(title)s"
        set xlabel "%(xlabel)s"
        set ylabel "%(ylabel)s"
        set yzeroaxis
        #set logscale
        set xrange [highest:lowest]
        set xtics border nomirror rotate by 90 font "Verdana, 5" offset 0,-3
        set ytics nomirror

        binwidth = %(highest)-%(lowest)/100
        scale = (binwidth/(%(highest)-%(lowest)))

        bin_number(x) = floor(x/binwidth)
        rounded(x) = binwidth * ( binnumber(x) + 0.5 )

        #f(x) = mean_x
        #fit f(x) 'gpw_DATAFILE_gpw' u 1:2 via mean_x
        #stddev_x = sqrt(FIT_WSSR / (FIT_NDF + 1))
        #
        #plot mean_y-stddev_y with lines y1=mean_y lt 1 lc rgb "#afafaf",\
        #     mean_y+stddev_y with lines y1=mean_y lt 1 lc rgb "#afafaf",\
        #     mean_y with lines lc rgb '#000000' lw 1,\
        plot "gpw_DATAFILE_gpw" using (rounded($1)):(1) smooth frequency
        """

        if MinimumResults <= len(analyses):
            _plotscript = str(plotscript)%\
            {'title': "",
             'xlabel': "",
             'ylabel': "",
             'highest': max(results),
             'lowest': min(results)}

            plot_png = plot(str(plotdata),
                                plotscript=str(_plotscript),
                                usefifo=False)

            print plotdata
            print _plotscript
            print "-------"

            # Temporary PNG data file
            fh,data_fn = tempfile.mkstemp(suffix='.png')
            os.write(fh, plot_png)
            plot_url = data_fn
            self.request['to_remove'].append(data_fn)

            plot_url = data_fn
        else:
            plot_url = ""

        table = {
            'title': "%s: %s" % (
                self.context.translate(_("Analysis Service")),
                service_title),
            'columns': [_('Analysis'),
                        _('Result'),
                        _('Analyst'),
                        _('Captured')],
            'parms':[],
            'data': tabledata,
            'plot_url': plot_url,
        }

        self.report_data['tables'].append(table)

        ## footnotes
        if out_of_range_count:
            msgid = _("Analyses out of range")
            translate = self.context.translate
            self.report_data['footnotes'].append(
                "%s %s" % (error_icon, translate(msgid)))
        if in_shoulder_range_count:
            msgid = _("Analyses in error shoulder range")
            self.report_data['footnotes'].append(
                "%s %s" % (warning_icon, translate(msgid)))

        self.report_data['parms'].append(
            {"title": _("Analyses out of range"),
             "value": out_of_range_count})
        self.report_data['parms'].append(
            {"title": _("Analyses in error shoulder range"),
             "value": in_shoulder_range_count})

        title = self.context.translate(header)
        if titles:
            title += " (%s)" % " ".join(titles)
        return {
            'report_title': title,
            'report_data': self.template(),
        }
    def __call__(self):
        MinimumResults = self.context.bika_setup.getMinimumResults()
        warning_icon = "<img " +\
            "src='"+self.portal_url+"/++resource++bika.lims.images/warning.png' " +\
            "height='9' width='9'/>"
        error_icon = "<img " +\
            "src='"+self.portal_url+"/++resource++bika.lims.images/exclamation.png' " +\
            "height='9' width='9'/>"

        header = _("Reference analysis QC")
        subheader = _("Reference analysis quality control graphs ")

        self.contentFilter = {
            'portal_type': 'ReferenceAnalysis',
            'review_state': ['verified', 'published'],
        }

        self.parms = []
        titles = []

        sample_uid = self.request.form.get('ReferenceSampleUID', '')
        sample = self.reference_catalog.lookupObject(sample_uid)
        if not sample:
            message = _("No reference sample was selected.")
            self.context.plone_utils.addPortalMessage(message, 'error')
            return self.default_template()
        self.parms.append({
            'title': _("Reference Sample"),
            'value': sample.Title()
        })
        titles.append(sample.Title())

        service_uid = self.request.form.get('ReferenceServiceUID', '')
        service = self.reference_catalog.lookupObject(service_uid)
        if not service:
            message = _("No analysis services were selected.")
            self.context.plone_utils.addPortalMessage(message, 'error')
            return self.default_template()

        self.contentFilter['path'] = {
            "query": "/".join(sample.getPhysicalPath()),
            "level": 0
        }
        keyword = service.getKeyword()
        unit = service.getUnit()
        service_title = "%s (%s)" % (service.Title(), service.getKeyword())
        try:
            precision = str(service.getPrecision())
        except:
            precision = "2"
        self.parms.append({
            'title': _("Analysis Service"),
            'value': service.Title()
        })
        titles.append(service.Title())

        val = self.selection_macros.parse_daterange(self.request,
                                                    'getDateVerified',
                                                    'DateVerified')
        if val:
            self.contentFilter[val['contentFilter']
                               [0]] = val['contentFilter'][1]
            self.parms.append(val['parms'])
            titles.append(val['titles'])

        # GET min/max for range checking

        proxies = self.bika_analysis_catalog(self.contentFilter)
        if not proxies:
            message = _("No analyses matched your query")
            self.context.plone_utils.addPortalMessage(message, 'error')
            return self.default_template()

        ## Compile a list with all relevant analysis data
        analyses = []
        out_of_range_count = 0
        in_shoulder_range_count = 0
        plot_data = ""
        formatted_results = []
        results = []
        tabledata = []

        for analysis in proxies:
            analysis = analysis.getObject()
            analyses.append(analysis)
            try:
                result = float(analysis.getResult())
            except ValueError:
                pass
            results.append(result)
            captured = self.ulocalized_time(analysis.getResultCaptureDate(),
                                            long_format=1)
            analyst = analysis.getAnalyst()
            title = analysis.getId()
            formatted_result = str("%." + precision + "f") % result
            formatted_results.append(formatted_result)
            tabledata.append({
                _("Analysis"): title,
                _("Result"): formatted_result,
                _("Analyst"): analyst,
                _("Captured"): captured
            })
        plotdata = "\n".join(formatted_results)
        plotdata.encode('utf-8')

        ### CHECK RANGES

        self.parms += [
            {
                "title": _("Total analyses"),
                "value": len(analyses)
            },
        ]

        ## This variable is output to the TAL
        self.report_data = {
            'header': header,
            'subheader': subheader,
            'parms': self.parms,
            'tables': [],
            'footnotes': [],
        }

        plotscript = """
        set terminal png transparent truecolor enhanced size 700,350 font "Verdana, 8"
        set title "%(title)s"
        set xlabel "%(xlabel)s"
        set ylabel "%(ylabel)s"
        set yzeroaxis
        #set logscale
        set xrange [highest:lowest]
        set xtics border nomirror rotate by 90 font "Verdana, 5" offset 0,-3
        set ytics nomirror

        binwidth = %(highest)-%(lowest)/100
        scale = (binwidth/(%(highest)-%(lowest)))

        bin_number(x) = floor(x/binwidth)
        rounded(x) = binwidth * ( binnumber(x) + 0.5 )

        #f(x) = mean_x
        #fit f(x) 'gpw_DATAFILE_gpw' u 1:2 via mean_x
        #stddev_x = sqrt(FIT_WSSR / (FIT_NDF + 1))
        #
        #plot mean_y-stddev_y with lines y1=mean_y lt 1 lc rgb "#afafaf",\
        #     mean_y+stddev_y with lines y1=mean_y lt 1 lc rgb "#afafaf",\
        #     mean_y with lines lc rgb '#000000' lw 1,\
        plot "gpw_DATAFILE_gpw" using (rounded($1)):(1) smooth frequency
        """

        if MinimumResults <= len(analyses):
            _plotscript = str(plotscript)%\
            {'title': "",
             'xlabel': "",
             'ylabel': "",
             'highest': max(results),
             'lowest': min(results)}

            plot_png = plot(str(plotdata),
                            plotscript=str(_plotscript),
                            usefifo=False)

            print plotdata
            print _plotscript
            print "-------"

            # Temporary PNG data file
            fh, data_fn = tempfile.mkstemp(suffix='.png')
            os.write(fh, plot_png)
            plot_url = data_fn
            self.request['to_remove'].append(data_fn)

            plot_url = data_fn
        else:
            plot_url = ""

        table = {
            'title':
            "%s: %s" %
            (self.context.translate(_("Analysis Service")), service_title),
            'columns':
            [_('Analysis'),
             _('Result'),
             _('Analyst'),
             _('Captured')],
            'parms': [],
            'data':
            tabledata,
            'plot_url':
            plot_url,
        }

        self.report_data['tables'].append(table)

        ## footnotes
        if out_of_range_count:
            msgid = _("Analyses out of range")
            translate = self.context.translate
            self.report_data['footnotes'].append(
                "%s %s" % (error_icon, translate(msgid)))
        if in_shoulder_range_count:
            msgid = _("Analyses in error shoulder range")
            self.report_data['footnotes'].append(
                "%s %s" % (warning_icon, translate(msgid)))

        self.report_data['parms'].append({
            "title": _("Analyses out of range"),
            "value": out_of_range_count
        })
        self.report_data['parms'].append({
            "title":
            _("Analyses in error shoulder range"),
            "value":
            in_shoulder_range_count
        })

        title = self.context.translate(header)
        if titles:
            title += " (%s)" % " ".join(titles)
        return {
            'report_title': title,
            'report_data': self.template(),
        }