Skip to content

Commit

Permalink
feat(area): Intent to ship area.linearGradient
Browse files Browse the repository at this point in the history
Implement linearGradient for area type

Fix #755
Close #800
  • Loading branch information
netil authored Mar 8, 2019
1 parent 86cf5fe commit 0063a4c
Show file tree
Hide file tree
Showing 5 changed files with 272 additions and 7 deletions.
69 changes: 69 additions & 0 deletions demo/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -1095,6 +1095,7 @@ var demos = {
]
},
XAxisTickTimeseries: {
description: "Drag over chart area and checkout the x Axis tick text label",
options: {
data: {
x: "x",
Expand Down Expand Up @@ -2460,6 +2461,74 @@ d3.select(".chart_area")
}
]
},
AreaChartOptions: {
Above: {
options: {
data: {
columns: [
["data1", 230, 280, 251, 400, 150, 546, 158],
["data2", 130, 357, 151, 400, 250, 250, 395]
],
type: "area",
groups: [["data1", "data2"]]
},
area: {
above: true
}
}
},
LinearGradient: [
{
options: {
data: {
columns: [
["data1", 230, 280, 251, 400, 150, 546, 158],
["data2", 130, 357, 151, 400, 250, 250, 395],
["data3", 330, 280, 320, 218, 450, 150, 500]
],
type: "area-spline",
groups: [["data1", "data2", "data3"]]
},
area: {
linearGradient: true
}
}
},
{
options: {
data: {
columns: [
["data1", 30, 200, 100, 400, 150, 250, 150, 200, 170, 240, 350, 150, 100, 400, 150, 250, 150, 200, 170, 240, 100, 150, 250, 150, 200, 170, 240, 30, 200, 100, 400, 150, 250, 150, 200, 170, 240, 350, 150, 100, 400, 350, 220, 250, 300, 270, 140, 150, 90, 150, 50, 120, 70, 40]
],
type: "area"
},
area: {
linearGradient: {
x: [1, 0],
y: [0, 1],
stops: [
[0, function(id) {
return id == "data1" ? "red" : "yellow";
}, 1],
[0.3, "orange", 0.5],
[0.6, "green", 0.7],
[0.8, "purple", 0.7],
[1, null, 1],
]
}
},
point: {
r: 0,
focus: {
expand: {
r: 5
}
}
}
}
}
]
},
BarChartOptions: {
BarPadding: {
options: {
Expand Down
85 changes: 85 additions & 0 deletions spec/shape/shape.line-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -494,4 +494,89 @@ describe("SHAPE LINE", () => {
expect(chart.internal.config.line_classes).to.include('line-class-2');
});
});

describe("area linear gradient", () => {
before(() => {
args = {
data: {
columns: [
["data1", 230, 280, 251, 400, 150, 546, 158],
["data2", 230, 280, 251, 400, 150, 546, 158]
],
type: "area",
},
area: {
linearGradient: true
}
}
});

it("should generate liearGradient element", () => {
const internal = chart.internal;
const expected = {
x: [0, 0],
y: [0, 1],
offset: [0, 1],
opacity: [1, 0]
};

chart.data().forEach(v => {
const color = chart.color(v.id);
const id = `#${internal.datetimeId}-areaGradient-${v.id}`;
const gradient = chart.$.svg.select(id);

expect(gradient.empty()).to.be.false;
expect([+gradient.attr("x1"), +gradient.attr("x2")]).to.be.deep.equal(expected.x);
expect([+gradient.attr("y1"), +gradient.attr("y2")]).to.be.deep.equal(expected.y);

gradient.selectAll("stop").each(function(d, i) {
const stop = d3.select(this);

expect(+stop.attr("offset")).to.be.equal(expected.offset[i]);
expect(stop.attr("stop-color")).to.be.equal(color);
expect(+stop.attr("stop-opacity")).to.be.equal(expected.opacity[i]);
});

expect(chart.$.line.areas.filter(`.${CLASS.area}-${v.id}`).style("fill")).to.be.equal(`url("${id}")`);
});
});

it("set options: customzied linearGradient", () => {
args.area.linearGradient = {
x: [1, 0],
y: [0, 1],
stops: [
[0, id => id == "data1" ? "red" : "yellow", 1],
[0.3, "orange", 0.5],
[0.6, "green", 0.7],
[0.8, "purple", 0.7],
[1, null, 1]
]
};
});

it("should generate customized liearGradient element", () => {
chart.data().forEach(v => {
const id = `#${chart.internal.datetimeId}-areaGradient-${v.id}`;
const gradient = chart.$.svg.select(id);

expect(gradient.empty()).to.be.false;
expect([+gradient.attr("x1"), +gradient.attr("x2")]).to.be.deep.equal(args.area.linearGradient.x);
expect([+gradient.attr("y1"), +gradient.attr("y2")]).to.be.deep.equal(args.area.linearGradient.y);

const stops = args.area.linearGradient.stops;

gradient.selectAll("stop").each(function(d, i) {
const color = i === 0 ? stops[i][1](v.id) : stops[i][1];
const stop = d3.select(this);

expect(+stop.attr("offset")).to.be.equal(stops[i][0]);
expect(stop.attr("stop-color")).to.be.equal(color || chart.color(v.id));
expect(+stop.attr("stop-opacity")).to.be.equal(stops[i][2]);
});

expect(chart.$.line.areas.filter(`.${CLASS.area}-${v.id}`).style("fill")).to.be.equal(`url("${id}")`);
});
});
});
});
40 changes: 37 additions & 3 deletions src/config/Options.js
Original file line number Diff line number Diff line change
Expand Up @@ -1599,7 +1599,7 @@ export default class Options {
* @memberof Options
* @type {Boolean}
* @default true
* @see [Demo](http://jindo.com/git/billboard.js/demo/#Axis.XAxisTickFitting)
* @see [Demo](https://naver.github.io/billboard.js/demo/#Axis.XAxisTickFitting)
* @see [Demo: for timeseries zoom](https://naver.github.io/billboard.js/demo/#Axis.XAxisTickTimeseries)
* @example
* axis: {
Expand Down Expand Up @@ -2859,15 +2859,49 @@ export default class Options {
* @memberof Options
* @type {Object}
* @property {Boolean} [area.zerobased=true] Set if min or max value will be 0 on area chart.
* @property {Boolean} [area.above=false]
* @property {Boolean} [area.above=false] Set background area above the data chart line.
* @property {Boolean|Object} [area.linearGradient=false] Set the linear gradient on area.<br><br>
* Or customize by giving below object value:
* - x {Array}: `x1`, `x2` value
* - y {Array}: `y1`, `y2` value
* - stops {Array}: Each item should be having `[offset, stop-color, stop-opacity]` values.
* @see [MDN's &lt;linearGradient>](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/linearGradient), [&lt;stop>](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/stop)
* @see [Demo](https://naver.github.io/billboard.js/demo/#Chart.AreaChart)
* @see [Demo: above](https://naver.github.io/billboard.js/demo/#AreaChartOptions.Above)
* @see [Demo: linearGradient](https://naver.github.io/billboard.js/demo/#AreaChartOptions.LinearGradient)
* @example
* area: {
* zerobased: false,
* above: true
* above: true,
*
* // will generate follwing linearGradient:
* // <linearGradient x1="0" x2="0" y1="0" y2="1">
* // <stop offset="0" stop-color="$DATA_COLOR" stop-opacity="1"></stop>
* // <stop offset="1" stop-color="$DATA_COLOR" stop-opacity="0"></stop>
* // </linearGradient>
* linearGradient: true,
*
* // Or customized gradient
* linearGradient: {
* x: [0, 0], // x1, x2 attributes
* y: [0, 0], // y1, y2 attributes
* stops: [
* // offset, stop-color, stop-opacity
* [0, "#7cb5ec", 1],
*
* // setting 'null' for stop-color, will set its original data color
* [0.5, null, 0],
*
* // setting 'function' for stop-color, will pass data id as argument.
* // It should return color string or null value
* [1, function(id) { return id === "data1" ? "red" : "blue"; }, 0],
* ]
* }
* }
*/
area_zerobased: true,
area_above: false,
area_linearGradient: false,

/**
* Set pie options
Expand Down
45 changes: 42 additions & 3 deletions src/shape/line.js
Original file line number Diff line number Diff line change
Expand Up @@ -344,9 +344,48 @@ extend(ChartInternal.prototype, {
return path;
},

updateAreaGradient() {
const $$ = this;

$$.data.targets.forEach(d => {
const color = $$.color(d);
const {
x = [0, 0],
y = [0, 1],
stops = [[0, color, 1], [1, color, 0]]
} = $$.config.area_linearGradient;

const linearGradient = $$.defs.append("linearGradient")
.attr("id", `${$$.datetimeId}-areaGradient-${d.id}`)
.attr("x1", x[0])
.attr("x2", x[1])
.attr("y1", y[0])
.attr("y2", y[1]);

stops.forEach(v => {
const stopColor = isFunction(v[1]) ? v[1](d.id) : v[1];

linearGradient.append("stop")
.attr("offset", v[0])
.attr("stop-color", stopColor || color)
.attr("stop-opacity", v[2]);
});
});
},

updateAreaColor(d) {
const $$ = this;

return $$.config.area_linearGradient ?
`url(#${$$.datetimeId}-areaGradient-${d.id})` :
$$.color(d);
},

updateArea(durationForExit) {
const $$ = this;

$$.config.area_linearGradient && !$$.mainArea && $$.updateAreaGradient();

$$.mainArea = $$.main.selectAll(`.${CLASS.areas}`)
.selectAll(`.${CLASS.area}`)
.data($$.lineData.bind($$));
Expand All @@ -358,7 +397,7 @@ extend(ChartInternal.prototype, {

$$.mainArea = $$.mainArea.enter().append("path")
.attr("class", $$.classArea.bind($$))
.style("fill", $$.color)
.style("fill", $$.updateAreaColor.bind($$))
.style("opacity", function() {
$$.orgAreaOpacity = d3Select(this).style("opacity");
return "0";
Expand All @@ -373,9 +412,9 @@ extend(ChartInternal.prototype, {
const $$ = this;

return [
(withTransition ? this.mainArea.transition(getRandom()) : this.mainArea)
(withTransition ? $$.mainArea.transition(getRandom()) : $$.mainArea)
.attr("d", drawArea)
.style("fill", this.color)
.style("fill", $$.updateAreaColor.bind($$))
.style("opacity", d => ($$.isAreaRangeType(d) ? $$.orgAreaOpacity / 1.75 : $$.orgAreaOpacity))
];
},
Expand Down
40 changes: 39 additions & 1 deletion types/options.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export interface ChartOptions {
/**
* Set the color value for each data point when mouse/touch onover event occurs.
*/
onover: string | object | ((d: DataItem) => string);
onover: string | {[key: string]: string} | ((d: DataItem) => string);
};

interaction?: {
Expand Down Expand Up @@ -203,6 +203,20 @@ export interface ChartOptions {
};

area?: {
/**
* Set background area above the data chart line.
*/
above?: boolean;

/**
* Set the linear gradient on area.<br><br>
* Or customize by giving below object value:
* - x {Array}: `x1`, `x2` value
* - y {Array}: `y1`, `y2` value
* - stops {Array}: Each item should be having `[offset, stop-color, stop-opacity]` values.
*/
linearGradient?: boolean | AreaLinearGradientOptions;

/**
* Set if min or max value will be 0 on area chart.
*/
Expand Down Expand Up @@ -531,6 +545,30 @@ export interface ChartOptions {
clipPath?: boolean;
}

export interface AreaLinearGradientOptions {
/**
* x1, x2 attributes
*/
x?: [number, number];

/**
* y1, y2 attributes
*/
y?: [number, number];

/**
* The ramp of colors to use on a gradient
*/
stops?: [
/**
* offset, stop-color, stop-opacity
* - setting 'null' for stop-color, will set its original data color
* - setting 'function' for stop-color, will pass data id as argument. It should return color string or null value
*/
[number, string | null | ((id: string) => string), number]
];
}

export interface RegionOptions {
axis?: string;
start?: string | number | Date;
Expand Down

0 comments on commit 0063a4c

Please sign in to comment.