COM Visualizer

by th3uiguy

HTML

<script src="http://knockoutjs.com/downloads/knockout-2.3.0.js"></script>
<h1>COM Visualizer</h1>
<div class="weightPlacement">
    <div class="carInput">
        <div class="row">
            <div class="label">units</div>
            <select data-bind="options: ['g','oz'], value: changeWeightUnits"></select>
        </div>
        <div class="row">
            <div class="title">Car body</div>
        </div>
        <div class="row">
            <div class="label">width</div>
            <input data-bind="value: car.body.attemptedWidth, valueUpdate: 'afterkeydown', returnKey: function () {}" /> in</div>
        <div class="row">
            <div class="label">length</div>
            <input data-bind="value: car.body.attemptedHeight, valueUpdate: 'afterkeydown', returnKey: function () {}" /> in</div>
        <div class="row">
            <div class="title">Wheels</div>
        </div>
        <div class="row">
            <div class="label">wheel width</div>
            <div class="slider" data-bind="slider: car.attemptedWheelWidth, sliderOptions: {min: 1, max: 7.5, range: 'min', step: 0.1}"></div> 
            <input data-bind="value: car.wheelWidth, visible: car.wheelWidthEdit, hasFocus: car.wheelWidthEdit, returnKey: function () {}" />
            <div class="resultHint" data-bind="visible: !car.wheelWidthEdit(), click: car.wheelWidthEditClick">
                <span data-bind="text: car.wheelWidth()"></span> mm
            </div>
        </div>
        <!-- ko foreach: { data: car.wheels, as: 'wheel' } -->
        <div class="row">
            <div class="title" data-bind="text: wheel.title"></div>
        </div>
        <!-- ko if: wheel.name == 'DFW' -->
        <div class="row">
            <div class="label">side</div>
            <select data-bind="options: ['left','right'], value: wheel.side">        </select>
        </div>
        <div class="row">
            <div class="label">wheel base</div>        
            <div class="slider"...

CSS

h1 {
    text-align: center;
}
.weightPlacement {
    min-width: 610px;
    position: relative;
    text-align: center;
}
.weightPlacement .carInput {
    display: inline-block;
    width: 310px;
    text-align: left;
    vertical-align: top;
}
.weightPlacement .carInput .row {
    padding-bottom: 5px;
}
.weightPlacement .carInput .row .title {
    font-weight: 700;
    padding-top: 5px;
}
.weightPlacement .carInput .row .label {
    width: 125px;
    display: inline-block;
    font-weight: normal;
}
.weightPlacement .carInput .row input, .weightPlacement .carInput .row select {
    width: 70px;
}
.weightPlacement .carInput .row .slider {
    width: 90px;
    display: inline-block;
    margin-top: 5px;
    margin-right: 10px
}

.weightPlacement .carInput .row .resultHint {
    cursor: pointer;
    display: inline-block;
}

.weightPlacement .carInput .row .resultHint:hover {
    text-decoration: underline;
}

.weightPlacement .car {
    display: inline-block;
    position: relative;
    width: 300px;
}
.weightPlacement .car .carBody {
    position: absolute;
    border: 2px solid #000;
}
.weightPlacement .car .wheel {
    position: absolute;
    background-color: #111;
    z-index: 102;
}
.weightPlacement .car .wheelContact {
    position: absolute;
    background-color: #F33;
    z-index: 103;
    width: 1px;
}
.weightPlacement .car .axle {
    position: absolute;
    background-color: #999;
    z-index: 101;
}
.weightPlacement .car .carBodyCenter {
    position: absolute;
    border-left: 1px dashed gray;
    width: 0px;
}
.weightPlacement .car .carBodyBackAxle {
    position: absolute;
    border-top: 1px dashed gray;
    height: 0px;
}
.weightPlacement .car .carBodyComX {
    position: absolute;
    border-left: 1px dashed red;
    width: 0px;
}
.weightPlacement .car .carBodyComY {
    position: absolute;
    border-top: 1px dashed red;
    height: 0px;
}
.weightPlacement .car .carBodyComLegend {
    position: absolute;
    padding: 5px;
    width: 150px;
   ...

JavaScript

function round(value, precision) { return +(value.toFixed(precision || 4)); }
function gToOz(value) { return round(value * 0.035274); }
function ozToG(value) { return round(value * 28.3495); }
function mmToIn(value) { return round(value * 0.0393701); }
function inToMM(value) { return round(value * 25.4); }

function screenSettings() {
    var self = this;

    self.dpi = ko.observable(96);
    self.scale = ko.observable(1);

    self.inToPx = function (inches) {
        return self.dpi() * self.scale() * inches;
    };
}

function weightPlacementViewModel() {
    var self = this;

    self.screenSettings = new screenSettings();
    self.weightUnits = ko.observable("oz");
    
    self.changeWeightUnits = ko.computed({
        read: self.weightUnits,
        write: function (value) {
            var convert = value == "g" ? ozToG : gToOz,
                length = self.car.wheels().length;

            for (var i = 0; i < length; i++) {
                var wheel = self.car.wheels()[i];
                wheel.force(convert(wheel.force()));
            }
            self.weightUnits(value);
        }
    });

    self.car = new carViewModel({
        screenSettings: self.screenSettings
    });
    
    self.reset = function () {
        self.weightUnits("oz");
        self.car.reset();
    };
}

function carViewModel(manifest) {
    var self = this;

    self.manifest = manifest;

    self.width = ko.observable(2.75);
    self.height = ko.observable(8);
    self.wheelWidth = ko.observable(7.5);
    
    self.wheelWidthEdit = ko.observable(false);
    self.wheelWidthEditClick = function () {
        self.wheelWidthEdit(true);
    };

    self.attemptedWheelWidth = ko.computed({
        read: self.wheelWidth,
        write: function (value) {
            if (value && !isNaN(value) && value <= 10 && value > 0) {
                self.wheelWidth(parseFloat(value));
            }
        }
    });
    
    self.pxWidth = ko.computed(function () {
        return...