I have made a gauge chart using D3.js and it is working perfectly. However, when I am trying to add the same javascript code to render the same chart in a Qlikview extension, it is not working.
Can anyone please help me to find out where I am going wrong? I am attaching the code of the Script.js file below:
Qva.LoadScript("QlikView/Examples/Gauge/d3.min.js", function () {
/*
function loadGoogleCoreChart() {
google.load('visualization', '1', {
packages: ['corechart'],
callback: googleCoreChartLoaded
});
}
function googleCoreChartLoaded() {
*/
Qva.AddExtension('QlikView/Examples/Gauge', function () {
//this.Element.innerHTML = "<script type=\"text/javascript\" src=\"http://d3js.org/d3.v2.min.js\"></script>"
//this.Element.innerHTML = "";
var gauge = function(container, configuration) {
var that = {};
var config = {
size : 200,
clipWidth : 200,
clipHeight : 110,
ringInset : 20,
ringWidth : 20,
pointerWidth : 10,
pointerTailLength : 5,
pointerHeadLengthPercent : 0.9,
minValue : 0,
maxValue : 10,
minAngle : -90,
maxAngle : 90,
transitionMs : 750,
majorTicks : 5,
labelFormat : d3.format(',g'),
labelInset : 10,
arcColorFn : d3.interpolateHsl(d3.rgb('#e8e2ca'), d3.rgb('#3e6c0a'))
};
var range = undefined;
var r = undefined;
var pointerHeadLength = undefined;
var value = 0;
var svg = undefined;
var arc = undefined;
var scale = undefined;
var ticks = undefined;
var tickData = undefined;
var pointer = undefined;
var donut = d3.layout.pie();
function deg2rad(deg) {
return deg * Math.PI / 180;
}
function newAngle(d) {
var ratio = scale(d);
var newAngle = config.minAngle + (ratio * range);
return newAngle;
}
function configure(configuration) {
var prop = undefined;
for ( prop in configuration ) {
config[prop] = configuration[prop];
}
range = config.maxAngle - config.minAngle;
r = config.size / 2;
pointerHeadLength = Math.round(r * config.pointerHeadLengthPercent);
// a linear scale that maps domain values to a percent from 0..1
scale = d3.scale.linear()
.range([0,1])
.domain([config.minValue, config.maxValue]);
ticks = scale.ticks(config.majorTicks);
tickData = d3.range(config.majorTicks).map(function() {return 1/config.majorTicks;});
arc = d3.svg.arc()
.innerRadius(r - config.ringWidth - config.ringInset)
.outerRadius(r - config.ringInset)
.startAngle(function(d, i) {
var ratio = d * i;
return deg2rad(config.minAngle + (ratio * range));
})
.endAngle(function(d, i) {
var ratio = d * (i+1);
return deg2rad(config.minAngle + (ratio * range));
});
}
that.configure = configure;
function centerTranslation() {
return 'translate('+r +','+ r +')';
}
function isRendered() {
return (svg !== undefined);
}
that.isRendered = isRendered;
function render(newValue) {
svg = d3.select(container)
.append('svg:svg')
.attr('class', 'gauge')
.attr('width', config.clipWidth)
.attr('height', config.clipHeight);
var centerTx = centerTranslation();
var arcs = svg.append('g')
.attr('class', 'arc')
.attr('transform', centerTx);
arcs.selectAll('path')
.data(tickData)
.enter().append('path')
.attr('fill', function(d, i) {
return config.arcColorFn(d * i);
})
.attr('d', arc);
var lg = svg.append('g')
.attr('class', 'label')
.attr('transform', centerTx);
lg.selectAll('text')
.data(ticks)
.enter().append('text')
.attr('transform', function(d) {
var ratio = scale(d);
var newAngle = config.minAngle + (ratio * range);
return 'rotate(' +newAngle +') translate(0,' +(config.labelInset - r) +')';
})
.text(config.labelFormat);
var lineData = [ [config.pointerWidth / 2, 0],
[0, -pointerHeadLength],
[-(config.pointerWidth / 2), 0],
[0, config.pointerTailLength],
[config.pointerWidth / 2, 0] ];
var pointerLine = d3.svg.line().interpolate('monotone');
var pg = svg.append('g').data([lineData])
.attr('class', 'pointer')
.attr('transform', centerTx);
pointer = pg.append('path')
.attr('d', pointerLine/*function(d) { return pointerLine(d) +'Z';}*/ )
.attr('transform', 'rotate(' +config.minAngle +')');
update(newValue === undefined ? 0 : newValue);
}
that.render = render;
function update(newValue, newConfiguration) {
if ( newConfiguration !== undefined) {
configure(newConfiguration);
}
var ratio = scale(newValue);
var newAngle = config.minAngle + (ratio * range);
pointer.transition()
.duration(config.transitionMs)
.ease('elastic')
.attr('transform', 'rotate(' +newAngle +')');
}
that.update = update;
configure(configuration);
return that;
};
function onDocumentReady() {
var powerGauge = gauge(this.Element, {
size: 300,
clipWidth: 300,
clipHeight: 300,
ringWidth: 60,
maxValue: 10,
transitionMs: 4000,
});
alert("Some Text");
powerGauge.render();
function updateReadings() {
// just pump in random data here...
powerGauge.update(Math.random() * 10);
}
// every few seconds update reading values
updateReadings();
setInterval(function() {
updateReadings();
}, 5 * 1000);
}
if ( !window.isLoaded ) {
window.addEventListener("load", function() {
onDocumentReady();
}, false);
} else {
onDocumentReady();
}
});
});
Please help me with this as I am unable to find out the glitch.
Related
I am trying to use crossfilter.js and d3.js to generate brushes and charts to filter my data. The dates are in the format "2019-04-08T09:07:22.512Z" . I tried different ways to parse through the date and to generate the charts but am unable to do so . I get the following error in the console 't.apply is not a function'. Looking at the error I presume there is a problem with the render and renderAll functions.
d3 version 5.7.0
var cf = crossfilter(json);
var all = cf.groupAll();
var byAlertType = cf.dimension(function(p) {return p['alertType'];});
var byDate = cf.dimension(function (p) { return p['created'].substring(0,10);});
var byHour = cf.dimension(function (p) { return p['created'].substring(11,13);});
var byTimezone = cf.dimension(function (p) { return p['created'].substring(30,39);});
var groupByAlertType = byAlertType.group();
var groupByDate = byDate.group();
var groupByHour = byHour.group();
var groupByTimezone = byTimezone.group();
//data selected, counts by day date hour
groupByAlertType.top(Infinity).forEach(function(p,i){
console.log(p.key + ":" + p.value);
});
groupByDate.top(Infinity).forEach(function(p,i){
console.log(p.key + ":" + p.value);
});
groupByTimezone.top(Infinity).forEach(function(p,i){
console.log(p.key + ":" + p.value);
});
groupByHour.top(Infinity).forEach(function(p,i){
console.log(p.key + ":" + p.value);
});
var charts = [
barChart()
.dimension(byHour)
.group(groupByHour)
.x(d3.scaleLinear()
.domain([0,24])
.rangeRound([0, 10*24])),
barChart()
.dimension(byDate)
.group(groupByDate)
.round(d3.timeDay.round)
.x(d3.scaleTime()
.domain([new Date(2019,4,1),new Date(2019,5,1)])
.rangeRound([0,10*30])),
barChart()
.dimension(byAlertType)
.group(groupByAlertType)
.x(d3.scaleLinear()
.domain([0,24])
.rangeRound([0, 10*24])),
barChart()
.dimension(byTimezone)
.group(groupByTimezone)
.x(d3.scaleLinear()
.domain([0,24])
.rangeRound([0, 10*24])),
];
//charts as they appear in DOM
const viz = d3.selectAll('.chart')
.data([charts])
// render initial list
const list = d3.selectAll('.list')
.data([alertList]);
d3.selectAll('#total')
.text((cf.size()));
renderAll();
function render(method){
d3.select(this).call(method);
}
function renderAll() {
viz.each(render);
list.each(render);
d3.select('#active').text(all.value());
}
window.filter = filters => {
filters.forEach((d, i) => { charts[i].filter(d); });
renderAll();
};
window.reset = i => {
charts[i].filter(null);
renderAll();
};
function alertList(div) {
}
function barChart() {
if (!barChart.id) barChart.id = 0;
let margin = { top: 10, right: 13, bottom: 20, left: 10 };
let x;
let y = d3.scaleLinear().range([100, 0]);
const id = barChart.id++;
const axis = d3.axisBottom();
const brush = d3.brushX();
let brushDirty;
let dimension;
let group;
let round;
let gBrush;
function chart(div) {
const width = x.range()[1];
const height = y.range()[0];
brush.extent([[0, 0], [width, height]]);
y.domain([0, group.top(1)[0].value]);
div.each(function () {
const div = d3.select(this);
let g = div.select('g');
// Create the skeletal chart.
if (g.empty()) {
div.select('.title').append('a')
.attr('href', `javascript:reset(${id})`)
.attr('class', 'reset')
.text('reset')
.style('display', 'none');
g = div.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
g.append('clipPath')
.attr('id', `clip-${id}`)
.append('rect')
.attr('width', width)
.attr('height', height);
g.selectAll('.bar')
.data(['background', 'foreground'])
.enter().append('path')
.attr('class', d => `${d} bar`)
.datum(group.all());
g.selectAll('.foreground.bar')
.attr('clip-path', `url(#clip-${id})`);
g.append('g')
.attr('class', 'axis')
.attr('transform', `translate(0,${height})`)
.call(axis);
// Initialize the brush component with pretty resize handles.
gBrush = g.append('g')
.attr('class', 'brush')
.call(brush);
gBrush.selectAll('.handle--custom')
.data([{ type: 'w' }, { type: 'e' }])
.enter().append('path')
.attr('class', 'brush-handle')
.attr('cursor', 'ew-resize')
.attr('d', resizePath)
.style('display', 'none');
}
// Only redraw the brush if set externally.
if (brushDirty !== false) {
const filterVal = brushDirty;
brushDirty = false;
div.select('.title a').style('display', d3.brushSelection(div) ? null : 'none');
if (!filterVal) {
g.call(brush);
g.selectAll(`#clip-${id} rect`)
.attr('x', 0)
.attr('width', width);
g.selectAll('.brush-handle').style('display', 'none');
renderAll();
} else {
const range = filterVal.map(x);
brush.move(gBrush, range);
}
}
g.selectAll('.bar').attr('d', barPath);
});
function barPath(groups) {
const path = [];
let i = -1;
const n = groups.length;
let d;
while (++i < n) {
d = groups[i];
path.push('M', x(d.key), ',', height, 'V', y(d.value), 'h9V', height);
}
return path.join('');
}
function resizePath(d) {
const e = +(d.type === 'e');
const x = e ? 1 : -1;
const y = height / 3;
return `M${0.5 * x},
${y}A6,6 0 0 ${e} ${6.5 * x},
${y + 6}V${2 * y - 6}A6,6 0 0 ${e} ${0.5 * x},${2 * y}ZM${2.5 * x},${y + 8}V${2 * y - 8}M${4.5 * x},${y + 8}V${2 * y - 8}`;
}
}
brush.on('start.chart', function () {
const div = d3.select(this.parentNode.parentNode.parentNode);
div.select('.title a').style('display', null);
});
brush.on('brush.chart', function () {
const g = d3.select(this.parentNode);
const brushRange = d3.event.selection || d3.brushSelection(this); // attempt to read brush range
const xRange = x && x.range(); // attempt to read range from x scale
let activeRange = brushRange || xRange; // default to x range if no brush range available
const hasRange = activeRange &&
activeRange.length === 2 &&
!isNaN(activeRange[0]) &&
!isNaN(activeRange[1]);
if (!hasRange) return; // quit early if we don't have a valid range
// calculate current brush extents using x scale
let extents = activeRange.map(x.invert);
// if rounding fn supplied, then snap to rounded extents
// and move brush rect to reflect rounded range bounds if it was set by user interaction
if (round) {
extents = extents.map(round);
activeRange = extents.map(x);
if (
d3.event.sourceEvent &&
d3.event.sourceEvent.type === 'mousemove'
) {
d3.select(this).call(brush.move, activeRange);
}
}
// move brush handles to start and end of range
g.selectAll('.brush-handle')
.style('display', null)
.attr('transform', (d, i) => `translate(${activeRange[i]}, 0)`);
// resize sliding window to reflect updated range
g.select(`#clip-${id} rect`)
.attr('x', activeRange[0])
.attr('width', activeRange[1] - activeRange[0]);
// filter the active dimension to the range extents
dimension.filterRange(extents);
// re-render the other charts accordingly
renderAll();
});
brush.on('end.chart', function () {
// reset corresponding filter if the brush selection was cleared
// (e.g. user "clicked off" the active range)
if (!d3.brushSelection(this)) {
reset(id);
}
});
chart.margin = function (_) {
if (!arguments.length) return margin;
margin = _;
return chart;
};
chart.x = function (_) {
if (!arguments.length) return x;
x = _;
axis.scale(x);
return chart;
};
chart.y = function (_) {
if (!arguments.length) return y;
y = _;
return chart;
};
chart.dimension = function (_) {
if (!arguments.length) return dimension;
dimension = _;
return chart;
};
chart.filter = _ => {
if (!_) dimension.filterAll();
brushDirty = _;
return chart;
};
chart.group = function (_) {
if (!arguments.length) return group;
group = _;
return chart;
};
chart.round = function (_) {
if (!arguments.length) return round;
round = _;
return chart;
};
chart.gBrush = () => gBrush;
return chart;
}
};
};
Following Pluralsight D3.js Data Visualization Fundamentals Course.
Get error:
Data is from this Github endpoint
Plunker is here
Code:
var realHeight = document.getElementById('d3Container').offsetHeight;
var realWidth = document.getElementById('d3Container').offsetWidth;
var scale = d3.scaleLinear().domain([130, 350]).range([10, 100]);
var githubUrl = 'https://api.github.com/repos/bsullins/d3js-resources/contents/monthlySalesbyCategoryMultiple.json';
var buildLine = function(ds, dl, idx) {
var minSales = d3.min(ds.monthlySales, function(d) {
return d.month
});
var maxSales = d3.max(ds.monthlySales, function(d) {
return d.month
});
// create scales
var xScale = d3.scaleLinear()
.domain([minSales, maxSales])
.range(0, realWidth);
var yScale = d3.scaleLinear()
.domain([0, maxSales])
.range([realHeight, 0]);
// add scales below in .x and .y
var lineFun = d3.line()
.x(function(d) {
console.log(d.month); // outputs as type numbers in console
// return ((d.month -20130001)/3.25); // hardcoded works
return xScale( d.month );
})
.y(function(d, i) {
// var svgHeight = document.getElementById('d3Container').offsetHeight;
// return ( (svgHeight / ( dl * (1+idx) ) - d.sales) ); // hardcoded works
return yScale( d.sales );
})
.curve(d3.curveLinear);
var svg3 = d3.select('body #d3Container')
.append('svg')
.attrs({
'h': '100%',
'w': '100%',
'fill': 'black'
});
var viz = svg3.append('path')
.attrs({
/** access nested JSON here **/
'd': lineFun(ds.monthlySales),
'stroke': function() {
return (idx + 1 === dl) ? 'royalblue' : 'lime';
},
'stroke-width': 4,
'fill': 'white;'
})
};
var showHeader = function(ds) {
d3.select('body #d3Container')
.append('h2')
.text(ds.category + 'Sales 2013');
};
d3.json(githubUrl, function(error, data) {
if (error) {
return;
} else {
// decode data
var decodedData = JSON.parse(window.atob(data.content));
// pass in functions for each
decodedData.contents.forEach(function(ds, idx) {
showHeader(ds);
buildLine(ds, decodedData.contents.length, idx);
})
}
});
Easiest to work with the plunk.
Can uncomment out the 'hardcoded works' comments to see what it should look like.
In D3 (any version), range() accepts an array.
Your xScale right now has this range:
.range(0, hackWidth);
However, it has to be:
.range([0, hackWidth]);
Here is the updated plunker: https://plnkr.co/edit/DB0VVRsGQO4teIC6ltyQ?p=preview
How would I add padding between some groups of segments in a donut/pie chart using d3?
UPDATE
I am using the d3.svg.arc shape generator and .padAngle(paddingFunction) where paddingFunction is defined as:
var paddingFunction = function(d,i) { return i%1==0 ? 0.1 : 0 };
This image is using the paddingFunction described above.
But if I change the padding function to this:
var paddingFunction = function(d,i) { return i%5==0 ? 0.1 : 0 };
I get this image:
Shouldn't the code return two groups of segments with a gap in-between?
Complete code:
// magic numbers
var t = 2 * Math.PI;
var arcHeight = 100;
var innerRadius = 50;
var hours = 10;
function postion(i,offset) {
offset = offset || 0;
return i*(t / hours) + offset*(t / hours)
}
var paddingFunction = function(d,i) { return i%1==0 ? 0.1 : 0 };
// arc generators
var arc = d3.svg.arc()
.innerRadius(function(d,i) { return innerRadius; })
.outerRadius(function(d,i) { return innerRadius + arcHeight; })
.startAngle(function(d, i){ return postion(d.hour);})
.endAngle(function(d, i){ return postion(d.hour,1);})
.padAngle(paddingFunction);
// data
var data = d3.range(0,hours);
data = data.map(function(d) {
return {
hour: d,
color: Math.random(),
}
});
// Scales
var colorScale = d3.scale.linear()
.domain([0, 1])
.interpolate(d3.interpolateRgb)
.range(["#ffffff", "#ffba19"]);
// render viz
var svg = d3.select("svg")
.append("g")
.attr("transform", "translate(320, 320)");
var select = svg.selectAll(".fore").data(data).enter();
select.append("path")
.classed("fore", true)
.style({
"stroke": "black",
"stroke-width": 1,
"fill": function(d) { return colorScale(d.color); }
})
.attr("d", arc);
UPDATE 2
Seems that I misunderstood how the .padAngle() method works. It adds padding on BOTH sides of a segment, I thought it added a gap between segments.
Is there an alternative method in d3 which adds a gap between segements (whilst recalculating the area of the segments so they all keep their proportions)?
Running code: http://blockbuilder.org/GitNoise/13f38aa8f4f2f06dd869
I've started creating a graph with nvd3 and need to fetch the 'brushend' event.
Ive found the following information on this topic:
https://github.com/novus/nvd3/issues/942
how to add brush in nvd3 like d3
The following code works with the 'brush' event but not the 'brushend'
var chart = nv.models.lineWithFocusChart();
chart.brushExtent([50,70]);
chart.xAxis.tickFormat(d3.format(',f'));
chart.x2Axis.tickFormat(d3.format(',f'));
chart.yAxis.tickFormat(d3.format(',.2f'));
chart.y2Axis.tickFormat(d3.format(',.2f'));
chart.useInteractiveGuideline(true);
chart.dispatch.on('brush', function(brush){ console.log(brush); } );
d3.select("#chart svg").datum(tmp).call(chart);
I fixed the problem by adding some lines to the lineChart.js file:
nv.models.lineChart = function() {
"use strict";
var lines = nv.models.line()
, xAxis = nv.models.axis()
, yAxis = nv.models.axis()
, legend = nv.models.legend()
, interactiveLayer = nv.interactiveGuideline()
, tooltip = nv.models.tooltip()
, lines2 = nv.models.line()
, x2Axis = nv.models.axis()
, y2Axis = nv.models.axis()
, brush = d3.svg.brush()
;
var margin = {top: 30, right: 20, bottom: 50, left: 60}
, margin2 = {top: 0, right: 20, bottom: 20, left: 60}
, color = nv.utils.defaultColor()
, width = null
, height = null
, showLegend = true
, showXAxis = true
, showYAxis = true
, rightAlignYAxis = false
, useInteractiveGuideline = false
, x
, y
, x2
, y2
, focusEnable = false
, focusShowAxisY = false
, focusShowAxisX = true
, focusHeight = 50
, brushExtent = null
, state = nv.utils.state()
, defaultState = null
, noData = null
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush', 'stateChange', 'changeState', 'renderEnd', 'brushend')
, duration = 250
;
// set options on sub-objects for this chart
xAxis.orient('bottom').tickPadding(7);
yAxis.orient(rightAlignYAxis ? 'right' : 'left');
lines.clipEdge(true).duration(0);
lines2.interactive(false);
// We don't want any points emitted for the focus chart's scatter graph.
lines2.pointActive(function(d) { return false; });
x2Axis.orient('bottom').tickPadding(5);
y2Axis.orient(rightAlignYAxis ? 'right' : 'left');
tooltip.valueFormatter(function(d, i) {
return yAxis.tickFormat()(d, i);
}).headerFormatter(function(d, i) {
return xAxis.tickFormat()(d, i);
});
interactiveLayer.tooltip.valueFormatter(function(d, i) {
return yAxis.tickFormat()(d, i);
}).headerFormatter(function(d, i) {
return xAxis.tickFormat()(d, i);
});
//============================================================
// Private Variables
//------------------------------------------------------------
var renderWatch = nv.utils.renderWatch(dispatch, duration);
var stateGetter = function(data) {
return function(){
return {
active: data.map(function(d) { return !d.disabled; })
};
};
};
var stateSetter = function(data) {
return function(state) {
if (state.active !== undefined)
data.forEach(function(series,i) {
series.disabled = !state.active[i];
});
};
};
function chart(selection) {
renderWatch.reset();
renderWatch.models(lines);
renderWatch.models(lines2);
if (showXAxis) renderWatch.models(xAxis);
if (showYAxis) renderWatch.models(yAxis);
if (focusShowAxisX) renderWatch.models(x2Axis);
if (focusShowAxisY) renderWatch.models(y2Axis);
selection.each(function(data) {
var container = d3.select(this);
nv.utils.initSVG(container);
var availableWidth = nv.utils.availableWidth(width, container, margin),
availableHeight1 = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focusHeight : 0),
availableHeight2 = focusHeight - margin2.top - margin2.bottom;
chart.update = function() {
if( duration === 0 ) {
container.call( chart );
} else {
container.transition().duration(duration).call(chart);
}
};
chart.container = this;
state
.setter(stateSetter(data), chart.update)
.getter(stateGetter(data))
.update();
// DEPRECATED set state.disableddisabled
state.disabled = data.map(function(d) { return !!d.disabled; });
if (!defaultState) {
var key;
defaultState = {};
for (key in state) {
if (state[key] instanceof Array)
defaultState[key] = state[key].slice(0);
else
defaultState[key] = state[key];
}
}
// Display noData message if there's nothing to show.
if (!data || !data.length || !data.filter(function(d) { return d.values.length; }).length) {
nv.utils.noData(chart, container);
return chart;
} else {
container.selectAll('.nv-noData').remove();
}
// Setup Scales
x = lines.xScale();
y = lines.yScale();
x2 = lines2.xScale();
y2 = lines2.yScale();
// Setup containers and skeleton of chart
var wrap = container.selectAll('g.nv-wrap.nv-lineChart').data([data]);
var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g');
var g = wrap.select('g');
gEnter.append('g').attr('class', 'nv-legendWrap');
var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
focusEnter.append('g').attr('class', 'nv-background').append('rect');
focusEnter.append('g').attr('class', 'nv-x nv-axis');
focusEnter.append('g').attr('class', 'nv-y nv-axis');
focusEnter.append('g').attr('class', 'nv-linesWrap');
focusEnter.append('g').attr('class', 'nv-interactive');
var contextEnter = gEnter.append('g').attr('class', 'nv-context');
contextEnter.append('g').attr('class', 'nv-background').append('rect');
contextEnter.append('g').attr('class', 'nv-x nv-axis');
contextEnter.append('g').attr('class', 'nv-y nv-axis');
contextEnter.append('g').attr('class', 'nv-linesWrap');
contextEnter.append('g').attr('class', 'nv-brushBackground');
contextEnter.append('g').attr('class', 'nv-x nv-brush');
// Legend
if (showLegend) {
legend.width(availableWidth);
g.select('.nv-legendWrap')
.datum(data)
.call(legend);
if ( margin.top != legend.height()) {
margin.top = legend.height();
availableHeight1 = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focusHeight : 0);
}
wrap.select('.nv-legendWrap')
.attr('transform', 'translate(0,' + (-margin.top) +')');
}
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
if (rightAlignYAxis) {
g.select(".nv-y.nv-axis")
.attr("transform", "translate(" + availableWidth + ",0)");
}
//Set up interactive layer
if (useInteractiveGuideline) {
interactiveLayer
.width(availableWidth)
.height(availableHeight1)
.margin({left:margin.left, top:margin.top})
.svgContainer(container)
.xScale(x);
wrap.select(".nv-interactive").call(interactiveLayer);
}
g.select('.nv-focus .nv-background rect')
.attr('width', availableWidth)
.attr('height', availableHeight1);
lines
.width(availableWidth)
.height(availableHeight1)
.color(data.map(function(d,i) {
return d.color || color(d, i);
}).filter(function(d,i) { return !data[i].disabled; }));
var linesWrap = g.select('.nv-linesWrap')
.datum(data.filter(function(d) { return !d.disabled; }));
// Setup Main (Focus) Axes
if (showXAxis) {
xAxis
.scale(x)
._ticks(nv.utils.calcTicksX(availableWidth/100, data) )
.tickSize(-availableHeight1, 0);
}
if (showYAxis) {
yAxis
.scale(y)
._ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
.tickSize( -availableWidth, 0);
}
//============================================================
// Update Axes
//============================================================
function updateXAxis() {
if(showXAxis) {
g.select('.nv-focus .nv-x.nv-axis')
.transition()
.duration(duration)
.call(xAxis)
;
}
}
function updateYAxis() {
if(showYAxis) {
g.select('.nv-focus .nv-y.nv-axis')
.transition()
.duration(duration)
.call(yAxis)
;
}
}
g.select('.nv-focus .nv-x.nv-axis')
.attr('transform', 'translate(0,' + availableHeight1 + ')');
if( !focusEnable )
{
linesWrap.call(lines);
updateXAxis();
updateYAxis();
}
else
{
lines2
.defined(lines.defined())
.width(availableWidth)
.height(availableHeight2)
.color(data.map(function(d,i) {
return d.color || color(d, i);
}).filter(function(d,i) { return !data[i].disabled; }));
g.select('.nv-context')
.attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')')
.style('display', focusEnable ? 'initial' : 'none')
;
var contextLinesWrap = g.select('.nv-context .nv-linesWrap')
.datum(data.filter(function(d) { return !d.disabled; }))
;
d3.transition(contextLinesWrap).call(lines2);
// Setup Brush
brush
.x(x2)
.on('brush', function() {
onBrush();
})
.on('brushend', function() {
dispatch.brushend({brush: brush});
});
if (brushExtent) brush.extent(brushExtent);
var brushBG = g.select('.nv-brushBackground').selectAll('g')
.data([brushExtent || brush.extent()]);
var brushBGenter = brushBG.enter()
.append('g');
brushBGenter.append('rect')
.attr('class', 'left')
.attr('x', 0)
.attr('y', 0)
.attr('height', availableHeight2);
brushBGenter.append('rect')
.attr('class', 'right')
.attr('x', 0)
.attr('y', 0)
.attr('height', availableHeight2);
var gBrush = g.select('.nv-x.nv-brush')
.call(brush);
gBrush.selectAll('rect')
.attr('height', availableHeight2);
gBrush.selectAll('.resize').append('path').attr('d', resizePath);
onBrush();
g.select('.nv-context .nv-background rect')
.attr('width', availableWidth)
.attr('height', availableHeight2);
// Setup Secondary (Context) Axes
if (focusShowAxisX) {
x2Axis
.scale(x2)
._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
.tickSize(-availableHeight2, 0);
g.select('.nv-context .nv-x.nv-axis')
.attr('transform', 'translate(0,' + y2.range()[0] + ')');
d3.transition(g.select('.nv-context .nv-x.nv-axis'))
.call(x2Axis);
}
if (focusShowAxisY) {
y2Axis
.scale(y2)
._ticks( nv.utils.calcTicksY(availableHeight2/36, data) )
.tickSize( -availableWidth, 0);
d3.transition(g.select('.nv-context .nv-y.nv-axis'))
.call(y2Axis);
}
g.select('.nv-context .nv-x.nv-axis')
.attr('transform', 'translate(0,' + y2.range()[0] + ')');
}
//============================================================
// Event Handling/Dispatching (in chart's scope)
//------------------------------------------------------------
legend.dispatch.on('stateChange', function(newState) {
for (var key in newState)
state[key] = newState[key];
dispatch.stateChange(state);
chart.update();
});
interactiveLayer.dispatch.on('elementMousemove', function(e) {
lines.clearHighlights();
var singlePoint, pointIndex, pointXLocation, allData = [];
data
.filter(function(series, i) {
series.seriesIndex = i;
return !series.disabled && !series.disableTooltip;
})
.forEach(function(series,i) {
var extent = focusEnable ? (brush.empty() ? x2.domain() : brush.extent()) : x.domain();
var currentValues = series.values.filter(function(d,i) {
return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
});
pointIndex = nv.interactiveBisect(currentValues, e.pointXValue, lines.x());
var point = currentValues[pointIndex];
var pointYValue = chart.y()(point, pointIndex);
if (pointYValue !== null) {
lines.highlightPoint(series.seriesIndex, pointIndex, true);
}
if (point === undefined) return;
if (singlePoint === undefined) singlePoint = point;
if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
allData.push({
key: series.key,
value: pointYValue,
color: color(series,series.seriesIndex),
data: point
});
});
//Highlight the tooltip entry based on which point the mouse is closest to.
if (allData.length > 2) {
var yValue = chart.yScale().invert(e.mouseY);
var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
var threshold = 0.03 * domainExtent;
var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value;}),yValue,threshold);
if (indexToHighlight !== null)
allData[indexToHighlight].highlight = true;
}
interactiveLayer.tooltip
.chartContainer(chart.container.parentNode)
.valueFormatter(function(d,i) {
return d === null ? "N/A" : yAxis.tickFormat()(d);
})
.data({
value: chart.x()( singlePoint,pointIndex ),
index: pointIndex,
series: allData
})();
interactiveLayer.renderGuideLine(pointXLocation);
});
interactiveLayer.dispatch.on('elementClick', function(e) {
var pointXLocation, allData = [];
data.filter(function(series, i) {
series.seriesIndex = i;
return !series.disabled;
}).forEach(function(series) {
var pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
var point = series.values[pointIndex];
if (typeof point === 'undefined') return;
if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
var yPos = chart.yScale()(chart.y()(point,pointIndex));
allData.push({
point: point,
pointIndex: pointIndex,
pos: [pointXLocation, yPos],
seriesIndex: series.seriesIndex,
series: series
});
});
lines.dispatch.elementClick(allData);
});
interactiveLayer.dispatch.on("elementMouseout",function(e) {
lines.clearHighlights();
});
dispatch.on('changeState', function(e) {
if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
data.forEach(function(series,i) {
series.disabled = e.disabled[i];
});
state.disabled = e.disabled;
}
chart.update();
});
//============================================================
// Functions
//------------------------------------------------------------
// Taken from crossfilter (http://square.github.com/crossfilter/)
function resizePath(d) {
var e = +(d == 'e'),
x = e ? 1 : -1,
y = availableHeight2 / 3;
return 'M' + (0.5 * x) + ',' + y
+ 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
+ 'V' + (2 * y - 6)
+ 'A6,6 0 0 ' + e + ' ' + (0.5 * x) + ',' + (2 * y)
+ 'Z'
+ 'M' + (2.5 * x) + ',' + (y + 8)
+ 'V' + (2 * y - 8)
+ 'M' + (4.5 * x) + ',' + (y + 8)
+ 'V' + (2 * y - 8);
}
function updateBrushBG() {
if (!brush.empty()) brush.extent(brushExtent);
brushBG
.data([brush.empty() ? x2.domain() : brushExtent])
.each(function(d,i) {
var leftWidth = x2(d[0]) - x.range()[0],
rightWidth = availableWidth - x2(d[1]);
d3.select(this).select('.left')
.attr('width', leftWidth < 0 ? 0 : leftWidth);
d3.select(this).select('.right')
.attr('x', x2(d[1]))
.attr('width', rightWidth < 0 ? 0 : rightWidth);
});
}
function onBrush() {
brushExtent = brush.empty() ? null : brush.extent();
var extent = brush.empty() ? x2.domain() : brush.extent();
//The brush extent cannot be less than one. If it is, don't update the line chart.
if (Math.abs(extent[0] - extent[1]) <= 1) {
return;
}
dispatch.brush({extent: extent, brush: brush});
updateBrushBG();
// Update Main (Focus)
var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
.datum(
data
.filter(function(d) { return !d.disabled; })
.map(function(d,i) {
return {
key: d.key,
area: d.area,
classed: d.classed,
values: d.values.filter(function(d,i) {
return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
}),
disableTooltip: d.disableTooltip
};
})
);
focusLinesWrap.transition().duration(duration).call(lines);
// Update Main (Focus) Axes
updateXAxis();
updateYAxis();
}
});
renderWatch.renderEnd('lineChart immediate');
return chart;
}
//============================================================
// Event Handling/Dispatching (out of chart's scope)
//------------------------------------------------------------
lines.dispatch.on('elementMouseover.tooltip', function(evt) {
if(!evt.series.disableTooltip){
tooltip.data(evt).hidden(false);
}
});
lines.dispatch.on('elementMouseout.tooltip', function(evt) {
tooltip.hidden(true);
});
//============================================================
// Expose Public Variables
//------------------------------------------------------------
// expose chart's sub-components
chart.dispatch = dispatch;
chart.lines = lines;
chart.lines2 = lines2;
chart.legend = legend;
chart.xAxis = xAxis;
chart.x2Axis = x2Axis;
chart.yAxis = yAxis;
chart.y2Axis = y2Axis;
chart.interactiveLayer = interactiveLayer;
chart.tooltip = tooltip;
chart.dispatch = dispatch;
chart.options = nv.utils.optionsFunc.bind(chart);
chart._options = Object.create({}, {
// simple options, just get/set the necessary values
width: {get: function(){return width;}, set: function(_){width=_;}},
height: {get: function(){return height;}, set: function(_){height=_;}},
showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
focusEnable: {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}},
focusHeight: {get: function(){return height2;}, set: function(_){focusHeight=_;}},
focusShowAxisX: {get: function(){return focusShowAxisX;}, set: function(_){focusShowAxisX=_;}},
focusShowAxisY: {get: function(){return focusShowAxisY;}, set: function(_){focusShowAxisY=_;}},
brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}},
defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
noData: {get: function(){return noData;}, set: function(_){noData=_;}},
// options that require extra logic in the setter
margin: {get: function(){return margin;}, set: function(_){
margin.top = _.top !== undefined ? _.top : margin.top;
margin.right = _.right !== undefined ? _.right : margin.right;
margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
margin.left = _.left !== undefined ? _.left : margin.left;
}},
duration: {get: function(){return duration;}, set: function(_){
duration = _;
renderWatch.reset(duration);
lines.duration(duration);
xAxis.duration(duration);
x2Axis.duration(duration);
yAxis.duration(duration);
y2Axis.duration(duration);
}},
focusMargin: {get: function(){return margin2;}, set: function(_){
margin2.top = _.top !== undefined ? _.top : margin2.top;
margin2.right = _.right !== undefined ? _.right : margin2.right;
margin2.bottom = _.bottom !== undefined ? _.bottom : margin2.bottom;
margin2.left = _.left !== undefined ? _.left : margin2.left;
}},
color: {get: function(){return color;}, set: function(_){
color = nv.utils.getColor(_);
legend.color(color);
lines.color(color);
}},
interpolate: {get: function(){return lines.interpolate();}, set: function(_){
lines.interpolate(_);
lines2.interpolate(_);
}},
xTickFormat: {get: function(){return xAxis.tickFormat();}, set: function(_){
xAxis.tickFormat(_);
x2Axis.tickFormat(_);
}},
yTickFormat: {get: function(){return yAxis.tickFormat();}, set: function(_){
yAxis.tickFormat(_);
y2Axis.tickFormat(_);
}},
x: {get: function(){return lines.x();}, set: function(_){
lines.x(_);
lines2.x(_);
}},
y: {get: function(){return lines.y();}, set: function(_){
lines.y(_);
lines2.y(_);
}},
rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
rightAlignYAxis = _;
yAxis.orient( rightAlignYAxis ? 'right' : 'left');
}},
useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
useInteractiveGuideline = _;
if (useInteractiveGuideline) {
lines.interactive(false);
lines.useVoronoi(false);
}
}}
});
nv.utils.inheritOptions(chart, lines);
nv.utils.initOptions(chart);
return chart;
};
nv.models.lineWithFocusChart = function() {
return nv.models.lineChart()
.margin({ bottom: 30 })
.focusEnable( true );
};
Can some one explain me this function:
var transitions = function ()
{
return states.reduce(function (initial, state) {
return initial.concat(
state.transitions.map(function (transition) {
return {source: state, transition: transition};
})
);
}, []);
};
and this line: var gTransitions = svg.append('g').selectAll("path.transition"); - how path.transition is getting selected?
I am new d3 and javascript and I am really stuck at this point in my project.
The above snippet is taken out of below code. I have put comments saying "QUESTION1" and "QUESTION2" to find it.
window.onload = function ()
{
var radius = 40;
window.states = [
{x: 43, y: 67, label: "first", transitions: []},
{x: 340, y: 150, label: "second", transitions: []},
{x: 200, y: 250, label: "third", transitions: []}
];
window.svg = d3.select('body')
.append("svg")
.attr("width", "960px")
.attr("height", "500px");
// define arrow markers for graph links
svg.append('svg:defs').append('svg:marker')
.attr('id', 'end-arrow')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 4)
.attr('markerWidth', 8)
.attr('markerHeight', 8)
.attr('orient', 'auto')
.append('svg:path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('class', 'end-arrow')
;
// line displayed when dragging new nodes
var drag_line = svg.append('svg:path')
.attr({
'class': 'dragline hidden',
'd': 'M0,0L0,0'
})
;
//QUESTION1
var gTransitions = svg.append('g').selectAll("path.transition");
var gStates = svg.append("g").selectAll("g.state");
//QUESTION2
var transitions = function ()
{
return states.reduce(function (initial, state) {
return initial.concat(
state.transitions.map(function (transition) {
return {source: state, transition: transition};
})
);
}, []);
};
var transformTransitionEndpoints = function (d, i) {
var endPoints = d.endPoints();
var point = [
d.type == 'start' ? endPoints[0].x : endPoints[1].x,
d.type == 'start' ? endPoints[0].y : endPoints[1].y
];
return "translate(" + point + ")";
}
var transformTransitionPoints = function (d, i) {
return "translate(" + [d.x, d.y] + ")";
}
var computeTransitionPath = (function () {
var line = d3.svg.line()
.x(function (d, i) {
return d.x;
})
.y(function (d, i) {
return d.y;
})
.interpolate("cardinal");
return function (d) {
var source = d.source,
target = d.transition.points.length && d.transition.points[0] || d.transition.target,
deltaX = target.x - source.x,
deltaY = target.y - source.y,
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY),
normX = deltaX / dist,
normY = deltaY / dist,
sourcePadding = radius + 4, //d.left ? 17 : 12,
sourceX = source.x + (sourcePadding * normX),
sourceY = source.y + (sourcePadding * normY);
source = d.transition.points.length && d.transition.points[ d.transition.points.length - 1] || d.source;
target = d.transition.target;
deltaX = target.x - source.x;
deltaY = target.y - source.y;
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
normX = deltaX / dist;
normY = deltaY / dist;
targetPadding = radius + 8;//d.right ? 17 : 12,
targetX = target.x - (targetPadding * normX);
targetY = target.y - (targetPadding * normY);
var points =
[{x: sourceX, y: sourceY}].concat(
d.transition.points,
[{x: targetX, y: targetY}]
)
;
var l = line(points);
return l;
};
})();
var dragPoint = d3.behavior.drag()
.on("drag", function (d, i) {
console.log("transitionmidpoint drag");
var gTransitionPoint = d3.select(this);
gTransitionPoint.attr("transform", function (d, i) {
d.x += d3.event.dx;
d.y += d3.event.dy;
return "translate(" + [d.x, d.y] + ")"
});
// refresh transition path
gTransitions.selectAll("path").attr('d', computeTransitionPath);
// refresh transition endpoints
gTransitions.selectAll("circle.endpoint").attr({
transform: transformTransitionEndpoints
});
// refresh transition points
gTransitions.selectAll("circle.point").attr({
transform: transformTransitionPoints
});
d3.event.sourceEvent.stopPropagation();
});
var renderTransitionMidPoints = function (gTransition) {
gTransition.each(function (transition) {
var transitionPoints = d3.select(this).selectAll('circle.point').data(transition.transition.points, function (d) {
return transition.transition.points.indexOf(d);
});
transitionPoints.enter().append("circle")
.attr({
'class': 'point',
r: 4,
transform: transformTransitionPoints
})
.call(dragPoint);
transitionPoints.exit().remove();
});
};
var renderTransitionPoints = function (gTransition) {
gTransition.each(function (d) {
var endPoints = function () {
var source = d.source,
target = d.transition.points.length && d.transition.points[0] || d.transition.target,
deltaX = target.x - source.x,
deltaY = target.y - source.y,
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY),
normX = deltaX / dist,
normY = deltaY / dist,
sourceX = source.x + (radius * normX),
sourceY = source.y + (radius * normY);
source = d.transition.points.length && d.transition.points[ d.transition.points.length - 1] || d.source;
target = d.transition.target;
deltaX = target.x - source.x;
deltaY = target.y - source.y;
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
normX = deltaX / dist;
normY = deltaY / dist;
targetPadding = radius + 8;//d.right ? 17 : 12,
targetX = target.x - (radius * normX);
targetY = target.y - (radius * normY);
return [{x: sourceX, y: sourceY}, {x: targetX, y: targetY}];
};
var transitionEndpoints = d3.select(this).selectAll('circle.endpoint').data([
{endPoints: endPoints, type: 'start'},
{endPoints: endPoints, type: 'end'}
]);
transitionEndpoints.enter().append("circle")
.attr({
'class': function (d) {
return 'endpoint ' + d.type;
},
r: 4,
transform: transformTransitionEndpoints
})
;
transitionEndpoints.exit().remove();
});
};
var renderTransitions = function () {
gTransition = gTransitions.enter().append('g')
.attr({
'class': 'transition'
})
gTransition.append('path')
.attr({
d: computeTransitionPath,
class: 'background'
})
.on({
dblclick: function (d, i) {
gTransition = d3.select(d3.event.target.parentElement);
if (d3.event.ctrlKey) {
var p = d3.mouse(this);
gTransition.classed('selected', true);
d.transition.points.push({x: p[0], y: p[1]});
renderTransitionMidPoints(gTransition, d);
gTransition.selectAll('path').attr({
d: computeTransitionPath
});
} else {
var gTransition = d3.select(d3.event.target.parentElement),
transition = gTransition.datum(),
index = transition.source.transitions.indexOf(transition.transition);
transition.source.transitions.splice(index, 1)
gTransition.remove();
d3.event.stopPropagation();
}
}
});
gTransition.append('path')
.attr({
d: computeTransitionPath,
class: 'foreground'
});
renderTransitionPoints(gTransition);
renderTransitionMidPoints(gTransition);
gTransitions.exit().remove();
};
var renderStates = function () {
var gState = gStates.enter()
.append("g")
.attr({
"transform": function (d) {
return "translate(" + [d.x, d.y] + ")";
},
'class': 'state'
})
.call(drag);
gState.append("circle")
.attr({
r: radius + 4,
class: 'outer'
})
.on({
mousedown: function (d) {
console.log("state circle outer mousedown");
startState = d, endState = undefined;
// reposition drag line
drag_line
.style('marker-end', 'url(#end-arrow)')
.classed('hidden', false)
.attr('d', 'M' + d.x + ',' + d.y + 'L' + d.x + ',' + d.y);
// force element to be an top
this.parentNode.parentNode.appendChild(this.parentNode);
//d3.event.stopPropagation();
},
mouseover: function () {
svg.select("rect.selection").empty() && d3.select(this).classed("hover", true);
},
mouseout: function () {
svg.select("rect.selection").empty() && d3.select(this).classed("hover", false);
//$( this).popover( "hide");
}
});
gState.append("circle")
.attr({
r: radius,
class: 'inner'
})
.on({
mouseover: function () {
svg.select("rect.selection").empty() && d3.select(this).classed("hover", true);
},
mouseout: function () {
svg.select("rect.selection").empty() && d3.select(this).classed("hover", false);
},
});
};
var startState, endState;
var drag = d3.behavior.drag()
.on("drag", function (d, i) {
console.log("drag");
if (startState) {
return;
}
var selection = d3.selectAll('.selected');
// if dragged state is not in current selection
// mark it selected and deselect all others
if (selection[0].indexOf(this) == -1) {
selection.classed("selected", false);
selection = d3.select(this);
selection.classed("selected", true);
}
// move states
selection.attr("transform", function (d, i) {
d.x += d3.event.dx;
d.y += d3.event.dy;
return "translate(" + [d.x, d.y] + ")"
});
// move transistion points of each transition
// where transition target is also in selection
var selectedStates = d3.selectAll('g.state.selected').data();
var affectedTransitions = selectedStates.reduce(function (array, state) {
return array.concat(state.transitions);
}, [])
.filter(function (transition) {
return selectedStates.indexOf(transition.target) != -1;
});
affectedTransitions.forEach(function (transition) {
for (var i = transition.points.length - 1; i >= 0; i--) {
var point = transition.points[i];
point.x += d3.event.dx;
point.y += d3.event.dy;
}
});
// reappend dragged element as last
// so that its stays on top
selection.each(function () {
this.parentNode.appendChild(this);
});
// refresh transition path
gTransitions.selectAll("path").attr('d', computeTransitionPath);
// refresh transition endpoints
gTransitions.selectAll("circle.endpoint").attr({
transform: transformTransitionEndpoints
});
// refresh transition points
gTransitions.selectAll("circle.point").attr({
transform: transformTransitionPoints
});
d3.event.sourceEvent.stopPropagation();
})
.on("dragend", function (d) {
console.log("dragend");
// needed by FF
drag_line.classed('hidden', true)
.style('marker-end', '');
if (startState && endState) {
startState.transitions.push({label: "transition label 1", points: [], target: endState});
update();
}
startState = undefined;
d3.event.sourceEvent.stopPropagation();
});
svg.on({
mousedown: function () {
console.log("mousedown", d3.event.target);
if (d3.event.target.tagName == 'svg') {
if (!d3.event.ctrlKey) {
d3.selectAll('g.selected').classed("selected", false);
}
var p = d3.mouse(this);
}
},
mousemove: function () {
var p = d3.mouse(this);
// update drag line
drag_line.attr('d', 'M' + startState.x + ',' + startState.y + 'L' + p[0] + ',' + p[1]);
var state = d3.select('g.state .inner.hover');
endState = (!state.empty() && state.data()[0]) || undefined;
},
mouseup: function () {
console.log("mouseup");
// remove temporary selection marker class
d3.selectAll('g.state.selection').classed("selection", false);
},
mouseout: function ()
{
if (!d3.event.relatedTarget || d3.event.relatedTarget.tagName == 'HTML') {
// remove temporary selection marker class
d3.selectAll('g.state.selection').classed("selection", false);
}
}
});
update();
function update() {
gStates = gStates.data(states, function (d) {
return states.indexOf(d);
});
renderStates();
var _transitions = transitions();
gTransitions = gTransitions.data(_transitions, function (d) {
return _transitions.indexOf(d);
});
renderTransitions();
}
;
};
I assume this is from http://bl.ocks.org/lgersman/5370827.
Background
states (=window.states) is an array of state objects (3 in your case). Each state object has a property transitions (which represents possible changes to other states from this state), which is an array.
Question 1
This uses the reduce, concat and map method of the Array prototype to build a function that returns an array of objects of the form { source: state, transition: transition } using the transition arrays inside the state array.
The 1st layer is pretty simple - just a function definition. You call it eventually using var _transitions = transition();
var transitions = function () {
return ...
};
Note that each call returns the list based on the states / transitions that exist at the time the function is called.
The 2nd layer builds an array by concatenating array fragments from the 3rd layer. From the documentation (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce), reduce effectively gets a single value from an array.
In our case, that single value is a larger array built by concatenating array fragments. The 2nd parameter to the reduce function is the intial value (in this case an empty array)
return states.reduce(function (initial, state) {
return initial.concat(
...
);
}, []);
So we first pass in an empty array. The output of the 3rd layer (... in the section above) using the 1st element of states (i.e. states[0]) is concatenated to it to build a new array. This new array is then concatenated with the 2nd output of the 3rd layer (i.e. using states[1]) and so on
The 3rd layer is a simple map (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map). For each transition array entry in the state, it returns an object of the form { source: state, transition: transition }, using this to build an array (which is used by the 2nd layer as we saw above)
state.transitions.map(function (transition) {
return { source: state, transition: transition };
})
So, if we were to trace this for the "first" state and assuming you had 2 transition entries (your code has an empty array, but the original example inserts a couple of transitions), you'd get something like
[
{
source: <<"first" state object>>
transition: <<transition1a of "first" state - from it's transition array, 1st element>>
},
{
source: <<"first" state object>>
transition: <<transition1b of "first" state - from it's transition array, 2nd element>>
},
]
Carrying this up to the 2nd layer, you'd get something like this (assuming state "second" had 3 transitions emanating from it)
[
{
source: <<"first" state object>>
transition: <<transition1a of "first" state - from it's transition array, 1st element>>
},
{
source: <<"first" state object>>
transition: <<transition1b of "first" state - from it's transition array, 2nd element>>
},
{
source: <<"second" state object>>
transition: <<transition2a of "second" state - from it's transition array, 1st element>>
},
{
source: <<"second" state object>>
transition: <<transition2b of "second" state - from it's transition array, 2nd element>>
},
{
source: <<"second" state object>>
transition: <<transition2c of "second" state - from it's transition array, 3rd element>>
},
...
... and so on for all the states
]
And the 1st layer is effectively a function which does all the steps above when called.
Question 2
This effectively builds a d3 selection (see https://github.com/mbostock/d3/wiki/Selections) - the selection's d3 data comes from the output of the 1st question. The very end of your code has this link
gTransitions = gTransitions.data(_transitions, function (d) {
return _transitions.indexOf(d);
});
_transitions being set by a call to transitions(); in the line just above that.
This d3 selection is then used as d3 selections normally are (with an enter() / exit()), to update the svg element DOM. If you search for gTransitions.enter() and gTransitions.exit() you can find the related bits of code that keep your svg DOM updated. Note that the enter() involves a number of steps (append a g, set it's class, attach behaviour, append a path to the g...)
The first time, the update() function is called takes care of syncing the DOM to the initial data (in your case since your transition properties are empty arrays, nothing is created).
Subsequently, DOM event handlers update the respective states's transition arrays and the update() function is called at the end of the handler to reattach updated data (i.e. output of the transition() call) and hence drive the creation / removal of DOM elements for the transitions (via a call to renderTransitions()) - these are effectively the svg paths between (state) svg circles