diff --git a/js/cktsim.js b/js/cktsim.js index e61beee0f0..ca289a6b8e 100644 --- a/js/cktsim.js +++ b/js/cktsim.js @@ -4,12 +4,30 @@ // ////////////////////////////////////////////////////////////////////////////// -// Chris Terman, Dec. 2011 +// Copyright (C) 2011 Massachusetts Institute of Technology + +// 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. // create a circuit for simulation using "new cktsim.Circuit()" // for modified nodal analysis (MNA) stamps see -// http://books.google.com/books?id=qhHsSlazGrQC&pg=PA44&lpg=PA44&dq=MNA+stamp+inductor&source=bl&ots=ThMq-FmhLo&sig=cTP1ld_fhIJbGPSBXPDbh3Xappk&hl=en&sa=X&ei=6wb-ToecFMHj0QH61-Fs&ved=0CFcQ6AEwAw#v=onepage&q=MNA%20stamp%20inductor&f=false +// http://www.analog-electronics.eu/analog-electronics/modified-nodal-analysis/modified-nodal-analysis.xhtml cktsim = (function() { @@ -23,6 +41,15 @@ cktsim = (function() { T_VOLTAGE = 0; T_CURRENT = 1; + v_abstol = 1e-6; // criterion for absolute convergence (voltage) + i_abstol = 1e-12; // criterion for absolute convergence (current) + min_time_step = 1e-18; // smallest possible time step + max_iterations = 50; // max iterations before giving up + increase_limit = 4; // if we converge in this many iterations, increase time step + time_step_increase_factor = 2.0; + time_step_decrease_factor = 0.3; + reltol = 0.001; // convergence criterion relative to max observed value + function Circuit() { this.node_map = new Array(); this.ntypes = []; @@ -34,11 +61,6 @@ cktsim = (function() { this.finalized = false; this.node_index = -1; - - // for backward Euler: coeff0 = 1/timestep, coeff1 = 0 - // for trapezoidal: coeff0 = 2/timestep, coeff1 = 1 - this.coeff0 = undefined; - this.coeff1 = undefined; } // index of ground node @@ -67,10 +89,22 @@ cktsim = (function() { // set up augmented matrix and various temp vectors this.matrix = new Array(this.N); - for (var i = this.N - 1; i >= 0; --i) + this.soln_max = new Array(this.N); // max abs value seen for each unknown + this.rtol = new Array(this.N); // soln_max * reltol + this.abstol = new Array(this.N); + this.solution = new Array(this.N); + for (var i = this.N - 1; i >= 0; --i) { this.matrix[i] = new Array(this.N + 1); - this.swap = new Array(this.N); // keep track of row swaps during pivoting - this.soln = new Array(this.N); // hold swapped solution + this.soln_max[i] = 0.0; + this.rtol[i] = 0.0; + this.abstol[i] = this.ntypes[i] == T_VOLTAGE ? v_abstol : i_abstol; + this.solution[i] = 0.0; + } + + // for backward Euler: coeff0 = 1/timestep, coeff1 = 0 + // for trapezoidal: coeff0 = 2/timestep, coeff1 = 1 + this.coeff0 = undefined; + this.coeff1 = undefined; } } @@ -84,8 +118,8 @@ cktsim = (function() { var component = netlist[i]; var type = component[0]; - // ignore wires, ground connections and view info - if (type == 'view' || type == 'w' || type == 'g') continue; + // ignore wires, ground connections, scope probes and view info + if (type == 'view' || type == 'w' || type == 'g' || type == 's') continue; var properties = component[2]; var name = properties['name']; @@ -121,27 +155,127 @@ cktsim = (function() { } } + // if converges: updates this.solution, this.soln_max, returns iter count + // otherwise: return undefined and set this.problem_node + // The argument should be a function that sets up the linear system. + Circuit.prototype.find_solution = function(load) { + var soln = this.solution; + var old_soln,temp,converged; + + // iteratively solve until values convere or iteration limit exceeded + for (var iter = 0; iter < max_iterations; i++) { + // set up equations + this.initialize_linear_system(); + load(this); + + // solve for node voltages and branch currents + old_soln = soln; + soln = solve_linear_system(this.matrix); + + // check convergence: abs(new-old) <= abstol + reltol*max; + converged = true; + for (var i = this.N - 1; i >= 0; --i) { + temp = Math.abs(soln[i] - old_soln); + if (temp > this.abstol[i] + this.rtol[i]) { + converged = false; + this.problem_node = i; + break; + } + } + if (!converged) continue; + + // other convergence checks here? + + // update solution and maximum + this.solution = soln; + for (var i = this.N - 1; i >= 0; --i) { + temp = Math.abs(soln[i]); + if (temp > this.soln_max[i]) { + this.soln_max[i] = temp; + this.rtol[i] = temp * reltol; + } + } + return iter+1; + } + + // too many iterations + return undefined; + } + // DC analysis Circuit.prototype.dc = function() { this.finalize(); - // set up equations - this.initialize_linear_system(); - for (var i = this.devices.length - 1; i >= 0; --i) - this.devices[i].load_dc(this); + // this function calls load_dc for all devices + function load_dc(ckt) { + for (var i = ckt.devices.length - 1; i >= 0; --i) + ckt.devices[i].load_dc(ckt); + } - // solve for operating point - var x = solve_linear_system(this.matrix); + // find the operating point + var iterations = this.find_solution(load_dc); + + if (typeof iterations == 'undefined') + return 'Node '+this.node_map[this.problem_node]+' did not converge'; // create solution dictionary var result = new Array(); for (var name in this.node_map) { var index = this.node_map[name]; - result[name] = (index == -1) ? 0 : x[index]; + result[name] = (index == -1) ? 0 : this.solution[index]; } return result; } + // AC analysis: npts/decade for freqs in range [fstart,fstop] + // result['frequencies'] = vector of log10(sample freqs) + // result['xxx'] = vector of dB(response for node xxx) + Circuit.prototype.ac = function(npts,fstart,fstop) { + this.finalize(); + + // this function calls load_ac for all devices + function load_ac(ckt) { + for (var i = ckt.devices.length - 1; i >= 0; --i) + ckt.devices[i].load_ac(ckt); + } + + // build array to hold list of results for each node + // last entry is for frequency values + var response = new Array(this.N + 1); + for (var i = this.N; i >= 0; --i) response[i] = new Array(); + + // multiplicative frequency increase between freq points + var delta_f = Math.exp(Math.LN10/npts); + + var f = fstart; + fstop *= 1.0001; // capture that last time point! + while (f <= fstop) { + this.omega = 2 * Math.PI * f; + + // find the operating point + var iterations = this.find_solution(load_ac); + + if (typeof iterations == 'undefined') + return 'Node '+this.node_map[this.problem_node]+' did not converge'; + else { + response[this.N].push(f); + for (var i = this.N - 1; i >= 0; --i) + response[i].push(this.solution[i]); + } + + f *= delta_f; // increment frequency + } + + // create solution dictionary + var result = new Array(); + for (var name in this.node_map) { + var index = this.node_map[name]; + result[name] = (index == -1) ? 0 : response[index]; + } + result['frequencies'] = response[this.N]; + return result; + } + Circuit.prototype.r = function(n1,n2,v,name) { // try to convert string value into numeric value, barf if we can't if ((typeof v) == 'string') { @@ -488,6 +622,8 @@ cktsim = (function() { return result; } + Circuit.prototype.parse_number = parse_number; // make it easy to call from outside + /////////////////////////////////////////////////////////////////////////////// // // Sources @@ -650,19 +786,23 @@ cktsim = (function() { VSource.prototype.construction = VSource; // load linear system equations for dc analysis - VSource.prototype.load_dc = function(ckt,soln) { + VSource.prototype.load_dc = function(ckt) { + // MNA stamp for independent voltage source + ckt.add_to_A(this.branch,this.npos,1.0); + ckt.add_to_A(this.branch,this.nneg,-1.0); + ckt.add_to_A(this.npos,this.branch,1.0); + ckt.add_to_A(this.nneg,this.branch,-1.0); + ckt.add_to_b(this.branch,this.src.dc); + } + + // load linear system equations for tran analysis (just like DC) + VSource.prototype.load_tran = function(ckt,soln) { // MNA stamp for independent voltage source ckt.add_to_A(this.branch,this.npos,1.0); ckt.add_to_A(this.branch,this.nneg,-1.0); ckt.add_to_A(this.npos,this.branch,1.0); ckt.add_to_A(this.nneg,this.branch,-1.0); ckt.add_to_b(this.branch,this.src.value(ckt.time)); - - } - - // load linear system equations for tran analysis (just like DC) - VSource.prototype.load_tran = function(ckt,soln) { - this.load_dc(ckt); } // return time of next breakpoint for the device @@ -671,12 +811,8 @@ cktsim = (function() { } // small signal model: short circuit - VSource.prototype.load_ac = function() { - // use branch row in matrix to set following constraint on system: - // v_pos - v_neg = 0V - ckt.add_to_A(this.branch,this.npos,1.0); - ckt.add_to_A(this.branch,this.nneg,-1.0); - // ckt.add_to_b(this.branch,0); // adding 0 isn't necessary! + VSource.prototype.load_ac = function(ckt) { + this.load_dc(ckt); } function ISource(npos,nneg,v) { @@ -691,7 +827,7 @@ cktsim = (function() { // load linear system equations for dc analysis ISource.prototype.load_dc = function(ckt) { - var i = this.src.value(ckt.time); + var i = this.src.dc; // MNA stamp for independent current source ckt.add_to_b(this.npos,-i); // current flow into npos @@ -700,7 +836,11 @@ cktsim = (function() { // load linear system equations for tran analysis (just like DC) ISource.prototype.load_tran = function(ckt,soln) { - this.load_dc(ckt); + var i = this.src.value(ckt.time); + + // MNA stamp for independent current source + ckt.add_to_b(this.npos,-i); // current flow into npos + ckt.add_to_b(this.nneg,i); // and out of nneg } // return time of next breakpoint for the device @@ -709,7 +849,8 @@ cktsim = (function() { } // small signal model: open circuit - ISource.prototype.load_ac = function() { + ISource.prototype.load_ac = function(ckt) { + this.load_dc(ckt); } /////////////////////////////////////////////////////////////////////////////// diff --git a/js/schematic.js b/js/schematic.js index 3d9b64135c..2febce050d 100644 --- a/js/schematic.js +++ b/js/schematic.js @@ -4,11 +4,29 @@ // //////////////////////////////////////////////////////////////////////////////// -// Chris Terman, Nov. 2011 +// Copyright (C) 2011 Massachusetts Institute of Technology + +// 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. // add schematics to a document with // -// +// // // other attributes you can add to the input tag: // width -- width in pixels of diagram @@ -27,14 +45,8 @@ // need a netlist? just use the part's type, properites and connections // TO DO: - -// - draggable overlay window base class (dialogs, scope, ...) // - wire labels? -// - devices: diode, nfet, pfet, opamp, scope probe -// - icons for test equipment? (scope, sig gen, counter, ...) - // - zoom/scroll canvas -// - freeze_diagram, freeze_properties attributes (freeze certain components/properties?) // - rotate multiple objects around their center of mass // - rubber band wires when moving components @@ -100,11 +112,7 @@ schematic = (function() { // setup a schematic by populating the
with the appropriate children function Schematic(input) { - this.div = document.createElement('div'); - // set up div so we can position elements inside of it - this.div.style.position = 'relative'; - this.div.style.cursor = 'default'; - + // set up diagram viewing parameters this.grid = 8; this.scale = 2; this.origin_x = input.getAttribute("origin_x"); @@ -125,6 +133,15 @@ schematic = (function() { parts = []; } else parts = parts.split(','); + // now add the parts to the parts bin + this.parts_bin = []; + for (var i = 0; i < parts.length; i++) { + var part = new Part(this); + var pm = parts_map[parts[i]]; + part.set_component(new pm[0](0,0,0),pm[1]); + this.parts_bin.push(part); + } + // use user-supplied list of analyses, otherwise provide them all // analyses="" means no analyses var analyses = input.getAttribute('analyses'); @@ -135,46 +152,6 @@ schematic = (function() { if (parts.length == 0 && analyses.length == 0) this.diagram_only = true; else this.diagram_only = false; - if (!this.diagram_only) { - // start with a background element with normal positioning - this.background = document.createElement('canvas'); - this.background.style.backgroundColor = background_style; - this.background.style.borderStyle = 'solid'; - this.background.style.borderWidth = '2px'; - - this.status_div = document.createElement('div'); - this.status_div.style.position = 'absolute'; - this.status_div.style.padding = '2px'; - this.status = document.createTextNode(''); - this.status_div.appendChild(this.status); - } - - this.connection_points = new Array(); // location string => list of cp's - this.components = []; - - // this is where schematic is rendered - this.canvas = document.createElement('canvas'); - if (!this.diagram_only) { - this.canvas.tabIndex = 1; // so we get keystrokes - this.canvas.style.borderStyle = 'solid'; - this.canvas.style.borderWidth = '1px'; - this.canvas.style.borderColor = grid_style; - this.canvas.style.position = 'absolute'; - this.canvas.style.outline = 'none'; - } - - this.canvas.schematic = this; - if (this.edits_allowed) { - this.canvas.addEventListener('mousemove',schematic_mouse_move,false); - this.canvas.addEventListener('mouseover',schematic_mouse_enter,false); - this.canvas.addEventListener('mouseout',schematic_mouse_leave,false); - this.canvas.addEventListener('mousedown',schematic_mouse_down,false); - this.canvas.addEventListener('mouseup',schematic_mouse_up,false); - this.canvas.addEventListener('dblclick',schematic_double_click,false); - this.canvas.addEventListener('keydown',schematic_key_down,false); - this.canvas.addEventListener('keyup',schematic_key_up,false); - } - // toolbar this.tools = new Array(); this.toolbar = []; @@ -206,6 +183,52 @@ schematic = (function() { } } + // set up diagram canvas + this.canvas = document.createElement('canvas'); + this.width = input.getAttribute('width'); + this.width = parseInt(this.width == undefined ? '400' : this.width); + this.canvas.width = this.width; + this.height = input.getAttribute('height'); + this.height = parseInt(this.height == undefined ? '300' : this.height); + this.canvas.height = this.height; + + // repaint simply draws this buffer and then adds selected elements on top + this.bg_image = document.createElement('canvas'); + this.bg_image.width = this.width; + this.bg_image.height = this.height; + + if (!this.diagram_only) { + this.canvas.tabIndex = 1; // so we get keystrokes + this.canvas.style.borderStyle = 'solid'; + this.canvas.style.borderWidth = '1px'; + this.canvas.style.borderColor = grid_style; + //this.canvas.style.position = 'absolute'; + this.canvas.style.outline = 'none'; + } + + this.canvas.schematic = this; + if (this.edits_allowed) { + this.canvas.addEventListener('mousemove',schematic_mouse_move,false); + this.canvas.addEventListener('mouseover',schematic_mouse_enter,false); + this.canvas.addEventListener('mouseout',schematic_mouse_leave,false); + this.canvas.addEventListener('mousedown',schematic_mouse_down,false); + this.canvas.addEventListener('mouseup',schematic_mouse_up,false); + this.canvas.addEventListener('dblclick',schematic_double_click,false); + this.canvas.addEventListener('keydown',schematic_key_down,false); + this.canvas.addEventListener('keyup',schematic_key_up,false); + } + + // set up message area + if (!this.diagram_only) { + this.status_div = document.createElement('div'); + this.status = document.createTextNode(''); + this.status_div.appendChild(this.status); + this.status_div.style.height = status_height + 'px'; + } else this.status_div = undefined; + + this.connection_points = new Array(); // location string => list of cp's + this.components = []; + this.dragging = false; this.drawCursor = false; this.cursor_x = 0; @@ -222,148 +245,78 @@ schematic = (function() { this.altKey = false; this.cmdKey = false; - // repaint simply draws this buffer and then adds selected elements on top - this.bg_image = document.createElement('canvas'); - - // now add the parts to the parts bin - var parts_left = this.width + 3 + background_margin; - var parts_top = background_margin; - this.parts_bin = []; - for (var i = 0; i < parts.length; i++) { - var part = new Part(this); - var pm = parts_map[parts[i]]; - part.set_component(new pm[0](0,0,0),pm[1]); - this.parts_bin.push(part); - } - - // add all elements to the DOM - if (!this.diagram_only) { - this.div.appendChild(this.background); - for (var i = 0; i < this.toolbar.length; i++) { - var tool = this.toolbar[i]; - if (tool != null) this.div.appendChild(tool); - } - this.div.appendChild(this.status_div); - for (var i = 0; i < this.parts_bin.length; i++) - this.div.appendChild(this.parts_bin[i].canvas); - } - this.div.appendChild(this.canvas); - input.parentNode.insertBefore(this.div,input.nextSibling); - // make sure other code can find us! input.schematic = this; this.input = input; - // set locations of all the elements in the editor - var w = parseInt(input.getAttribute('width')); - var h = parseInt(input.getAttribute('height')); - this.set_locations(w,h); + // set up DOM -- use nested tables to do the layout + var table,tr,td; + table = document.createElement('table'); + if (!this.diagram_only) { + table.style.borderStyle = 'solid'; + table.style.borderWidth = '2px'; + table.style.padding = '5px'; + table.style.backgroundColor = background_style; + } + + // add tools to DOM + if (this.toolbar.length > 0) { + tr = document.createElement('tr'); + table.appendChild(tr); + td = document.createElement('td'); + td.colspan = 2; + td.vAlign = 'baseline'; + tr.appendChild(td); + for (var i = 0; i < this.toolbar.length; ++i) { + var tool = this.toolbar[i]; + if (tool != null) td.appendChild(tool); + } + } + + // add canvas and parts bin to DOM + tr = document.createElement('tr'); + tr.vAlign = 'top'; + table.appendChild(tr); + td = document.createElement('td'); + tr.appendChild(td); + td.appendChild(this.canvas); + td = document.createElement('td'); + tr.appendChild(td); + var parts_table = document.createElement('table'); + td.appendChild(parts_table); + + // fill in parts_table here!!! + var parts_per_column = Math.floor(this.height / part_h); + for (var i = 0; i < parts_per_column; ++i) { + tr = document.createElement('tr'); + parts_table.appendChild(tr); + for (var j = i; j < this.parts_bin.length; j += parts_per_column) { + td = document.createElement('td'); + tr.appendChild(td); + td.appendChild(this.parts_bin[j].canvas); + } + } + + if (this.status_div != undefined) { + tr = document.createElement('tr'); + table.appendChild(tr); + td = document.createElement('td'); + tr.appendChild(td); + td.colspan = 2; + td.appendChild(this.status_div); + } + + // add to dom + this.input.parentNode.insertBefore(table,this.input.nextSibling); // process initial contents of diagram this.load_schematic(this.input.value); } - background_margin = 5; part_w = 42; // size of a parts bin compartment part_h = 42; status_height = 18; - // w,h are the dimensions of the canvas, everyone else is positioned accordingly - Schematic.prototype.set_locations = function(w,h) { - // limit the shrinkage factor - w = Math.max(w,120); - h = Math.max(h,120); - this.width = w; - this.height = h; - this.bg_image.width = w; - this.bg_image.height = h; - - if (this.diagram_only) { - this.canvas.width = w; - this.canvas.height = h; - this.redraw_background(); // redraw diagram - return; - } - - this.min_x = 0; - this.min_y = 0; - this.max_x = w/this.scale; - this.max_y = h/this.scale; - - var left,top; - - // start with tool bar - left = 2*background_margin; // space to the left - top = background_margin; - var max_height = 0; - if (this.toolbar.length > 0) { - tool_left = left; - for (var i = 0; i < this.toolbar.length; i++) { - var tool = this.toolbar[i]; - if (tool == null) { // spacer - tool_left += 8; - continue; - } - tool.style.left = tool_left + 'px'; - tool.style.top = top + 'px'; - tool_left += tool.offsetWidth + 2; // width + padding + border + gap - max_height = Math.max(max_height,tool.offsetHeight); - } - top += max_height + 5; // height + padding + border + gap; - } - - // configure canvas - this.canvas.style.left = left + 'px'; - this.canvas.style.top = top + 'px'; - this.canvas.width = w; - this.canvas.height = h; - this.redraw_background(); // redraw diagram - - // configure status bar - this.status_div.style.left = left + 'px'; - this.status_div.style.top = this.canvas.offsetTop + this.canvas.offsetHeight + 3 + 'px'; - this.status_div.style.width = (w - 4) + 'px'; // subtract interior padding - this.status_div.style.height = status_height + 'px'; - - // configure parts bin - var total_w = this.canvas.offsetLeft + this.canvas.offsetWidth; - var parts_left = total_w + 5; - var parts_top = top; - var parts_h_limit = this.canvas.offsetTop + this.canvas.offsetHeight; - for (var i = 0; i < this.parts_bin.length; i++) { - var part = this.parts_bin[i]; - part.set_location(parts_left,parts_top); - - total_w = part.right(); - parts_top = part.bottom() + 2; - if (parts_top + part_h > parts_h_limit) { - parts_left = total_w + 2; - parts_top = top; - } - } - - // configure background - var total_h = this.status_div.offsetTop + this.status_div.offsetHeight + background_margin; - total_w += background_margin; - this.background.height = total_h; - this.background.width = total_w; - - /* enable when there's support for resizing schematic - // redraw thumb - var c = this.background.getContext('2d'); - c.clearRect(0,0,w,h); - c.strokeStyle = thumb_style; - c.lineWidth = 1; - c.beginPath(); - w = total_w - 1; - h = total_h - 1; - c.moveTo(w,h-4); c.lineTo(w-4,h); - c.moveTo(w,h-8); c.lineTo(w-8,h); - c.moveTo(w,h-12); c.lineTo(w-12,h); - c.stroke(); - */ - } - Schematic.prototype.add_component = function(new_c) { this.components.push(new_c); @@ -570,10 +523,10 @@ schematic = (function() { part.add(this) } } - - // see what we've got! - this.redraw_background(); } + + // see what we've got! + this.redraw_background(); } // label all the nodes in the circuit @@ -764,8 +717,6 @@ schematic = (function() { // Also redraws dynamic portion. Schematic.prototype.redraw_background = function() { var c = this.bg_image.getContext('2d'); - var w = this.bg_image.width; - var h = this.bg_imageheight; c.lineCap = 'round'; @@ -776,10 +727,10 @@ schematic = (function() { // grid c.strokeStyle = grid_style; - var first_x = this.min_x; - var last_x = this.max_x; - var first_y = this.min_y; - var last_y = this.max_y; + var first_x = 0; + var last_x = this.width/this.scale; + var first_y = 0; + var last_y = this.height/this.scale; for (var i = first_x; i < last_x; i += this.grid) this.draw_line(c,i,first_y,i,last_y,0.1); for (var i = first_y; i < last_y; i += this.grid) @@ -903,22 +854,27 @@ schematic = (function() { c.fillText(text,(x - this.origin_x) * this.scale,(y - this.origin_y) * this.scale); } + HTMLCanvasElement.prototype.totalOffset = function(){ + } + // add method to canvas to compute relative coords for event HTMLCanvasElement.prototype.relMouseCoords = function(event){ // run up the DOM tree to figure out coords for top,left of canvas var totalOffsetX = 0; var totalOffsetY = 0; - var canvasY = 0; var currentElement = this; do { totalOffsetX += currentElement.offsetLeft; totalOffsetY += currentElement.offsetTop; } - while(currentElement = currentElement.offsetParent); + while (currentElement = currentElement.offsetParent); // now compute relative position of click within the canvas this.mouse_x = event.pageX - totalOffsetX; this.mouse_y = event.pageY - totalOffsetY; + + this.page_x = event.pageX; + this.page_y = event.pageY; } /////////////////////////////////////////////////////////////////////////////// @@ -1365,8 +1321,8 @@ schematic = (function() { content.win = win; // so content can contact us // compute location in top-level div - win.left = this.canvas.mouse_x + this.canvas.offsetLeft; - win.top = this.canvas.mouse_y + this.canvas.offsetTop; + win.left = this.canvas.page_x; + win.top = this.canvas.page_y; // add to DOM win.style.background = 'white'; @@ -1375,7 +1331,8 @@ schematic = (function() { win.style.left = win.left + 'px'; win.style.top = win.top + 'px'; win.style.border = '2px solid'; - this.div.appendChild(win); + + this.input.parentNode.insertBefore(win,this.input.nextSibling); } // close the window @@ -1458,7 +1415,7 @@ schematic = (function() { tool.style.borderWidth = '1px'; tool.style.borderStyle = 'solid'; tool.style.borderColor = background_style; - tool.style.position = 'absolute'; + //tool.style.position = 'absolute'; tool.style.padding = '2px'; // set up event processing @@ -1783,7 +1740,7 @@ schematic = (function() { this.canvas.style.borderStyle = 'solid'; this.canvas.style.borderWidth = '1px'; this.canvas.style.borderColor = background_style; - this.canvas.style.position = 'absolute'; + //this.canvas.style.position = 'absolute'; this.canvas.style.cursor = 'default'; this.canvas.height = part_w; this.canvas.width = part_h;