mirror of https://github.com/masayuki0812/c3.git
Roman Charugin
9 years ago
31 changed files with 1372 additions and 131 deletions
File diff suppressed because one or more lines are too long
@ -0,0 +1,49 @@
|
||||
<html> |
||||
<head> |
||||
<link rel="stylesheet" type="text/css" href="/css/c3.css"> |
||||
</head> |
||||
<body> |
||||
<div id="chart"></div> |
||||
|
||||
<script src="http://d3js.org/d3.v3.js" charset="utf-8"></script> |
||||
<script src="/js/c3.js"></script> |
||||
<script> |
||||
var chart = c3.generate({ |
||||
bindto: '#chart', |
||||
data: { |
||||
x: 'date', |
||||
xFormat : '%Y%m%d', |
||||
columns: [ |
||||
['date', '20130101', '20130102', '20130103', '20130104', '20130105', '20130106', '20130107', '20130108', '20130109'], |
||||
['min', 10, 10, 30, 10, 55, 10, 55, 30, 10,], |
||||
['start', 30, 30, 30, 55, 55, 55, 55, 30, 80], |
||||
['end', 80, 80, 80, 55, 55, 55, 55, 80, 30], |
||||
['max', 110, 80, 110, 110, 55, 55, 110, 80, 110] |
||||
], |
||||
type: 'candle-stick' |
||||
}, |
||||
candlestick: { |
||||
data: { |
||||
min: 'min', |
||||
max: 'max', |
||||
start: 'start', |
||||
end: 'end' |
||||
}, |
||||
color: { |
||||
inc: 'yellow', |
||||
dec: 'orange', |
||||
neutral: 'green' |
||||
} |
||||
}, |
||||
axis: { |
||||
x: { |
||||
type: 'timeseries', |
||||
tick: { |
||||
format: '%Y-%m-%d' |
||||
} |
||||
} |
||||
} |
||||
}); |
||||
</script> |
||||
</body> |
||||
</html> |
@ -0,0 +1,250 @@
|
||||
describe('c3 api region', function () { |
||||
'use strict'; |
||||
|
||||
var chart, args; |
||||
|
||||
beforeEach(function (done) { |
||||
chart = window.initChart(chart, args, done); |
||||
}); |
||||
|
||||
describe('api.region', function () { |
||||
|
||||
it('should update args', function () { |
||||
args = { |
||||
data: { |
||||
columns: [ |
||||
['data1', 30, 200, 100, 400, 150, 250], |
||||
] |
||||
}, |
||||
regions: [ |
||||
{ |
||||
axis: 'y', |
||||
start: 300, |
||||
end: 400, |
||||
class: 'green', |
||||
}, |
||||
{ |
||||
axis: 'y', |
||||
start: 0, |
||||
end: 100, |
||||
class: 'green', |
||||
} |
||||
] |
||||
}; |
||||
expect(true).toBeTruthy(); |
||||
}); |
||||
|
||||
it('should update regions', function (done) { |
||||
var main = chart.internal.main, |
||||
expectedRegions = [ |
||||
{ |
||||
axis: 'y', |
||||
start: 250, |
||||
end: 350, |
||||
class: 'red' |
||||
}, |
||||
{ |
||||
axis: 'y', |
||||
start: 25, |
||||
end: 75, |
||||
class: 'red' |
||||
} |
||||
], |
||||
regions; |
||||
|
||||
// Call regions API
|
||||
chart.regions(expectedRegions); |
||||
setTimeout(function () { |
||||
regions = main.selectAll('.c3-region'); |
||||
expect(regions.size()).toBe(expectedRegions.length); |
||||
|
||||
regions.each(function (d, i) { |
||||
var region = d3.select(this), |
||||
rect = region.select('rect'), |
||||
y = +rect.attr('y'), |
||||
height = +rect.attr('height'), |
||||
expectedClass = 'red', |
||||
unexpectedClass = 'green', |
||||
expectedStart = Math.round(chart.internal.y(expectedRegions[i].start)), |
||||
expectedEnd = Math.round(chart.internal.y(expectedRegions[i].end)), |
||||
expectedY = expectedEnd, |
||||
expectedHeight = expectedStart - expectedEnd; |
||||
expect(y).toBeCloseTo(expectedY, -1); |
||||
expect(height).toBeCloseTo(expectedHeight, -1); |
||||
expect(region.classed(expectedClass)).toBeTruthy(); |
||||
expect(region.classed(unexpectedClass)).toBeFalsy(); |
||||
}); |
||||
}, 500); |
||||
|
||||
setTimeout(function () { |
||||
done(); |
||||
}, 1000); |
||||
}); |
||||
}); |
||||
|
||||
describe('api.region.add', function () { |
||||
|
||||
it('should update args', function () { |
||||
args = { |
||||
data: { |
||||
columns: [ |
||||
['data1', 30, 200, 100, 400, 150, 250], |
||||
] |
||||
}, |
||||
regions: [ |
||||
{ |
||||
axis: 'y', |
||||
start: 300, |
||||
end: 400, |
||||
class: 'green', |
||||
}, |
||||
{ |
||||
axis: 'y', |
||||
start: 0, |
||||
end: 100, |
||||
class: 'green', |
||||
} |
||||
] |
||||
}; |
||||
expect(true).toBeTruthy(); |
||||
}); |
||||
|
||||
it('should add regions', function (done) { |
||||
var main = chart.internal.main, |
||||
expectedRegions = [ |
||||
{ |
||||
axis: 'y', |
||||
start: 300, |
||||
end: 400, |
||||
class: 'green', |
||||
}, |
||||
{ |
||||
axis: 'y', |
||||
start: 0, |
||||
end: 100, |
||||
class: 'green', |
||||
}, |
||||
{ |
||||
axis: 'y', |
||||
start: 250, |
||||
end: 350, |
||||
class: 'red' |
||||
}, |
||||
{ |
||||
axis: 'y', |
||||
start: 25, |
||||
end: 75, |
||||
class: 'red' |
||||
} |
||||
], |
||||
expectedClasses = [ |
||||
'green', |
||||
'green', |
||||
'red', |
||||
'red', |
||||
], |
||||
regions; |
||||
|
||||
// Call regions API
|
||||
chart.regions(expectedRegions); |
||||
setTimeout(function () { |
||||
regions = main.selectAll('.c3-region'); |
||||
expect(regions.size()).toBe(expectedRegions.length); |
||||
|
||||
regions.each(function (d, i) { |
||||
var region = d3.select(this), |
||||
rect = region.select('rect'), |
||||
y = +rect.attr('y'), |
||||
height = +rect.attr('height'), |
||||
expectedClass = expectedClasses[i], |
||||
expectedStart = Math.round(chart.internal.y(expectedRegions[i].start)), |
||||
expectedEnd = Math.round(chart.internal.y(expectedRegions[i].end)), |
||||
expectedY = expectedEnd, |
||||
expectedHeight = expectedStart - expectedEnd; |
||||
expect(y).toBeCloseTo(expectedY, -1); |
||||
expect(height).toBeCloseTo(expectedHeight, -1); |
||||
expect(region.classed(expectedClass)).toBeTruthy(); |
||||
}); |
||||
}, 500); |
||||
|
||||
setTimeout(function () { |
||||
done(); |
||||
}, 1000); |
||||
}); |
||||
}); |
||||
|
||||
describe('api.region.remove', function () { |
||||
|
||||
it('should update args', function () { |
||||
args = { |
||||
data: { |
||||
columns: [ |
||||
['data1', 30, 200, 100, 400, 150, 250], |
||||
] |
||||
}, |
||||
regions: [ |
||||
{ |
||||
axis: 'y', |
||||
start: 300, |
||||
end: 400, |
||||
class: 'green', |
||||
}, |
||||
{ |
||||
axis: 'y', |
||||
start: 0, |
||||
end: 100, |
||||
class: 'green', |
||||
}, |
||||
{ |
||||
axis: 'y', |
||||
start: 250, |
||||
end: 350, |
||||
class: 'red' |
||||
}, |
||||
] |
||||
}; |
||||
expect(true).toBeTruthy(); |
||||
}); |
||||
|
||||
it('should remove regions', function (done) { |
||||
var main = chart.internal.main, |
||||
expectedRegions = [ |
||||
{ |
||||
axis: 'y', |
||||
start: 250, |
||||
end: 350, |
||||
class: 'red' |
||||
}, |
||||
], |
||||
expectedClasses = ['red'], |
||||
regions; |
||||
|
||||
// Call regions API
|
||||
chart.regions(expectedRegions); |
||||
setTimeout(function () { |
||||
regions = main.selectAll('.c3-region'); |
||||
expect(regions.size()).toBe(expectedRegions.length); |
||||
|
||||
regions.each(function (d, i) { |
||||
var region = d3.select(this), |
||||
rect = region.select('rect'), |
||||
y = +rect.attr('y'), |
||||
height = +rect.attr('height'), |
||||
expectedClass = expectedClasses[i], |
||||
expectedStart = Math.round(chart.internal.y(expectedRegions[i].start)), |
||||
expectedEnd = Math.round(chart.internal.y(expectedRegions[i].end)), |
||||
expectedY = expectedEnd, |
||||
expectedHeight = expectedStart - expectedEnd; |
||||
expect(y).toBeCloseTo(expectedY, -1); |
||||
expect(height).toBeCloseTo(expectedHeight, -1); |
||||
expect(region.classed(expectedClass)).toBeTruthy(); |
||||
}); |
||||
}, 500); |
||||
|
||||
setTimeout(function () { |
||||
done(); |
||||
}, 1000); |
||||
}); |
||||
}); |
||||
|
||||
}); |
@ -0,0 +1,264 @@
|
||||
// Candle stick chart initialization
|
||||
c3_chart_internal_fn.initCandleStick = function() { |
||||
var $$ = this; |
||||
$$.main.select('.' + CLASS.chart).append("g") |
||||
.attr('class', CLASS.chartCandleSticks); |
||||
}; |
||||
// Convert candlestick data
|
||||
c3_chart_internal_fn.convertTargetsForCandleStick = function (targets) { |
||||
var $$ = this, |
||||
config = $$.config, |
||||
minValues = (targets.filter(function (t) { return t.id === config.candlestick_data_min; })[0] || {}).values, |
||||
maxValues = (targets.filter(function (t) { return t.id === config.candlestick_data_max; })[0] || {}).values, |
||||
startValues = (targets.filter(function (t) { return t.id === config.candlestick_data_start; })[0] || {}).values, |
||||
endValues = (targets.filter(function (t) { return t.id === config.candlestick_data_end; })[0] || {}).values; |
||||
|
||||
if (!minValues) { |
||||
throw new Error('No min values found at ' + config.candlestick_data_min + ' data property!'); |
||||
} |
||||
|
||||
if (!maxValues) { |
||||
throw new Error('No max values found at ' + config.candlestick_data_max + ' data property!'); |
||||
} |
||||
|
||||
if (!startValues) { |
||||
throw new Error('No start values found at ' + config.candlestick_data_start + ' data property!'); |
||||
} |
||||
|
||||
if (!endValues) { |
||||
throw new Error('No end values found at ' + config.candlestick_data_end + ' data property!'); |
||||
} |
||||
|
||||
var dataNum = Math.max(minValues.length, maxValues.length, startValues.length, endValues.length); |
||||
|
||||
if (minValues.length !== dataNum) { |
||||
throw new Error('Not enough min values! Required ' + dataNum + ' values'); |
||||
} |
||||
|
||||
if (maxValues.length !== dataNum) { |
||||
throw new Error('Not enough max values! Required ' + dataNum + ' values'); |
||||
} |
||||
|
||||
if (startValues.length !== dataNum) { |
||||
throw new Error('Not enough start values! Required ' + dataNum + ' values'); |
||||
} |
||||
|
||||
if (endValues.length !== dataNum) { |
||||
throw new Error('Not enough end values! Required ' + dataNum + ' values'); |
||||
} |
||||
|
||||
// Saving data to internal var
|
||||
var candleStickValues = minValues.map(function(min, index) { |
||||
return { |
||||
id: 'cs-data', |
||||
index: index, |
||||
value: min.value, |
||||
csValue: { |
||||
min: min.value, |
||||
max: maxValues[index].value, |
||||
start: startValues[index].value, |
||||
end: endValues[index].value |
||||
}, |
||||
x: min.x |
||||
}; |
||||
}); |
||||
|
||||
$$.config.data_types['cs-data'] = 'candle-stick'; |
||||
$$.candleStickValues = candleStickValues; |
||||
|
||||
return $$.candleStickValues; |
||||
}; |
||||
// Updating candle stick chart containers
|
||||
c3_chart_internal_fn.updateTargetsForCandleStick = function (targets) { |
||||
var $$ = this, |
||||
mainCandleStickUpdate, |
||||
mainCandleStickEnter, |
||||
classChartCandleStick = $$.classChartCandleStick.bind($$), |
||||
classFocus = $$.classFocus.bind($$), |
||||
classCandleStick = $$.classCandleStick.bind($$), |
||||
classCandleSticks = $$.classCandleSticks.bind($$); |
||||
|
||||
var candleStickValues = $$.convertTargetsForCandleStick(targets); |
||||
|
||||
mainCandleStickUpdate = $$.main.select('.' + CLASS.chartCandleSticks).selectAll('.' + CLASS.chartCandleStick) |
||||
.data(candleStickValues.length > 0 ? [{id: 'cs-data', values: candleStickValues}] : null) |
||||
.attr('class', function (d) { return classCandleStick(d) + classFocus(d); }); |
||||
mainCandleStickEnter = mainCandleStickUpdate.enter().append('g') |
||||
.attr('class', classChartCandleStick) |
||||
.style('opacity', 0) |
||||
.style('pointer-events', 'none'); |
||||
// Candlestick for data
|
||||
mainCandleStickEnter.append('g') |
||||
.attr('class', classCandleSticks); |
||||
}; |
||||
// Updating one candle stick
|
||||
c3_chart_internal_fn.updateCandleStick = function (durationForExit) { |
||||
var $$ = this, |
||||
config = $$.config, |
||||
candleStickData = $$.candleStickData.bind($$), |
||||
classCandleStick = $$.classCandleStick.bind($$), |
||||
classCandleStickShadowUpper = $$.classCandleStickShadowUpper.bind($$), |
||||
classCandleStickShadowLower = $$.classCandleStickShadowLower.bind($$); |
||||
|
||||
$$.mainCandleStick = $$.main.selectAll('.' + CLASS.candleSticks) |
||||
.selectAll('.' + CLASS.candleStick) |
||||
.data(candleStickData); |
||||
$$.mainCandleStick.enter().append('path') |
||||
.attr('class', classCandleStick) |
||||
.style('fill', function(d) { |
||||
return !!d.csValue && d.csValue.start < d.csValue.end ? config.candlestick_color_inc : |
||||
!!d.csValue && d.csValue.start > d.csValue.end ? config.candlestick_color_dec : config.candlestick_color_neutral; |
||||
}); |
||||
$$.mainCandleStick.exit().transition().duration(durationForExit) |
||||
.style('opacity', 0) |
||||
.remove(); |
||||
|
||||
$$.mainCandleStickShadowsUpper = $$.main.selectAll('.' + CLASS.candleSticks) |
||||
.selectAll('.' + CLASS.candleStickShadowUpper) |
||||
.data(candleStickData); |
||||
$$.mainCandleStickShadowsUpper.enter() |
||||
.append('path') |
||||
.attr('class', classCandleStickShadowUpper); |
||||
$$.mainCandleStickShadowsUpper.exit().transition().duration(durationForExit) |
||||
.style('opacity', 0) |
||||
.remove(); |
||||
|
||||
$$.mainCandleStickShadowsLower = $$.main.selectAll('.' + CLASS.candleSticks) |
||||
.selectAll('.' + CLASS.candleStickShadowLower) |
||||
.data(candleStickData); |
||||
$$.mainCandleStickShadowsLower.enter() |
||||
.append('path') |
||||
.attr('class', classCandleStickShadowLower); |
||||
$$.mainCandleStickShadowsLower.exit().transition().duration(durationForExit) |
||||
.style('opacity', 0) |
||||
.remove(); |
||||
}; |
||||
c3_chart_internal_fn.redrawCandleStick = function (drawCandleStick, withTransition) { |
||||
var $$ = this; |
||||
return [ |
||||
(withTransition ? this.mainCandleStick.transition(Math.random().toString()) : this.mainCandleStick) |
||||
.attr('d', drawCandleStick) |
||||
.style('opacity', 1), |
||||
(withTransition ? this.mainCandleStickShadowsUpper.transition(Math.random().toString()) : this.mainCandleStickShadowsUpper) |
||||
.attr('d', $$.generateDrawCandleStickUpperShadow()) |
||||
.style('opacity', 1), |
||||
(withTransition ? this.mainCandleStickShadowsLower.transition(Math.random().toString()) : this.mainCandleStickShadowsLower) |
||||
.attr('d', $$.generateDrawCandleStickLowerShadow()) |
||||
.style('opacity', 1) |
||||
]; |
||||
}; |
||||
c3_chart_internal_fn.getCandleStickW = function (axis) { |
||||
var $$ = this, config = $$.config, |
||||
w = typeof config.candlestick_width === 'number' ? config.candlestick_width : axis.tickInterval() * config.candlestick_width_ratio; |
||||
return config.candlestick_width_max && w > config.candlestick_width_max ? config.candlestick_width_max : w; |
||||
}; |
||||
c3_chart_internal_fn.getCandleStick = function (i, id) { |
||||
var $$ = this; |
||||
return (id ? $$.main.selectAll('.' + CLASS.candleSticks + $$.getTargetSelectorSuffix(id)) : $$.main) |
||||
.selectAll('.' + CLASS.candleSticks + (isValue(i) ? '-' + i : '')); |
||||
}; |
||||
c3_chart_internal_fn.expandCandleStick = function (i, id, reset) { |
||||
var $$ = this; |
||||
if (reset) { $$.unexpandCandleStick(); } |
||||
$$.getCandleStick(i, id).classed(CLASS.EXPANDED, true); |
||||
}; |
||||
c3_chart_internal_fn.unexpandCandleStick = function (i) { |
||||
var $$ = this; |
||||
$$.getCandleStick(i).classed(CLASS.EXPANDED, false); |
||||
}; |
||||
c3_chart_internal_fn.generateDrawCandleStick = function (candleStickIndices) { |
||||
var $$ = this, |
||||
getPoints = $$.generateGetCandleStickPoints(candleStickIndices); |
||||
return function (d, i) { |
||||
// 8 points describing candlestick chart
|
||||
var points = getPoints(d, i); |
||||
|
||||
if (points[0][1] - points[2][1] < 1) { |
||||
points[2][1] -= 1; |
||||
points[3][1] -= 1; |
||||
} |
||||
|
||||
var path = |
||||
'M ' + points[0][0] + ',' + points[0][1] + ' ' + |
||||
'L' + points[1][0] + ',' + points[1][1] + ' ' + |
||||
'L' + points[2][0] + ',' + points[2][1] + ' ' + |
||||
'L' + points[3][0] + ',' + points[3][1] + ' ' + |
||||
'z'; |
||||
|
||||
return path; |
||||
}; |
||||
}; |
||||
c3_chart_internal_fn.generateDrawCandleStickUpperShadow = function (candleStickIndices) { |
||||
var $$ = this, |
||||
getPoints = $$.generateGetCandleStickPoints(candleStickIndices); |
||||
return function (d, i) { |
||||
// 8 points describing candlestick chart
|
||||
var points = getPoints(d, i); |
||||
|
||||
var path = |
||||
'M ' + points[4][0] + ',' + points[4][1] + ' ' + |
||||
'L' + points[5][0] + ',' + points[5][1]; |
||||
|
||||
return path; |
||||
}; |
||||
}; |
||||
c3_chart_internal_fn.generateDrawCandleStickLowerShadow = function (candleStickIndices) { |
||||
var $$ = this, |
||||
getPoints = $$.generateGetCandleStickPoints(candleStickIndices); |
||||
return function (d, i) { |
||||
// 8 points describing candlestick chart
|
||||
var points = getPoints(d, i); |
||||
|
||||
var path = |
||||
'M ' + points[6][0] + ',' + points[6][1] + ' ' + |
||||
'L' + points[7][0] + ',' + points[7][1]; |
||||
|
||||
return path; |
||||
}; |
||||
}; |
||||
c3_chart_internal_fn.generateGetCandleStickPoints = function (candleStickIndices, isSub) { |
||||
var $$ = this, |
||||
axis = isSub ? $$.subXAxis : $$.xAxis, |
||||
candleStickW = $$.getCandleStickW(axis), |
||||
candleStickX = $$.getShapeX(candleStickW, 1, {data: 0}, false), |
||||
candleStickY = $$.getShapeY(false), |
||||
candleStickValues = $$.candleStickValues; |
||||
|
||||
return function (d, i) { |
||||
var value = candleStickValues[i]; |
||||
var inc = value.csValue.start < value.csValue.end; |
||||
var min = value.csValue.min, |
||||
max = value.csValue.max, |
||||
end = inc ? value.csValue.end : value.csValue.start, |
||||
start = inc ? value.csValue.start : value.csValue.end; |
||||
var posX = candleStickX({id: 'cs-data', x: value.x}), |
||||
posYMax = candleStickY({id: 'cs-data', x: value.x, value: max}), |
||||
posYMin = candleStickY({id: 'cs-data', x: value.x, value: min}), |
||||
posYStart = candleStickY({id: 'cs-data', x: value.x, value: start}), |
||||
posYEnd = candleStickY({id: 'cs-data', x: value.x, value: end}); |
||||
|
||||
// 8 points that make a candle stick
|
||||
return [ |
||||
// Body
|
||||
[posX, posYEnd], |
||||
[posX + candleStickW, posYEnd], |
||||
[posX + candleStickW, posYStart], |
||||
[posX, posYStart], |
||||
// Upper shadow
|
||||
[posX + candleStickW / 2, posYMax], |
||||
[posX + candleStickW / 2, posYEnd], |
||||
// Lower shadow
|
||||
[posX + candleStickW / 2, posYStart], |
||||
[posX + candleStickW / 2, posYMin] |
||||
]; |
||||
}; |
||||
}; |
||||
c3_chart_internal_fn.isWithinCandleStick = function (that) { |
||||
var mouse = this.d3.mouse(that), |
||||
box = that.getBoundingClientRect(), |
||||
seg0 = that.pathSegList.getItem(0), seg1 = that.pathSegList.getItem(1), |
||||
x = Math.min(seg0.x, seg1.x), y = Math.min(seg0.y, seg1.y), |
||||
w = box.width, h = box.height, offset = 2, |
||||
sx = x - offset, ex = x + w + offset, sy = y + h + offset, ey = y - offset; |
||||
return sx < mouse[0] && mouse[0] < ex && ey < mouse[1] && mouse[1] < sy; |
||||
}; |
Loading…
Reference in new issue