Browse Source

Fix zoom and brush for d3.v4

pull/2246/head
Masayuki Tanaka 7 years ago
parent
commit
c40f9bf482
  1. 2
      htdocs/samples/zoom.html
  2. 44
      src/core.js
  3. 6
      src/data.js
  4. 5
      src/domain.js
  5. 3
      src/drag.js
  6. 44
      src/interaction.js
  7. 3
      src/scale.js
  8. 67
      src/subchart.js
  9. 54
      src/zoom.js

2
htdocs/samples/zoom.html

@ -7,7 +7,7 @@
<button onclick="load()">Load</button>
<div id="chart2"></div>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="http://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<script src="/js/c3.js"></script>
<script>

44
src/core.js

@ -168,10 +168,6 @@ c3_chart_internal_fn.initWithData = function (data) {
$$.axis = new Axis($$);
if ($$.initPie) { $$.initPie(); }
if ($$.initBrush) { $$.initBrush(); }
if ($$.initZoom) { $$.initZoom(); }
if (!config.bindto) {
$$.selectChart = d3.selectAll([]);
}
@ -224,10 +220,6 @@ c3_chart_internal_fn.initWithData = function (data) {
// Save original x domain for zoom update
$$.orgXDomain = $$.x.domain();
// Set initialized scales to brush and zoom
if ($$.brush) { $$.brush.scale($$.subX); }
if (config.zoom_enabled) { $$.zoom.scale($$.x); }
/*-- Basic Elements --*/
// Define svgs
@ -252,10 +244,16 @@ c3_chart_internal_fn.initWithData = function (data) {
// Define regions
main = $$.main = $$.svg.append("g").attr("transform", $$.getTranslate('main'));
if ($$.initPie) { $$.initPie(); }
if ($$.initSubchart) { $$.initSubchart(); }
if ($$.initTooltip) { $$.initTooltip(); }
if ($$.initLegend) { $$.initLegend(); }
if ($$.initTitle) { $$.initTitle(); }
if ($$.initZoom) { $$.initZoom(); }
// Update extent based on size and scale
// TODO: currently this must be called after initLegend because of update of sizes, but it should be done in initSubchart.
if ($$.initSubchartBrush) { $$.initSubchartBrush(); }
/*-- Main Region --*/
@ -281,19 +279,12 @@ c3_chart_internal_fn.initWithData = function (data) {
// Cover whole with rects for events
$$.initEventRect();
// TODO: fix
$$.main.select('.' + CLASS.eventRect).call($$.zoom).on("dblclick.zoom", null);
// Define g for chart
$$.initChartElements();
// if zoom privileged, insert rect to forefront
// TODO: is this needed?
main.insert('rect', config.zoom_privileged ? null : 'g.' + CLASS.regions)
.attr('class', CLASS.zoomRect)
.attr('width', $$.width)
.attr('height', $$.height)
.style('opacity', 0)
.on("dblclick.zoom", null);
// Set default extent if defined
if (config.axis_x_extent) { $$.brush.extent($$.getDefaultExtent()); }
@ -604,8 +595,7 @@ c3_chart_internal_fn.redraw = function (options, transitions) {
// event rects will redrawn when flow called
if (config.interaction_enabled && !options.flow && withEventRect) {
$$.redrawEventRect();
if ($$.updateZoom) { $$.updateZoom(); }
$$.updateEventRect();
}
// update circleY based on updated parameters
@ -693,8 +683,8 @@ c3_chart_internal_fn.updateAndRedraw = function (options) {
options.withTransform = getOption(options, "withTransform", false);
options.withLegend = getOption(options, "withLegend", false);
// NOT same with redraw
options.withUpdateXDomain = true;
options.withUpdateOrgXDomain = true;
options.withUpdateXDomain = getOption(options, "withUpdateXDomain", true);
options.withUpdateOrgXDomain = getOption(options, "withUpdateOrgXDomain", true);
options.withTransitionForExit = false;
options.withTransitionForTransform = getOption(options, "withTransitionForTransform", options.withTransition);
// MEMO: this needs to be called before updateLegend and it means this ALWAYS needs to be called)
@ -854,9 +844,6 @@ c3_chart_internal_fn.updateSvgSize = function () {
$$.svg.select('#' + $$.clipIdForSubchart).select('rect')
.attr('width', $$.width)
.attr('height', brush.size() ? brush.attr('height') : 0);
$$.svg.select('.' + CLASS.zoomRect)
.attr('width', $$.width)
.attr('height', $$.height);
// MEMO: parent div's height will be bigger than svg when <!DOCTYPE html>
$$.selectChart.style('max-height', $$.currentHeight + "px");
};
@ -929,7 +916,14 @@ c3_chart_internal_fn.bindResize = function () {
}
$$.resizeTimeout = window.setTimeout(function () {
delete $$.resizeTimeout;
$$.api.flush();
$$.updateAndRedraw({
withUpdateXDomain: false,
withUpdateOrgXDomain: false,
withTransition: false,
withTransitionForTransform: false,
withLegend: true,
});
if ($$.brush) { $$.brush.update(); }
}, 100);
});
}

6
src/data.js

@ -48,10 +48,12 @@ c3_chart_internal_fn.addXs = function (xs) {
});
};
c3_chart_internal_fn.hasMultipleX = function (xs) {
return this.d3.set(Object.keys(xs).map(function (id) { return xs[id]; })).size() > 1;
// return this.d3.set(Object.keys(xs).map(function (id) { return xs[id]; })).size() > 1;
return true;
};
c3_chart_internal_fn.isMultipleX = function () {
return notEmpty(this.config.data_xs) || !this.config.data_xSort || this.hasType('scatter');
// return notEmpty(this.config.data_xs) || !this.config.data_xSort || this.hasType('scatter');
return true;
};
c3_chart_internal_fn.addName = function (data) {
var $$ = this, name;

5
src/domain.js

@ -209,13 +209,12 @@ c3_chart_internal_fn.updateXDomain = function (targets, withUpdateXDomain, withU
if (withUpdateOrgXDomain) {
$$.x.domain(domain ? domain : $$.d3.extent($$.getXDomain(targets)));
$$.orgXDomain = $$.x.domain();
if (config.zoom_enabled) { $$.zoom.scale($$.x).updateScaleExtent(); }
if (config.zoom_enabled) { $$.zoom.update(); }
$$.subX.domain($$.x.domain());
if ($$.brush) { $$.brush.scale($$.subX); }
if ($$.brush) { $$.brush.updateScale($$.subX); }
}
if (withUpdateXDomain) {
$$.x.domain(domain ? domain : (!$$.brush || $$.brush.empty()) ? $$.orgXDomain : $$.brush.selectionAsValue());
if (config.zoom_enabled) { $$.zoom.scale($$.x).updateScaleExtent(); }
}
// Trim domain when too big by zoom mousemove event

3
src/drag.js

@ -7,8 +7,7 @@ c3_chart_internal_fn.drag = function (mouse) {
var sx, sy, mx, my, minX, maxX, minY, maxY;
if ($$.hasArcType()) { return; }
if (! config.data_selection_enabled) { return; } // do nothing if not selectable
if (config.zoom_enabled && ! $$.zoom.altDomain) { return; } // skip if zoomable because of conflict drag dehavior
if (!config.data_selection_enabled) { return; } // do nothing if not selectable
if (!config.data_selection_multiple) { return; } // skip when single selection because drag is used for multiple selection
sx = $$.dragStart[0];

44
src/interaction.js

@ -6,10 +6,11 @@ c3_chart_internal_fn.initEventRect = function () {
$$.main.select('.' + CLASS.chart).append("g")
.attr("class", CLASS.eventRects)
.style('fill-opacity', 0);
$$.redrawEventRect();
};
c3_chart_internal_fn.redrawEventRect = function () {
var $$ = this, config = $$.config,
eventRectEnter, eventRectUpdate, maxDataCountTarget,
eventRect, eventRectEnter, maxDataCountTarget,
isMultipleX = $$.isMultipleX();
// rects for mouseover
@ -18,28 +19,30 @@ c3_chart_internal_fn.redrawEventRect = function () {
.classed(CLASS.eventRectsMultiple, isMultipleX)
.classed(CLASS.eventRectsSingle, !isMultipleX);
// clear old rects
eventRects.selectAll('.' + CLASS.eventRect).remove();
if (isMultipleX) {
$$.eventRect = eventRects.selectAll('.' + CLASS.eventRect).data([0]);
eventRect = eventRects.selectAll('.' + CLASS.eventRect).data([0]);
// enter : only one rect will be added
eventRectEnter = $$.generateEventRectsForMultipleXs($$.eventRect.enter());
eventRectEnter = $$.generateEventRectsForMultipleXs(eventRect.enter());
// update
$$.updateEventRect(eventRectEnter.merge($$.eventRect));
$$.eventRect = eventRectEnter.merge(eventRect);
$$.updateEventRect();
// exit : not needed because always only one rect exists
}
// DUPLICATED: this will be removed because it seems to be unable to work with zoom...
else {
// clear old rects
eventRects.selectAll('.' + CLASS.eventRect).remove();
// Set data and update $$.eventRect
maxDataCountTarget = $$.getMaxDataCountTarget($$.data.targets);
eventRects.datum(maxDataCountTarget ? maxDataCountTarget.values : []);
$$.eventRect = eventRects.selectAll('.' + CLASS.eventRect).data(function (d) { return d; });
eventRect.datum(maxDataCountTarget ? maxDataCountTarget.values : []);
eventRect = eventRects.selectAll('.' + CLASS.eventRect).data(function (d) { return d; });
// enter
eventRectEnter = $$.generateEventRectsForSingleX($$.eventRect.enter());
eventRectEnter = $$.generateEventRectsForSingleX(eventRect.enter());
// update
$$.updateEventRect(eventRectEnter.merge($$.eventRect));
$$.eventRect = eventRectEnter.merge(eventRect);
$$.updateEventRect();
// exit
$$.eventRect.exit().remove();
eventRect.exit().remove();
}
};
c3_chart_internal_fn.updateEventRect = function (eventRectUpdate) {
@ -47,7 +50,7 @@ c3_chart_internal_fn.updateEventRect = function (eventRectUpdate) {
x, y, w, h, rectW, rectX;
// set update selection if null
eventRectUpdate = eventRectUpdate || $$.eventRect.data(function (d) { return d; });
eventRectUpdate = eventRectUpdate || $$.eventRect;
if ($$.isMultipleX()) {
// TODO: rotated not supported yet
@ -56,6 +59,7 @@ c3_chart_internal_fn.updateEventRect = function (eventRectUpdate) {
w = $$.width;
h = $$.height;
}
// DUPLICATED: this will be removed because it seems to be unable to work with zoom...
else {
if (($$.isCustomX() || $$.isTimeSeries()) && !$$.isCategorized()) {
@ -107,6 +111,7 @@ c3_chart_internal_fn.updateEventRect = function (eventRectUpdate) {
.attr("width", w)
.attr("height", h);
};
// DUPLICATED: this will be removed because it seems to be unable to work with zoom...
c3_chart_internal_fn.generateEventRectsForSingleX = function (eventRectEnter) {
var $$ = this, d3 = $$.d3, config = $$.config;
return eventRectEnter.append("rect")
@ -215,14 +220,13 @@ c3_chart_internal_fn.generateEventRectsForSingleX = function (eventRectEnter) {
})
.call(
config.data_selection_draggable && $$.drag ? (
d3.behavior.drag().origin(Object)
d3.drag()
.on('drag', function () { $$.drag(d3.mouse(this)); })
.on('dragstart', function () { $$.dragstart(d3.mouse(this)); })
.on('dragend', function () { $$.dragend(); })
.on('start', function () { $$.dragstart(d3.mouse(this)); })
.on('end', function () { $$.dragend(); })
) : function () {}
);
};
c3_chart_internal_fn.generateEventRectsForMultipleXs = function (eventRectEnter) {
var $$ = this, d3 = $$.d3, config = $$.config;
@ -315,10 +319,10 @@ c3_chart_internal_fn.generateEventRectsForMultipleXs = function (eventRectEnter)
})
.call(
config.data_selection_draggable && $$.drag ? (
d3.behavior.drag().origin(Object)
d3.drag()
.on('drag', function () { $$.drag(d3.mouse(this)); })
.on('dragstart', function () { $$.dragstart(d3.mouse(this)); })
.on('dragend', function () { $$.dragend(); })
.on('start', function () { $$.dragstart(d3.mouse(this)); })
.on('end', function () { $$.dragend(); })
) : function () {}
);
};

3
src/scale.js

@ -83,8 +83,7 @@ c3_chart_internal_fn.updateScales = function () {
// Set initialized scales to brush and zoom
if (!forInit) {
if ($$.brush) { $$.brush.scale($$.subX); }
if (config.zoom_enabled) { $$.zoom.scale($$.x); }
if ($$.brush) { $$.brush.updateScale($$.subX); }
}
// update for arc
if ($$.updateArc) { $$.updateArc(); }

67
src/subchart.js

@ -2,48 +2,66 @@ import CLASS from './class';
import { c3_chart_internal_fn } from './core';
import { isFunction } from './util';
c3_chart_internal_fn.initBrush = function () {
var $$ = this, d3 = $$.d3, currentScale;
c3_chart_internal_fn.initBrush = function (scale) {
var $$ = this, d3 = $$.d3;
// TODO: dynamically change brushY/brushX according to axis_rotated.
$$.brush = ($$.config.axis_rotated ? d3.brushY() : d3.brushX()).on("brush", function () {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") { return; }
// TODO: fix
var s = d3.event.selection || $$.brush.scale.range();
$$.main.select('.' + CLASS.eventRect).call($$.zoom.transform, d3.zoomIdentity
.scale($$.width / (s[1] - s[0]))
.translate(-s[0], 0));
$$.redrawForBrush();
});
$$.brush.update = function () {
var brush;
if ($$.context) {
$$.context.select('.' + CLASS.brush).call(this);
}
return this;
};
$$.brush.scale = function (scale) {
currentScale = scale;
var range = scale.range(),
extent;
$$.brush.updateExtent = function () {
var range = this.scale.range(), extent;
if ($$.config.axis_rotated) {
extent = [[0, range[0]], [$$.width2, range[1]]];
}
else {
extent = [[range[0], 0], [range[1], $$.height2]];
}
$$.brush.extent(extent).update();
this.extent(extent);
return this;
};
$$.brush.updateScale = function (scale) {
this.scale = scale;
return this;
};
$$.brush.update = function (scale) {
this.updateScale(scale || $$.subX).updateExtent();
$$.context.select('.' + CLASS.brush).call(this);
};
$$.brush.selection = function () {
return d3.brushSelection($$.context.select('.' + CLASS.brush).node());
};
$$.brush.selectionAsValue = function () {
var selection = $$.brush.selection() || [0,0];
return [currentScale.invert(selection[0]), currentScale.invert(selection[1])];
$$.brush.selectionAsValue = function (selectionAsValue) {
var selection;
if (selectionAsValue) {
if ($$.context) {
selection = [this.scale(selectionAsValue[0]), this.scale(selectionAsValue[1])];
$$.brush.move($$.context.select('.' + CLASS.brush), selection);
}
return [];
}
selection = $$.brush.selection() || [0,0];
return [this.scale.invert(selection[0]), this.scale.invert(selection[1])];
};
$$.brush.empty = function () {
var selection = $$.brush.selection();
return !selection || selection[0] === selection[1];
};
return $$.brush.updateScale(scale);
};
c3_chart_internal_fn.initSubchart = function () {
var $$ = this, config = $$.config,
context = $$.context = $$.svg.append("g").attr("transform", $$.getTranslate('context')),
visibility = config.subchart_show ? 'visible' : 'hidden';
// set style
context.style('visibility', visibility);
// Define g for chart area
@ -62,8 +80,7 @@ c3_chart_internal_fn.initSubchart = function () {
// Add extent rect for Brush
context.append("g")
.attr("clip-path", $$.clipPath)
.attr("class", CLASS.brush)
.call($$.brush);
.attr("class", CLASS.brush);
// ATTENTION: This must be called AFTER chart added
// Add Axis
@ -73,6 +90,12 @@ c3_chart_internal_fn.initSubchart = function () {
.attr("clip-path", config.axis_rotated ? "" : $$.clipPathForXAxis)
.style("visibility", config.subchart_axis_x_show ? visibility : 'hidden');
};
c3_chart_internal_fn.initSubchartBrush = function () {
var $$ = this;
// Add extent rect for Brush
$$.initBrush($$.subX).updateExtent();
$$.context.select('.' + CLASS.brush).call($$.brush);
};
c3_chart_internal_fn.updateTargetsForSubchart = function (targets) {
var $$ = this, context = $$.context, config = $$.config,
contextLineEnter, contextLine, contextBarEnter, contextBar,
@ -180,14 +203,13 @@ c3_chart_internal_fn.redrawSubchart = function (withSubchart, transitions, durat
if (config.subchart_show) {
// reflect main chart to extent on subchart if zoomed
if (d3.event && d3.event.type === 'zoom') {
$$.brush.extent($$.x.orgDomain()).update();
$$.brush.selectionAsValue($$.x.orgDomain());
}
// update subchart elements if needed
if (withSubchart) {
// extent rect
if (!$$.brush.empty()) {
$$.brush.extent($$.x.orgDomain()).update();
$$.brush.selectionAsValue($$.x.orgDomain());
}
// setup drawer - MEMO: this must be called after axis updated
drawAreaOnSub = $$.generateDrawArea(areaIndices, true);
@ -211,6 +233,7 @@ c3_chart_internal_fn.redrawForBrush = function () {
withY: $$.config.zoom_rescale,
withSubchart: false,
withUpdateXDomain: true,
withEventRect: false,
withDimension: false
});
$$.config.subchart_onbrush.call($$.api, x.orgDomain());

54
src/zoom.js

@ -7,36 +7,43 @@ c3_chart_internal_fn.initZoom = function () {
$$.zoom = d3.zoom()
.on("start", function () {
startEvent = d3.event.sourceEvent;
$$.zoom.altDomain = d3.event.sourceEvent.altKey ? $$.x.orgDomain() : null;
config.zoom_onzoomstart.call($$.api, d3.event.sourceEvent);
var e = d3.event.sourceEvent;
if (e && e.type === "brush") { return; }
startEvent = e;
config.zoom_onzoomstart.call($$.api, e);
})
.on("zoom", function () {
var e = d3.event.sourceEvent;
if (e && e.type === "brush") { return; }
$$.redrawForZoom.call($$);
})
.on('end', function () {
var event = d3.event.sourceEvent;
var e = d3.event.sourceEvent;
if (e && e.type === "brush") { return; }
// if click, do nothing. otherwise, click interaction will be canceled.
if (event && startEvent.clientX === event.clientX && startEvent.clientY === event.clientY) {
if (e && startEvent.clientX === e.clientX && startEvent.clientY === e.clientY) {
return;
}
$$.redrawEventRect();
$$.updateZoom();
config.zoom_onzoomend.call($$.api, $$.x.orgDomain());
});
$$.zoom.scale = function (scale) {
return config.axis_rotated ? this.y(scale) : this.x(scale);
};
$$.zoom.orgScaleExtent = function () {
var extent = config.zoom_extent ? config.zoom_extent : [1, 10];
return [extent[0], Math.max($$.getMaxDataCount() / extent[1], extent[1])];
$$.zoom.updateDomain = function () {
if (d3.event && d3.event.transform) {
$$.x.domain(d3.event.transform.rescaleX($$.subX).domain());
}
return this;
};
$$.zoom.updateScaleExtent = function () {
var ratio = diffDomain($$.x.orgDomain()) / diffDomain($$.getZoomDomain()),
extent = this.orgScaleExtent();
this.scaleExtent([extent[0] * ratio, extent[1] * ratio]);
$$.zoom.updateExtent = function () {
this.scaleExtent([1, Infinity])
.translateExtent([[0, 0], [$$.width, $$.height]])
.extent([[0, 0], [$$.width, $$.height]]);
return this;
};
$$.zoom.update = function () {
return this.updateExtent().updateDomain();
};
return $$.zoom.updateExtent();
};
c3_chart_internal_fn.getZoomDomain = function () {
var $$ = this, config = $$.config, d3 = $$.d3,
@ -44,11 +51,6 @@ c3_chart_internal_fn.getZoomDomain = function () {
max = d3.max([$$.orgXDomain[1], config.zoom_x_max]);
return [min, max];
};
c3_chart_internal_fn.updateZoom = function () {
var $$ = this, z = $$.config.zoom_enabled ? $$.zoom : function () {};
$$.main.select('.' + CLASS.zoomRect).call(z).on("dblclick.zoom", null);
$$.main.selectAll('.' + CLASS.eventRect).call(z).on("dblclick.zoom", null);
};
c3_chart_internal_fn.redrawForZoom = function () {
var $$ = this, d3 = $$.d3, config = $$.config, zoom = $$.zoom, x = $$.x;
if (!config.zoom_enabled) {
@ -57,11 +59,9 @@ c3_chart_internal_fn.redrawForZoom = function () {
if ($$.filterTargetsToShow($$.data.targets).length === 0) {
return;
}
if (d3.event.sourceEvent.type === 'mousemove' && zoom.altDomain) {
x.domain(zoom.altDomain);
zoom.scale(x).updateScaleExtent();
return;
}
zoom.update();
if ($$.isCategorized() && x.orgDomain()[0] === $$.orgXDomain[0]) {
x.domain([$$.orgXDomain[0] - 1e-10, x.orgDomain()[1]]);
}

Loading…
Cancel
Save