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 +20/−0
- Main.hs +50/−0
- Network/Socketed/Application/Plocketed/Internal.hs +7/−0
- Network/Socketed/Application/Plocketed/Template.hs +171/−0
- plocketed.cabal +26/−0
+ 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