const createMarketProfile = (data, priceBuckets) => { // find the price bucket size const priceStep = priceBuckets[1] - priceBuckets[0]; // determine whether a datapoint is within a bucket const inBucket = (datum, priceBucket) => datum.low < priceBucket && datum.high > (priceBucket - priceStep); // the volume contribution for this range const volumeInBucket = (datum, priceBucket) => // inBucket(datum, priceBucket) ? 1 : 0; inBucket(datum, priceBucket) ? datum.volume / Math.ceil((datum.high - datum.low) / priceStep) : 0; // map each point in our time series, to construct the market profile const marketProfile = data.map( (datum, index) => priceBuckets.map(priceBucket => { // determine how many points to the left are also within this time bucket const base = d3.sum(data.slice(0, index) .map(d => volumeInBucket(d, priceBucket))); return { base, value: base + volumeInBucket(datum, priceBucket), price: priceBucket }; }) ); // similar to d3-stack - cache the underlying data marketProfile.data = data; return marketProfile; }; const timePeriods = 40; // create some random financial data const generator = fc.randomFinancial() .interval(d3.timeMinute) const timeSeries = generator(timePeriods); // determine the price range const extent = fc.extentLinear() .accessors([d => d.high, d => d.low]); const priceRange = extent(timeSeries); // use a d3 scale to create a set of price buckets const priceScale = d3.scaleLinear() .domain(priceRange); const priceBuckets = priceScale.ticks(20); const marketProfile = createMarketProfile(timeSeries, priceBuckets); const colorScale = d3.scaleSequential(d3.interpolateSpectral) .domain([0, timePeriods]); const barSeries = fc.autoBandwidth(fc.seriesSvgBar()) .orient('horizontal') .align('left') .crossValue(d => d.price) .mainValue(d => d.value) .baseValue(d => d.base); const repeat = fc.seriesSvgRepeat() .series(barSeries) .orient('horizontal') .decorate((selection) => { selection.enter() .each((data, index, group) => d3.select(group[index]) .selectAll('g.bar') .attr('fill', () => colorScale(index)) ); }); const xExtent = fc.extentLinear() .accessors([d => d.value]) .include([0]) const profileChart = fc.chartCartesian( d3.scaleLinear(), d3.scaleBand() ) .xDomain(xExtent(_.flattenDeep(marketProfile))) .yDomain(priceBuckets) .yTickValues(priceBuckets.filter((d, i) => i % 4 == 0)) .svgPlotArea(repeat); d3.select('#chart') .datum(marketProfile) .call(profileChart);