Working towards a reusable matrix component to help illustrate linear algebra concepts.
try scrubbing the numbers on each matrix, notice that the colored cells correspond to each other in the 3x3.
Built with blockbuilder.org
forked from enjalot's block: matrix component
xxxxxxxxxx
<head>
<meta charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
svg { width: 100%; height: 100%; }
.number {
cursor: col-resize;
}
</style>
</head>
<body>
<svg></svg>
<script>
var matrix = {utils: {}, layout: {}, component: {}};
matrix.utils = {
merge: function(obj1, obj2){
for(var p in obj2){
if(obj2[p] && obj2[p].constructor == Object){
if(obj1[p]){
this.merge(obj1[p], obj2[p]);
continue;
}
}
obj1[p] = obj2[p];
}
},
mergeAll: function(){
var newObj = {};
for(var i = 0; i < arguments.length; i++){
this.merge(newObj, arguments[i]);
}
return newObj;
}
};
matrix.layout = function(){
/*
We accept our matrix data as a list of rows:
[ [a, b],
[c, d] ]
*/
var config = {
margin: [0, 0],
cellWidth: 20,
cellHeight: 20
};
var data = [[]];
var nodes;
var nRows;
function getX(i) {
return i * (config.cellWidth + config.margin[0])
}
function getY(j) {
return j * (config.cellHeight + config.margin[1])
}
function newNodes() {
nRows = data.length;
nodes = [];
data.forEach(function(rows,i) {
rows.forEach(function(col, j) {
var node = {
x: getX(j),
y: getY(i),
data: col,
i: i,
j: j,
index: i * nRows + j
};
nodes.push(node);
})
})
}
function calculate() {
nRows = data.length;
data.forEach(function(rows,i) {
rows.forEach(function(col, j) {
var node = nodes[i * nRows + j];
if(!node) return;
node.data = col;
node.x = getX(j);
node.y = getY(i);
});
})
}
var exports = {};
exports.nodes = function(val) {
if(val) {
this.data(val);
}
return nodes;
};
exports.data = function(val) {
if(val) {
if(val.length === data.length && val[0].length === data[0].length) {
// if the same size matrix is being updated,
// just update the values by reference
// the positions shouldn't change
data = val;
calculate();
} else {
data = val;
newNodes();
}
nRows = data.length;
return this;
}
return data;
};
exports.config = function(val) {
if(val) {
config = matrix.utils.mergeAll(config, val);
calculate();
return this;
}
return matrix.utils.mergeAll({}, config);
};
return exports;
};
matrix.component = function() {
var g;
var data = [[]];
var nodes = [];
var matrixLayout = matrix.layout();
var config = {
container: null,
mapping: [[]]
};
/*
TODO
make scrubbing configurable, per-cell
*/
var dispatch = d3.dispatch("change");
function set(val, i, j) {
var m = config.mapping[i][j];
if(m){
config.mapping.forEach(function(row, mi) {
row.forEach(function(col, mj) {
if(col === m) {
data[mi][mj] = val;
}
})
})
}
data[i][j] = val;
}
var exports = {};
exports.update = function(_data) {
if(config.container) g = config.container;
data = _data;
nodes = matrixLayout.nodes(data);
var layout = matrixLayout.config();
var line = d3.svg.line()
.x(function(d) { return d[0] })
.y(function(d) { return d[1] });
var brackets = g.selectAll("path.brackets")
.data([1, -1]);
brackets.enter().append("path")
.attr("d", function(d) {
var nRows = data.length;
var x0 = d * layout.cellWidth/4;
var x1 = -layout.margin[0]/2;
var y0 = -layout.margin[1]/2;
var y1 = (layout.cellHeight + layout.margin[1]) * nRows - layout.margin[1]/2;
if(d === 1) {
return line([
[x0, y0],
[x1, y0],
[x1, y1],
[x0, y1]
])
} else {
var dx = (layout.cellWidth + layout.margin[0]) * data[0].length - layout.margin[0]/2;
x0 -= layout.margin[0]/2;
return line([
[x0 + dx, y0],
[dx, y0],
[dx, y1],
[x0 + dx, y1]
])
}
}).attr({
stroke: "#111",
fill: "none"
});
var cells = g.selectAll("g.number").data(nodes);
var enter = cells.enter().append("g").classed("number", true);
enter.append("rect").classed("bg", true);
cells.select("rect.bg")
.attr({
width: layout.cellWidth,
height: layout.cellHeight,
x: function(d) { return d.x },
y: function(d) { return d.y },
fill: "#fff"
});
enter.append("text");
cells.select("text").attr({
x: function(d) { return d.x + layout.cellWidth/2 },
y: function(d) { return d.y + layout.cellHeight/2 },
"alignment-baseline": "middle",
"text-anchor": "middle",
"line-height": layout.cellHeight,
"fill": "#091242"
}).text(function(d) { return d.data });
var step = 0.1;
var that = this;
var drag = d3.behavior.drag()
.on("drag", function(d) {
var oldData = d.data;
var val = d.data + d3.event.dx * step;
val = +(Math.round(val*10)/10).toFixed(1);
set(val, d.i, d.j);
//data[d.i][d.j] = val;
that.update(data);
dispatch.change(d, oldData)
});
cells.call(drag);
return this;
};
exports.config = function(val) {
if(val) {
config = matrix.utils.mergeAll(config, val);
matrixLayout.config(val);
return this;
}
return matrix.utils.mergeAll({}, config);
};
d3.rebind(exports, dispatch, "on");
return exports;
};
// Usage
//////////////////
var twobytwo = [
[2, -3],
[-3, 1]
];
// our data is a list of rows, which matches the numeric.js format
var threebythree = [
[1, 2, 3],
[0, 0, 0],
[3, 1, 2]
];
// we want to link cells to the same value
var threemapping = [
["a", "b", "c"],
[ 0, 0, 0],
["c", "a", "b"]
];
// cell size
var size = 30;
var svg = d3.select("svg");
// create a container to hold our first matrix
var twog = svg.append("g")
.attr("transform", "translate(100, 100)");
// instantiate our layout
var twom = matrix.component()
.config({
container: twog,
margin: [15, 15],
cellWidth: size,
cellHeight: size
})
.update(twobytwo); //render the matrix into our group
var threeg = svg.append("g")
.attr("transform", "translate(243, 86)");
// TODO: structure the component so it doesn't need to use "new"
var threem = matrix.component()
.config({
container: threeg,
margin: [10, 10],
cellWidth: size + 10,
cellHeight: size,
mapping: threemapping // we can define links between cells
})
.update(threebythree);
//
// listen for changes to the data (due to scrubbing)
threem.on("change", function(d, oldData) {
console.log("matrix changed", d, d.data, oldData);
});
var color = d3.scale.category10();
threeg.selectAll("rect.bg").style({
//fill: "#ccfee9",
fill: function(d) {
var m = threemapping[d.i][d.j];
if(m)
return d3.hsl(color(m)).brighter(1.6)
},
rx: 4,
ry: 4
});
</script>
</body>
https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js