packages feed

plocketed (empty) → 0.1.0.0

raw patch · 5 files changed

+274/−0 lines, 5 filesdep +basedep +optparse-applicativedep +socketed

Dependencies added: base, optparse-applicative, socketed

Files

+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2017 sen.cenan@gmail.com++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ Main.hs view
@@ -0,0 +1,50 @@+module Main where++import Data.Semigroup ((<>))++import qualified Options.Applicative as Opt++import Network.Socketed.Internal (stdinPassthrough)+import Network.Socketed.Application.Plocketed.Template (plotHtml)+import Network.Socketed.Application.Plocketed.Internal (PlocketedOptions(..))++import System.IO (hFlush, stdout)++params :: Opt.Parser PlocketedOptions+params = PlocketedOptions+   <$> Opt.option Opt.auto+      ( Opt.long "width"+      <> Opt.short 'x'+      <> Opt.help "width of the chart"+      <> Opt.showDefault+      <> Opt.value 1100+      <> Opt.metavar "INT" )+   <*> Opt.option Opt.auto+      ( Opt.long "height"+      <> Opt.short 'y'+      <> Opt.help "height of the chart"+      <> Opt.showDefault+      <> Opt.value 600+      <> Opt.metavar "INT")+   <*> Opt.option Opt.auto+      ( Opt.long "maxData"+      <> Opt.short 'm'+      <> Opt.help "max data point kept"+      <> Opt.showDefault+      <> Opt.value 20+      <> Opt.metavar "INT")++main :: IO ()+main = Opt.execParser opts >>= \x -> do+      let s = plotHtml x+      putStrLn s+      putStrLn "\n" -- kick off socketed+      hFlush stdout+      stdinPassthrough+   where+      opts = Opt.info (Opt.helper <*> params)+         (+            Opt.fullDesc+            <> Opt.progDesc "plocketed"+            <> Opt.header "plocketed"+         )
+ Network/Socketed/Application/Plocketed/Internal.hs view
@@ -0,0 +1,7 @@+module Network.Socketed.Application.Plocketed.Internal where++data PlocketedOptions = PlocketedOptions {+      width :: Int,+      height :: Int,+      maxData :: Int+   }
+ Network/Socketed/Application/Plocketed/Template.hs view
@@ -0,0 +1,171 @@+{-# LANGUAGE QuasiQuotes #-}++module Network.Socketed.Application.Plocketed.Template (plotHtml) where++import Data.List (intercalate)++import Network.Socketed.Internal (stringQuote)+import Network.Socketed.Application.Plocketed.Internal (PlocketedOptions(..))++-- width+-- height+-- data points to keep++plotHtml :: PlocketedOptions -> String+plotHtml (PlocketedOptions w h m) = intercalate "" $ lines $ [stringQuote|++(function(width, height, maxDataAmount) {++var script = document.createElement('script');+script.src = '//www.chartjs.org/assets/Chart.min.js';++script.onload = function() {+   var canvas = document.createElement('canvas');+   canvas.setAttribute('width', width);+   canvas.setAttribute('height', height);+   document.body.appendChild(canvas);++   var legend = document.createElement('div');+   document.body.appendChild(legend);++   var+      ctx = canvas.getContext('2d'),+      seq = 0,+      dataMap = {}, /* data key to data set */+      chart;++   var colors = [+      '#00ffff', '#f0ffff', '#f5f5dc', '#000000', '#0000ff', '#a52a2a',+      '#00ffff', '#00008b', '#008b8b', '#a9a9a9', '#006400', '#bdb76b',+      '#8b008b', '#556b2f', '#ff8c00', '#9932cc', '#8b0000', '#e9967a',+      '#9400d3', '#ff00ff', '#ffd700', '#008000', '#4b0082', '#f0e68c',+      '#add8e6', '#e0ffff', '#90ee90', '#d3d3d3', '#ffb6c1', '#ffffe0',+      '#00ff00', '#ff00ff', '#800000', '#000080', '#808000', '#ffa500',+      '#ffc0cb', '#800080', '#800080', '#ff0000', '#c0c0c0'+   ];++   var randomColorFrag = function() {+      var c = colors[+         Math.floor(Math.random() * colors.length * 10) % colors.length+      ].substring(1);++      return 'rgba('+         + [c.substring(0,2), c.substring(2,4), c.substring(4)]+            .map(v => parseInt(v, 16))+            .join(',');+   };++   var blend = function(frag, a) {+      return frag + ',' + a + ')';+   };++   var mkDataSet = function(label) {+      var c = randomColorFrag();++      return {+        fillColor: blend(c, .2),+        strokeColor: blend(c, .7),+        pointColor: blend(c, .1),+        pointStrokeColor: "#fff",+        points: [],+        label: label+      };+   };++   var mkChart = function() {+      if (chart) {+         chart.clear();+         chart.destroy();+      }++      seq = 0;+      var ds = Object.keys(dataMap)+         .map(k => dataMap[k])+         .sort((a, b) => a.idx - b.idx)+         .map(set => {++            /* trim to the last element */+            set.data = [set.data[set.data.length - 1]];++            return Object.assign(+               mkDataSet(set.key),+               { data: set.data }+            );+         });++      console.log(JSON.stringify(ds));++      chart+         = window.chart+         = new Chart(ctx).Line(+            {+               labels: [seq],+               datasets: ds+            },+            {+               animationSteps: 15,+               legendTemplate: '<table><tr>'+                   +'<% for (var i=0; i<datasets.length; i++) { %>'+                   +'<td><div class=\"boxx\" '+                     + 'style=\"background-color:<%=datasets[i].fillColor %>\">'+                   +'<% if (datasets[i].label) '+                     + '{ %> <%= datasets[i].label %> <% } %></div></td>'+                   +'<% } %>'+                   +'</tr></table>'+            }+         );++      legend.innerHTML = chart.generateLegend();+   };++   window.dataMap = dataMap;++   window.handleMessage = function(e) {+      var+         vals = e.data.split(' ').map(x => x.trim()).map(x => parseInt(x, 10)),+         key = vals[0],+         val = vals[1];++      if (!(key in dataMap)) {+         dataMap[key] = {+            key: key,+            idx: Object.keys(dataMap).length,+            data: [val]+         };++         mkChart();+      } else {+         var newPoints = Object.keys(dataMap)+            .map(k => dataMap[k])+            .sort((a, b) => a.idx - b.idx)+            .map(set => {+               if (set.key === key) {+                  set.data.push(val);+               } else {+                  set.data.push(set.data[set.data.length - 1]);+               }++               return set.data[set.data.length - 1];+            });++         chart.addData(newPoints, ++seq);+      }++      if (seq > maxDataAmount) {+         chart.removeData();++         newPoints = Object.keys(dataMap).map(k => {+            dataMap[k].data.shift();+         });+      }+   };++   window.handleMessageError = function(e, err) {+      console.log(e, err);+   };+};++document.body.appendChild(script);++})(+|] ++ (intercalate "," $ map show [w, h, m]) ++ ");"
+ plocketed.cabal view
@@ -0,0 +1,26 @@+name: plocketed+version: 0.1.0.0++synopsis: plot data from stdin through socketed+description: plot data from stdin through socketed++license: MIT+license-file: LICENSE+author: sen.cenan@gmail.com+maintainer: sen.cenan@gmail.com++category: Web+build-type: Simple+cabal-version: >=1.10++executable plocketed+  main-is: Main.hs+  default-language: Haskell2010+  build-depends:+    base >=4.9 && <4.10,+    socketed >= 0.1.0.0,+    optparse-applicative >= 0.13.2.0+  ghc-options: -threaded+  other-modules:+    Network.Socketed.Application.Plocketed.Internal,+    Network.Socketed.Application.Plocketed.Template