GnuplotGraphView.st
author Claus Gittinger <cg@exept.de>
Sat, 02 May 2020 19:50:29 +0200
changeset 6244 2050c2ab251c
parent 6241 8de2ecadf40b
permissions -rw-r--r--
#REFACTORING by cg class: HierarchicalItem::Example changed: #children (send #label:icon: instead of #labeled:icon:, send #label: instead of #labeled:) class: HierarchicalItem::Example class added: #label: #label:icon: changed: #labeled: #labeled:icon:

"{ Encoding: utf8 }"

"
 COPYRIGHT (c) 2018 by eXept Software AG
              All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"
"{ Package: 'stx:libwidg2' }"

"{ NameSpace: Smalltalk }"

ImageView subclass:#GnuplotGraphView
	instanceVariableNames:'script data title formatHolder gnuplotCommand'
	classVariableNames:''
	poolDictionaries:''
	category:'Views-Misc'
!

!GnuplotGraphView class methodsFor:'documentation'!

copyright
"
 COPYRIGHT (c) 2018 by eXept Software AG
              All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"
!

documentation
"
    displays the graph of the data (in model) using a gnuplot script
    Now also shown in the collection-inspector tab.

    [author:]
        cg

    [instance variables:]

    [class variables:]

    [see also:]

"
!

examples
"
 Notice that everything between [exBegin] and [exEnd] is extracted by the html-doc generator
 to create nicely formatted and clickable executable examples in the generated html-doc.
 (see the browsers class-documentation menu items for more)

 trying the widget as standAlone view (with a fix script):
                                                        [exBegin]
    GnuplotGraphView new 
        script:(GnuplotGraphView defaultScript);
        model:(ValueHolder with:(Random new next:100));
        adjust:#smoothFit;
        extent:400@300;
        open
                                                        [exEnd]

 trying the widget as standAlone view (with no fix script):
                                                        [exBegin]
    GnuplotGraphView new 
        model:(ValueHolder with:(Random new next:100));
        adjust:#smoothFit;
        extent:300@200;
        open
                                                        [exEnd]

 embedded in another view:
                                                        [exBegin]
    |top v|

    top := StandardSystemView new.
    top extent:300@300.
    v := GnuplotGraphView new.  
    v data:(Random new next:100).
    v origin:0.0@0.0 corner:1.0@1.0.
    top add:v.
    top open
                                                        [exEnd]
 use as inspect tab:
                                                        [exBegin]
    (Random new next:100) inspect.
                                                        [exEnd]
"
! !

!GnuplotGraphView class methodsFor:'defaults'!

defaultScript
    "return a default initial gnuplot script"

    ^ self defaultScriptForHistogram
!

defaultScriptForDots
    "a default initial gnuplot script to show a dots diagram"

    ^ '
set term %(outputFormat) size %(width),%(height)
set output "%(outputFile)"
set title "%(title)"
set nokey
plot [-10:110] ''%(data)''
'

    "Modified: / 31-05-2018 / 11:54:43 / Claus Gittinger"
!

defaultScriptForHistogram
    "a default initial gnuplot script to show a histogram"

    ^ '
set term %(outputFormat) size %(width),%(height) 
set output "%(outputFile)"
set title "%(title)"
set nokey
plot [-10:110] ''%(data)'' with histogram 
'

    "Modified: / 31-05-2018 / 11:54:47 / Claus Gittinger"
!

defaultScriptForLines
    "a default initial gnuplot script to show a line diagram"

    ^ '
set term %(outputFormat) size %(width),%(height) 
set output "%(outputFile)"
set title "%(title)"
set nokey
plot ''%(data)'' with lines 
'

    "Modified: / 31-05-2018 / 11:54:52 / Claus Gittinger"
! !

!GnuplotGraphView class methodsFor:'menu specs'!

middleButtonMenuExtraSlice
    "This resource specification was automatically generated
     by the MenuEditor of ST/X."

    "Do not manually edit this!! If it is corrupted,
     the MenuEditor may not be able to read the specification."


    "
     MenuEditor new openOnClass:GnuplotGraphView andSelector:#middleButtonMenuExtraSlice
     (Menu new fromLiteralArrayEncoding:(GnuplotGraphView middleButtonMenuExtraSlice)) startUp
    "

    <resource: #menu>

    ^ 
     #(Menu
        (
         (MenuItem
            label: '-'
          )
         (MenuItem
            enabled: hasNoScript
            label: 'Histogram'
            hideMenuOnActivated: false
            choice: formatHolder
            choiceValue: histogram
          )
         (MenuItem
            enabled: hasNoScript
            label: 'Line Graph'
            hideMenuOnActivated: false
            choice: formatHolder
            choiceValue: lines
          )
         (MenuItem
            enabled: hasNoScript
            label: 'Dots'
            hideMenuOnActivated: false
            choice: formatHolder
            choiceValue: dots
          )
         )
        nil
        nil
      )
! !

!GnuplotGraphView methodsFor:'accessing'!

adjust:layoutSymbol
    image := nil.
    super adjust:layoutSymbol.
    self invalidate
!

data
    ^ data
!

data:something
    data := something.
!

gnuplotCommand:something
    gnuplotCommand := something.
!

hasNoScript
    ^ [ script isEmptyOrNil ]
!

hasScript
    ^ [ script notEmptyOrNil ]
!

magnificationFactor:aNumber
    image := nil.
    super magnificationFactor:aNumber.
    self invalidate
!

script
    ^ script
!

script:something
    script := something.
!

title
    ^ title
!

title:something
    title := something.
! !

!GnuplotGraphView methodsFor:'aspects'!

formatHolder
    formatHolder isNil ifTrue:[
        formatHolder := #histogram asValue.
        formatHolder onChangeSend:#formatChanged to:self.
    ].
    ^ formatHolder
! !

!GnuplotGraphView methodsFor:'defaults'!

defaultScript
    |format|

    format := self formatHolder value.
    format == #histogram ifTrue:[
        ^ self class defaultScriptForHistogram
    ].
    format == #lines ifTrue:[
        ^ self class defaultScriptForLines
    ].
    format == #dots ifTrue:[
        ^ self class defaultScriptForDots
    ].
    ^ self class defaultScript
! !

!GnuplotGraphView methodsFor:'drawing'!

generateDataFileIn:aDirectory
    "the format of data expected by gnuplot depends on the type of plot:
        regular plot: a number of values (each in a separate line)
        multiCol plots: a number of lines, each containing a row
     here, handle common situations;
     however, if the data does not match, the program should have prepared the 
     data into a string and present it as such.
     Strings will be sent as-is to the file"

    |dataFilename printRow|

    dataFilename := (Filename newTemporaryIn:aDirectory) pathName.
    dataFilename := dataFilename asFilename withSuffix:'dat'.

    (data isString or:[data isByteArray]) ifTrue:[
        dataFilename contents:data.
        ^ dataFilename.
    ].

    data isCollection ifFalse:[
        dataFilename contents:data printString.
        ^ dataFilename.
    ].

    printRow := 
        [:s :eachValueOrRow |
            eachValueOrRow isString ifTrue:[
                s nextPutAll:eachValueOrRow
            ] ifFalse:[
                eachValueOrRow isAssociation ifTrue:[
                    eachValueOrRow key printOn:s.
                    s space.
                    eachValueOrRow value printOn:s.
                ] ifFalse:[
                    eachValueOrRow isCollection ifTrue:[
                        eachValueOrRow 
                            do:[:element | element printOn:s]
                            separatedBy:[ s space]
                    ] ifFalse:[
                        eachValueOrRow printOn:s
                    ].
                ].
            ].
            s cr.
    ].

    dataFilename writingFileDo:[:s |
        data isSequenceable ifTrue:[
            data do:[:eachValueOrRow |
                printRow value:s value:eachValueOrRow.
            ].
        ] ifFalse:[
            data keysAndValuesDo:[:eachKey :eachValueOrRow |
                eachKey printOn:s.
                s space.
                printRow value:s value:eachValueOrRow.
            ].
        ].
    ].
    ^ dataFilename
!

generateImage
    "generates the magnifiedImage right away

     self new
        script:(GnuplotGraphView defaultScript);
        data:(RandomGenerator new next:50);
        open
    "
    |scriptUsed command tmpDir outStream errorStream statusCode ok
     dataFilename scriptFilename outFilename argsDict expandedScript
     stdout stderr|

    (scriptUsed := script) isNil ifTrue:[
        scriptUsed := self defaultScript.
    ].

    command := self gnuplotCommand,' "%1"'.
    argsDict := Dictionary new.

    tmpDir := Filename tempDirectory.
    [
        data notEmptyOrNil ifTrue:[
            dataFilename := self generateDataFileIn:tmpDir.
        ] ifFalse:[
            dataFilename := nil.
        ].

        outFilename := (Filename newTemporaryIn:tmpDir) withSuffix:'png'.

        argsDict at:'data' put:(dataFilename isNil ifTrue:[''] ifFalse:[dataFilename baseName]).
        argsDict at:'dataSize' put:(data size).
        argsDict at:'outputFile' put:(outFilename baseName).
        argsDict at:'outputFormat' put:'png'.
        argsDict at:'title' put:(title ? '').
        ((adjustHolder value ? '') includesString:'fit') not
        ifTrue:[
            argsDict at:'width' put:(640 * (self magnificationFactor)).
            argsDict at:'height' put:(400 * (self magnificationFactor)).
        ] ifFalse:[
            argsDict at:'width' put:self width.
            argsDict at:'height' put:self height.
        ].
        expandedScript := scriptUsed bindWithArguments:argsDict.
        scriptFilename := (Filename newTemporaryIn:tmpDir).
        scriptFilename := scriptFilename asFilename withSuffix:'gnuplot'.
        scriptFilename contents:expandedScript.

        command := command bindWith:(scriptFilename baseName).

        outStream := '' writeStream.
        errorStream := '' writeStream.

        ok := OperatingSystem 
            executeCommand:command
            inputFrom:Stdin
            outputTo:outStream
            errorTo:errorStream
            environment:nil
            inDirectory:tmpDir
            lineWise:true
            showWindow:false
            onError:[:status | 
                statusCode := status code.
                ok := false.
            ].

        ok ifFalse:[
            (stderr := errorStream contents) notEmptyOrNil ifTrue:[
                Transcript showCR:(stderr allRed).
            ].
        ] ifTrue:[
            (stdout := outStream contents) notEmpty ifTrue:[
                Transcript showCR:stdout.
            ].
            image := Image fromFile:outFilename.
        ].
    ] ensure:[
        dataFilename notNil ifTrue:[ dataFilename remove ]. 
        outFilename notNil ifTrue:[ outFilename remove ]. 
        scriptFilename notNil ifTrue:[ scriptFilename remove ]. 
    ].
!

generateMagnifiedImage
    image isNil ifTrue:[
        self generateImage  
    ].
    magnifiedImage := smoothMagnifiedImage := image.
"/    super generateMagnifiedImage.
!

gnuplotCommand
    gnuplotCommand isNil ifTrue:[
        gnuplotCommand := OperatingSystem getEnvironment:'GNUPLOT'.
        gnuplotCommand isEmptyOrNil ifTrue:[
            gnuplotCommand := 'gnuplot'
        ].
        (OperatingSystem canExecuteCommand:gnuplotCommand) ifFalse:[
            OperatingSystem isMSWINDOWSlike ifTrue:[
                (OperatingSystem canExecuteCommand:'%PROGRAMFILES%\gnuplot\bin\gnuplot.exe') ifTrue:[
                    gnuplotCommand := '%PROGRAMFILES%\gnuplot\bin\gnuplot.exe'
                ].
            ].
        ].
    ].
    ^ gnuplotCommand

    "
     self basicNew gnuplotCommand
    "
!

redrawX:x y:y width:w height:h
    (OperatingSystem canExecuteCommand:(self gnuplotCommand)) ifFalse:[
        self clearRectangleX:x y:y width:w height:h.
        self showWarning.
        ^ self.
    ].
    super redrawX:x y:y width:w height:h.
!

showWarning
    |infoMsg wString|

    infoMsg := resources string:'No gnuplot command found'.
    wString := gc font widthOf:infoMsg.
    gc displayString:infoMsg x:(self width - wString) // 2 y:(self height // 2).
!

sizeChanged:how
    super sizeChanged:how.
    self invalidate.
!

updateImageAfterSizeChange
    image := nil.
    super updateImageAfterSizeChange
! !

!GnuplotGraphView methodsFor:'menu'!

smoothFitBigMenuItemVisible
    ^ false
!

smoothingMenuItemVisible
    ^ false
! !

!GnuplotGraphView methodsFor:'menu actions'!

formatChanged
    image := magnifiedImage := smoothMagnifiedImage := nil.
    self invalidate
! !

!GnuplotGraphView methodsFor:'mvc'!

updateFromModel
    "the model changes, set my image"

    self data:model value.
! !

!GnuplotGraphView class methodsFor:'documentation'!

version
    ^ '$Header$'
!

version_CVS
    ^ '$Header$'
! !