Package: silver-spectacle
What is this?Silver Spectacle is a python library for displaying data. Its designed as a (superior) alternative tomatplotlib.Well designedone liners that do everythingBeautifulby defaultEasy to customize, withtotal controlover every visual elementLightweight and reliable (no X11, QT, or tkinter dependencies)Useful documentation (basic use-cases don't requirestack overflow questions with hundreds of upvotes)How do I use it?Install just like any other pip modulepip install silver_spectacleThen inside a python file (or python repl)importsilver_spectacleasssss.DisplayCard("quickScatter",[# [x,y][1,2],[2,3],[5,5],[1.5,2.3],[2,3.2]])# >>> Server started at: http://0.0.0.0:9900ss.DisplayCard("quickLine",[# [x,y][1,2],[1.5,2.3],[2,3],[5,5],# for quickLine: dont forget to sort them by x value!# (otherwise it'll look like an etch-a-sketch)])Open the address in a browser and you should see something like:You can display imagesimportsilver_spectacleasssimportnumpygrayscale_or_rgb=numpy.ones((100,200,3))*(127)# doesn't even need to be a numpy arraycard=ss.DisplayCard("quickImage",grayscale_or_rgb)card=ss.DisplayCard("quickImage","./your_image.png")You can also perform live/progressive updates on the charts.importsilver_spectacleassscard=ss.DisplayCard("quickScatter",[[1,2],[2,3.2]])# live / progressive updatescard.send([5,5])card.send([1.5,2.3])card.send([2,3.2])NOTE: Currently live updates are not stored to disk. So if the browser window isn't open, then that data will be sent nowhere. This behavior will be improved in the future, but requires a bit of effort to be done in a performant way.Multiline update exampleimportsilver_spectacleassscard=ss.DisplayCard("multiLine",dict(# name : [ [x1,y1], [x2,y2],  ]Line1Name=[[1,2],[1.5,2.3],[2,3],[5,5],],Line2Name=[[1+1,2],[1+1.5,2.3],[1+2,3],[1+5,5],],),dict(title="Howdy!",horizonal_label="horizonal_label here"),)# Live updatecard.send(dict(Line1Name=[[1,2],[1.5,2.3],[2,3],[5,5],],Line2Name=[[1+1,2],[1+1.5,2.3],[1+2,3],[1+5,5],],))NOTE: Currently each.send()depends on the type of card (the interface). For example the arguments forquickScattercan be different fromchartjs.What are the available cards?ss.DisplayCard("quickScatter",list_of_x_y_pairs)ss.DisplayCard("quickLine",list_of_x_y_pairs)ss.DisplayCard("quickImage",numpy_image_or_filepath)ss.DisplayCard("quickMarkdown",markdown_text)ss.DisplayCard("chartjs",config)# does tons: see https://www.chartjs.org/docs/latest/general/data-structures.htmlss.DisplayCard("multiLine",dict_to_x_y_pairs,options)What kind of plots are possible?IfChart JShas it, then it is already available in this library. (More visualization libraries likeplotlywill be added to enable additional 2D plots, 3D charts, video/image integration, etc.)To use a ChartJS chart:Go totheir websiteFind a plot, such asthis line chartThen do a near 1-to-1 mapping to pythonFor example, with that line chart, look at theirsetuptabLook at theconfigtabThen make averysimilar structure with a python dictionary## setup tab part#labels=["January","February","March","April","May","June","July",]data={"labels":labels,"datasets":[{"label":'My First Dataset',"data":[65,59,80,81,56,55,40],"fill":False,"borderColor":'rgb(75, 192, 192)',"tension":0.1}]}## config tag part#config={"type":'line',"data":data,}## display the data#importsilver_spectacleasssss.DisplayCard("chartjs",config)Here's another example of how that might lookimportsilver_spectacleasssss.DisplayCard("chartjs",{"type":'line',"data":{"datasets":[{"label":'Approach 1',"data":[85,90,89,91,92,88],"fill":True,"tension":0.1,"backgroundColor":'rgb(75, 192, 192, 0.5)',},{"label":'Approach 2',"data":[75,80,78,81,82,77],"fill":True,"tension":0.1,"backgroundColor":'rgb(0, 292, 192, 0.5)',},{"label":'Approach 3',"data":[95,90,99,91,92,99],"fill":True,"tension":0.1,"backgroundColor":'rgb(0, 92, 192, 0.5)',},]},"options":{"pointRadius":3,# the size of the dots"scales":{"y":{"min":50,"max":100,},}}})What kind of features does it have?Works everywhere: You can run this on your Raspberry Pi and view the results on your phone. It works whether it's WSL, Docker, or ssh over a VPN. No more 5 page tutorials about X11 forwarding, display variables, and editing config files.Non-blocking: No morehacky/painful workaroundsto get both code to execute, and a graph to update at the same time.ss.displayis non-blocking by default, so use it without worrying if your overnight computation is going to stop in the middle because its waiting for user input.Multiple plots are effortless: Graphs are displayed in a stream with the most recent one at the top. Just call display and forget about it. No more complex mashing of graphs together, or hassle of closing window after window, only to realize you meant to screenshot the one you just closed.Independent runtime: If your main python program crashes that's fine, the cleanup function will intentionally leave the graph server running instead of killing it so you can still access your data. As long as you don't manually kill the server process, your data will be there.Interactive: I'm not saying its impossible for tools like matplotlib to have custom interactivity, but lets just say nobody is doing it unless they have an unusual level of determination. Silver Spectacles let's you add custom buttons, effects, etc with ease.Documentation?There is some additional documentation below for fully fledged customization of the javascript and css. However, this readme is currently all of the documentation. The power is in the flexibilty, not in the quantity of methods. Don't be afraid to open an issue asking for examples.Whats the status of the library?There are many planned features. This library is under active development. However, the API is stable, and effectively all changes will only be adding tools to the toolbox. Some of the planned features are small:adding a button for clearing the screen of existing graphsan option to save/load all visual data to a filebetter interfaces for graphs that incrementally updateworking with numpy/pytorch/tensorflow tensors without needing to convertOther features will be a major additionstools for displaying videosintegration with 3D plot librariesa simple system for combining/shaping graphsa theming systembetter visual notifications for errorsa simple interface for graphical plugins (buttons/sliders)Development will, more than likely, be sporadic, PR's are welcome.How can I contribute?All the dependencies / setup instuctions are indocumentation/SETUP.md.That^ will automatically create a venv environment for testing/developmentYou can modify the files under./main/silver_spectacle/./main/silver_spectacle/library.pyis the code that actually gets imported./main/silver_spectacle/server.pyis the code that is run inside of a subprocessAfter modification, you can use thecommands/project/local_installcommand to install the local version you've createdHow does it work?Everytime thedisplayfunction is called the library checks if the display server is running (using an http request). If the server is not running, then it starts the server as a subprocess in the background. It waits until the server is responding, then it uses http requests to tell the server about the data it wants to display.The server does two thingsIt embeds this data into the html as JSON data. This way any newly-opened pages already have all the available data. In terms of files, the server only serves the one html+js+css file.When the backend gets a http display request from the python process, it uses socket.io to notify all existing browser windows about the new data.For live updates, the system usessocket.ioandaiohttpto get a push notification-like effect within browser windows.This seems trivial, but it is important: existing browser windows do not overwrite existing data with incoming data. That would be bad because browser windows can be open longer than both the server process or python process. So, the browser window can have more information than the server does. For that reason, all data is timestamped and stored inside the globaldisplayRequestsvariable. Those timestamps are used as keys, allowing the browser windows to simply merge incoming data without duplication and without losing old information.On the graphical side, the browser iterates over all the display commands, creating a chart for each one, and places them into a vertical list. This vertical list is displayed in reverse; the most-recent graph is at the top.Thats it!Additional documentationHere's the basic configuration for full customization.importsilver_spectacleasssss.configure(port=9900,# Note: until version 1.0.0 is released# make sure to pin the exact version number of silver_spectacle to keep your code reliable# interactions inside JavaScript and CSS# (e.g. variable and class names) will be unstable.custom_css="",custom_js="",# Note2: currently custom CSS and JS only get applied when the server starts.# Meaning, if the server is still running from an old process# (a zombie server beacuse the python program crashed suddenly),# then it will look like your custom CSS and javascript are not being appliedserver_start_timeout=10,# this is not very important)Here's a more full example.importsilver_spectacleasssss.configure(port=69420,custom_css="""body .card {background-color: gray;color: whitesmoke; /* font color */}body #stream-container {flex-direction: column; /* makes oldest graphs be at the top */}body {background: slategray !important; /* needs to be important to override other values */}""",custom_js="""window.onload = ()=>{//// main API//silverSpectacle.log("string")      // add information to the graphical logsilverSpectacle.cards              // dict with cardId's as keyssilverSpectacle.cards[0].element   // html element of card with ID of 0silverSpectacle.cards[0].createdAt // unix time in secondssilverSpectacle.cards[0].interface // name of interface as stringsilverSpectacle.cards[0].arguments // array of json valuessilverSpectacle.interface          // dict of fuctions that create card elementssilverSpectacle.libraries          // dict of libraries, like ChartJS that you can usesilverSpectacle.libraries.lodash   // if you like lodash, its available//// add your own card/interface//// step 1, create a function like the one below (an interface)silverSpectacle.interface["myCustomUi"] = async (args) => {let message = args[0]let myComponent = document.createElement("div")myComponent.innerHTML = "Python says:<br>"+message// silverSpectacle.createCard() wraps the component to give it the white background and shadowlet card = silverSpectacle.createCard({ children: [ myComponent ], })// (optional) add a handler for dyanmic datacard.receive = function (data) {// update the componentmyComponent.innerHTML += `<br> python also wanted to say: {JSON.stringify(data)}`// warning / error checkingif (typeof data != 'string') {silverSpectacle.log("[warning] data was dynamically sent to myCustomUi, but it wasn't a string")}}// needs to return an html elementreturn card}// step 2, call it from python!////     import silver_spectacle as ss//     # create card//     custom_card = ss.DisplayCard("myCustomUi", "hello world")//     import time//     time.sleep(5)//     # add more data after the fact//     custom_card.send("I waited 5 sec to send this")}""",server_start_timeout=10,# (seconds)# this^ is not very important# the value is how long to wait if the server doesn't start at all# designed to fail without throwing an error encase the user# is performing important computations)Right now, there is still a lot of room for improving/expanding the javascript interface with events, tools, and encapsulation.