如何将dc.js与d3 horizon chart集成进行数据渲染?
How to integrate dc.js with d3 horizon chart for data rendering?
这个dc.jssample shows a line chart (source). I wonder how one can replace its one focusChart with a set of horizon charts using d3-horizon-chart (example source)?
Here (JSFiddle code sample) 是我卡住的地方 - 它在 dc 图表上没有显示任何数据......请帮忙=(
class dcHorizonChart {
constructor(parent, groupByKeyName, valueKeyName, group ) {
this.data = null;
this.height = 30
this._groupByKeyIdx = groupByKeyName;
this._valueKeyName = valueKeyName;
this._root = d3.select(parent);
dc.registerChart(this, group);
}
groupAll(groupAll) {
console.log("gac")
if(!arguments.length)
return this._groupAll;
this._groupAll = groupAll;
return this;
}
setData(data) {
this.data = data
}
render() {
console.log("called once");
this.redraw();
}
redraw() {
// console.log(this.data.all())
//console.log(this.data.all())
var ndata = Enumerable.From(this.data.all()).Select("r=> { 'name': r.key["+this._groupByKeyIdx+"], 'val': r.key["+this._valueKeyName + "]}");
ndata = ndata.GroupBy("$.name", "$.val" ).Select("{'id': $.Key(), 'values':$.ToArray()}")
.ToArray();
console.log(ndata)
this._root.html(null)
this._root.selectAll('.horizon')
.data(ndata)
.enter()
.append('div')
.attr('class', 'horizon')
.each(function(d) {
d3.horizonChart()
.title(d.id)
.call(this, d.values);
});
console.log("called");
/*
d3.select('body').selectAll('.horizon')
.data(stocks)
.enter()
.append('div')
.attr('class', 'horizon')
.each(function(d) {
d3.horizonChart()
.title(d.stock)
.call(this, d.values);
});
this._rect.transition()
.duration(this._duration)
.attr('fill', this._colors(this._groupAll.value()));
*/
}
}
function loadStockData(stock, callback) {
d3.csv('https://bost.ocks.org/mike/cubism/intro/stocks/' + stock + '.csv').then(function(rows) {
rows = rows.map(function(d) {
return [d3.timeParse(d.Date), +d.Open];
}).filter(function(d) {
return d[1];
}).reverse();
var date = rows[0][0],
compare = rows[400][1],
value = rows[0][1],
values = [],
indices = [];
rows.forEach(function(d, i) {
values.push(value = (d[1] - compare) / compare);
indices.push(i);
});
callback({
'stock': stock,
'values': values,
'indices': indices
});
});
}
var promises = [];
['AAPL', 'GOOG', 'MSFT'].forEach(function(stock) {
promises.push(new Promise(function(resolve, reject) {
var r = loadStockData(stock, resolve);
}));
});
Promise.all(promises).then(function(stocks) {
console.log(stocks);
var data = [];
data = Enumerable.From(stocks)
.SelectMany( "val, index=>" +
"Enumerable.From(val.values)" +
".Select(\"v,i => {'value': v, 'idx':i, 'name':'\" + val.stock + \"' } \")")
.ToArray();
/*
for(var i = 0; i < stocks.length; i++) {
for(var j= 0; j < stocks[i].indices.length; j++) {
data.push({ 'idx':stocks[i].indices[j], 'name': stocks[i].stock, 'value': stocks[i].values[j] })
}
}
*/
console.log(data);
var ndx, runDimension, runGroup, overviewRunDimension, overviewRunGroup;
ndx = crossfilter(data);
var allDim = ndx.dimension(function(d){ return [d.idx, d.name, d.value] ;});
runDimension = ndx.dimension(function(d) {return [d.name, d.idx]; });
overviewRunDimension = ndx.dimension(function(d) {return [d.name, d.idx]; });
runGroup = runDimension.group().reduceSum(function(d) { return d.value; });
overviewRunGroup = overviewRunDimension.group().reduceSum(function(d) { return d.value; });
var horizonChart = new dcHorizonChart("#test-hc", 1,2);
horizonChart.setData(allDim.group());
var overviewChart = dc.seriesChart("#test-overview");
overviewChart
.width(768)
.height(100)
.chart(function(c) { return dc.lineChart(c).curve(d3.curveCardinal); })
.x(d3.scaleLinear().domain([0,20]))
.brushOn(true)
.xAxisLabel("Run")
.clipPadding(10)
.dimension(runDimension)
.group(runGroup)
.seriesAccessor(function(d) {return "Expt: " + d.key[0];})
.keyAccessor(function(d) {return +d.key[1];})
.valueAccessor(function(d) {return +d.value;});
dc.renderAll();
});
body {
margin: 0;
padding: 0;
}
.horizon {
border-top: solid 1px #000;
border-bottom: solid 1px #000;
overflow: hidden;
position: relative;
}
.horizon + .horizon {
border-top: none;
}
.horizon canvas {
display: block;
image-rendering: pixelated;
}
.horizon .title,
.horizon .value {
bottom: 0;
line-height: 30px;
margin: 0 6px;
position: absolute;
font-family: sans-serif;
text-shadow: 0 1px 0 rgba(255,255,255,.5);
white-space: nowrap;
}
.horizon .title {
left: 0;
}
.horizon .value {
right: 0;
}
<!DOCTYPE html>
<html lang="en">
<head>
<title>dc.js - Custom Chart Example</title>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="//dc-js.github.io/dc.js/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="//unpkg.com/dc@4/dist/style/dc.css" />
<script src="//d3js.org/d3.v5.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/crossfilter2/1.4.4/crossfilter.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/dc/3.2.1/dc.min.js"></script>
<script src="//npmcdn.com/d3-horizon-chart/build/d3-horizon-chart.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/linq.js/2.2.0.2/linq.js"></script>
</head>
<body>
<div class="container">
<div id="test-overview"></div>
<div id="bar"></div>
<br/>
AAA
<br>
<div id="test-hc"></div>
</div>
</body>
</html>
我为 dc.js 创建了一个 horizon chart example。
将 d3-horizon-chart 与 dc.js 集成的主要问题是它没有 X 刻度 - 它只是按数组顺序将每个值显示为 1 像素的颜色列.
这意味着它不使用组键,在绘制水平图之前必须按正确的顺序对数据进行排序。
图表定义如下:
class HorizonChart {
constructor(parent, group) {
this._group = null;
this._colors = null;
this._seriesAccessor = null;
this._root = d3.select(parent);
dc.registerChart(this, group);
}
// initialization functions for user
group(group) {
if(!arguments.length)
return this._group;
this._group = group;
return this;
}
// takes array of colors (not scale)
colors(colors) {
if(!arguments.length)
return this._colors;
this._colors = colors;
return this;
}
seriesAccessor(seriesAccessor) {
if(!arguments.length)
return this._seriesAccessor;
this._seriesAccessor = seriesAccessor;
return this;
}
valueAccessor(valueAccessor) {
if(!arguments.length)
return this._valueAccessor;
this._valueAccessor = valueAccessor;
return this;
}
// interface for dc.js chart registry
render() {
this.redraw();
}
redraw() {
const nester = d3.nest().key(this._seriesAccessor),
nesting = nester.entries(this._group.all());
let horizon = this._root.selectAll('.horizon')
.data(nesting);
horizon = horizon.enter()
.append('div')
.attr('class', 'horizon')
.merge(horizon);
const colors = this._colors,
valueAccessor = this._valueAccessor;
horizon
.each(function(series) {
d3.select(this).selectAll('*').remove();
d3.horizonChart()
.colors(typeof colors === 'function' ? colors(series.key) : colors)
.title(series.key)
.call(this, series.values.map(valueAccessor));
});
}
它实现了dc.jsseries chart的一小部分接口。一个区别是 d3-horizon-chart 采用一组颜色,并根据该数组的长度决定绘制多少正负波段。所以 HorizonChart.colors()
接受一个颜色数组,或者一个接受系列键和 returns 颜色数组的函数。
它使用d3.nest()按系列访问器拆分数据。然后它添加或删除水平图表的 divs,删除每个 div 内的任何内容并在那里绘制水平图表。
排序数据
由于数据需要排序,而我们从group.all()
得到的multikey会排序错误,我们可以使用fake group按key的两个元素排序:
function sort_multikey_group(group) {
return {
all: () => {
return group.all().slice().sort(
({key: keyA}, {key: keyB}) => d3.ascending(keyA[0],keyB[0]) || d3.ascending(keyA[1],keyB[1]));
}
};
}
我们在初始化水平图表时应用假组:
var horizonChart = new HorizonChart("#horizon"),
horizonChart
.group(sort_multikey_group(exptRunGroup))
.colors(n => [d3.schemeBlues, d3.schemeOranges, d3.schemeGreens, d3.schemeReds, d3.schemePurples][n-1][6]) // levels * 2
.seriesAccessor(d => d.key[0])
.valueAccessor(d => d.value - 500)
.colors()
的函数参数为每个水平图表选择 D3 Color Schemes 之一。它选择了6种颜色的方案来显示3条正条带和3条负条带。
它根据键的第一个元素将数据放入每个系列图表中。在此示例中,数据向下移动以使用负色带。
示例数据
例子中的数据是dc.js Series Chart Example中使用的迈克尔逊-莫雷实验数据,每一个插值20个点:
const experiments2 = d3.range(experiments.length-1).flatMap(i => {
if(experiments[i].Expt !== experiments[i+1].Expt)
return [];
let {Expt, Run, Speed: Speed0} = experiments[i],
{Speed: Speed1} = experiments[i+1];
Expt = +Expt; Run = +Run; Speed0 = +Speed0; Speed1 = +Speed1;
const terp = d3.scaleLinear().range([Speed0, Speed1]);
return d3.range(mult).map(j => ({Expt, Run: Run + j/mult, Speed: terp(j/mult)}));
});
这是 d3-horizon-chart 和内置 dc.js 图表之间的另一个区别:它需要每个像素的数据。
这个dc.jssample shows a line chart (source). I wonder how one can replace its one focusChart with a set of horizon charts using d3-horizon-chart (example source)?
Here (JSFiddle code sample) 是我卡住的地方 - 它在 dc 图表上没有显示任何数据......请帮忙=(
class dcHorizonChart {
constructor(parent, groupByKeyName, valueKeyName, group ) {
this.data = null;
this.height = 30
this._groupByKeyIdx = groupByKeyName;
this._valueKeyName = valueKeyName;
this._root = d3.select(parent);
dc.registerChart(this, group);
}
groupAll(groupAll) {
console.log("gac")
if(!arguments.length)
return this._groupAll;
this._groupAll = groupAll;
return this;
}
setData(data) {
this.data = data
}
render() {
console.log("called once");
this.redraw();
}
redraw() {
// console.log(this.data.all())
//console.log(this.data.all())
var ndata = Enumerable.From(this.data.all()).Select("r=> { 'name': r.key["+this._groupByKeyIdx+"], 'val': r.key["+this._valueKeyName + "]}");
ndata = ndata.GroupBy("$.name", "$.val" ).Select("{'id': $.Key(), 'values':$.ToArray()}")
.ToArray();
console.log(ndata)
this._root.html(null)
this._root.selectAll('.horizon')
.data(ndata)
.enter()
.append('div')
.attr('class', 'horizon')
.each(function(d) {
d3.horizonChart()
.title(d.id)
.call(this, d.values);
});
console.log("called");
/*
d3.select('body').selectAll('.horizon')
.data(stocks)
.enter()
.append('div')
.attr('class', 'horizon')
.each(function(d) {
d3.horizonChart()
.title(d.stock)
.call(this, d.values);
});
this._rect.transition()
.duration(this._duration)
.attr('fill', this._colors(this._groupAll.value()));
*/
}
}
function loadStockData(stock, callback) {
d3.csv('https://bost.ocks.org/mike/cubism/intro/stocks/' + stock + '.csv').then(function(rows) {
rows = rows.map(function(d) {
return [d3.timeParse(d.Date), +d.Open];
}).filter(function(d) {
return d[1];
}).reverse();
var date = rows[0][0],
compare = rows[400][1],
value = rows[0][1],
values = [],
indices = [];
rows.forEach(function(d, i) {
values.push(value = (d[1] - compare) / compare);
indices.push(i);
});
callback({
'stock': stock,
'values': values,
'indices': indices
});
});
}
var promises = [];
['AAPL', 'GOOG', 'MSFT'].forEach(function(stock) {
promises.push(new Promise(function(resolve, reject) {
var r = loadStockData(stock, resolve);
}));
});
Promise.all(promises).then(function(stocks) {
console.log(stocks);
var data = [];
data = Enumerable.From(stocks)
.SelectMany( "val, index=>" +
"Enumerable.From(val.values)" +
".Select(\"v,i => {'value': v, 'idx':i, 'name':'\" + val.stock + \"' } \")")
.ToArray();
/*
for(var i = 0; i < stocks.length; i++) {
for(var j= 0; j < stocks[i].indices.length; j++) {
data.push({ 'idx':stocks[i].indices[j], 'name': stocks[i].stock, 'value': stocks[i].values[j] })
}
}
*/
console.log(data);
var ndx, runDimension, runGroup, overviewRunDimension, overviewRunGroup;
ndx = crossfilter(data);
var allDim = ndx.dimension(function(d){ return [d.idx, d.name, d.value] ;});
runDimension = ndx.dimension(function(d) {return [d.name, d.idx]; });
overviewRunDimension = ndx.dimension(function(d) {return [d.name, d.idx]; });
runGroup = runDimension.group().reduceSum(function(d) { return d.value; });
overviewRunGroup = overviewRunDimension.group().reduceSum(function(d) { return d.value; });
var horizonChart = new dcHorizonChart("#test-hc", 1,2);
horizonChart.setData(allDim.group());
var overviewChart = dc.seriesChart("#test-overview");
overviewChart
.width(768)
.height(100)
.chart(function(c) { return dc.lineChart(c).curve(d3.curveCardinal); })
.x(d3.scaleLinear().domain([0,20]))
.brushOn(true)
.xAxisLabel("Run")
.clipPadding(10)
.dimension(runDimension)
.group(runGroup)
.seriesAccessor(function(d) {return "Expt: " + d.key[0];})
.keyAccessor(function(d) {return +d.key[1];})
.valueAccessor(function(d) {return +d.value;});
dc.renderAll();
});
body {
margin: 0;
padding: 0;
}
.horizon {
border-top: solid 1px #000;
border-bottom: solid 1px #000;
overflow: hidden;
position: relative;
}
.horizon + .horizon {
border-top: none;
}
.horizon canvas {
display: block;
image-rendering: pixelated;
}
.horizon .title,
.horizon .value {
bottom: 0;
line-height: 30px;
margin: 0 6px;
position: absolute;
font-family: sans-serif;
text-shadow: 0 1px 0 rgba(255,255,255,.5);
white-space: nowrap;
}
.horizon .title {
left: 0;
}
.horizon .value {
right: 0;
}
<!DOCTYPE html>
<html lang="en">
<head>
<title>dc.js - Custom Chart Example</title>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="//dc-js.github.io/dc.js/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="//unpkg.com/dc@4/dist/style/dc.css" />
<script src="//d3js.org/d3.v5.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/crossfilter2/1.4.4/crossfilter.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/dc/3.2.1/dc.min.js"></script>
<script src="//npmcdn.com/d3-horizon-chart/build/d3-horizon-chart.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/linq.js/2.2.0.2/linq.js"></script>
</head>
<body>
<div class="container">
<div id="test-overview"></div>
<div id="bar"></div>
<br/>
AAA
<br>
<div id="test-hc"></div>
</div>
</body>
</html>
我为 dc.js 创建了一个 horizon chart example。
将 d3-horizon-chart 与 dc.js 集成的主要问题是它没有 X 刻度 - 它只是按数组顺序将每个值显示为 1 像素的颜色列.
这意味着它不使用组键,在绘制水平图之前必须按正确的顺序对数据进行排序。
图表定义如下:
class HorizonChart {
constructor(parent, group) {
this._group = null;
this._colors = null;
this._seriesAccessor = null;
this._root = d3.select(parent);
dc.registerChart(this, group);
}
// initialization functions for user
group(group) {
if(!arguments.length)
return this._group;
this._group = group;
return this;
}
// takes array of colors (not scale)
colors(colors) {
if(!arguments.length)
return this._colors;
this._colors = colors;
return this;
}
seriesAccessor(seriesAccessor) {
if(!arguments.length)
return this._seriesAccessor;
this._seriesAccessor = seriesAccessor;
return this;
}
valueAccessor(valueAccessor) {
if(!arguments.length)
return this._valueAccessor;
this._valueAccessor = valueAccessor;
return this;
}
// interface for dc.js chart registry
render() {
this.redraw();
}
redraw() {
const nester = d3.nest().key(this._seriesAccessor),
nesting = nester.entries(this._group.all());
let horizon = this._root.selectAll('.horizon')
.data(nesting);
horizon = horizon.enter()
.append('div')
.attr('class', 'horizon')
.merge(horizon);
const colors = this._colors,
valueAccessor = this._valueAccessor;
horizon
.each(function(series) {
d3.select(this).selectAll('*').remove();
d3.horizonChart()
.colors(typeof colors === 'function' ? colors(series.key) : colors)
.title(series.key)
.call(this, series.values.map(valueAccessor));
});
}
它实现了dc.jsseries chart的一小部分接口。一个区别是 d3-horizon-chart 采用一组颜色,并根据该数组的长度决定绘制多少正负波段。所以 HorizonChart.colors()
接受一个颜色数组,或者一个接受系列键和 returns 颜色数组的函数。
它使用d3.nest()按系列访问器拆分数据。然后它添加或删除水平图表的 divs,删除每个 div 内的任何内容并在那里绘制水平图表。
排序数据
由于数据需要排序,而我们从group.all()
得到的multikey会排序错误,我们可以使用fake group按key的两个元素排序:
function sort_multikey_group(group) {
return {
all: () => {
return group.all().slice().sort(
({key: keyA}, {key: keyB}) => d3.ascending(keyA[0],keyB[0]) || d3.ascending(keyA[1],keyB[1]));
}
};
}
我们在初始化水平图表时应用假组:
var horizonChart = new HorizonChart("#horizon"),
horizonChart
.group(sort_multikey_group(exptRunGroup))
.colors(n => [d3.schemeBlues, d3.schemeOranges, d3.schemeGreens, d3.schemeReds, d3.schemePurples][n-1][6]) // levels * 2
.seriesAccessor(d => d.key[0])
.valueAccessor(d => d.value - 500)
.colors()
的函数参数为每个水平图表选择 D3 Color Schemes 之一。它选择了6种颜色的方案来显示3条正条带和3条负条带。
它根据键的第一个元素将数据放入每个系列图表中。在此示例中,数据向下移动以使用负色带。
示例数据
例子中的数据是dc.js Series Chart Example中使用的迈克尔逊-莫雷实验数据,每一个插值20个点:
const experiments2 = d3.range(experiments.length-1).flatMap(i => {
if(experiments[i].Expt !== experiments[i+1].Expt)
return [];
let {Expt, Run, Speed: Speed0} = experiments[i],
{Speed: Speed1} = experiments[i+1];
Expt = +Expt; Run = +Run; Speed0 = +Speed0; Speed1 = +Speed1;
const terp = d3.scaleLinear().range([Speed0, Speed1]);
return d3.range(mult).map(j => ({Expt, Run: Run + j/mult, Speed: terp(j/mult)}));
});
这是 d3-horizon-chart 和内置 dc.js 图表之间的另一个区别:它需要每个像素的数据。