d2b
Version:
278 lines (245 loc) • 9.85 kB
JavaScript
import { axisBottom, axisLeft, axisTop, axisRight } from 'd3-axis';
import { ascending, extent } from 'd3-array';
import * as d3Scales from 'd3-scale';
import functor from '../util/functor';
import oreq from '../util/oreq';
import oreqUndefined from '../util/oreqUndefined';
import { svgBar, svgLine, svgBubblePack, svgBoxPlot, svgArea, svgScatter } from '../svg/svg';
/**
* d2b.chartPieAdvanced(chart, datum) configures the input chart and formats a returned datum set
*/
export default function (chart, datum) {
const legendConfig = datum.legend || {};
const tooltipConfig = datum.tooltip || {};
// Chart Config
chart
.duration.conditionally(datum.duration)
.graphColor.proxy(graph => graph.color || functor(datum.graphColor)(graph))
.groupColor.proxy(group => group.color || functor(datum.groupColor)(group));
// Chart Frame Config
chart.chartFrame()
.size.conditionally(datum.size)
.chartPadding.conditionally(datum.chartPadding)
.padding.conditionally(datum.padding)
.legendEnabled.conditionally(legendConfig.enabled)
.legendOrient.conditionally(legendConfig.orient);
// Plane Config
chart.plane()
.margin.conditionally(datum.planeMargin)
.padding.conditionally(datum.planePadding);
// Legend Config
chart.legend()
.vertical.conditionally(legendConfig.orient, ['right', 'left'].includes(legendConfig.orient))
.clickable.conditionally(legendConfig.clickable)
.dblclickable.conditionally(legendConfig.dblclickable)
.allowEmptied.conditionally(legendConfig.allowEmptied)
.icon.proxy(d => d.data.legendIcon || functor(legendConfig.icon)(d.data));
// Tooltip Config
chart.tooltipConfig.proxy(tooltipAxis => {
if (!datum.tooltip) return undefined;
tooltipAxis
.trackX.conditionally(tooltipConfig.trackX)
.trackY.conditionally(tooltipConfig.trackY)
.threshold.conditionally(tooltipConfig.threshold)
.title.conditionally(tooltipConfig.title, rows => {
return tooltipConfig.title(rows.map(row => {
return { value: row.data, graph: row.graph.data };
}));
});
});
// Tooltip Row Config
chart.graphTooltipConfig.proxy(graph => {
if (!graph.tooltip) return undefined;
return tooltipGraph => {
tooltipGraph
.row.proxy(row => {
const value = row.data;
const tooltip = oreqUndefined(value.tooltip, graph.tooltip, tooltipConfig.row);
return functor(tooltip)(value, graph);
});
};
});
// Generators Config
chart.setGenerators(set => {
const generators = [];
set.generators.forEach(gen => {
// If generator is a function it is probably a d2b generator and is ready to go
if (typeof gen === 'function') {
generators.push(gen);
// Otherwise if multiple types are provided, setup the described generators
} else if (gen.types) {
gen.types.forEach(type => {
generators.push(buildGenerator(type, gen));
});
// Otherwise setup the described type generator
} else {
generators.push(buildGenerator(gen.type, gen));
}
generators.forEach(newGen => {
if (newGen.pcolor) newGen.pcolor.proxy(point => point.color);
if (newGen.pcentered) newGen.pcentered.proxy(point => oreqUndefined(point.centered, gen.centered));
if (newGen.box) newGen.box().width.proxy(point => oreq(point.width, gen.width));
if (newGen.symbol) newGen.symbol.proxy(graph => oreq(graph.symbol, typeof gen === 'function' ? undefined : gen.symbol));
if (newGen.psymbol) newGen.psymbol.proxy(point => point.symbol);
if (newGen.psize) newGen.psize.proxy(point => point.size !== undefined || gen.size !== undefined ? (oreq(point.size, 1)) * oreq(gen.size, 25) : undefined);
if (newGen.stackBy) newGen.stackBy.proxy(graph => oreqUndefined(graph.stack, typeof gen === 'function' ? undefined : gen.stack));
});
});
return generators;
});
// Axis Config
['x', 'x2', 'y', 'y2'].forEach(axis => {
const axisConfig = datum[axis] || {};
chart[axis].proxy((d, points) => {
if (!datum[axis]) return undefined;
if (!points.length) return {};
const scaleConfig = axisConfig.scale || {};
const config = {};
// Unique set of values.
const values = points.filter((value, index, self) => self.indexOf(value) === index).sort(ascending);
config.orient = axisConfig.orient || 'outer';
if (axisConfig.wrapLength !== undefined) config.wrapLength = axisConfig.wrapLength;
if (axisConfig.tickSize !== undefined) config.tickSize = axisConfig.tickSize;
if (axisConfig.showGrid !== undefined) config.showGrid = axisConfig.showGrid;
if (axisConfig.label !== undefined) config.label = axisConfig.label;
if (axisConfig.labelOrient !== undefined) config.labelOrient = axisConfig.labelOrient;
if (axisConfig.linearPadding !== undefined) config.linearPadding = axisConfig.linearPadding;
// D3 Axis Config:
// If axis is given as a function it is probably a d3.axis and is ready to go
if (typeof axisConfig.axis === 'function') {
config.axis = axisConfig.axis;
// Otherwise we can dynamically setup the axis
} else {
switch (`${axis}-${config.orient}`) {
case 'x-outer':
config.axis = axisBottom();
break;
case 'y-outer':
config.axis = axisLeft();
break;
case 'x2-outer':
config.axis = axisTop();
break;
case 'y2-outer':
config.axis = axisRight();
break;
case 'x-inner':
config.axis = axisTop();
break;
case 'y-inner':
config.axis = axisRight();
break;
case 'x2-inner':
config.axis = axisBottom();
break;
case 'y2-inner':
config.axis = axisLeft();
break;
}
}
if (axisConfig.tickPadding !== undefined) config.axis.tickPadding(axisConfig.tickPadding);
if (axisConfig.ticks !== undefined) config.axis.ticks(axisConfig.ticks);
if (axisConfig.tickFormat !== undefined) config.axis.tickFormat(axisConfig.tickFormat);
if (axisConfig.tickValues !== undefined) config.axis.tickValues(axisConfig.tickValues);
// D3 Scale Config
// If scale is given as a function it is probably a d3.scale and is ready to go
let domain = [0, 1];
if (typeof scaleConfig === 'function') {
config.scale = scaleConfig;
// Otherwise we can dynamically setup the scale
} else {
let type = scaleConfig.type;
if (!type) {
const firstValue = values[0];
if (firstValue.constructor.name === 'String') type = 'band';
else if (firstValue.constructor.name === 'Date') type = 'time';
else type = 'linear';
}
type = type.toLowerCase();
const scaleGenerator = d3Scales[`scale${type.charAt(0).toUpperCase() + type.slice(1)}`];
config.scale = scaleGenerator();
switch (type) {
case 'band':
case 'point':
domain = values;
break;
default:
domain = extent(values.map(Number));
}
}
if (scaleConfig.domain) domain = functor(scaleConfig.domain)(values);
if (scaleConfig.clamp !== undefined) config.scale.clamp(scaleConfig.clamp);
if (scaleConfig.nice !== undefined) config.scale.nice(scaleConfig.nice);
if (scaleConfig.exponent !== undefined) config.scale.exponent(scaleConfig.exponent);
if (scaleConfig.base !== undefined) config.scale.base(scaleConfig.base);
if (scaleConfig.constant !== undefined) config.scale.constant(scaleConfig.constant);
if (scaleConfig.forceBounds) {
if (scaleConfig.forceBounds.min !== undefined) domain[0] = scaleConfig.forceBounds.min;
if (scaleConfig.forceBounds.max !== undefined) domain[1] = scaleConfig.forceBounds.max;
}
config.scale.domain(domain);
return config;
});
});
return datum;
}
function buildGenerator (type, gen) {
let newGen = null;
const stackOffset = gen.stackOffset;
const stackOrder = gen.stackOrder;
const curve = gen.curve;
const orient = gen.orient;
const align = gen.align;
const configureStack = svgGen => {
const stack = svgGen.stack();
if (stackOffset !== undefined) stack.offset(stackOffset);
if (stackOrder !== undefined) stack.order(stackOrder);
};
const configureCurve = shape => {
if (curve !== undefined) shape.curve(curve);
};
const configureOrient = svgGen => {
svgGen.orient.conditionally(orient);
};
const configureAlign = svgGen => {
svgGen.align.conditionally(align);
};
switch (type) {
case 'bar':
newGen = svgBar()
.padding.conditionally(gen.padding)
.groupPadding.conditionally(gen.groupPadding);
configureOrient(newGen);
break;
case 'line':
newGen = svgLine();
configureStack(newGen);
configureAlign(newGen);
configureCurve(newGen.line());
break;
case 'area':
newGen = svgArea();
configureStack(newGen);
configureCurve(newGen.area());
break;
case 'scatter':
newGen = svgScatter();
// .psize.conditionally(gen.size);
configureStack(newGen);
configureAlign(newGen);
break;
case 'bubblePack':
newGen = svgBubblePack()
.tendancy.conditionally(gen.tendancy);
newGen.point()
.size.conditionally(gen.size, d => gen.size * d.size);
break;
case 'boxPlot':
newGen = svgBoxPlot();
newGen.box()
.valueFormat.conditionally(gen.valueFormat);
configureOrient(newGen);
break;
}
return newGen;
}