D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
k-izzo
Full window
Github gist
candlesticks
<!DOCTYPE html> <meta charset="utf-8"> <head><link rel='stylesheet' type='text/css' href='style.css'></head> <body style='background-color:lightgray'> <div id='container'></div> <script src='https://d3js.org/d3.v3.min.js'></script> <script> var format_date = function (raw) { var formatted = raw; if (raw.length !== 12) { formatted = formatted.slice(0, 4) + '0' + formatted.slice(4); } return formatted; }; var parse_date = d3.time.format("%b %d, %Y"); var scale = 0.85, chart_w = 950 * scale, chart_h = 600 * scale, top_h = chart_h * 0.65, margin = {left: 140 * scale, top: 140 * scale, right: 50 * scale, bottom: 130 * scale, mid: 80 * scale}; d3.select('#container') .style({ 'margin-left': -1 * (chart_w + margin.left + margin.right)/2 + 'px', 'margin-top': -1 * (chart_h + margin.top + margin.bottom)/2 + 'px', width: (chart_w + margin.left + margin.right) + 'px', height: (chart_h + margin.top + margin.bottom) + 'px' }); var get_global_range = function (objects) { var global_min = objects[0].open, global_max = objects[0].open, local_min, local_max; var get_local_range = function (object) { var min, max, values = []; Object.keys(object).forEach(function (prop) { if (prop !== 'date' && prop !== 'vol') { values.push(object[prop]); } }); values.sort(function (a, b) { return a - b; }); min = values[0]; max = values[values.length - 1]; return [min, max]; }; objects.slice(1).forEach(function (object) { local_min = get_local_range(object)[0]; if (local_min < global_min) { global_min = local_min; } local_max = get_local_range(object)[1]; if (local_max > global_max) { global_max = local_max; } }); return [global_min, global_max]; }; d3.csv('data.csv', function (data) { data.forEach(function (d) { d.date = parse_date.parse(format_date(d.date)); d.open = +d.open; d.close = +d.close; d.low = +d.low; d.high = +d.high; d.vol = +(d.vol.replace(/,/g,'')) / 1000000; }); data.reverse() var xs = d3.scale.ordinal() .domain(d3.range(data.length)) .rangePoints([0, chart_w], 0.05); var price_range = get_global_range(data), price_min = price_range[0], price_max = price_range[1]; var ys = d3.scale.linear() .domain([price_min, price_max]) .range([top_h, 0]); var vol_max = d3.max(data, function (d) { return d.vol; }); var vol_ys = d3.scale.linear() .domain([0, vol_max]) .range([chart_h, top_h + margin.mid]); var area = d3.svg.area() .x(function (d, i) { return xs(i); }) .y0(chart_h) .y1(function (d) { return vol_ys(d.vol); }) .interpolate('cardinal') .tension(0.85); var line = d3.svg.line() .x(function (d, i) { return xs(i); }) .y(function (d) { return vol_ys(d.vol); }) .interpolate('cardinal') .tension(0.85); var yaxis = d3.svg.axis() .scale(ys) .orient('left') .ticks(5); var vol_yaxis = d3.svg.axis() .scale(vol_ys) .orient('left') .ticks(4); var tvals = []; data.forEach(function (d) { var insert = parse_date(d.date).slice(0, 6); if (insert[0] !== 'x') { tvals.push(insert); } else { tvals.push(insert.slice(1)); } }); var xaxis = d3.svg.axis() .scale(xs) .orient('bottom') .tickFormat(''); var vis = d3.select('#container') .append('svg') .attr({ width: chart_w + margin.left + margin.right, height: chart_h + margin.top + margin.bottom }) .append('g') .attr('transform', 'translate(' + [margin.left, margin.top] + ')'); vis.append('path') .classed('area_fill', true) .attr('d', area(data, function (d) { return d.vol; })); vis.append('path') .classed('area_line', true) .attr('d', line(data, function (d) { return d.vol; })); var sticks = vis.selectAll('.stick') .data(data) .enter() .append('g') .classed('stick', true) .attr('transform', function (d, i) { return 'translate(' + [xs(i), 0] + ')'; }); var tops = sticks.append('line') .classed('wick', true) .attr({ x1: 0, y1: function (d) { return ys(d.high) }, x2: 0, y2: function (d) { return ys(Math.max(d.open, d.close)); } }); var bottoms = sticks.append('line') .classed('wick', true) .attr({ x1: 0, y1: function (d) { return ys(Math.min(d.open, d.close)); }, x2: 0, y2: function (d) { return ys(d.low); } }); var mids = sticks.append('rect') .attr({ class: function (d) { return d.close > d.open ? 'pos' : 'neg'; }, x: -7, y: function (d) { return ys(Math.max(d.open, d.close)); }, width: 14, height: function (d) { return ys(Math.min(d.open, d.close)) - ys(Math.max(d.open, d.close)); } }); var verts = sticks.append('rect') .classed('vert', true) .attr({ x: -7, y: 0, width: 14, height: chart_h }) .style('opacity', 0); sticks.on('mouseenter', function () { d3.select(this).select('rect').style('fill-opacity', 1); var tip_group = d3.select(this) .append('g') .classed('tip_group', true) .attr('transform', function (d) { return 'translate(' + [0, ys(d.high) - 71] + ')'; }) .style('opacity', 0); tip_group.append('text') .text(function (d) { return d.date.toString().slice(4,10); }) .attr({ x: 0, y: -4 }) .style('text-anchor', 'middle') .style('font-size', 14); tip_group.append('rect') .attr({ x: -30, y: 0, width: 60, height: 59 }) tip_group.selectAll('.interior_lab') .data(['open:', 'close:', 'high:', 'low:', 'vol:']) .enter() .append('text') .classed('interior_lab', true) .text(function (d) { return d; }) .attr({ x: 1, y: function (d, i) { return 8 + i*11; } }) .style({ 'font-size': '12px', 'text-anchor': 'end', 'alignment-baseline': 'text-after-edge' }); var pts = ['open', 'close', 'high', 'low', 'vol']; pts.forEach(function (x, j) { tip_group.append('text') .text(function (d) { return x === 'vol' ? Math.round(d[x]*10)/10 : Math.round(d[x]); }) .attr({ x: 5, y: 8 + j * 11 }) .style({ 'font-size': 12, 'alignment-baseline': 'text-after-edge' }); }) tip_group.append('circle') .attr({ cx: function (d, i) { return xs(i); }, cy: function (d) { return 71 - ys(d.high) + vol_ys(d.vol); }, r: 5 }) tip_group.style('opacity', 1) }) .on('mouseout', function () { d3.select(this).select('rect').style('fill-opacity', 0.45); d3.select('.tip_group').remove() }) vis.append('g') .classed('axis', true) .attr('transform', 'translate(' + [-20, 0] + ')') .call(yaxis); vis.append('g') .classed('axis', true) .attr('transform', 'translate(' + [-20, 0] + ')') .call(vol_yaxis); var xaxis_group = vis.append('g') .classed('axis', true) .attr('transform', 'translate(' + [0, chart_h + 30] + ')') .call(xaxis) var x_lab_group = xaxis_group.append('g') .classed('x_labs', true) .attr('transform', 'translate(' + [0, 18] + ')') var x_labs = x_lab_group.selectAll('.x_lab') .data(tvals) .enter() .append('g') .attr('transform', function (d, i) { return 'translate(' + [5 + xs(i), 0] + ')'; }) x_labs.append('text') .text(function (d) { return d; }) .attr({ transform: 'rotate(-50)', }) .style({ 'text-anchor': 'end', 'font-size': '14px' }) vis.append('text') .text('GOOG Daily Performace') .classed('large_label', true) .attr({ x: chart_w / 2, y: 0 - margin.top / 2 - 25, 'text-anchor': 'middle', 'alignment-baseline': 'baseline' }); vis.append('text') .text('Apr 1 - May 30, 2014') .classed('small_label', true) .attr({ x: chart_w / 2, y: 0 - margin.top / 2, 'text-anchor': 'middle', 'alignment-baseline': 'baseline' }); vis.append('text') .text('Price') .classed('large_label', true) .attr({ x: -20, y: -35, 'text-anchor': 'end' }); vis.append('text') .text('($/share)') .classed('small_label', true) .attr({ x: -20, y: -15, 'text-anchor': 'end' }); vis.append('text') .text('Volume') .classed('large_label', true) .attr({ x: -20, y: top_h + margin.mid - 35, 'text-anchor': 'end' }); vis.append('text') .text('(mill. shares)') .classed('small_label', true) .attr({ x: -20, y: top_h + margin.mid - 15, 'text-anchor': 'end' }); var key_group = vis.append('g') .attr('transform', 'translate(' + [chart_w - 250, 0 - margin.top/2 - 25] + ')scale(1)') key_group.append('rect') .attr({ x: 0, y: 0, width: 250, height: 110 }) .style('fill', 'rgb(231, 231, 231)') .style('fill-opacity', 1) var symbol_groups = key_group.selectAll('.symbol') .data(d3.range(2)) .enter() .append('g') .attr({ id: function (d, i) { return 'sg' + i; }, 'transform': function (d, i) { return 'translate(' + [250/2 - 60 + i * 120, 0] + ')'} }); symbol_groups.append('line') .classed('wick', true) .attr({ x1: 0, y1: 20, x2: 0, y2: 40 }) symbol_groups.append('rect') .classed('pos', true) .attr({ class: function (d, i) { return d === 0 ? 'pos' : 'neg'; }, x: -7, y: 40, width: 14, height: 30 }) symbol_groups.append('line') .classed('wick', true) .attr({ x1: 0, y1: 70, x2: 0, y2: 90 }) key_group.selectAll('.high_low') .data(['daily high', 'daily low']) .enter() .append('text') .classed('key_label', true) .text(function (d) { return d; }) .attr({ x: 250 / 2, y: function (d, i) { return 20 + i * 70; }, 'text-anchor': 'middle' }); d3.select('#sg0').selectAll('.close_open') .data(['close', 'open']) .enter() .append('text') .classed('key_label', true) .text(function (d) { return d; }) .attr({ x: -45, y: function (d, i) { return 40 + i * 30; }, 'text-anchor': 'middle' }); d3.select('#sg0').selectAll('.arrow_line') .data(d3.range(2)) .enter() .append('line') .attr({ x1: -27, y1: function (d, i) { return 40 + i * 30; }, x2: -12, y2: function (d, i) { return 40 + i * 30; } }); d3.select('#sg1').selectAll('.arrow_line') .data(d3.range(2)) .enter() .append('line') .attr({ x1: 27, y1: function (d, i) { return 40 + i * 30; }, x2: 12, y2: function (d, i) { return 40 + i * 30; } }); key_group.selectAll('.arrow_line') .data(d3.range(2)) .enter() .append('line') .attr({ x1: 250 / 2 - 60 + 5, y1: function (d, i) { return 20 + i * 70; }, x2: 250 / 2 - 60 + 25, y2: function (d, i) { return 20 + i * 70; } }); key_group.selectAll('.arrow_line') .data(d3.range(2)) .enter() .append('line') .attr({ x1: 250 / 2 + 60 - 25, y1: function (d, i) { return 20 + i * 70; }, x2: 250 / 2 + 60 - 5, y2: function (d, i) { return 20 + i * 70; } }); d3.select('#sg1').selectAll('.close_open') .data(['open', 'close']) .enter() .append('text') .classed('key_label', true) .text(function (d) { return d; }) .attr({ x: 45, y: function (d, i) { return 40 + i * 30; }, 'text-anchor': 'middle' }); var triangle = d3.svg.line() .x(function (d) { return d.x; }) .y(function (d) { return d.y; }) .interpolate('cardinal-closed') .tension(1); var points_right = [{x: -5, y: -2}, {x: -5, y: 2}, {x: 0, y: 0}], points_left = [{x: 5, y: -2}, {x: 5, y: 2}, {x: 0, y: 0}], arrow_coords = [ {x: 250 / 2 - 60 + 5, y: 20, dir: 'l'}, {x: 250 / 2 - 60 + 5, y: 90, dir: 'l'}, {x: 250 / 2 + 60 - 5, y: 20, dir: 'r'}, {x: 250 / 2 + 60 - 5, y: 90, dir: 'r'}, {x: 250 / 2 - 60 - 12, y: 40, dir: 'r'}, {x: 250 / 2 - 60 - 12, y: 70, dir: 'r'}, {x: 250 / 2 + 60 + 12, y: 40, dir: 'l'}, {x: 250 / 2 + 60 + 12, y: 70, dir: 'l'} ]; var ah_groups = key_group.selectAll('.ah_group') .data(arrow_coords) .enter() .append('g') .attr('transform', function (d) { return 'translate(' + [d.x, d.y] + ')'; }) ah_groups.append('path') .attr('d', function (d) { return d.dir === 'l' ? triangle(points_left) : triangle(points_right); }); }); </script>
Modified
http://d3js.org/d3.v3.min.js
to a secure url
https://d3js.org/d3.v3.min.js