diff --git a/package.json b/package.json index 1fa3789..98d8a48 100755 --- a/package.json +++ b/package.json @@ -1,12 +1,13 @@ { "name": "@chartwerk/bar-pod", - "version": "0.6.0-beta", + "version": "0.6.1", "description": "Chartwerk bar pod", "main": "dist/index.js", "scripts": { "build": "webpack --config build/webpack.prod.conf.js && webpack --config build/webpack.dev.conf.js", "dev": "webpack --watch --config build/webpack.dev.conf.js", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "update-core": "yarn up @chartwerk/core && yarn up @chartwerk/core@latest" }, "repository": { "type": "git", @@ -15,7 +16,7 @@ "author": "CorpGlory", "license": "Apache-2.0", "dependencies": { - "@chartwerk/core": "0.6.3" + "@chartwerk/core": "latest" }, "devDependencies": { "css-loader": "^3.4.2", diff --git a/src/index.ts b/src/index.ts index 9faf806..c977792 100755 --- a/src/index.ts +++ b/src/index.ts @@ -1,29 +1,29 @@ -import { ChartwerkPod, VueChartwerkPodMixin, TickOrientation, TimeFormat, AxisFormat } from '@chartwerk/core'; +import { ChartwerkPod, VueChartwerkPodMixin, TimeFormat, AxisFormat } from '@chartwerk/core'; -import { BarTimeSerie, BarOptions, RowValues } from './types'; +import { BarConfig } from './models/bar_options'; +import { BarSeries } from './models/bar_series'; + +import { BarSerie, BarOptions, RowValues } from './types'; import { findClosest } from './utils'; import * as d3 from 'd3'; import * as _ from 'lodash'; -const DEFAULT_BAR_OPTIONS: BarOptions = { - renderBarLabels: false, - stacked: false, - matching: false -} - -export class ChartwerkBarPod extends ChartwerkPod { +export class ChartwerkBarPod extends ChartwerkPod { barYScale: null | d3.ScaleLinear = null; _seriesDataForRendring = []; + series: BarSeries; + options: BarConfig; - constructor(el: HTMLElement, _series: BarTimeSerie[] = [], _options: BarOptions = {}) { + constructor(el: HTMLElement, _series: BarSerie[] = [], _options: BarOptions = {}) { super(el, _series, _options); - _.defaults(this.options, DEFAULT_BAR_OPTIONS); + this.series = new BarSeries(_series); + this.options = new BarConfig(_options); } protected renderMetrics(): void { - if(this.series.length === 0 || this.series[0].datapoints.length === 0) { + if(!this.series.isSeriesAvailable) { this.renderNoDataPointsMessage(); return; } @@ -34,15 +34,15 @@ export class ChartwerkBarPod extends ChartwerkPod { } get isMatchingDisabled(): boolean { - return this.options.matching === false || this.seriesUniqKeys.length === 0; + return this.options.barOptions.matching === false || this.seriesUniqKeys.length === 0; } setSeriesDataForRendering(): void { if(this.isMatchingDisabled) { - this._seriesDataForRendring = this.getZippedDataForRender(this.visibleSeries); + this._seriesDataForRendring = this.getZippedDataForRender(this.series.visibleSeries); } else { const matchedSeries = this.seriesForMatching.map( - (series: BarTimeSerie[], idx: number) => this.getZippedDataForRender(series) + (series: BarSerie[], idx: number) => this.getZippedDataForRender(series) ); this._seriesDataForRendring = this.mergeMacthedSeriesAndSort(matchedSeries); } @@ -77,7 +77,7 @@ export class ChartwerkBarPod extends ChartwerkPod { .on('contextmenu', this.contextMenu.bind(this)); // render bar annotations, its all hardcoded - if(_.isEmpty(this.options.annotations)) { + if(_.isEmpty(this.options.barOptions.annotations)) { return; } // find all series for single matchedKey @@ -86,7 +86,7 @@ export class ChartwerkBarPod extends ChartwerkPod { const key = matchedKeys[0]; const lastRect = _.last(container.selectAll('rect')?.nodes()); - const annotation = _.find(this.options.annotations, a => a.key === key); + const annotation = _.find(this.options.barOptions.annotations, a => a.key === key); if(!lastRect || !key || !annotation) { return; } @@ -97,7 +97,7 @@ export class ChartwerkBarPod extends ChartwerkPod { .attr('d', () => { const x = Math.ceil(_.toNumber(rectSelection.attr('x'))); const y = Math.ceil(_.toNumber(rectSelection.attr('y'))); - const options = { max: this.options.maxAnnotationSize, min: this.options.minAnnotationSize }; + const options = { max: this.options.barOptions.maxAnnotationSize, min: this.options.barOptions.minAnnotationSize }; return this.getTrianglePath(x, y, this.barWidth, options); }) .attr('fill', annotation.color); @@ -132,10 +132,10 @@ export class ChartwerkBarPod extends ChartwerkPod { } getBarOpacity(rowValues: RowValues): number { - if(this.options.opacityFormatter === undefined) { + if(this.options.barOptions.opacityFormatter === undefined) { return 1; } - return this.options.opacityFormatter(rowValues); + return this.options.barOptions.opacityFormatter(rowValues); } mergeMacthedSeriesAndSort(matchedSeries: any[]) { @@ -155,27 +155,27 @@ export class ChartwerkBarPod extends ChartwerkPod { } get seriesUniqKeys(): string[] { - if(this.visibleSeries.length === 0) { + if(!this.series.isSeriesAvailable) { return []; } - const keys = this.visibleSeries.map(serie => serie.matchedKey); + const keys = this.series.visibleSeries.map(serie => serie.matchedKey); const uniqKeys = _.uniq(keys); const filteredKeys = _.filter(uniqKeys, key => key !== undefined); return filteredKeys; } - get seriesForMatching(): BarTimeSerie[][] { + get seriesForMatching(): BarSerie[][] { if(this.seriesUniqKeys.length === 0) { - return [this.visibleSeries]; + return [this.series.visibleSeries]; } const seriesList = this.seriesUniqKeys.map(key => { - const seriesWithKey = _.filter(this.visibleSeries, serie => serie.matchedKey === key); + const seriesWithKey = _.filter(this.series.visibleSeries, serie => serie.matchedKey === key); return seriesWithKey; }); return seriesList; } - getZippedDataForRender(series: BarTimeSerie[]): RowValues[] { + getZippedDataForRender(series: BarSerie[]): RowValues[] { if(series.length === 0) { throw new Error('There is no visible series'); } @@ -185,7 +185,7 @@ export class ChartwerkBarPod extends ChartwerkPod { const additionalValuesColumns = _.map(series, serie => _.map(serie.datapoints, row => row[2] !== undefined ? row[2] : null)); const zippedAdditionalValuesColumn = _.zip(...additionalValuesColumns); const zippedValuesColumn = _.zip(...valuesColumns); - const colors = _.map(series, serie => this.getBarColor(serie)); + const colors = _.map(series, serie => serie.color); const tagrets = _.map(series, serie => serie.target); const zippedData = _.zip(keysColumn, zippedValuesColumn, zippedAdditionalValuesColumn, tagrets); const data = _.map(zippedData, row => { return { key: row[0], values: row[1], additionalValues: row[2], colors, serieTarget: tagrets } }); @@ -195,7 +195,7 @@ export class ChartwerkBarPod extends ChartwerkPod { public renderSharedCrosshair(values: { x?: number, y?: number }): void { this.crosshair.style('display', null); - const x = this.xScale(values.x); + const x = this.state.xScale(values.x); this.crosshair.select('#crosshair-line-x') .attr('x1', x) .attr('x2', x); @@ -209,59 +209,40 @@ export class ChartwerkBarPod extends ChartwerkPod { // TODO: mouse move work bad with matching const event = d3.mouse(this.chartContainer.node()); const eventX = event[0]; - if(this.isOutOfChart() === true) { - this.crosshair.style('display', 'none'); - return; - } this.crosshair.select('#crosshair-line-x') .attr('x1', eventX) .attr('x2', eventX); const series = this.getSeriesPointFromMousePosition(eventX); - if(this.options.eventsCallbacks !== undefined && this.options.eventsCallbacks.mouseMove !== undefined) { - this.options.eventsCallbacks.mouseMove({ - x: d3.event.pageX, - y: d3.event.pageY, - time: this.xScale.invert(eventX), - series, - chartX: eventX, - chartWidth: this.width - }); - } else { - console.log('mouse move, but there is no callback'); - } + this.options.callbackMouseMove({ + x: d3.event.pageX, + y: d3.event.pageY, + time: this.state.xScale.invert(eventX), + series, + chartX: eventX, + chartWidth: this.width + }); } getSeriesPointFromMousePosition(eventX: number): any[] | undefined { - if(this.series === undefined || this.series.length === 0) { + if(!this.series.isSeriesAvailable) { return undefined; } - const mousePoisitionKey = Math.ceil(this.xScale.invert(eventX)); + const mousePoisitionKey = Math.ceil(this.state.xScale.invert(eventX)); const keys = _.map(this._seriesDataForRendring, el => el.key); const idx = findClosest(keys, mousePoisitionKey); return this._seriesDataForRendring[idx]; } - getBarColor(serie: any) { - if(serie.color === undefined) { - return this.getSerieColor(0); - } - return serie.color; - } - onMouseOver(): void { this.crosshair.style('display', null); this.crosshair.raise(); } onMouseOut(): void { - if(this.options.eventsCallbacks !== undefined && this.options.eventsCallbacks.mouseOut !== undefined) { - this.options.eventsCallbacks.mouseOut(); - } else { - console.log('mouse out, but there is no callback'); - } + this.options.callbackMouseOut(); this.crosshair.style('display', 'none'); } @@ -272,30 +253,25 @@ export class ChartwerkBarPod extends ChartwerkPod { const event = d3.mouse(this.chartContainer.node()); const eventX = event[0]; const series = this.getSeriesPointFromMousePosition(eventX); - - if(this.options.eventsCallbacks !== undefined && this.options.eventsCallbacks.contextMenu !== undefined) { - this.options.eventsCallbacks.contextMenu({ - x: d3.event.pageX, - y: d3.event.pageY, - time: this.xScale.invert(eventX), - series, - chartX: eventX - }); - } else { - console.log('contextmenu, but there is no callback'); - } + this.options.callbackContextMenu({ + pageX: d3.event.pageX, + pageY: d3.event.pageY, + xVal: this.state.xScale.invert(eventX), + series, + chartX: eventX + }); } get barWidth(): number { // TODO: here we use first value + timeInterval as bar width. It is not a good idea - const xAxisStartValue = _.first(this.series[0].datapoints)[0]; - let width = this.xScale(xAxisStartValue + this.timeInterval) / 2; - if(this.options.barWidth !== undefined) { + const xAxisStartValue = _.first(this.series.visibleSeries[0].datapoints)[0]; + let width = this.state.xScale(xAxisStartValue + this.timeInterval) / 2; + if(this.options.barOptions.barWidth !== undefined) { // barWidth now has axis-x dimension - width = this.xScale(this.state.getMinValueX() + this.options.barWidth); + width = this.state.xScale(this.state.getMinValueX() + this.options.barOptions.barWidth); } - let rectColumns = this.visibleSeries.length; - if(this.options.stacked === true) { + let rectColumns = this.series.visibleSeries.length; + if(this.options.barOptions.stacked === true) { rectColumns = 1; } return this.updateBarWidthWithBorders(width / rectColumns); @@ -303,11 +279,11 @@ export class ChartwerkBarPod extends ChartwerkPod { updateBarWidthWithBorders(width: number): number { let barWidth = width; - if(this.options.minBarWidth !== undefined) { - barWidth = Math.max(barWidth, this.options.minBarWidth); + if(this.options.barOptions.minBarWidth !== undefined) { + barWidth = Math.max(barWidth, this.options.barOptions.minBarWidth); } - if(this.options.maxBarWidth !== undefined) { - barWidth = Math.min(barWidth, this.options.maxBarWidth); + if(this.options.barOptions.maxBarWidth !== undefined) { + barWidth = Math.min(barWidth, this.options.barOptions.maxBarWidth); } return barWidth; } @@ -320,8 +296,8 @@ export class ChartwerkBarPod extends ChartwerkPod { } getBarPositionX(key: number, idx: number): number { - let xPosition: number = this.xScale(key); - if(this.options.stacked === false) { + let xPosition: number = this.state.xScale(key); + if(this.options.barOptions.stacked === false) { xPosition += idx * this.barWidth; } return xPosition; @@ -329,7 +305,7 @@ export class ChartwerkBarPod extends ChartwerkPod { getBarPositionY(val: number, idx: number, values: number[]): number { let yPosition: number = this.barYScale(Math.max(val, 0)); - if(this.options.stacked === true) { + if(this.options.barOptions.stacked === true) { const previousBarsHeight = _.sum( _.map(_.range(idx), i => this.getBarHeight(values[i])) ); @@ -354,15 +330,15 @@ export class ChartwerkBarPod extends ChartwerkPod { } getYMaxValue(): number | undefined { - if(this.series === undefined || this.series.length === 0 || this.series[0].datapoints.length === 0) { + if(!this.series.isSeriesAvailable) { return undefined; } - if(this.options.axis.y !== undefined && this.options.axis.y.range !== undefined) { + if(this.options.axis.y.range) { return _.max(this.options.axis.y.range); } let maxValue: number; - if(this.options.stacked === true) { - if(this.options.matching === true && this.seriesUniqKeys.length > 0) { + if(this.options.barOptions.stacked === true) { + if(this.options.barOptions.matching === true && this.seriesUniqKeys.length > 0) { const maxValues = this.seriesForMatching.map(series => { const valuesColumns = _.map(series, serie => _.map(serie.datapoints, row => row[1])); const zippedValuesColumn = _.zip(...valuesColumns); @@ -370,14 +346,13 @@ export class ChartwerkBarPod extends ChartwerkPod { }); return _.max(maxValues); } else { - const valuesColumns = _.map(this.visibleSeries, serie => _.map(serie.datapoints, row => row[1])); + const valuesColumns = _.map(this.series.visibleSeries, serie => _.map(serie.datapoints, row => row[1])); const zippedValuesColumn = _.zip(...valuesColumns); maxValue = _.max(_.map(zippedValuesColumn, row => _.sum(row))); } } else { - console.log('else') maxValue = _.max( - this.visibleSeries.map( + this.series.visibleSeries.map( serie => _.maxBy(serie.datapoints, dp => dp[1])[0] ) ); @@ -409,4 +384,4 @@ export const VueChartwerkBarChartObject = { } }; -export { BarTimeSerie, BarOptions, TickOrientation, TimeFormat, AxisFormat }; +export { BarSerie, BarOptions, TimeFormat, AxisFormat }; diff --git a/src/models/bar_options.ts b/src/models/bar_options.ts new file mode 100644 index 0000000..8de5df3 --- /dev/null +++ b/src/models/bar_options.ts @@ -0,0 +1,46 @@ +import { CoreOptions } from '@chartwerk/core'; +import { BarOptions, BarAdditionalOptions } from '../types'; + +import * as _ from 'lodash'; + +const BAR_SERIE_DEFAULTS = { + renderBarLabels: false, + stacked: false, + barWidth: undefined, + maxBarWidth: undefined, + minBarWidth: undefined, + maxAnnotationSize: undefined, + minAnnotationSize: undefined, + matching: false, + opacityFormatter: undefined, + annotations: [], +}; + +export class BarConfig extends CoreOptions { + + constructor(options: BarOptions) { + super(options, BAR_SERIE_DEFAULTS); + } + + get barOptions(): BarAdditionalOptions { + return { + renderBarLabels: this._options.renderBarLabels, + stacked: this._options.stacked, + barWidth: this._options.barWidth, + maxBarWidth: this._options.maxBarWidth, + minBarWidth: this._options.minBarWidth, + maxAnnotationSize: this._options.maxAnnotationSize, + minAnnotationSize: this._options.minAnnotationSize, + matching: this._options.matching, + opacityFormatter: this._options.opacityFormatter, + annotations: this._options.annotations, + } + } + + // event callbacks + callbackContextMenu(data: any): void { + if(_.has(this._options.eventsCallbacks, 'contextMenu')) { + this._options.eventsCallbacks.contextMenu(data); + } + } +} diff --git a/src/models/bar_series.ts b/src/models/bar_series.ts new file mode 100644 index 0000000..e9624af --- /dev/null +++ b/src/models/bar_series.ts @@ -0,0 +1,15 @@ +import { CoreSeries } from '@chartwerk/core'; +import { BarSerie } from '../types'; + + +const BAR_SERIE_DEFAULTS = { + matchedKey: undefined, + colorFormatter: undefined +}; + +export class BarSeries extends CoreSeries { + + constructor(series: BarSerie[]) { + super(series, BAR_SERIE_DEFAULTS); + } +} diff --git a/src/types.ts b/src/types.ts index 389c28e..b1b400a 100755 --- a/src/types.ts +++ b/src/types.ts @@ -1,27 +1,30 @@ -import { TimeSerie, Options } from '@chartwerk/core'; +import { Serie, Options } from '@chartwerk/core'; export type BarSerieParams = { matchedKey: string; - colorFormatter: (serie: BarTimeSerie) => string; + colorFormatter: (serie: BarSerie) => string; } -export type BarTimeSerie = TimeSerie & Partial; -export type BarOptionsParams = { - renderBarLabels: boolean; - stacked: boolean; - barWidth: number; // width in x axis unit - maxBarWidth: number; // in px - minBarWidth: number; // in px - maxAnnotationSize: number; // in px TODO: move to annotaions - minAnnotationSize: number; // in px - matching: boolean; - opacityFormatter: (data: RowValues) => number; - annotations: { +export type BarSerie = Serie & Partial; +export type BarAdditionalOptions = { + renderBarLabels?: boolean; + stacked?: boolean; + barWidth?: number; // width in x axis unit + maxBarWidth?: number; // in px + minBarWidth?: number; // in px + maxAnnotationSize?: number; // in px TODO: move to annotaions + minAnnotationSize?: number; // in px + matching?: boolean; + opacityFormatter?: (data: RowValues) => number; + annotations?: { key: string, // matchedKey from series // TODO: add enum with "triangle" option color: string, }[]; + eventsCallbacks?: { + contextMenu?: (data: any) => void; + } } -export type BarOptions = Options & Partial; +export type BarOptions = Options & Partial; export type RowValues = { key: number, values: number[], diff --git a/yarn.lock b/yarn.lock index 0df7084..23765a5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9,7 +9,7 @@ __metadata: version: 0.0.0-use.local resolution: "@chartwerk/bar-pod@workspace:." dependencies: - "@chartwerk/core": 0.6.3 + "@chartwerk/core": latest css-loader: ^3.4.2 style-loader: ^1.1.3 ts-loader: ^6.2.1 @@ -19,13 +19,13 @@ __metadata: languageName: unknown linkType: soft -"@chartwerk/core@npm:0.6.3": - version: 0.6.3 - resolution: "@chartwerk/core@npm:0.6.3" +"@chartwerk/core@npm:latest": + version: 0.6.5 + resolution: "@chartwerk/core@npm:0.6.5" dependencies: d3: ^5.7.2 lodash: ^4.14.149 - checksum: bb804b1a2339fc19857e5caa07d16ed78fb1b0739878d3f7488e9f8661667ed78ada3a63afd15206bd4210746b50916b253e98f077451cc8d899728ecf08ba50 + checksum: 2757ae8be04b84c3624bd807a437d6bf3c67fef0808e3192f0a97ba819627f3b28821628cfefdfabe1951e8b7d0d45d1b9f6ade13cdc4a5bca5e539fc70850df languageName: node linkType: hard