DAT.GUI: so useful...so ugly. I extracted the CSS and prettied it up

Mary Dean

This exercise is a good demonstration of an important principle: you don't have to have any idea what you are doing to get in there and change things up.

When I was making my Manifesto page, I kept being annoyed by a mysterious, ugly control panel that clung to the top-right of my screen and wouldn't be moved.

Finally, I noticed that it had a "Close" button, so I was able to make it retreat, but then it lurked like a cockroach at the corner of my page, and reappeared full-size every time I refreshed.

gui-original-both-h300

Gradually, I came to understand what that little box was doing, and I saw the light. How awesome is that? A little panel that lets you change the original programmer's variables on the fly!

For example, here is my Columbine flag with low gravity, then high gravity.

gui-highgravity-h400

Fun! If you haven't tried it yet, you should go to my Manifesto page and remove the pins that are holding up my flag. Isn't that a cool effect?

Until yesterday, I thought dat-GUI was part of Pixi.js. (GUI stands for 'graphical user interface,' by the way.). It wouldn't be worth "fixing up" if I was never going to use it again. But now that I understand that it's something you can use with any javascript program, it seems like an incredibly useful tool to have around, if only to impress readers with my programmer-like skills.

See, I can't take any credit for programming my "dangling cloth photo flag" myself (check out this guy and this guy), but it would still be impressive if I could say, "Hey, readers, while you're here, check out my mouseInfluence slider!"

But egads, it really is so ugly. It totally doesn't match my purply-bluey color scheme. And it doesn't have any place to put instructions for the user. It's really just a basic HTML input form, though, isn't it? Buttons and checkboxes and drop-downs, those can't be too hard to stylize, right?

The tricky part is that the interface is created on the fly by a javascript program that loads when the page loads. So you can't just look at the raw HTML and target the various controls with CSS. If you right-click and look at the source code for the page, you won't see the dat-GUI at all. So how can we target it?

Luckily, I've been mucking around a bit, so I have a head-start. I'm going to write this blog post as I am actually doing it, so you can follow along and modify it to suit your own purposes.

What is dat-GUI, briefly?

"dat.GUI is a GUI widget for your demos." The best place to see it in action is on this demo page for threejs. And you can check out awesome examples of 3-D rendering while you're at it.

gui-threejs

It is a "lightweight controller library for JavaScript," made by Google, and kept here on GitHub

Here is a nice tutorial for using it, and here is where you can find the latest CDN version.

Here is a sweet little example which shows how you would create a rectangle and then vary all its options with a dat-GUI: https://codepen.io/webhacck/pen/mVQJVj

Here is an intense example showing a huge number of physics properties in one giant model: http://brm.io/matter-js/demo/#mixed

It consists of a single javascript file that you at the bottom of your HTML file in the usual way, such as:

<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.1/dat.gui.min.js"></script>

But if you are going to edit some of the variables I mention below, you will probably want to use the un-minified version, which you can copy from here:

https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.1/dat.gui.js

I am using the most recent version 0.7.1 even though my project originally used 0.6.1 because I haven't noticed any changes in terms of how my flag controller works.

In addition to the actual dat.gui.js file, you will need the code that actually creates the GUI in the first place. I am working with the code that was written on the Codepen that I am borrowing from. I am assuming you are here because you already have a dat-GUI that you want to pretty up. If not, go borrow some code from the demo place that I mentioned.

The brute force method of CSS extraction

Before it occurred to me to look into the actual javascript file, I was planning to simply right-click on the widget and use the "inspector" tools available in Chrome and Firefox to figure out the CSS that we needed. Often, this very effective. Here, for example, you can see that the class name we need to target is "property-name".

guibruteforce

What I like to do is create a mini stylesheet that targets each class or ID I find in the most extreme way possible.

span.property-name{
  background-color: midnightblue;
  color: white;
  text-align: center;
  font-family: serif;
  font-size: 1.1em;
  border-radius: 15px;
  border: 2px purple ridge;
  display: inline-block;
  height: 30px;
  line-height: 2em;
}

It's a good exercise for your brain to spew out random CSS properties and colors as quickly as you can. The goal is to make every item completely different from the others, so you can see exactly what each class or ID is targeting. Soon, you will have converted that boring, black GUI utility into an exciting, colorful monstrosity like this:

GUI-UGLYWAY-400

It's especially important to place brightly colored borders around things, which can reveal surprises, like when certain borders extend beyond the edges of their container.

Once you have figured out what's what, it's a simple matter to go back and tone down and up-class the mini CSS sheet you have created.

But sometimes you will get frustrated, because things just don't respond to your commands. The height of the rows, for example, seem to be impervious to the charms of my CSS.

Digging into the javascript

I won't bore you with the details of my explorations because I want to finish this project before I fall asleep tonight. I will just tell you a few things I have learned so far.

To free the GUI from its prison on the top right corner, do this

The first task is to rip that sticky beast off the top right side of your screen. And the way to do that, I have discovered, is to add this line to your code:

{ autoPlace: false }

More specifically, you want to add that to the place in your code where you actually create the new GUI, and you want to tell it where to put it instead. So you will see something like this:

let gui = new dat.GUI();

and you want to change it to something like this:

let gui = new dat.GUI({ autoPlace: false });
var GUIContainer = document.getElementById('my-gui-container');
GUIContainer.appendChild(gui.domElement);

Then, in your CSS you would want to position it where you want it to land. To do that, you need to use "absolute" positioning.<a href="javascript:void(0)" class="popover-dismiss" data-toggle="popover" data-trigger="hover" title="" data-content="If position:absolute doesn't work, remember that your container should be relative, not static.">

#my-gui-container {
	position: absolute;
    left: 600px;   /* position inside relatively positioned parent */
	top: 150px;
    z-index: 0;   /* adjust as needed */
}
Hot tip: If you ONLY want the gui to be LARGER (or smaller), here's an easy method. In your CSS for my-gui-container, just add the line: transform: scale(1.25); then adjust the number in parentheses to the proportion you want.

Changes you can make to the javascript itself... if you do, you must keep track of it forever

Adust the default width variable

In the JS, you can change the default width for the entire controller by changing this variable:

GUI.DEFAULT_WIDTH = 245;

Change the wording on the Open and Close buttons

While you are there, you can change the text
//GUI.TEXT_CLOSED = 'Close Controls';
GUI.TEXT_CLOSED = 'Hide this control box';
//GUI.TEXT_OPEN = 'Open Controls';
GUI.TEXT_OPEN = 'Goof around with my Flag';

To get rid of the ugly skinny left borders

gui-uglyleftborder
Comment out or change the colors on these:

/*
 * GETTING RID OF THE UGLY LEFT BORDERS

.dg .cr.boolean {
    border-left: 3px solid #806787
}

.dg .cr.color {
    border-left: 3px solid
}

.dg .cr.function {
    border-left: 3px solid #e61d5f
}

.dg .cr.number {
    border-left: 3px solid #2FA1D6
}
*/

Extract the CSS from the JS file

Luckily, we don't have to "figure out" the CSS that we need, because there is a CSS style sheet buried in the javascript file. Just search for "var styleSheet"

gui-stylesheet

Yep, the entire, 400+ line stylesheet is just a single variable in javascript.

Now let's look at it!

Raw stylesheet extracted from 0.7.1 of dat.GUI

.dg ul {
    list-style: none;
    margin: 0;
    padding: 0;
    width: 100%;
    clear: both
}

.dg.ac {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 0;
    z-index: 0
}

.dg:not(.ac) .main {
    overflow: hidden
}

.dg.main {
    -webkit-transition: opacity .1s linear;
    -o-transition: opacity .1s linear;
    -moz-transition: opacity .1s linear;
    transition: opacity .1s linear
}

.dg.main.taller-than-window {
    overflow-y: auto
}

.dg.main.taller-than-window .close-button {
    opacity: 1;
    margin-top: -1px;
    border-top: 1px solid #2c2c2c
}

.dg.main ul.closed .close-button {
    opacity: 1 !important
}

.dg.main:hover .close-button,
.dg.main .close-button.drag {
    opacity: 1
}

.dg.main .close-button {
    -webkit-transition: opacity .1s linear;
    -o-transition: opacity .1s linear;
    -moz-transition: opacity .1s linear;
    transition: opacity .1s linear;
    border: 0;
    line-height: 19px;
    height: 20px;
    cursor: pointer;
    text-align: center;
    background-color: #000
}

.dg.main .close-button.close-top {
    position: relative
}

.dg.main .close-button.close-bottom {
    position: absolute
}

.dg.main .close-button:hover {
    background-color: #111
}

.dg.a {
    float: right;
    margin-right: 15px;
    overflow-y: visible
}

.dg.a.has-save>ul.close-top {
    margin-top: 0
}

.dg.a.has-save>ul.close-bottom {
    margin-top: 27px
}

.dg.a.has-save>ul.closed {
    margin-top: 0
}

.dg.a .save-row {
    top: 0;
    z-index: 1002
}

.dg.a .save-row.close-top {
    position: relative
}

.dg.a .save-row.close-bottom {
    position: fixed
}

.dg li {
    -webkit-transition: height .1s ease-out;
    -o-transition: height .1s ease-out;
    -moz-transition: height .1s ease-out;
    transition: height .1s ease-out;
    -webkit-transition: overflow .1s linear;
    -o-transition: overflow .1s linear;
    -moz-transition: overflow .1s linear;
    transition: overflow .1s linear
}

.dg li:not(.folder) {
    cursor: auto;
    height: 27px;
    line-height: 27px;
    padding: 0 4px 0 5px
}

.dg li.folder {
    padding: 0;
    border-left: 4px solid transparent
}

.dg li.title {
    cursor: pointer;
    margin-left: -4px
}

.dg .closed li:not(.title),
.dg .closed ul li,
.dg .closed ul li>* {
    height: 0;
    overflow: hidden;
    border: 0
}

.dg .cr {
    clear: both;
    padding-left: 3px;
    height: 27px;
    overflow: hidden
}

.dg .property-name {
    cursor: default;
    float: left;
    clear: left;
    width: 40%;
    overflow: hidden;
    text-overflow: ellipsis
}

.dg .c {
    float: left;
    width: 60%;
    position: relative
}

.dg .c input[type=text] {
    border: 0;
    margin-top: 4px;
    padding: 3px;
    width: 100%;
    float: right
}

.dg .has-slider input[type=text] {
    width: 30%;
    margin-left: 0
}

.dg .slider {
    float: left;
    width: 66%;
    margin-left: -5px;
    margin-right: 0;
    height: 19px;
    margin-top: 4px
}

.dg .slider-fg {
    height: 100%
}

.dg .c input[type=checkbox] {
    margin-top: 7px
}

.dg .c select {
    margin-top: 5px
}

.dg .cr.function,
.dg .cr.function .property-name,
.dg .cr.function *,
.dg .cr.boolean,
.dg .cr.boolean * {
    cursor: pointer
}

.dg .cr.color {
    overflow: visible
}

.dg .selector {
    display: none;
    position: absolute;
    margin-left: -9px;
    margin-top: 23px;
    z-index: 10
}

.dg .c:hover .selector,
.dg .selector.drag {
    display: block
}

.dg li.save-row {
    padding: 0
}

.dg li.save-row .button {
    display: inline-block;
    padding: 0px 6px
}

.dg.dialogue {
    background-color: #222;
    width: 460px;
    padding: 15px;
    font-size: 13px;
    line-height: 15px
}

#dg-new-constructor {
    padding: 10px;
    color: #222;
    font-family: Monaco, monospace;
    font-size: 10px;
    border: 0;
    resize: none;
    box-shadow: inset 1px 1px 1px #888;
    word-wrap: break-word;
    margin: 12px 0;
    display: block;
    width: 440px;
    overflow-y: scroll;
    height: 100px;
    position: relative
}

#dg-local-explain {
    display: none;
    font-size: 11px;
    line-height: 17px;
    border-radius: 3px;
    background-color: #333;
    padding: 8px;
    margin-top: 10px
}

#dg-local-explain code {
    font-size: 10px
}

#dat-gui-save-locally {
    display: none
}

.dg {
    color: #eee;
    font: 11px 'Lucida Grande', sans-serif;
    text-shadow: 0 -1px 0 #111
}

.dg.main::-webkit-scrollbar {
    width: 5px;
    background: #1a1a1a
}

.dg.main::-webkit-scrollbar-corner {
    height: 0;
    display: none
}

.dg.main::-webkit-scrollbar-thumb {
    border-radius: 5px;
    background: #676767
}

.dg li:not(.folder) {
    background: #1a1a1a;
    border-bottom: 1px solid #2c2c2c
}

.dg li.save-row {
    line-height: 25px;
    background: #dad5cb;
    border: 0
}

.dg li.save-row select {
    margin-left: 5px;
    width: 108px
}

.dg li.save-row .button {
    margin-left: 5px;
    margin-top: 1px;
    border-radius: 2px;
    font-size: 9px;
    line-height: 7px;
    padding: 4px 4px 5px 4px;
    background: #c5bdad;
    color: #fff;
    text-shadow: 0 1px 0 #b0a58f;
    box-shadow: 0 -1px 0 #b0a58f;
    cursor: pointer
}

.dg li.save-row .button.gears {
    background: #c5bdad url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAANCAYAAAB/9ZQ7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQJJREFUeNpiYKAU/P//PwGIC/ApCABiBSAW+I8AClAcgKxQ4T9hoMAEUrxx2QSGN6+egDX+/vWT4e7N82AMYoPAx/evwWoYoSYbACX2s7KxCxzcsezDh3evFoDEBYTEEqycggWAzA9AuUSQQgeYPa9fPv6/YWm/Acx5IPb7ty/fw+QZblw67vDs8R0YHyQhgObx+yAJkBqmG5dPPDh1aPOGR/eugW0G4vlIoTIfyFcA+QekhhHJhPdQxbiAIguMBTQZrPD7108M6roWYDFQiIAAv6Aow/1bFwXgis+f2LUAynwoIaNcz8XNx3Dl7MEJUDGQpx9gtQ8YCueB+D26OECAAQDadt7e46D42QAAAABJRU5ErkJggg==) 2px 1px no-repeat;
    height: 7px;
    width: 8px
}

.dg li.save-row .button:hover {
    background-color: #bab19e;
    box-shadow: 0 -1px 0 #b0a58f
}

.dg li.folder {
    border-bottom: 0
}

.dg li.title {
    padding-left: 16px;
    background: #000 url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==) 6px 10px no-repeat;
    cursor: pointer;
    border-bottom: 1px solid rgba(255, 255, 255, 0.2)
}

.dg .closed li.title {
    background-image: url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlGIWqMCbWAEAOw==)
}

.dg .cr.boolean {
    border-left: 3px solid #806787
}

.dg .cr.color {
    border-left: 3px solid
}

.dg .cr.function {
    border-left: 3px solid #e61d5f
}

.dg .cr.number {
    border-left: 3px solid #2FA1D6
}

.dg .cr.number input[type=text] {
    color: #2FA1D6
}

.dg .cr.string {
    border-left: 3px solid #1ed36f
}

.dg .cr.string input[type=text] {
    color: #1ed36f
}

.dg .cr.function:hover,
.dg .cr.boolean:hover {
    background: #111
}

.dg .c input[type=text] {
    background: #303030;
    outline: none
}

.dg .c input[type=text]:hover {
    background: #3c3c3c
}

.dg .c input[type=text]:focus {
    background: #494949;
    color: #fff
}

.dg .c .slider {
    background: #303030;
    cursor: ew-resize
}

.dg .c .slider-fg {
    background: #2FA1D6;
    max-width: 100%
}

.dg .c .slider:hover {
    background: #3c3c3c
}

.dg .c .slider:hover .slider-fg {
    background: #44abda
}