diff --git a/docs/04-line.md b/docs/04-line.md index 9221f80..7da4725 100644 --- a/docs/04-line.md +++ b/docs/04-line.md @@ -42,3 +42,5 @@ const lineChart = new chartXkcd.Line(svg, { Possible values: - up left: `chart.Xkcd.positionType.upLeft` - up right: `chart.Xkcd.positionType.upLeft` +- `dataColors`: array of colors for different datasets +- `fontFamily`: customize font family used in the chart diff --git a/docs/05-XY.md b/docs/05-XY.md index 517b24c..526ff02 100644 --- a/docs/05-XY.md +++ b/docs/05-XY.md @@ -54,6 +54,8 @@ You can also plot XY line chart by connecting the points. - `timeFormat`: specify the time format if the x values are time (default `undefined`) chart.xkcd use [dayjs](https://github.com/iamkun/dayjs) to format time, you can find the all the available formats [here](https://github.com/iamkun/dayjs/blob/dev/docs/en/API-reference.md#list-of-all-available-formats) - `dotSize`: you can change size of the dots if you want (default `1`) +- `dataColors`: array of colors for different datasets +- `fontFamily`: customize font family used in the chart **Another example of XY chart: XY line chart with `timeFormat`** diff --git a/docs/06-bar.md b/docs/06-bar.md index 2425b68..7a4bbcf 100644 --- a/docs/06-bar.md +++ b/docs/06-bar.md @@ -32,4 +32,6 @@ const barChart = new chartXkcd.Bar(svg, { ## Customize chart by defining options -- `yTickCount`: customize tick numbers you want to see on the y axis \ No newline at end of file +- `yTickCount`: customize tick numbers you want to see on the y axis +- `dataColors`: array of colors for different datasets +- `fontFamily`: customize font family used in the chart \ No newline at end of file diff --git a/docs/07-pie.md b/docs/07-pie.md index 2b1e58e..e171610 100644 --- a/docs/07-pie.md +++ b/docs/07-pie.md @@ -35,3 +35,5 @@ const pieChart = new chartXkcd.Pie(svg, { Possible values: - up left: `chart.Xkcd.positionType.upLeft` - up right: `chart.Xkcd.positionType.upLeft +- `dataColors`: array of colors for different datasets +- `fontFamily`: customize font family used in the chart diff --git a/examples/npm/index.html b/examples/npm/index.html index 25b2645..49899fb 100644 --- a/examples/npm/index.html +++ b/examples/npm/index.html @@ -1,6 +1,7 @@ +
@@ -8,6 +9,7 @@
+
diff --git a/examples/npm/index.js b/examples/npm/index.js index faad1f7..341e5c2 100644 --- a/examples/npm/index.js +++ b/examples/npm/index.js @@ -77,7 +77,7 @@ new chartXkcd.XY(svgXY, { }); const svgXY2 = document.querySelector('.xyline-chart2'); -const xy2Chart = new chartXkcd.XY(svgXY2, { +new chartXkcd.XY(svgXY2, { title: 'Github star history', xLabel: 'Month', yLabel: 'Stars abc', @@ -100,3 +100,23 @@ const xy2Chart = new chartXkcd.XY(svgXY2, { }, }); +const svgLineCus = document.querySelector('.line-chart-cus'); +new chartXkcd.Line(svgLineCus, { + title: 'Customize Font & colors (定制外观)', + xLabel: 'this is x label', + yLabel: 'y label', + data: { + labels: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'], + datasets: [{ + label: 'font', + data: [30, 70, 200, 300, 500, 800, 100, 290, 500, 300], + }, { + label: 'color', + data: [0, 1, 30, 70, 80, 100, 500, 80, 40, 250], + }], + }, + options: { + fontFamily: 'ZCOOL KuaiLe', + dataColors: ['black', '#aaa'], + }, +}); diff --git a/package.json b/package.json index 38c71ba..bcea121 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "chart.xkcd", - "version": "1.0.10", + "version": "1.1.0", "description": "xkcd style chart lib", "jsdelivr": "dist/chart.xkcd.min.js", "unpkg": "dist/chart.xkcd.min.js", diff --git a/src/Bar.js b/src/Bar.js index 8b799b1..f968552 100644 --- a/src/Bar.js +++ b/src/Bar.js @@ -20,6 +20,8 @@ class Bar { title, xLabel, yLabel, data: { labels, datasets }, options = { yTickCount: 3, + dataColors: [], + fontFamily: 'xkcd', }, }) { if (title) { @@ -39,9 +41,12 @@ class Bar { datasets, }; this.options = options; - this.svgEl = select(svg).style('stroke-width', '3') + this.svgEl = select(svg) + .style('stroke-width', '3') + .style('font-family', this.options.fontFamily || 'xkcd') .attr('width', svg.parentElement.clientWidth) .attr('height', Math.min((svg.parentElement.clientWidth * 2) / 3, window.innerHeight)); + this.svgEl.selectAll('*').remove(); this.chart = this.svgEl.append('g') @@ -81,9 +86,16 @@ class Bar { const graphPart = this.chart.append('g'); // axis - addAxis.xAxis(graphPart, { xScale, tickCount: 3, moveDown: this.height }); + addAxis.xAxis(graphPart, { + xScale, + tickCount: 3, + moveDown: this.height, + fontFamily: this.options.fontFamily || 'xkcd', + }); addAxis.yAxis(graphPart, { - yScale, tickCount: this.options.yTickCount === undefined ? 3 : this.options.yTickCount, + yScale, + tickCount: this.options.yTickCount || 3, + fontFamily: this.options.fontFamily || 'xkcd', }); // Bars @@ -104,7 +116,7 @@ class Bar { // .attr('cursor','crosshair') .attr('filter', 'url(#xkcdify)') .on('mouseover', (d, i, nodes) => { - select(nodes[i]).attr('fill', colors[0][i]); + select(nodes[i]).attr('fill', this.options.dataColors ? this.options.dataColors[i] : colors[i]); // select(nodes[i]).attr('fill', 'url(#hatch00)'); this.tooltip.show(); }) @@ -127,7 +139,7 @@ class Bar { this.tooltip.update({ title: this.data.labels[i], items: [{ - color: colors[0][i], + color: this.options.dataColors ? this.options.dataColors[i] : colors[i], text: `${this.data.datasets[0].label || ''}: ${d}`, }], position: { diff --git a/src/Line.js b/src/Line.js index 5acf33e..e15951d 100644 --- a/src/Line.js +++ b/src/Line.js @@ -23,7 +23,10 @@ class Line { constructor(svg, { title, xLabel, yLabel, data: { labels, datasets }, options = { - yTickCount: 3, legendPosition: config.positionType.upLeft, + yTickCount: 3, + legendPosition: config.positionType.upLeft, + dataColors: [], + fontFamily: 'xkcd', }, }) { if (title) { @@ -43,7 +46,9 @@ class Line { datasets, }; this.options = options; - this.svgEl = select(svg).style('stroke-width', '3') + this.svgEl = select(svg) + .style('stroke-width', '3') + .style('font-family', this.options.fontFamily || 'xkcd') .attr('width', svg.parentElement.clientWidth) .attr('height', Math.min((svg.parentElement.clientWidth * 2) / 3, window.innerHeight)); this.svgEl.selectAll('*').remove(); @@ -85,9 +90,16 @@ class Line { .attr('pointer-events', 'all'); // axis - addAxis.xAxis(graphPart, { xScale, tickCount: 3, moveDown: this.height }); + addAxis.xAxis(graphPart, { + xScale, + tickCount: 3, + moveDown: this.height, + fontFamily: this.options.fontFamily || 'xkcd', + }); addAxis.yAxis(graphPart, { - yScale, tickCount: this.options.yTickCount === undefined ? 3 : this.options.yTickCount, + yScale, + tickCount: this.options.yTickCount || 3, + fontFamily: this.options.fontFamily || 'xkcd', }); selectAll('.domain') @@ -105,7 +117,7 @@ class Line { .attr('class', 'xkcd-chart-line') .attr('d', (d) => theLine(d.data)) .attr('fill', 'none') - .attr('stroke', (d, i) => colors[0][i]) + .attr('stroke', (d, i) => this.options.dataColors ? this.options.dataColors[i] : colors[i]) .attr('filter', 'url(#xkcdify)'); // hover effect @@ -121,8 +133,8 @@ class Line { const circles = this.data.datasets.map((dataset, i) => graphPart .append('circle') - .style('stroke', colors[0][i]) - .style('fill', colors[0][i]) + .style('stroke', this.options.dataColors ? this.options.dataColors[i] : colors[i]) + .style('fill', this.options.dataColors ? this.options.dataColors[i] : colors[i]) .attr('r', 3.5) .style('visibility', 'hidden')); @@ -163,7 +175,7 @@ class Line { }); const tooltipItems = this.data.datasets.map((dataset, j) => ({ - color: colors[0][j], + color: this.options.dataColors ? this.options.dataColors[j] : colors[j], text: `${this.data.datasets[j].label || ''}: ${this.data.datasets[j].data[mostNearLabelIndex]}`, })); @@ -189,7 +201,7 @@ class Line { // Legend const legendItems = this.data.datasets.map( - (dataset, i) => ({ color: colors[0][i], text: dataset.label }), + (dataset, i) => ({ color: this.options.dataColors ? this.options.dataColors[i] : colors[i], text: dataset.label }), ); if (this.options.legendPosition === config.positionType.upLeft || !this.options.legendPosition) { diff --git a/src/Pie.js b/src/Pie.js index 18880e5..5ed57e7 100644 --- a/src/Pie.js +++ b/src/Pie.js @@ -24,7 +24,9 @@ class Pie { datasets, }; this.options = options; - this.svgEl = select(svg).style('stroke-width', '3') + this.svgEl = select(svg) + .style('stroke-width', '3') + .style('font-family', this.options.fontFamily || 'xkcd') .attr('width', svg.parentElement.clientWidth) .attr('height', Math.min((svg.parentElement.clientWidth * 2) / 3, window.innerHeight)); this.svgEl.selectAll('*').remove(); @@ -52,7 +54,7 @@ class Pie { if (this.title) { this.svgEl .append('text') - .style('font-family', 'xkcd') + .style('font-size', '20') .style('font-weight', 'bold') .attr('x', '50%') @@ -81,7 +83,7 @@ class Pie { .attr('fill', 'none') .attr('stroke', 'black') .attr('stroke-width', 2) - .attr('fill', (d, i) => colors[0][i]) + .attr('fill', (d, i) => colors[i]) .attr('filter', 'url(#xkcdify-pie)') // .attr("fill-opacity", 0.6) .on('mouseover', (d, i, nodes) => { @@ -99,7 +101,7 @@ class Pie { this.tooltip.update({ title: this.data.labels[i], items: [{ - color: colors[0][i], + color: colors[i], text: `${this.data.datasets[0].label || ''}: ${d.data}`, }], position: { @@ -112,7 +114,7 @@ class Pie { // Legend const legendItems = this.data.datasets[0].data - .map((data, i) => ({ color: colors[0][i], text: this.data.labels[i] })); + .map((data, i) => ({ color: colors[i], text: this.data.labels[i] })); if (this.options.legendPosition === config.positionType.upLeft || !this.options.legendPosition) { new Legend({ diff --git a/src/XY.js b/src/XY.js index e44ccc5..4dafe51 100644 --- a/src/XY.js +++ b/src/XY.js @@ -23,6 +23,7 @@ class XY { title, xLabel, yLabel, data: { datasets }, options = { dotSize: 1, showLine: false, timeFormat: '', xTickCount: 3, yTickCount: 3, legendPosition: config.positionType.upLeft, + }, }) { // TODO: extract a function? @@ -42,7 +43,9 @@ class XY { datasets, }; this.options = options; - this.svgEl = select(svg).style('stroke-width', 3) + this.svgEl = select(svg) + .style('stroke-width', 3) + .style('font-family', this.options.fontFamily || 'xkcd') .attr('width', svg.parentElement.clientWidth) .attr('height', Math.min((svg.parentElement.clientWidth * 2) / 3, window.innerHeight)); this.svgEl.selectAll('*').remove(); @@ -105,10 +108,12 @@ class XY { xScale, tickCount: this.options.xTickCount === undefined ? 3 : this.options.xTickCount, moveDown: this.height, + fontFamily: this.options.fontFamily || 'xkcd', }); addAxis.yAxis(graphPart, { yScale, tickCount: this.options.yTickCount === undefined ? 3 : this.options.yTickCount, + fontFamily: this.options.fontFamily || 'xkcd', }); // lines @@ -125,7 +130,7 @@ class XY { .attr('class', 'xkcd-chart-xyline') .attr('d', (d) => theLine(d.data)) .attr('fill', 'none') - .attr('stroke', (d, i) => colors[0][i]) + .attr('stroke', (d, i) => (this.options.dataColors ? this.options.dataColors[i] : colors[i])) .attr('filter', 'url(#xkcdify)'); } @@ -147,11 +152,11 @@ class XY { // FIXME: here I want to pass xyGroupIndex down to the circles by reading parent attrs // It might have perfomance issue with a large dataset, not sure there are better ways const xyGroupIndex = Number(select(nodes[i].parentElement).attr('xy-group-index')); - return colors[0][xyGroupIndex]; + return colors[xyGroupIndex]; }) .style('fill', (d, i, nodes) => { const xyGroupIndex = Number(select(nodes[i].parentElement).attr('xy-group-index')); - return colors[0][xyGroupIndex]; + return colors[xyGroupIndex]; }) .attr('r', dotInitSize) .attr('cx', (d) => xScale(d.x)) @@ -175,7 +180,7 @@ class XY { this.tooltip.update({ title: this.options.timeFormat ? dayjs(this.data.datasets[xyGroupIndex].data[i].x).format(this.options.timeFormat) : `${this.data.datasets[xyGroupIndex].data[i].x}`, items: [{ - color: colors[0][xyGroupIndex], + color: colors[xyGroupIndex], text: `${this.data.datasets[xyGroupIndex].label || ''}: ${d.y}`, }], position: { @@ -196,7 +201,10 @@ class XY { // Legend const legendItems = this.data.datasets.map( - (dataset, i) => ({ color: colors[0][i], text: dataset.label }), + (dataset, i) => ({ + color: this.options.dataColors ? this.options.dataColors[i] : colors[i], + text: dataset.label, + }), ); if (this.options.legendPosition === config.positionType.upLeft || !this.options.legendPosition) { diff --git a/src/components/Legend.js b/src/components/Legend.js index 342bc19..dac67fb 100644 --- a/src/components/Legend.js +++ b/src/components/Legend.js @@ -55,7 +55,6 @@ class Legend { .attr('y', 17 + 20 * i); g.append('text') - .style('font-family', 'xkcd') .style('font-size', '15') .style('font-weight', 'lighter') .attr('x', 15 + 12) diff --git a/src/components/Tooltip.js b/src/components/Tooltip.js index 87abc93..2cc601e 100644 --- a/src/components/Tooltip.js +++ b/src/components/Tooltip.js @@ -49,7 +49,6 @@ class Tooltip { .attr('y', 5); this.tipTitle = this.svg.append('text') - .style('font-family', 'xkcd') .style('font-size', 15) .style('font-weight', 'bold') .attr('x', 15) @@ -69,7 +68,6 @@ class Tooltip { .attr('y', 37 + 20 * i); g.append('text') - .style('font-family', 'xkcd') .style('font-size', '15') .style('font-weight', 'lighter') .attr('x', 15 + 12) @@ -114,7 +112,6 @@ class Tooltip { .attr('y', 37 + 20 * i); g.append('text') - .style('font-family', 'xkcd') .style('font-size', '15') .style('font-weight', 'lighter') .attr('x', 15 + 12) diff --git a/src/utils/addAxis.js b/src/utils/addAxis.js index 8678e32..1b1fe46 100644 --- a/src/utils/addAxis.js +++ b/src/utils/addAxis.js @@ -1,7 +1,8 @@ import { axisBottom, axisLeft } from 'd3-axis/src/axis'; -import selectAll from 'd3-selection/src/selectAll'; -const yAxis = (parent, { yScale, tickCount }) => { +const yAxis = (parent, { + yScale, tickCount, fontFamily, +}) => { parent .append('g') .call( @@ -11,15 +12,18 @@ const yAxis = (parent, { yScale, tickCount }) => { .ticks(tickCount, 's'), ); - selectAll('.domain') - .attr('filter', 'url(#xkcdify)'); + parent.selectAll('.domain') + .attr('filter', 'url(#xkcdify)') + .style('stroke', 'black'); - selectAll('.tick > text') - .style('font-family', 'xkcd') + parent.selectAll('.tick > text') + .style('font-family', fontFamily) .style('font-size', '16'); }; -const xAxis = (parent, { xScale, tickCount, moveDown }) => { +const xAxis = (parent, { + xScale, tickCount, moveDown, fontFamily, +}) => { parent .append('g') .attr('transform', `translate(0,${moveDown})`) @@ -30,11 +34,12 @@ const xAxis = (parent, { xScale, tickCount, moveDown }) => { .ticks(tickCount), ); - selectAll('.domain') - .attr('filter', 'url(#xkcdify)'); + parent.selectAll('.domain') + .attr('filter', 'url(#xkcdify)') + .style('stroke', 'black'); - selectAll('.tick > text') - .style('font-family', 'xkcd') + parent.selectAll('.tick > text') + .style('font-family', fontFamily) .style('font-size', '16'); }; diff --git a/src/utils/addFilter.js b/src/utils/addFilter.js index a1f2545..ad357cd 100644 --- a/src/utils/addFilter.js +++ b/src/utils/addFilter.js @@ -1,5 +1,3 @@ -import colors from './colors'; - export default function addFilter(parent) { parent.append('filter') .attr('id', 'xkcdify') @@ -32,15 +30,16 @@ export default function addFilter(parent) { .attr('in', 'SourceGraphic') .attr('in2', 'noise')); - parent.append('pattern') - .attr('id', 'hatch00') - .attr('patternUnits', 'userSpaceOnUse') - .attr('x', 0) - .attr('y', 0) - .attr('width', 10) - .attr('height', 10) - .call((f) => f.append('path') - .attr('d', 'M3,0 l7,7 l0,-2 l-5,-5 l-2,0 M0,7 l3,3 l2,0 l-5,-5 l0,2') - .attr('fill', colors[0][1]) - .attr('stroke', 'none')); + // TODO bar chart hatch effect + // parent.append('pattern') + // .attr('id', 'hatch00') + // .attr('patternUnits', 'userSpaceOnUse') + // .attr('x', 0) + // .attr('y', 0) + // .attr('width', 10) + // .attr('height', 10) + // .call((f) => f.append('path') + // .attr('d', 'M3,0 l7,7 l0,-2 l-5,-5 l-2,0 M0,7 l3,3 l2,0 l-5,-5 l0,2') + // .attr('fill', colors[1]) + // .attr('stroke', 'none')); } diff --git a/src/utils/addLabels.js b/src/utils/addLabels.js index e4b26f6..256efc2 100644 --- a/src/utils/addLabels.js +++ b/src/utils/addLabels.js @@ -1,7 +1,6 @@ const title = (parent, text) => { parent .append('text') - .style('font-family', 'xkcd') .style('font-size', '20') .style('font-weight', 'bold') .attr('x', '50%') @@ -13,7 +12,6 @@ const title = (parent, text) => { const xLabel = (parent, text) => { parent .append('text') - .style('font-family', 'xkcd') .style('font-size', 17) .attr('x', '50%') .attr('y', parent.attr('height') - 10) @@ -27,7 +25,6 @@ const yLabel = (parent, text) => { .attr('text-anchor', 'end') .attr('dy', '.75em') .attr('transform', 'rotate(-90)') - .style('font-family', 'xkcd') .style('font-size', 17) .text(text) .attr('y', 6) diff --git a/src/utils/colors.js b/src/utils/colors.js index 5c3d291..11efc39 100644 --- a/src/utils/colors.js +++ b/src/utils/colors.js @@ -1,6 +1,3 @@ // TODO: use: https://github.com/mrmrs/colors ? // use: https://gui.apex.sh/ -export default [ - ['#dd4528', '#28a3dd', '#f3db52', '#ed84b5', '#4ab74e', '#9179c0', '#8e6d5a', '#f19839', '#949494'], - ['#2CCCE4', '#F47373', '#DCE775', '#FF8A65', '#37D67A', '#D9E3F0'], -]; +export default ['#dd4528', '#28a3dd', '#f3db52', '#ed84b5', '#4ab74e', '#9179c0', '#8e6d5a', '#f19839', '#949494'];