show graphs for params

This commit is contained in:
Filip Gralinski 2018-07-28 17:04:27 +02:00
parent 276ca4d596
commit 0a51543957
10 changed files with 122 additions and 12 deletions

View File

@ -3,21 +3,71 @@ module Handler.Graph where
import Import import Import
import Handler.Tables import Handler.Tables
import Handler.Shared (getMainTest, formatParameter)
import Data.Maybe import Data.Maybe
import Data.List ((!!)) import Data.List ((!!))
import Database.Persist.Sql import Database.Persist.Sql
import GEval.Core (MetricValue)
import qualified Data.Map as M
getChallengeGraphDataR :: Text -> Handler Value getChallengeGraphDataR :: Text -> Handler Value
getChallengeGraphDataR challengeName = submissionsToJSON (\_ -> True) challengeName getChallengeGraphDataR challengeName = submissionsToJSON (\_ -> True) challengeName
data ParamGraphItem = ParamGraphItem TableEntry Text Text MetricValue
data ParamGraphSeries = ParamGraphSeries Text [(TableEntry, Text, MetricValue)]
getChallengeParamGraphDataR :: Text -> Text -> Handler Value getChallengeParamGraphDataR :: Text -> Text -> Handler Value
getChallengeParamGraphDataR challengeName paramName = do getChallengeParamGraphDataR challengeName paramName = do
return $ object [ "xs" .= object [ (Entity challengeId _) <- runDB $ getBy404 $ UniqueName challengeName
"data1" .= ("x1" :: Text),
"data2" .= ("x2" :: Text)], (entries, tests) <- getChallengeSubmissionInfos (const True) challengeId
"columns" .= [
["x1"::Text, "10", "30", "40"], let mainTestId = entityKey $ getMainTest tests
["x2", "10", "20", "50"] ]]
let values = map (findParamValue paramName) entries
let items = Data.Maybe.catMaybes $ map (toParamGraphItem mainTestId paramName) $ zip entries values
let series = map (\(label, rs) -> ParamGraphSeries label rs)
$ organizeBy
$ map (\(ParamGraphItem entry label x y) -> (label, (entry, x, y))) items
return $ object [
"xs" .= object (map (\(ParamGraphSeries seriesName _) -> (seriesName .= (xSeriesName seriesName))) series),
"columns" .= ((map toYColumn series) ++ (map toXColumn series))
]
toYColumn :: ParamGraphSeries -> [Text]
toYColumn (ParamGraphSeries seriesName items) =
seriesName : (map (\(_,_,v) -> pack $ show v) items)
toXColumn :: ParamGraphSeries -> [Text]
toXColumn (ParamGraphSeries seriesName items) =
(xSeriesName seriesName) : (map (\(_,x,_) -> x) items)
xSeriesName :: Text -> Text
xSeriesName = (++ "_x")
organizeBy :: (Eq a, Ord a) => [(a, b)] -> [(a, [b])]
organizeBy pList = M.toList $ M.fromListWith (++) $ map (\(x, y) -> (x, [y])) pList
toParamGraphItem :: TestId -> Text -> (TableEntry, Maybe Text) -> Maybe ParamGraphItem
toParamGraphItem _ _ (_, Nothing) = Nothing
toParamGraphItem tid paramName (entry, Just val) = (ParamGraphItem entry label val) <$> join y
where label = unwords (tagsFormatted ++ paramsFormatted)
tagsFormatted =
map (tagName . entityVal . fst)
$ tableEntryTagsInfo entry
paramsFormatted =
map formatParameter
$ filter (\pe -> parameterName pe /= paramName)
$ map entityVal $ tableEntryParams entry
y = evaluationScore <$> lookup tid (tableEntryMapping entry)
findParamValue :: Text -> TableEntry -> Maybe Text
findParamValue paramName entry =
(parameterValue . entityVal) <$> (find (\e -> parameterName (entityVal e) == paramName) $ tableEntryParams entry)
submissionsToJSON :: ((Entity Submission) -> Bool) -> Text -> Handler Value submissionsToJSON :: ((Entity Submission) -> Bool) -> Text -> Handler Value
submissionsToJSON condition challengeName = do submissionsToJSON condition challengeName = do

View File

@ -332,6 +332,12 @@ formatTruncatedScore (Just precision) (Just evaluation) = case evaluationScore e
Just score -> T.pack $ printf "%0.*f" precision score Just score -> T.pack $ printf "%0.*f" precision score
Nothing -> formatFullScore Nothing Nothing -> formatFullScore Nothing
formatParameter :: Parameter -> Text
formatParameter param = parameterName param ++ "=" ++ parameterValue param
formatTest :: Test -> Text
formatTest test = (testName test) ++ "/" ++ (T.pack $ show $ testMetric test)
findFilePossiblyCompressed :: FilePath -> IO (Maybe FilePath) findFilePossiblyCompressed :: FilePath -> IO (Maybe FilePath)
findFilePossiblyCompressed baseFilePath = do findFilePossiblyCompressed baseFilePath = do
let possibleFiles = [baseFilePath] ++ (map (baseFilePath <.>) ["gz", "bz2", "xz"]) let possibleFiles = [baseFilePath] ++ (map (baseFilePath <.>) ["gz", "bz2", "xz"])

View File

@ -38,6 +38,8 @@ import Data.Text (pack, unpack)
import Data.Conduit.SmartSource import Data.Conduit.SmartSource
import Data.List (nub)
getShowChallengeR :: Text -> Handler Html getShowChallengeR :: Text -> Handler Html
getShowChallengeR name = do getShowChallengeR name = do
(Entity challengeId challenge) <- runDB $ getBy404 $ UniqueName name (Entity challengeId challenge) <- runDB $ getBy404 $ UniqueName name
@ -114,7 +116,7 @@ getChallengeHowToR name = do
challengeLayout False challenge (challengeHowTo challenge settings repo (idToBeShown challenge maybeUser) isIDSet isSSHUploaded mToken) challengeLayout False challenge (challengeHowTo challenge settings repo (idToBeShown challenge maybeUser) isIDSet isSSHUploaded mToken)
idToBeShown :: p -> Maybe (Entity User) -> Text idToBeShown :: p -> Maybe (Entity User) -> Text
idToBeShown challenge maybeUser = idToBeShown _ maybeUser =
case maybeUser of case maybeUser of
Just user -> case userLocalId $ entityVal user of Just user -> case userLocalId $ entityVal user of
Just localId -> localId Just localId -> localId
@ -495,10 +497,37 @@ getChallengeSubmissions condition name = do
challengeRepo <- runDB $ get404 $ challengePublicRepo challenge challengeRepo <- runDB $ get404 $ challengePublicRepo challenge
challengeLayout True challenge (challengeAllSubmissionsWidget muserId challenge scheme challengeRepo evaluationMaps tests) let params = sort
$ nub
$ concat
$ map (\entry -> map (parameterName . entityVal) (tableEntryParams entry)) evaluationMaps
challengeLayout True challenge (challengeAllSubmissionsWidget muserId challenge scheme challengeRepo evaluationMaps tests params)
challengeAllSubmissionsWidget :: Maybe UserId -> Challenge -> RepoScheme -> Repo -> [TableEntry] -> [Entity Test] -> [Text] -> WidgetFor App ()
challengeAllSubmissionsWidget muserId challenge scheme challengeRepo submissions tests params =
$(widgetFile "challenge-all-submissions")
where chartJSs = mconcat $ map (getChartJs challenge mainTest) params
mainTest = entityVal $ getMainTest tests
getChartJs :: Challenge -> Test -> Text -> JavascriptUrl (Route App)
getChartJs challenge test param = [julius|
$.getJSON("@{ChallengeParamGraphDataR (challengeName challenge) param}", function(data) {
c3.generate({
bindto: '#chart-' + #{toJSON param},
data: data,
axis: {
x: {
label: #{toJSON param},
},
y: {
label: #{toJSON testFormatted},
}
}
}) });
|]
where testFormatted = formatTest test
challengeAllSubmissionsWidget :: Maybe UserId -> Challenge -> RepoScheme -> Repo -> [TableEntry] -> [Entity Test] -> WidgetFor App ()
challengeAllSubmissionsWidget muserId challenge scheme challengeRepo submissions tests = $(widgetFile "challenge-all-submissions")
challengeLayout :: Bool -> Challenge -> WidgetFor App () -> HandlerFor App Html challengeLayout :: Bool -> Challenge -> WidgetFor App () -> HandlerFor App Html
challengeLayout withHeader challenge widget = do challengeLayout withHeader challenge widget = do

View File

@ -42,6 +42,11 @@ data TableEntry = TableEntry (Entity Submission)
[(Entity Tag, Entity SubmissionTag)] [(Entity Tag, Entity SubmissionTag)]
[Entity Parameter] [Entity Parameter]
-- TODO change into a record
tableEntryParams (TableEntry _ _ _ _ _ paramEnts) = paramEnts
tableEntryMapping (TableEntry _ _ _ mapping _ _) = mapping
tableEntryTagsInfo (TableEntry _ _ _ _ tagsInfo _) = tagsInfo
submissionsTable :: Maybe UserId -> Text -> RepoScheme -> Repo -> [Entity Test] -> Table App TableEntry submissionsTable :: Maybe UserId -> Text -> RepoScheme -> Repo -> [Entity Test] -> Table App TableEntry
submissionsTable mauthId challengeName repoScheme challengeRepo tests = mempty submissionsTable mauthId challengeName repoScheme challengeRepo tests = mempty
++ Table.text "submitter" (formatSubmitter . (\(TableEntry _ _ (Entity _ submitter) _ _ _) -> submitter)) ++ Table.text "submitter" (formatSubmitter . (\(TableEntry _ _ (Entity _ submitter) _ _ _) -> submitter))
@ -65,7 +70,7 @@ descriptionToBeShown s v params = (submissionDescription s) ++ (Data.Text.pack v
"" ""
else else
" " ++ r " " ++ r
paramsShown = Data.Text.unwords $ map (\p -> parameterName p ++ "=" ++ parameterValue p) params paramsShown = Data.Text.unwords $ map formatParameter params
extractScore :: Key Test -> TableEntry -> Maybe Evaluation extractScore :: Key Test -> TableEntry -> Maybe Evaluation
extractScore k (TableEntry _ _ _ m _ _) = lookup k m extractScore k (TableEntry _ _ _ m _ _) = lookup k m
@ -105,7 +110,7 @@ statusCell :: Text -> RepoScheme -> Repo -> (a -> (SubmissionId, Submission, Use
statusCell challengeName repoScheme challengeRepo fun = Table.widget "" (statusCellWidget challengeName repoScheme challengeRepo . fun) statusCell challengeName repoScheme challengeRepo fun = Table.widget "" (statusCellWidget challengeName repoScheme challengeRepo . fun)
resultCell :: Test -> (a -> Maybe Evaluation) -> Table App a resultCell :: Test -> (a -> Maybe Evaluation) -> Table App a
resultCell test fun = hoverTextCell ((testName test) ++ "/" ++ (Data.Text.pack $ show $ testMetric test)) (formatTruncatedScore (testPrecision test) . fun) (formatFullScore . fun) resultCell test fun = hoverTextCell (formatTest test) (formatTruncatedScore (testPrecision test) . fun) (formatFullScore . fun)
statusCellWidget :: Eq a => Text -> RepoScheme -> Repo -> (SubmissionId, Submission, a, Maybe a) -> WidgetFor App () statusCellWidget :: Eq a => Text -> RepoScheme -> Repo -> (SubmissionId, Submission, a, Maybe a) -> WidgetFor App ()
statusCellWidget challengeName repoScheme challengeRepo (submissionId, submission, userId, mauthId) = $(widgetFile "submission-status") statusCellWidget challengeName repoScheme challengeRepo (submissionId, submission, userId, mauthId) = $(widgetFile "submission-status")

1
static/css/c3.min.css vendored Normal file
View File

@ -0,0 +1 @@
.c3 svg{font:10px sans-serif;-webkit-tap-highlight-color:transparent}.c3 line,.c3 path{fill:none;stroke:#000}.c3 text{-webkit-user-select:none;-moz-user-select:none;user-select:none}.c3-bars path,.c3-event-rect,.c3-legend-item-tile,.c3-xgrid-focus,.c3-ygrid{shape-rendering:crispEdges}.c3-chart-arc path{stroke:#fff}.c3-chart-arc rect{stroke:#fff;stroke-width:1}.c3-chart-arc text{fill:#fff;font-size:13px}.c3-grid line{stroke:#aaa}.c3-grid text{fill:#aaa}.c3-xgrid,.c3-ygrid{stroke-dasharray:3 3}.c3-text.c3-empty{fill:grey;font-size:2em}.c3-line{stroke-width:1px}.c3-circle._expanded_{stroke-width:1px;stroke:#fff}.c3-selected-circle{fill:#fff;stroke-width:2px}.c3-bar{stroke-width:0}.c3-bar._expanded_{fill-opacity:1;fill-opacity:.75}.c3-target.c3-focused{opacity:1}.c3-target.c3-focused path.c3-line,.c3-target.c3-focused path.c3-step{stroke-width:2px}.c3-target.c3-defocused{opacity:.3!important}.c3-region{fill:#4682b4;fill-opacity:.1}.c3-brush .extent{fill-opacity:.1}.c3-legend-item{font-size:12px}.c3-legend-item-hidden{opacity:.15}.c3-legend-background{opacity:.75;fill:#fff;stroke:#d3d3d3;stroke-width:1}.c3-title{font:14px sans-serif}.c3-tooltip-container{z-index:10}.c3-tooltip{border-collapse:collapse;border-spacing:0;background-color:#fff;empty-cells:show;-webkit-box-shadow:7px 7px 12px -9px #777;-moz-box-shadow:7px 7px 12px -9px #777;box-shadow:7px 7px 12px -9px #777;opacity:.9}.c3-tooltip tr{border:1px solid #ccc}.c3-tooltip th{background-color:#aaa;font-size:14px;padding:2px 5px;text-align:left;color:#fff}.c3-tooltip td{font-size:13px;padding:3px 6px;background-color:#fff;border-left:1px dotted #999}.c3-tooltip td>span{display:inline-block;width:10px;height:10px;margin-right:6px}.c3-tooltip td.value{text-align:right}.c3-area{stroke-width:0;opacity:.2}.c3-chart-arcs-title{dominant-baseline:middle;font-size:1.3em}.c3-chart-arcs .c3-chart-arcs-background{fill:#e0e0e0;stroke:#fff}.c3-chart-arcs .c3-chart-arcs-gauge-unit{fill:#000;font-size:16px}.c3-chart-arcs .c3-chart-arcs-gauge-max{fill:#777}.c3-chart-arcs .c3-chart-arcs-gauge-min{fill:#777}.c3-chart-arc .c3-gauge-value{fill:#000}.c3-chart-arc.c3-target g path{opacity:1}.c3-chart-arc.c3-target.c3-focused g path{opacity:1}

2
static/js/c3.min.js vendored Normal file

File diff suppressed because one or more lines are too long

2
static/js/d3.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -3,7 +3,19 @@
^{Table.buildBootstrap (submissionsTable muserId (challengeName challenge) scheme challengeRepo tests) submissions} ^{Table.buildBootstrap (submissionsTable muserId (challengeName challenge) scheme challengeRepo tests) submissions}
$if not (null params)
<h3>Submission graph
<div id="graph-container"> <div id="graph-container">
<script src="/static/js/sigma.min.js"> <script src="/static/js/sigma.min.js">
<script src="/static/js/sigma.parsers.json.min.js"> <script src="/static/js/sigma.parsers.json.min.js">
$if not (null params)
<h3>Graphs by parameters
$forall param <- params
<div id="chart-#{param}"></div>
<script src="/static/js/d3.min.js" charset="utf-8"></script>
<script src="/static/js/c3.min.js"></script>

View File

@ -10,3 +10,5 @@
'pageLength': 50, 'pageLength': 50,
'order': [[1, 'desc']]}); 'order': [[1, 'desc']]});
} ); } );
^{chartJSs}

View File

@ -14,6 +14,7 @@ $newline never
<!--datatables, including JQuery--> <!--datatables, including JQuery-->
<link rel="stylesheet" type="text/css" href="/static/css/datatables.min.css"/> <link rel="stylesheet" type="text/css" href="/static/css/datatables.min.css"/>
<link rel="stylesheet" type="text/css" href="/static/css/c3.min.css"/>
<script type="text/javascript" src="/static/js/datatables.min.js"> <script type="text/javascript" src="/static/js/datatables.min.js">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"