Skip to content

Commit

Permalink
feat(point): Implement alternate markers
Browse files Browse the repository at this point in the history
Implementation to allow use a customized data points

Fix #209
Close #219
  • Loading branch information
Julien Castelain authored and netil committed Dec 15, 2017
1 parent 9699a55 commit 8f1b56f
Show file tree
Hide file tree
Showing 10 changed files with 251 additions and 207 deletions.
106 changes: 43 additions & 63 deletions demo/demo.js
Expand Up @@ -1606,79 +1606,59 @@ var demos = {
]
},
point: {
type: {
create: function(element, cssClassFn, sizeFn, fillStyleFn) {
return element.enter().append("polygon")
.attr("class", cssClassFn)
.style("fill", fillStyleFn);
},
update: function(element, xPosFn, yPosFn, opacityStyleFn, fillStyleFn,
withTransition, flow, selectedCircles) {
var size = this.pointR(element) * 3.0;
var halfSize = size * 0.5;

function getPoints(d) {
var x1 = xPosFn(d);
var y1 = yPosFn(d) - halfSize;
var x2 = x1 - halfSize;
var y2 = y1 + size;
var x3 = x1 + halfSize;
var y3 = y2;

return [x1, y1, x2, y2, x3, y3].join(" ");
}

return element
.attr("points", getPoints)
.style("opacity", opacityStyleFn)
.style("fill", fillStyleFn);
}
}
pattern: [
"<polygon points='2.5 0 0 5 5 5'></polygon>"
]
}
}
},
CustomPointsDiamond: {
CustomPointsDiamonds: {
options: {
data: {
columns: [
['data1', 100, 200, 1000, 900, 500],
['data1', 100, 400, 1000, 900, 500],
['data2', 20, 40, 500, 300, 200]
]
},
point: {
type: {
create: function(element, cssClassFn, sizeFn, fillStyle) {
// create custom an element node
return element.enter().append("polygon")
.attr("class", cssClassFn)
.style("fill", fillStyle);
},

update: function(element, xPosFn, yPosFn, opacityStyleFn, fillStyleFn,
withTransition, flow, selectedCircles) {
var size = this.pointR(element) * 3.0;
var halfSize = size * 0.5;

function getPoints(d) {
var x1 = xPosFn(d);
var y1 = yPosFn(d) - halfSize;
var x2 = x1 - halfSize;
var y2 = y1 + halfSize;
var x3 = x1;
var y3 = y2 + halfSize;
var x4 = x1 + halfSize;
var y4 = y2;

return [x1, y1, x2, y2, x3, y3, x4, y4].join(" ");
}

// style the custom element added
return element
.attr("points", getPoints)
.style("opacity", opacityStyleFn)
.style("fill", fillStyleFn);
}
}
pattern: [
"<polygon points='2.5 0 0 2.5 2.5 5 5 2.5 2.5 0'></polygon>"
]
}
}
},
CustomPointsHearts: {
options: {
data: {
columns: [
['data1', 100, 400, 1000, 900, 500],
['data2', 20, 40, 500, 300, 200]
]
},
point: {
pattern: [
"<path d='m3.937502,2.348755c1.314192,-3.618047 6.463238,0 0,4.651779c-6.463238,-4.651779 -1.314192,-8.269826 0,-4.651779z' />"
]
}
}
},
CombinationPoints: {
options: {
data: {
columns: [
['data1', 100, 400, 1000, 900, 500],
['data2', 20, 40, 500, 300, 200],
['data3', 80, 350, 800, 450, 500],
['data4', 150, 240, 300, 700, 300]
]
},
point: {
pattern: [
"circle",
"rectangle",
"<polygon points='2.5 0 0 2.5 2.5 5 5 2.5 2.5 0'></polygon>",
"<polygon points='2.5 0 0 5 5 5'></polygon>"
]
}
}
}
Expand Down
41 changes: 8 additions & 33 deletions spec/interaction-spec.js
Expand Up @@ -163,7 +163,7 @@ describe("INTERACTION", () => {
});

it("set option point.type='rectangle'", () => {
args.point.type = "rectangle";
args.point.pattern = ["rectangle"];
clicked = false;
data = null;
});
Expand All @@ -183,34 +183,9 @@ describe("INTERACTION", () => {
});

it("set option point.type=polygon(custom triangle)", () => {
args.point.type = {
create: function(element, cssClassFn, sizeFn, fillStyleFn) {
return element.enter().append("polygon")
.attr("class", cssClassFn)
.style("fill", fillStyleFn);
},
update: function(element, xPosFn, yPosFn, opacityStyleFn, fillStyleFn,
withTransition, flow, selectedCircles) {
var size = this.pointR(element) * 3.0;
var halfSize = size * 0.5;

function getPoints(d) {
var x1 = xPosFn(d);
var y1 = yPosFn(d) - halfSize;
var x2 = x1 - halfSize;
var y2 = y1 + size;
var x3 = x1 + halfSize;
var y3 = y2;

return [x1, y1, x2, y2, x3, y3].join(" ");
}

return element
.attr("points", getPoints)
.style("opacity", opacityStyleFn)
.style("fill", fillStyleFn);
}
};
args.point.pattern = [
"<polygon points='5 2.5 2.5 5 7.5 5'></polygon>"
];

clicked = false;
data = null;
Expand All @@ -219,20 +194,20 @@ describe("INTERACTION", () => {
it("check for data click for polygon data point", () => {
const main = chart.internal.main;
const rect = main.select(`.${CLASS.eventRect}.${CLASS.eventRect}`).node();
const circle = main.select(`.${CLASS.circles}-data2 polygon`).node().getBBox();
const circle = main.select(`.${CLASS.circles}-data2 use`).node().getBBox();

util.fireEvent(rect, "click", {
clientX: circle.x,
clientY: circle.y
}, chart);

expect(clicked).to.be.true;
expect(data.value).to.be.equal(20);
expect(clicked).to.be.false;
expect(data).to.be.equal(null);
});

it("set option data.type='area'", () => {
args.data.type = "area";
args.point.type = "circle";
args.point.pattern = ["circle"];

clicked = false;
data = null;
Expand Down
41 changes: 6 additions & 35 deletions spec/shape.point-spec.js
Expand Up @@ -51,7 +51,7 @@ describe("SHAPE POINT", () => {
]
},
point: {
type: "rectangle"
pattern: ["rectangle"]
}
};
});
Expand All @@ -76,46 +76,17 @@ describe("SHAPE POINT", () => {
]
},
point: {
type: {
create(element, cssClassFn, sizeFn, fillStyle) {
return element.enter().append("polygon")
.attr("class", cssClassFn)
.style("fill", fillStyle);
},

update(element, xPosFn, yPosFn, opacityStyleFn, fillStyleFn,
withTransition, flow, selectedCircles) {
let mainCircles;
const triangleSize = 10;

function getPoints(d) {
const x1 = xPosFn(d);
const y1 = yPosFn(d) - (triangleSize * 0.5);
const x2 = x1 - (triangleSize * 0.5);
const y2 = y1 + (triangleSize * 0.5);
const x3 = x1;
const y3 = y2 + (triangleSize * 0.5);
const x4 = x1 + (triangleSize * 0.5);
const y4 = y2;
return `${x1} ${y1} ${x2} ${y2} ${x3} ${y3} ${x4} ${y4}`;
}

mainCircles = element
.attr("points", getPoints)
.style("opacity", opacityStyleFn)
.style("fill", fillStyleFn);

return mainCircles;
}
}
pattern: [
"<polygon points='5 2.5 2.5 5 7.5 5'></polygon>"
]
}
};
});

it("Should render svg polygon elements", () => {
it("Should render svg \"use\" elements", () => {
const target = chart.internal.svg.select(".bb-chart-line.bb-target-data1");
const circlesEl = target.select(".bb-circles-data1").node();
const polygons = circlesEl.getElementsByTagName("polygon");
const polygons = circlesEl.getElementsByTagName("use");

expect(polygons.length).to.be.equal(6);
});
Expand Down
4 changes: 3 additions & 1 deletion src/api/api.flow.js
Expand Up @@ -406,7 +406,9 @@ extend(ChartInternal.prototype, {

mainCircle
.attr("x", xFunc)
.attr("y", yFunc);
.attr("y", yFunc)
.attr("cx", cx) // when pattern is used, it possibly contain 'circle' also.
.attr("cy", cy);
}

mainText
Expand Down
41 changes: 22 additions & 19 deletions src/config/Options.js
Expand Up @@ -2087,14 +2087,23 @@ export default class Options {
* @property {Boolean} [point.focus.expand.r=point.r*1.75] The radius size of each point on focus.<br>
* - **Note:** For 'bubble' type, the default is `bubbleSize*1.15`
* @property {Number} [point.select.r=point.r*4] The radius size of each point on selected.
* @property {String|Object} [point.type="circle"] The type of point to be drawn<br>
* - **Note:** If chart has 'bubble' type, only circle can be used.<br>
* @property {String} [point.type="circle"] The type of point to be drawn<br>
* - **Note:**
* - If chart has 'bubble' type, only circle can be used.
* - For IE, non circle point expansions are not supported due to lack of transform support.
* - **Available Values:**
* - circle
* - rectangle
*
* @property {Function} [point.type.create] If specified will be invoked to create data points, this function must return a d3 selection.
* @property {Function} [point.type.update] If specified will be invoked to update data points, this function must return a d3 selection.
* @property {Array} [point.pattern=[]] The type of point or svg shape as string, to be drawn for each line<br>
* - **Note:**
* - This is an `experimental` feature and can have some unexpected behaviors.
* - If chart has 'bubble' type, only circle can be used.
* - For IE, non circle point expansions are not supported due to lack of transform support.
* - **Available Values:**
* - circle
* - rectangle
* - svg shape tag interpreted as string<br>
* (ex. `<polygon points='2.5 0 0 5 5 5'></polygon>`)
* @example
* point: {
* show: false,
Expand All @@ -2116,29 +2125,23 @@ export default class Options {
* r: 3
* },
*
* // valid values are "circle" or "rectangle"
* type: "rectangle",
*
* // or for custom shapes you can use an object with a "create" and "update" functions
* type: {
* // to create a custom type, set create & update functions as well
* create: function(element, cssClassFn, sizeFn, fillStyleFn) {
* // should create node element to be used as data point and must return a d3.selection
* ...
* return element;
* },
* update: function(element, xPosFn, yPosFn, opacityStyleFn, fillStyleFn, withTransition, flow, selectedCircles) {
* // adjust the position & styles to the given element and must return a d3.selection
* ...
* return element;
* }
* }
* // or indicate as pattern
* pattern: [
* "circle",
* "rectangle",
* "<polygon points='0 6 4 0 -4 0'></polygon>"
* ],
* }
*/
point_show: true,
point_r: 2.5,
point_sensitivity: 10,
point_focus_expand_enabled: true,
point_focus_expand_r: undefined,
point_pattern: [],
point_select_r: undefined,
point_type: "circle",

Expand Down
18 changes: 10 additions & 8 deletions src/internals/ChartInternal.js
Expand Up @@ -82,7 +82,8 @@ export default class ChartInternal {
const config = $$.config;

// MEMO: clipId needs to be unique because it conflicts when multiple charts exist
$$.clipId = `bb-${+new Date()}-clip`;
$$.datetimeId = `bb-${+new Date()}`;
$$.clipId = `${$$.datetimeId}-clip`;
$$.clipIdForXAxis = `${$$.clipId}-xaxis`;
$$.clipIdForYAxis = `${$$.clipId}-yaxis`;
$$.clipIdForGrid = `${$$.clipId}-grid`;
Expand All @@ -101,6 +102,7 @@ export default class ChartInternal {

$$.color = $$.generateColor();
$$.levelColor = $$.generateLevelColor();
$$.point = $$.generatePoint();

$$.extraLineClasses = $$.generateExtraLineClass();

Expand Down Expand Up @@ -257,17 +259,17 @@ export default class ChartInternal {
$$.svg.attr("class", config.svg_classname);

// Define defs
const defs = $$.svg.append("defs");
$$.defs = $$.svg.append("defs");

$$.clipChart = $$.appendClip(defs, $$.clipId);
$$.clipXAxis = $$.appendClip(defs, $$.clipIdForXAxis);
$$.clipYAxis = $$.appendClip(defs, $$.clipIdForYAxis);
$$.clipGrid = $$.appendClip(defs, $$.clipIdForGrid);
$$.clipSubchart = $$.appendClip(defs, $$.clipIdForSubchart);
$$.clipChart = $$.appendClip($$.defs, $$.clipId);
$$.clipXAxis = $$.appendClip($$.defs, $$.clipIdForXAxis);
$$.clipYAxis = $$.appendClip($$.defs, $$.clipIdForYAxis);
$$.clipGrid = $$.appendClip($$.defs, $$.clipIdForGrid);
$$.clipSubchart = $$.appendClip($$.defs, $$.clipIdForSubchart);

// set color patterns
if (isFunction(config.color_tiles) && $$.patterns) {
$$.patterns.forEach(p => defs.append(() => p.node));
$$.patterns.forEach(p => $$.defs.append(() => p.node));
}

$$.updateSvgSize();
Expand Down
6 changes: 5 additions & 1 deletion src/internals/type.js
Expand Up @@ -112,7 +112,11 @@ extend(ChartInternal.prototype, {

// determine if is 'circle' data point
isCirclePoint() {
return this.config.point_type === "circle";
const config = this.config;
const pattern = config.point_pattern;

return config.point_type === "circle" &&
(!pattern || (isArray(pattern) && pattern.length === 0));
},

lineData(d) {
Expand Down

0 comments on commit 8f1b56f

Please sign in to comment.