import { CLASS, isValue, isUndefined, isDefined, ChartInternal, } from './chartinternal.js'; let c3_chart_fn; function Chart(config) { const $$ = this.internal = new ChartInternal(this); $$.loadConfig(config); $$.beforeInit(config); $$.init(); $$.afterInit(config); // bind "this" to nested API (function bindThis(fn, target, argThis) { Object.keys(fn).forEach((key) => { target[key] = fn[key].bind(argThis); if (Object.keys(fn[key]).length > 0) { bindThis(fn[key], target[key], argThis); } }); })(c3_chart_fn, this, this); } c3_chart_fn = Chart.prototype; c3_chart_fn.focus = function (targetIds) { let $$ = this.internal, candidates; targetIds = $$.mapToTargetIds(targetIds); candidates = $$.svg.selectAll($$.selectorTargets(targetIds.filter($$.isTargetToShow, $$))), this.revert(); this.defocus(); candidates.classed(CLASS.focused, true).classed(CLASS.defocused, false); if ($$.hasArcType()) { $$.expandArc(targetIds); } $$.toggleFocusLegend(targetIds, true); $$.focusedTargetIds = targetIds; $$.defocusedTargetIds = $$.defocusedTargetIds.filter((id) => { return targetIds.indexOf(id) < 0; }); }; c3_chart_fn.defocus = function (targetIds) { let $$ = this.internal, candidates; targetIds = $$.mapToTargetIds(targetIds); candidates = $$.svg.selectAll($$.selectorTargets(targetIds.filter($$.isTargetToShow, $$))), candidates.classed(CLASS.focused, false).classed(CLASS.defocused, true); if ($$.hasArcType()) { $$.unexpandArc(targetIds); } $$.toggleFocusLegend(targetIds, false); $$.focusedTargetIds = $$.focusedTargetIds.filter((id) => { return targetIds.indexOf(id) < 0; }); $$.defocusedTargetIds = targetIds; }; c3_chart_fn.revert = function (targetIds) { let $$ = this.internal, candidates; targetIds = $$.mapToTargetIds(targetIds); candidates = $$.svg.selectAll($$.selectorTargets(targetIds)); // should be for all targets candidates.classed(CLASS.focused, false).classed(CLASS.defocused, false); if ($$.hasArcType()) { $$.unexpandArc(targetIds); } if ($$.config.legend_show) { $$.showLegend(targetIds.filter($$.isLegendToShow.bind($$))); $$.legend.selectAll($$.selectorLegends(targetIds)) .filter(function () { return $$.d3.select(this).classed(CLASS.legendItemFocused); }) .classed(CLASS.legendItemFocused, false); } $$.focusedTargetIds = []; $$.defocusedTargetIds = []; }; c3_chart_fn.show = function (targetIds, options) { let $$ = this.internal, targets; targetIds = $$.mapToTargetIds(targetIds); options = options || {}; $$.removeHiddenTargetIds(targetIds); targets = $$.svg.selectAll($$.selectorTargets(targetIds)); targets.transition() .style('opacity', 1, 'important') .call($$.endall, () => { targets.style('opacity', null).style('opacity', 1); }); if (options.withLegend) { $$.showLegend(targetIds); } $$.redraw({ withUpdateOrgXDomain: true, withUpdateXDomain: true, withLegend: true }); }; c3_chart_fn.hide = function (targetIds, options) { let $$ = this.internal, targets; targetIds = $$.mapToTargetIds(targetIds); options = options || {}; $$.addHiddenTargetIds(targetIds); targets = $$.svg.selectAll($$.selectorTargets(targetIds)); targets.transition() .style('opacity', 0, 'important') .call($$.endall, () => { targets.style('opacity', null).style('opacity', 0); }); if (options.withLegend) { $$.hideLegend(targetIds); } $$.redraw({ withUpdateOrgXDomain: true, withUpdateXDomain: true, withLegend: true }); }; c3_chart_fn.toggle = function (targetIds, options) { let that = this, $$ = this.internal; $$.mapToTargetIds(targetIds).forEach((targetId) => { $$.isTargetToShow(targetId) ? that.hide(targetId, options) : that.show(targetId, options); }); }; c3_chart_fn.zoom = function (domain) { const $$ = this.internal; if (domain) { if ($$.isTimeSeries()) { domain = domain.map((x) => { return $$.parseDate(x); }); } $$.brush.extent(domain); $$.redraw({ withUpdateXDomain: true, withY: $$.config.zoom_rescale }); $$.config.zoom_onzoom.call(this, $$.x.orgDomain()); } return $$.brush.extent(); }; c3_chart_fn.zoom.enable = function (enabled) { const $$ = this.internal; $$.config.zoom_enabled = enabled; $$.updateAndRedraw(); }; c3_chart_fn.unzoom = function () { const $$ = this.internal; $$.brush.clear().update(); $$.redraw({ withUpdateXDomain: true }); }; c3_chart_fn.zoom.max = function (max) { let $$ = this.internal, config = $$.config, d3 = $$.d3; if (max === 0 || max) { config.zoom_x_max = d3.max([$$.orgXDomain[1], max]); } else { return config.zoom_x_max; } }; c3_chart_fn.zoom.min = function (min) { let $$ = this.internal, config = $$.config, d3 = $$.d3; if (min === 0 || min) { config.zoom_x_min = d3.min([$$.orgXDomain[0], min]); } else { return config.zoom_x_min; } }; c3_chart_fn.zoom.range = function (range) { if (arguments.length) { if (isDefined(range.max)) { this.domain.max(range.max); } if (isDefined(range.min)) { this.domain.min(range.min); } } else { return { max: this.domain.max(), min: this.domain.min(), }; } }; c3_chart_fn.load = function (args) { let $$ = this.internal, config = $$.config; // update xs if specified if (args.xs) { $$.addXs(args.xs); } // update names if exists if ('names' in args) { c3_chart_fn.data.names.bind(this)(args.names); } // update classes if exists if ('classes' in args) { Object.keys(args.classes).forEach((id) => { config.data_classes[id] = args.classes[id]; }); } // update categories if exists if ('categories' in args && $$.isCategorized()) { config.axis_x_categories = args.categories; } // update axes if exists if ('axes' in args) { Object.keys(args.axes).forEach((id) => { config.data_axes[id] = args.axes[id]; }); } // update colors if exists if ('colors' in args) { Object.keys(args.colors).forEach((id) => { config.data_colors[id] = args.colors[id]; }); } // use cache if exists if ('cacheIds' in args && $$.hasCaches(args.cacheIds)) { $$.load($$.getCaches(args.cacheIds), args.done); return; } // unload if needed if ('unload' in args) { // TODO: do not unload if target will load (included in url/rows/columns) $$.unload($$.mapToTargetIds((typeof args.unload === 'boolean' && args.unload) ? null : args.unload), () => { $$.loadFromArgs(args); }); } else { $$.loadFromArgs(args); } }; c3_chart_fn.unload = function (args) { const $$ = this.internal; args = args || {}; if (args instanceof Array) { args = { ids: args }; } else if (typeof args === 'string') { args = { ids: [args] }; } $$.unload($$.mapToTargetIds(args.ids), () => { $$.redraw({ withUpdateOrgXDomain: true, withUpdateXDomain: true, withLegend: true }); if (args.done) { args.done(); } }); }; c3_chart_fn.flow = function (args) { let $$ = this.internal, targets, data, notfoundIds = [], orgDataCount = $$.getMaxDataCount(), dataCount, domain, baseTarget, baseValue, length = 0, tail = 0, diff, to; if (args.json) { data = $$.convertJsonToData(args.json, args.keys); } else if (args.rows) { data = $$.convertRowsToData(args.rows); } else if (args.columns) { data = $$.convertColumnsToData(args.columns); } else { return; } targets = $$.convertDataToTargets(data, true); // Update/Add data $$.data.targets.forEach((t) => { let found = false, i, j; for (i = 0; i < targets.length; i++) { if (t.id === targets[i].id) { found = true; if (t.values[t.values.length - 1]) { tail = t.values[t.values.length - 1].index + 1; } length = targets[i].values.length; for (j = 0; j < length; j++) { targets[i].values[j].index = tail + j; if (!$$.isTimeSeries()) { targets[i].values[j].x = tail + j; } } t.values = t.values.concat(targets[i].values); targets.splice(i, 1); break; } } if (!found) { notfoundIds.push(t.id); } }); // Append null for not found targets $$.data.targets.forEach((t) => { let i, j; for (i = 0; i < notfoundIds.length; i++) { if (t.id === notfoundIds[i]) { tail = t.values[t.values.length - 1].index + 1; for (j = 0; j < length; j++) { t.values.push({ id: t.id, index: tail + j, x: $$.isTimeSeries() ? $$.getOtherTargetX(tail + j) : tail + j, value: null, }); } } } }); // Generate null values for new target if ($$.data.targets.length) { targets.forEach((t) => { let i, missing = []; for (i = $$.data.targets[0].values[0].index; i < tail; i++) { missing.push({ id: t.id, index: i, x: $$.isTimeSeries() ? $$.getOtherTargetX(i) : i, value: null, }); } t.values.forEach((v) => { v.index += tail; if (!$$.isTimeSeries()) { v.x += tail; } }); t.values = missing.concat(t.values); }); } $$.data.targets = $$.data.targets.concat(targets); // add remained // check data count because behavior needs to change when it's only one dataCount = $$.getMaxDataCount(); baseTarget = $$.data.targets[0]; baseValue = baseTarget.values[0]; // Update length to flow if needed if (isDefined(args.to)) { length = 0; to = $$.isTimeSeries() ? $$.parseDate(args.to) : args.to; baseTarget.values.forEach((v) => { if (v.x < to) { length++; } }); } else if (isDefined(args.length)) { length = args.length; } // If only one data, update the domain to flow from left edge of the chart if (!orgDataCount) { if ($$.isTimeSeries()) { if (baseTarget.values.length > 1) { diff = baseTarget.values[baseTarget.values.length - 1].x - baseValue.x; } else { diff = baseValue.x - $$.getXDomain($$.data.targets)[0]; } } else { diff = 1; } domain = [baseValue.x - diff, baseValue.x]; $$.updateXDomain(null, true, true, false, domain); } else if (orgDataCount === 1) { if ($$.isTimeSeries()) { diff = (baseTarget.values[baseTarget.values.length - 1].x - baseValue.x) / 2; domain = [new Date(+baseValue.x - diff), new Date(+baseValue.x + diff)]; $$.updateXDomain(null, true, true, false, domain); } } // Set targets $$.updateTargets($$.data.targets); // Redraw with new targets $$.redraw({ flow: { index: baseValue.index, length, duration: isValue(args.duration) ? args.duration : $$.config.transition_duration, done: args.done, orgDataCount, }, withLegend: true, withTransition: orgDataCount > 1, withTrimXDomain: false, withUpdateXAxis: true, }); }; c3_chart_fn.selected = function (targetId) { let $$ = this.internal, d3 = $$.d3; return d3.merge( $$.main.selectAll('.' + CLASS.shapes + $$.getTargetSelectorSuffix(targetId)).selectAll('.' + CLASS.shape) .filter(function () { return d3.select(this).classed(CLASS.SELECTED); }) .map((d) => { return d.map((d) => { const data = d.__data__; return data.data ? data.data : data; }); }) ); }; c3_chart_fn.select = function (ids, indices, resetOther) { let $$ = this.internal, d3 = $$.d3, config = $$.config; if (!config.data_selection_enabled) { return; } $$.main.selectAll('.' + CLASS.shapes).selectAll('.' + CLASS.shape).each(function (d, i) { let shape = d3.select(this), id = d.data ? d.data.id : d.id, toggle = $$.getToggle(this, d).bind($$), isTargetId = config.data_selection_grouped || !ids || ids.indexOf(id) >= 0, isTargetIndex = !indices || indices.indexOf(i) >= 0, isSelected = shape.classed(CLASS.SELECTED); // line/area selection not supported yet if (shape.classed(CLASS.line) || shape.classed(CLASS.area)) { return; } if (isTargetId && isTargetIndex) { if (config.data_selection_isselectable(d) && !isSelected) { toggle(true, shape.classed(CLASS.SELECTED, true), d, i); } } else if (isDefined(resetOther) && resetOther) { if (isSelected) { toggle(false, shape.classed(CLASS.SELECTED, false), d, i); } } }); }; c3_chart_fn.unselect = function (ids, indices) { let $$ = this.internal, d3 = $$.d3, config = $$.config; if (!config.data_selection_enabled) { return; } $$.main.selectAll('.' + CLASS.shapes).selectAll('.' + CLASS.shape).each(function (d, i) { let shape = d3.select(this), id = d.data ? d.data.id : d.id, toggle = $$.getToggle(this, d).bind($$), isTargetId = config.data_selection_grouped || !ids || ids.indexOf(id) >= 0, isTargetIndex = !indices || indices.indexOf(i) >= 0, isSelected = shape.classed(CLASS.SELECTED); // line/area selection not supported yet if (shape.classed(CLASS.line) || shape.classed(CLASS.area)) { return; } if (isTargetId && isTargetIndex) { if (config.data_selection_isselectable(d)) { if (isSelected) { toggle(false, shape.classed(CLASS.SELECTED, false), d, i); } } } }); }; c3_chart_fn.transform = function (type, targetIds) { let $$ = this.internal, options = ['pie', 'donut'].indexOf(type) >= 0 ? { withTransform: true } : null; $$.transformTo(targetIds, type, options); }; c3_chart_fn.groups = function (groups) { let $$ = this.internal, config = $$.config; if (isUndefined(groups)) { return config.data_groups; } config.data_groups = groups; $$.redraw(); return config.data_groups; }; c3_chart_fn.xgrids = function (grids) { let $$ = this.internal, config = $$.config; if (!grids) { return config.grid_x_lines; } config.grid_x_lines = grids; $$.redrawWithoutRescale(); return config.grid_x_lines; }; c3_chart_fn.xgrids.add = function (grids) { const $$ = this.internal; return this.xgrids($$.config.grid_x_lines.concat(grids ? grids : [])); }; c3_chart_fn.xgrids.remove = function (params) { // TODO: multiple const $$ = this.internal; $$.removeGridLines(params, true); }; c3_chart_fn.ygrids = function (grids) { let $$ = this.internal, config = $$.config; if (!grids) { return config.grid_y_lines; } config.grid_y_lines = grids; $$.redrawWithoutRescale(); return config.grid_y_lines; }; c3_chart_fn.ygrids.add = function (grids) { const $$ = this.internal; return this.ygrids($$.config.grid_y_lines.concat(grids ? grids : [])); }; c3_chart_fn.ygrids.remove = function (params) { // TODO: multiple const $$ = this.internal; $$.removeGridLines(params, false); }; c3_chart_fn.regions = function (regions) { let $$ = this.internal, config = $$.config; if (!regions) { return config.regions; } config.regions = regions; $$.redrawWithoutRescale(); return config.regions; }; c3_chart_fn.regions.add = function (regions) { let $$ = this.internal, config = $$.config; if (!regions) { return config.regions; } config.regions = config.regions.concat(regions); $$.redrawWithoutRescale(); return config.regions; }; c3_chart_fn.regions.remove = function (options) { let $$ = this.internal, config = $$.config, duration, classes, regions; options = options || {}; duration = $$.getOption(options, 'duration', config.transition_duration); classes = $$.getOption(options, 'classes', [CLASS.region]); regions = $$.main.select('.' + CLASS.regions).selectAll(classes.map((c) => { return '.' + c; })); (duration ? regions.transition().duration(duration) : regions) .style('opacity', 0) .remove(); config.regions = config.regions.filter((region) => { let found = false; if (!region.class) { return true; } region.class.split(' ').forEach((c) => { if (classes.indexOf(c) >= 0) { found = true; } }); return !found; }); return config.regions; }; c3_chart_fn.data = function (targetIds) { const targets = this.internal.data.targets; return typeof targetIds === 'undefined' ? targets : targets.filter((t) => { return [].concat(targetIds).indexOf(t.id) >= 0; }); }; c3_chart_fn.data.shown = function (targetIds) { return this.internal.filterTargetsToShow(this.data(targetIds)); }; c3_chart_fn.data.values = function (targetId) { let targets, values = null; if (targetId) { targets = this.data(targetId); values = targets[0] ? targets[0].values.map((d) => { return d.value; }) : null; } return values; }; c3_chart_fn.data.names = function (names) { this.internal.clearLegendItemTextBoxCache(); return this.internal.updateDataAttributes('names', names); }; c3_chart_fn.data.colors = function (colors) { return this.internal.updateDataAttributes('colors', colors); }; c3_chart_fn.data.axes = function (axes) { return this.internal.updateDataAttributes('axes', axes); }; c3_chart_fn.category = function (i, category) { let $$ = this.internal, config = $$.config; if (arguments.length > 1) { config.axis_x_categories[i] = category; $$.redraw(); } return config.axis_x_categories[i]; }; c3_chart_fn.categories = function (categories) { let $$ = this.internal, config = $$.config; if (!arguments.length) { return config.axis_x_categories; } config.axis_x_categories = categories; $$.redraw(); return config.axis_x_categories; }; // TODO: fix c3_chart_fn.color = function (id) { const $$ = this.internal; return $$.color(id); // more patterns }; c3_chart_fn.x = function (x) { const $$ = this.internal; if (arguments.length) { $$.updateTargetX($$.data.targets, x); $$.redraw({ withUpdateOrgXDomain: true, withUpdateXDomain: true }); } return $$.data.xs; }; c3_chart_fn.xs = function (xs) { const $$ = this.internal; if (arguments.length) { $$.updateTargetXs($$.data.targets, xs); $$.redraw({ withUpdateOrgXDomain: true, withUpdateXDomain: true }); } return $$.data.xs; }; c3_chart_fn.axis = function () {}; c3_chart_fn.axis.labels = function (labels) { const $$ = this.internal; if (arguments.length) { Object.keys(labels).forEach((axisId) => { $$.axis.setLabelText(axisId, labels[axisId]); }); $$.axis.updateLabels(); } // TODO: return some values? }; c3_chart_fn.axis.max = function (max) { let $$ = this.internal, config = $$.config; if (arguments.length) { if (typeof max === 'object') { if (isValue(max.x)) { config.axis_x_max = max.x; } if (isValue(max.y)) { config.axis_y_max = max.y; } if (isValue(max.y2)) { config.axis_y2_max = max.y2; } } else { config.axis_y_max = config.axis_y2_max = max; } $$.redraw({ withUpdateOrgXDomain: true, withUpdateXDomain: true }); } else { return { x: config.axis_x_max, y: config.axis_y_max, y2: config.axis_y2_max, }; } }; c3_chart_fn.axis.min = function (min) { let $$ = this.internal, config = $$.config; if (arguments.length) { if (typeof min === 'object') { if (isValue(min.x)) { config.axis_x_min = min.x; } if (isValue(min.y)) { config.axis_y_min = min.y; } if (isValue(min.y2)) { config.axis_y2_min = min.y2; } } else { config.axis_y_min = config.axis_y2_min = min; } $$.redraw({ withUpdateOrgXDomain: true, withUpdateXDomain: true }); } else { return { x: config.axis_x_min, y: config.axis_y_min, y2: config.axis_y2_min, }; } }; c3_chart_fn.axis.range = function (range) { if (arguments.length) { if (isDefined(range.max)) { this.axis.max(range.max); } if (isDefined(range.min)) { this.axis.min(range.min); } } else { return { max: this.axis.max(), min: this.axis.min(), }; } }; c3_chart_fn.legend = function () {}; c3_chart_fn.legend.show = function (targetIds) { const $$ = this.internal; $$.showLegend($$.mapToTargetIds(targetIds)); $$.updateAndRedraw({ withLegend: true }); }; c3_chart_fn.legend.hide = function (targetIds) { const $$ = this.internal; $$.hideLegend($$.mapToTargetIds(targetIds)); $$.updateAndRedraw({ withLegend: true }); }; c3_chart_fn.resize = function (size) { let $$ = this.internal, config = $$.config; config.size_width = size ? size.width : null; config.size_height = size ? size.height : null; this.flush(); }; c3_chart_fn.flush = function () { const $$ = this.internal; $$.updateAndRedraw({ withLegend: true, withTransition: false, withTransitionForTransform: false }); }; c3_chart_fn.destroy = function () { const $$ = this.internal; window.clearInterval($$.intervalForObserveInserted); if ($$.resizeTimeout !== undefined) { window.clearTimeout($$.resizeTimeout); } if (window.detachEvent) { window.detachEvent('onresize', $$.resizeFunction); } else if (window.removeEventListener) { window.removeEventListener('resize', $$.resizeFunction); } else { const wrapper = window.onresize; // check if no one else removed our wrapper and remove our resizeFunction from it if (wrapper && wrapper.add && wrapper.remove) { wrapper.remove($$.resizeFunction); } } $$.selectChart.classed('c3', false).html(''); // MEMO: this is needed because the reference of some elements will not be released, then memory leak will happen. Object.keys($$).forEach((key) => { $$[key] = null; }); return null; }; c3_chart_fn.tooltip = function () {}; c3_chart_fn.tooltip.show = function (args) { let $$ = this.internal, index, mouse; // determine mouse position on the chart if (args.mouse) { mouse = args.mouse; } // determine focus data if (args.data) { if ($$.isMultipleX()) { // if multiple xs, target point will be determined by mouse mouse = [$$.x(args.data.x), $$.getYScale(args.data.id)(args.data.value)]; index = null; } else { // TODO: when tooltip_grouped = false index = isValue(args.data.index) ? args.data.index : $$.getIndexByX(args.data.x); } } else if (typeof args.x !== 'undefined') { index = $$.getIndexByX(args.x); } else if (typeof args.index !== 'undefined') { index = args.index; } // emulate mouse events to show $$.dispatchEvent('mouseover', index, mouse); $$.dispatchEvent('mousemove', index, mouse); $$.config.tooltip_onshow.call($$, args.data); }; c3_chart_fn.tooltip.hide = function () { // TODO: get target data by checking the state of focus this.internal.dispatchEvent('mouseout', 0); this.internal.config.tooltip_onhide.call(this); }; export { Chart }; export default Chart;